mlt-7.22.0/000775 000000 000000 00000000000 14531534050 012356 5ustar00rootroot000000 000000 mlt-7.22.0/.clang-format000664 000000 000000 00000006265 14531534050 014742 0ustar00rootroot000000 000000 # .clang-format for MLT # # Adapted from https://github.com/qt-creator/qt-creator/blob/master/.clang-format # # The configuration below follows the Qt Creator Coding Rules [1] as closely as # possible. For documentation of the options, see [2]. # # # [1] https://doc-snapshots.qt.io/qtcreator-extending/coding-style.html # [2] https://clang.llvm.org/docs/ClangFormatStyleOptions.html # --- AccessModifierOffset: -4 AlignAfterOpenBracket: Align AlignConsecutiveAssignments: false AlignConsecutiveDeclarations: false AlignEscapedNewlines: DontAlign AlignOperands: true AlignTrailingComments: true AllowAllParametersOfDeclarationOnNextLine: true AllowShortBlocksOnASingleLine: Never AllowShortCaseLabelsOnASingleLine: false AllowShortFunctionsOnASingleLine: Inline AllowShortIfStatementsOnASingleLine: false AllowShortLoopsOnASingleLine: false AlwaysBreakAfterReturnType: None AlwaysBreakBeforeMultilineStrings: false AlwaysBreakTemplateDeclarations: Yes BinPackArguments: false BinPackParameters: false BraceWrapping: AfterClass: true AfterControlStatement: Never AfterEnum: false AfterFunction: true AfterNamespace: false AfterObjCDeclaration: false AfterStruct: true AfterUnion: false BeforeCatch: false BeforeElse: false IndentBraces: false SplitEmptyFunction: false SplitEmptyRecord: false SplitEmptyNamespace: false BreakBeforeBinaryOperators: All BreakBeforeBraces: Custom BreakBeforeInheritanceComma: false BreakBeforeTernaryOperators: true BreakConstructorInitializersBeforeComma: false BreakConstructorInitializers: BeforeComma BreakAfterJavaFieldAnnotations: false BreakStringLiterals: true ColumnLimit: 100 CommentPragmas: '^ IWYU pragma:' CompactNamespaces: false ConstructorInitializerAllOnOneLineOrOnePerLine: false ConstructorInitializerIndentWidth: 4 ContinuationIndentWidth: 4 Cpp11BracedListStyle: true DerivePointerAlignment: false DisableFormat: false ExperimentalAutoDetectBinPacking: false FixNamespaceComments: true ForEachMacros: - forever # avoids { wrapped to next line - foreach - Q_FOREACH - BOOST_FOREACH IncludeCategories: - Regex: '^> /etc/apt/sources.list - apt-get -qq update - apt-get -yqq build-dep mlt - apt-get -yqq install cmake - cmake -DCMAKE_BUILD_TYPE=Debug . && make -j -f Makefile install debian-testing: image: debian:testing script: - echo 'deb-src http://deb.debian.org/debian testing main' >> /etc/apt/sources.list - apt-get -qq update - apt-get -yqq build-dep mlt - apt-get -yqq install cmake - cmake -DCMAKE_BUILD_TYPE=Debug . && make -j -f Makefile install debian-stable: image: debian:stable script: - echo -e 'deb-src http://deb.debian.org/debian stable main\ndeb-src http://deb.debian.org/debian stable-updates main' >> /etc/apt/sources.list - echo 'deb http://deb.debian.org/debian buster-backports main' >> /etc/apt/sources.list - apt-get -qq update - apt-get -yqq build-dep mlt - apt-get -yqq install cmake - cmake -DCMAKE_BUILD_TYPE=Debug . && make -j -f Makefile install fedora-38: image: fedora:38 script: - yum --assumeyes groupinstall "Development Tools" - yum --assumeyes install yasm gavl-devel libsamplerate-devel libxml2-devel ladspa-devel jack-audio-connection-kit-devel sox-devel SDL-devel gtk2-devel qt-devel libexif-devel libtheora-devel libvorbis-devel libvdpau-devel libsoup-devel liboil-devel python-devel alsa-lib pulseaudio-libs-devel gcc-c++ cmake # unclear why `gcc-c++` isn't in `Development Tools` - cmake -DCMAKE_BUILD_TYPE=Debug . && make -j -f Makefile install fedora-36: image: fedora:36 script: - yum --assumeyes groupinstall "Development Tools" - yum --assumeyes install yasm gavl-devel libsamplerate-devel libxml2-devel ladspa-devel jack-audio-connection-kit-devel sox-devel SDL-devel gtk2-devel qt-devel libexif-devel libtheora-devel libvorbis-devel libvdpau-devel libsoup-devel liboil-devel python-devel alsa-lib pulseaudio-libs-devel gcc-c++ cmake # unclear why `gcc-c++` isn't in `Development Tools` - cmake -DCMAKE_BUILD_TYPE=Debug . && make -j -f Makefile install fedora-34: image: fedora:34 script: - yum --assumeyes groupinstall "Development Tools" - yum --assumeyes install yasm gavl-devel libsamplerate-devel libxml2-devel ladspa-devel jack-audio-connection-kit-devel sox-devel SDL-devel gtk2-devel qt-devel libexif-devel libtheora-devel libvorbis-devel libvdpau-devel libsoup-devel liboil-devel python-devel alsa-lib pulseaudio-libs-devel gcc-c++ cmake # unclear why `gcc-c++` isn't in `Development Tools` - cmake -DCMAKE_BUILD_TYPE=Debug . && make -j -f Makefile install fedora-30: image: fedora:32 script: - yum --assumeyes groupinstall "Development Tools" - yum --assumeyes install yasm gavl-devel libsamplerate-devel libxml2-devel ladspa-devel jack-audio-connection-kit-devel sox-devel SDL-devel gtk2-devel qt-devel libexif-devel libtheora-devel libvorbis-devel libvdpau-devel libsoup-devel liboil-devel python-devel alsa-lib pulseaudio-libs-devel gcc-c++ cmake # unclear why `gcc-c++` isn't in `Development Tools` - cmake -DCMAKE_BUILD_TYPE=Debug . && make -j -f Makefile install mlt-7.22.0/.gitmodules000664 000000 000000 00000000233 14531534050 014531 0ustar00rootroot000000 000000 [submodule "src/modules/glaxnimate/glaxnimate"] path = src/modules/glaxnimate/glaxnimate url = https://gitlab.com/mattbas/glaxnimate.git ignore = dirty mlt-7.22.0/AUTHORS000664 000000 000000 00000000646 14531534050 013434 0ustar00rootroot000000 000000 Charles Yates Dan Dennedy Stephane Fillod (effectv) Marco Gittler (frei0r, oldfilm, qimage/kdenlivetitle) Jean-Baptiste Mardelle (kdenlive, qimage) Zachary Drew (motion_est) Maksym Veremeyenko Brian Matherly Janne Liljeblad Steinar H. Gunderson mlt-7.22.0/CMakeLists.txt000664 000000 000000 00000047347 14531534050 015135 0ustar00rootroot000000 000000 cmake_minimum_required(VERSION 3.14) project(MLT VERSION 7.22.0 DESCRIPTION "Multimedia Framework" HOMEPAGE_URL "https://www.mltframework.org" LANGUAGES C CXX ) option(GPL "Enable GPLv2 components" ON) option(GPL3 "Enable GPLv3 components" ON) option(BUILD_TESTING "Enable tests" OFF) option(BUILD_DOCS "Enable Doxygen documentation" OFF) option(CLANG_FORMAT "Enable Clang Format" ON) option(BUILD_TESTS_WITH_QT6 "Build test against Qt 6" OFF) option(MOD_AVFORMAT "Enable avformat module" ON) option(MOD_DECKLINK "Enable DeckLink module" ON) option(MOD_FREI0R "Enable Frei0r module" ON) option(MOD_GDK "Enable GDK module" ON) option(MOD_GLAXNIMATE "Enable Glaxnimate module (Qt5)" OFF) option(MOD_GLAXNIMATE_QT6 "Enable Glaxnimate module (Qt6)" OFF) option(MOD_JACKRACK "Enable JACK Rack module" ON) option(MOD_KDENLIVE "Enable Kdenlive module" ON) option(MOD_NDI "Enable NDI module" OFF) option(MOD_NORMALIZE "Enable Normalize module (GPL)" ON) option(MOD_OLDFILM "Enable Oldfilm module" ON) option(MOD_OPENCV "Enable OpenCV module" OFF) option(MOD_MOVIT "Enable OpenGL module" ON) option(MOD_PLUS "Enable Plus module" ON) option(MOD_PLUSGPL "Enable PlusGPL module (GPL)" ON) option(MOD_QT "Enable Qt5 module (GPL)" ON) option(MOD_QT6 "Enable Qt6 module (GPL)" OFF) option(MOD_RESAMPLE "Enable Resample module (GPL)" ON) option(MOD_RTAUDIO "Enable RtAudio module" ON) option(MOD_RUBBERBAND "Enable Rubberband module (GPL)" ON) option(MOD_SDL1 "Enable SDL1 module" ON) option(MOD_SDL2 "Enable SDL2 module" ON) option(MOD_SOX "Enable SoX module" ON) option(MOD_VIDSTAB "Enable vid.stab module (GPL)" ON) option(MOD_VORBIS "Enable Vorbis module" ON) option(MOD_XINE "Enable XINE module (GPL)" ON) option(MOD_XML "Enable XML module" ON) option(SWIG_CSHARP "Enable SWIG C# bindings" OFF) option(SWIG_JAVA "Enable SWIG Java bindings" OFF) option(SWIG_LUA "Enable SWIG Lua bindings" OFF) option(SWIG_NODEJS "Enable SWIG Node.js bindings" OFF) option(SWIG_PERL "Enable SWIG Perl bindings" OFF) option(SWIG_PHP "Enable SWIG PHP bindings" OFF) option(SWIG_PYTHON "Enable SWIG Python bindings" OFF) option(SWIG_RUBY "Enable SWIG Ruby bindings" OFF) option(SWIG_TCL "Enable SWIG Tcl bindings" OFF) if(WIN32) option(WINDOWS_DEPLOY "Install exes/libs directly to prefix (no subdir /bin)" ON) else() if(APPLE) option(RELOCATABLE "Use standard app bundle layout" ON) else() option(RELOCATABLE "Look for plugins relative to app" OFF) endif() endif() list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") include(FeatureSummary) include(GNUInstallDirs) if(WINDOWS_DEPLOY) set(CMAKE_INSTALL_BINDIR .) endif() set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/out/${CMAKE_INSTALL_BINDIR}") set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/out/lib") set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/out/lib") set(MLT_MODULE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/out/lib/mlt") set(MLT_DATA_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/out/share/mlt") # https://gitlab.kitware.com/cmake/community/-/wikis/doc/cmake/RPATH-handling set(CMAKE_SKIP_BUILD_RPATH FALSE) set(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE) set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib") set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) list(FIND CMAKE_PLATFORM_IMPLICIT_LINK_DIRECTORIES "${CMAKE_INSTALL_PREFIX}/lib" isSystemDir) if(NOT "${isSystemDir}" STREQUAL "-1") set(CMAKE_INSTALL_RPATH "") endif() if(NOT EXISTS ${MLT_DATA_OUTPUT_DIRECTORY}) if(WIN32) # symlinks require admin rights on Windows file(COPY "${CMAKE_SOURCE_DIR}/src/modules" DESTINATION "${CMAKE_BINARY_DIR}/out/share" FILES_MATCHING REGEX yml|txt) file(RENAME "${CMAKE_BINARY_DIR}/out/share/modules" "${MLT_DATA_OUTPUT_DIRECTORY}") file(COPY "${CMAKE_SOURCE_DIR}/presets" DESTINATION "${MLT_DATA_OUTPUT_DIRECTORY}") file(COPY "${CMAKE_SOURCE_DIR}/profiles" DESTINATION "${MLT_DATA_OUTPUT_DIRECTORY}") else() file(MAKE_DIRECTORY "${MLT_DATA_OUTPUT_DIRECTORY}") file(GLOB MOD_SUBDIRS "${CMAKE_SOURCE_DIR}/src/modules/*") foreach(MOD_SUBDIR ${MOD_SUBDIRS}) file(RELATIVE_PATH MOD_NAME "${CMAKE_SOURCE_DIR}/src/modules" ${MOD_SUBDIR}) file(CREATE_LINK "${CMAKE_SOURCE_DIR}/src/modules/${MOD_NAME}" "${MLT_DATA_OUTPUT_DIRECTORY}/${MOD_NAME}" SYMBOLIC) endforeach() file(CREATE_LINK "${CMAKE_SOURCE_DIR}/presets" "${MLT_DATA_OUTPUT_DIRECTORY}/presets" SYMBOLIC) file(CREATE_LINK "${CMAKE_SOURCE_DIR}/profiles" "${MLT_DATA_OUTPUT_DIRECTORY}/profiles" SYMBOLIC) endif() endif() set(MLT_SUBDIR mlt) if(NOT (WIN32 OR APPLE)) set(MLT_SUBDIR mlt-${MLT_VERSION_MAJOR}) set(MLT_SUBDIR mlt-${MLT_VERSION_MAJOR}) endif() set(MLT_INSTALL_MODULE_DIR ${CMAKE_INSTALL_LIBDIR}/${MLT_SUBDIR}) set(MLT_INSTALL_DATA_DIR ${CMAKE_INSTALL_DATADIR}/${MLT_SUBDIR}) set(CMAKE_C_STANDARD 11) set(CMAKE_C_STANDARD_REQUIRED ON) set(CMAKE_C_EXTENSIONS ON) set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) list(APPEND MLT_COMPILE_OPTIONS "") # MSVC cl doesn't support GNU inline assembly (but MSVC-compatible clang-cl does) if(CMAKE_C_COMPILER_ID MATCHES "GNU|Clang") if(CMAKE_SYSTEM_PROCESSOR MATCHES "i686|x86|x86_64|AMD64") set(CPU_MMX ON) set(CPU_SSE ON) set(CPU_SSE2 ON) if(NOT MSVC) # also NOT clang-cl list(APPEND MLT_COMPILE_OPTIONS "-mmmx;-msse;-msse2") endif() endif() if(CMAKE_SYSTEM_PROCESSOR MATCHES "i686" OR (WIN32 AND CMAKE_SYSTEM_PROCESSOR MATCHES "x86")) set(CPU_X86_32 ON) endif() if(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|AMD64") set(CPU_X86_64 ON) endif() endif() if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_BUILD_TYPE STREQUAL "Debug") # Treat warnings as errors with some exceptions set(GCC_FLAGS "-Wall -Werror -Wno-deprecated-declarations") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${GCC_FLAGS} -Wno-class-memaccess -Wno-array-compare -Wno-unused-result -Wno-maybe-uninitialized") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${GCC_FLAGS} -Wno-discarded-qualifiers") endif () if(MSVC) list(APPEND MLT_COMPILE_OPTIONS "$<$:/fp:fast>") elseif(CMAKE_C_COMPILER_ID MATCHES "GNU|Clang") list(APPEND MLT_COMPILE_OPTIONS "$<$:-ffast-math>") endif() if(NOT GPL) set(MOD_NORMALIZE OFF) set(MOD_PLUSGPL OFF) set(MOD_QT OFF) set(MOD_QT6 OFF) set(MOD_RESAMPLE OFF) set(MOD_RUBBERBAND OFF) set(MOD_VIDSTAB OFF) set(MOD_XINE OFF) endif() find_package(Threads REQUIRED) find_package(PkgConfig REQUIRED) if(WIN32) find_package(Iconv REQUIRED) if(NOT CMAKE_DL_LIBS) find_package(dlfcn-win32 REQUIRED) set(CMAKE_DL_LIBS dlfcn-win32::dl) endif() endif() pkg_check_modules(sdl2 IMPORTED_TARGET sdl2) if(MOD_QT OR MOD_QT6 OR MOD_PLUS) pkg_check_modules(FFTW IMPORTED_TARGET fftw3) if(NOT FFTW_FOUND) pkg_check_modules(FFTW IMPORTED_TARGET fftw) endif() endif() if(MOD_QT OR MOD_QT6 OR MOD_GDK) pkg_check_modules(libexif IMPORTED_TARGET libexif) endif() if(MOD_GDK OR MOD_GTK) pkg_check_modules(fontconfig IMPORTED_TARGET fontconfig) endif() if(MOD_XML OR MOD_JACKRACK) pkg_check_modules(xml IMPORTED_TARGET libxml-2.0) endif() if(MOD_AVFORMAT) pkg_check_modules(libavformat IMPORTED_TARGET libavformat) pkg_check_modules(libswscale IMPORTED_TARGET libswscale) pkg_check_modules(libavutil IMPORTED_TARGET libavutil) if(TARGET PkgConfig::libavformat AND TARGET PkgConfig::libswscale AND TARGET PkgConfig::libavutil) pkg_check_modules(libavcodec IMPORTED_TARGET libavcodec) pkg_check_modules(libavfilter IMPORTED_TARGET libavfilter) pkg_check_modules(libavdevice IMPORTED_TARGET libavdevice) pkg_check_modules(libswresample IMPORTED_TARGET libswresample) list(APPEND MLT_SUPPORTED_COMPONENTS avformat) else() set(MOD_AVFORMAT OFF) endif() endif() if(MOD_DECKLINK) list(APPEND MLT_SUPPORTED_COMPONENTS decklink) endif() if(MOD_FREI0R) pkg_check_modules(FREI0R frei0r) if(FREI0R_FOUND) list(APPEND MLT_SUPPORTED_COMPONENTS frei0r) else() set(MOD_FREI0R OFF) endif() endif() if(MOD_GDK) pkg_check_modules(GdkPixbuf IMPORTED_TARGET gdk-pixbuf-2.0) if(TARGET PkgConfig::GdkPixbuf) pkg_check_modules(pango IMPORTED_TARGET pango) pkg_check_modules(pangoft2 IMPORTED_TARGET pangoft2) list(APPEND MLT_SUPPORTED_COMPONENTS gdk) else() set(MOD_GDK OFF) endif() endif() if(MOD_JACKRACK) find_package(JACK) pkg_check_modules(glib IMPORTED_TARGET glib-2.0) check_include_file(ladspa.h ladspa_h_FOUND) list(APPEND MLT_SUPPORTED_COMPONENTS jackrack) endif() if(MOD_KDENLIVE) list(APPEND MLT_SUPPORTED_COMPONENTS kdenlive) endif() if(MOD_NDI) find_package(NDI REQUIRED) list(APPEND MLT_SUPPORTED_COMPONENTS ndi) endif() if(MOD_NORMALIZE) list(APPEND MLT_SUPPORTED_COMPONENTS normalize) endif() if(MOD_OLDFILM) list(APPEND MLT_SUPPORTED_COMPONENTS oldfilm) endif() if(MOD_OPENCV) find_package(OpenCV REQUIRED COMPONENTS tracking) if(OpenCV_tracking_FOUND) list(APPEND MLT_SUPPORTED_COMPONENTS opencv) else() set(MOD_OPENCV OFF) endif() endif() if(MOD_MOVIT) pkg_check_modules(movit IMPORTED_TARGET movit) find_package(OpenGL) if(TARGET PkgConfig::movit AND TARGET OpenGL::GL) if(UNIX AND NOT APPLE) find_package(X11 REQUIRED) endif() list(APPEND MLT_SUPPORTED_COMPONENTS movit) else() set(MOD_MOVIT OFF) endif() endif() if(MOD_PLUS) pkg_check_modules(libebur128 IMPORTED_TARGET libebur128) list(APPEND MLT_SUPPORTED_COMPONENTS plus) endif() if(MOD_PLUSGPL) list(APPEND MLT_SUPPORTED_COMPONENTS plusgpl) endif() # It is necessary to look for Qt6 before Qt5, otherwise there will # be a conflict with the targets in case both are enabled if(MOD_QT6) find_package(Qt6 COMPONENTS Core Gui Xml SvgWidgets Core5Compat) if(Qt6_FOUND) list(APPEND MLT_SUPPORTED_COMPONENTS qt6) else() set(MOD_QT6 OFF) endif() endif() if(MOD_GLAXNIMATE_QT6) find_package(Qt6 COMPONENTS Core Gui Network Widgets Xml) if(Qt6_FOUND) list(APPEND MLT_SUPPORTED_COMPONENTS glaxnimate-qt6) else() set(MOD_GLAXNIMATE_QT6 OFF) endif() endif() if (BUILD_TESTS_WITH_QT6) set(QT_MAJOR_VERSION 6) else() set(QT_MAJOR_VERSION 5) endif() if(BUILD_TESTING) find_package(Qt${QT_MAJOR_VERSION} REQUIRED COMPONENTS Core Test) find_package(Kwalify REQUIRED) enable_testing() endif() if(MOD_QT) find_package(Qt5 COMPONENTS Core Xml Gui Svg Widgets) if(Qt5_FOUND) list(APPEND MLT_SUPPORTED_COMPONENTS qt) else() set(MOD_QT OFF) endif() endif() if(MOD_GLAXNIMATE) find_package(Qt5 COMPONENTS Core Gui Network Widgets Xml) if(Qt5_FOUND) list(APPEND MLT_SUPPORTED_COMPONENTS glaxnimate) else() set(MOD_GLAXNIMATE OFF) endif() endif() if(MOD_RESAMPLE) pkg_check_modules(samplerate IMPORTED_TARGET samplerate) if(TARGET PkgConfig::samplerate) list(APPEND MLT_SUPPORTED_COMPONENTS resample) else() set(MOD_RESAMPLE OFF) endif() endif() if(MOD_RTAUDIO) pkg_check_modules(rtaudio IMPORTED_TARGET rtaudio) if(NOT TARGET PkgConfig::rtaudio AND UNIX AND NOT APPLE) pkg_check_modules(alsa IMPORTED_TARGET alsa) pkg_check_modules(libpulse-simple IMPORTED_TARGET libpulse-simple) endif() list(APPEND MLT_SUPPORTED_COMPONENTS rtaudio) endif() if(MOD_RUBBERBAND) pkg_check_modules(rubberband IMPORTED_TARGET rubberband) if(TARGET PkgConfig::rubberband) list(APPEND MLT_SUPPORTED_COMPONENTS rubberband) else() set(MOD_RUBBERBAND OFF) endif() endif() if(MOD_SDL1) pkg_check_modules(sdl IMPORTED_TARGET sdl) if(TARGET PkgConfig::sdl) list(APPEND MLT_SUPPORTED_COMPONENTS sdl) else() set(MOD_SDL1 OFF) endif() endif() if(MOD_SDL2) if(TARGET PkgConfig::sdl2) list(APPEND MLT_SUPPORTED_COMPONENTS sdl2) else() set(MOD_SDL2 OFF) endif() endif() if(MOD_SOX) pkg_check_modules(sox IMPORTED_TARGET sox) if(TARGET PkgConfig::sox) list(APPEND MLT_SUPPORTED_COMPONENTS sox) else() set(MOD_SOX OFF) endif() endif() if(MOD_VIDSTAB) pkg_check_modules(vidstab IMPORTED_TARGET vidstab) if(TARGET PkgConfig::vidstab) list(APPEND MLT_SUPPORTED_COMPONENTS vidstab) else() set(MOD_VIDSTAB OFF) endif() endif() if(MOD_VORBIS) pkg_check_modules(vorbis IMPORTED_TARGET vorbis) pkg_check_modules(vorbisfile IMPORTED_TARGET vorbisfile) if(TARGET PkgConfig::vorbis AND TARGET PkgConfig::vorbisfile) list(APPEND MLT_SUPPORTED_COMPONENTS vorbis) else() set(MOD_VORBIS OFF) endif() endif() if(MOD_XINE) list(APPEND MLT_SUPPORTED_COMPONENTS xine) endif() if(MOD_XML) if(TARGET PkgConfig::xml) list(APPEND MLT_SUPPORTED_COMPONENTS xml) else() set(MOD_XML OFF) endif() endif() find_package(SWIG) if(SWIG_CSHARP) find_package(Mono REQUIRED) endif() if(SWIG_JAVA) find_package(JNI REQUIRED) endif() if(SWIG_LUA) find_package(Lua REQUIRED) endif() if(SWIG_NODEJS) find_package(Node REQUIRED) if(NODE_VERSION_MAJOR VERSION_GREATER_EQUAL 12 AND SWIG_VERSION VERSION_LESS 4.1) # https://github.com/swig/swig/issues/1520 set(SWIG_NODEJS OFF) endif() endif() if(SWIG_PERL) find_package(PerlLibs REQUIRED) endif() if(SWIG_PHP) find_package(PHP REQUIRED) endif() if(SWIG_PYTHON) find_package(Python3 REQUIRED COMPONENTS Interpreter Development) endif() if(SWIG_RUBY) find_package(Ruby REQUIRED) endif() if(SWIG_TCL) find_package(TCL REQUIRED) endif() if(BUILD_DOCS) find_package(Doxygen REQUIRED) set(DOXYGEN_OUTPUT_DIRECTORY "docs") set(DOXYGEN_ABBREVIATE_BRIEF "") set(DOXYGEN_STRIP_FROM_PATH "src/framework/") set(DOXYGEN_JAVADOC_AUTOBRIEF "YES") set(DOXYGEN_QT_AUTOBRIEF "YES") set(DOXYGEN_ALIASES "properties=\\xrefitem properties \\\"Property\\\" \\\"Properties Dictionary\\\"") list(APPEND DOXYGEN_ALIASES "event=\\xrefitem event \\\"Event\\\" \\\"Events Dictionary\\\"") list(APPEND DOXYGEN_ALIASES "envvar=\\xrefitem envvars \\\"Environment Variable\\\" \\\"Environment Variables\\\"") set(DOXYGEN_OPTIMIZE_OUTPUT_FOR_C "YES") set(DOXYGEN_SORT_BRIEF_DOCS "YES") set(DOXYGEN_EXTRACT_ALL "YES") set(DOXYGEN_EXTRACT_PRIVATE "YES") set(DOXYGEN_EXTRACT_STATIC "YES") set(DOXYGEN_FILE_PATTERNS "") set(DOXYGEN_RECURSIVE "NO") set(DOXYGEN_EXCLUDE_PATTERNS "") set(DOXYGEN_EXAMPLE_PATTERNS "") set(DOXYGEN_SEARCHENGINE "NO") doxygen_add_docs(docs src/framework) endif() if(CLANG_FORMAT) # Formatting may change with different versions of clang-format. # Test new versions before changing the allowed version here to avoid # accidental broad changes to formatting. find_package(ClangFormat 14 EXACT) if(CLANGFORMAT_FOUND) file(GLOB_RECURSE FORMAT_FILES "src/*.h" "src/*.c" "src/*.cpp") # exclude 3rd party source from format checking list(FILTER FORMAT_FILES EXCLUDE REGEX "/.*/glaxnimate/glaxnimate/") list(FILTER FORMAT_FILES EXCLUDE REGEX "/.*autogen") list(FILTER FORMAT_FILES EXCLUDE REGEX "/.*/avformat/mmx.h") list(FILTER FORMAT_FILES EXCLUDE REGEX "/.*/decklink/darwin") list(FILTER FORMAT_FILES EXCLUDE REGEX "/.*/decklink/linux") list(FILTER FORMAT_FILES EXCLUDE REGEX "/.*/decklink/win") list(FILTER FORMAT_FILES EXCLUDE REGEX "/.*/gdk/pixops") list(FILTER FORMAT_FILES EXCLUDE REGEX "/.*/jackrack/jack_rack.*") list(FILTER FORMAT_FILES EXCLUDE REGEX "/.*/jackrack/lock_free_fifo.*") list(FILTER FORMAT_FILES EXCLUDE REGEX "/.*/plugin_desc.*") list(FILTER FORMAT_FILES EXCLUDE REGEX "/.*/plugin_mgr.*") list(FILTER FORMAT_FILES EXCLUDE REGEX "/.*/plugin_settings.*") list(FILTER FORMAT_FILES EXCLUDE REGEX "/.*/plugin.*") list(FILTER FORMAT_FILES EXCLUDE REGEX "/.*/process.*") list(FILTER FORMAT_FILES EXCLUDE REGEX "/.*/plus/ebur128") list(FILTER FORMAT_FILES EXCLUDE REGEX "/.*/movit/optional_effect.h") list(FILTER FORMAT_FILES EXCLUDE REGEX "/.*/opencv/filter_opencv_tracker.cpp") list(FILTER FORMAT_FILES EXCLUDE REGEX "/.*/plusgpl/cJSON") list(FILTER FORMAT_FILES EXCLUDE REGEX "/.*/plusgpl/image.*") list(FILTER FORMAT_FILES EXCLUDE REGEX "/.*/plusgpl/utils.*") list(FILTER FORMAT_FILES EXCLUDE REGEX "/.*/rtaudio/RtAudio.*") list(FILTER FORMAT_FILES EXCLUDE REGEX "/.*/xine/attributes.h") list(FILTER FORMAT_FILES EXCLUDE REGEX "/.*/xine/cpu_accel.c") list(FILTER FORMAT_FILES EXCLUDE REGEX "/.*/xine/deinterlace.*") list(FILTER FORMAT_FILES EXCLUDE REGEX "/.*/xine/vf_yadif_template.h") list(FILTER FORMAT_FILES EXCLUDE REGEX "/.*/xine/xineutils.h") list(FILTER FORMAT_FILES EXCLUDE REGEX "/.*/xine/yadif.*") list(FILTER FORMAT_FILES EXCLUDE REGEX "/.*/win32") add_custom_target(clang-format COMMAND ${CLANGFORMAT_EXECUTABLE} -style=file -i ${FORMAT_FILES} ) add_custom_target(clang-format-check COMMAND ${CLANGFORMAT_EXECUTABLE} --dry-run --Werror -style=file -i ${FORMAT_FILES} ) else() set(CLANG_FORMAT OFF) endif() endif() install(DIRECTORY presets profiles DESTINATION ${MLT_INSTALL_DATA_DIR}) if(UNIX AND NOT APPLE) install(FILES docs/melt.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1 RENAME melt-${MLT_VERSION_MAJOR}.1) install(CODE "execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink melt-${MLT_VERSION_MAJOR}.1 melt.1 \ WORKING_DIRECTORY ${CMAKE_INSTALL_FULL_MANDIR}/man1)" ) endif() add_subdirectory(src) install(EXPORT MltTargets FILE Mlt${MLT_VERSION_MAJOR}Targets.cmake NAMESPACE Mlt${MLT_VERSION_MAJOR}:: DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/Mlt${MLT_VERSION_MAJOR} ) include(CMakePackageConfigHelpers) configure_package_config_file(${CMAKE_CURRENT_SOURCE_DIR}/MltConfig.cmake.in "${CMAKE_CURRENT_BINARY_DIR}/Mlt${MLT_VERSION_MAJOR}Config.cmake" INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/Mlt${MLT_VERSION_MAJOR} NO_CHECK_REQUIRED_COMPONENTS_MACRO ) write_basic_package_version_file( "${CMAKE_CURRENT_BINARY_DIR}/Mlt${MLT_VERSION_MAJOR}ConfigVersion.cmake" COMPATIBILITY SameMajorVersion ) install(FILES "${CMAKE_CURRENT_BINARY_DIR}/Mlt${MLT_VERSION_MAJOR}Config.cmake" "${CMAKE_CURRENT_BINARY_DIR}/Mlt${MLT_VERSION_MAJOR}ConfigVersion.cmake" DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/Mlt${MLT_VERSION_MAJOR} ) add_feature_info("GPLv2" GPL "") add_feature_info("GPLv3" GPL3 "") add_feature_info("Tests" BUILD_TESTING "") add_feature_info("Doxygen" BUILD_DOCS "") add_feature_info("Clang Format" CLANG_FORMAT "") add_feature_info("Module: avformat" MOD_AVFORMAT "") add_feature_info("Module: DeckLink" MOD_DECKLINK "") add_feature_info("Module: Frei0r" MOD_FREI0R "") add_feature_info("Module: GDK" MOD_GDK "") add_feature_info("Module: Glaxnimate (Qt5)" MOD_GLAXNIMATE "") add_feature_info("Module: Glaxnimate (Qt6)" MOD_GLAXNIMATE_QT6 "") add_feature_info("Module: JACKRack" MOD_JACKRACK "") add_feature_info("Module: Kdenlive" MOD_KDENLIVE "") add_feature_info("Module: NDI" MOD_NDI "") add_feature_info("Module: Normalize" MOD_NORMALIZE "") add_feature_info("Module: Oldfilm" MOD_OLDFILM "") add_feature_info("Module: OpenCV" MOD_OPENCV "") add_feature_info("Module: Movit" MOD_MOVIT "") add_feature_info("Module: Plus" MOD_PLUS "") add_feature_info("Module: PlusGPL" MOD_PLUSGPL "") add_feature_info("Module: Qt (Qt5)" MOD_QT "") add_feature_info("Module: Qt6" MOD_QT6 "") add_feature_info("Module: Resample" MOD_RESAMPLE "") add_feature_info("Module: RtAudio" MOD_RTAUDIO "") add_feature_info("Module: Rubberband" MOD_RUBBERBAND "") add_feature_info("Module: SDL1" MOD_SDL1 "") add_feature_info("Module: SDL2" MOD_SDL2 "") add_feature_info("Module: SoX" MOD_SOX "") add_feature_info("Module: vid.stab" MOD_VIDSTAB "") add_feature_info("Module: Vorbis" MOD_VORBIS "") add_feature_info("Module: XINE" MOD_XINE "") add_feature_info("Module: XML" MOD_XML "") add_feature_info("SWIG: C#" SWIG_CSHARP "") add_feature_info("SWIG: Java" SWIG_JAVA "") add_feature_info("SWIG: Lua" SWIG_LUA "") add_feature_info("SWIG: Node.js" SWIG_NODEJS "") add_feature_info("SWIG: Perl" SWIG_PERL "") add_feature_info("SWIG: PHP" SWIG_PHP "") add_feature_info("SWIG: Python" SWIG_PYTHON "") add_feature_info("SWIG: Ruby" SWIG_RUBY "") add_feature_info("SWIG: Tcl" SWIG_TCL "") feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES) mlt-7.22.0/COPYING000664 000000 000000 00000063631 14531534050 013422 0ustar00rootroot000000 000000 GNU LESSER GENERAL PUBLIC LICENSE Version 2.1, February 1999 Copyright (C) 1991, 1999 Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. GNU LESSER GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. , 1 April 1990 Ty Coon, President of Vice That's all there is to it! mlt-7.22.0/Dockerfile000664 000000 000000 00000004235 14531534050 014354 0ustar00rootroot000000 000000 FROM ubuntu:20.04 AS base ENV DEBIAN_FRONTEND noninteractive ENV HOME /tmp RUN apt-get update -qq && apt-get install -yqq apt-utils FROM base AS build # Install packages for building RUN apt-get install -yqq wget git automake autoconf libtool intltool g++ yasm nasm \ swig libgavl-dev libsamplerate0-dev libxml2-dev ladspa-sdk libjack-dev \ libsox-dev libsdl2-dev libgtk2.0-dev libsoup2.4-dev \ qt5-default libqt5webkit5-dev libqt5svg5-dev \ libexif-dev libtheora-dev libvorbis-dev python3-dev cmake xutils-dev \ libegl1-mesa-dev libeigen3-dev libfftw3-dev libvdpau-dev meson ninja-build # Get and run the build script RUN wget --quiet -O /tmp/build-melt.sh https://raw.githubusercontent.com/mltframework/mlt-scripts/master/build/build-melt.sh && \ echo "INSTALL_DIR=\"/usr/local\"" > /tmp/build-melt.conf && \ echo "SOURCE_DIR=\"/tmp/melt\"" >> /tmp/build-melt.conf && \ echo "AUTO_APPEND_DATE=0" >> /tmp/build-melt.conf && \ echo "FFMPEG_HEAD=0" >> /tmp/build-melt.conf && \ echo "FFMPEG_REVISION=origin/release/5.0" >> /tmp/build-melt.conf && \ bash /tmp/build-melt.sh -c /tmp/build-melt.conf FROM base # Install packages for running RUN apt-get install -yqq dumb-init \ libsamplerate0 libxml2 libjack0 \ libsdl2-2.0-0 libgtk2.0-0 libsoup2.4-1 \ libqt5core5a libqt5gui5 libqt5opengl5 libqt5svg5 libqt5widgets5 \ libqt5x11extras5 libqt5xml5 libqt5webkit5 \ libtheora0 libvorbis0a python3 \ libegl1-mesa libfftw3-3 libvdpau1 \ # Additional runtime libs \ libgavl1 libsox3 libexif12 xvfb libxkbcommon-x11-0 libhyphen0 libwebp6 \ # LADSPA plugins \ amb-plugins ambdec autotalent blepvco blop bs2b-ladspa caps cmt \ csladspa fil-plugins guitarix-ladspa invada-studio-plugins-ladspa mcp-plugins \ omins rev-plugins ste-plugins swh-plugins tap-plugins vco-plugins wah-plugins \ lsp-plugins-ladspa dpf-plugins-ladspa \ # Fonts \ fonts-liberation 'ttf-.+' # Install the build COPY --from=build /usr/local/ /usr/local/ WORKDIR /mnt ENV LD_LIBRARY_PATH /usr/local/lib # Qt, Movit, and WebVfx require xvfb-run, which requires a PID 1 init provided by dumb-init ENTRYPOINT ["/usr/bin/dumb-init", "--", "/usr/bin/xvfb-run", "-a", "/usr/local/bin/melt"] mlt-7.22.0/Doxyfile000664 000000 000000 00000166304 14531534050 014076 0ustar00rootroot000000 000000 # Doxyfile 1.5.7 # This file describes the settings to be used by the documentation system # doxygen (www.doxygen.org) for a project # # All text after a hash (#) is considered a comment and will be ignored # The format is: # TAG = value [value, ...] # For lists items can also be appended using: # TAG += value [value, ...] # Values that contain spaces should be placed between quotes (" ") #--------------------------------------------------------------------------- # Project related configuration options #--------------------------------------------------------------------------- # This tag specifies the encoding used for all characters in the config file # that follow. The default is UTF-8 which is also the encoding used for all # text before the first occurrence of this tag. Doxygen uses libiconv (or the # iconv built into libc) for the transcoding. See # http://www.gnu.org/software/libiconv for the list of possible encodings. DOXYFILE_ENCODING = UTF-8 # The PROJECT_NAME tag is a single word (or a sequence of words surrounded # by quotes) that should identify the project. PROJECT_NAME = MLT # The PROJECT_NUMBER tag can be used to enter a project or revision number. # This could be handy for archiving the generated documentation or # if some version control system is used. PROJECT_NUMBER = 7.22.0 # The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) # base path where the generated documentation will be put. # If a relative path is entered, it will be relative to the location # where doxygen was started. If left blank the current directory will be used. OUTPUT_DIRECTORY = docs # If the CREATE_SUBDIRS tag is set to YES, then doxygen will create # 4096 sub-directories (in 2 levels) under the output directory of each output # format and will distribute the generated files over these directories. # Enabling this option can be useful when feeding doxygen a huge amount of # source files, where putting all generated files in the same directory would # otherwise cause performance problems for the file system. CREATE_SUBDIRS = NO # The OUTPUT_LANGUAGE tag is used to specify the language in which all # documentation generated by doxygen is written. Doxygen will use this # information to generate all constant output in the proper language. # The default language is English, other supported languages are: # Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, # Croatian, Czech, Danish, Dutch, Farsi, Finnish, French, German, Greek, # Hungarian, Italian, Japanese, Japanese-en (Japanese with English messages), # Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, Polish, # Portuguese, Romanian, Russian, Serbian, Serbian-Cyrilic, Slovak, Slovene, # Spanish, Swedish, and Ukrainian. OUTPUT_LANGUAGE = English # If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will # include brief member descriptions after the members that are listed in # the file and class documentation (similar to JavaDoc). # Set to NO to disable this. BRIEF_MEMBER_DESC = YES # If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend # the brief description of a member or function before the detailed description. # Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the # brief descriptions will be completely suppressed. REPEAT_BRIEF = YES # This tag implements a quasi-intelligent brief description abbreviator # that is used to form the text in various listings. Each string # in this list, if found as the leading text of the brief description, will be # stripped from the text and the result after processing the whole list, is # used as the annotated text. Otherwise, the brief description is used as-is. # If left blank, the following values are used ("$name" is automatically # replaced with the name of the entity): "The $name class" "The $name widget" # "The $name file" "is" "provides" "specifies" "contains" # "represents" "a" "an" "the" ABBREVIATE_BRIEF = # If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then # Doxygen will generate a detailed section even if there is only a brief # description. ALWAYS_DETAILED_SEC = NO # If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all # inherited members of a class in the documentation of that class as if those # members were ordinary class members. Constructors, destructors and assignment # operators of the base classes will not be shown. INLINE_INHERITED_MEMB = NO # If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full # path before files name in the file list and in the header files. If set # to NO the shortest path that makes the file name unique will be used. FULL_PATH_NAMES = YES # If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag # can be used to strip a user-defined part of the path. Stripping is # only done if one of the specified strings matches the left-hand part of # the path. The tag can be used to show relative paths in the file list. # If left blank the directory from which doxygen is run is used as the # path to strip. STRIP_FROM_PATH = src/framework/ # The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of # the path mentioned in the documentation of a class, which tells # the reader which header file to include in order to use a class. # If left blank only the name of the header file containing the class # definition is used. Otherwise one should specify the include paths that # are normally passed to the compiler using the -I flag. STRIP_FROM_INC_PATH = # If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter # (but less readable) file names. This can be useful is your file systems # doesn't support long names like on DOS, Mac, or CD-ROM. SHORT_NAMES = NO # If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen # will interpret the first line (until the first dot) of a JavaDoc-style # comment as the brief description. If set to NO, the JavaDoc # comments will behave just like regular Qt-style comments # (thus requiring an explicit @brief command for a brief description.) JAVADOC_AUTOBRIEF = YES # If the QT_AUTOBRIEF tag is set to YES then Doxygen will # interpret the first line (until the first dot) of a Qt-style # comment as the brief description. If set to NO, the comments # will behave just like regular Qt-style comments (thus requiring # an explicit \brief command for a brief description.) QT_AUTOBRIEF = YES # The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen # treat a multi-line C++ special comment block (i.e. a block of //! or /// # comments) as a brief description. This used to be the default behaviour. # The new default is to treat a multi-line C++ comment block as a detailed # description. Set this tag to YES if you prefer the old behaviour instead. MULTILINE_CPP_IS_BRIEF = NO # If the INHERIT_DOCS tag is set to YES (the default) then an undocumented # member inherits the documentation from any documented member that it # re-implements. INHERIT_DOCS = YES # If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce # a new page for each member. If set to NO, the documentation of a member will # be part of the file/class/namespace that contains it. SEPARATE_MEMBER_PAGES = NO # The TAB_SIZE tag can be used to set the number of spaces in a tab. # Doxygen uses this value to replace tabs by spaces in code fragments. TAB_SIZE = 4 # This tag can be used to specify a number of aliases that acts # as commands in the documentation. An alias has the form "name=value". # For example adding "sideeffect=\par Side Effects:\n" will allow you to # put the command \sideeffect (or @sideeffect) in the documentation, which # will result in a user-defined paragraph with heading "Side Effects:". # You can put \n's in the value part of an alias to insert newlines. ALIASES = "properties=\xrefitem properties \"Property\" \"Properties Dictionary\"" ALIASES += "event=\xrefitem event \"Event\" \"Events Dictionary\"" ALIASES += "envvar=\xrefitem envvars \"Environment Variable\" \"Environment Variables\"" # Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C # sources only. Doxygen will then generate output that is more tailored for C. # For instance, some of the names that are used will be different. The list # of all members will be omitted, etc. OPTIMIZE_OUTPUT_FOR_C = YES # Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java # sources only. Doxygen will then generate output that is more tailored for # Java. For instance, namespaces will be presented as packages, qualified # scopes will look different, etc. OPTIMIZE_OUTPUT_JAVA = NO # Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran # sources only. Doxygen will then generate output that is more tailored for # Fortran. OPTIMIZE_FOR_FORTRAN = NO # Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL # sources. Doxygen will then generate output that is tailored for # VHDL. OPTIMIZE_OUTPUT_VHDL = NO # If you use STL classes (i.e. std::string, std::vector, etc.) but do not want # to include (a tag file for) the STL sources as input, then you should # set this tag to YES in order to let doxygen match functions declarations and # definitions whose arguments contain STL classes (e.g. func(std::string); v.s. # func(std::string) {}). This also make the inheritance and collaboration # diagrams that involve STL classes more complete and accurate. BUILTIN_STL_SUPPORT = NO # If you use Microsoft's C++/CLI language, you should set this option to YES to # enable parsing support. CPP_CLI_SUPPORT = NO # Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. # Doxygen will parse them like normal C++ but will assume all classes use public # instead of private inheritance when no explicit protection keyword is present. SIP_SUPPORT = NO # For Microsoft's IDL there are propget and propput attributes to indicate getter # and setter methods for a property. Setting this option to YES (the default) # will make doxygen to replace the get and set methods by a property in the # documentation. This will only work if the methods are indeed getting or # setting a simple type. If this is not the case, or you want to show the # methods anyway, you should set this option to NO. IDL_PROPERTY_SUPPORT = YES # If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC # tag is set to YES, then doxygen will reuse the documentation of the first # member in the group (if any) for the other members of the group. By default # all members of a group must be documented explicitly. DISTRIBUTE_GROUP_DOC = NO # Set the SUBGROUPING tag to YES (the default) to allow class member groups of # the same type (for instance a group of public functions) to be put as a # subgroup of that type (e.g. under the Public Functions section). Set it to # NO to prevent subgrouping. Alternatively, this can be done per class using # the \nosubgrouping command. SUBGROUPING = YES # When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum # is documented as struct, union, or enum with the name of the typedef. So # typedef struct TypeS {} TypeT, will appear in the documentation as a struct # with name TypeT. When disabled the typedef will appear as a member of a file, # namespace, or class. And the struct will be named TypeS. This can typically # be useful for C code in case the coding convention dictates that all compound # types are typedef'ed and only the typedef is referenced, never the tag name. TYPEDEF_HIDES_STRUCT = NO # The SYMBOL_CACHE_SIZE determines the size of the internal cache use to # determine which symbols to keep in memory and which to flush to disk. # When the cache is full, less often used symbols will be written to disk. # For small to medium size projects (<1000 input files) the default value is # probably good enough. For larger projects a too small cache size can cause # doxygen to be busy swapping symbols to and from disk most of the time # causing a significant performance penalty. # If the system has enough physical memory increasing the cache will improve the # performance by keeping more symbols in memory. Note that the value works on # a logarithmic scale so increasing the size by one will roughly double the # memory usage. The cache size is given by this formula: # 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0, # corresponding to a cache size of 2^16 = 65536 symbols SYMBOL_CACHE_SIZE = 0 #--------------------------------------------------------------------------- # Build related configuration options #--------------------------------------------------------------------------- # If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in # documentation are documented, even if no documentation was available. # Private class members and static file members will be hidden unless # the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES EXTRACT_ALL = YES # If the EXTRACT_PRIVATE tag is set to YES all private members of a class # will be included in the documentation. EXTRACT_PRIVATE = YES # If the EXTRACT_STATIC tag is set to YES all static members of a file # will be included in the documentation. EXTRACT_STATIC = YES # If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) # defined locally in source files will be included in the documentation. # If set to NO only classes defined in header files are included. EXTRACT_LOCAL_CLASSES = YES # This flag is only useful for Objective-C code. When set to YES local # methods, which are defined in the implementation section but not in # the interface are included in the documentation. # If set to NO (the default) only methods in the interface are included. EXTRACT_LOCAL_METHODS = NO # If this flag is set to YES, the members of anonymous namespaces will be # extracted and appear in the documentation as a namespace called # 'anonymous_namespace{file}', where file will be replaced with the base # name of the file that contains the anonymous namespace. By default # anonymous namespace are hidden. EXTRACT_ANON_NSPACES = NO # If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all # undocumented members of documented classes, files or namespaces. # If set to NO (the default) these members will be included in the # various overviews, but no documentation section is generated. # This option has no effect if EXTRACT_ALL is enabled. HIDE_UNDOC_MEMBERS = NO # If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all # undocumented classes that are normally visible in the class hierarchy. # If set to NO (the default) these classes will be included in the various # overviews. This option has no effect if EXTRACT_ALL is enabled. HIDE_UNDOC_CLASSES = NO # If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all # friend (class|struct|union) declarations. # If set to NO (the default) these declarations will be included in the # documentation. HIDE_FRIEND_COMPOUNDS = NO # If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any # documentation blocks found inside the body of a function. # If set to NO (the default) these blocks will be appended to the # function's detailed documentation block. HIDE_IN_BODY_DOCS = NO # The INTERNAL_DOCS tag determines if documentation # that is typed after a \internal command is included. If the tag is set # to NO (the default) then the documentation will be excluded. # Set it to YES to include the internal documentation. INTERNAL_DOCS = NO # If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate # file names in lower-case letters. If set to YES upper-case letters are also # allowed. This is useful if you have classes or files whose names only differ # in case and if your file system supports case sensitive file names. Windows # and Mac users are advised to set this option to NO. CASE_SENSE_NAMES = NO # If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen # will show members with their full class and namespace scopes in the # documentation. If set to YES the scope will be hidden. HIDE_SCOPE_NAMES = NO # If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen # will put a list of the files that are included by a file in the documentation # of that file. SHOW_INCLUDE_FILES = YES # If the INLINE_INFO tag is set to YES (the default) then a tag [inline] # is inserted in the documentation for inline members. INLINE_INFO = YES # If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen # will sort the (detailed) documentation of file and class members # alphabetically by member name. If set to NO the members will appear in # declaration order. SORT_MEMBER_DOCS = YES # If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the # brief documentation of file, namespace and class members alphabetically # by member name. If set to NO (the default) the members will appear in # declaration order. SORT_BRIEF_DOCS = YES # If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the # hierarchy of group names into alphabetical order. If set to NO (the default) # the group names will appear in their defined order. SORT_GROUP_NAMES = NO # If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be # sorted by fully-qualified names, including namespaces. If set to # NO (the default), the class list will be sorted only by class name, # not including the namespace part. # Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. # Note: This option applies only to the class list, not to the # alphabetical list. SORT_BY_SCOPE_NAME = NO # The GENERATE_TODOLIST tag can be used to enable (YES) or # disable (NO) the todo list. This list is created by putting \todo # commands in the documentation. GENERATE_TODOLIST = YES # The GENERATE_TESTLIST tag can be used to enable (YES) or # disable (NO) the test list. This list is created by putting \test # commands in the documentation. GENERATE_TESTLIST = YES # The GENERATE_BUGLIST tag can be used to enable (YES) or # disable (NO) the bug list. This list is created by putting \bug # commands in the documentation. GENERATE_BUGLIST = YES # The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or # disable (NO) the deprecated list. This list is created by putting # \deprecated commands in the documentation. GENERATE_DEPRECATEDLIST = YES # The ENABLED_SECTIONS tag can be used to enable conditional # documentation sections, marked by \if sectionname ... \endif. ENABLED_SECTIONS = # The MAX_INITIALIZER_LINES tag determines the maximum number of lines # the initial value of a variable or define consists of for it to appear in # the documentation. If the initializer consists of more lines than specified # here it will be hidden. Use a value of 0 to hide initializers completely. # The appearance of the initializer of individual variables and defines in the # documentation can be controlled using \showinitializer or \hideinitializer # command in the documentation regardless of this setting. MAX_INITIALIZER_LINES = 30 # Set the SHOW_USED_FILES tag to NO to disable the list of files generated # at the bottom of the documentation of classes and structs. If set to YES the # list will mention the files that were used to generate the documentation. SHOW_USED_FILES = YES # If the sources in your project are distributed over multiple directories # then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy # in the documentation. The default is NO. SHOW_DIRECTORIES = NO # Set the SHOW_FILES tag to NO to disable the generation of the Files page. # This will remove the Files entry from the Quick Index and from the # Folder Tree View (if specified). The default is YES. SHOW_FILES = YES # Set the SHOW_NAMESPACES tag to NO to disable the generation of the # Namespaces page. This will remove the Namespaces entry from the Quick Index # and from the Folder Tree View (if specified). The default is YES. SHOW_NAMESPACES = YES # The FILE_VERSION_FILTER tag can be used to specify a program or script that # doxygen should invoke to get the current version for each file (typically from # the version control system). Doxygen will invoke the program by executing (via # popen()) the command , where is the value of # the FILE_VERSION_FILTER tag, and is the name of an input file # provided by doxygen. Whatever the program writes to standard output # is used as the file version. See the manual for examples. FILE_VERSION_FILTER = # The LAYOUT_FILE tag can be used to specify a layout file which will be parsed by # doxygen. The layout file controls the global structure of the generated output files # in an output format independent way. The create the layout file that represents # doxygen's defaults, run doxygen with the -l option. You can optionally specify a # file name after the option, if omitted DoxygenLayout.xml will be used as the name # of the layout file. LAYOUT_FILE = #--------------------------------------------------------------------------- # configuration options related to warning and progress messages #--------------------------------------------------------------------------- # The QUIET tag can be used to turn on/off the messages that are generated # by doxygen. Possible values are YES and NO. If left blank NO is used. QUIET = NO # The WARNINGS tag can be used to turn on/off the warning messages that are # generated by doxygen. Possible values are YES and NO. If left blank # NO is used. WARNINGS = YES # If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings # for undocumented members. If EXTRACT_ALL is set to YES then this flag will # automatically be disabled. WARN_IF_UNDOCUMENTED = YES # If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for # potential errors in the documentation, such as not documenting some # parameters in a documented function, or documenting parameters that # don't exist or using markup commands wrongly. WARN_IF_DOC_ERROR = YES # This WARN_NO_PARAMDOC option can be abled to get warnings for # functions that are documented, but have no documentation for their parameters # or return value. If set to NO (the default) doxygen will only warn about # wrong or incomplete parameter documentation, but not about the absence of # documentation. WARN_NO_PARAMDOC = NO # The WARN_FORMAT tag determines the format of the warning messages that # doxygen can produce. The string should contain the $file, $line, and $text # tags, which will be replaced by the file and line number from which the # warning originated and the warning text. Optionally the format may contain # $version, which will be replaced by the version of the file (if it could # be obtained via FILE_VERSION_FILTER) WARN_FORMAT = "$file:$line: $text" # The WARN_LOGFILE tag can be used to specify a file to which warning # and error messages should be written. If left blank the output is written # to stderr. WARN_LOGFILE = #--------------------------------------------------------------------------- # configuration options related to the input files #--------------------------------------------------------------------------- # The INPUT tag can be used to specify the files and/or directories that contain # documented source files. You may enter file names like "myfile.cpp" or # directories like "/usr/src/myproject". Separate the files or directories # with spaces. INPUT = src/framework # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is # also the default input encoding. Doxygen uses libiconv (or the iconv built # into libc) for the transcoding. See http://www.gnu.org/software/libiconv for # the list of possible encodings. INPUT_ENCODING = UTF-8 # If the value of the INPUT tag contains directories, you can use the # FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp # and *.h) to filter out the source-files in the directories. If left # blank the following patterns are tested: # *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx # *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py *.f90 FILE_PATTERNS = # The RECURSIVE tag can be used to turn specify whether or not subdirectories # should be searched for input files as well. Possible values are YES and NO. # If left blank NO is used. RECURSIVE = NO # The EXCLUDE tag can be used to specify files and/or directories that should # excluded from the INPUT source files. This way you can easily exclude a # subdirectory from a directory tree whose root is specified with the INPUT tag. EXCLUDE = # The EXCLUDE_SYMLINKS tag can be used select whether or not files or # directories that are symbolic links (a Unix filesystem feature) are excluded # from the input. EXCLUDE_SYMLINKS = NO # If the value of the INPUT tag contains directories, you can use the # EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude # certain files from those directories. Note that the wildcards are matched # against the file with absolute path, so to exclude all test directories # for example use the pattern */test/* EXCLUDE_PATTERNS = # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names # (namespaces, classes, functions, etc.) that should be excluded from the # output. The symbol name can be a fully qualified name, a word, or if the # wildcard * is used, a substring. Examples: ANamespace, AClass, # AClass::ANamespace, ANamespace::*Test EXCLUDE_SYMBOLS = # The EXAMPLE_PATH tag can be used to specify one or more files or # directories that contain example code fragments that are included (see # the \include command). EXAMPLE_PATH = # If the value of the EXAMPLE_PATH tag contains directories, you can use the # EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp # and *.h) to filter out the source-files in the directories. If left # blank all files are included. EXAMPLE_PATTERNS = # If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be # searched for input files to be used with the \include or \dontinclude # commands irrespective of the value of the RECURSIVE tag. # Possible values are YES and NO. If left blank NO is used. EXAMPLE_RECURSIVE = NO # The IMAGE_PATH tag can be used to specify one or more files or # directories that contain image that are included in the documentation (see # the \image command). IMAGE_PATH = # The INPUT_FILTER tag can be used to specify a program that doxygen should # invoke to filter for each input file. Doxygen will invoke the filter program # by executing (via popen()) the command , where # is the value of the INPUT_FILTER tag, and is the name of an # input file. Doxygen will then use the output that the filter program writes # to standard output. If FILTER_PATTERNS is specified, this tag will be # ignored. INPUT_FILTER = # The FILTER_PATTERNS tag can be used to specify filters on a per file pattern # basis. Doxygen will compare the file name with each pattern and apply the # filter if there is a match. The filters are a list of the form: # pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further # info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER # is applied to all files. FILTER_PATTERNS = # If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using # INPUT_FILTER) will be used to filter the input files when producing source # files to browse (i.e. when SOURCE_BROWSER is set to YES). FILTER_SOURCE_FILES = NO #--------------------------------------------------------------------------- # configuration options related to source browsing #--------------------------------------------------------------------------- # If the SOURCE_BROWSER tag is set to YES then a list of source files will # be generated. Documented entities will be cross-referenced with these sources. # Note: To get rid of all source code in the generated output, make sure also # VERBATIM_HEADERS is set to NO. SOURCE_BROWSER = NO # Setting the INLINE_SOURCES tag to YES will include the body # of functions and classes directly in the documentation. INLINE_SOURCES = NO # Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct # doxygen to hide any special comment blocks from generated source code # fragments. Normal C and C++ comments will always remain visible. STRIP_CODE_COMMENTS = YES # If the REFERENCED_BY_RELATION tag is set to YES # then for each documented function all documented # functions referencing it will be listed. REFERENCED_BY_RELATION = NO # If the REFERENCES_RELATION tag is set to YES # then for each documented function all documented entities # called/used by that function will be listed. REFERENCES_RELATION = NO # If the REFERENCES_LINK_SOURCE tag is set to YES (the default) # and SOURCE_BROWSER tag is set to YES, then the hyperlinks from # functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will # link to the source code. Otherwise they will link to the documentstion. REFERENCES_LINK_SOURCE = YES # If the USE_HTAGS tag is set to YES then the references to source code # will point to the HTML generated by the htags(1) tool instead of doxygen # built-in source browser. The htags tool is part of GNU's global source # tagging system (see http://www.gnu.org/software/global/global.html). You # will need version 4.8.6 or higher. USE_HTAGS = NO # If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen # will generate a verbatim copy of the header file for each class for # which an include is specified. Set to NO to disable this. VERBATIM_HEADERS = YES #--------------------------------------------------------------------------- # configuration options related to the alphabetical class index #--------------------------------------------------------------------------- # If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then # the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns # in which this list will be split (can be a number in the range [1..20]) COLS_IN_ALPHA_INDEX = 5 # In case all classes in a project start with a common prefix, all # classes will be put under the same header in the alphabetical index. # The IGNORE_PREFIX tag can be used to specify one or more prefixes that # should be ignored while generating the index headers. IGNORE_PREFIX = #--------------------------------------------------------------------------- # configuration options related to the HTML output #--------------------------------------------------------------------------- # If the GENERATE_HTML tag is set to YES (the default) Doxygen will # generate HTML output. GENERATE_HTML = YES # The HTML_OUTPUT tag is used to specify where the HTML docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `html' will be used as the default path. HTML_OUTPUT = html # The HTML_FILE_EXTENSION tag can be used to specify the file extension for # each generated HTML page (for example: .htm,.php,.asp). If it is left blank # doxygen will generate files with .html extension. HTML_FILE_EXTENSION = .html # The HTML_HEADER tag can be used to specify a personal HTML header for # each generated HTML page. If it is left blank doxygen will generate a # standard header. HTML_HEADER = # The HTML_FOOTER tag can be used to specify a personal HTML footer for # each generated HTML page. If it is left blank doxygen will generate a # standard footer. HTML_FOOTER = # The HTML_STYLESHEET tag can be used to specify a user-defined cascading # style sheet that is used by each HTML page. It can be used to # fine-tune the look of the HTML output. If the tag is left blank doxygen # will generate a default style sheet. Note that doxygen will try to copy # the style sheet file to the HTML output directory, so don't put your own # stylesheet in the HTML output directory as well, or it will be erased! HTML_STYLESHEET = # If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, # files or namespaces will be aligned in HTML using tables. If set to # NO a bullet list will be used. HTML_ALIGN_MEMBERS = YES # If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML # documentation will contain sections that can be hidden and shown after the # page has loaded. For this to work a browser that supports # JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox # Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari). HTML_DYNAMIC_SECTIONS = NO # If the GENERATE_DOCSET tag is set to YES, additional index files # will be generated that can be used as input for Apple's Xcode 3 # integrated development environment, introduced with OSX 10.5 (Leopard). # To create a documentation set, doxygen will generate a Makefile in the # HTML output directory. Running make will produce the docset in that # directory and running "make install" will install the docset in # ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find # it at startup. # See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html for more information. GENERATE_DOCSET = NO # When GENERATE_DOCSET tag is set to YES, this tag determines the name of the # feed. A documentation feed provides an umbrella under which multiple # documentation sets from a single provider (such as a company or product suite) # can be grouped. DOCSET_FEEDNAME = "Doxygen generated docs" # When GENERATE_DOCSET tag is set to YES, this tag specifies a string that # should uniquely identify the documentation set bundle. This should be a # reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen # will append .docset to the name. DOCSET_BUNDLE_ID = org.doxygen.Project # If the GENERATE_HTMLHELP tag is set to YES, additional index files # will be generated that can be used as input for tools like the # Microsoft HTML help workshop to generate a compiled HTML help file (.chm) # of the generated HTML documentation. GENERATE_HTMLHELP = NO # If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can # be used to specify the file name of the resulting .chm file. You # can add a path in front of the file if the result should not be # written to the html output directory. CHM_FILE = # If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can # be used to specify the location (absolute path including file name) of # the HTML help compiler (hhc.exe). If non-empty doxygen will try to run # the HTML help compiler on the generated index.hhp. HHC_LOCATION = # If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag # controls if a separate .chi index file is generated (YES) or that # it should be included in the master .chm file (NO). GENERATE_CHI = NO # If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING # is used to encode HtmlHelp index (hhk), content (hhc) and project file # content. CHM_INDEX_ENCODING = # If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag # controls whether a binary table of contents is generated (YES) or a # normal table of contents (NO) in the .chm file. BINARY_TOC = NO # The TOC_EXPAND flag can be set to YES to add extra items for group members # to the contents of the HTML help documentation and to the tree view. TOC_EXPAND = NO # If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and QHP_VIRTUAL_FOLDER # are set, an additional index file will be generated that can be used as input for # Qt's qhelpgenerator to generate a Qt Compressed Help (.qch) of the generated # HTML documentation. GENERATE_QHP = NO # If the QHG_LOCATION tag is specified, the QCH_FILE tag can # be used to specify the file name of the resulting .qch file. # The path specified is relative to the HTML output folder. QCH_FILE = # The QHP_NAMESPACE tag specifies the namespace to use when generating # Qt Help Project output. For more information please see # Qt Help Project / Namespace. QHP_NAMESPACE = org.doxygen.Project # The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating # Qt Help Project output. For more information please see # Qt Help Project / Virtual Folders. QHP_VIRTUAL_FOLDER = doc # If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can # be used to specify the location of Qt's qhelpgenerator. # If non-empty doxygen will try to run qhelpgenerator on the generated # .qhp file . QHG_LOCATION = # The DISABLE_INDEX tag can be used to turn on/off the condensed index at # top of each HTML page. The value NO (the default) enables the index and # the value YES disables it. DISABLE_INDEX = NO # This tag can be used to set the number of enum values (range [1..20]) # that doxygen will group on one line in the generated HTML documentation. ENUM_VALUES_PER_LINE = 4 # The GENERATE_TREEVIEW tag is used to specify whether a tree-like index # structure should be generated to display hierarchical information. # If the tag value is set to FRAME, a side panel will be generated # containing a tree-like index structure (just like the one that # is generated for HTML Help). For this to work a browser that supports # JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+, # Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are # probably better off using the HTML help feature. Other possible values # for this tag are: HIERARCHIES, which will generate the Groups, Directories, # and Class Hierarchy pages using a tree view instead of an ordered list; # ALL, which combines the behavior of FRAME and HIERARCHIES; and NONE, which # disables this behavior completely. For backwards compatibility with previous # releases of Doxygen, the values YES and NO are equivalent to FRAME and NONE # respectively. GENERATE_TREEVIEW = NO # If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be # used to set the initial width (in pixels) of the frame in which the tree # is shown. TREEVIEW_WIDTH = 250 # Use this tag to change the font size of Latex formulas included # as images in the HTML documentation. The default is 10. Note that # when you change the font size after a successful doxygen run you need # to manually remove any form_*.png images from the HTML output directory # to force them to be regenerated. FORMULA_FONTSIZE = 10 #--------------------------------------------------------------------------- # configuration options related to the LaTeX output #--------------------------------------------------------------------------- # If the GENERATE_LATEX tag is set to YES (the default) Doxygen will # generate Latex output. GENERATE_LATEX = NO # The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `latex' will be used as the default path. LATEX_OUTPUT = latex # The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be # invoked. If left blank `latex' will be used as the default command name. LATEX_CMD_NAME = latex # The MAKEINDEX_CMD_NAME tag can be used to specify the command name to # generate index for LaTeX. If left blank `makeindex' will be used as the # default command name. MAKEINDEX_CMD_NAME = makeindex # If the COMPACT_LATEX tag is set to YES Doxygen generates more compact # LaTeX documents. This may be useful for small projects and may help to # save some trees in general. COMPACT_LATEX = NO # The PAPER_TYPE tag can be used to set the paper type that is used # by the printer. Possible values are: a4, a4wide, letter, legal and # executive. If left blank a4wide will be used. PAPER_TYPE = a4wide # The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX # packages that should be included in the LaTeX output. EXTRA_PACKAGES = # The LATEX_HEADER tag can be used to specify a personal LaTeX header for # the generated latex document. The header should contain everything until # the first chapter. If it is left blank doxygen will generate a # standard header. Notice: only use this tag if you know what you are doing! LATEX_HEADER = # If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated # is prepared for conversion to pdf (using ps2pdf). The pdf file will # contain links (just like the HTML output) instead of page references # This makes the output suitable for online browsing using a pdf viewer. PDF_HYPERLINKS = YES # If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of # plain latex in the generated Makefile. Set this option to YES to get a # higher quality PDF documentation. USE_PDFLATEX = YES # If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. # command to the generated LaTeX files. This will instruct LaTeX to keep # running if errors occur, instead of asking the user for help. # This option is also used when generating formulas in HTML. LATEX_BATCHMODE = NO # If LATEX_HIDE_INDICES is set to YES then doxygen will not # include the index chapters (such as File Index, Compound Index, etc.) # in the output. LATEX_HIDE_INDICES = NO #--------------------------------------------------------------------------- # configuration options related to the RTF output #--------------------------------------------------------------------------- # If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output # The RTF output is optimized for Word 97 and may not look very pretty with # other RTF readers or editors. GENERATE_RTF = NO # The RTF_OUTPUT tag is used to specify where the RTF docs will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `rtf' will be used as the default path. RTF_OUTPUT = rtf # If the COMPACT_RTF tag is set to YES Doxygen generates more compact # RTF documents. This may be useful for small projects and may help to # save some trees in general. COMPACT_RTF = NO # If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated # will contain hyperlink fields. The RTF file will # contain links (just like the HTML output) instead of page references. # This makes the output suitable for online browsing using WORD or other # programs which support those fields. # Note: wordpad (write) and others do not support links. RTF_HYPERLINKS = NO # Load stylesheet definitions from file. Syntax is similar to doxygen's # config file, i.e. a series of assignments. You only have to provide # replacements, missing definitions are set to their default value. RTF_STYLESHEET_FILE = # Set optional variables used in the generation of an rtf document. # Syntax is similar to doxygen's config file. RTF_EXTENSIONS_FILE = #--------------------------------------------------------------------------- # configuration options related to the man page output #--------------------------------------------------------------------------- # If the GENERATE_MAN tag is set to YES (the default) Doxygen will # generate man pages GENERATE_MAN = NO # The MAN_OUTPUT tag is used to specify where the man pages will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `man' will be used as the default path. MAN_OUTPUT = man # The MAN_EXTENSION tag determines the extension that is added to # the generated man pages (default is the subroutine's section .3) MAN_EXTENSION = .3 # If the MAN_LINKS tag is set to YES and Doxygen generates man output, # then it will generate one additional man file for each entity # documented in the real man page(s). These additional files # only source the real man page, but without them the man command # would be unable to find the correct page. The default is NO. MAN_LINKS = NO #--------------------------------------------------------------------------- # configuration options related to the XML output #--------------------------------------------------------------------------- # If the GENERATE_XML tag is set to YES Doxygen will # generate an XML file that captures the structure of # the code including all documentation. GENERATE_XML = NO # The XML_OUTPUT tag is used to specify where the XML pages will be put. # If a relative path is entered the value of OUTPUT_DIRECTORY will be # put in front of it. If left blank `xml' will be used as the default path. XML_OUTPUT = xml # The XML_SCHEMA tag can be used to specify an XML schema, # which can be used by a validating XML parser to check the # syntax of the XML files. XML_SCHEMA = # The XML_DTD tag can be used to specify an XML DTD, # which can be used by a validating XML parser to check the # syntax of the XML files. XML_DTD = # If the XML_PROGRAMLISTING tag is set to YES Doxygen will # dump the program listings (including syntax highlighting # and cross-referencing information) to the XML output. Note that # enabling this will significantly increase the size of the XML output. XML_PROGRAMLISTING = YES #--------------------------------------------------------------------------- # configuration options for the AutoGen Definitions output #--------------------------------------------------------------------------- # If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will # generate an AutoGen Definitions (see autogen.sf.net) file # that captures the structure of the code including all # documentation. Note that this feature is still experimental # and incomplete at the moment. GENERATE_AUTOGEN_DEF = NO #--------------------------------------------------------------------------- # configuration options related to the Perl module output #--------------------------------------------------------------------------- # If the GENERATE_PERLMOD tag is set to YES Doxygen will # generate a Perl module file that captures the structure of # the code including all documentation. Note that this # feature is still experimental and incomplete at the # moment. GENERATE_PERLMOD = NO # If the PERLMOD_LATEX tag is set to YES Doxygen will generate # the necessary Makefile rules, Perl scripts and LaTeX code to be able # to generate PDF and DVI output from the Perl module output. PERLMOD_LATEX = NO # If the PERLMOD_PRETTY tag is set to YES the Perl module output will be # nicely formatted so it can be parsed by a human reader. This is useful # if you want to understand what is going on. On the other hand, if this # tag is set to NO the size of the Perl module output will be much smaller # and Perl will parse it just the same. PERLMOD_PRETTY = YES # The names of the make variables in the generated doxyrules.make file # are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. # This is useful so different doxyrules.make files included by the same # Makefile don't overwrite each other's variables. PERLMOD_MAKEVAR_PREFIX = #--------------------------------------------------------------------------- # Configuration options related to the preprocessor #--------------------------------------------------------------------------- # If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will # evaluate all C-preprocessor directives found in the sources and include # files. ENABLE_PREPROCESSING = YES # If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro # names in the source code. If set to NO (the default) only conditional # compilation will be performed. Macro expansion can be done in a controlled # way by setting EXPAND_ONLY_PREDEF to YES. MACRO_EXPANSION = NO # If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES # then the macro expansion is limited to the macros specified with the # PREDEFINED and EXPAND_AS_DEFINED tags. EXPAND_ONLY_PREDEF = NO # If the SEARCH_INCLUDES tag is set to YES (the default) the includes files # in the INCLUDE_PATH (see below) will be search if a #include is found. SEARCH_INCLUDES = YES # The INCLUDE_PATH tag can be used to specify one or more directories that # contain include files that are not input files but should be processed by # the preprocessor. INCLUDE_PATH = # You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard # patterns (like *.h and *.hpp) to filter out the header-files in the # directories. If left blank, the patterns specified with FILE_PATTERNS will # be used. INCLUDE_FILE_PATTERNS = # The PREDEFINED tag can be used to specify one or more macro names that # are defined before the preprocessor is started (similar to the -D option of # gcc). The argument of the tag is a list of macros of the form: name # or name=definition (no spaces). If the definition and the = are # omitted =1 is assumed. To prevent a macro definition from being # undefined via #undef or recursively expanded use the := operator # instead of the = operator. PREDEFINED = # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then # this tag can be used to specify a list of macro names that should be expanded. # The macro definition that is found in the sources will be used. # Use the PREDEFINED tag if you want to use a different macro definition. EXPAND_AS_DEFINED = # If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then # doxygen's preprocessor will remove all function-like macros that are alone # on a line, have an all uppercase name, and do not end with a semicolon. Such # function macros are typically used for boiler-plate code, and will confuse # the parser if not removed. SKIP_FUNCTION_MACROS = YES #--------------------------------------------------------------------------- # Configuration::additions related to external references #--------------------------------------------------------------------------- # The TAGFILES option can be used to specify one or more tagfiles. # Optionally an initial location of the external documentation # can be added for each tagfile. The format of a tag file without # this location is as follows: # TAGFILES = file1 file2 ... # Adding location for the tag files is done as follows: # TAGFILES = file1=loc1 "file2 = loc2" ... # where "loc1" and "loc2" can be relative or absolute paths or # URLs. If a location is present for each tag, the installdox tool # does not have to be run to correct the links. # Note that each tag file must have a unique name # (where the name does NOT include the path) # If a tag file is not located in the directory in which doxygen # is run, you must also specify the path to the tagfile here. TAGFILES = # When a file name is specified after GENERATE_TAGFILE, doxygen will create # a tag file that is based on the input files it reads. GENERATE_TAGFILE = # If the ALLEXTERNALS tag is set to YES all external classes will be listed # in the class index. If set to NO only the inherited external classes # will be listed. ALLEXTERNALS = NO # If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed # in the modules index. If set to NO, only the current project's groups will # be listed. EXTERNAL_GROUPS = YES # The PERL_PATH should be the absolute path and name of the perl script # interpreter (i.e. the result of `which perl'). PERL_PATH = /usr/bin/perl #--------------------------------------------------------------------------- # Configuration options related to the dot tool #--------------------------------------------------------------------------- # If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will # generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base # or super classes. Setting the tag to NO turns the diagrams off. Note that # this option is superseded by the HAVE_DOT option below. This is only a # fallback. It is recommended to install and use dot, since it yields more # powerful graphs. CLASS_DIAGRAMS = YES # You can define message sequence charts within doxygen comments using the \msc # command. Doxygen will then run the mscgen tool (see # http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the # documentation. The MSCGEN_PATH tag allows you to specify the directory where # the mscgen tool resides. If left empty the tool is assumed to be found in the # default search path. MSCGEN_PATH = # If set to YES, the inheritance and collaboration graphs will hide # inheritance and usage relations if the target is undocumented # or is not a class. HIDE_UNDOC_RELATIONS = YES # If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is # available from the path. This tool is part of Graphviz, a graph visualization # toolkit from AT&T and Lucent Bell Labs. The other options in this section # have no effect if this option is set to NO (the default) HAVE_DOT = NO # By default doxygen will write a font called FreeSans.ttf to the output # directory and reference it in all dot files that doxygen generates. This # font does not include all possible unicode characters however, so when you need # these (or just want a differently looking font) you can specify the font name # using DOT_FONTNAME. You need need to make sure dot is able to find the font, # which can be done by putting it in a standard location or by setting the # DOTFONTPATH environment variable or by setting DOT_FONTPATH to the directory # containing the font. DOT_FONTNAME = FreeSans # By default doxygen will tell dot to use the output directory to look for the # FreeSans.ttf font (which doxygen will put there itself). If you specify a # different font using DOT_FONTNAME you can set the path where dot # can find it using this tag. DOT_FONTPATH = # If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen # will generate a graph for each documented class showing the direct and # indirect inheritance relations. Setting this tag to YES will force the # the CLASS_DIAGRAMS tag to NO. CLASS_GRAPH = YES # If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen # will generate a graph for each documented class showing the direct and # indirect implementation dependencies (inheritance, containment, and # class references variables) of the class with other documented classes. COLLABORATION_GRAPH = YES # If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen # will generate a graph for groups, showing the direct groups dependencies GROUP_GRAPHS = YES # If the UML_LOOK tag is set to YES doxygen will generate inheritance and # collaboration diagrams in a style similar to the OMG's Unified Modeling # Language. UML_LOOK = NO # If set to YES, the inheritance and collaboration graphs will show the # relations between templates and their instances. TEMPLATE_RELATIONS = NO # If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT # tags are set to YES then doxygen will generate a graph for each documented # file showing the direct and indirect include dependencies of the file with # other documented files. INCLUDE_GRAPH = YES # If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and # HAVE_DOT tags are set to YES then doxygen will generate a graph for each # documented header file showing the documented files that directly or # indirectly include this file. INCLUDED_BY_GRAPH = YES # If the CALL_GRAPH and HAVE_DOT options are set to YES then # doxygen will generate a call dependency graph for every global function # or class method. Note that enabling this option will significantly increase # the time of a run. So in most cases it will be better to enable call graphs # for selected functions only using the \callgraph command. CALL_GRAPH = YES # If the CALLER_GRAPH and HAVE_DOT tags are set to YES then # doxygen will generate a caller dependency graph for every global function # or class method. Note that enabling this option will significantly increase # the time of a run. So in most cases it will be better to enable caller # graphs for selected functions only using the \callergraph command. CALLER_GRAPH = NO # If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen # will graphical hierarchy of all classes instead of a textual one. GRAPHICAL_HIERARCHY = YES # If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES # then doxygen will show the dependencies a directory has on other directories # in a graphical way. The dependency relations are determined by the #include # relations between the files in the directories. DIRECTORY_GRAPH = YES # The DOT_IMAGE_FORMAT tag can be used to set the image format of the images # generated by dot. Possible values are png, jpg, or gif # If left blank png will be used. DOT_IMAGE_FORMAT = png # The tag DOT_PATH can be used to specify the path where the dot tool can be # found. If left blank, it is assumed the dot tool can be found in the path. DOT_PATH = # The DOTFILE_DIRS tag can be used to specify one or more directories that # contain dot files that are included in the documentation (see the # \dotfile command). DOTFILE_DIRS = # The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of # nodes that will be shown in the graph. If the number of nodes in a graph # becomes larger than this value, doxygen will truncate the graph, which is # visualized by representing a node as a red box. Note that doxygen if the # number of direct children of the root node in a graph is already larger than # DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note # that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. DOT_GRAPH_MAX_NODES = 50 # The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the # graphs generated by dot. A depth value of 3 means that only nodes reachable # from the root by following a path via at most 3 edges will be shown. Nodes # that lay further from the root node will be omitted. Note that setting this # option to 1 or 2 may greatly reduce the computation time needed for large # code bases. Also note that the size of a graph can be further restricted by # DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. MAX_DOT_GRAPH_DEPTH = 0 # Set the DOT_TRANSPARENT tag to YES to generate images with a transparent # background. This is disabled by default, because dot on Windows does not # seem to support this out of the box. Warning: Depending on the platform used, # enabling this option may lead to badly anti-aliased labels on the edges of # a graph (i.e. they become hard to read). DOT_TRANSPARENT = NO # Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output # files in one run (i.e. multiple -o and -T options on the command line). This # makes dot run faster, but since only newer versions of dot (>1.8.10) # support this, this feature is disabled by default. DOT_MULTI_TARGETS = NO # If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will # generate a legend page explaining the meaning of the various boxes and # arrows in the dot generated graphs. GENERATE_LEGEND = YES # If the DOT_CLEANUP tag is set to YES (the default) Doxygen will # remove the intermediate dot files that are used to generate # the various graphs. DOT_CLEANUP = YES #--------------------------------------------------------------------------- # Configuration::additions related to the search engine #--------------------------------------------------------------------------- # The SEARCHENGINE tag specifies whether or not a search engine should be # used. If set to NO the values of all tags below this one will be ignored. SEARCHENGINE = NO mlt-7.22.0/GPL000664 000000 000000 00000043254 14531534050 012733 0ustar00rootroot000000 000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. mlt-7.22.0/GPLv3000664 000000 000000 00000104513 14531534050 013200 0ustar00rootroot000000 000000 GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . mlt-7.22.0/MltConfig.cmake.in000664 000000 000000 00000000622 14531534050 015647 0ustar00rootroot000000 000000 @PACKAGE_INIT@ set(_supported_components "@MLT_SUPPORTED_COMPONENTS@") foreach(_comp ${Mlt@MLT_VERSION_MAJOR@_FIND_COMPONENTS}) if (NOT _comp IN_LIST _supported_components) set(Mlt@MLT_VERSION_MAJOR@_FOUND False) set(Mlt@MLT_VERSION_MAJOR@_NOT_FOUND_MESSAGE "Unsupported component: ${_comp}") endif() endforeach() include("${CMAKE_CURRENT_LIST_DIR}/Mlt@MLT_VERSION_MAJOR@Targets.cmake") mlt-7.22.0/NEWS000664 000000 000000 00000300736 14531534050 013066 0ustar00rootroot000000 000000 MLT Release Notes ----------------- Version 7.22.0 Framework * Added new functions: - `mlt_property_is_color()` - `mlt_property_is_numeric()` - `mlt_property_is_rect()` * Many new keyframe types: - `mlt_keyframe_smooth_loose` - `~=` (same as old `mlt_keyframe_smooth` - Unity Catmull-Rom spline) - `mlt_keyframe_smooth_natural` - `$=` (Centripetal Catmull-Rom spline with natural slope) - `mlt_keyframe_smooth_tight` - `-=` (Centripetal Catmull-Rom spline with 0 slope) - `mlt_keyframe_sinusoidal_in` - `a=` - `mlt_keyframe_sinusoidal_out` - `b=` - `mlt_keyframe_sinusoidal_in_out` - `c=` - `mlt_keyframe_quadratic_in` - `d=` - `mlt_keyframe_quadratic_out` - `e=` - `mlt_keyframe_quadratic_in_out` - `f=` - `mlt_keyframe_cubic_in` - `g=` - `mlt_keyframe_cubic_out` - `h=` - `mlt_keyframe_cubic_in_out` - `i=` - `mlt_keyframe_quartic_in` - `j=` - `mlt_keyframe_quartic_out` - `k=` - `mlt_keyframe_quartic_in_out` - `l=` - `mlt_keyframe_quintic_in` - `m=` - `mlt_keyframe_quintic_out` - `n=` - `mlt_keyframe_quintic_in_out` - `o=` - `mlt_keyframe_exponential_in` - `p=` - `mlt_keyframe_exponential_out` - `q=` - `mlt_keyframe_exponential_in_out` - `r=` - `mlt_keyframe_circular_in` - `s=` - `mlt_keyframe_circular_out` - `t=` - `mlt_keyframe_circular_in_out` - `u=` - `mlt_keyframe_back_in` - `v=` - `mlt_keyframe_back_out` - `w=` - `mlt_keyframe_back_in_out` - `x=` - `mlt_keyframe_elastic_in` - `y=` - `mlt_keyframe_elastic_out` - `z=` - `mlt_keyframe_elastic_in_out` - `A=` - `mlt_keyframe_bounce_in` - `B=` - `mlt_keyframe_bounce_out` - `C=` - `mlt_keyframe_bounce_in_out` - `D=` * Fixed missing support for `mlt_service_transition` in `Mlt::Producer()` C++ constructor. Modules * Fixed `rotoscoping` filter crash on image with height = 0. * Fixed crashed due to `qtblend` transition requesting an image of 0 width or height. * Added support for RtAudio 6 in the `rtaudio` consumer. * Fixed `createdate` keyword deletes preceeding text in `dynamictext` filter. * Added `opacity` property to filters that use `qtext`: - `dynamictext` - `gpstext` - `qtext` - `timer` * Added `fade_video`, `fade_audio`, and `fade_color` properties to `autofade` filter. * Added backwards compatibility for changed filter names in frei0r v2.3.1: - `frei0r.measure_pr0be` - `frei0r.measure_pr0file` - `frei0r.tehroxx0r` - `frei0r.alpha0ps_alpha0ps` - `frei0r.alpha0ps_alphagrad` - `frei0r.alpha0ps_alphaspot` - `frei0r.denoise_hqdn3d` * Fixed a memory leak in `avformat` producer with consumer deinterlacer=yadif. * Fixed `qimage` producer color if consumer color_range=pc pix_fmt=yuv444p. Other * Fixed `ten_bit/ProRes 422` avformat preset produced ProRes 444. * Fixed `YouTube` avformat preset did not output high profile with some hardware encoders. Version 7.20.0 Framework * Fixed "blank" in a playlist does not have audio normalization filters. * Fixed serializing `mlt_color` transparent black as "#00000000" when the property was set using an integer or `mlt_color`. * Fixed `mlt_chain_set_source()` would always fetch a frame from the producer even if it has "meta.media.frame_rate_num" and "meta.media.frame_rate_den" properties making things slow. * Fixed `Mlt::Chain` leaking memory. Modules * Added a `blank` producer to the `core` module. * Added keywords to `gpstext` filter: - `#gps_cadence#` - `#gps_grade_degrees#` - `#gps_grade_percentage#` - `#gps_temperature#` * Added some `color_style`s to the `gpsgraphic` filter: - 10 = color by speed (max 100 km/h) - 11 = color by grade (max 90 degrees) - 12 = color by grade (max 20 degrees) * Added more unit formats to `legend_unit` property of `gpsgraphic` filter: - `mmin` or `m/min` - `ftmin` or `ft/min` * Added keywords to `dynamictext` filter: - `#basename#` - `#filename#` * Fixed installing `filter_audioseam.yml`. * Added an `avlink` link to the `avformat` module for FFmpeg filters that can benefit from future frames such as `adeclick`. * Added the `preserve_alpha` property to the `box_blur` filter. * Fixed loading service metadata for the `qt6` and `glaxnimate-qt6` modules. * Fixed a crash when changing the `rotate` property in `avformat` producer with interlace video. * Add `astream` and `vstream` properties to avformat producer. Unlike `audio_index` and `video_index` are absolute indices across the entire array of streams regardless their type, these new 0-based properties are relative to the type audio or video. For example, astream=1 is the second audio stream. * Fixed a possible crash in the `avformat` producer's `mlt_producer_probe` virtual function. * Updated the `glaxnimate` module to version 0.5.4. * Fixed the `sdl2` consumer crashing with the Linux radeonsi_dri driver and showing only all black with the Linux `nvidia` driver. Other * Fix compiling on Android (not supported by the core developers). * Changed the `avformat` consumer `FLAC` preset to use the `flac` format. * Fixed the `melt` Shift+H and Shift+L keyboard shortcuts when the SDL2 window has focus. Version 7.18.0 Framework * Fixed `mlt_frame_get_audio` fails on `mlt_audio_none`. * Added `mlt_audio_free_data()`. * Added `meta.playlist.clip_position` and `meta.playlist.clip_length` properties to `mlt_playlist`. Modules * Added two audio filters to core module to be used on a playlist/track: - `audioseam` - `autofade` * Fixed a crash in `vidstab` filter on image format change. * Fixed font weight in `qtext` filter on Qt 6. * Fixed yuv420p not working in `rescale` filter. * Fixed text shadow outline in `kdenlivetitle` producer. * Fixed crash when changing the profile with `count` producer. * Fixed constructor corruption in `frei0r` module. * Fixed `deinterlace` link was added to invalid producer in `xml` producer. * Fixed producers not indicating progressive scan video: - `kdenlivetitle` - `pango` - `qimage` - `qtext` * Fixed video scan mode detection in `avformat` producers that only indicate on their container format and not on frames such as Ut Video in Matroska. * Fixed very large images in `qimage` producer on Qt 6. * Fixed seeking on clips that use `speed_map` in `timeremap` link. * Fixed a color level problem with sRGB inputs in the `movit` module. * Fixed `avformat` producer's deallocation function for `AVCodecContext`. * Fixed field order of `qtblend` and `frei0r.cairoblend` transitions. * Changed the `avformat` producer `seek_threshold` default to 64. * Updated `ebur128` filter to version 1.2.6. Version 7.16.0 Framework * Added a `chain_normalizers.ini` to the data directory. * Added New C functions to support deinterlacer links: - `mlt_deinterlacer_name()` - `mlt_deinterlacer_id()` - `mlt_link_filter_init()` - `mlt_link_filter_metadata()` - `mlt_cache_put_frame_audio()` - `mlt_cache_put_frame_image()` - `mlt_frame_clone_audio()` - `mlt_frame_clone_image()` * Added support for loading a filter as a link via `mlt_link_filter_init()`. * Added enum `mlt_deinterlacer` with: - `mlt_deinterlacer_none` - `mlt_deinterlacer_onefield` - `mlt_deinterlacer_linearblend` - `mlt_deinterlacer_weave` - `mlt_deinterlacer_bob` - `mlt_deinterlacer_greedy` - `mlt_deinterlacer_yadif_nospatial` - `mlt_deinterlacer_yadif` - `mlt_deinterlacer_bwdif` - `mlt_deinterlacer_estdif` - `mlt_deinterlacer_invalid` * Added new 10-bit YUV members to enum `mlt_image_format`: - `mlt_image_yuv420p10` - `mlt_image_yuv444p10` * Fixed a deadlock and improved quality of start of playback when `mlt_consumer` property `prefill` is greater than 1. * Fixed a couple of data races in `mlt_events` and `mlt_consumer`. * Fixed a crash in `mlt_frame_clone()` with movit and the `mask_start` filter. Modules * Fixed regressions in version 7.14.0: - memory and thread count usage in `swresample` and `resample` links - automatic profile support in `melt` - crash in `count` producer * Upgraded the `glaxnimate` git submodule to version 0.5.3. * Added avformat/`avdeinterlace` (default) and xine/`deinterlace` links. * Fixed deinterlacing in the `multi` and `qglsl` consumers. * Added 10-bit video support to `movit.convert` filter. * Several things in the `avformat` producer: - Fixed artifacts decoding raw FLAC audio. - Fixed a potential crash on `mlt_producer_probe()`. - Fixed seeking on music with album art. - Fixed possible infinite loop on end-of-file. - Fixed a potential deadlock. - Fixed chroma bleeding on interlaced yuv420p. - Fixed `color_range` or `force_full_range` sometimes not working. - Fixed `autorotate` property not working with a chain. - Added audio caching. - Deprecated the `mute_on_pause` property. * Fixed FFmpeg version 6 compilation error. * Fixed rendering the text outline in `kdenlivetitle` producer. * Fixed `'movit.rect` property animation. * Fixed corrupt video in `crop` filter when `mlt_image_yuv420p` requested. * Fixed possible null pointer crashes in some audio filters: - `audiolevel` - `volume` - `loudness` * Fixed a possible roi assert crash in `opencv.tracker` filter. * Added support for "Nano" `algo` to the `opencv.tracker` filter. * Added the property `fix_background_alpha` to the `luma` transition. Other * Added `-query links` to `melt` command line. * Added `avformat` consumer presets for 10-bit video: - AV1 - DNxHR-HQ - FFV1 - ProRes 422 - ProRes 444 - ProRes HQ - x264-high10 - x265-main10 * Added a `clang-format` target to CMake and reformatted all code. * Added warnings as errors with some exceptions to CMake with `Debug` build type and gcc. * Fixed numerous warnings throughout the code. Version 7.14.0 Framework * Added functions to get detailed info about a producer more directly (without having to get a frame and get its image in the case of avformat producer, for example): - `mlt_producer_probe()` - `Mlt::Producer::probe()` * Added functions to add normalizer links to chains (based on a `chain_loader.ini` configuration data file: - `mlt_chain_attach_normalizers()` - `Mlt::Chain::attach_normalizers()` * Changed `locale_t` to `mlt_locale_t` to avoid redefinition on some systems (e.g. clang/llvm on win32). * Fixed the value provided with event "consumer-thread-join" to be `mlt_event_data_thread` as documented. * Fixed `mlt_image_format_planes()` for `mlt_image_yuv420p`. Modules * Added a `swresample` link to the avformat module. * Added a `resample` link to the resample module. * Fixed compatibility of avformat module with FFmpeg version 6. * Fixed `rotoscoping` filter when request image size different than profile. * Fixed `timeremap` link breaking `crop` filter. * Fixed audio/video sync in `avformat` producer when the video start time is not 0. * Improved seeking on a WMA audio file in `avformat` producer. * Optimization to set `AVDISCARD_ALL` on disinterested streams in `avformat` producer. * Added separate demuxing thread in `avformat` producer. * Added `filtergraph` property to the `avformat` producer. * Fixed filter `movit.convert`'s CPU image converter in `mlt_tractor` and `mlt_frame_clone()`. * Fixed using `movit` module with mlt_chain. * Fixed 10-bit full range YUV color input with Movit. * Fixed the `movit.luma` transition. * Changed the `qglsl` consumer to use an OpenGL core profile version 3.2 context to make it compatible with recent Movit versions. * Fixed aspect ratio issues in `qtblend` filter transform. * Upgraded `glaxnimate` git submodule to version 0.5.2. * Fixed `xml` producer incorrectly adds a path prefix to a `consumer` producer. * Fixed using `opencv.tracker` filter with `mlt_chain`. * Added interlace-aware chroma conversion from mlt_image_yuv422 to yuv420p in the `avformat` consumer. * Added the `speed_map` property to the `timeremap` link. * Fixed the `loader` producer not injecting the `consumer` producer when a `xml` producer changes the frame rate. * Fixed 'loader' producer corrupts the profile colorspace and description when it injects a `consumer` producer. * Added a `loader-nogl` producer to the core module based on `loader` but prevents adding `movit`-based filters. * Changed `count` producer to take an optional string argument with the name of a loader producer. * Fixed `yadif` deinterlace not working in a mlt_chain. * Fixed the bob, weave, greedy, onefield `deinterlace` filter methods on x86-64 architecture. Other * Fixed SWIG python shadow functions for mlt7. * Added CMake build option `MOD_GLAXNIMATE_QT6`. Version 7.12.0 This version is released soon after 7.10.0 to fix a couple of major new bugs in the popular `qtblend` and `frei0r.cairoblend` transitions. It also includes new color animation APIs with sensible interpolation! Framework * Added new color animation APIs: - `mlt_property_set_color()` - `mlt_property_get_color()` - `mlt_property_anim_set_color()` - `mlt_property_anim_get_color()` - `mlt_properties_anim_set_color()` - `mlt_properties_anim_get_color()` - `Mlt::Properties::anim_get_color(char const*, int, int)` - `Mlt::Properties::anim_set(char const*, mlt_color, int, int, mlt_keyframe_type)` Modules * Updated the following services to support animation of color properties: - `frei0r` (any color parameter in any frei0r plugin) - `chroma` - `chroma_hold` - `audiolevelgraph` - `audiospectrum` - `audiowaveform` - `gpsgraphic` - `gpstext` - `qtcrop` - `qtext` * Added `discontinuity_reset` property to `dynamic_loudness` filter. * Fixed `qtblend` transition not blending with an opaque rgba image. * Added support for the "finer" engine in Rubberband version 3. * Fixed crash in `frei0r.cairoblend` when `threads` property not set. Other * Fixed leaking the xml producer in `melt` when the XML contains a `consumer` element but no profile information. * Fixed symbol not found error in `rtaudio` consumer. Version 7.10.0 The highlight of this version is support for Qt 6. Framework * Fixed some unguarded null pointers. * Added `MLT_REPOSITORY_DENY` environment variable to skip loading a module (colon delimited list of file names without extension, for example libmltqt). * Fixed frame corruption with one frame transition * Changed so-called test-card frame with audio to show a checkerboard: - Added `mlt_image_fill_checkerboard()` - Added `mlt_image_fill_white()` * Preserve the producer `creation_time` property when creating a chain. * Added `mlt_image_rgba_opaque()`. * Fixed getting a property as a timecode or clock value with 24 or 23.98 fps in `mlt_property.c`. Modules * Added support for Qt 6: - Added `MOD_QT6` and `BUILD_TESTS_WITH_QT6` CMake options. - Allow installing building and installing both Qt 5 & 6 modules. - Avoid loading both Qt 5 & 6 modules by preferring Qt 5 (use MLT_REPOSITORY_DENY=libmltqt to block Qt 5 and use Qt 6). - This is limited to the `qt` module for now and not `glaxnimate` (still a work-in-progress). * Added support for WebP animation to `qimage` producer. * Added `gps_graphic` filter to the `qt` module. * Added the `format` property in each producer's get_frame method to indicate the producer's default/preferred mlt_image_format to facilitate an optimization in the `qtblend` transition when the B frame is opaque and has the same aspect ratio. * Added property animation to all audio visualization filters in the `qt` module. * Improved TGA format detection in `qimage` filter. * Fixed `qtblend` transition has incorrect scaling with consumer scaling. * Fixed an case of incorrect alpha scaling in `qtblend` transition. * Fixed `luma` transition not updated when `resource` property changes. * Added the `alpha_operation` property to the `shape` filter. * Updated the `glaxnimate` git submodule to version 0.5.1. * Fixed `lines` filter in `oldfilm` regression in v7.6.0. * Added `dbpeak` property to the `audiolevel` filter in dB. * Fixed memory leak using some frei0r plugins in conjunction with an `affine` that animates the `rect` property. Other * Fixed building for musl. * Fixed underlinking iconv in `gdk` module on MinGW. * Fixed SWIG CMake options can overwrite each other. * Fixed SWIG 4 no longer generates a `mlt.php`. Version 7.8.0 This highlight of this version is a new glaxnimate producer to render 2D vector art and animation. Framework * Added `mlt_frame_get_alpha_size()` and refactored code to use it. * Fixed a possible null pointer crash in `mlt_service_apply_filters()`. Modules * Added a `glaxnimate` producer to the glaxnimate module. * Added new file extensions for `glaxnimate` producer: json, lottie, rawr, tgs. * Removed Qt4 compatibility from the qt module. * Added Qt6 compatibility to the qt module. * Added new file extensions for `qimage` producer: avif, heic, heif, jxl. * Fixed `color_range` when using the `multi` consumer. * Fixed reloading updated `results` in the `loudness` filter. * Fixed `image_mode=blend` in the `timeremap` link. * Fixed crash regression in `swscale` filter with odd size YUV image. * Fixed the `choppy` filter may result in black frames with transitions. * Prevent a crash in `avfilter` producer for a bug in glibc with `_FORTIFY_SOURCE=3`. Version 7.6.0 This version adds image slice-threading to many filters and full support for full range color. All inputs are normalized to and processed at the range specified by the consumer property `color_range` that defaults to tv/mpeg (limited). Framework * Added `Mlt::Animation::next_key()` and `previous_key()` with error checking. * Fixed the `moduledir` and `mltdatadir` variables in the pkg-config file. * Removed calling `setlocale()` in `mlt_factory_init()` (moved to `melt` option `-setlocale`). * Added `mlt_properties_copy()` and `Mlt::Properties::copy()`. * Changed some primarily internal property names to consolidate on "consumer." as a prefix convention for all consumer properties copied to `mlt_frame`s. * Added consumer property `deinterlacer` to replace deprecated `deinterlace_method`. * Fixed full range color from producer to consumer. * Added `mlt_slices_size_slice()` helper function. * Fixed choppy playback due to large values in `frame_rate_num` or `frame_rate_den` in `mlt_consumer`. * Added performance optimization for a single slice in `mlt_slices`. Modules * Added `audiolevelgraph` video filter to the `qt` module. * Added property `segment_gap` to the `audiospectrum` video filter. * Added `segments` property to the `audiolevelgraph` and `audiospectrum` filters. * Fixed loading image sequence with extended UTF-8 characters in the name of a folder for the `qimage` producer. * Fixed a crash in `avformat` producer if the `rotate` property is set after the first frame is fetched. * Added the `invert_mask` property to the `shape` video filter. * Changed `avformat` producer to normalize frame rates very close to non-integer broadcast frames 24/1.001, 30/1.001, and 60/1.001. * Converted the `chroma` and `chroma_hold` filters' `key` property to a proper color type. * Added slice threading to: - `avformat` producer (with FFmpeg v5) - `swsscale` (with FFmpeg v5) - `lift_gamma_gain` - `shape` - `charcoal` - `vignette` - `wave` - `threshold` - `tcolor` - `sepia` - `mirror` - `invert` - `grain` - `lines` - `spot_remover` * Improved the speed of the `oldfilm` filter. * Added a faster `box_blur` filter to the core module and deprecated the `boxblur` filter in the kdenlive module. * Fixed preview scaling for the `avfilter.gblur` filter. * Fixed incorrect text overlap in `kdenlivetitle` producer. * Improved audio synchronization in `avformat` when playing in reverse. * Added much more service metadata (documentation). * Fixed full range 10-bit video input in `avformat` producer. * Fixed full range color handling in: - `avformat` producer - `avcolor_space` - `brightness` - `resize` - `luma` transition - `movit.convert` - `charcoal` - `invert` - `shape` * Fixed identifying unsupported colorspaces in `avformat` producer. * Fixed preserving the alpha channel in the `avfilter.fspp` filter. Other - Some CMake fixes. - Added `dumb-init` to the docker (no need to remember `docker run --init`). Version 7.4.0 The main highlight of this version is property animation for avfilter! Framework * Added more constructors and assignment operators in C++ wrapper: - `Mlt::Filter::Filter(Mlt::Filter*)` - `Mlt::Link::Link(Mlt::Link*)` - `Mlt::Link::Link(Mlt::Service&)` - `Mlt::Link::Link(Mlt::Link&)` - `Mlt::Link::Link(Mlt::Link const&)` - `Mlt::Link::operator=(Mlt::Link const&)` - `Mlt::Service::Service(Mlt::Service*)` * Fixed serialized animation in `mlt_animation_serialize_cut_tf()` and `mlt_animation_serialize_cut()` to include a trailing keyframe value. Modules * Added property animation for `avfilter` filters. This only works for numeric parameters, but many libavfilter options that have a type string are actually numeric in nature but accept a string expression. * Added `rotate` property to `avformat` producer to override orientation. * Changed `jackrack` module to silence false LADSPA plugin loading errors. * Fixed a crash in the `oldfilm` filter when using preview scaling. * Fixed `timeremap` link distorts audio when speed is zero. * Added nautical mile and knot units of measure to the `gpstext` filter. * Fixed full range color handling with embedded tractor (e.g. same track transition). * Fixed device capture in `avformat` producer regression in version 7.2.0. * Fixed a crash in the `matte` transition. Version 7.2.0 This is the first major maintenance release for the new major version 7 rendering it much more production ready. Plus there are a few nice new features. Framework * Added support for `mlt_properties` as a child of `mlt_properties` including XML (de)serialization: - `mlt_property_set_properties()` - `mlt_property_get_properties()` - `mlt_properties_set_properties()` - `mlt_properties_get_properties()` - `mlt_properties_get_properties_at)(` - `Mlt::Properties::set()` - `Mlt::Properties::get_props()` - `Mlt::Properties::get_props_at()` Applications can use this to store structured data in its own namespace, for example "shotcut:markers". And modules could use this for hierarchical parameters. * Fixed crash in `mlt_transition` upon inserting or removing a track. * Stopped loading `mlt_profile` until needed in `mlt_chain` creation. Modules * Added filter `gpstext` that is similar to `dynamictext` based on data in a GPX file. * Added speed parameter to `timer` filter. * Added WebP presets for `avformat` consumer. * Added a pixelate option to the `opencv_tracker` filter's `blur` property. * Fixed `center_bias` of `crop` filter not working with `use_profile`. * Fixed some missing RGB `mlt_image_format` renames after change in v7.0.0. This primarily affected presets and service metadata. * Fixed a crash when changing preview scaling in `timeremap` link. * Fixes problems due to adding redundant normalize filters upon loading a producer from XML. * Ensure filters added by the `loader` producer always come first in list. * Fixed a crash using `shape` and `affine` filters together on `color` producer. * Fixed a crash when a `vidstab` file fails to open. * Changed `vidstab` filter to save its file in ASCII text mode. * Fixed a clang LTO error in the `decklink` module. * Fixed a video decoding regression on some videos in the `avformat` producer. * Fixed a crash in the `audiowaveform` filter. * Fixed loading a relative filename from XML for `mask_start` with `shape`. * Fixed "#filedate#" in `dynamictext` filter when used with `timeremap` link. * Fixed `timer` filter's new `speed` property interaction with `start` delay. * Fixed a crash with YUYV422 (YUY2) input in `avformat` producer. * Fixed data race condition in `timeremap` link. * Fixed compiling `avformat` module with FFmpeg git beyond v4.4 with many deprecations removed. * Fixed alpha channel size calculation in `brightness` filter. * Restore legacy tracker and the new DaSiam tracker for OpenCV >= 4.5.3 in the `opencv_tracker` filter. * Fixed a crash in `opencv_tracker` on `shape_width` = 0. * Fixed incorrect handling of in and out points and duration in the `opencv_tracker` filter. * Fixed the `composite` transition leaking left border of an image on the right side on uneven width. * Fixed a problem handling some UTF-8 in thhe `typerwriter` filter. Other * Added support for the `RELOCATABLE` CMake option for Linux or BSD build. Version 7.0.1 This version is just build fixes for the most immediate problems with the somewhat new but exclusive build system in v7. * Fixed docker image not working. * Fixed a system-installed build cannot finds its modules and data. * Fixed the python installation path for binaries. * Added support for the `DESTDIR` environment variable when creating melt symlink. * Increased the build constant for the maximum size of a line of a properties file. * Fixed the vid.stab metadata install path. Version 7.0.0 This is a major new version that breaks API to add a major new feature to the framework: retiming. This is accomplished through new classes `mlt_chain` and `mlt_link`. And since we are breaking API we decided to clean house by removing deprecations and switching the build system over entirely to CMake. For more information see our [migration guide](https://mltframework.org/docs/v7migration/). Framework * Added `mlt_chain` and `Mlt::Chain` classes. * Added `mlt_link` and `Mlt::Link` classes. * Added a `link` value to service `type` in the service metadata schema. * Added a boolean `animation` parameter attribute to the service metadata schema. * Added `mlt_animation_shift_frame()` and `Mlt::Animation::shift_frames()`. * Added `mlt_animation_get_string()`. * Fixed using a stale cached property animation string. * Added `mlt_image` and `Mlt::Image` classes. * Remove legacy "height + 1" workaround in image allocation. * Fixed a crash on setting `timewarp` speed higher than 23x. * Added `mlt_audio_silence()`. * Removed `mlt_image_opengl`. * Replaced variadic arguments in `mlt_events` with new `mlt_event_data` APIs. * Removed `mlt_geometry` APIs. * Renamed `mlt_image_rgb24a` as `mlt_image_rgba`. * Renamed `mlt_image_rgb24` to `mlt_image_rgb`. * Renamed `mlt_image_glsl` to `mlt_image_movit`. * Renamed `mlt_image_glsl_texture` to `mlt_image_opengl_texture`. * Removed virtual function `mlt_frame::get_alpha_mask()`. * Removed `mlt_frame_get_alpha_mask()`. * Removed deprecated functions: - `mlt_sample_calculator` - `mlt_sample_calculator_to_now` - `mlt_channel_layout_name` - `mlt_channel_layout_id` - `mlt_channel_layout_channels` - `mlt_channel_layout_default` - `mlt_slices_init` - `mlt_slices_close` - `mlt_slices_run` - `mlt_playlist_move_region` - `Mlt::Playlist::move_region` * Fixed a rounding error calculating display aspect ratio in `mlt_profile_from_producer()`. Modules * Added a `timeremap` link to the core module with animatable `map` property. (Speed can increase or decrease between keyframes including reverse.) * Added `chain` and `link` XML elements to `xml` module. * Added "meta.media.has_b_frames" property to `avformat` producer. * Removed deprecated modules: - `dv` - `gtk2` (not gdk) - `kino` - `linsys` - `lumas` - `motion_est` - `swfdec` - `videostab` * Removed the following services: - `data_feed` filter - `data_show` filter - `region` filter and transition - `sdl_image` * Converted filters to use new `mlt_image` class: - `brightness` - `imageconver` - `mirror` - `spot_remover` * Deprecated the `audiowave` filter. * Added the ability to build the `jackrack` module without JACK to get only LADSPA producers and filters. * Deprecated `start` and `end` properties for the following filters: - `brightness` - `panner` - `boxblur` - `wave` - `volume` * Removed deprecated `font` property from `pango` producer. * Improved album art (attached pic) detection in `avformat` producer. * Improved the `resample` filter to have less artifacts and use less memory. Other * CMake: nearly complete rewrite. * Removed the old configure bash scripts and Makefiles. * Added `-chain` and `-link` options to `melt` command line. Version 6.26.1 This version fixes a major regression in the avformat producer to read from network URLs. Version 6.26.0 This is the last planned release of major version 6. Version 7 will be released soon and introduce some minor API breakage while removing deprecations. The main new feature in this version is hardware-accellerated decoding! However, this is a basic implementation: It always returns the uncompressed video to the CPU memory with no pipelining to filters. Even when coupled with hardware encoding in the avformat consumer it must transfer the video. Also, there is no automatic software/CPU fallback and no resource management. Modules * Added support for `hwaccel` query string parameter to the `avformat` producer. It accepts the following values: vaapi (Linux/BSD), cuda (Linux), videotoolbox (macOS), d3d11va (Windows), dxva2 (Windows) * Added support for `hwaccel_device` query string parameter to the `avformat` producer. This is only used with vaapi (device path) and cuda, d3d11va, or dxva (number). * Improved the usage of image slice threading in `frei0r`. This only applies when `threads`=0 and only works with some frei0r plugins that you must decide yourself. * Added an ellipse item to `kdenlivetitle` producer. * Added support for PNG and GIF as album art in the `avformat` producer. * Added BT.2020 color space metadata to the `avformat` producer. * Resolved many FFmpeg deprecations in the `avformat` producer making it possible to support AV1 decoding. * Added a `strobe` fitler that periodically makes the alpha channel transparent. * Added a new `typewriter` text filter (currently only works with the kdenlivetitle producer). * Improved sound quality for lower pitch shifts in `rbpitch`. * Fixed speed of trick play in the `jack`, `rtaudio`, `sdl_audio`, and `sdl2_audio` consumers. * Fixed matrix for independent channels in `swresample` filter. * Fixed leading zeros for the `timer` filter. * Fixed flickering using `affine` with a `luma` transition. * Fixed a crash using RGBA images in the `qimage` producer (regression in v6.22.0). * Fixed `brightness` filter misbehaves on `alpha` > 1. * Fixed writing `flac` format file does not set its duration in the `avformat` consumer. * Fixed an infinite loop in `rbpitch` filter. * Fixed `ttl` in the `qimage` producer. * Fixed building with OpenCV 4.5 * Fixed artifacts with multiple HTML `qtext` filters and frame threading. * Deprecated the `start` and `end` properties on the following (use property animation instead): - brightness - panner - boxblur - wave - volume * Deprecated the following services: - data_show - region - transition filter - autotrack_rectangle - motion_est - slowmotion Other * CMake: - Fixed building without SWIG. - Added many "MOD_..." options to explictly disable modules. - Added src/tests and the option `BUILD_TESTING`, which defaults off. - All dependency checks moved to top level CMakeLists.txt. - Install melt man page. - Install oldfilm SVG files. - Added src/examples. - Install framework/metaschema.yaml. - Fixed `plusgpl` datadir. - Added all swwig/ languages. - Increased C++ standard to C++14. * Added an `AV1` encoding preset. * Improved documentation of the requirement for C11. * The minimum version of FFmpeg is v4.0 and Libav is no longer supported. Version 6.24.0 This version is mostly fixes plus a few new filters. Framework * Trigger a `property-changed` event on `mlt_properties_pass_list`. * Fixed using a video transition with a video clip on an audio track. * Reduce the amount of service caching to 2X #tracks to reduce memory usage. Modules * Added the `pillar_echo` filter to the plus module. * Added a `qtcrop` filter to the qt module. * Added `html`, `resource`, `overflow-y`, and `_hide` properties to the `qtext` filter for rich text. * Added the filter `choppy` to the core module. * Added slice threading to the `brightness` filter. * Fixed compiling with OpenCV 4. * Fixed the colors when using `mlt_image_format=rgb24a` with `avformat` consumer. * Fixed using WebVfx in a Docker container. * Fixed a possible crash in the `timewarp` producer on sources with non-integer frame rates. * Fixed a regression in version 6.22 with multiple affine filters at the same time. * Fixed possible abort or deadlock on recursive pthread mutexes in `avformat` producer. * Fixed a crash in `crop` filter with large `center_bias` value when `use_profile` is 1. * Fixed a white video frame appearing on threaded rendering in `freeze` filter. * Fixed MLT XML DRD to permit empty playlists, which may occur on empty tracks in a multitrack. * Fixed initializing QApplication in the `qimage` producer. * Fixed interpolation when scaling with the `affine` rect and geomety properties. * Fixed high memory usage with high factors of pitch shifting in the `rbpitch` filter. * Fixed a crash on files with more than 32 streams in the `avformat` producer. Other * Fixed CMake build on MSYS2 and Windows Craft. * Added the Python binding to the CMake build. * Added the `sdl` (v1) module to the CMake build. * Removed minrate and maxrate from the `webm` avformat consumer preset. Version 6.22.1 - July 30, 2020 This patch version only fixes the version reported in the CMake build. Version 6.22.0 - July 30, 2020 This version fixes bugs associated with the preview scaling introduced in the previous version. Framework * Added mlt_properties_exists() and Mlt::Properties::property_exists(). * Added mlt_audio C class with: - mlt_audio_new() - mlt_audio_close() - mlt_audio_set_values() - mlt_audio_get_values() - mlt_audio_alloc_data() - mlt_audio_calculate_size() - mlt_audio_plane_count() - mlt_audio_plane_size() - mlt_audio_get_planes() - mlt_audio_shrink() - mlt_audio_reverse() - mlt_audio_copy() - mlt_audio_calculate_frame_samples() - mlt_audio_calculate_samples_to_position() - mlt_audio_channel_layout_name() - mlt_audio_channel_layout_id() - mlt_audio_channel_layout_channels() - mlt_audio_channel_layout_default() * Added Mlt::Audio C++ class with: - Mlt::Audio::Audio() - Mlt::Audio::Audio(mlt_audio_s*) - Mlt::Audio::~Audio() - Mlt::Audio::data() - Mlt::Audio::set_data(void*) - Mlt::Audio::frequency() - Mlt::Audio::set_frequency(int) - Mlt::Audio::format() - Mlt::Audio::set_format(mlt_audio_format) - Mlt::Audio::samples() - Mlt::Audio::set_samples(int) - Mlt::Audio::channels() - Mlt::Audio::set_channels(int) - Mlt::Audio::layout() - Mlt::Audio::set_layout(mlt_channel_layout) * Fixed drop-frame timecode for 59.94 fps. * Fixed crash on null pointer passed to mlt_consumer_stop(). Modules * Fixed frei0r transitions with preview scaling. * Fixed affine ox and oy properties incorrect with preview scaling. * Fixed a crash and incorrect preview scaling with more than one affine filter active on the same frame. * Fixed preview scaling for the rotoscoping filter. * Added the sample_fmt property to the avformat consumer. * Fixed a possible segfault in the mix transition. * Removed support for text keyframes to the text and qtext filters to fix regression on strings containing '='. * Disable frame-threading with bigsh0t, distort0r, and medians frei0r plugins. * Added "meta.media.%d.stream.projection" property the avformat producer. * Fixed a crash with with filters not supporting preview scale in frei0r transitions. * Fix artifacts in luma transition and affine filter with frame-threading. * Stop including 'title="Anonymous Submission"' in xml consumer. * Fixed a crash in opencv.tracker filter. * Fixed a crash in composite transition if luma file fails to load. * Added validations in opengl module to prevent asserts in Movit. * Fixed building with OpenCV 4. * Moved some services from gtk2 module to new gdk module: - gtkrescale filter - pango producer - pixbuf producer * Deprecated the gtk2 module and no longer enabled by default. * Changed avformat producer to accept a '?' in argument/resource property by escaping it as '\?'. * Changed the background property of the affine filter to be mutable. * Deprecated the linsys (DVEO SDI) module. * Fixed changing the audio_index property in the avformat producer. * Changed resample filter to more resiliant to frequency changes. * Added a video_delay property to the sdl2_audio and rtaudio consumers. * Add millisecond options to the timer filter. * Fixed the in point handling for the timewarp producer. * Fixed some audio gaps and sync issues with the rbpitch filter and timewarp pitch compensation. * Fixed a possible crash caused by producer consumer. * Changed avformat consumer to set AVOption color_primaries based on the MLT colorspace if not already set as property. * Fixed crop right on image with odd width skews image in crop filter. * Fixed incorrect silence value for unsigned 8-bit audio in avformat producer. * Changed qimage to use Qt's internal orientation detection instead of libexif. * Reduced clicks in mix transition by silencing buffers on discontinuity. * Improved A/V synchronization in (sw)resample filters - also reduces audio clicks. * Improved speed of the qimage producer. * Fixed incorrect color using libx264rgb in avformat consumer. * Fixed relative paths for avfilters that have the "filename" option. * Fixed some avfilters dropping the alpha channel: smartblur, vaguedenoiser. * Improved performance of the resize filter. * Fixed an affine filter inside a transition was always nearest neighbor interpolation. * Changed the lift_gamma_gain filter to use round values up. Other * Fixed melt option "-group" applies to an implicit consumer. * Added "-quiet" option to melt (implies -silent but more so). * CMake build improvments adding modules: - gdk - jackrack - lumas - resample - sox - vorbis * Added avformat consumer presets: - Slide-Deck-H264 - Slide-Deck-HEVC * Removed intra=1 from some avformat presets (use g=1 for intra only): - intermediate/MPEG-2 - intermediate/MPEG-4 - lossless/H.264 * Fixed using Qt, Movit, and WebVfx in the official docker image: https://hub.docker.com/repository/docker/mltframework/melt IMPORTANT: it now requires `docker run` with the `--init` option. Version 6.20.0 - February 15, 2020 This version adds support for low resolution preview scaling and adds a module based on librubberband for audio pitch-shifting. An official docker image is now available at https://hub.docker.com/repository/docker/mltframework/melt Framework * Added consumer scaling: - mlt_profile_scale_width() - mlt_profile_scale_height() - Mlt::Profile::scale_width() - Mlt::Profile::scale_height() - support for a double "scale" property to melt and the xml producer * Fixed mlt_properties_set() with an invalid expression. * Added new functions that do not evaluate expressions: - mlt_properties_set_string() - Mlt::Properties::set_string() * Improved the service-caching heuristic in mlt_multitrack. * Fixed possible crashes in mlt_playlist get_frame() and mlt_filter_process(). Modules * Added the rubberband module with a rbpitch filter. * Added pitch compensation to timewarp producer. * Added the invert_scale property to the affine filter and transition. * Added the reverse property to shape filter. * Added support for text keyframes to the text and qtext filters. * Added support for the CSRT and MOSSE algorithms in opencv.tracker filter. * Fixed a crash on empty algo property in the opencv.tracker filter. * Changed vorbis module to no longer be deprecated. * Improved colorspace conversions in the avformat module. * Fixed audio artifacts on initial seek to in point in avformat producer. * Fixed the colorspace of the cached image in avformat producer. * Fixed white video flashes on property changes in the qtext filter. * Fixed a crash in the rotoscoping filter with large spline deviations. * Fixed a crash in the sdi consumer if the driver is not loaded. * Improved support for a video clip as luma producer to the luma transition. * Fixed a crash in the matte transition. * Fixed a crash when using invert property =1 in the composite transition. Other * Added a Dockerfile and integrated docker build into Travis CI. * Added more avformat consumer presets: - intermediate/DNxHR-HQ - intermediate/ProRes HQ - ALAC - FLAC * Fixed some parameters in the XDCAM and D10 avformat presets. * Fixed link failure on some CPU architectures. Version 6.18.0 - November 11, 2019 This version is a general maintenance release with a bunch of fixes, improvements, and additions. Framework * Fixed some data races in mlt_consumer, mlt_deque, and mlt_property. * Fixed the mlt_events listener incorrect owner argument. * Added support for the LC_ALL environmant variable on Windows. * Fixed the argument to mlt_factory_init() not working on Windows. * Fixed mlt_service_identify() not reliable in some use cases. * Added some default and copy constructors and assignment operators to mlt++ - Filter() - Filter( const Filter &filter ) - Filter& operator=( const Filter &filter ) - Producer( const Producer &producer ) - Producer& operator=( const Producer &producer ) - Properties( const Properties &properties ) - Properties& operator=( const Properties &properties ) - Service( const Service &service ) - Service& operator=( const Service &service ) - Transition() - Transition( const Transition &transition ) - Transition& operator=( const Transition &transition ) * Added mlt_luma_map: - mlt_luma_map_init - mlt_luma_map_new - mlt_luma_map_render - mlt_luma_map_from_pgm - mlt_luma_map_from_yuv422 * Fixed preset overrides depend on the XML attribute order. * Fixed serializing an animated property with a new length. Modules * Fixed interpolation in rotoscoping filter. * Fixed crop filter not working with color producer. * Fixed some data races in the sdl and sdl2 consumers. * Fixed some data races in the avformat producer. * Added a movit.flip filter to the opengl module. * Fixed using filters on frei0r producers. * Added support for in and out attributes on the "consumer" xml element. * Fixed using an in point with the multi consumer. * Fixed avfilter fails if the image size changes. * Fixed showing superfluous decimals for seconds in the timer filter. * Stop serializing an invalid producer as an "INVALID" text producer in xml. * Fixed an access violation crash in wave filter. * Added the meta.media.color_range property to the avformat producer. * Fixed full range yuv422p not converted correctly in the avformat producer. * Fixed the text filter not working with pango. * Fixed a regression using dynamictext with pango. * Added a position property to avfilter for filters that need position info. * Fixed avfilter.subtitles not using the source position. * Added an analyze property to vidstab filter. When set, analysis only starts and the results file written if true. * Fixed crash combining affine the affine filter with the shape filter. * Added interlace detection from AVCodecContext.field_order. * Changed the avformat producer to not use the rescale.interp frame property. Previously, when interp == nearest, it would relax seeking. Now, seek accuracy is reduced during trick play (rewind or fast forward). * Fixed sws flags for auto-inserted scalers in avfilter. * Fixed a double free crash in ladspa filter on channel count mismatch. * Refactored the composite and luma transitions to use mlt_luma_map. * Refactored the pgm producer and shape filter to use mlt_luma_map. * Refactored the lumas module to use mlt_luma_map. * The lumas module is now disabled by default and must be explicitly enabled. * Added property animation to the threshold filter. * Added a cairoblend_mode filter to the frei0r module to affect a frei0r.cairoblend transition used to composite/blend tracks. * Added support for new vaapi options to the avformat consumer: - connection_type: x11 or drm - driver - kernel_driver * Fixed the timewarp producer with a colon in the filename. * Fixed a relative file name with a colon in it in the xml producer. * Fixed defaulting to album or poster art if there is another video stream. * Fixed parameter animation in frei0r plugins when using frame threads. This change also enables frame-threading for more plugins. * Improved the qtblend filter to not process alpha if no transparency. * Added a background_color property to the qtblend filter. * Fixed the opencv.tracker incorrect behavior on cut clips. * Changed opencv.tracker to store absolute frame numbers. * Fixed incorrect frame offset on render in opencv.tracker. * Add an alpha_over property to luma transition. This addresses a behavior regression in version 6.14.0. * Fixed noimagecache not working in the avformat producer. Other * Mlt++ now requires C11 compiler support. * Fixed closing melt SDL2 window from window manager (i.e. close button). * Added -repository option to the melt command. * Added unit tests for Mlt::Event. * Fixed returning image data for Python 3. * Switch to python3 by default. * Updated the prores encoding presets to set vendor ID and colr atom. * Added a CMake build system. This is not yet prefered over the existing configure script and Makefiles and has less flexibility. It is a start and has limited support. Version 6.16.0 - May 7, 2019 This version is released to facilitate packaging the latest version of Shotcut, which is using new APIs. Framework Added functions to get/set a creation date to a producer: - mlt_producer_get_creation_time() - mlt_producer_set_creation_time() - Mlt::Producer::set_creation_time() - Mlt::Producer::get_creation_time() Modules * Fixed dance filter not showing when lower track is transparent. * Refactored dynamictext filter to use mlt_producer_get_creation_time(). * Marked frei0r rgsplit0r plugin version < 1.1 as not thread-safe. * Fixed possible null pointer crash in mlt_properties_serialise_yaml. Version 6.14.0 - March 30, 2019 This version is mostly fixes plus a few API additions and filters. Framework * Added mlt_profile_lumas_dir(). * Added mlt_frame_get_unique_properties(). * Added mlt_playlist_reorder() and Mlt::Playlist::reorder(). * Added some new convenience constructors to mlt++ - Producer(mlt_profile profile, const char *id, const char *service = NULL) - Consumer(mlt_profile profile, const char *id , const char *service = NULL) - Transition(mlt_profile profile, const char *id, const char *arg = NULL) - Filter(mlt_profile profile, const char *id, const char *service = NULL) - Tractor(mlt_profile profile, char *id, char *arg = NULL) * Added Mlt::Transition::connect(Service&). * Added unit tests for mlt_playlist. * Fixed a crash on invalid transition track values in mlt_transition. * Fixed a deadlock regression in v6.12.0 of mlt_consumer when starting from a paused state (producer speed=0). Modules * The avformat module now requires at least FFmpeg v2.4 or Libav 12. * Added mask_start and mask_apply filters to the core module. * Added qtext filter to qt module. * Changed dynamictext and timer filters to use qtext. * Fixed number of digits for seconds in timer filter. * Added mlt_image_format property to color producer. * Improved color accuracy of libswscale RGB->YUV conversion. * Fixed frei0r producer not working with tractor. * Fixed decklink consumer stalling on dropped frames. * Generate lumas for 16:9, 9:16 (vertical), and square aspect ratios. * Fixed crash in qimage when alpha_size is zero. * Fixed the mlt_consumer channels property not being passed to multi consumer. * Fixed the shape filter for full range color and crashes. * Converted the shape filter to use mlt_animation. * Added a use_mix property to the shape filter. * Fixed invert=1 and mix=100 gives wrong image in shape filter. * Fixed a possible free null pointer in the linsys sdi consumer. * Fixed using destroyed temporary object in qimage. * Fixed a possible null pointer dereference in the spot_remover filter. * Fixed memory leak on swr_convert() failure in swresample filter. * Fixed possible null pointer dereference in affine when not using rect. * Fixed loading image sequence on Windows in qimage. * Fixed some null pointer crashes using Movit opengl services. * Fixed sdl2 consumer crashes during initialization on Linux or BSD. * Fixed distorted image using melt_file. * Fixed qimage build on Qt version < 5.5. * Added offset property to the timer filter. * Changed the boxblur hori & vert properties' minimum to 0. * Fixed crash in duplicate frame on rotated videos. * Added automatic scaling and padding to avfilter. * Fixed field order when encoding progressive as interlace. * Fixed frei0r plugins to use the number of slices from the threads property. * Fixed over compositing with transparent clips in luma transition. * Added sliced processing to dissolve-with-alpha using the threads property. * Added createdate keyword to dynamictext filter. * Fixed possible crash changing audio_index in avformat producer. * Fixed small memory leaks in xml consumer, jackrack, and timewarp producer. * Fixed compiling opencv module with OpenCV > 3. Other * Added vertical video profiles: - vertical_hd_30 - vertical_hd_60 * Mlt++ now requires C++11 compiler support. * Added --disable-windeploy to configure to keep bin & lib folders on Windows. * Added support for consumer in & out to melt. * Fixed color accuracy of lossless/Ut Video preset and use pix_fmt yuv422p. * Fixed x264 lossless preset to use crf=0. * Fixed compiling with mingw32. * Fixed build with Python 3. Version 6.12.0 - November 26, 2018 This version has many important fixes plus a few new filters and support for encoding using VA-API. Framework * Changed buffer property to be mutable and adaptive to speed property in mlt_consumer. * Changed macOS RELOCATABLE build to use standard app bundle layout: - lib/mlt -> ../PlugIns/mlt - lib/frei0r-1 -> ../PlugIns/frei0r-1 - lib/ladspa -> ../PlugIns/ladspa - share/mlt -> ../Resources/mlt - share/movit -> ../Resources/movit * Fixed a_track of transitions matching deleted track in mlt_tractor_remove_track(). * Fixed multi-thread race crash in mlt_properties_clear(). * Fixed possiblle null pointer crash in mlt_property_get_rect() and mlt_property_get_time(). * Fixed non-animated strings containing ';' or '=' in mlt_animation_parse(). * Fixed crash in clear_property() with mlt_animation. Modules * Added a generic text filter to the plus module. * Added a timer filter to the plus module. * Added audio timeout handling to sdl2 consumers. * Added spot_remove filter to the plus module. * Added dds, ico, and webp filename extensions for qimage producer. * Added support for color_range property in avformat consumer: "pc" or "jpeg" for full range, otherwise limited range. * Added a window property to the audiowaveform filter. * Added MM:SS.SS to the timer filter. * Added query string param "multi" to the xml producer to force using the multi consumer. * Improved WebP image support in avformat producer. * Integrated hwupload filter in avformat consumer if using VAAPI codec. * Changed count producer to use pango if qtext not available. * Changed qt moduled to not call XInitThreads() * Changed color producer to only set alpha on frame if rgb24a requested or not opaque. * Changed the xml producer to pass quality and performance parameters to the multi consumer. * Fixed sdl2_audio distortion (regression in v6.10.0). * Fixed dynamictext filter to not error on empty text. * Fixed dynamictext aliased (regression in v6.10.0). * Fixed qimage outputs premultiplied if scaled internally. * Fixed crash in cbrts consumer if running property was never set. * Fixed rendering edges of some typefaces in qtext producer. * Fixed qimage fails to load with wrong filename extension. * Fixed affine dark right and bottom edge artifacts regression in (v6.10.0). * Fixed support for vp8 and vp9 with alpha channel in avformat producer. * Fixed interpolation mode selection in qimage producer. * Fixed crash in qimage with alpha channel. * Fixed some AAC MP4 files start playing from middle in avformat producer. * Fixed crash in avfilter if initialization fails. * Fixed crash in mix when frame rate is very low. * Fixed crash on missing luma file in composite transition. * Fixed A/V sync on some files in avformat producer. * Fixed seeking on audio filter with album art in avformat producer. * Fixed colorspace conversion in avformat consumer. Other * Added more avformat consumer presets: - alpha/Quicktime Animation - alpha/vp8 - alpha/vp9 - alpha/Ut Video - lossless/Ut Video * Added square video profiles: - square_1080p_30 - square_1080p_60 * Added support for nodejs to the swig bindings. * Changed configure script to require opencv module be explicitly enabled. * Numerous spelling fixes in source code and comments thanks to codespell. Version 6.10.0 - July 2, 2018 This version fixes bugs and supports serializing animation keyframes with a specified time format (previously only frame number). Framework * Reverted mlt_pool change in v6.8.0 pending further testing. (USE_MLT_POOL compiler define is now a 0/1 boolean, defaults to 1.) * Fixed crash regression in v6.8.0 "parsing non-animated string as an animation." * Added pointer checks to mlt_animation. * Changed producer cache size heuristic in mlt_multitrack to be more liberal. * Fixed handling reserved characters in names for YAML in mlt_properties. * Added clamping to prevent computing negative in and out points to mlt_producer. * Added functions to serialize animation with a time format: - mlt_animation_serialize_cut_tf() - mlt_animation_serialize_tf() - mlt_property_get_string_tf() - mlt_property_get_string_l_tf() - mlt_properties_get_value_tf() - Mlt::Properties::get(int, mlt_time_format) - Mlt::Animation::serialize_cut(mlt_time_format, int, int) * Added functions to clear a property to mlt_properties: - mlt_property_clear() - mlt_properties_clear() - Mlt::Properties::clear() Modules * Fixed enabling sliced pix_fmt conversion in avformat producer. * Fixed incorrect seek and sync on audio files with discard packets. * Added support for avcodec_send_frame() API to avformat consumer. * Fixed compile errors with Libav master. * Fixed a crash in affine transition. * Fixed a crash in ladspa filters when consumer frame rate is low (e.g. <= 8). * Fixed a crash in boxblur filter. * Added animation support to boxblur hori and vert properties. * Fixed a crash in movit.convert. * Fixed incorrect alpha in affine transition blending routine. * Converted frei0r from deprecated mlt_geometry to mlt_animation API. * Fixed tilde in text string for pango producer. * Fixed using more than one channelcopy filter. * Fixed the mono filter reducing volume level. * Fixed degraded audio scrubbing in sdl2_audio consumer. * Converted dynamictext filter to use affine transition for more correct alpha compositing and sub-pixel positioning. * Added time format support for animation keyframes to the xml consumer. * Added animation support to more affine transition properties: - fix_rotate_x - fix_rotate_y - fix_rotate_z - fix_shear_x - fix_shear_y - fix_shear_z - ox - oy - scale_x - scale_y * Fixed gaps in text when characters overlap in qtext and kdenlive producers. * Fixed a crash in pixbuf producer with multiple render threads. * Converted the oldfilm vignette filter from mlt_geometry to mlt_animation. Other * Numerous updates to mlt-xml.dtd. * Categorized many of the encode presets (using meta.preset.name). Version 6.8.0 - May 10, 2018 This version improves support for multi-channel audio and adds some new manipulation functions to the mlt_animation API. Framework * Added support for musl C library. * Added functions for audio channel layouts: - mlt_channel_layout_name() - mlt_channel_layout_id() - mlt_channel_layout_channels() - mlt_channel_layout_default() * Added channel_layout property to mlt_consumer. * Added mlt_channel_layout enum. * Disabled memory pooling by default and require compile macro USE_MLT_POOL to re-enable it. * Fixed reliability of keyframed properties serializing properly. * Fixed parsing non-animated string as an animation. * Added more functions to mlt_animation: - mlt_animation_key_set_type() - mlt_animation_key_set_frame() - Mlt::Animation::key_set_type() - Mlt::Animation::key_set_frame() Modules * Fixed some crashes in qimage producer especially with alpha channel. * Fixed >2 channel audio output in the SDL consumers. * Fixed >2 channel audio output in the rtaudio consumer on Windows. * Fixed vorbis encoding with FFmpeg v3.4+. * qimage and qtext are now higher priority than gtk2 pixbuf and pango by the loader producer. * Added support for more channel counts to decklink consumer. * Added swresample filter based on libswresample from FFmpeg. This is now the preferred channel count normalizing filter used by the loader producer. * Fixed the strange "Undefined constant" and "Unable to parse option value" log messages in the the avformat consumer. * Fixed GIF and DPX writing in avformat consumer. * Reduced the memory usage of the affine transition and filters. * Fixed a crash in kdenlivetitle producer. * Fixed a crash in the rotoscoping filter. * Fixed frame rate reported in Matroska and WebM files produced by the avformat consumer. * Added sdl2_audio consumer. * Fixed alpha channel support for more pixel formats in the avformat producer. * Converted the affine transition to use mlt_rect and mlt_animation. * Fixed LADSPA plugins with mono channel audio. Other * Fixed a melt command line parsing bug when argument supplied to -transition. * Fixed melt with SDL2 on Windows not using stdio and stderr. * Improved speed of the vp9 avformat consumer preset. Version 6.6.0 - January 22, 2018 This version builds upon the previous release with performance improvements using the sliced image processing framework. It also improves compatibility with dependencies (FFmpeg, Qt 5, SDL 2, NDI, OpenCV, libebur128). Framework * Added a thread pool to mlt_slices: - mlt_slices_run_normal() - mlt_slices_run_rr() - mlt_slices_run_fifo() - mlt_slices_count_normal() - mlt_slices_count_rr() - mlt_slices_count_fifo() - MLT_SLICES_COUNT environment variable * Added mlt_log_timings_now() to mlt_log. * Added mlt_image_yuv422p16 image format. * Added mlt_image_format_planes() and mlt_image_format_id() to mlt_frame. * Added mlt_service_disconnect_all_producers() and Mlt::Service::disconnect_all_producers() * Fixed accuracy of mlt_time_format conversions. * Fixed mlt_filter_get_progress() never reaching 1.0. * Fixed divide by zero in mlt_filter_get_progress(). Modules * Added sdl2 module! * Added sum property to mix transition for simple mixing without dynamics. * Added TLD and KCF tracking modes to opencv module. * Added CSV (comma-separated values) simple slideshow to pixbuf producer. * Added jack filter to jackrack module. * Added sliced processing to frei0r. * Added sliced processing to composite transition. * Added sliced processing to avformat producer pixel format conversion. * Added sliced processing to affine transition and filter. * Converted volume and pan filters to floating point. * Added rotate_center property to qtblend transition and filters. * Added meta.media.variable_frame_rate (VFR detection) to avformat producer. * Added support for external libebur128. * Updated internal libebur128 to latest. * Added 10-bit capture to decklink producer. * Added mlt_image_yuv422p16 to fieldorder filter, avcolour_space filter, and avformat consumer. * Added no_profile property to xml consumer. * Various ndi module fixes and improvements. * Fixed setting color_trc in multi consumer. * Fixed kdenlive title transparency. * Fixed incorrect alpha channel breaking freeze filter with qtblend transition. * Fixed transparency of pixbuf images with qtblend transition. * Fixed some crashes in new qtblend transition. * Fixed building qt module with C++11 for Qt 5.7+. * Fixed reporting accurage length for image sequences in qimage and pixbuf. * Fixed extra audio samples added to end of file in avformat consumer. * Fixed rawvideo export in avformat consumer. * Fixed multi-threading with FFmpeg 3.2+ in avformat module. * Fixed JPEG album art in avformat producer. * Removed filter_avresample (dropped by FFmpeg). * Stop flushing audio buffer in decklink consumer. * Fixed field order handling in decklink consumer. * Make composite transition field order aware. * Fixed nesting mlt_tractor with opengl module. * Fixed kdenlive titler crash on resized frame. Other * Various memory leak fixes. * Fixed opening files with extended chars on Windows (framework and modules). Version 6.4.1 - November 15, 2016 Hot fix for new C++ symbol Mlt::Profile::is_valid() not declare const in symbol versioning. This was breaking script bindings. Version 6.4.0 - November 11, 2016 This is both a bugfix and enhancement release: Framework * Added functions for multi-threaded slice-based image processing: mlt_slices_init, mlt_slices_close, and mlt_slices_run. * Added Mlt::Profile::is_valid(). * Added MLT_DIRLIST_DELIMITER to mlt_types.h. * Renamed mlt++/config.h to mlt++/MltConfig.h. * Fixed mlt_properties_set_lcnumeric() on macOS. * Fixed address of Free Software Foundation in comment headers. Modules * Added crop_to_fill property to composite transition. * Added sliced_composite property to composite transition. * Added peak and true peak properties to loudness_meter filter. * Added qtblend transition and filter to qt module. * Added ndi (NewTek NDI) module with producer and consumer. * Added opencv module with opencv_tracker filter. * Added line_spacing, stretch, wrap_width, and wrap_type properties to pango producer. * Added oblique value for style property to pango producer. * Added fontmap-reload event to pango producer. * Added support for pkg-config to sdl module. * Added .kra (Krita Image) file name extension to loader.dict. * Improved performance of kdenlivetitle producer. * Improved decklink producer and consumer. * Improved accuracy of seeking on lossy compressed audio in avformat producer. * Improved mix transition using 32-bit floating point. * Fixed avfilter when image format changes. * Fixed loading relative file name in vidstab filter. * Fixed crash on Windows with avfilter. * Fixed parsing LADSPA_PATH with semi-colon delimiter on Windows. * Fixed parsing FREI0R_PATH with semi-colon delimiter on Windows. * Fixed reading relative path with backslash (Windows) in xml producer. * Fixed loading relative file name for av.file (avfilter). * Fixed loading multiple LADSPA plugins on some systems. * Fixed compile error when not configured with --enable-gpl. * Fixed loading in avfilter.lut3d in locales with comma decimal point. * Fixed a possible crash in resample filter. * Fixed alpha channel in kdenlivetitle producer. * Fixed possible crash in pixbuf and qimage producers. * Fixed count when counting down in count producer. Other * Moved some avformat presets from lossless to new intermediate folder. * Added a YouTube avformat consumer preset. * Changed metadata.rb metadata publisher to output Markdown. Version 6.2.0 - April 20, 2016 There are no framework changes in this release. The major announcement is the introduction of support for libavfilter! This is still a work-in-progress. It is limited to FFmpeg 2.3 and up, and there are a number of filters that are black-listed because they are known to not integrate with MLT, which is not a full libav* environment or simple wrapper for it. There are likey avfilters that are not yet black-listed but might not work because they have not been completely tested. Also, they do not support MLT's keyframable property animation nor its frame-threaded parallelism due to architectural or integration limitations. However, some avfilters are slice-threaded (internal parallelism), and that works. Finally, libavfilter filtergraph syntax is not supported either. All of the supported libavfilters are exposed as MLT filters beginning with the prefix "avfilter." All of the avfilter parameters are exposed as MLT properties with the "av." prefix to prevent clashes with MLT properties. You can run `melt -query filters` to see the new avfilters, and `melt -query filter=avfilter.rotate`, for example, to view generated documentation for an individual filter. Here is a list of notable fixes and enhancements in this release: * Added support for libavfilter to avformat module. * Added auto-rotate support to avformat producer. * Added animated GIF preset for avformat consumer. * Prevent serializing and deserializing mlt_type property to xml module. * Fixed relative paths for WebVfx "plain:" resources in xml module. * Updated libebur128 to v1.1.0 in plus module. * Added dynamic_loudness filter to plus module. * Added loudness_meter filter to plus module. * Qt 5 fixes for kdenlivetitle producer. * Added gradients and text shadows to kdenlivetitle producer. * Added support for building rtaudio against external build of lib. * Upgraded bundled RtAudio to v4.1.2. * Added status parameters to ladspa producer and filters. * Added 5.1 surround to stereo downmix to audiochannels filter in core module. * Fixed compiling SWIG bindings for Ruby 2.0. Version 6.0.0 - February 17, 2016 This is a bugfix and minor enhancement release. Note that our release versioning scheme has changed. We were approaching 1.0 but decided to synchronize release version with the C library ABI version, which is currently at v6. Here are some of the notable changes and enhancements: Framework * Added unit tests for tractor, multitrack, and field. * Deprecate mlt_frame_get_alpha_mask(). * Added drop_count readable property to mlt_consumer. * Added mlt_factory_repository(). * Added mlt_properties_to_utf8(). * Define MIN, MAX, CLAMP in mlt_types.h in not already defined. * Switched to __APPLE__ and _WIN32 defines throughout codebase. Modules * Added UDP and SMPTE 2022-2 support to cbrts consumer. * Fixed build against latest FFmpeg versions - now requires v1.1 and up. * Added audiospectrum filter to qt module. * Added meta.media.0.codec.rotate property to avformat producer to let apps and other services get the media orientation. * Make the avformat producer handle animated images. * Added style property to dynamictext filter. * Added timewarp producer to core module. * Fixed slowly accumulating A/V sync drift in mix audio transition. * Added width_crop and width_fit properties to pango producer. Melt * Added -abort option to simply exit without full cleanup. * Fix key-press handling on Windows. Version 0.9.8 - July 29, 2015 Here is a list of the enhancements included in this release. There are many API additions to support the ability for apps to add and remove tracks and represent and manipulate property animations. There are still many services, however, that need updating to support animated properties. Framework * Added mlt_service_disconnect_producer() and Mlt::Service::disconnect_producer(). * Added mlt_multitrack_disconnect() and Mlt::Multitrack::disconnect(). * Added mlt_tractor_remove_track() and Mlt::Tractor::remove_track(). * Added mlt_service_insert_producer() and Mlt::Service::insert_producer(). * Added mlt_multitrack_insert() and Mlt::Multitrack::insert(). * Added mlt_tractor_insert_track() and Mlt::Tractor::insert_track(). * Added mlt_transition_set_tracks() and Mlt::Transition::set_tracks(). * Added Mlt::Properties::get_animation(). * Added Mlt::Properties::get_anim(). * Added Mlt:Animation class with methods: - length() - is_key() - keyframe_type() - get_item() - next_key() - previous_key() - set_length() - remove() - interpolate() - serialize_cut() * Added mlt_animation_key_count() and Mlt::Animation::key_count(). * Added mlt_animation_key_get() and Mlt::Animation::key_get(). Modules * Added audiowaveform video filter. * Added fft audio filter. * Added dance video filter (uses fft). * Added lighshow video filter (uses fft). * Added distort property to movit.rect video filter. * Added rotate property to pango video producer. * Added 2K DCI and 4K modes to decklink producer and consumer. * Added audiomap (channel remapping) filter. * Added property animation to all LADSPA audio filters and producers. Version 0.9.6 - March 1, 2015 A major regression slipped into the 0.9.4 release plus some other good fixes rolled in just after that release prompting this new release. Please discontinue using version 0.9.4 and upgrade. Version 0.9.4 - February 15, 2015 This is a bugfix and minor enhancement release. Here are some of the notable changes and enhancements. Framework * Added color_trc (transfer characteristic) property to mlt_consumer and mlt_frame. * Added Mlt::Profile::set_display_aspect(int, int). * Added mlt_pool_stat(). * Added mlt_smpte_df and mlt_smpte_ndf to mlt_time_format for non-drop-frame timecode support. * Added Mlt::Tractor::Tractor(Mlt::Profile&). * Added mlt_frame_get_alpha(). * Added default, copy, and assignment methods to Mlt::Frame. * Added Mlt::Filter::process(Mlt::Frame&). Modules * Added support for color_trc property to avformat and opengl modules. * Performance improvements for composite and matte transitions. * Added fill, halign, and valign properties to affine transition and filter. * Added producer.* and consumer.* properties to consumer producer. * Fixes for libavformat and libavcodec v56. * Dropped support for FFmpeg < v1.0 and Libav < v9. * Added a lumakey filter. * Added a localtime property to dynamictext filter. * Added date/time format string support to dynamictext filter. * Added no_root property to xml consumer. * Added audio-only tone producer. * Added drop property to count producer. * Added caching to pango producer to improve performance. Other * Added WMV and WMA avformat consumer presets. * Added a ProRes-Kostya avformat consumer preset. * Changed VP9 WebM preset to use Opus audio codec. * Added 4K UHD and 2.5K QHD profiles. * Added x265-medium and x265-medium-pass1 avformat consumer presets. * Added a unit test for Mlt::Frame. Version 0.9.2 - June 29, 2014 This is a bugfix and minor enhancement release. Framework * Added "boolean" parameter type and "argument" parameter attribute to service metadata schema. * Added mlt_properties_frames_to_time(). * Added mlt_properties_time_to_frames(). * Changed mlt_events_fire() to return the number of listeners. * Added consumer-thread-create and consumer-thread-join events. * Added LC_NUMERIC handling for Windows. * Added mlt_playlist_mix_out() and mlt_playlist_mix_in() * Added mlt_properties_from_utf8() Modules * Renamed "qimage" module to "qt". * Added support for Qt 5. * Added qtext producer, which is now preferred by dynamictext filter over pango. * Added xml-nogl consumer. * Consolidated dgraft, burningtv, and rotoscoping into new plusgpl module. * Added vid.stab module with vidstab and deshake filters. This depends on external vid.stab library unlike its predecessors. * Rewrote opengl module. * Added loudness filter based on EBU R128. * Added rgblut filter to plus module. * Added luma-only liftgammagain filter to plusgpl module. * Added lift_gamma_gain filter to plus module. * Added support new keyframable animated properties to brightness, volume, panner, boxblur, wave, sepia, charcoal, burn, gamma, grain, dust, lines, tcolor, oldfilm, * Added movit.luma transition to opengl module. * Added cbrts consumer to plusgpl module. * Removed the "ppm" PPM-pipe producer. * Added more VITC functionality to decklink module. * Added matte transition to core module. DEPRECATION WARNINGS * Deprecate videostab module with videostab and videostab filters. This will not be removed soon in order to keep old scripts and projects functional. * Deprecate the dv (libdv), kino, and vorbis modules. These are scheduled to be removed in next release. Version 0.9.0 - June 2, 2013 This is a significant enhancement release. Build (especially interesting for Linux packagers) * Added --rename-melt and --enable-extra-versioning configure options. * Added symbol versioning on Linux. Framework * Improved pause behavior when using buffered rendering in mlt_consumer. * Added mlt_animation API (exposed via Mlt::Properties in C++). * Added mlt_rect and mlt_color types. * Deprecated mlt_geometry API. Modules * Support for the latest versions of FFmpeg and Libav (but dropping support for 0.5 and 0.6 versions). * Added alpha channel output to avformat consumer. * Added reconnect and exit_on_disconnect properties to avformat producer. * Added opengl module that uses Movit for GLSL image processing. * Added qglsl consumer to use opengl with avformat, sdi, and decklink. * Added avsync module with blipflash producer and consumer for testing. * Added new "count" producer to gtk2 module. * Changed frei0r to use index-based property names making it impervious to param name changes (param name still accepted for compatibility). * Added default parameter values to frei0r metadata. Other * Added more python example web services. * Added the beginnings of unit test suite. Version 0.8.8 - January 20, 2013 This is purely a bugfix release. See the ChangeLog or git log. Version 0.8.6 - November 14, 2012 This is a re-issue of the 0.8.4 release with a fix for a performance regression on videos that use full-range colorspaces such as yuv420p. Version 0.8.4 - November 13, 2012 This is a bugfix and minor enhancement release. * Added playlist-next event and PlaylistNextListener to Ruby binding * FFmpeg 1.0 and libAV master compatibility * Improvements to motion_est filter to generate keyframes for apps * Added audiolevel (measurement) filter Version 0.8.2 - August 28, 2012 This is a bugfix and minor enhancement release. * Overhaul of A/V sync with libavformat-based inputs. * Fix a major memory leak introduced in previous release. * Fixes to problems revealed by Coverity Scan static analysis. * Improved encoding presets. * melt can now be built without SDL with define MELT_NOSDL, which is handy for running it as a child process on Windows and OS X. * melt can now be signaled to quit, which also makes it more useful as a child process. Special thanks to Mikko Rapeli who provided many of the Coverity fixes. Version 0.8.0 - June 1, 2012 The minor version is increased due to the addition of time properties! The soname version increased in the process because some mlt_property functions changed; however, very few if any apps actually directly use mlt_property preferring to use mlt_properties instead. In addition: * improve seek speed on AVCHD when using FFmpeg v0.9.1+ (NOT Libav!) * composite and dissolve speed improvements on x86-64 * improve performance of caching in image producers * add device enumeration to decklink producer and consumer Special thanks go to contributors Maksym Veremeyenko and Ed Rogalsky. Version 0.7.8 - February 13, 2012 This is a bugfix and minor enhancement release. * Improved support for v53 of libavcodec/libavformat * Added "multi" consumer - multiple, simultaneous outputs * Added framerate adaption to "consumer" producer and "multi" consumer * Can now use YADIF deinterlacer with decklink producer * Added "rtaudio" consumer for native audio support on multiple platforms * Added ability to request image format closest to source (mlt_image_none) * Added more audio formats * Added vqm (video quality measurement) transition Version 0.7.6 - October 31, 2011 This is a bugfix and minor enhancement release. * Improved support for v53 of libavcodec/libavformat (0.7 and 0.8 releases) * Major DeckLink consumer improvements * Much more metadata * Added audio-only JACK consumer * Added video stabilization filters * Added dual pass audio normalization to sox filter * Added VITC and VANC capture to DeckLink producer * Added support for writing timecode tracks * Added MLT, frei0r, and SoX version to xml serialization * Added D-10, XDCAM, DNxHD, and Sony-PSP encoding presets * Can now use rotoscoping for masking filters * Added dynamictext filter makes burned-in timecode and similar easier * Added support for consumer element in MLT XML * Added outlining, padding, and alignment to pango filter Special thanks go to contributors Maksym Veremeyenko, Brian Matherly, and Marco Gittler. Version 0.7.4 - July 16, 2011 This is a bugfix and minor enhancement release. Framework * Important: change consumer property profile to mlt_profile. * Improve frame-dropping and drop_max property to mlt_consumer. * Added support for presets for any service through special property named "properties" * Added mlt_profile_from_producer() for auto-profile. * Added mlt_properties_set_lcnumeric() and mlt_properties_get_lcnumeric(). * Added LC_NUMERIC to YAML Tiny metadata schema and parser. Modules * Added support for more than 2 channels of greater than 16-bit audio. * Added discrete filters for each SoX effect. * Added discrete filters for each LADSPA audio plugin. * Added automatic service metadata for SoX and LADSPA plugins. * Added at least basic metadata for nearly every service. * Added support for decklink on Windows (tested) and Mac OS X (untested). * Added support for JACK transport synchronization. * Added blacklist.txt to jackrack plugin (contains dssi-vst). * Rewrite of decklink consumer. * Added support for live network, multi-stream device, and pipe/fifo sources in avformat producer. * Added LC_NUMERIC attribute to root XML element. Melt * Added '-query presets' option. * Added -jack option for transport synchronization. * Send -help and -query output to stdout to make it convenient for pagers. Other * Added mlt.Frame.get_image() for Python. * Removed configure option --avformat-svn. * Fixes for locales that use comma for decimal point. * Added presets for DVD, DV, x264, and WebM encoding. Since FFmpeg forked and there were a few releases, there is no recommended version at this time. With that said, there were changes to accommodate the API changes with some moderate testing of the 0.6, 0.7, and 0.8 series releases of both FFmpeg and Libav. Version 0.7.2 - May 1, 2011 This is a minor release to fix a few things between the 0.7.0 release and the release of Kdenlive 0.8. I recommend Kdenlive v0.8 users to upgrade to this version of Mlt. Beyond that there are some exciting additions to the Blackmagic Design DeckLink plugin! Framework * Added mlt_profile_list(). Modules * Added decklink producer (i.e. capture, live encoding). * Added keyer output for decklink consumer. * Added AVOptions to the avformat service metadata. * Added support for new major API versions (53) of FFmpeg. Melt * Added '-query profile' option. * Added '-query formats', '-query audio_codecs' and '-query video_codecs'. The recommended version of FFmpeg for use with this release is 0.6.1. Version 0.7.0 - March 27, 2011 This is a major new release due to significant additions to API, framework, and build. Build * Added support for Windows via MinGW. * Enabled linsys module by default. * Disabled VDPAU by default and added --avformat-vdpau to enable it. * Added support for swfdec 0.7. Framework: * Added parallelism to mlt_consumer when 'real_time' > 1 or < -1. * Added mlt_deque_insert() and mlt_deque_peek(). * Added mlt_profile parameter to mlt_producer_new(). * Let transitions with no out point run forever. * Added mlt_frame_unique_properties(). * Added mlt_frame_set_image() and mlt_frame_set_alpha(). * Added mlt_image_format_size() and mlt_audio_format_size(). * Added mlt_filter_get_length() and mlt_transition_get_length(). * Added mlt_filter_get_progress(), mlt_transition_get_progress(), and mlt_transition_get_progress_delta(). * Added mlt_filter_get_position() and mlt_transition_get_position(). * Added mlt_properties_lock() and mlt_properties_unlock(). Modules * Added rotoscoping filter. * Improve libavdevice support (V4L2, ALSA, libdc1394). * Added support for new FFmpeg metadata API. * Various fixes, refactoring, and improvements. The recommended version of FFmpeg for use with this release is 0.6.1. Version 0.6.2 - January 23, 2011 This is just a minor release to address a few things prior to introducing major changes from other branches. * Added force_aspect_ratio property to pixbuf and qimage producers. * Added opacity handling in geometry property of the affine filter and transition. * Added use_normalised property to affine filter. * Added always_active property to affine transition. * Fix building on NetBSD. The recommended version of FFmpeg for use with this release is 0.6.1. Version 0.6.0 - January 1, 2011 The recommended version of FFmpeg for use with this release is 0.6.1. There were quite a few enhancements and changes including a minor interface change. Therefore, this release gets a jump in the versioning. Framework * mlt_profile - Added (Y'CbCr) colorspace attribute. - Added mlt_profile_clone(). * Added mlt_consumer_position() and Mlt::Consumer::position(). * Added Mlt::Properties::wait_for(string). * Added a version API: mlt_version_get_int() and others. * Added Mlt::Producer::pause(). * Added mlt_frame_write_ppm() for debugging. Melt * Added automatic detection of profile from first input when profile not specified. * Now exits with error result when consumer fails fatally. Modules * Added swfdec producer for Flash files including variables support. (Does not support audio.) * Added consumer for Blackmagic Design DeckLink SDI and Intensity HDMI. * Added Y'CbCr colorspace conversion and option to use full luma range. * Added (de)serialization of profile to XML. * Added support for #frame# variable to the data_show filter. * Added support for frei0r string parameters. * Make FFmpeg formats and codecs available as properties (not just stderr). * Try to load .xml file as MLT XML. * Added a consumer-sdl-paused event to sdl_preview. * Added a consumer-fatal-error event to avformat. * Change composite transition to default to progressive rendering; field- based rendering available only explicitly. Version 0.5.10 - September 13, 2010 This is a quick followup to the 0.5.8 release to address an issue I want to address immediately. I noticed an extra unconditional colorspace conversion to and from RGB was added for all YCbCr (YUV) video sources. In addition, I have enabled the avcolor_space filter on OS X since it works now. Version 0.5.8 - September 12, 2010 The recommended version of FFmpeg for use with this release is 0.6. This is a maintenance release to address some bugs that appeared thus far in the 0.5.x series. Beyond that is a few enhancements: * Added EXIF-based auto-rotation of images to pixbuf and qimage producers. * Improved quality of libswscale-based image conversion and scaling. * Added channelswap and panner audio filters; panner also does balance. * Improve audio waveform and add audiowave video filter. * Enhanced luma filter to work with animated filters such as affine. * Automatically crop 8 bottom lines of 1088 source in a 16:9 project (common in Canon EOS digital cameras). * Added support for inline images in kdenlivetitler. Version 0.5.6 - June 20, 2010 The recommended version of FFmpeg for use with this release is 0.6. This is a maintenance release to address some bugs that appeared this far in the 0.5.x series. Beyond that it a few enhancements: * Added interpolation to the affine transition and filter. * Added multi-track audio encoding to avformat consumer. * Added interlaced field rendering to kdenlivetitle producer. Version 0.5.4 - April 19, 2010 The recommended version of FFmpeg for use with this release is SVN r21322. This is another maintenance release to address some bugs that appeared this far in the 0.5.x series. Beyond that it adds two things that only very specific users will see: * Added C# (ECMA CLR) binding (not enabled by default). * Linsys SDI consumer now configures itself from MLT profile. Version 0.5.2 - March 10, 2010 The recommended version of FFmpeg for use with this release is SVN r21322. This is a minor maintenance release, but it is interesting because it now enables usage of libswscale as the default choice for image scaling, image format conversion, and color space conversion. That gives better quality and performance. In addition, there are some improvements in the sdl_preview consumer to make it suitable for use in OpenShot 1.1. Other things: * Fixed mlt++/MltFilteredProducer * Fixed playing to the end in Kdenlive (mantis bug 1207) * Fixed crash load uncompressed video * Fixed compiling yadif for non-sse2 builds Version 0.5.0 - February 15, 2010 The recommended version of FFmpeg for use with this release is SVN r21322. This is an enhancement release, confined mainly to the modules rather than the framework. In particular, this adds support for VDPAU, YADIF, and HD-SDI technologies! configure: added --disable-sse2 framework: * mlt_cache: added mlt_cache_set_size() * mlt_filter: added data property "service" - set when attached * mlt_frame: - added Doxygen docs - added "previous frame" and "next frame" data properties - available when its producer has _need_previous_next=1 * mlt_playlist: added support for negative out point same as length-1 * mlt_service: - added mlt_service_cache_purge() - added "_need_previous_next" handling in mlt_service_get_frame() - added firing event "service-changed" in mlt_service_attach() modules: * avformat producer: - added decoding H.264 with NVIDIA VDPAU Requires FFmpeg built with vdpau. This is automatically detected and enabled. You can disable this by setting environment variable MLT_NO_VDPAU=1 or property novdpau=1. - added caching of FFmpeg contexts and decoded images This allows large numbers of clips in a project avoiding limitations with number of threads and file descriptors permitted per process. You can disable image caching with property noimagecache=1. - added variant of producer named avformat-novalidate - restored support for video4linux(2) * avformat consumer: added apre, fpre, and vpre preset properties * crop filter: added center_bias integer property * deinterlace filter: added the excellent YADIF as a method * kdenlivetitle producer: added text outlining * linsys/sdi consumer: - added support for HD-SDI - changed name from "linsys_sdi" to just "sdi" * oldfilm filter: added "uneven development" effect * xml producer: add support for unspecified out points profiles: * added several missing ATSC (HD) profiles * change descriptions from using Hz to fps Version 0.4.10 - December 8, 2009 The recommended version of FFmpeg for use with this release is SVN r19873. This is "hotfix" for the 0.4.8 release that fixes a couple of regression bugs introduced just before the release. Version 0.4.8 - December 7, 2009 The recommended version of FFmpeg for use with this release is SVN r19873. This is mainly a maintenance release. Besides bug fixes here are other notable changes. modules: * avformat producer: - refactored producer to use much less properties - added support for audio_index=all for linsys_sdi consumer - added force_fps property (does yet not adjust duration) * core/crop: added "center" property to crop filter * linsys_sdi: - added support for >2 audio channels - added property meta.map.audio..channels= - added property meta.map.audio..start= * qimage/kdenlivetitle: add typewriter effect Version 0.4.6 - October 7, 2009 The recommended version of FFmpeg for use with this release is SVN r19873. This release is an enhancement release along with numerous build, A/V synch, concurrency, and other bug fixes. configure: new option --avformat-svn-version modules: * avformat: much improved seeking on H.264/MPEG2-TS (AVCHD) (Ivan Schreter) * core: new imageconvert and audioconvert filters (framework refactorization) * linsys: new SDI consumer (Broadcast Centre Europe) * qimage: new kdenlivetitle producer (J.B. Mardelle and Marco Gittler) * sdl: new audio_only consumer for OS X mlt++ and swig: update bindings framework: * refactored image format conversion mlt_frame.h: - added convert_image() virtual function - added mlt_image_format_name() - removed many mlt_convert_ and scaling/padding functions * refactored audio format conversion mlt_frame.h: - mlt_get_audio() virtual function parameters changed - added convert_audio() virtual function - mlt_frame_get_audio() parameters changed - added mlt_frame_set_audio() - added mlt_audio_format_name() mlt_types.h: - deprecated mlt_audio_pcm - added mlt_audio_s16 - added mlt_audio_s32 - added mlt_audio_float Version 0.4.4 - June 30, 2009 The recommended version of FFmpeg for use with this release is 0.5.0. This release is a minor maintenance update to the 0.4.2 - just build and bug fixes. * new configure script options: --swig-languages --rename-melt --mandir --datadir * added man page for melt (not installed) * added invert property to composite transition * added frei0r plugin blacklist, currently only contains facedetect * added Lua binding via SWIG Version 0.4.2 - May 30, 2009 The recommended version of FFmpeg for use with this release is 0.5.0. This release is a minor maintenance update to the 0.4.0 - just build and bug fixes. Version 0.4.0 - May 17, 2009 The recommended version of FFmpeg for use with this release is 0.5.0. This release is primarily a reorganization of the mlt and mlt++ projects. In brief: * "inigo" was renamed "melt" * "westley" is no longer the XML file extension and root element - they are simply "mlt" now * mlt++ is included with mlt and no longer a separate project * miracle, valerie, and humperdink were moved to a new, separate project For details: http://www.mltframework.org/twiki/bin/view/MLT/ExtremeMakeover avformat producer improvements: * improve audio synchronization * improve reliability of video playback with FFmpeg newer than v0.5.0 * improve seeking performance of DNxHD and HuffYUV Version 0.3.8 - April 15, 2009 The recommended version of FFmpeg for use with this release is SVN r17923. This almost entirely a bugfix release to coincide with the Kdenlive 0.7.3 release. See the ChangeLog (SVN log) for details. framework: * added mlt_cache API * improved doxygen documentation comments * added some 15 fps profiles * improved color property handling (support web-style '#' color value) * add const qualifier to many string parameters modules: * core: improved brightness filter * core: added image crop filter * frei0r: added support for producer/source plugins * frei0r: added support for color parameters * sdl: added window_background color property Version 0.3.6 - February 2, 2009 The recommended version of FFmpeg for use with this release is SVN r16849. This almost entirely a bugfix release to coincide with the Kdenlive 0.7.2 release. See the ChangeLog (SVN log) for details. framework: * added mlt_log logging API * improved doxygen documentation comments avformat module: * consumer: report list of muxers when f=list and codecs when acodec=list or vcodec=list * consumer: added support for an=1 or acodec=none and vn=1 or vcodec=none * producer: list available demuxers and decoders when constructor arg is like f-list[[,]acodec-list][[,]vcodec-list] Version 0.3.4 - December 29, 2008 The recommended version of FFmpeg for use with this release is SVN r16313. This almost entirely a bugfix release. See the ChangeLog (SVN log) for details. There are a few notes: framework: * improved doxygen documentation comments (work in progress) published docs are at http://mltframework.org/doxygen/ avformat module: * added support for AVOption parsing to producer * added filter_swscale as alternative rescaler * added recommended FFmpeg revision to configure option --help * use recommended FFmpeg revision with --avformat-svn on release versions * added configure option --avformat-no-codecs * added configure option --avformat-no-filters misc: * new profile atsc_1080i_50 * added --disable-sse option to configure script * improved build for OS X and x86-64 and improved handling of mmx/sse Version 0.3.2 - November 10, 2008 In addition to bug fixes detailed in the ChangeLog, here is a list of enhancements. framework: * deprecated mlt-config; use pkg-config instead * added more HD profiles modules: * sdl: added fullscreen property * sox: sox v14.1.0 compatibility * gtk: added force_reload property to producer_pixbuf * frei0r: added support for YAML Tiny metadata * frei0r: added keyframe support on double and boolean parameters * oldfilm: added keyframe support for filter_vignette * kdenlive: added filter_freeze inigo: * added -version, -silent, and -progress options * improved output of usage information * removed realtime process scheduling Version 0.3.0 - August 5, 2008 framework: * fix bugs with introduction of mlt_profile in v0.2.4 * added versioning to libs * remove module registry and add dynamic module loading: added mlt_repository_register, mlt_repository_consumers, mlt_repository_filters, mlt_repository_producers, mlt_repository_transitions * new module metadata system based on YAML Tiny: added mlt_repository_register_metadata, mlt_repository_metadata, mlt_repository_languages, mlt_properties_is_sequence, mlt_properties_parse_yaml, mlt_properties_serialise_yaml, and added metaschema.yaml Kwalify schema file * mlt_consumer: added threaded, non-lossy processing when real_time=-1 * added autoclose property to mlt_playlist for sequential processing of very large playlists (prevents resource exhaustion) * mlt_factory_init now returns the global mlt_repository * change mlt_repository_fetch to mlt_repository_create * change mlt_factory_prefix to mlt_factory_directory * added mlt_field_disconnect_service modules: * move all modules from $datadir to $libdir * new oldfilm module by Marco Gittler * new frei0r module by Marco Gittler * new dgraft module by Dan Dennedy for inverse telecine (not ready yet) * avformat: added support for multi-threaded encoding and decoding * consumer_avformat: added support for AVOption to support all ffmpeg options using ffmpeg-style property names * consumer_avformat: added support for dual pass encoding * qimage: added support for Qt4 * sox: added support for sox v14.0.0 * transition_composite: added animatable geometry-type "pan" property to crop and pan instead of automatic down-scale inigo: * added -query option to lookup module metadata * added -profile option and support for progress=1 for kdenlive Version 0.2.4 - August 4, 2007 * framework: new extensible profiles system to replace MLT_NORMALISATION * module avformat: interlaced coding support for ffmpeg/libavcodec * module avformat: build improvements for --avformat-svn * new effectv module with BurningTV video filter * module qimage: added support for psd, xcf and exr images * numerous bugfixes Version 0.2.3 - April 9, 2007 * Addition of kdenlive module * Support for ffmpeg from subversion * Support for ffmpeg libswscale * Copyright and license cleanup Version 0.2.2 - May 27, 2006 * Prepared specifically for the kdenlive 0.3 release. * Contains some patches to support rgb24a output for the gdk-pixbuf and qimage producers as well as some minor bugfixes. Version 0.2.1 - December 5, 2005 * Many improvements since initial releases due to development of Shotcut and Jahshaka editing interfaces. Version 0.1.1 - June 9, 2004 * Minor modifications and bug fixes from the previous release. Better ffmpeg/avformat integration and more reliable playback. Version 0.1.0 - May 6, 2004 * First official release mlt-7.22.0/README.md000664 000000 000000 00000002456 14531534050 013644 0ustar00rootroot000000 000000 MLT FRAMEWORK README -------------------- MLT is a LGPL multimedia framework designed for video editing. This document provides a quick reference for the minimal configuration, build and installation of MLT. See the `docs/` directory for usage details. See the [website](https://www.mltframework.org/docs/) for development details and a [contributing](https://www.mltframework.org/docs/contributing/) guide. Configuration ------------- Configuration is triggered by running: cmake . More information on usage is found by viewing `CMakeLists.txt` and the [cmake man page](https://cmake.org/cmake/help/latest/manual/cmake.1.html). Compilation ----------- Once configured, it should be sufficient to run: cmake --build . to compile the system. Alternatively, you can run `cmake` with `-G Ninja` and use ninja to compile. Testing ------- To execute the mlt tools without installation, or to test a new version on a system with an already installed mlt version, you should run: . setenv NB: This applies to your current shell only and it assumes a bash or regular bourne shell is in use. Installation ------------ The install is triggered by running: cmake --install . More Information ---------------- For more detailed information, please refer to https://mltframework.org/docs/install/. mlt-7.22.0/cmake/000775 000000 000000 00000000000 14531534050 013436 5ustar00rootroot000000 000000 mlt-7.22.0/cmake/FindClangFormat.cmake000664 000000 000000 00000005564 14531534050 017450 0ustar00rootroot000000 000000 # # .rst: FindClangFormat # --------------- # # The module defines the following variables # # ``CLANGFORMAT_EXECUTABLE`` Path to clang-format executable # ``CLANGFORMAT_FOUND`` True if the clang-format executable was found. # ``CLANGFORMAT_VERSION`` The version of clang-format found # # Example usage: # # .. code-block:: cmake # # find_package(ClangFormat) # if(CLANGFORMAT_FOUND) # message("clang-format executable found: ${CLANGFORMAT_EXECUTABLE}\n" "version: ${CLANGFORMAT_VERSION}") # endif() include(FindPackageHandleStandardArgs) function(_ClangFormat_get_version clangformat_version result_var clangformat_path) execute_process( COMMAND "${clangformat_path}" --version OUTPUT_VARIABLE full_clangformat_version OUTPUT_STRIP_TRAILING_WHITESPACE RESULT_VARIABLE version_result ) # full_clangformat_version sample: "clang-format version 3.9.1-4ubuntu3~16.04.1 (tags/RELEASE_391/rc2)" # clean clangformat_version sample: "3.9.1" string(REGEX REPLACE "[^0-9]*([.0-9]+).*" "\\1" clean_clangformat_version "${full_clangformat_version}") set(${result_var} ${version_result} PARENT_SCOPE) set(${clangformat_version} ${clean_clangformat_version} PARENT_SCOPE) endfunction() function(_ClangFromat_version_validator version_match clangformat_path) if(NOT DEFINED ClangFormat_FIND_VERSION) set(${is_valid_version} TRUE PARENT_SCOPE) else() _ClangFormat_get_version(candidate_version version_result "${clangformat_path}") if(version_result) message(DEBUG "Unable to determine candidate clang-format version at ${clangformat_path}: ${version_result}") endif() find_package_check_version("${candidate_version}" valid_clangformat_version HANDLE_VERSION_RANGE ) set(${version_match} "${valid_clangformat_version}" PARENT_SCOPE) endif() endfunction() find_program(CLANGFORMAT_EXECUTABLE NAMES clang-format clang-format-16 clang-format-15 clang-format-14 clang-format-13 clang-format-12 clang-format-11 clang-format-10 DOC "clang-format executable" VALIDATOR _ClangFromat_version_validator ) mark_as_advanced(CLANGFORMAT_EXECUTABLE) if(CLANGFORMAT_EXECUTABLE) _ClangFormat_get_version(CLANGFORMAT_VERSION _Clangformat_version_result "${CLANGFORMAT_EXECUTABLE}") if(_Clangformat_version_result) set(CLANGFORMAT_FOUND FALSE) message(WARNING "Unable to determine clang-format version: ${_Clangformat_version_result}") else() set(CLANGFORMAT_FOUND TRUE) endif() endif() find_package_handle_standard_args(ClangFormat FOUND_VAR CLANGFORMAT_FOUND REQUIRED_VARS CLANGFORMAT_EXECUTABLE CLANGFORMAT_VERSION VERSION_VAR CLANGFORMAT_VERSION ) mlt-7.22.0/cmake/FindJACK.cmake000664 000000 000000 00000001426 14531534050 015754 0ustar00rootroot000000 000000 find_package(PkgConfig) pkg_check_modules(PC_JACK QUIET jack) find_path(JACK_INCLUDE_DIR NAMES jack/jack.h PATHS ${PC_JACK_INCLUDE_DIRS} ) find_library(JACK_LIBRARY NAMES jack PATHS ${PC_JACK_LIBRARY_DIRS} ) set(JACK_VERSION ${PC_JACK_VERSION}) include(FindPackageHandleStandardArgs) find_package_handle_standard_args(JACK FOUND_VAR JACK_FOUND REQUIRED_VARS JACK_LIBRARY JACK_INCLUDE_DIR VERSION_VAR JACK_VERSION ) if(JACK_FOUND AND NOT TARGET JACK::JACK) add_library(JACK::JACK UNKNOWN IMPORTED) set_target_properties(JACK::JACK PROPERTIES IMPORTED_LOCATION "${JACK_LIBRARY}" INTERFACE_COMPILE_OPTIONS "${PC_JACK_CFLAGS_OTHER}" INTERFACE_INCLUDE_DIRECTORIES "${JACK_INCLUDE_DIR}" ) endif() mark_as_advanced( JACK_INCLUDE_DIR JACK_LIBRARY ) mlt-7.22.0/cmake/FindKwalify.cmake000664 000000 000000 00000000570 14531534050 016651 0ustar00rootroot000000 000000 find_program(Kwalify_EXECUTABLE kwalify) if(Kwalify_EXECUTABLE) execute_process(COMMAND ${Kwalify_EXECUTABLE} -v OUTPUT_VARIABLE Kwalify_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE ) endif() include(FindPackageHandleStandardArgs) find_package_handle_standard_args(Kwalify FOUND_VAR Kwalify_FOUND REQUIRED_VARS Kwalify_EXECUTABLE VERSION_VAR Kwalify_VERSION ) mlt-7.22.0/cmake/FindMono.cmake000664 000000 000000 00000020120 14531534050 016144 0ustar00rootroot000000 000000 # https://github.com/SimpleITK/SimpleITK/blob/master/CMake/FindMono.cmake # # A CMake Module for finding Mono. # # The following variables are set: # CSHARP_MONO_FOUND # CSHARP_MONO_COMPILER_${version} eg. "CSHARP_MONO_COMPILER_2.10.2" # CSHARP_MONO_INTERPRETOR_${version} eg. "CSHARP_MONO_INTERPRETOR_2.10.2" # CSHARP_MONO_VERSION eg. "2.10.2" # CSHARP_MONO_VERSIONS eg. "2.10.2, 2.6.7" # # Additional references can be found here: # http://www.mono-project.com/Main_Page # http://www.mono-project.com/CSharp_Compiler # http://mono-project.com/FAQ:_Technical (How can I tell where the Mono runtime is installed) # # This file is based on the work of GDCM: # http://gdcm.svn.sf.net/viewvc/gdcm/trunk/CMake/FindMono.cmake # Copyright (c) 2006-2010 Mathieu Malaterre # set( csharp_mono_valid 1 ) if( DEFINED CSHARP_MONO_FOUND ) # The Mono compiler has already been found # It may have been reset by the user, verify it is correct if( NOT DEFINED CSHARP_MONO_COMPILER_${CSHARP_MONO_VERSION} ) set( csharp_mono_version_user ${CSHARP_MONO_VERSION} ) set( csharp_mono_valid 0 ) set( CSHARP_MONO_FOUND 0 ) set( CSHARP_MONO_VERSION "CSHARP_MONO_VERSION-NOTVALID" CACHE STRING "C# Mono compiler version, choices: ${CSHARP_MONO_VERSIONS}" FORCE ) message( FATAL_ERROR "The C# Mono version '${csharp_mono_version_user}' is not valid. Please enter one of the following: ${CSHARP_MONO_VERSIONS}" ) endif( NOT DEFINED CSHARP_MONO_COMPILER_${CSHARP_MONO_VERSION} ) endif( DEFINED CSHARP_MONO_FOUND ) unset( CSHARP_MONO_VERSIONS CACHE ) # Clear versions if( WIN32 ) # Search for Mono on Win32 systems # See http://mono-project.com/OldReleases and http://www.go-mono.com/mono-downloads/download.html set( csharp_mono_bin_dirs ) set( csharp_mono_search_hints "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Novell\\Mono\\2.11.2;SdkInstallRoot]/bin" "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Novell\\Mono\\2.10.9;SdkInstallRoot]/bin" "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Novell\\Mono\\2.10.8;SdkInstallRoot]/bin" "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Novell\\Mono\\2.10.7;SdkInstallRoot]/bin" "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Novell\\Mono\\2.10.6;SdkInstallRoot]/bin" "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Novell\\Mono\\2.10.5;SdkInstallRoot]/bin" "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Novell\\Mono\\2.10.4;SdkInstallRoot]/bin" "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Novell\\Mono\\2.10.3;SdkInstallRoot]/bin" "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Novell\\Mono\\2.10.2;SdkInstallRoot]/bin" "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Novell\\Mono\\2.10.1;SdkInstallRoot]/bin" "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Novell\\Mono\\2.10;SdkInstallRoot]/bin" "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Novell\\Mono\\2.8;SdkInstallRoot]/bin" "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Novell\\Mono\\2.6.7;SdkInstallRoot]/bin" "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Novell\\Mono\\2.6.4;SdkInstallRoot]/bin" "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Novell\\Mono\\2.6.3;SdkInstallRoot]/bin" "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Novell\\Mono\\2.6.1;SdkInstallRoot]/bin" "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Novell\\Mono\\2.6;SdkInstallRoot]/bin" ) foreach( csharp_mono_search_hint ${csharp_mono_search_hints} ) get_filename_component( csharp_mono_bin_dir "${csharp_mono_search_hint}" ABSOLUTE ) if ( EXISTS "${csharp_mono_bin_dir}" ) set( csharp_mono_bin_dirs ${csharp_mono_bin_dirs} ${csharp_mono_bin_dir} ) endif ( EXISTS "${csharp_mono_bin_dir}" ) endforeach( csharp_mono_search_hint ) # TODO: Use HKLM_LOCAL_MACHINE\Software\Novell\Mono\DefaultCLR to specify default version # get_filename_component( test "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Novell\\Mono;DefaultCLR]" NAME ) foreach ( csharp_mono_bin_dir ${csharp_mono_bin_dirs} ) string( REPLACE "\\" "/" csharp_mono_bin_dir ${csharp_mono_bin_dir} ) if (EXISTS "${csharp_mono_bin_dir}/dmcs.bat") set( csharp_mono_executable "${csharp_mono_bin_dir}/dmcs.bat") elseif (EXISTS "${csharp_mono_bin_dir}/gmcs.bat") set( csharp_mono_executable "${csharp_mono_bin_dir}/gmcs.bat") elseif (EXISTS "${csharp_mono_bin_dir}/mcs.bat") set( csharp_mono_executable "${csharp_mono_bin_dir}/mcs.bat") endif (EXISTS "${csharp_mono_bin_dir}/dmcs.bat") if( csharp_mono_valid ) # Extract version number (eg. 2.10.2) string(REGEX MATCH "([0-9]*)([.])([0-9]*)([.]*)([0-9]*)" csharp_mono_version_temp ${csharp_mono_bin_dir}) set( CSHARP_MONO_VERSION ${csharp_mono_version_temp} CACHE STRING "C# Mono compiler version" ) mark_as_advanced( CSHARP_MONO_VERSION ) # Add variable holding executable set( CSHARP_MONO_COMPILER_${csharp_mono_version_temp} ${csharp_mono_executable} CACHE STRING "C# Mono compiler ${csharp_mono_version_temp}" FORCE ) mark_as_advanced( CSHARP_MONO_COMPILER_${csharp_mono_version_temp} ) # Set interpreter if (EXISTS "${csharp_mono_bin_dir}/mono.exe") set( CSHARP_MONO_INTERPRETER_${csharp_mono_version_temp} "${csharp_mono_bin_dir}/mono.exe" CACHE STRING "C# Mono interpreter ${csharp_mono_version_temp}" FORCE ) mark_as_advanced( CSHARP_MONO_INTERPRETER_${csharp_mono_version_temp} ) endif (EXISTS "${csharp_mono_bin_dir}/mono.exe") endif( csharp_mono_valid ) # Create a list of supported compiler versions if( NOT DEFINED CSHARP_MONO_VERSIONS ) set( CSHARP_MONO_VERSIONS "${csharp_mono_version_temp}" CACHE STRING "Available C# Mono compiler versions" FORCE ) else( NOT DEFINED CSHARP_MONO_VERSIONS ) set( CSHARP_MONO_VERSIONS "${CSHARP_MONO_VERSIONS}, ${csharp_mono_version_temp}" CACHE STRING "Available C# Mono versions" FORCE ) endif( NOT DEFINED CSHARP_MONO_VERSIONS ) mark_as_advanced( CSHARP_MONO_VERSIONS ) # We found at least one Mono compiler version set( CSHARP_MONO_FOUND 1 CACHE INTERNAL "Boolean indicating if C# Mono was found" ) endforeach( csharp_mono_bin_dir ) else( UNIX ) # Search for Mono on non-Win32 systems set( chsarp_mono_names "mcs" "mcs.exe" "dmcs" "dmcs.exe" "smcs" "smcs.exe" "gmcs" "gmcs.exe" ) set( csharp_mono_paths "/usr/bin/" "/usr/local/bin/" "/usr/lib/mono/2.0" "/opt/novell/mono/bin" ) find_program( csharp_mono_compiler # variable is added to the cache, we removed it below NAMES ${chsarp_mono_names} PATHS ${csharp_mono_paths} ) if( EXISTS ${csharp_mono_compiler} ) # Determine version find_program( csharp_mono_interpreter # variable is added to the cache, we removed it below NAMES mono PATHS ${csharp_mono_paths} ) if ( EXISTS ${csharp_mono_interpreter} ) execute_process( COMMAND ${csharp_mono_interpreter} -V OUTPUT_VARIABLE csharp_mono_version_string ) string( REGEX MATCH "([0-9]*)([.])([0-9]*)([.]*)([0-9]*)" csharp_mono_version_temp ${csharp_mono_version_string} ) set( CSHARP_MONO_INTERPRETER_${CSHARP_MONO_VERSION} ${csharp_mono_interpreter} CACHE STRING "C# Mono interpreter ${csharp_mono_version_temp}" FORCE ) mark_as_advanced( CSHARP_MONO_INTERPRETER_${CSHARP_MONO_VERSION} ) endif ( EXISTS ${csharp_mono_interpreter} ) unset( csharp_mono_interpreter CACHE ) # We found Mono compiler set( CSHARP_MONO_VERSION ${csharp_mono_version_temp} CACHE STRING "C# Mono compiler version" ) mark_as_advanced( CSHARP_MONO_VERSION ) set( CSHARP_MONO_COMPILER_${CSHARP_MONO_VERSION} ${csharp_mono_compiler} CACHE STRING "C# Mono compiler ${CSHARP_MONO_VERSION}" FORCE ) mark_as_advanced( CSHARP_MONO_COMPILER_${CSHARP_MONO_VERSION} ) set( CSHARP_MONO_VERSIONS ${CSHARP_MONO_VERSION} CACHE STRING "Available C# Mono compiler versions" FORCE ) mark_as_advanced( CSHARP_MONO_VERSIONS ) set( CSHARP_MONO_FOUND 1 CACHE INTERNAL "Boolean indicating if C# Mono was found" ) endif( EXISTS ${csharp_mono_compiler} ) # Remove temp variable from cache unset( csharp_mono_compiler CACHE ) endif( WIN32 ) if( CSHARP_MONO_FOUND ) # Report the found versions message( STATUS "Found the following C# Mono versions: ${CSHARP_MONO_VERSIONS}" ) endif( CSHARP_MONO_FOUND ) # Set USE_FILE get_filename_component( current_list_path ${CMAKE_CURRENT_LIST_FILE} PATH ) set( Mono_USE_FILE ${current_list_path}/UseMono.cmake ) mlt-7.22.0/cmake/FindNDI.cmake000664 000000 000000 00000002621 14531534050 015654 0ustar00rootroot000000 000000 set(NDI_SDK_INCLUDE_PATH "" CACHE PATH "NDI SDK include path") set(NDI_SDK_LIBRARY_PATH "" CACHE PATH "NDI SDK library path") if(NOT (NDI_SDK_INCLUDE_PATH AND NDI_SDK_LIBRARY_PATH)) message(FATAL_ERROR "NDI SDK: Please provide NDI_SDK_INCLUDE_PATH and NDI_SDK_LIBRARY_PATH!") endif() find_path(NDI_INCLUDE_DIR NAMES Processing.NDI.compat.h Processing.NDI.deprecated.h Processing.NDI.DynamicLoad.h Processing.NDI.Find.h Processing.NDI.FrameSync.h Processing.NDI.Lib.cplusplus.h Processing.NDI.Lib.h Processing.NDI.Recv.ex.h Processing.NDI.Recv.h Processing.NDI.Routing.h Processing.NDI.Send.h Processing.NDI.structs.h Processing.NDI.utilities.h PATHS "${NDI_SDK_INCLUDE_PATH}" ) find_library(NDI_LIBRARY NAMES ndi PATHS "${NDI_SDK_LIBRARY_PATH}" ) if(NOT NDI_LIBRARY) message(FATAL_ERROR "NDI SDK: libndi.so / ndi.dll not found in:\n${NDI_SDK_LIBRARY_PATH}\nMaybe you have to create a symlink or rename the file.") endif() include(FindPackageHandleStandardArgs) find_package_handle_standard_args(NDI FOUND_VAR NDI_FOUND REQUIRED_VARS NDI_LIBRARY NDI_INCLUDE_DIR ) if(NDI_FOUND AND NOT TARGET NDI::NDI) add_library(NDI::NDI SHARED IMPORTED) set_target_properties(NDI::NDI PROPERTIES IMPORTED_LOCATION "${NDI_LIBRARY}" INTERFACE_INCLUDE_DIRECTORIES "${NDI_INCLUDE_DIR}" ) endif() mark_as_advanced( NDI_INCLUDE_DIR NDI_LIBRARY ) mlt-7.22.0/cmake/FindNode.cmake000664 000000 000000 00000007603 14531534050 016134 0ustar00rootroot000000 000000 # https://github.com/eclipse/upm/blob/master/cmake/modules/FindNode.cmake # Macro to add directory to NODEJS_INCLUDE_DIRS if it exists and is not /usr/include macro(add_include_dir dir) if (IS_DIRECTORY ${dir} AND NOT ${dir} STREQUAL "/usr/include") set(NODEJS_INCLUDE_DIRS ${NODEJS_INCLUDE_DIRS} ${dir}) endif() endmacro() find_program (NODEJS_EXECUTABLE NAMES node nodejs HINTS $ENV{NODE_DIR} PATH_SUFFIXES bin DOC "Node.js interpreter") include (FindPackageHandleStandardArgs) # If compat-libuv package exists, it must be at start of include path find_path (UV_ROOT_DIR "uv.h" PATHS /usr/include/compat-libuv010 NO_DEFAULT_PATH) if (UV_ROOT_DIR) # set (NODEJS_INCLUDE_DIRS ${UV_ROOT_DIR}) add_include_dir(${UV_ROOT_DIR}) endif() # Now look for node. Flag an error if not found find_path (NODE_ROOT_DIR NAMES node.h src/node.h PATH_SUFFIXES node node4 node5 node6 node7 node8 nodejs PATHS /usr/include /usr/local/include) if (NODE_ROOT_DIR) add_include_dir(${NODE_ROOT_DIR}) add_include_dir(${NODE_ROOT_DIR}/deps/uv/include) add_include_dir(${NODE_ROOT_DIR}/deps/v8/include) add_include_dir(${NODE_ROOT_DIR}/include/deps/uv/include) add_include_dir(${NODE_ROOT_DIR}/include/deps/v8/include) add_include_dir(${NODE_ROOT_DIR}/include/node) add_include_dir(${NODE_ROOT_DIR}/include/src) add_include_dir(${NODE_ROOT_DIR}/src) else() unset(NODEJS_INCLUDE_DIRS) message(ERROR " - node.h not found") endif() # Check that v8.h is in NODEJS_INCLUDE_DIRS find_path (V8_ROOT_DIR "v8.h" PATHS ${NODEJS_INCLUDE_DIRS}) if (NOT V8_ROOT_DIR) unset(NODEJS_INCLUDE_DIRS) message(ERROR " - v8.h not found") endif() # Check that uv.h is in NODEJS_INCLUDE_DIRS find_path (UV_ROOT_DIR "uv.h" PATHS ${NODEJS_INCLUDE_DIRS}) if (NOT UV_ROOT_DIR) unset(NODEJS_INCLUDE_DIRS) message(ERROR " - uv.h not found") endif() if (NODEJS_EXECUTABLE) execute_process(COMMAND ${NODEJS_EXECUTABLE} --version OUTPUT_VARIABLE _VERSION RESULT_VARIABLE _NODE_VERSION_RESULT) execute_process(COMMAND ${NODEJS_EXECUTABLE} -e "console.log(process.versions.v8)" OUTPUT_VARIABLE _V8_VERSION RESULT_VARIABLE _V8_RESULT) if (NOT _NODE_VERSION_RESULT AND NOT _V8_RESULT) string (REPLACE "v" "" NODE_VERSION_STRING "${_VERSION}") string (REPLACE "." ";" _VERSION_LIST "${NODE_VERSION_STRING}") list (GET _VERSION_LIST 0 NODE_VERSION_MAJOR) list (GET _VERSION_LIST 1 NODE_VERSION_MINOR) list (GET _VERSION_LIST 2 NODE_VERSION_PATCH) set (V8_VERSION_STRING ${_V8_VERSION}) string (REPLACE "." ";" _V8_VERSION_LIST "${_V8_VERSION}") string (REPLACE "." "" V8_DEFINE_STRING "${_V8_VERSION}") string (STRIP ${V8_DEFINE_STRING} V8_DEFINE_STRING) list (GET _V8_VERSION_LIST 0 V8_VERSION_MAJOR) list (GET _V8_VERSION_LIST 1 V8_VERSION_MINOR) list (GET _V8_VERSION_LIST 2 V8_VERSION_PATCH) # we end up with a nasty newline so strip everything that isn't a number string (REGEX MATCH "^[0-9]*" V8_VERSION_PATCH ${V8_VERSION_PATCH}) else () set (NODE_VERSION_STRING "0.10.30") set (NODE_VERSION_MAJOR "0") set (NODE_VERSION_MINOR "10") set (NODE_VERSION_PATCH "30") set (V8_VERSION_MAJOR "3") set (V8_VERSION_MINOR "28") set (V8_VERSION_PATCH "72") set (V8_VERSION_STRING "3.28.72") message (STATUS "defaulted to node 0.10.30") endif () string (REGEX REPLACE "\n" "" NODE_VERSION_STRING ${NODE_VERSION_STRING}) string (REGEX REPLACE "\n" "" V8_VERSION_STRING ${V8_VERSION_STRING}) mark_as_advanced (NODEJS_EXECUTABLE) find_package_handle_standard_args (Node REQUIRED_VARS NODEJS_EXECUTABLE NODEJS_INCLUDE_DIRS VERSION_VAR NODE_VERSION_STRING) message(STATUS "Found v8: ${V8_ROOT_DIR}/v8.h (found version \"${V8_VERSION_STRING}\")") endif () mlt-7.22.0/cmake/FindPHP.cmake000664 000000 000000 00000001761 14531534050 015675 0ustar00rootroot000000 000000 find_program(PHP_CONFIG_EXECUTABLE php-config) if(PHP_CONFIG_EXECUTABLE) execute_process(COMMAND ${PHP_CONFIG_EXECUTABLE} --version OUTPUT_VARIABLE PHP_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE ) execute_process(COMMAND ${PHP_CONFIG_EXECUTABLE} --includes OUTPUT_VARIABLE PHP_INCLUDE_DIRS OUTPUT_STRIP_TRAILING_WHITESPACE ) string(REPLACE "-I" "" PHP_INCLUDE_DIRS ${PHP_INCLUDE_DIRS}) separate_arguments(PHP_INCLUDE_DIRS) execute_process(COMMAND ${PHP_CONFIG_EXECUTABLE} --extension-dir OUTPUT_VARIABLE PHP_EXTENSION_DIR OUTPUT_STRIP_TRAILING_WHITESPACE ) endif() include(FindPackageHandleStandardArgs) find_package_handle_standard_args(PHP FOUND_VAR PHP_FOUND REQUIRED_VARS PHP_CONFIG_EXECUTABLE PHP_INCLUDE_DIRS VERSION_VAR PHP_VERSION ) if(PHP_FOUND AND NOT TARGET PHP::Extension) add_library(PHP::Extension INTERFACE IMPORTED) set_target_properties(PHP::Extension PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${PHP_INCLUDE_DIRS}" ) endif() mlt-7.22.0/demo/000775 000000 000000 00000000000 14531534050 013302 5ustar00rootroot000000 000000 mlt-7.22.0/demo/README000664 000000 000000 00000021724 14531534050 014170 0ustar00rootroot000000 000000 MLT Demo Notes Before running the demo script, make sure you '. setenv' from the parent directory. Also, please create clips clip1.dv, clip2.dv, clip3.dv, clip1.mpeg, clip2.mpeg, clip3.mpeg, and music1.ogg. Please make sure clips are at least 500 frames duration. These notes explain the the concepts presented in each demonstration and what details to look for. First, a note on consumers. When you start the script, the main menu asks you to choose a consumer. A consumer is like a viewer, but it could also write to a stream/file. The "SDL" consumer is the popular Simple DirectMedia Layer audio and video output. The "xml" consumer generates an XML representation of the service network. That can be played directly due to the XML producer plugin. See https://mltframework.org/docs/mltxml/ for more information. These examples assume the numeric locale LC_NUMERIC decimal separator is a period. Therefore, the demo script sets LC_NUMERIC=C for you, but if you are running these manually or learning from them, remember to use the appropriate separator for your locale. And now the demos... All clips Simply builds a playlist containing each video clip, and you can transport between them using j and k keys. Filter in/out A video filter can be applied to a portion of a producer (clip, playlist, or multitrack). This examples shows the greyscale filter. Watermark A graphic can overlay video in realtime with support for alpha channel. This example uses a PNG file with an alpha channel. Distortion is explicitly enabled here so the otherwise circular graphic is scaled to fill the compositing region. By default, compositing honours the aspect ratio of the overlay. My name is... Titles are very easy to composite in realtime. The titler uses Pango with the FreeType2 rendering backend. This means it supports high quality scalable font rendering with anti-aliasing, unicode (UTF-8), and Pango markup capabilities. The compsiting here respects the aspect ratio of the rendered title in the first two title pieces but distorts the final one. This demo also shows the motion and scaling capabilities of the compositor in conjunction with honouring aspect. The compositor is doing field-based rendering. So, when displayed non-progressively with SDL, you can see motion artifacts during animation. A composite transition The compositor also handles video over video as demonstrated in this usage of the compositor to create a special transition. This demonstration also crossfades the audio during the transition! Progressive rendering is explicitly enabled on the compositor due to the poor results that would otherwise occur due to scaling an interleaved video frame and moving the video in a reverse direction horizontally. Fade in and out A simple series of transitions between 3 clips using dissolves and audio crossfades. This is easy :-). Clock in and out Wipe transitions are very easy and highly extensible as they are generated using a very convenient lookup table based upon the luma of an image. This image can be a 16 bit PGM (grayscale bitmap) or the luma channel of any video producer. A number of high quality wipes can be downloaded from http://mlt.sf.net/. It also performs field rendering. The second wipe demonstrates the ability to control the direction of the wipe as well. Audio Stuff A music bed sound track can be mixed with a video. The sound track of the video clip has a "floating" amplitude normalisation filter applied. Typically, audio normalisation applies a constant gain factor across the entire duration of an audio segment from a single source where the gain factor is automatically determined by anaylsing the maximum "power" or peak levels. However, in news production, a popular requirement is to to dynamically boost the amplitude in soft areas and reduce the amplitude in louder areas. Thus, the gain analysis is performed using a "sliding window" approach. This example also applies a constant gain factor of 0.5 (50%) to the normalised audio of the video clip (to get a nicer mix level). Audio and Video Levels Audio can be normalised by setting a target amplitude level in decibels. A gamma curve can be applied to the luma channel of video. Shadowed Title and Watermark Two instances of the titler are used to create a shadow effect. The aspect ratio of the watermark in this example is not distorted. Since the original image is a circle with square pixels--a computer-generated image--and ITU BT.601 video is not composed of square samples. Therefore, the compositor normalises the pixel aspect ratio of the overlay to the destination image, and the circular image remains circular on the analog video output. Finally, a greyscale filter is applied to the watermark while its opacity is set at 30%. Station Promo into Story? Here is fun demo that might show using a still graphic with some music to introduce a show. A luma wipe with an audio crossfade transitions from the show title or station promotional material. Voiceover 2 clips with title A common news production requirement to have a "voiceover" audio track to a clip or even multiple clips as demonstrated here. Likewise, it is common to place a title caption on the video at the same time! This demo has a little fun with the titler at the sake of practicality :-) The foreground of the title is transparent while the opacity of the background is reduced to blend with the video. Meanwhile, the compositor stretches the image to fill the bottom slice of the video--not suitable for overscan displays ;-) Also, pay close attention to the mixing levels of the audio tracks. The audio of the video fades out as the voiceover track (just music in this demo) fades in. Then, the voiceover remains mixed with the ambient audio at a 60% level. Finally, the voiceover fades out smoothly from the 60% level to nothing. GJ-TTAvantika title This demo requires a special TrueType font called Avantika. If you have the font, register it with fontconfig using the fc-cache utility. This demonstrates i18n capabilities of the titler and the alignment capabilities of both the titler and the compositor. The titler centre aligns the two lines of text, and the compositor centre aligns the title horizontally on the frame. Title over graphic You can superimpose a title over a graphic over video! Also, you can apply a luma wipe to the compositor! Slideshow This demo requires any number of JPEG images with the extension ".jpg" in a subdirectory named "photos." Bouncy, Bouncy The "watermark" filter encapsulates the compositor, and you have full control over the compositor properties. Who says a watermark can not also be a video?! Bouncy, Bouncy Ball A variation on the above Bouncy, Bouncy demo that applies a shape, or alpha producer, to the the compositing region. Breaking News This demonstrates layout capabilities of the compositor. Squeeze Transitions This demonstrates a distorting barndoor-like wipe. J Cut A J cut is an edit where the audio cuts before the video. It gets its name from the way it looks on a NLE timeline user interface. When the audio cuts over, it does an audio crossfade over the duration of one frame. This makes the audio cut slightly less abrupt and avoids any "click" due to mismatched sample levels at the edit point. The video edit is a hard cut. L Cut An L cut is an edit where the video cuts before the audio. It gets its name from the way it looks on a NLE timeline user interface. This demo shows a very quick dissolve over 5 frames for a soft video cut. Like the J Cut demo, an audio crossfade for the duration of one frame makes an audio edit nearly instantaneous while being slightly softened and avoiding aberrations. Fade from/to black/silence Of course, it is possible using MLT to fade from black on video and silence on audio as well fade to black and silence. Push wipe A push wipe is a somewhat fancier transition than most standard wipes because it involves motion. The new video clip "pushes" the old video clip off one edge. If you can preview on an analog monitor you will notice how smooth the motion is due to field-based rendering. Ticker tape A very minimal reverse crawling title neard the bottom of the screen. The goal of the demo is show fluid motion of the field-based rendering of the compositor when viewed on an analog monitor using a DV or BlueFish444 consumer. The demo also shows the potientional for using and extending the existing set of services for a full blown news ticker implementation. Pango Keyframed Markup You can create timed text and subtitles using a .mpl file, which is a properties format file. A properties file contains key=value pairs on separate lines. For .mpl the key is a frame number and the value is Pango Markup Language. A tilde is interpreted as a new line. This example also demonstrates using the watermark and the alignment properties of its encapsulated composite transition where halign is the horizontal, valign is the vertical, c is for center, and m is for middle. mlt-7.22.0/demo/circle.png000664 000000 000000 00000012167 14531534050 015260 0ustar00rootroot000000 000000 ‰PNG  IHDR,,y}ŽusBIT|d.IDATxśíÝa°\eyŔń˙˝×$0BbĂ$”(HDef ĄĄkÇi;ĄN?8µ˘ťV«ťvô›ŽÎtŞ3Nż´úˇVeTŔ©Ž #&ŕ@'VB 4$&!q’÷Ţ~xö&››Ý˝gwĎ9ď9g˙ż™göĆ{÷śGö=ĎĽď»ďyĎŇéÎÖ‹%­/™őó"ŕ `0ż Ú^ÇŁŔËŔńÖëĚĎÇ€CŔ~ŕĹÖkűĎ€ť­K'ŚĄN@IŚ—«ZŻ«3S%6ËQŕIŕi`wëuđ8đ0ť*1ĄaÁjľĄŔeŔ[Ú^×Rť˘4¨ŁŔvŕQŕ‘¶×R&ĄbY°šgđ.`#p-p~ŇlĘ÷ p/° ř đ\Ňl”+ Vý-6´b#1÷¤“vĹks+öĄMGð`ŐĎbŕN¨µř9f5M #g Ř}Äżj†^}cŔ DqÚ¬#ľÓ𦀭DńÚÜŤůŇ@Ö_ö‘Q|ěmý7_—áó‘FŢJŕSŔc¤żxG=k}+{~bŇ9 ¸ř10Iú Ő85&[źÍ-­ĎJ9ŔőŔ׀äż(Ťlq¸ő™]ßú ĄF»řÎK5!ö¶>Ë‹‘fp;ůš“­ĎÖ‰zŐŢzŕ‡Ä×ç©/,ŁŘj}Öë‘jć:bmOę‹ČH›‰6 UÖp°…ôŚQŤŘB´ h«2&€ŰHŐŚmDń›E%3ř °ô„QŹŘA´™yH%ÚHl$—ú0ę»6$ę<ŕŇ7xŁqѦ¤\Í#î+;BúFn4+ŽmËa˘rq-±‡Rę†m4;¶mMČrŕë¸čÓ(/¦6·)Ł ŕŁŔAŇ7`c4ă Ń]ˇž®"v LÝ` cšh‹W!Í2ř,˙ŚęĹŃ6ť”Wpňˇś†QŐx‚h«a&Č™ş1F–8J´YŤ…Ŕ·Hß cřцGÎ(ŢEľŽřŔW§NDÂ“Ä Ő[S'R¦QűÚôV˘X-Kť4¤ß!n¤>üOÚT”·EŔwHß•7Ś"â;DoĽQ^IôŞ.LťT §!â©)R“‡„cŔǀۀĄ‰s‘жř ŕ% |˛äsJ*ĆZ`%.u(sHx1oĺŢVRs#ćł.ăde¬…Dö¦f©yv Ŕ źĎ*kHřeŕú’Î%©\K§ď|żč•ŃĂşŠŘŚ/ő"UIĹ™"6ýŰRäIŠ.XŔωů+IÍö0đv`˛¨=$ĽřË‚Ď!©Î^¤ŔUđEö°–Ź ň!Ňč8Ľxľ9ŻôĎX¬¤Qł¸ö QTëZ`SÇ—T]ÓŔFŕŢĽ\DA™ü‚¸«[Ňhú%đVŕ•<ZĤűßRŔq%ŐÇRŕ·Ä’¦ÜäÝĂ:ʍ¬ŻÍů¸’ęç·ÄHëŮĽ÷¤ű±XI Ż%jBnňěa˝‡xđ˘$µ»řQĘ«`Ť7@ş?»¤Ůž"6>ö@y oÁb%©ł ‰1´ĂCtÁ$© ž%özN-XK™µćA’*`qoó)CÂËŇä"I=ť¨Míë- ‘¤ąś¨Mö°$Uť=,Iµq˘6ÍLşO‡3“¤#IÝÎ&gzX«±XIަ3‰ubHř¦tąHŇśŢ' ÖŞ„‰HŇ\VÁÉ‚uAş<$iN€=,Iő`KRm\'—5ĽD|m(IUtX8śCëĆBIްĄă´mÝ Ivţ8đúÔYHRŻ–¤ÎB’2X2NĚaIRŐťcKR],±`IŞ ‡„’jĂ!ˇ¤ÚX2Ľ.u’”ÁëĆ©ł¤ X°$ŐĹ‚q`~ę,$)ů,Iu1ß!ˇ¤şpH(©6揯Ď%”¤*›źűo$©Ćă©“¤ ŽŹ/§ÎB’2xŮ–¤ş°‡%©6^¶`IŞ ‡„’jă¸KR]¦ÎB’288ěOť…$e°x1u’”Á‹ö°$ŐĹ~ –¤şpH(©6JŞŤýăŔŢÔYHR{Ç'?ż:IšĂұÖ/g§ĚD’z8 ,śŮqô鄉HŇ\ž†ŘqôÄ?$©˘ž†“kwş<$iN»Á–¤zxěaIއSzXżJ$ÍĺW3Ë&Ż ĎL–Ž$uv”XvuâAŞ“ŔötůHRWۉEű“źM“‹$őt˘6µ¬G$"Is9Q›ěaIŞşµi¬í\ ě+?Ięi­ Ú{X/Ď&IG’:{–¶ÝdĆgýrsąąHRO§Ô¤ŮkS‰‰HŇ\N©Icł~ůJŞŽó€_Ďücvë×ŔÎRÓ‘¤ÎvŇV¬ŕô‚ %UĂiµ¨SÁrâ]RśV‹fĎaA¬yxľËď$© ÓŔrf­ íÔĂÚ‡7BKJk;˛w*Xŕ<–¤´:Ö nËy,I)u¬AÝć©Ëá' KG’:›$îm>0űÝzX€‡‹ĚH’şxĹ ş,pX()Ť®µ§WÁş§€D$i.]kOݵVŻž#ÖeIRö+€W;ý˛WëUŕ¶"2’¤.nŁK±‚Ţ ŕkůć"I=ő¬9Ynży X›O.’ÔŐvŕÍ˝ţ`®Ŕć“‹$ő4g­ÉŇĂZ ÇĹ ňYü9AlIÇ’ÔLŹłÉ|ß`'yěÓ> |>‡ăHj®Ď3d±‚ünŻ'nŠľ0§ăIjާ›ś§†=P^O™nÍéX’šĺVr(V~„;9H:ŐťDmČEŢ;.śüxmÎÇ•T?ż.žÍë€y¨ĺ1±v]ÎÇ•T?ź~ç‹ŘÓj±ôŇŽ-©~I,}%ĎńřůW€Ź0ä1Iµ5MÔ€\‹S° ¶ŹřfAÇ–Tmߤ í§ŠÜćx9°XTŕ9$UË!ŕŤŔóE<ďI÷vGo ŢSŕ9$UË'MEĽčIL?®(ř<’Ň{x;9Ü‚ÓMQsX3&ż&§U®’*kŠ¸Ö +VP|Á‚Ř•ô«%śGR:_%®őB•őlÁ…Äł ×”t>IĺŮIĽBާ€uÄ ÎI¤Ăjw糤:™·JV¬ ÝV»˙%&ďÜŐAŞ®ŹßMťDę!a»;€÷ĄNBŇiîţ(uP­‚5ř>ŔBŞ’{»S^NťT«`ś l&¤IJë!`p8u"3ŞV°–÷ăÎRJ;«}©iWĹ‚° x87u"ŇÚ¬v§Nd¶ÔËşŮM|kô+Ti"®˝Ę+¨nÁŘü!îˇ%•ĺqÍmKťH7U.X÷JÁűDKb’¸ÖîKťH/UX8:—Äć7ĄNDj°ßHťÄ\ęP° ľ^ť"ľb•”ŻĎ_LťDu)X?–ďHťÔ ˙ üCę$˛ŞSÁ¸ XAÜ1.i8_>L§N$«ş¬ibĂ{‡‡Ňp>|‚+¨_ÁšńSbqŰŤT˙›N©J&‰ öZĚYÍVŐ•îYÝ |8#u"R #–.$ß&fPu/X×űK/JťTa‡Eˇ•^g5—&,€Ë὇R'{Űm*»‚=«¦,¦ďĆ]¤v;¨č˝ýjŇ„őnb;ڇR'"UÄCÄ5ŃbÍ*X{÷l vI”FŮ=ĵP©ý¬†Ő´‚±;âŤŔ÷R'"%ň=â¨ĚNˇyibÁxx/đ7ř1ŤŽăD›/q 4N“&Ý»ąř>¬UÍöńÜŔS'R¤¦ö°Ú=HÜ{x{ęD¤‚ÜN´ńF«Qt+±ÚwÚ0Ç6=2FaH8Ű:b¸:u"Ňž$†€[S'R¦Qζ•xîá·S'" čŰD©bĄ¸ký(é»ö†‘%ŽmV#ě ŕ Ň7FĂčOmUbđYbcŔÔ Ó0ÚcŠh›ófąŠHÝH cšh‹W!ő0|8HúkŚf$Ú`]wVËŻă0Ń(/¦6·i@×ŰIߍfÇv˘­IC›| 8Bú†m4+ŽmËIuĺî<ŕŇ7rŁqѦ¤Bmv‘ľÁőŚ]D’J3ř °ô€QŹŘA´‡Jf‚¸ ué/Łš±Ťh#.SPeŚ7[HŐ-D›Ĺ]QT#×›IÁib3ѤZYüźŽBLµ>ëőH57łEó$é/,#ßääĹRŁ\ |ŘKú Í.ö¶>Ë‹‘n¸řńě¸Ôź‘-·>łëń?Ť¨ł€[€㱊1Ůúlni}V’ZV÷•=Fú uÔă±Ög±˛ç'& IÜ/á|W™±·őßÜ ôŠrA[őŤ7÷žm .¦Q|ÚQ¦]=7›€»‰ÂĄŠ˛`ŐĎbŕ˘xmÖâçŐ4±ďÔ&˘HÝHš‘úbCŻżeDńš)`kҦS9;9Y 6űҦŁaX°šgđ.˘x] śź4›ň=ÜK©źĎ%ÍFą˛`5ßRŕ2ŕ-mŻk3S&•ŁÄđîQŕ‘¶×R&ĄbY°FÓ±Bűŕ"ŕ`Uëu5Ő)fG'§Ý­×]Ŕăś|ř­FKťśCĚ…-–´ţ˝dÖĎ‹€3€ŔüV,h{' ÎËŔńÖëĚĎÇ€CŔ~ŕĹÖkűĎą§‹ţ?ŞzůK¸„ˇ8eÇIEND®B`‚mlt-7.22.0/demo/circle.svg000664 000000 000000 00000000125 14531534050 015262 0ustar00rootroot000000 000000 mlt-7.22.0/demo/consumers.ini000664 000000 000000 00000000376 14531534050 016027 0ustar00rootroot000000 000000 SDL Default sdl2 SDL Half D1 sdl2:360x288 rescale=nearest resize=1 SDL High Latency sdl2 buffer=12 rescale=none SDL Progressive sdl2 progressive=1 XML to Terminal xml XML to File xml: DeckLink decklink DeckLink Prog LL decklink progressive=1 buffer=1 mlt-7.22.0/demo/demo000775 000000 000000 00000004017 14531534050 014156 0ustar00rootroot000000 000000 #!/bin/bash export MLT_PROFILE=dv_pal export LC_NUMERIC=C function show_consumers( ) { awk -F '\t' '{ printf( "%d. %s\n", ++ i, $1 ); }' < consumers.ini } function get_consumer( ) { option=$1 [ "$option" != "" ] && [ $option -gt 0 ] && sed 's/\t\+/\t/g' < consumers.ini | cut -f 2 | head -n $option | tail -n -1 } function show_menu( ) { sed 's/\t\+/\t/g' < demo.ini | awk -F '\t' '{ printf( "%2d. %-30.30s", ++ i, $2 ); if ( i % 2 == 0 ) printf( "\n" ); } END { if ( i % 2 == 1 ) printf( "\n" ); }' } function check_dependencies( ) { option=$1 if [ $option -gt 0 ] then deps=`sed 's/\t\+/\t/g' < demo.ini | cut -f 3 | head -n $option | tail -n -1` if [ "$deps" != "" ] then echo "$deps" | tr ',' '\n' | while read dep do ls $dep > /dev/null 2>&1 val=$? [ $val != 0 ] && echo Failed to find $dep >&2 && echo $val done fi echo 0 fi } function get_demo( ) { option=$1 if [ $option -gt 0 ] then cut -f 1 demo.ini | head -n $option | tail -n -1 fi } while [ 1 ] do echo Select Consumer echo show_consumers echo echo 0. Exit echo echo -n "Option: " read option echo [ "$option" == "0" ] && break export MLT_CONSUMER=`get_consumer $option` while [ "$option" != "0" -a "$MLT_CONSUMER" != "" ] do echo Choose Demo echo show_menu echo echo -n "Option: " read option echo [ "$option" == "" ] && break demo=`get_demo $option` usable=`check_dependencies $option` if [ "$usable" = "0" -a "$demo" != "" ] then if [ "$MLT_CONSUMER" == "xml:" ] then export XML_CONSUMER="xml:$demo.mlt" bash $demo -consumer $XML_CONSUMER melt +$demo.txt out=100 $demo.mlt $demo.mlt -filter watermark:watermark1.png composite.fill=1 composite.geometry=85%/5%:10%x10% elif [ "$MLT_CONSUMER" == "xml" ] then bash $demo -consumer $MLT_CONSUMER | less else bash $demo -consumer $MLT_CONSUMER fi elif [ "$usable" != "" ] then echo echo Unable to locate suitable files for the demo - please provide them. read pause fi stty sane done done mlt-7.22.0/demo/demo.ini000664 000000 000000 00000002500 14531534050 014724 0ustar00rootroot000000 000000 mlt_all All clips clip* mlt_effect_in_middle Filter in/out clip1.mpeg mlt_watermark Watermark clip2.dv,watermark1.png mlt_my_name_is My name is... clip3.dv mlt_composite_transition A composite transition clip1.dv,clip2.mpeg mlt_fade_in_and_out Fade in and out clip1.dv,clip2.mpeg,clip3.dv mlt_clock_in_and_out Clock in and out clip2.dv,clip1.dv,clip3.mpeg mlt_audio_stuff Audio Stuff clip*.dv,music1.ogg mlt_levels Audio and Video Levels clip*.dv mlt_titleshadow_watermark Shadowed Title and Watermark clip3.dv mlt_intro Station Promo into Story? watermark1.png,clip3.mpeg,music1.ogg mlt_voiceover Voiceover 2 clips with title clip1.dv,clip2.mpeg,music1.ogg mlt_avantika_title GJ-TTAvantika title pango.mlt mlt_title_over_gfx Title over graphic watermark1.png,clip1.dv mlt_slideshow Slideshow photos mlt_bouncy Bouncy, Bouncy clip1.dv,clip3.dv mlt_news Breaking News clip1.dv,clip2.dv mlt_squeeze Squeeze Transitions clip1.dv,clip2.dv,clip3.dv mlt_squeeze_box Squeeze Box clip1.dv,clip2.dv,clip3.dv mlt_jcut J Cut clip1.dv,clip2.dv mlt_lcut L Cut clip1.dv,clip2.dv mlt_fade_black Fade from/to black/silence clip3.mpeg mlt_push Push wipe clip1.mpeg, clip2.mpeg mlt_ticker Ticker tape clip1.dv mlt_slideshow_black Composite slideshow photos mlt_slideshow2 Ken Burns slideshow photos mlt_pango_keyframes Pango Keyframed Markup pango_keyframes.mpl mlt-7.22.0/demo/demo.kino000664 000000 000000 00000000661 14531534050 015113 0ustar00rootroot000000 000000 mlt-7.22.0/demo/entity.mlt000664 000000 000000 00000000432 14531534050 015333 0ustar00rootroot000000 000000 ]> pango Hello &name;, My name is Inigo Montoya. mlt-7.22.0/demo/luma1.pgm000664 000000 000000 00001452017 14531534050 015040 0ustar00rootroot000000 000000 P5 720 576 255 ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖ×××××××××ŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢßßßßßßßŕŕŕŕŕŕŕááááááâââââââăăăăăăääääääĺĺĺĺĺĺććććććççççççčččččééééééęęęęęëëëëëěěěěěěíííííîîîîîďďďďďđđđđđńńńńńňňňňňóóóóôôôôôőőőőőööööö÷÷÷÷řřřřřůůůůúúúúúűűűűüüüüüýýýýţţţţţ˙˙˙˙  !!!!!!!!"""""""########$$$$$$$$%%%%%%%%%&&&&&&&&''''''''''((((((((()))))))))))***********++++++++++,,,,,,,,,,,,,--------------.............///////////////00000000000000001111111111111111ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖ××××××××××ŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢßßßßßßßŕŕŕŕŕŕŕáááááááââââââăăăăăăäääääääĺĺĺĺĺĺććććććçççççččččččéééééęęęęęęëëëëëěěěěěíííííîîîîîďďďďďđđđđđńńńńńňňňňňóóóóóôôôôőőőőőööööö÷÷÷÷řřřřřůůůůúúúúúűűűűüüüüüýýýýţţţţţ˙˙˙˙  !!!!!!!""""""""#######$$$$$$$$$%%%%%%%&&&&&&&&&&'''''''''(((((((((())))))))))**********++++++++++++,,,,,,,,,,,,-------------..............////////////////00000000000000001111111111111111ÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖ××××××××ŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢßßßßßßßŕŕŕŕŕŕŕáááááááââââââăăăăăăääääääĺĺĺĺĺĺććććććççççççčččččééééééęęęęęëëëëëěěěěěíííííîîîîîîďďďďđđđđđńńńńńňňňňňóóóóóôôôôőőőőőööööö÷÷÷÷řřřřřůůůůúúúúúűűűűűüüüüýýýýýţţţţ˙˙˙˙  !!!!!!!""""""""########$$$$$$$%%%%%%%%%&&&&&&&&'''''''''''(((((((()))))))))))***********+++++++++++,,,,,,,,,,,,,------------.............///////////////00000000000000000111111111111111112ÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖ××××××××××ŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢßßßßßßŕŕŕŕŕŕŕŕááááááââââââăăăăăăäääääääĺĺĺĺĺććććććççççççččččččéééééęęęęęëëëëëěěěěěěíííííîîîîîďďďďďđđđđđńńńńňňňňňóóóóóôôôôôőőőőööööö÷÷÷÷řřřřřůůůůůúúúúűűűűűüüüüýýýýýţţţţ˙˙˙˙  !!!!!!!!"""""""########$$$$$$$$%%%%%%%%&&&&&&&&&'''''''''(((((((((())))))))))***********+++++++++++,,,,,,,,,,,--------------..............///////////////00000000000000011111111111111111122ÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖ×××××××××ŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝŢŢŢŢŢŢŢßßßßßßßŕŕŕŕŕŕŕáááááááââââââăăăăăăääääääĺĺĺĺĺĺććććććçççççččččččéééééęęęęęęëëëëëěěěěěíííííîîîîîďďďďďđđđđđńńńńńňňňňóóóóóôôôôôőőőőööööö÷÷÷÷÷řřřřůůůůůúúúúűűűűűüüüüýýýýýţţţţ˙˙˙˙  !!!!!!!"""""""########$$$$$$$$%%%%%%%%%&&&&&&&&''''''''''((((((((()))))))))))**********+++++++++++,,,,,,,,,,,,------------..............////////////////000000000000000011111111111111111122ÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖ×××××××××××ŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢßßßßßßŕŕŕŕŕŕŕŕááááááââââââăăăăăăăäääääĺĺĺĺĺĺĺćććććççççççčččččééééééęęęęęëëëëëěěěěěííííííîîîîďďďďďđđđđđńńńńńňňňňňóóóóôôôôôőőőőőöööö÷÷÷÷÷řřřřůůůůůúúúúűűűűűüüüüýýýýýţţţţ˙˙˙˙  !!!!!!!""""""""#######$$$$$$$$$%%%%%%%%&&&&&&&&&''''''''((((((((((()))))))))***********+++++++++++,,,,,,,,,,,,-------------.............///////////////00000000000000001111111111111111122222ÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖ×××××××××ŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝŢŢŢŢŢŢßßßßßßßŕŕŕŕŕŕŕááááááâââââââăăăăăăääääääĺĺĺĺĺĺććććććçççççččččččéééééęęęęęëëëëëëěěěěěíííííîîîîîďďďďđđđđđńńńńńňňňňňóóóóôôôôôőőőőőöööö÷÷÷÷÷řřřřůůůůůúúúúűűűűűüüüüýýýýýţţţţ˙˙˙˙  !!!!!!""""""""########$$$$$$$$%%%%%%%%%&&&&&&&''''''''''((((((((()))))))))))*********+++++++++++++,,,,,,,,,,,------------...............///////////////00000000000000011111111111111111222222ÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖ××××××××××ŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝŢŢŢŢŢŢŢßßßßßßßŕŕŕŕŕŕŕááááááââââââăăăăăăăäääääĺĺĺĺĺĺććććććçççççččččččéééééęęęęęęëëëëëěěěěěíííííîîîîîďďďďďđđđđđńńńńňňňňňóóóóóôôôôőőőőőööööö÷÷÷÷řřřřřůůůůúúúúűűűűűüüüüýýýýýţţţţ˙˙˙˙  !!!!!!!"""""""########$$$$$$$$%%%%%%%%&&&&&&&&&'''''''''(((((((((()))))))))***********+++++++++++,,,,,,,,,,,,-------------.............///////////////0000000000000000011111111111111111222222ÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖ××××××××ŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝŢŢŢŢŢŢßßßßßßßŕŕŕŕŕŕŕááááááâââââââăăăăăăääääääĺĺĺĺĺĺćććććççççççčččččééééééęęęęęëëëëëěěěěěíííííîîîîîďďďďďđđđđđńńńńňňňňňóóóóóôôôôőőőőőööööö÷÷÷÷řřřřřůůůůúúúúúűűűűüüüüýýýýýţţţţ˙˙˙˙  !!!!!!""""""""#######$$$$$$$$%%%%%%%%%&&&&&&&&''''''''''(((((((()))))))))))**********++++++++++++,,,,,,,,,,,-------------..............//////////////00000000000000011111111111111111122222222ÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖ××××××××××ŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝŢŢŢŢŢŢŢßßßßßßßŕŕŕŕŕŕáááááááââââââăăăăăăäääääääĺĺĺĺĺććććććçççççččččččéééééęęęęęëëëëëëěěěěííííííîîîîďďďďďđđđđđńńńńńňňňňňóóóóôôôôôőőőőööööö÷÷÷÷řřřřřůůůůúúúúúűűűűüüüüýýýýýţţţţ˙˙˙˙  !!!!!!!"""""""########$$$$$$$$%%%%%%%%&&&&&&&&&''''''''(((((((((()))))))))***********+++++++++++,,,,,,,,,,,,,-----------..............////////////////00000000000000011111111111111111222222222ÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖ×××××××××ŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢßßßßßßŕŕŕŕŕŕŕáááááááââââââăăăăăăääääääĺĺĺĺĺĺćććććççççççčččččéééééęęęęęęëëëëëěěěěěíííííîîîîîďďďďđđđđđńńńńńňňňňňóóóóôôôôôőőőőööööö÷÷÷÷řřřřřůůůůúúúúúűűűűüüüüýýýýýţţţţ˙˙˙˙  !!!!!!!""""""""#######$$$$$$$$%%%%%%%%%&&&&&&&''''''''''((((((((())))))))))**********++++++++++++,,,,,,,,,,,-------------.............//////////////00000000000000000111111111111111112222222222ÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖ××××××××××ŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝŢŢŢŢŢŢŢßßßßßßßŕŕŕŕŕŕŕááááááâââââââăăăăăääääääĺĺĺĺĺĺććććććçççççččččččéééééęęęęęëëëëëěěěěěíííííîîîîîďďďďďđđđđđńńńńňňňňňóóóóôôôôôőőőőőöööö÷÷÷÷řřřřřůůůůúúúúúűűűűüüüüýýýýýţţţţ˙˙˙˙  !!!!!!!"""""""########$$$$$$$%%%%%%%%&&&&&&&&&''''''''(((((((((())))))))))**********+++++++++++,,,,,,,,,,,,------------...............//////////////00000000000000011111111111111111222222222222ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖ×××××××××ŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢßßßßßßŕŕŕŕŕŕŕáááááááââââââăăăăăăääääääĺĺĺĺĺććććććççççççčččččéééééęęęęęëëëëëëěěěěíííííîîîîîďďďďďđđđđđńńńńńňňňňóóóóóôôôôőőőőőöööö÷÷÷÷÷řřřřůůůůúúúúúűűűűüüüüýýýýýţţţţ˙˙˙˙  !!!!!!!""""""""######$$$$$$$$%%%%%%%%%&&&&&&&&'''''''''((((((((())))))))))***********++++++++++,,,,,,,,,,,,-------------............////////////////000000000000000011111111111111112222222222222ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖ××××××××××ŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝŢŢŢŢŢŢßßßßßßßŕŕŕŕŕŕŕááááááââââââăăăăăăăäääääĺĺĺĺĺĺććććććçççççčččččééééééęęęęęëëëëëěěěěěíííííîîîîîďďďďđđđđđńńńńńňňňňóóóóóôôôôőőőőőöööö÷÷÷÷÷řřřřůůůůůúúúúűűűűüüüüüýýýýţţţţ˙˙˙˙  !!!!!!"""""""########$$$$$$$$%%%%%%%%&&&&&&&&&''''''''((((((((()))))))))))*********++++++++++++,,,,,,,,,,,-------------.............//////////////00000000000000001111111111111111122222222222222ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖ×××××××××ŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÝÝÝÝÝÝÝŢŢŢŢŢŢŢßßßßßßßŕŕŕŕŕŕáááááááââââââăăăăăăääääääĺĺĺĺĺĺćććććçççççččččččéééééęęęęęëëëëëěěěěěíííííîîîîîďďďďđđđđđńńńńńňňňňňóóóóôôôôôőőőőööööö÷÷÷÷řřřřůůůůůúúúúűűűűüüüüüýýýýţţţţ˙˙˙˙  !!!!!!!"""""""#######$$$$$$$$%%%%%%%%%&&&&&&&''''''''''((((((((()))))))))***********++++++++++,,,,,,,,,,,,,-----------..............///////////////00000000000000111111111111111112222222222222222ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖ××××××××××ŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝŢŢŢŢŢŢŢßßßßßßŕŕŕŕŕŕŕááááááââââââăăăăăăääääääĺĺĺĺĺĺćććććççççççčččččéééééęęęęęëëëëëěěěěěěííííîîîîîďďďďďđđđđđńńńńňňňňňóóóóôôôôôőőőőööööö÷÷÷÷řřřřůůůůůúúúúűűűűüüüüüýýýýţţţţ˙˙˙˙  !!!!!!!"""""""########$$$$$$$%%%%%%%%&&&&&&&&&''''''''(((((((((())))))))))*********++++++++++++,,,,,,,,,,,-------------............///////////////0000000000000000111111111111111122222222222222222ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖ×××××××××ŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢßßßßßßŕŕŕŕŕŕáááááááââââââăăăăăăäääääĺĺĺĺĺĺććććććçççççččččččéééééęęęęęëëëëëěěěěěíííííîîîîďďďďďđđđđđńńńńňňňňňóóóóóôôôôőőőőööööö÷÷÷÷řřřřřůůůůúúúúűűűűüüüüüýýýýţţţţ˙˙˙˙  !!!!!!!!"""""""#######$$$$$$$$%%%%%%%%&&&&&&&&'''''''''((((((((()))))))))***********++++++++++,,,,,,,,,,,,,-----------..............//////////////00000000000000011111111111111111222222222222222222ĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖ×××××××××ŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝŢŢŢŢŢŢŢßßßßßßŕŕŕŕŕŕŕáááááááâââââăăăăăăääääääĺĺĺĺĺĺćććććççççççčččččéééééęęęęęëëëëëěěěěěíííííîîîîîďďďďđđđđđńńńńńňňňňóóóóóôôôôőőőőőöööö÷÷÷÷řřřřřůůůůúúúúűűűűüüüüüýýýýţţţţ˙˙˙˙  !!!!!!!"""""""########$$$$$$$%%%%%%%%&&&&&&&&&''''''''((((((((())))))))))**********+++++++++++,,,,,,,,,,,-------------............////////////////00000000000000111111111111111122222222222222222223ĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖ××××××××××ŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÝÝÝÝÝÝŢŢŢŢŢŢŢŢßßßßßßŕŕŕŕŕŕŕááááááââââââăăăăăăääääääĺĺĺĺĺććććććçççççčččččéééééęęęęęęëëëëěěěěěíííííîîîîîďďďďďđđđđńńńńńňňňňóóóóóôôôôőőőőőöööö÷÷÷÷řřřřřůůůůúúúúűűűűűüüüüýýýýţţţţ˙˙˙˙  !!!!!!!!""""""#######$$$$$$$$%%%%%%%%%&&&&&&&'''''''''(((((((((())))))))***********+++++++++++,,,,,,,,,,,------------............../////////////00000000000000001111111111111111122222222222222222233ĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖ×××××××××ŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝŢŢŢŢŢŢßßßßßßßŕŕŕŕŕŕáááááááââââââăăăăăääääääĺĺĺĺĺĺćććććçççççččččččéééééęęęęęëëëëëěěěěěííííîîîîîďďďďďđđđđđńńńńňňňňňóóóóôôôôőőőőőöööö÷÷÷÷řřřřřůůůůúúúúűűűűűüüüüýýýýţţţţ˙˙˙˙  !!!!!!""""""""#######$$$$$$$%%%%%%%%&&&&&&&&&''''''''((((((((())))))))))**********+++++++++++,,,,,,,,,,,,-----------..............//////////////00000000000000111111111111111112222222222222222222233ĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖ×××××××××ŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÝÝÝÝÝÝÝŢŢŢŢŢŢŢßßßßßßŕŕŕŕŕŕŕááááááââââââăăăăăăääääääĺĺĺĺĺćććććççççççčččččéééééęęęęęëëëëëěěěěěíííííîîîîďďďďďđđđđđńńńńňňňňňóóóóôôôôôőőőőöööö÷÷÷÷÷řřřřůůůůúúúúűűűűűüüüüýýýýţţţţ˙˙˙˙  !!!!!!!"""""""#######$$$$$$$$%%%%%%%%&&&&&&&&'''''''''((((((((()))))))))**********+++++++++++,,,,,,,,,,,-------------............///////////////000000000000000111111111111111222222222222222222233333ĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖ××××××××ŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢßßßßßßŕŕŕŕŕŕááááááâââââââăăăăăăäääääĺĺĺĺĺććććććçççççčččččééééééęęęęęëëëëěěěěěíííííîîîîîďďďďđđđđđńńńńńňňňňóóóóôôôôôőőőőööööö÷÷÷÷řřřřůůůůúúúúúűűűűüüüüýýýýţţţţ˙˙˙˙  !!!!!!!""""""""#######$$$$$$$%%%%%%%%&&&&&&&&'''''''''(((((((())))))))))***********++++++++++,,,,,,,,,,,,-----------............../////////////00000000000000011111111111111111222222222222222222333333ĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖ×××××××××ŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÝÝÝÝÝÝŢŢŢŢŢŢŢßßßßßßŕŕŕŕŕŕŕááááááââââââăăăăăăäääääĺĺĺĺĺĺćććććçççççččččččéééééęęęęęëëëëëěěěěěííííîîîîîďďďďďđđđđńńńńńňňňňóóóóóôôôôőőőőööööö÷÷÷÷řřřřůůůůúúúúúűűűűüüüüýýýýţţţţ˙˙˙˙  !!!!!!!""""""########$$$$$$$$%%%%%%%%&&&&&&&'''''''''((((((((()))))))))**********++++++++++++,,,,,,,,,,-------------............///////////////00000000000001111111111111111122222222222222222222333333ĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖ×××××××××ŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰÜÜÜÜÜÜÜÝÝÝÝÝÝÝŢŢŢŢŢŢŢßßßßßßŕŕŕŕŕŕááááááââââââăăăăăăääääääĺĺĺĺĺććććććçççççčččččéééééęęęęęëëëëëěěěěěíííííîîîîďďďďďđđđđńńńńńňňňňóóóóóôôôôőőőőööööö÷÷÷÷řřřřůůůůúúúúúűűűűüüüüýýýýţţţţ˙˙˙˙  !!!!!!!"""""""#######$$$$$$$%%%%%%%%&&&&&&&&&''''''''((((((((())))))))))**********++++++++++,,,,,,,,,,,------------.............//////////////0000000000000001111111111111111222222222222222222333333333ĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖ××××××××××ŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝŢŢŢŢŢŢßßßßßßŕŕŕŕŕŕŕááááááââââââăăăăăăäääääĺĺĺĺĺĺćććććççççççččččééééééęęęęęëëëëëěěěěíííííîîîîîďďďďđđđđđńńńńňňňňňóóóóôôôôőőőőőöööö÷÷÷÷řřřřůůůůůúúúúűűűűüüüüýýýýţţţţ˙˙˙˙  !!!!!!"""""""########$$$$$$$%%%%%%%%&&&&&&&&''''''''(((((((((()))))))))*********+++++++++++,,,,,,,,,,,------------.............//////////////00000000000000011111111111111111222222222222222223333333333ĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖ××××××××ŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰÜÜÜÜÜÜÜÝÝÝÝÝÝÝŢŢŢŢŢŢŢßßßßßßŕŕŕŕŕŕŕááááááâââââăăăăăăääääääĺĺĺĺĺććććććçççççčččččéééééęęęęęëëëëëěěěěěííííîîîîîďďďďđđđđđńńńńňňňňňóóóóôôôôôőőőőöööö÷÷÷÷řřřřůůůůůúúúúűűűűüüüüýýýýţţţţ˙˙˙˙  !!!!!!!"""""""#######$$$$$$$%%%%%%%%&&&&&&&&'''''''''(((((((())))))))))**********++++++++++,,,,,,,,,,,-------------...........///////////////000000000000001111111111111111222222222222222222223333333333ĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖ×××××××××ŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÝÝÝÝÝÝÝŢŢŢŢŢŢŢßßßßßßŕŕŕŕŕŕááááááââââââăăăăăăäääääĺĺĺĺĺĺćććććçççççčččččéééééęęęęęëëëëëěěěěěííííîîîîîďďďďďđđđđńńńńńňňňňóóóóôôôôôőőőőöööö÷÷÷÷řřřřřůůůůúúúúűűűűüüüüýýýýţţţţ˙˙˙˙  !!!!!!!"""""""#######$$$$$$$$%%%%%%%%&&&&&&&'''''''''((((((((()))))))))*********+++++++++++,,,,,,,,,,,,----------............../////////////00000000000000001111111111111112222222222222222223333333333333ĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖ×××××××××ŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰÜÜÜÜÜÜÝÝÝÝÝÝÝŢŢŢŢŢŢŢßßßßßßŕŕŕŕŕŕŕááááááââââââăăăăăääääääĺĺĺĺĺććććććçççççčččččéééééęęęęęëëëëëěěěěíííííîîîîďďďďďđđđđńńńńńňňňňóóóóóôôôôőőőőöööö÷÷÷÷řřřřřůůůůúúúúűűűűüüüüýýýýţţţţ˙˙˙˙  !!!!!!!"""""""######$$$$$$$$%%%%%%%&&&&&&&&&''''''''((((((((()))))))))**********+++++++++++,,,,,,,,,,------------............///////////////00000000000000111111111111111112222222222222222233333333333333ĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖ×××××××××ŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÝÝÝÝÝÝÝŢŢŢŢŢŢŢßßßßßßŕŕŕŕŕŕááááááââââââăăăăăăäääääĺĺĺĺĺĺćććććçççççčččččéééééęęęęęëëëëëěěěěíííííîîîîîďďďďđđđđđńńńńňňňňóóóóóôôôôőőőőöööö÷÷÷÷÷řřřřůůůůúúúúűűűűüüüüýýýýţţţţ˙˙˙˙  !!!!!!!"""""""#######$$$$$$$$%%%%%%%&&&&&&&&''''''''((((((((())))))))))*********++++++++++,,,,,,,,,,,,-----------............./////////////0000000000000001111111111111112222222222222222222233333333333333ĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖ××××××××ŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰÜÜÜÜÜÜÜÝÝÝÝÝÝŢŢŢŢŢŢŢßßßßßßŕŕŕŕŕŕŕááááááââââââăăăăăääääääĺĺĺĺĺćććććçççççččččččéééééęęęęëëëëëěěěěěíííííîîîîďďďďđđđđđńńńńňňňňňóóóóôôôôőőőőööööö÷÷÷÷řřřřůůůůúúúúűűűűüüüüýýýýţţţţ˙˙˙˙  !!!!!!!""""""#######$$$$$$$%%%%%%%%&&&&&&&&'''''''''(((((((()))))))))**********+++++++++++,,,,,,,,,,------------............./////////////00000000000000011111111111111112222222222222222233333333333333333ËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖ×××××××××ŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰÜÜÜÜÜÜÜÝÝÝÝÝÝÝŢŢŢŢŢŢŢßßßßßßŕŕŕŕŕŕŕáááááââââââăăăăăääääääĺĺĺĺĺĺćććććçççççčččččéééééęęęęęëëëëěěěěěíííííîîîîďďďďďđđđđńńńńńňňňňóóóóôôôôőőőőőöööö÷÷÷÷řřřřůůůůúúúúűűűűüüüüýýýýţţţţ˙˙˙˙  !!!!!!!""""""########$$$$$$$%%%%%%%%&&&&&&&''''''''((((((((())))))))))*********++++++++++,,,,,,,,,,,-------------...........///////////////00000000000001111111111111111122222222222222222333333333333333333ËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖ×××××××××ŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰÜÜÜÜÜÜÝÝÝÝÝÝÝŢŢŢŢŢŢßßßßßßŕŕŕŕŕŕŕááááááââââââăăăăăääääääĺĺĺĺĺćććććçççççčččččéééééęęęęęëëëëëěěěěíííííîîîîîďďďďđđđđńńńńńňňňňóóóóôôôôôőőőőöööö÷÷÷÷řřřřůůůůúúúúűűűűüüüüýýýýţţţţ˙˙˙˙  !!!!!!"""""""######$$$$$$$$%%%%%%%%&&&&&&&&''''''''((((((((())))))))**********+++++++++++,,,,,,,,,,,----------..............////////////00000000000000001111111111111122222222222222222222333333333333333333ËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖ×××××××××ŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÝÝÝÝÝÝÝŢŢŢŢŢŢßßßßßßŕŕŕŕŕŕŕááááááâââââăăăăăăäääääĺĺĺĺĺćććććççççççčččččéééééęęęęëëëëëěěěěěííííîîîîîďďďďđđđđđńńńńňňňňóóóóóôôôôőőőőöööö÷÷÷÷řřřřůůůůúúúúűűűűüüüüýýýýţţţţ˙˙˙˙  !!!!!!"""""""#######$$$$$$$$%%%%%%%&&&&&&&&''''''''(((((((())))))))))**********+++++++++,,,,,,,,,,,------------............//////////////00000000000000111111111111111122222222222222222333333333333333333334ËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖ××××××××ŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰÜÜÜÜÜÜÜÝÝÝÝÝÝŢŢŢŢŢŢŢßßßßßßŕŕŕŕŕŕááááááâââââăăăăăăääääääĺĺĺĺĺćććććçççççčččččéééééęęęęęëëëëěěěěěíííííîîîîďďďďđđđđđńńńńňňňňóóóóóôôôôőőőőöööö÷÷÷÷řřřřůůůůúúúúűűűűüüüüýýýýţţţţ˙˙˙˙  !!!!!!!""""""#######$$$$$$$%%%%%%%%&&&&&&&&'''''''''(((((((()))))))))*********+++++++++++,,,,,,,,,,,-----------............//////////////000000000000011111111111111111222222222222222223333333333333333334444ËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖ×××××××××ŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰÜÜÜÜÜÜÜÝÝÝÝÝÝÝŢŢŢŢŢŢßßßßßßŕŕŕŕŕŕŕáááááââââââăăăăăăäääääĺĺĺĺĺćććććçççççčččččéééééęęęęęëëëëëěěěěíííííîîîîďďďďďđđđđńńńńňňňňňóóóóôôôôőőőőöööö÷÷÷÷řřřřůůůůúúúúúűűűűüüüüýýýýţţţ˙˙˙˙  !!!!!!"""""""#######$$$$$$$%%%%%%%%&&&&&&&''''''''((((((((()))))))))**********++++++++++,,,,,,,,,,------------.............////////////00000000000000011111111111111222222222222222222223333333333333333334444ËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖ×××××××××ŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰÜÜÜÜÜÜÜÝÝÝÝÝÝÝŢŢŢŢŢŢßßßßßßŕŕŕŕŕŕááááááââââââăăăăăäääääĺĺĺĺĺĺćććććçççççčččččéééééęęęęęëëëëěěěěěííííîîîîîďďďďđđđđńńńńńňňňňóóóóôôôôőőőőöööö÷÷÷÷řřřřřůůůůúúúúűűűűüüüüýýýýţţţţ˙˙˙  !!!!!!"""""""#######$$$$$$$%%%%%%%%&&&&&&&&''''''''((((((((())))))))*********++++++++++,,,,,,,,,,,,-----------...........///////////////00000000000001111111111111111222222222222222223333333333333333333344444ËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖ×××××××××ŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚŰŰŰŰŰŰŰÜÜÜÜÜÜÜÝÝÝÝÝÝÝŢŢŢŢŢŢßßßßßßŕŕŕŕŕŕááááááââââââăăăăăăäääääĺĺĺĺĺćććććçççççčččččéééééęęęęęëëëëěěěěěíííííîîîîďďďďđđđđđńńńńňňňňóóóóôôôôőőőőööööö÷÷÷÷řřřřůůůůúúúúűűűűüüüüýýýýţţţţ˙˙˙  !!!!!!"""""""#######$$$$$$$%%%%%%%&&&&&&&&'''''''((((((((()))))))))**********++++++++++,,,,,,,,,,-----------............./////////////0000000000000011111111111111112222222222222222233333333333333333344444444ËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖ×××××××××ŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚŰŰŰŰŰŰŰÜÜÜÜÜÜÜÝÝÝÝÝÝÝŢŢŢŢŢŢßßßßßßŕŕŕŕŕŕááááááââââââăăăăăääääääĺĺĺĺĺćććććçççççččččéééééęęęęęëëëëëěěěěíííííîîîîďďďďđđđđđńńńńňňňňóóóóôôôôôőőőőöööö÷÷÷÷řřřřůůůůúúúúűűűűüüüüýýýýţţţţ˙˙˙  !!!!!!"""""""#######$$$$$$$%%%%%%%&&&&&&&&''''''''((((((((()))))))))*********+++++++++,,,,,,,,,,,------------............/////////////00000000000000011111111111111222222222222222222233333333333333333344444444ËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖ××××××××ŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰÜÜÜÜÜÜÜÝÝÝÝÝÝŢŢŢŢŢŢßßßßßßŕŕŕŕŕŕááááááââââââăăăăăääääääĺĺĺĺĺćććććçççççčččččéééééęęęęęëëëëěěěěěííííîîîîďďďďďđđđđńńńńňňňňóóóóóôôôôőőőőöööö÷÷÷÷řřřřůůůůúúúúűűűűüüüüýýýýţţţţ˙˙˙  !!!!!!""""""#######$$$$$$$%%%%%%%%&&&&&&&''''''''(((((((()))))))))**********++++++++++,,,,,,,,,,,----------.............//////////////00000000000011111111111111111222222222222222233333333333333333333444444444ËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖ××××××××ŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰÜÜÜÜÜÜÜÝÝÝÝÝÝŢŢŢŢŢŢßßßßßßŕŕŕŕŕŕááááááââââââăăăăăäääääĺĺĺĺĺĺćććććçççççčččččééééęęęęęëëëëěěěěěííííîîîîîďďďďđđđđńńńńňňňňňóóóóôôôôőőőőöööö÷÷÷÷řřřřůůůůúúúúűűűűüüüüýýýýţţţţ˙˙˙  !!!!!!""""""#######$$$$$$$%%%%%%%%&&&&&&&'''''''''(((((((()))))))))*********++++++++++,,,,,,,,,,------------.............///////////00000000000000011111111111111122222222222222222333333333333333333444444444444ËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖ×××××××××ŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚŰŰŰŰŰŰŰÜÜÜÜÜÜÜÝÝÝÝÝÝÝŢŢŢŢŢŢßßßßßßŕŕŕŕŕŕááááááâââââăăăăăăäääääĺĺĺĺĺćććććçççççčččččééééęęęęęëëëëëěěěěíííííîîîîďďďďđđđđńńńńńňňňňóóóóôôôôőőőőöööö÷÷÷÷řřřřůůůůúúúúűűűűüüüüýýýýţţţţ˙˙˙  !!!!!!"""""""#######$$$$$$$%%%%%%%&&&&&&&&'''''''((((((((())))))))**********++++++++++,,,,,,,,,,,-----------...........//////////////00000000000000111111111111112222222222222222222333333333333333334444444444444ËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖ××××××××ŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚŰŰŰŰŰŰŰÜÜÜÜÜÜÜÝÝÝÝÝÝŢŢŢŢŢŢßßßßßßŕŕŕŕŕŕááááááââââââăăăăăäääääĺĺĺĺĺćććććçççççčččččéééééęęęęęëëëëěěěěěííííîîîîďďďďđđđđđńńńńňňňňóóóóôôôôőőőőöööö÷÷÷÷řřřřůůůůúúúúűűűűüüüüýýýýţţţţ˙˙˙  !!!!!!""""""#######$$$$$$$%%%%%%%&&&&&&&&''''''''(((((((()))))))))**********+++++++++,,,,,,,,,,-----------............./////////////000000000000011111111111111112222222222222222333333333333333333334444444444444ËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖ××××××××ŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰÜÜÜÜÜÜÝÝÝÝÝÝÝŢŢŢŢŢŢßßßßßßŕŕŕŕŕŕááááááâââââăăăăăăäääääĺĺĺĺĺćććććçççççčččččééééęęęęęëëëëëěěěěííííîîîîďďďďďđđđđńńńńňňňňóóóóôôôôôőőőöööö÷÷÷÷řřřřůůůůúúúúűűűűüüüüýýýýţţţţ˙˙˙  !!!!!!"""""""######$$$$$$$%%%%%%%%&&&&&&&''''''''(((((((()))))))))*********++++++++++,,,,,,,,,,------------............////////////00000000000000011111111111111222222222222222223333333333333333334444444444444444ËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖ×××××××××ŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮÚÚÚÚÚÚÚŰŰŰŰŰŰŰÜÜÜÜÜÜÜÝÝÝÝÝÝŢŢŢŢŢŢßßßßßßŕŕŕŕŕŕááááááâââââăăăăăăäääääĺĺĺĺĺćććććçççççčččččééééęęęęęëëëëëěěěěííííîîîîîďďďďđđđđńńńńňňňňóóóóóôôôôőőőőöööö÷÷÷÷řřřůůůůúúúúűűűűüüüüýýýýţţţţ˙˙˙  !!!!!!!""""""#######$$$$$$$%%%%%%%&&&&&&&''''''''((((((((())))))))**********++++++++++,,,,,,,,,,----------............//////////////00000000000001111111111111112222222222222222223333333333333333344444444444444444ËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖ×××××××××ŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚŰŰŰŰŰŰŰÜÜÜÜÜÜÜÝÝÝÝÝÝŢŢŢŢŢŢŢßßßßßŕŕŕŕŕŕááááááâââââăăăăăääääääĺĺĺĺĺćććććççççčččččéééééęęęęëëëëëěěěěíííííîîîîďďďďđđđđńńńńńňňňňóóóóôôôôőőőőöööö÷÷÷÷řřřřůůůúúúúűűűűüüüüýýýýţţţţ˙˙˙  !!!!!!!""""""#######$$$$$$$%%%%%%%&&&&&&&&'''''''((((((((())))))))*********++++++++++,,,,,,,,,,------------............////////////0000000000000111111111111111112222222222222223333333333333333333344444444444444444ËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖ××××××××ŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚŰŰŰŰŰŰŰÜÜÜÜÜÜÝÝÝÝÝÝŢŢŢŢŢŢŢßßßßßßŕŕŕŕŕŕáááááââââââăăăăăäääääĺĺĺĺĺćććććçççççčččččééééęęęęęëëëëěěěěěííííîîîîďďďďđđđđđńńńńňňňňóóóóôôôôőőőőöööö÷÷÷÷řřřřůůůůúúúűűűűüüüüýýýýţţţţ˙˙˙  !!!!!!!""""""######$$$$$$$%%%%%%%&&&&&&&&''''''''(((((((())))))))**********++++++++++,,,,,,,,,,-----------.........../////////////00000000000000111111111111112222222222222222233333333333333333344444444444444444444ĘĘËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖ××××××××ŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰÜÜÜÜÜÜÝÝÝÝÝÝÝŢŢŢŢŢŢßßßßßŕŕŕŕŕŕááááááâââââăăăăăäääääĺĺĺĺĺćććććçççççčččččéééééęęęęëëëëëěěěěííííîîîîîďďďďđđđđńńńńňňňňóóóóôôôôőőőőöööö÷÷÷÷řřřřůůůůúúúűűűűüüüüýýýýţţţţ˙˙˙  !!!!!!"""""""######$$$$$$$%%%%%%%%&&&&&&&''''''''(((((((()))))))))*********+++++++++,,,,,,,,,,-----------............//////////////00000000000011111111111111122222222222222222233333333333333333444444444444444444445ĘĘĘĘËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖ××××××××ŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮÚÚÚÚÚÚÚŰŰŰŰŰŰŰÜÜÜÜÜÜÝÝÝÝÝÝÝŢŢŢŢŢŢßßßßßßŕŕŕŕŕŕááááááâââââăăăăăäääääĺĺĺĺĺćććććçççççččččéééééęęęęëëëëëěěěěíííííîîîîďďďďđđđđńńńńňňňňóóóóôôôôőőőőöööö÷÷÷÷řřřřůůůůúúúúűűűüüüüýýýýţţţţ˙˙˙  !!!!!!"""""""######$$$$$$$%%%%%%%&&&&&&&''''''''(((((((()))))))))*********+++++++++,,,,,,,,,,,-----------...........////////////00000000000000111111111111111122222222222222233333333333333333333444444444444444444555ĘĘĘĘËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖ×××××××××ŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚŰŰŰŰŰŰŰÜÜÜÜÜÜÜÝÝÝÝÝÝŢŢŢŢŢŢßßßßßŕŕŕŕŕŕááááááâââââăăăăăääääääĺĺĺĺĺćććććççççčččččééééęęęęęëëëëěěěěěííííîîîîďďďďđđđđđńńńňňňňňóóóóôôôôőőőőööö÷÷÷÷řřřřůůůůúúúúűűűüüüüýýýýţţţţ˙˙˙  !!!!!!""""""#######$$$$$$$%%%%%%%&&&&&&&&'''''''((((((((())))))))*********++++++++++,,,,,,,,,,----------...........//////////////00000000000000111111111111122222222222222222333333333333333333444444444444444444444555ĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖ××××××××ŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚŰŰŰŰŰŰÜÜÜÜÜÜÜÝÝÝÝÝÝŢŢŢŢŢŢßßßßßßŕŕŕŕŕŕáááááââââââăăăăăäääääĺĺĺĺĺćććććçççççččččéééééęęęęëëëëëěěěěííííîîîîîďďďďđđđđńńńńňňňňóóóóôôôôőőőőööö÷÷÷÷řřřřůůůůúúúúűűűüüüüýýýýţţţţ˙˙˙  !!!!!!""""""#######$$$$$$%%%%%%%&&&&&&&&'''''''(((((((()))))))))*********+++++++++,,,,,,,,,,-----------............/////////////000000000000111111111111111122222222222222222333333333333333344444444444444444444455555ĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖ××××××××ŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚŰŰŰŰŰŰÜÜÜÜÜÜÜÝÝÝÝÝÝŢŢŢŢŢŢßßßßßŕŕŕŕŕŕááááááâââââăăăăăăäääääĺĺĺĺćććććçççççččččéééééęęęęëëëëëěěěěííííîîîîîďďďďđđđđńńńńňňňňóóóóôôôôőőőőöööö÷÷÷řřřřůůůůúúúúűűűűüüüýýýýţţţţ˙˙˙  !!!!!!""""""#######$$$$$$%%%%%%%%&&&&&&&''''''''(((((((()))))))))*********+++++++++,,,,,,,,,,-----------...........////////////00000000000000111111111111111222222222222222333333333333333333344444444444444444455555555ĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖ×××××××ŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮÚÚÚÚÚÚÚŰŰŰŰŰŰÜÜÜÜÜÜÜÝÝÝÝÝÝÝŢŢŢŢŢŢßßßßßŕŕŕŕŕŕáááááâââââăăăăăăäääääĺĺĺĺĺćććććççççčččččéééééęęęęëëëëěěěěěííííîîîîďďďďđđđđńńńńňňňňóóóóôôôôőőőőöööö÷÷÷÷řřřůůůůúúúúűűűűüüüýýýýţţţţ˙˙˙  !!!!!!"""""""#######$$$$$$%%%%%%%&&&&&&&''''''''((((((()))))))))*********+++++++++,,,,,,,,,,-----------............/////////////00000000000001111111111111222222222222222223333333333333333334444444444444444444455555555ĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖ××××××××ŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚŰŰŰŰŰŰŰÜÜÜÜÜÜÝÝÝÝÝÝŢŢŢŢŢŢßßßßßßŕŕŕŕŕŕáááááâââââăăăăăäääääĺĺĺĺĺćććććçççççčččččééééęęęęëëëëëěěěěííííîîîîďďďďđđđđđńńńńňňňóóóóôôôôőőőőöööö÷÷÷÷řřřůůůůúúúúűűűűüüüýýýýţţţţ˙˙˙  !!!!!!""""""######$$$$$$$%%%%%%%&&&&&&&&'''''''(((((((()))))))))*********+++++++++,,,,,,,,,,-----------...........////////////0000000000000111111111111111222222222222222223333333333333333444444444444444444444555555555ĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖ××××××××ŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮÚÚÚÚÚÚŰŰŰŰŰŰŰÜÜÜÜÜÜÜÝÝÝÝÝÝŢŢŢŢŢŢßßßßßŕŕŕŕŕŕáááááââââââăăăăăäääääĺĺĺĺĺććććçççççčččččééééęęęęęëëëëěěěěíííííîîîîďďďďđđđđńńńńňňňňóóóôôôôőőőőöööö÷÷÷÷řřřřůůůúúúúűűűűüüüýýýýţţţţ˙˙˙  !!!!!!""""""#######$$$$$$$%%%%%%&&&&&&&''''''''(((((((())))))))*********+++++++++,,,,,,,,,,-----------.........../////////////00000000000000111111111111112222222222222223333333333333333333444444444444444444555555555555ĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖ××××××××ŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚŰŰŰŰŰŰÜÜÜÜÜÜÝÝÝÝÝÝŢŢŢŢŢŢßßßßßßŕŕŕŕŕŕáááááâââââăăăăăäääääĺĺĺĺĺćććććçççççččččéééééęęęęëëëëěěěěíííííîîîîďďďďđđđđńńńńňňňňóóóóôôôôőőőöööö÷÷÷÷řřřřůůůúúúúűűűűüüüýýýýţţţţ˙˙˙  !!!!!!""""""######$$$$$$%%%%%%%%&&&&&&&''''''''(((((((())))))))********++++++++++,,,,,,,,,,----------............/////////////00000000000011111111111112222222222222222223333333333333333344444444444444444444555555555555ĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖ××××××××ŘŘŘŘŘŘŘŮŮŮŮŮŮŮÚÚÚÚÚÚÚŰŰŰŰŰŰÜÜÜÜÜÜÜÝÝÝÝÝÝŢŢŢŢŢŢßßßßßŕŕŕŕŕŕááááááâââââăăăăăäääääĺĺĺĺćććććçççççčččččééééęęęęëëëëëěěěěííííîîîîďďďďđđđđńńńńňňňňóóóóôôôôőőőőööö÷÷÷÷řřřřůůůúúúúűűűűüüüüýýýţţţţ˙˙˙  !!!!!!""""""#######$$$$$$%%%%%%%&&&&&&&'''''''(((((((()))))))))*********+++++++++,,,,,,,,,,----------...........////////////00000000000001111111111111112222222222222222233333333333333334444444444444444444445555555555555ĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖ××××××××ŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚŰŰŰŰŰŰÜÜÜÜÜÜÝÝÝÝÝÝŢŢŢŢŢŢŢßßßßßŕŕŕŕŕŕáááááâââââăăăăăäääääĺĺĺĺĺćććććççççčččččééééęęęęęëëëëěěěěííííîîîîďďďďđđđđńńńńňňňňóóóóôôôôőőőőööö÷÷÷÷řřřřůůůůúúúűűűűüüüüýýýţţţţ˙˙˙  !!!!!!!""""""######$$$$$$%%%%%%%&&&&&&&&'''''''(((((((())))))))********++++++++++,,,,,,,,,-----------.........../////////////00000000000001111111111111122222222222222233333333333333333334444444444444444445555555555555555ĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖ××××××××ŘŘŘŘŘŘŘŘŮŮŮŮŮŮÚÚÚÚÚÚÚŰŰŰŰŰŰŰÜÜÜÜÜÜÜÝÝÝÝÝŢŢŢŢŢŢßßßßßŕŕŕŕŕŕááááááâââââăăăăăääääĺĺĺĺĺĺććććçççççččččéééééęęęęëëëëěěěěěííííîîîîďďďďđđđđńńńńňňňňóóóôôôôőőőőöööö÷÷÷řřřřůůůůúúúűűűűüüüüýýýţţţţ˙˙˙  !!!!!!"""""#######$$$$$$$%%%%%%%&&&&&&''''''''(((((((())))))))*********++++++++++,,,,,,,,,-----------...........////////////000000000000111111111111112222222222222222233333333333333333444444444444444444455555555555555555ĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖ×××××××ŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮÚÚÚÚÚÚÚŰŰŰŰŰŰŰÜÜÜÜÜÜÝÝÝÝÝÝŢŢŢŢŢŢßßßßßŕŕŕŕŕŕáááááâââââăăăăăäääääĺĺĺĺĺććććçççççčččččééééęęęęëëëëëěěěěííííîîîîďďďďđđđđńńńńňňňňóóóôôôôőőőőöööö÷÷÷řřřřůůůůúúúűűűűüüüüýýýţţţţ˙˙˙  !!!!!!""""""######$$$$$$$%%%%%%%&&&&&&&''''''''((((((())))))))********+++++++++,,,,,,,,,,----------.........../////////////00000000000001111111111111112222222222222222333333333333333444444444444444444444455555555555555555ĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖ××××××××ŘŘŘŘŘŘŘŮŮŮŮŮŮŮÚÚÚÚÚÚŰŰŰŰŰŰŰÜÜÜÜÜÜÜÝÝÝÝÝÝŢŢŢŢŢßßßßßßŕŕŕŕŕŕáááááâââââăăăăăäääääĺĺĺĺĺććććççççčččččéééééęęęęëëëëěěěěííííîîîîďďďďđđđđńńńńňňňňóóóóôôôőőőőöööö÷÷÷÷řřřůůůůúúúúűűűüüüüýýýţţţţ˙˙˙  !!!!!""""""#######$$$$$$$%%%%%%&&&&&&&'''''''(((((((()))))))))********+++++++++,,,,,,,,,,----------.........../////////////00000000000001111111111111222222222222222333333333333333333444444444444444444455555555555555555555ĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖ×××××××ŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚŰŰŰŰŰŰÜÜÜÜÜÜÝÝÝÝÝÝŢŢŢŢŢŢßßßßßŕŕŕŕŕŕáááááâââââăăăăăäääääĺĺĺĺĺćććććççççččččéééééęęęęëëëëěěěěěííííîîîîďďďđđđđńńńńňňňňóóóóôôôôőőőöööö÷÷÷÷řřřůůůůúúúúűűűüüüüýýýţţţţ˙˙˙  !!!!!!""""""######$$$$$$%%%%%%%&&&&&&&&'''''''((((((())))))))********++++++++++,,,,,,,,,,----------...........////////////0000000000001111111111111122222222222222222333333333333333334444444444444444445555555555555555555555ÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖ×××××××××ŘŘŘŘŘŘŘŮŮŮŮŮŮÚÚÚÚÚÚÚŰŰŰŰŰŰŰÜÜÜÜÜÜÝÝÝÝÝÝŢŢŢŢŢßßßßßßŕŕŕŕŕŕáááááâââââăăăăăäääääĺĺĺĺćććććçççççččččééééęęęęëëëëěěěěěííííîîîîďďďďđđđđńńńńňňňóóóóôôôôőőőőööö÷÷÷÷řřřůůůůúúúúűűűüüüüýýýţţţţ˙˙˙  !!!!!""""""######$$$$$$$%%%%%%%&&&&&&'''''''((((((((())))))))********+++++++++,,,,,,,,,,----------...........////////////00000000000001111111111111122222222222222223333333333333334444444444444444444445555555555555555555566ÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖ××××××××ŘŘŘŘŘŘŘŮŮŮŮŮŮŮÚÚÚÚÚÚÚŰŰŰŰŰŰÜÜÜÜÜÜÝÝÝÝÝÝÝŢŢŢŢŢßßßßßŕŕŕŕŕŕáááááâââââăăăăăäääääĺĺĺĺĺććććçççççččččéééééęęęęëëëëěěěěííííîîîîďďďďđđđđńńńńňňňňóóóôôôôőőőőööö÷÷÷÷řřřřůůůúúúúűűűüüüüýýýţţţţ˙˙˙  !!!!!"""""""######$$$$$$%%%%%%%&&&&&&&'''''''((((((())))))))*********+++++++++,,,,,,,,,,-----------...........///////////000000000000011111111111112222222222222223333333333333333334444444444444444444555555555555555555555666ÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖ××××××××ŘŘŘŘŘŘŘŮŮŮŮŮŮŮÚÚÚÚÚÚŰŰŰŰŰŰŰÜÜÜÜÜÜÝÝÝÝÝÝŢŢŢŢŢŢßßßßßŕŕŕŕŕŕááááââââââăăăăăääääĺĺĺĺĺćććććççççčččččééééęęęęëëëëěěěěííííîîîîďďďďđđđđńńńńňňňňóóóôôôôőőőőööö÷÷÷÷řřřřůůůúúúúűűűüüüüýýýţţţţ˙˙˙  !!!!!!""""""######$$$$$$$%%%%%%&&&&&&&'''''''(((((((())))))))********+++++++++,,,,,,,,,----------...........////////////00000000000011111111111111122222222222222223333333333333333344444444444444444555555555555555555555555666ÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖ×××××××ŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮÚÚÚÚÚÚŰŰŰŰŰŰÜÜÜÜÜÜÜÝÝÝÝÝÝŢŢŢŢŢßßßßßßŕŕŕŕŕáááááâââââăăăăăäääääĺĺĺĺĺććććçççççččččééééęęęęëëëëëěěěěííííîîîîďďďđđđđńńńńňňňňóóóóôôôőőőőöööö÷÷÷řřřřůůůúúúúűűűüüüüýýýţţţţ˙˙˙  !!!!!""""""#######$$$$$%%%%%%%&&&&&&&&'''''''((((((()))))))*********++++++++++,,,,,,,,,----------...........////////////00000000000011111111111111122222222222222233333333333333344444444444444444444555555555555555555555666666ÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖ×××××××××ŘŘŘŘŘŘŘŮŮŮŮŮŮÚÚÚÚÚÚÚŰŰŰŰŰŰŰÜÜÜÜÜÝÝÝÝÝÝŢŢŢŢŢŢßßßßßŕŕŕŕŕááááááâââââăăăăăääääĺĺĺĺĺćććććççççččččéééééęęęęëëëëěěěěííííîîîîďďďďđđđđńńńňňňňóóóóôôôőőőőöööö÷÷÷řřřřůůůúúúúűűűüüüüýýýţţţţ˙˙˙  !!!!!!""""""#####$$$$$$$%%%%%%%&&&&&&'''''''((((((((()))))))********+++++++++,,,,,,,,,-----------...........////////////000000000000111111111111122222222222222233333333333333333344444444444444444445555555555555555555566666666ÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖ×××××××ŘŘŘŘŘŘŘŮŮŮŮŮŮŮÚÚÚÚÚÚÚŰŰŰŰŰŰÜÜÜÜÜÜÝÝÝÝÝÝŢŢŢŢŢßßßßßßŕŕŕŕŕáááááâââââăăăăăäääääĺĺĺĺćććććççççčččččééééęęęęëëëëěěěěííííîîîîďďďďđđđđńńńńňňňóóóóôôôôőőőöööö÷÷÷řřřřůůůůúúúűűűűüüüýýýýţţţ˙˙˙  !!!!!""""""######$$$$$$%%%%%%%&&&&&&&'''''''((((((())))))))*********+++++++++,,,,,,,,,----------..........////////////00000000000011111111111111222222222222222233333333333333333444444444444444445555555555555555555555566666666ÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖ××××××××ŘŘŘŘŘŘŘŮŮŮŮŮŮŮÚÚÚÚÚÚŰŰŰŰŰŰŰÜÜÜÜÜÜÝÝÝÝÝŢŢŢŢŢŢßßßßßŕŕŕŕŕŕáááááâââââăăăăăäääääĺĺĺĺććććçççççččččééééęęęęëëëëëěěěěííííîîîďďďďđđđđńńńńňňňňóóóôôôôőőőöööö÷÷÷÷řřřůůůůúúúűűűűüüüýýýýţţţ˙˙˙  !!!!!!"""""######$$$$$$$%%%%%%&&&&&&&'''''''(((((((()))))))********+++++++++,,,,,,,,,,----------...........////////////00000000000011111111111111222222222222222333333333333333444444444444444444455555555555555555555556666666666ÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖ×××××××ŘŘŘŘŘŘŮŮŮŮŮŮŮŮÚÚÚÚÚÚŰŰŰŰŰŰÜÜÜÜÜÜÜÝÝÝÝÝŢŢŢŢŢßßßßßßŕŕŕŕŕáááááââââââăăăăäääääĺĺĺĺĺććććççççčččččééééęęęęëëëëěěěěííííîîîîďďďđđđđńńńńňňňňóóóôôôôőőőőööö÷÷÷÷řřřůůůůúúúűűűűüüüýýýýţţţ˙˙˙  !!!!!"""""#######$$$$$$%%%%%%&&&&&&&&''''''((((((()))))))))********+++++++++,,,,,,,,----------...........////////////0000000000001111111111111222222222222222333333333333333334444444444444444444455555555555555555556666666666666ÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖ××××××××ŘŘŘŘŘŘŘŮŮŮŮŮŮÚÚÚÚÚÚÚŰŰŰŰŰŰÜÜÜÜÜÜÝÝÝÝÝÝŢŢŢŢŢßßßßßŕŕŕŕŕŕáááááâââââăăăăăääääĺĺĺĺĺćććććççççččččééééęęęęëëëëěěěěííííîîîîďďďďđđđđńńńňňňňóóóóôôôőőőőööö÷÷÷÷řřřůůůůúúúűűűűüüüýýýýţţţ˙˙˙  !!!!!""""""######$$$$$$%%%%%%%&&&&&&'''''''(((((((()))))))********++++++++++,,,,,,,,,----------..........////////////00000000000011111111111111222222222222222333333333333333334444444444444444455555555555555555555566666666666666ÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖ××××××ŘŘŘŘŘŘŘŮŮŮŮŮŮŮÚÚÚÚÚÚÚŰŰŰŰŰÜÜÜÜÜÜÜÝÝÝÝÝŢŢŢŢŢŢßßßßßŕŕŕŕŕááááááââââăăăăăäääääĺĺĺĺćććććççççččččééééęęęęęëëëëěěěííííîîîîďďďďđđđđńńńňňňňóóóóôôôőőőőööö÷÷÷÷řřřůůůůúúúűűűűüüüýýýýţţţ˙˙˙  !!!!!!"""""#######$$$$$%%%%%%%&&&&&&&'''''''(((((()))))))))********++++++++,,,,,,,,,-----------...........///////////000000000000011111111111112222222222222223333333333333334444444444444444445555555555555555555555566666666666666ÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖ×××××××ŘŘŘŘŘŘŘŘŮŮŮŮŮŮÚÚÚÚÚÚŰŰŰŰŰŰŰÜÜÜÜÜÝÝÝÝÝÝŢŢŢŢŢŢßßßßßŕŕŕŕŕáááááâââââăăăăăääääĺĺĺĺĺććććççççčččččééééęęęęëëëëěěěěííííîîîďďďďđđđđńńńńňňňóóóóôôôőőőőöööö÷÷÷řřřřůůůúúúűűűűüüüýýýýţţţ˙˙˙  !!!!!!""""""#####$$$$$$$%%%%%%&&&&&&''''''''((((((())))))))********+++++++++,,,,,,,,,---------...........///////////00000000000011111111111112222222222222223333333333333333344444444444444444445555555555555555555566666666666666666ÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖ×××××××ŘŘŘŘŘŘŘŮŮŮŮŮŮŮÚÚÚÚÚÚŰŰŰŰŰŰÜÜÜÜÜÜÝÝÝÝÝŢŢŢŢŢŢßßßßßŕŕŕŕŕááááááââââăăăăăäääääĺĺĺĺćććććççççččččééééęęęęëëëëěěěěííííîîîîďďďđđđđńńńńňňňňóóóôôôôőőőöööö÷÷÷řřřřůůůúúúúűűűüüüýýýýţţţ˙˙˙  !!!!!!"""""######$$$$$$%%%%%%&&&&&&&'''''''((((((()))))))))*******++++++++,,,,,,,,,,----------...........///////////00000000000011111111111112222222222222223333333333333333344444444444444444555555555555555555556666666666666666666ÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖ××××××××ŘŘŘŘŘŘŘŮŮŮŮŮŮÚÚÚÚÚÚÚŰŰŰŰŰŰÜÜÜÜÜÝÝÝÝÝÝÝŢŢŢŢŢßßßßßŕŕŕŕŕáááááâââââăăăăäääääĺĺĺĺĺććććççççččččééééęęęęęëëëëěěěííííîîîîďďďďđđđńńńńňňňňóóóôôôôőőőöööö÷÷÷řřřřůůůúúúúűűűüüüýýýýţţţ˙˙˙  !!!!!"""""""#####$$$$$$%%%%%%%&&&&&&'''''''(((((((()))))))********+++++++++,,,,,,,,,---------...........///////////0000000000000111111111111122222222222222233333333333333344444444444444444455555555555555555555556666666666666666666ÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖ×××××××ŘŘŘŘŘŘŮŮŮŮŮŮŮÚÚÚÚÚÚŰŰŰŰŰŰÜÜÜÜÜÜÝÝÝÝÝÝŢŢŢŢŢŢßßßßŕŕŕŕŕŕáááááââââăăăăăääääĺĺĺĺĺććććççççčččččééééęęęęëëëëěěěěíííîîîîîďďďđđđđńńńňňňňóóóóôôôőőőőööö÷÷÷řřřřůůůúúúúűűűüüüýýýýţţţ˙˙˙  !!!!!!""""""######$$$$$$%%%%%%&&&&&&&''''''((((((()))))))))*******++++++++,,,,,,,,,,----------..........///////////00000000000111111111111122222222222222233333333333333333444444444444444444455555555555555555555666666666666666666666ÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖ×××××××ŘŘŘŘŘŘŘŘŮŮŮŮŮŮÚÚÚÚÚÚŰŰŰŰŰŰÜÜÜÜÜÜÝÝÝÝÝÝŢŢŢŢŢßßßßßŕŕŕŕŕáááááâââââăăăăäääääĺĺĺĺćććććççççččččééééęęęęëëëëěěěěííííîîîîďďďđđđđńńńńňňňóóóóôôôőőőőööö÷÷÷÷řřřůůůúúúúűűűüüüýýýýţţţ˙˙˙  !!!!!""""""######$$$$$$%%%%%%&&&&&&''''''''((((((()))))))*********++++++++,,,,,,,,----------...........////////////00000000000111111111111122222222222222233333333333333333444444444444444445555555555555555556666666666666666666666666ČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖ×××××××ŘŘŘŘŘŘŘŮŮŮŮŮŮŮÚÚÚÚÚÚŰŰŰŰŰŰÜÜÜÜÜÜÝÝÝÝÝŢŢŢŢŢŢßßßßŕŕŕŕŕŕáááááâââââăăăăäääääĺĺĺĺććććçççççččččééééęęęęëëëěěěěííííîîîîďďďđđđđńńńńňňňóóóóôôôőőőőööö÷÷÷÷řřřůůůůúúúűűűüüüüýýýţţţ˙˙˙  !!!!!!"""""######$$$$$$%%%%%%&&&&&&&'''''''((((((())))))))*******+++++++++,,,,,,,,,---------..........///////////00000000000001111111111111222222222222222333333333333333444444444444444445555555555555555555556666666666666666666666667ČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖ×××××××ŘŘŘŘŘŘŘŮŮŮŮŮŮÚÚÚÚÚÚÚŰŰŰŰŰÜÜÜÜÜÜÝÝÝÝÝÝŢŢŢŢŢßßßßßŕŕŕŕŕáááááâââââăăăăăääääĺĺĺĺĺććććççççččččééééęęęęëëëëěěěěííííîîîďďďďđđđńńńńňňňňóóóôôôôőőőööö÷÷÷÷řřřůůůůúúúűűűüüüüýýýţţţ˙˙˙  !!!!!""""""######$$$$$%%%%%%%&&&&&&'''''''((((((()))))))*********++++++++,,,,,,,,-----------..........///////////00000000000111111111111122222222222222333333333333333344444444444444444445555555555555555555556666666666666666666677777ČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐÖÖÖÖÖÖÖ××××××××ŘŘŘŘŘŘŮŮŮŮŮŮŮÚÚÚÚÚÚŰŰŰŰŰŰÜÜÜÜÜÜÝÝÝÝÝŢŢŢŢŢŢßßßßßŕŕŕŕŕááááââââââăăăăäääääĺĺĺĺććććççççččččééééęęęęëëëëěěěěííííîîîîďďďđđđđńńńňňňňóóóôôôôőőőöööö÷÷÷řřřůůůůúúúűűűüüüüýýýţţţ˙˙˙  !!!!!!"""""######$$$$$$%%%%%%&&&&&&&''''''(((((((()))))))*******++++++++++,,,,,,,,---------..........////////////000000000000111111111111122222222222222333333333333333344444444444444444455555555555555555566666666666666666666666677777ČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖ××××××ŘŘŘŘŘŘŘŘŮŮŮŮŮŮÚÚÚÚÚÚŰŰŰŰŰŰÜÜÜÜÜÜÝÝÝÝÝŢŢŢŢŢßßßßßŕŕŕŕŕáááááâââââăăăăăääääĺĺĺĺććććçççççččččééééęęęęëëëěěěěííííîîîîďďďđđđđńńńňňňňóóóóôôôőőőöööö÷÷÷řřřůůůůúúúűűűüüüüýýýţţţ˙˙˙  !!!!!"""""######$$$$$$%%%%%%&&&&&&''''''''(((((())))))))********++++++++,,,,,,,,,----------.........///////////00000000000011111111111112222222222222223333333333333334444444444444444455555555555555555555666666666666666666666666677777ČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖ××××××××ŘŘŘŘŘŘŮŮŮŮŮŮŮÚÚÚÚÚŰŰŰŰŰŰŰÜÜÜÜÜÝÝÝÝÝÝŢŢŢŢŢßßßßßŕŕŕŕŕáááááââââăăăăăääääĺĺĺĺĺććććççççččččééééęęęęëëëëěěěěíííîîîîďďďďđđđńńńńňňňóóóóôôôőőőőööö÷÷÷řřřřůůůúúúűűűüüüüýýýţţţ˙˙˙  !!!!!""""""#####$$$$$$$%%%%%&&&&&&&''''''(((((((()))))))********+++++++++,,,,,,,,---------...........///////////00000000000111111111111222222222222223333333333333333444444444444444444555555555555555555555666666666666666666666677777777ČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖ××××××ŘŘŘŘŘŘŘŮŮŮŮŮŮÚÚÚÚÚÚÚŰŰŰŰŰÜÜÜÜÜÜÝÝÝÝÝŢŢŢŢŢŢßßßßŕŕŕŕŕŕááááâââââăăăăäääääĺĺĺĺćććććçççčččččééééęęęëëëëěěěěííííîîîďďďďđđđđńńńňňňóóóóôôôőőőőööö÷÷÷řřřřůůůúúúűűűüüüüýýýţţţ˙˙˙  !!!!!!"""""######$$$$$%%%%%%%&&&&&&'''''''(((((())))))))********++++++++,,,,,,,,,,--------..........///////////0000000000000111111111111222222222222223333333333333333444444444444444444555555555555555555566666666666666666666677777777777ČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖ×××××××ŘŘŘŘŘŘŮŮŮŮŮŮŮÚÚÚÚÚÚŰŰŰŰŰŰÜÜÜÜÜÝÝÝÝÝÝŢŢŢŢŢßßßßßŕŕŕŕŕáááááââââăăăăăääääĺĺĺĺĺććććççççččččééééęęęęëëëëěěěííííîîîîďďďđđđđńńńňňňňóóóôôôőőőőööö÷÷÷řřřřůůůúúúűűűűüüüýýýţţţ˙˙˙  !!!!!""""""#####$$$$$$%%%%%%&&&&&&&''''''((((((()))))))********++++++++,,,,,,,,-----------.........///////////00000000000111111111111122222222222222233333333333333344444444444444444555555555555555555566666666666666666666666677777777777ČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖ××××××ŘŘŘŘŘŘŘŘŮŮŮŮŮÚÚÚÚÚÚÚŰŰŰŰŰÜÜÜÜÜÜÝÝÝÝÝŢŢŢŢŢŢßßßßŕŕŕŕŕŕááááâââââăăăăäääääĺĺĺĺććććççççččččééééęęęęëëëëěěěěíííîîîîďďďđđđđńńńňňňňóóóôôôôőőőööö÷÷÷÷řřřůůůúúúűűűűüüüýýýţţţ˙˙˙  !!!!!!""""#######$$$$$%%%%%%%&&&&&''''''''(((((())))))))*******++++++++,,,,,,,,,---------..........////////////00000000000111111111111222222222222233333333333333334444444444444444455555555555555555555566666666666666666666666777777777777ČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖ××××××××ŘŘŘŘŘŘŮŮŮŮŮŮŮÚÚÚÚÚŰŰŰŰŰŰŰÜÜÜÜÝÝÝÝÝÝŢŢŢŢŢßßßßßŕŕŕŕŕáááááâââââăăăăääääĺĺĺĺćććććççççččččééééęęęëëëëěěěěííííîîîďďďďđđđńńńńňňňóóóôôôôőőőööö÷÷÷÷řřřůůůúúúűűűűüüüýýýţţţ˙˙˙  !!!!!"""""#####$$$$$$$%%%%%&&&&&&&''''''(((((((())))))*********+++++++,,,,,,,,,----------.........//////////00000000000001111111111111222222222222233333333333333334444444444444444455555555555555555555666666666666666666667777777777777777ČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖ××××××ŘŘŘŘŘŘŘŮŮŮŮŮŮÚÚÚÚÚÚÚŰŰŰŰŰÜÜÜÜÜÜÝÝÝÝÝŢŢŢŢŢßßßßßŕŕŕŕŕáááááââââăăăăăääääĺĺĺĺććććççççččččééééęęęęëëëëěěěííííîîîďďďďđđđńńńńňňňóóóóôôôőőőöööö÷÷÷řřřůůůúúúúűűűüüüýýýţţţ˙˙˙  !!!!!"""""######$$$$$%%%%%%%&&&&&&'''''''(((((())))))))*******+++++++++,,,,,,,,---------...........//////////00000000001111111111111222222222222222333333333333333444444444444444445555555555555555556666666666666666666666677777777777777777ČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖ××××××××ŘŘŘŘŘŮŮŮŮŮŮŮÚÚÚÚÚÚŰŰŰŰŰŰÜÜÜÜÜÝÝÝÝÝÝŢŢŢŢŢßßßßŕŕŕŕŕŕááááâââââăăăăääääĺĺĺĺćććććçççččččééééęęęęëëëëěěěěíííîîîîďďďđđđđńńńňňňóóóóôôôőőőöööö÷÷÷řřřůůůúúúúűűűüüüýýýţţţ˙˙˙  !!!!!""""""#####$$$$$$%%%%%%&&&&&&&'''''(((((((())))))*********+++++++,,,,,,,,,---------.........////////////000000000001111111111112222222222222333333333333333444444444444444445555555555555555555566666666666666666666666677777777777777777ČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖ××××××ŘŘŘŘŘŘŘŮŮŮŮŮÚÚÚÚÚÚÚŰŰŰŰŰÜÜÜÜÜÜÝÝÝÝÝŢŢŢŢŢßßßßßŕŕŕŕŕáááááââââăăăăäääääĺĺĺĺććććççççččččééééęęęęëëëěěěěíííîîîîďďďđđđđńńńňňňňóóóôôôőőőőööö÷÷÷řřřůůůůúúúűűűüüüýýýţţţ˙˙˙  !!!!!"""""######$$$$$%%%%%%%&&&&&'''''''(((((())))))))*******+++++++++,,,,,,,----------..........//////////00000000000011111111111112222222222222333333333333333444444444444444445555555555555555555566666666666666666666677777777777777777777ČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖ×××××××ŘŘŘŘŘŘŮŮŮŮŮŮŮÚÚÚÚÚŰŰŰŰŰŰÜÜÜÜÜÝÝÝÝÝÝŢŢŢŢŢßßßßŕŕŕŕŕŕááááâââââăăăăääääĺĺĺĺĺćććççççččččééééęęęęëëëëěěěííííîîîďďďďđđđńńńňňňňóóóôôôőőőőööö÷÷÷řřřůůůůúúúűűűüüüýýýţţţ˙˙˙  !!!!!""""""#####$$$$$$%%%%%&&&&&&&''''''((((((()))))))********+++++++,,,,,,,,,---------..........///////////00000000001111111111112222222222222223333333333333334444444444444444455555555555555555566666666666666666666677777777777777777777777ČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖ××××××ŘŘŘŘŘŘŘŮŮŮŮŮŮÚÚÚÚÚÚŰŰŰŰŰÜÜÜÜÜÜÝÝÝÝÝŢŢŢŢŢßßßßßŕŕŕŕŕáááááââââăăăăäääääĺĺĺĺććććççççččččééééęęęëëëëěěěííííîîîďďďďđđđńńńńňňňóóóôôôôőőőööö÷÷÷řřřřůůůúúúűűűüüüýýýţţţ˙˙˙  !!!!!"""""######$$$$$%%%%%%&&&&&&'''''''(((((()))))))********++++++++,,,,,,,,----------........///////////0000000000001111111111112222222222223333333333333334444444444444444455555555555555555556666666666666666666666677777777777777777777777ÇÇČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖ×××××××ŘŘŘŘŘŘŮŮŮŮŮŮÚÚÚÚÚŰŰŰŰŰŰÜÜÜÜÜÜÝÝÝÝÝŢŢŢŢŢßßßßßŕŕŕŕŕááááââââăăăăăääääĺĺĺĺććććççççččččééééęęęęëëëěěěěíííîîîîďďďđđđđńńńňňňóóóóôôôőőőööö÷÷÷řřřřůůůúúúűűűüüüýýýţţţ˙˙˙  !!!!!"""""######$$$$$$%%%%%&&&&&&''''''((((((()))))))********++++++++,,,,,,,,---------..........//////////00000000000111111111111122222222222223333333333333334444444444444444455555555555555555556666666666666666666666677777777777777777777778ÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖ××××××ŘŘŘŘŘŘŘŮŮŮŮŮÚÚÚÚÚÚÚŰŰŰŰŰÜÜÜÜÜÝÝÝÝÝÝŢŢŢŢßßßßßŕŕŕŕŕááááâââââăăăăäääääĺĺĺĺćććçççççčččééééęęęęëëëěěěěííííîîîďďďđđđđńńńňňňóóóóôôôőőőöööö÷÷÷řřřůůůúúúűűűüüüýýýţţţ˙˙˙  !!!!""""""#####$$$$$%%%%%%%&&&&&'''''''(((((()))))))********++++++++,,,,,,,,---------.........////////////00000000001111111111122222222222222233333333333333344444444444444444555555555555555555666666666666666666666777777777777777777777777788ÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐÖÖÖÖÖÖÖ×××××××ŘŘŘŘŘŘŮŮŮŮŮŮŮÚÚÚÚÚŰŰŰŰŰŰÜÜÜÜÜÝÝÝÝÝŢŢŢŢŢßßßßßŕŕŕŕáááááââââăăăăăääääĺĺĺĺććććççççččččééééęęęëëëëěěěííííîîîďďďďđđđńńńńňňňóóóôôôőőőöööö÷÷÷řřřůůůúúúűűűüüüýýýţţţ˙˙˙  !!!!!"""""#####$$$$$$%%%%%&&&&&&&''''''((((((()))))))*******++++++++,,,,,,,,---------.........//////////00000000000001111111111122222222222223333333333333344444444444444444555555555555555555666666666666666666666677777777777777777777777777788ÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖ×××××××ŘŘŘŘŘŘŮŮŮŮŮŮÚÚÚÚÚÚŰŰŰŰŰÜÜÜÜÜÜÝÝÝÝÝŢŢŢŢŢßßßßŕŕŕŕŕááááâââââăăăăăäääĺĺĺĺĺććććçççččččééééęęęęëëëěěěěíííîîîîďďďđđđńńńńňňňóóóôôôőőőőööö÷÷÷řřřůůůúúúűűűüüüýýýţţţ˙˙˙  !!!!!"""""######$$$$$%%%%%%&&&&&&''''''((((((())))))*********++++++,,,,,,,,,,-------...........//////////00000000001111111111111222222222222223333333333333344444444444444445555555555555555556666666666666666666666677777777777777777777777778888ÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐÖÖÖÖÖÖÖ×××××××ŘŘŘŘŘŘŮŮŮŮŮŮÚÚÚÚÚŰŰŰŰŰŰÜÜÜÜÜÜÝÝÝÝŢŢŢŢŢßßßßßŕŕŕŕáááááâââââăăăăääääĺĺĺĺććććççççččččéééęęęęëëëěěěěíííîîîîďďďđđđđńńńňňňóóóôôôôőőőööö÷÷÷řřřůůůúúúűűűüüüýýýţţţ˙˙˙  !!!!!""""######$$$$$$%%%%%&&&&&&''''''((((((()))))))*******++++++++,,,,,,,----------.........///////////000000000011111111111222222222222222333333333333333444444444444444445555555555555555556666666666666666666667777777777777777777777788888888ÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖ××××××ŘŘŘŘŘŘŘŮŮŮŮŮÚÚÚÚÚÚŰŰŰŰŰŰÜÜÜÜÜÝÝÝÝÝŢŢŢŢŢßßßßŕŕŕŕŕáááááââââăăăăäääääĺĺĺććććççççččččééééęęęëëëëěěěííííîîîďďďđđđđńńńňňňóóóóôôôőőőööö÷÷÷řřřůůůúúúűűűüüüýýýţţţ˙˙˙  !!!!!"""""#####$$$$$$%%%%%%&&&&&'''''''(((((()))))))********++++++,,,,,,,,,--------.........../////////00000000000011111111111222222222222233333333333333444444444444444445555555555555555556666666666666666666677777777777777777777777777888888888ÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔŐŐŐŐŐŐÖÖÖÖÖÖÖÖ××××××ŘŘŘŘŘŘŮŮŮŮŮŮŮÚÚÚÚÚŰŰŰŰŰÜÜÜÜÜÜÝÝÝÝÝŢŢŢŢßßßßßŕŕŕŕŕááááâââââăăăăääääĺĺĺĺććććçççččččééééęęęęëëëěěěěíííîîîďďďďđđđńńńňňňóóóóôôôőőőööö÷÷÷řřřůůůúúúűűűüüüýýýţţţ˙˙˙  !!!!"""""######$$$$$%%%%%&&&&&&&''''''(((((())))))))******++++++++,,,,,,,,---------.........///////////00000000011111111111112222222222222233333333333334444444444444444555555555555555555666666666666666666666677777777777777777777777777888888888ÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐÖÖÖÖÖÖ×××××××ŘŘŘŘŘŘŮŮŮŮŮŮÚÚÚÚÚÚŰŰŰŰŰÜÜÜÜÜÝÝÝÝÝŢŢŢŢŢßßßßßŕŕŕŕáááááââââăăăăääääĺĺĺĺććććççççččččéééęęęęëëëěěěěíííîîîîďďďđđđńńńńňňňóóóôôôőőőööö÷÷÷řřřůůůúúúűűűüüüýýýţţţ˙˙˙  !!!!!!"""""#####$$$$$%%%%%%&&&&&&''''''((((((())))))********+++++++,,,,,,,,---------.........//////////0000000000011111111111222222222222223333333333333334444444444444444555555555555555555666666666666666666666677777777777777777777777888888888888ÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔŐŐŐŐŐŐÖÖÖÖÖÖÖÖ××××××ŘŘŘŘŘŘŮŮŮŮŮŮÚÚÚÚÚŰŰŰŰŰŰÜÜÜÜÜÝÝÝÝÝŢŢŢŢßßßßßŕŕŕŕŕááááâââââăăăăääääĺĺĺĺććććçççččččééééęęęëëëëěěěííííîîîďďďđđđđńńńňňňóóóôôôőőőööö÷÷÷řřřůůůůúúúűűűüüüýýýţţţ˙˙  !!!!!"""""#####$$$$$$%%%%%&&&&&&''''''(((((())))))))******++++++++,,,,,,,,--------..........//////////00000000000111111111111222222222222333333333333334444444444444444455555555555555555566666666666666666666677777777777777777777778888888888888888ÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐÖÖÖÖÖÖ×××××××ŘŘŘŘŘŘŮŮŮŮŮÚÚÚÚÚÚŰŰŰŰŰŰÜÜÜÜÝÝÝÝÝŢŢŢŢŢŢßßßßŕŕŕŕáááááââââăăăăääääĺĺĺĺććććççççččččéééęęęęëëëěěěěíííîîîďďďđđđđńńńňňňóóóôôôőőőöööö÷÷÷řřřůůůúúúűűűüüüýýýţţţ˙˙  !!!!!!"""""####$$$$$$%%%%%%&&&&&''''''((((((())))))********++++++++,,,,,,,---------........////////////00000000011111111111122222222222222333333333333344444444444444455555555555555555566666666666666666666777777777777777777777777778888888888888888ÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖ×××××ŘŘŘŘŘŘŮŮŮŮŮŮŮÚÚÚÚÚŰŰŰŰŰÜÜÜÜÜÝÝÝÝÝÝŢŢŢŢßßßßŕŕŕŕŕáááááâââăăăăăääääĺĺĺĺćććççççččččéééęęęęëëëëěěěíííîîîďďďďđđđńńńňňňóóóôôôôőőőööö÷÷÷řřřůůůúúúűűűüüüýýýţţţ˙˙  !!!!""""""#####$$$$$%%%%%&&&&&&&''''''((((())))))))*******+++++++,,,,,,,,,-------........../////////00000000000011111111112222222222222233333333333333344444444444444455555555555555555566666666666666666666777777777777777777777777778888888888888888ÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐÖÖÖÖÖÖÖ××××××ŘŘŘŘŘŘŮŮŮŮŮŮÚÚÚÚÚÚŰŰŰŰŰÜÜÜÜÜÝÝÝÝÝŢŢŢŢŢßßßßŕŕŕŕŕááááââââăăăăääääĺĺĺĺććććççççčččééééęęęęëëëěěěííííîîîďďďđđđńńńňňňóóóóôôôőőőööö÷÷÷řřřůůůúúúűűűüüüýýýţţţ˙˙  !!!!!"""""#####$$$$$%%%%%%&&&&&&''''''(((((()))))))******+++++++++,,,,,,,---------.........//////////00000000001111111111112222222222223333333333333344444444444444444555555555555555555666666666666666666666777777777777777777777778888888888888888888ÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔŐŐŐŐŐŐŐÖÖÖÖÖÖÖ××××××ŘŘŘŘŘŘŮŮŮŮŮŮÚÚÚÚÚŰŰŰŰŰÜÜÜÜÜÜÝÝÝÝÝŢŢŢŢßßßßŕŕŕŕŕáááááââââăăăäääääĺĺĺĺćććççççččččéééęęęęëëëěěěěíííîîîďďďđđđńńńńňňňóóóôôôőőőööö÷÷÷řřřůůůúúúűűűüüüýýýţţţ˙˙  !!!!"""""######$$$$$%%%%%&&&&&&''''''(((((()))))))*******+++++++,,,,,,,,---------........///////////000000000011111111111222222222222223333333333333444444444444445555555555555555555666666666666666666667777777777777777777777788888888888888888888888ÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐÖÖÖÖÖÖ×××××××ŘŘŘŘŘŘŮŮŮŮŮÚÚÚÚÚÚŰŰŰŰŰÜÜÜÜÜÝÝÝÝÝŢŢŢŢŢßßßßŕŕŕŕŕááááââââăăăăääääĺĺĺĺććććçççččččéééęęęęëëëëěěěíííîîîďďďđđđđńńńňňňóóóôôôőőőööö÷÷÷řřřůůůúúúűűűüüüýýýţţţ˙˙  !!!!!"""""#####$$$$$%%%%%%&&&&&''''''((((((())))))******+++++++++,,,,,,,--------........../////////00000000000111111111112222222222222333333333333333444444444444445555555555555555566666666666666666666777777777777777777777777788888888888888888888888ÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔŐŐŐŐŐŐŐÖÖÖÖÖÖÖ×××××ŘŘŘŘŘŘŮŮŮŮŮŮŮÚÚÚÚÚŰŰŰŰÜÜÜÜÜÜÝÝÝÝÝŢŢŢŢŢßßßßŕŕŕŕáááááââââăăăăäääĺĺĺĺććććççççčččééééęęęëëëëěěěíííîîîîďďďđđđńńńňňňóóóôôôőőőööö÷÷÷řřřůůůúúúűűűüüüýýýţţţ˙˙  !!!!!"""""######$$$$%%%%%&&&&&&'''''''((((()))))))*******+++++++,,,,,,,,---------........///////////00000000011111111111112222222222233333333333333444444444444444445555555555555555566666666666666666666777777777777777777777777788888888888888888888888ĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔŐŐŐŐŐŐÖÖÖÖÖÖÖ×××××××ŘŘŘŘŘŮŮŮŮŮŮÚÚÚÚÚŰŰŰŰŰŰÜÜÜÜÜÝÝÝÝŢŢŢŢŢßßßßŕŕŕŕŕááááââââăăăăääääĺĺĺĺććććçççččččéééęęęęëëëěěěííííîîîďďďđđđńńńňňňňóóôôôôőőööö÷÷÷řřřůůůúúúűűűüüüýýýţţţ˙˙  !!!!!""""#####$$$$$$%%%%%&&&&&&'''''((((((()))))))******+++++++,,,,,,,,,-------........../////////0000000000111111111112222222222222233333333333344444444444444445555555555555555556666666666666666666677777777777777777777777888888888888888888888888889ĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐÖÖÖÖÖÖ××××××ŘŘŘŘŘŘŮŮŮŮŮŮÚÚÚÚÚŰŰŰŰŰÜÜÜÜÜÝÝÝÝÝŢŢŢŢŢßßßßŕŕŕŕááááâââââăăăăääääĺĺĺććććççççčččééééęęęëëëěěěěíííîîîďďďđđđńńńńňňňóóóôôôőőőööö÷÷řřřůůůúúúűűűüüüýýýţţţ˙˙  !!!!!"""""#####$$$$$%%%%%&&&&&&''''''(((((())))))*******++++++++,,,,,,,---------........//////////00000000001111111111122222222222223333333333333344444444444444555555555555555556666666666666666666677777777777777777777777888888888888888888888888888889ĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔŐŐŐŐŐŐÖÖÖÖÖÖÖ××××××ŘŘŘŘŘŘŮŮŮŮŮÚÚÚÚÚÚŰŰŰŰŰÜÜÜÜÜÝÝÝÝÝŢŢŢŢßßßßŕŕŕŕŕááááââââăăăăääääĺĺĺĺććććçççčččééééęęęëëëëěěěíííîîîîďďđđđđńńńňňňóóóôôôőőőööö÷÷÷řřůůůúúúűűűüüüýýýţţţ˙˙  !!!!"""""#####$$$$$%%%%%%&&&&&''''''(((((()))))))******+++++++,,,,,,,,,-------.........//////////000000000111111111111122222222222333333333333334444444444444444555555555555555556666666666666666666777777777777777777777777888888888888888888888888888889ĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐÖÖÖÖÖÖ×××××ŘŘŘŘŘŘŘŮŮŮŮŮÚÚÚÚÚÚŰŰŰŰÜÜÜÜÜÝÝÝÝÝŢŢŢŢŢßßßßŕŕŕŕŕáááââââăăăăăääääĺĺĺććććçççččččéééęęęęëëëěěěííííîîîďďďđđđńńńňňňóóóôôôőőőööö÷÷÷řřřůůůúúűűűüüüýýýţţţ˙˙  !!!!!"""""#####$$$$%%%%%%&&&&&'''''''((((())))))*******++++++++,,,,,,,--------..........////////00000000000111111111122222222222222333333333333444444444444444455555555555555555566666666666666666666777777777777777777777777888888888888888888888888889999ĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÔÔÔÔÔÔŐŐŐŐŐŐŐÖÖÖÖÖÖÖ××××××ŘŘŘŘŘŮŮŮŮŮÚÚÚÚÚÚŰŰŰŰŰÜÜÜÜÜÝÝÝÝÝŢŢŢŢßßßßŕŕŕŕŕááááââââăăăăääääĺĺĺććććççççčččééééęęęëëëěěěěíííîîîďďďđđđńńńňňňóóóôôôőőőööö÷÷÷řřřůůůúúűűűüüüýýýţţţ˙˙  !!!!"""""#####$$$$$%%%%%%&&&&&'''''(((((()))))))*******++++++,,,,,,,,---------.......///////////00000000011111111111122222222222233333333333333444444444444445555555555555555566666666666666666666777777777777777777777778888888888888888888888888899999999ĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔŐŐŐŐŐŐÖÖÖÖÖÖ××××××ŘŘŘŘŘŘŮŮŮŮŮŮÚÚÚÚÚŰŰŰŰŰÜÜÜÜÝÝÝÝÝŢŢŢŢŢßßßßŕŕŕŕŕááááââââăăăääääĺĺĺĺććććçççččččéééęęęëëëëěěěíííîîîďďďđđđńńńňňňňóóôôôőőőööö÷÷÷řřřůůůúúúűűüüüýýýţţţ˙˙  !!!!!"""""####$$$$$%%%%%&&&&&&''''''(((((())))))******++++++++,,,,,,,,-------........./////////0000000000111111111111222222222223333333333333344444444444444445555555555555556666666666666666666777777777777777777777778888888888888888888888888888999999999ĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÔÔÔÔÔÔÔŐŐŐŐŐŐŐÖÖÖÖÖÖ×××××××ŘŘŘŘŘŮŮŮŮŮÚÚÚÚÚŰŰŰŰŰŰÜÜÜÜÜÝÝÝÝŢŢŢŢŢßßßßŕŕŕŕááááââââăăăăääääĺĺĺĺćććçççččččééééęęęëëëěěěíííîîîîďďďđđđńńńňňňóóóôôôőőööö÷÷÷řřřůůůúúúűűüüüýýýţţţ˙˙  !!!!!""""#####$$$$$$%%%%%&&&&&'''''(((((()))))))*******+++++++,,,,,,,---------......../////////00000000000111111111222222222222223333333333334444444444444444555555555555555556666666666666666666777777777777777777777778888888888888888888888888888999999999ĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔŐŐŐŐŐŐÖÖÖÖÖÖ××××××ŘŘŘŘŘŘŮŮŮŮŮÚÚÚÚÚÚŰŰŰŰÜÜÜÜÜÝÝÝÝŢŢŢŢŢßßßßŕŕŕŕŕááááââââăăăăäääĺĺĺĺćććççççččččéééęęęëëëěěěěíííîîîďďďđđđńńńňňňóóóôôôőőőöö÷÷÷řřřůůůúúúűűüüüýýýţţţ˙˙  !!!!!""""#####$$$$%%%%%%&&&&&''''''(((((())))))******++++++++,,,,,,,,-------........///////////00000000011111111111222222222222333333333333334444444444444555555555555555555666666666666666666667777777777777777777777788888888888888888888888888999999999999ĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÔÔÔÔÔÔÔŐŐŐŐŐŐŐÖÖÖÖÖÖ××××××ŘŘŘŘŘŮŮŮŮŮÚÚÚÚÚÚŰŰŰŰŰÜÜÜÜÜÝÝÝÝÝŢŢŢŢßßßßŕŕŕŕááááââââăăăăääääĺĺĺććććççççčččéééęęęëëëëěěěíííîîîďďďđđđńńńňňňóóóôôôőőőööö÷÷řřřůůůúúúűűüüüýýýţţţ˙˙  !!!!"""""#####$$$$$%%%%%%&&&&&'''''(((((())))))*******+++++++,,,,,,,--------........./////////0000000000111111111112222222222233333333333333444444444444444555555555555555666666666666666666667777777777777777777777888888888888888888888888889999999999999999ĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÔÔÔÔÔÔÔŐŐŐŐŐŐÖÖÖÖÖÖ××××××ŘŘŘŘŘŘŮŮŮŮŮŮÚÚÚÚÚŰŰŰŰŰÜÜÜÜÝÝÝÝÝŢŢŢŢßßßßŕŕŕŕŕááááââââăăăăäääĺĺĺĺćććççççčččééééęęęëëëěěěíííîîîďďďđđđđńńňňňóóóôôôőőőööö÷÷řřřůůůúúúűűűüüýýýţţţ˙˙  !!!!"""""####$$$$$%%%%%&&&&&&''''''(((((())))))******+++++++,,,,,,,---------.......//////////00000000001111111112222222222222233333333333444444444444444445555555555555555666666666666666666777777777777777777777788888888888888888888888888899999999999999999ĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇÓÓÓÓÓÓÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐÖÖÖÖÖÖ××××××ŘŘŘŘŘŮŮŮŮŮÚÚÚÚÚŰŰŰŰŰÜÜÜÜÜÝÝÝÝÝŢŢŢŢßßßßŕŕŕŕŕááááâââăăăăäääĺĺĺĺććććçççččččéééęęęëëëěěěííííîîîďďďđđđńńńňňňóóôôôőőőööö÷÷÷řřůůůúúúűűűüüýýýţţţ˙˙  !!!!"""""#####$$$$$%%%%%&&&&&'''''(((((())))))*******++++++++,,,,,,-------........../////////000000000111111111112222222222223333333333333444444444444455555555555555555556666666666666666666777777777777777777777788888888888888888888888888899999999999999999ĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÔÔÔÔÔÔŐŐŐŐŐŐÖÖÖÖÖÖ××××××ŘŘŘŘŘŘŮŮŮŮŮÚÚÚÚÚŰŰŰŰŰÜÜÜÜÜÝÝÝÝŢŢŢŢŢßßßßŕŕŕŕááááââââăăăääääĺĺĺĺćććççççčččéééęęęëëëëěěěíííîîîďďďđđđńńńňňňóóôôôőőőööö÷÷÷řřůůůúúúűűűüüýýýţţţ˙˙  !!!!!""""#####$$$$%%%%%%&&&&&''''''(((((())))))******++++++,,,,,,,,--------.........////////00000000001111111111122222222222333333333333334444444444444455555555555555566666666666666666666677777777777777777777777888888888888888888888888889999999999999999999ĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔŐŐŐŐŐŐŐÖÖÖÖÖÖ×××××ŘŘŘŘŘŮŮŮŮŮÚÚÚÚÚŰŰŰŰŰÜÜÜÜÜÝÝÝÝÝŢŢŢŢßßßßŕŕŕŕŕááááâââăăăăääääĺĺĺććććçççčččééééęęęëëëěěěíííîîîďďďđđđńńńňňňóóóôôôőőööö÷÷÷řřřůůúúúűűűüüýýýţţţ˙˙  !!!!"""""#####$$$$%%%%%%&&&&&'''''((((())))))*******+++++++,,,,,,,,-------........//////////00000000011111111112222222222222333333333334444444444444444455555555555555566666666666666666777777777777777777777778888888888888888888888888899999999999999999999999ĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÔÔÔÔÔÔŐŐŐŐŐŐŐÖÖÖÖÖÖ××××××ŘŘŘŘŘŮŮŮŮŮŮÚÚÚÚÚŰŰŰŰŰÜÜÜÜÝÝÝÝÝŢŢŢŢßßßßŕŕŕŕááááââââăăăääääĺĺĺĺćććçççččččéééęęęëëëëěěíííîîîďďďđđđńńńňňňóóóôôôőőööö÷÷÷řřřůůúúúűűűüüýýýţţţ˙˙  !!!!"""""####$$$$$%%%%%&&&&&&'''''(((((())))))*******++++++,,,,,,,--------.........////////0000000001111111111112222222222233333333333334444444444444555555555555555555566666666666666666777777777777777777777888888888888888888888888889999999999999999999999999ĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔŐŐŐŐŐŐÖÖÖÖÖÖ××××××ŘŘŘŘŘŮŮŮŮŮÚÚÚÚÚŰŰŰŰŰÜÜÜÜÝÝÝÝÝŢŢŢŢßßßßŕŕŕŕŕáááââââăăăăääääĺĺĺććććçççčččéééęęęëëëëěěěíííîîîďďďđđđńńňňňóóóôôôőőőöö÷÷÷řřřůůůúúűűűüüüýýţţţ˙˙  !!!!"""""####$$$$$%%%%%&&&&&'''''(((((())))))******+++++++,,,,,,,,--------......./////////00000000001111111111222222222223333333333333344444444444445555555555555555666666666666666666666777777777777777777777888888888888888888888888889999999999999999999999999ĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇÓÓÓÓÓÓÔÔÔÔÔÔÔŐŐŐŐŐŐÖÖÖÖÖÖÖ×××××ŘŘŘŘŘŘŮŮŮŮŮÚÚÚÚÚŰŰŰŰŰÜÜÜÜÜÝÝÝÝŢŢŢŢßßßßŕŕŕŕááááââââăăăăäääĺĺĺĺćććçççčččééééęęęëëëěěěíííîîîďďďđđđńńńňňňóóôôôőőőööö÷÷řřřůůůúúűűűüüüýýţţţ˙˙  !!!!""""#####$$$$$%%%%%&&&&&''''''((((()))))))******+++++++,,,,,,-------.........//////////00000000111111111122222222222223333333333344444444444444445555555555555566666666666666666667777777777777777777777788888888888888888888888888999999999999999999999999999ĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÔÔÔÔÔÔÔŐŐŐŐŐŐÖÖÖÖÖÖ×××××ŘŘŘŘŘŮŮŮŮŮÚÚÚÚÚŰŰŰŰŰÜÜÜÜÜÝÝÝÝŢŢŢŢßßßßŕŕŕŕááááââââăăăăäääĺĺĺĺćććççççčččéééęęęëëëěěěěííîîîďďďđđđńńńňňňóóôôôőőőööö÷÷řřřůůůúúűűűüüüýýţţţ˙˙  !!!!""""#####$$$$$%%%%%&&&&&'''''((((())))))******+++++++,,,,,,,--------.........///////0000000000111111111112222222222233333333333334444444444444555555555555555556666666666666666677777777777777777777788888888888888888888888888999999999999999999999999999999:ĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŇŇŇŇŇŇŇÓÓÓÓÓÓÓÔÔÔÔÔÔÔŐŐŐŐŐŐÖÖÖÖÖÖ××××××ŘŘŘŘŘŘŮŮŮŮŮÚÚÚÚÚŰŰŰŰÜÜÜÜÜÝÝÝÝŢŢŢŢßßßßŕŕŕŕááááââââăăăăäääĺĺĺĺćććçççččččéééęęęëëëěěěíííîîîďďđđđńńńňňňóóóôôőőőööö÷÷řřřůůůúúűűűüüüýýţţţ˙˙  !!!!""""#####$$$$%%%%%&&&&&''''''(((((())))))******+++++++,,,,,,,-------.......//////////000000000011111111122222222222333333333333334444444444445555555555555555556666666666666666667777777777777777777788888888888888888888888899999999999999999999999999999999::ĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÔÔÔÔÔÔÔŐŐŐŐŐÖÖÖÖÖÖ××××××ŘŘŘŘŘŮŮŮŮŮÚÚÚÚÚŰŰŰŰŰÜÜÜÜÝÝÝÝŢŢŢŢŢßßßßŕŕŕŕáááââââăăăăäääĺĺĺĺćććçççččččéééęęęëëëěěěíííîîîďďďđđđńńňňňóóóôôôőőööö÷÷÷řřůůůúúúűűüüüýýţţţ˙˙  !!!!!""""####$$$$$%%%%%&&&&&'''''(((((())))))*****+++++++,,,,,,,--------......../////////0000000011111111111222222222222333333333334444444444444445555555555555566666666666666666666677777777777777777777788888888888888888888888899999999999999999999999999999999::ĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇÓÓÓÓÓÓÓÔÔÔÔÔÔÔŐŐŐŐŐŐÖÖÖÖÖÖ××××××ŘŘŘŘŘŮŮŮŮŮÚÚÚÚÚŰŰŰŰŰÜÜÜÜÝÝÝÝŢŢŢŢßßßßŕŕŕŕááááââââăăăääääĺĺĺććććçççčččéééęęęëëëěěěíííîîîďďďđđđńńńňňóóóôôôőőööö÷÷÷řřůůůúúúűűüüüýýţţţ˙˙  !!!!""""####$$$$$%%%%%&&&&&'''''(((((())))))******+++++++,,,,,,,-------........////////0000000000111111111112222222222333333333333444444444444445555555555555555666666666666666667777777777777777777777788888888888888888888888888999999999999999999999999999999::::ĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇÓÓÓÓÓÓÓÔÔÔÔÔÔŐŐŐŐŐŐÖÖÖÖÖÖ×××××ŘŘŘŘŘŮŮŮŮŮÚÚÚÚÚŰŰŰŰŰÜÜÜÜÜÝÝÝÝŢŢŢŢßßßßŕŕŕŕááááââââăăăääääĺĺĺćććçççččččéééęęęëëëěěěíííîîďďďđđđńńńňňňóóôôôőőőöö÷÷÷řřřůůúúúűűüüüýýţţţ˙˙  !!!!""""#####$$$$$%%%%%&&&&&'''''((((())))))******++++++,,,,,,,-------........//////////000000000111111111222222222223333333333333344444444444555555555555555555566666666666666667777777777777777777788888888888888888888888888999999999999999999999999999999::::::::ĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇÓÓÓÓÓÓÓÔÔÔÔÔÔÔŐŐŐŐŐŐÖÖÖÖÖÖ×××××ŘŘŘŘŘŘŮŮŮŮŮÚÚÚÚŰŰŰŰŰÜÜÜÜÝÝÝÝŢŢŢŢŢßßßŕŕŕŕááááââââăăăääääĺĺĺćććççççčččéééęęęëëëěěěíííîîîďďđđđńńńňňňóóóôôőőőöö÷÷÷řřřůůúúúűűüüüýýţţţ˙˙  !!!!!""""####$$$$$%%%%&&&&&''''''((((())))))******+++++++,,,,,,,-------........////////00000000111111111112222222222223333333333344444444444444555555555555555666666666666666666667777777777777777777888888888888888888888889999999999999999999999999999999:::::::::::ĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇÓÓÓÓÓÓÔÔÔÔÔÔŐŐŐŐŐŐÖÖÖÖÖÖ××××××ŘŘŘŘŘŮŮŮŮŮÚÚÚÚÚŰŰŰŰŰÜÜÜÜÝÝÝÝŢŢŢŢŢßßßŕŕŕŕááááâââăăăăäääĺĺĺĺćććçççčččéééęęęëëëěěěíííîîîďďďđđđńńňňňóóóôôőőőööö÷÷řřřůůúúúűűüüüýýţţţ˙˙  !!!!!""""####$$$$$%%%%%&&&&&'''''(((((())))))******++++++,,,,,,-------......../////////000000000111111111112222222222333333333333444444444444444555555555555566666666666666666667777777777777777777777888888888888888888888889999999999999999999999999999999:::::::::::ĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÔÔÔÔÔÔŐŐŐŐŐŐÖÖÖÖÖÖ×××××ŘŘŘŘŘŮŮŮŮŮÚÚÚÚÚŰŰŰŰÜÜÜÜÝÝÝÝÝŢŢŢŢßßßŕŕŕŕááááââââăăăääääĺĺĺććććçççčččéééęęęëëëěěěíííîîďďďđđđńńňňňóóóôôőőőööö÷÷řřřůůúúúűűüüüýýţţţ˙˙  !!!!"""""####$$$$%%%%%&&&&&'''''((((())))))******++++++,,,,,,,--------......../////////000000001111111112222222222233333333333333444444444445555555555555555566666666666666667777777777777777777777888888888888888888888888889999999999999999999999999999999:::::::::::ĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŇŇŇŇŇŇŇÓÓÓÓÓÓÓÔÔÔÔÔÔŐŐŐŐŐŐÖÖÖÖÖÖ×××××ŘŘŘŘŘŮŮŮŮŮÚÚÚÚÚŰŰŰŰŰÜÜÜÜÜÝÝÝÝŢŢŢŢßßßŕŕŕŕááááâââăăăăäääĺĺĺććććçççčččéééęęęëëëěěěíííîîîďďđđđńńńňňóóóôôôőőööö÷÷řřřůůúúúűűüüüýýţţţ˙˙  !!!!""""#####$$$$$%%%%%&&&&&'''''((((())))))******++++++,,,,,,,-------.......////////0000000001111111111122222222222333333333334444444444444455555555555555566666666666666666677777777777777777788888888888888888888888888999999999999999999999999999999::::::::::::::::ĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇÓÓÓÓÓÓÓÔÔÔÔÔÔŐŐŐŐŐŐÖÖÖÖÖÖ×××××ŘŘŘŘŘŮŮŮŮŮÚÚÚÚŰŰŰŰÜÜÜÜÜÝÝÝÝŢŢŢŢßßßßŕŕŕŕááááâââăăăääääĺĺĺćććçççčččéééęęęëëëěěěíííîîîďďđđđńńńňňňóóôôôőőööö÷÷řřřůůůúúűűűüüýýýţţ˙˙  !!!!""""#####$$$$%%%%&&&&&'''''((((())))))******++++++,,,,,,,-------......../////////000000000111111111122222222223333333333334444444444444445555555555556666666666666666666677777777777777777778888888888888888888888999999999999999999999999999999::::::::::::::::::::ĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇÓÓÓÓÓÓÓÔÔÔÔÔÔŐŐŐŐŐŐÖÖÖÖÖÖ×××××ŘŘŘŘŘŮŮŮŮŮÚÚÚÚÚŰŰŰŰŰÜÜÜÜÝÝÝÝŢŢŢŢßßßŕŕŕŕááááâââăăăăäääĺĺĺĺćććçççčččéééęęęëëëěěěííîîîďďďđđđńńňňňóóôôôőőőöö÷÷÷řřůůůúúűűűüüýýýţţ˙˙  !!!!""""####$$$$$%%%%%&&&&&'''''((((())))))******++++++,,,,,,,-------........////////000000001111111112222222222233333333333333444444444445555555555555555666666666666666667777777777777777777777888888888888888888888899999999999999999999999999999:::::::::::::::::::::ĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŇŇŇŇŇŇŇÓÓÓÓÓÓÔÔÔÔÔÔÔŐŐŐŐŐŐÖÖÖÖÖ×××××ŘŘŘŘŘŮŮŮŮŮÚÚÚÚŰŰŰŰŰÜÜÜÜÝÝÝÝŢŢŢŢßßßßŕŕŕŕááááâââăăăääääĺĺĺćććçççčččéééęęęëëëěěěíííîîďďďđđđńńňňňóóóôôőőőöö÷÷÷řřůůůúúűűűüüýýýţţ˙˙  !!!!""""####$$$$$%%%%&&&&&'''''((((()))))******+++++++,,,,,,-------......./////////00000000011111111112222222222233333333333444444444444455555555555555556666666666666667777777777777777777777888888888888888888888888899999999999999999999999999999:::::::::::::::::::::ĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŇŇŇŇŇŇŇÓÓÓÓÓÓÓÔÔÔÔÔÔŐŐŐŐŐŐÖÖÖÖÖ×××××ŘŘŘŘŘŮŮŮŮŮÚÚÚÚÚŰŰŰŰŰÜÜÜÜÝÝÝÝŢŢŢŢßßßŕŕŕŕááááââââăăăäääĺĺĺćććçççčččéééęęęëëëěěěíííîîîďďđđđńńńňňóóóôôőőőöö÷÷÷řřůůůúúűűűüüýýýţţ˙˙  !!!!""""####$$$$$%%%%%&&&&&'''''((((()))))******++++++,,,,,,--------......./////////000000000111111111222222222233333333333444444444444444555555555555566666666666666666677777777777777777788888888888888888888888888999999999999999999999999999999:::::::::::::::::::::::ĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŇŇŇŇŇŇŇÓÓÓÓÓÓÓÔÔÔÔÔÔŐŐŐŐŐŐÖÖÖÖÖÖ×××××ŘŘŘŘŘŮŮŮŮÚÚÚÚÚŰŰŰŰÜÜÜÜÝÝÝÝŢŢŢŢŢßßßŕŕŕŕáááââââăăăäääĺĺĺĺćććçççčččéééęęęëëëěěíííîîîďďđđđńńńňňóóóôôôőőööö÷÷řřřůůúúűűűüüýýýţţ˙˙  !!!!!""""####$$$$%%%%%&&&&'''''((((())))))******++++++,,,,,,,-------.......////////000000001111111112222222222223333333333334444444444445555555555555556666666666666666667777777777777777777888888888888888888888999999999999999999999999999999::::::::::::::::::::::::::::ĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŇŇŇŇŇŇŇÓÓÓÓÓÓÔÔÔÔÔÔŐŐŐŐŐŐÖÖÖÖÖ×××××ŘŘŘŘŘŮŮŮŮŮÚÚÚÚÚŰŰŰŰÜÜÜÜÝÝÝÝŢŢŢŢßßßŕŕŕŕááááââââăăăäääĺĺĺćććçççččččééęęęëëëěěěííîîîďďďđđńńńňňňóóôôôőőööö÷÷řřřůůúúűűűüüýýýţţ˙˙  !!!!""""####$$$$%%%%%&&&&&'''''((((()))))******++++++,,,,,,-------......./////////0000000011111111112222222222223333333333444444444444555555555555555556666666666666677777777777777777777777888888888888888888888999999999999999999999999999:::::::::::::::::::::::::::::::ĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŇŇŇŇŇŇŇÓÓÓÓÓÓÔÔÔÔÔÔÔŐŐŐŐŐŐÖÖÖÖÖ××××××ŘŘŘŘŮŮŮŮŮÚÚÚÚŰŰŰŰÜÜÜÜÜÝÝÝÝŢŢŢŢßßßŕŕŕŕáááââââăăăääääĺĺĺćććçççčččéééęęęëëěěěíííîîîďďđđđńńňňňóóôôôőőööö÷÷řřřůůúúúűűüüýýýţţ˙˙  !!!!""""#####$$$$%%%%&&&&&''''(((((()))))******+++++++,,,,,,-------......./////////0000000011111111122222222223333333333344444444444444555555555555556666666666666666777777777777777777778888888888888888888888888999999999999999999999999999:::::::::::::::::::::::::::::::ĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŇŇŇŇŇŇŇÓÓÓÓÓÓÔÔÔÔÔÔŐŐŐŐŐŐÖÖÖÖÖ×××××ŘŘŘŘŮŮŮŮŮÚÚÚÚÚŰŰŰŰŰÜÜÜÝÝÝÝŢŢŢŢßßßßŕŕŕŕáááâââăăăăäääĺĺĺćććçççčččéééęęęëëěěěíííîîîďďđđđńńńňňóóóôôőőőöö÷÷řřřůůúúúűűüüýýýţţ˙˙  !!!!"""####$$$$$%%%%%&&&&&''''((((()))))******++++++,,,,,,-------.......////////0000000011111111112222222222233333333333344444444444455555555555556666666666666666666777777777777777778888888888888888888888888999999999999999999999999999999:::::::::::::::::::::::::::::::ĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŇŇŇŇŇŇÓÓÓÓÓÓÔÔÔÔÔÔŐŐŐŐŐŐÖÖÖÖÖÖ×××××ŘŘŘŘŘŮŮŮŮÚÚÚÚÚŰŰŰŰÜÜÜÜÝÝÝÝŢŢŢŢßßßŕŕŕŕáááââââăăăäääĺĺĺćććççççčččééęęęëëëěěěííîîîďďďđđńńńňňóóóôôőőőöö÷÷řřřůůúúúűűüüýýýţţ˙˙  !!!!""""####$$$$%%%%%&&&&'''''((((())))))******++++++,,,,,,------........////////000000001111111111222222222223333333333444444444445555555555555555666666666666666677777777777777777777888888888888888888888999999999999999999999999999999:::::::::::::::::::::::::::::::::::ÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŇŇŇŇŇŇŇÓÓÓÓÓÓÓÔÔÔÔÔÔŐŐŐŐŐŐÖÖÖÖÖ××××ŘŘŘŘŘŮŮŮŮŮÚÚÚÚÚŰŰŰŰÜÜÜÜÝÝÝŢŢŢŢßßßßŕŕŕŕáááâââăăăăäääĺĺĺćććçççčččéééęęęëëěěěíííîîďďďđđńńńňňóóóôôőőőöö÷÷÷řřůůúúúűűüüýýýţţ˙˙  !!!!"""####$$$$%%%%%&&&&&'''''(((()))))******++++++,,,,,,,-------.......////////00000000111111111222222222233333333333444444444444455555555555555566666666666666777777777777777777777778888888888888888888899999999999999999999999999::::::::::::::::::::::::::::::::::::;;;;ÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŇŇŇŇŇŇŇÓÓÓÓÓÓÔÔÔÔÔŐŐŐŐŐŐÖÖÖÖÖ××××××ŘŘŘŘŘŮŮŮŮÚÚÚÚŰŰŰŰÜÜÜÜÜÝÝÝÝŢŢŢßßßŕŕŕŕááááâââăăăäääĺĺĺććććççčččéééęęęëëëěěíííîîîďďđđđńńňňňóóôôôőőöö÷÷÷řřůůůúúűűüüüýýţţ˙˙  !!!""""#####$$$$%%%%&&&&'''''(((((()))))******+++++,,,,,,-------.......////////0000000011111111122222222222333333333333444444444444555555555555566666666666666666777777777777777777788888888888888888888888899999999999999999999999999:::::::::::::::::::::::::::::::::::;;;;;ÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŇŇŇŇŇŇŇÓÓÓÓÓÓÔÔÔÔÔÔÔŐŐŐŐŐŐÖÖÖÖ×××××ŘŘŘŘŘŮŮŮŮÚÚÚÚÚŰŰŰŰÜÜÜÜÝÝÝÝŢŢŢŢßßßŕŕŕŕáááâââăăăăäääĺĺĺćććçççčččéééęęëëëěěíííîîîďďđđđńńňňňóóôôôőőööö÷÷řřůůůúúűűüüüýýţţ˙˙  !!!!""""####$$$$%%%%%&&&&'''''((((())))******+++++++,,,,,,-------.......////////0000000011111111122222222222333333333344444444444555555555555555666666666666666666777777777777777788888888888888888888888889999999999999999999999999999:::::::::::::::::::::::::::::::::::;;;;;ÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŇŇŇŇŇŇŇÓÓÓÓÓÓÔÔÔÔÔŐŐŐŐŐŐÖÖÖÖÖ×××××ŘŘŘŘŘŮŮŮŮÚÚÚÚŰŰŰŰŰÜÜÜÜÝÝÝÝŢŢŢßßßßŕŕŕŕáááâââăăăäääĺĺĺćććçççčččéééęęęëëěěěííîîîďďďđđńńńňňóóóôôőőööö÷÷řřůůůúúűűüüüýýţţ˙˙  !!!""""####$$$$$%%%%&&&&'''''((((()))))******+++++,,,,,,-------.......////////0000000011111111122222222223333333333444444444444455555555555555556666666666666677777777777777777777888888888888888888888999999999999999999999999999999::::::::::::::::::::::::::::::::::::;;;;;;ÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŇŇŇŇŇŇÓÓÓÓÓÓÔÔÔÔÔÔŐŐŐŐŐŐÖÖÖÖÖ×××××ŘŘŘŘŮŮŮŮŮÚÚÚÚÚŰŰŰÜÜÜÜÝÝÝÝŢŢŢŢßßßŕŕŕŕáááââââăăăäääĺĺĺćććçççččéééęęęëëëěěíííîîďďďđđńńńňňóóóôôőőőöö÷÷řřřůůúúűűüüüýýţţ˙˙  !!!!""""####$$$%%%%%&&&&&''''((((()))))******++++++,,,,,,------.......////////000000001111111112222222222333333333334444444444444555555555555566666666666666677777777777777777777788888888888888888888999999999999999999999999999::::::::::::::::::::::::::::::::::::;;;;;;;;;;;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇÓÓÓÓÓÓÔÔÔÔÔŐŐŐŐŐÖÖÖÖÖ××××××ŘŘŘŘŘŮŮŮŮÚÚÚÚŰŰŰŰÜÜÜÜÝÝÝÝŢŢŢßßßßŕŕŕŕáááâââăăăäääĺĺĺćććçççčččéééęęëëëěěěííîîîďďđđđńńňňóóóôôőőőöö÷÷řřřůůúúűűüüüýýţţ˙˙  !!!""""####$$$$%%%%&&&&'''''(((((()))))*****+++++,,,,,,-------........////////000000001111111112222222222333333333344444444444555555555555566666666666666666677777777777777777888888888888888888888888999999999999999999999999::::::::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐŃŃŃŃŃŃŇŇŇŇŇŇÓÓÓÓÓÓÔÔÔÔÔÔŐŐŐŐŐŐÖÖÖÖÖ××××ŘŘŘŘŘŮŮŮŮÚÚÚÚÚŰŰŰŰÜÜÜÝÝÝÝÝŢŢŢßßßŕŕŕŕááááâââăăääääĺĺĺćććççčččéééęęęëëěěěííîîîďďđđđńńňňňóóôôőőőöö÷÷řřřůůúúűűüüüýýţţ˙˙  !!!"""""###$$$$%%%%%&&&&'''''(((()))))******++++++,,,,,,------......////////000000001111111112222222222333333333344444444444445555555555555566666666666666667777777777777777788888888888888888888888899999999999999999999999999:::::::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐŃŃŃŃŃŃŇŇŇŇŇŇŇÓÓÓÓÓÓÓÔÔÔÔÔŐŐŐŐŐÖÖÖÖÖ×××××ŘŘŘŘŘŮŮŮŮÚÚÚÚŰŰŰŰŰÜÜÜÜÝÝÝŢŢŢßßßßŕŕŕŕáááâââăăăäääĺĺĺćććçççčččééęęęëëëěěíííîîďďďđđńńńňňóóôôôőőöö÷÷řřřůůúúűűűüüýýţţ˙˙  !!!"""####$$$$$%%%%&&&&'''''((((()))))*****+++++,,,,,,,-------......////////000000011111111122222222223333333333344444444444445555555555555666666666666667777777777777777777788888888888888888888999999999999999999999999999999:::::::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŇŇŇŇŇŇÓÓÓÓÓÔÔÔÔÔŐŐŐŐŐŐÖÖÖÖÖÖ××××ŘŘŘŘŮŮŮŮŮÚÚÚÚŰŰŰŰÜÜÜÜÝÝÝÝŢŢŢßßßŕŕŕŕáááâââăăăăäääĺĺĺććçççčččéééęęëëëěěěííîîďďďđđńńńňňóóôôôőőöö÷÷÷řřůůúúűűűüüýýţţ˙˙  !!!""""####$$$$%%%%&&&&&''''(((())))))******+++++,,,,,------........////////000000001111111112222222222333333333344444444444555555555555666666666666666667777777777777777777788888888888888888889999999999999999999999999999::::::::::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĐĐĐĐĐĐĐŃŃŃŃŃŃŃŇŇŇŇŇŇÓÓÓÓÓÓÔÔÔÔÔÔŐŐŐŐŐÖÖÖÖÖ×××××ŘŘŘŘŘŮŮŮŮÚÚÚŰŰŰŰŰÜÜÜÜÝÝÝŢŢŢŢßßßŕŕŕŕáááâââăăăäääĺĺĺćććççčččéééęęęëëěěěííîîîďďđđđńńňňóóóôôőőööö÷÷řřůůúúűűűüüýýţţ˙˙  !!!!"""####$$$$$%%%&&&&'''''((((()))))*****++++++,,,,,,------.......///////000000011111111122222222223333333333444444444444555555555555556666666666666666677777777777777778888888888888888888888899999999999999999999999::::::::::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŇŇŇŇŇŇŇÓÓÓÓÓÔÔÔÔÔŐŐŐŐŐŐÖÖÖÖÖ×××××ŘŘŘŘŮŮŮŮÚÚÚÚÚŰŰŰÜÜÜÜÝÝÝÝŢŢŢßßßßŕŕŕáááâââăăăăäääĺĺćććçççčččééęęęëëëěěíííîîďďđđđńńňňňóóôôőőööö÷÷řřůůúúúűűüüýýţţ˙˙  !!!""""####$$$%%%%%&&&&''''((((()))))*****++++++,,,,,-------.......////////00000001111111112222222223333333333344444444444455555555555555666666666666667777777777777777788888888888888888888888899999999999999999999999::::::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐŃŃŃŃŃŃŇŇŇŇŇŇÓÓÓÓÓÓÔÔÔÔÔÔŐŐŐŐŐÖÖÖÖÖ××××ŘŘŘŘŘŮŮŮŮÚÚÚÚŰŰŰŰŰÜÜÜÝÝÝŢŢŢŢßßßŕŕŕááááâââăăăäääĺĺĺćććçççččéééęęëëëěěíííîîďďďđđńńňňňóóôôőőőöö÷÷řřůůúúúűűüüýýţţ˙˙  !!!!"""###$$$$$%%%%&&&&'''''(((()))))*****++++++,,,,,,------......////////0000000011111111122222222223333333333444444444445555555555555666666666666667777777777777777777788888888888888888888999999999999999999999999999:::::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĐĐĐĐĐĐĐŃŃŃŃŃŃŇŇŇŇŇŇŇÓÓÓÓÓÓÓÔÔÔÔÔŐŐŐŐÖÖÖÖÖÖ××××ŘŘŘŘŮŮŮŮŮÚÚÚÚŰŰŰŰÜÜÜÜÝÝÝŢŢŢßßßßŕŕŕáááââââăăăääĺĺĺĺććçççčččééęęęëëěěěííîîîďďđđńńńňňóóôôőőőöö÷÷řřůůůúúűűüüýýţţ˙˙  !!!"""####$$$$%%%%&&&&&''''(((())))))****+++++,,,,,,,-------......///////000000011111111222222222233333333334444444444455555555555555666666666666666677777777777777777788888888888888888899999999999999999999999999999:::::::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŇŇŇŇŇÓÓÓÓÓÓÔÔÔÔÔŐŐŐŐŐŐÖÖÖÖ×××××ŘŘŘŘŘŮŮŮŮÚÚÚŰŰŰŰŰÜÜÜÝÝÝÝŢŢŢŢßßŕŕŕŕáááâââăăăäääĺĺĺćććççčččéééęęëëěěěíííîîďďđđđńńňňóóôôôőőöö÷÷řřřůůúúűűüüýýţţ˙˙  !!!!""""###$$$$$%%%&&&&'''''((((())))******+++++,,,,,,-----.......////////000000001111111122222222233333333334444444444445555555555555566666666666666677777777777777788888888888888888888889999999999999999999999999::::::::::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐŃŃŃŃŃŃŇŇŇŇŇŇÓÓÓÓÓÓÔÔÔÔÔÔŐŐŐŐÖÖÖÖÖ×××××ŘŘŘŘŮŮŮŮÚÚÚÚÚŰŰŰÜÜÜÜÝÝÝŢŢŢŢßßßŕŕŕáááââââăăääääĺĺćććçççččéééęęęëëěěíííîîďďđđđńńňňóóóôôőőöö÷÷řřřůůúúűűüüýýţţ˙˙  !!!!"""####$$$%%%%%&&&&''''((((()))))****++++++,,,,,,------......///////00000000111111111222222222233333333334444444444455555555555556666666666666777777777777777777888888888888888888888889999999999999999999999::::::::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎĎĎĎĎĎĎĎĐĐĐĐĐĐĐŃŃŃŃŃŃŃŇŇŇŇŇŇŇÓÓÓÓÓÔÔÔÔÔŐŐŐŐŐŐÖÖÖÖ××××ŘŘŘŘŘŮŮŮŮÚÚÚÚŰŰŰŰÜÜÜÝÝÝÝŢŢŢŢßßŕŕŕŕáááâââăăăäääĺĺĺććçççčččééęęęëëěěěííîîďďďđđńńňňňóóôôőőöö÷÷řřřůůúúűűüüýýţţ˙˙  !!!!""""###$$$$%%%%&&&&'''''(((())))******+++++,,,,,-------.......///////00000001111111222222222233333333334444444444455555555555566666666666666667777777777777777777888888888888888888899999999999999999999999999:::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐŃŃŃŃŃŃŇŇŇŇŇŇÓÓÓÓÓÓÔÔÔÔÔÔŐŐŐŐÖÖÖÖÖ×××××ŘŘŘŮŮŮŮŮÚÚÚÚŰŰŰÜÜÜÜÜÝÝÝŢŢŢßßßŕŕŕááááâââăăäääĺĺĺćććççčččéééęęëëěěěííîîîďďđđńńńňňóóôôőőööö÷÷řřůůúúűűüüýýţţ˙˙  !!!"""#####$$$%%%%&&&&''''((((()))))****++++++,,,,,,------......///////0000000001111111222222222333333333344444444444555555555555566666666666666667777777777777777788888888888888888999999999999999999999999999999::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĐĐĐĐĐĐĐŃŃŃŃŃŃŇŇŇŇŇŇŇÓÓÓÓÓÔÔÔÔÔŐŐŐŐŐÖÖÖÖÖ××××ŘŘŘŘŘŮŮŮŮÚÚÚŰŰŰŰÜÜÜÜÝÝÝÝŢŢŢßßßŕŕŕáááâââăăăäääĺĺĺććçççččéééęęęëëěěíííîîďďđđđńńňňóóôôőőőöö÷÷řřůůúúűűüüýýţţ˙˙  !!!""""####$$$$%%%&&&&'''''(((()))))*****+++++,,,,,-------......///////000000011111111122222222223333333333444444444445555555555555666666666666667777777777777778888888888888888888889999999999999999999999999:::::::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎĎĎĎĎĎĎĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŇŇŇŇŇÓÓÓÓÓÔÔÔÔÔÔÔŐŐŐŐÖÖÖÖ×××××ŘŘŘŘŮŮŮŮÚÚÚÚŰŰŰŰÜÜÜÜÝÝÝŢŢŢŢßßŕŕŕŕáááââăăăăääĺĺĺćććççčččééęęęëëěěěííîîďďđđđńńňňóóôôőőőöö÷÷řřůůúúűűüüýýţţ˙˙  !!!!"""####$$$$%%%%&&&&''''((((())))****+++++++,,,,,-----.......////////00000011111111222222222333333333344444444444555555555555666666666666667777777777777777778888888888888888888888999999999999999999999::::::::::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐŃŃŃŃŃŃŇŇŇŇŇŇÓÓÓÓÓÓÔÔÔÔÔŐŐŐŐŐÖÖÖÖÖ×××ŘŘŘŘŘŮŮŮŮÚÚÚÚŰŰŰŰÜÜÜÝÝÝÝŢŢŢßßßŕŕŕáááâââăăăäääĺĺćććçççččéééęęëëëěěííîîîďďđđńńňňóóóôôőőöö÷÷řřůůúúűűüüýýţţ˙˙  !!!""""###$$$$%%%%&&&&'''''((()))))*****+++++,,,,,,------......//////0000000001111111122222222333333333344444444445555555555555666666666666666777777777777777777888888888888888888899999999999999999999999::::::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<ĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĐĐĐĐĐĐŃŃŃŃŃŃŃŇŇŇŇŇŇŇÓÓÓÓÔÔÔÔÔÔŐŐŐŐŐÖÖÖÖ×××××ŘŘŘŘŮŮŮŮÚÚÚÚŰŰŰÜÜÜÜÝÝÝŢŢŢŢßßŕŕŕŕáááâââăăäääĺĺĺććçççčččééęęęëëěěíííîîďďđđńńňňňóóôôőőöö÷÷řřůůúúűűüüýýţţ˙˙  !!!!"""####$$$%%%%&&&&''''((((())))*****++++++,,,,-------.......//////0000001111111112222222222333333333344444444445555555555555666666666666666777777777777777888888888888888889999999999999999999999999999:::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<ĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐŃŃŃŃŃŃŇŇŇŇŇÓÓÓÓÓÓÔÔÔÔÔŐŐŐŐÖÖÖÖÖ××××ŘŘŘŘŘŮŮŮÚÚÚÚŰŰŰŰÜÜÜÝÝÝÝŢŢŢßßßŕŕŕááááââăăăäääĺĺćććççčččééęęęëëěěíííîîďďđđđńńňňóóôôőőöö÷÷řřůůúúűűüüýýţţ˙˙  !!!""""###$$$$%%%%&&&'''''((())))))****+++++,,,,,,-----......////////000000011111112222222223333333333444444444445555555555556666666666666677777777777777788888888888888888888899999999999999999999999999:::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<ĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĐĐĐĐĐĐŃŃŃŃŃŃŇŇŇŇŇŇŇÓÓÓÓÔÔÔÔÔŐŐŐŐŐŐÖÖÖ××××××ŘŘŘŮŮŮŮÚÚÚÚŰŰŰŰÜÜÜÝÝÝŢŢŢŢßßßŕŕŕáááâââăăäääĺĺĺććçççččéééęęëëëěěííîîďďđđđńńňňóóôôőőöö÷÷řřůůúúűűüüýýţţ˙˙  !!!!"""###$$$$%%%%&&&&'''((((())))******+++++,,,,-------......//////0000000011111111222222223333333334444444444555555555555666666666666667777777777777777778888888888888888888889999999999999999999999:::::::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<ĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎĎĎĎĎĎĎĐĐĐĐĐĐĐŃŃŃŃŃŃŃŇŇŇŇŇÓÓÓÓÓÔÔÔÔÔÔŐŐŐŐÖÖÖÖÖ××××ŘŘŘŘŮŮŮŮÚÚÚÚŰŰŰÜÜÜÜÝÝÝŢŢŢßßßŕŕŕáááâââăăăääĺĺĺćććççčččééęęęëëěěííîîîďďđđńńňňóóôôőőöö÷÷řřřůůúúűűüüýýţţ˙  !!!"""####$$$%%%%&&&&''''(((()))))****++++++,,,,,-----.......///////000000111111112222222222333333333444444444455555555555566666666666666777777777777777777888888888888888888999999999999999999999:::::::::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<ĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐŃŃŃŃŃŇŇŇŇŇŇÓÓÓÓÓÓÔÔÔÔŐŐŐŐŐŐÖÖÖ×××××ŘŘŘŘŮŮŮŮÚÚÚŰŰŰŰÜÜÜÝÝÝÝŢŢŢßßßŕŕŕáááâââăăäääĺĺĺććççčččééęęęëëěěěííîîďďđđńńňňóóôôôőőöö÷÷řřůůúúűűüüýýţţ˙  !!!""""###$$$$%%%&&&&''''((((()))******++++,,,,,,------.....///////00000000111111122222222333333333344444444444555555555555566666666666667777777777777777888888888888888889999999999999999999999999::::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<ĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĐĐĐĐĐĐŃŃŃŃŃŃŃŇŇŇŇŇÓÓÓÓÓÔÔÔÔÔÔŐŐŐŐÖÖÖÖÖ×××ŘŘŘŘŘŮŮŮÚÚÚÚŰŰŰŰÜÜÜÝÝÝŢŢŢŢßßŕŕŕŕááâââăăăäääĺĺćććççčččééęęëëëěěííîîďďđđńńňňňóóôôőőöö÷÷řřůůúúűűüüýýţţ˙  !!!!"""###$$$$%%%%&&&'''''((()))))****++++++,,,,,-----.......//////000000011111111122222223333333334444444444555555555555666666666666667777777777777778888888888888888888899999999999999999999999999:::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<ĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎĎĎĎĎĎĎĐĐĐĐĐĐĐĐŃŃŃŃŃŃŇŇŇŇŇÓÓÓÓÓÓÔÔÔÔŐŐŐŐŐÖÖÖÖ×××××ŘŘŘŮŮŮŮÚÚÚÚŰŰŰŰÜÜÜÝÝÝŢŢŢßßßŕŕŕáááâââăăăääĺĺĺććçççččééęęęëëěěííîîďďđđđńńňňóóôôőőöö÷÷řřůůúúűűüüýýţţ˙  !!!"""###$$$$%%%%&&&&'''((((())))*****++++,,,,,,-----......////////00000011111112222222222333333333444444444455555555555666666666666677777777777777777888888888888888888889999999999999999999999:::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<ĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐŃŃŃŃŃŇŇŇŇŇŇŇÓÓÓÓÔÔÔÔÔÔŐŐŐŐÖÖÖÖ××××ŘŘŘŘŮŮŮŮÚÚÚÚŰŰŰÜÜÜÜÝÝŢŢŢŢßßŕŕŕŕááâââăăăäääĺĺćććççččééęęęëëěěííîîîďďđđńńňňóóôôőőöö÷÷řřůůúúűűüüýýţţ˙  !!!!""####$$$%%%%&&&&''''(((())))****++++++,,,,-------.....//////0000000001111112222222233333333334444444444455555555555566666666666667777777777777777788888888888888888899999999999999999999:::::::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<ĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĐĐĐĐĐĐŃŃŃŃŃŃŃŇŇŇŇŇÓÓÓÓÓÓÔÔÔÔŐŐŐŐŐÖÖÖÖ××××ŘŘŘŘŮŮŮŮÚÚÚŰŰŰŰÜÜÜÝÝÝÝŢŢßßßŕŕŕáááâââăăäääĺĺćććççčččééęęëëěěěííîîďďđđńńňňóóôôőőöö÷÷řřůůúúűűüüýýţţ˙  !!""""###$$$$%%%&&&&''''(((())))*****++++,,,,,,-----.......//////000000111111111222222233333333344444444445555555555556666666666666677777777777777788888888888888888999999999999999999999999::::::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<ĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎĎĎĎĎĎĎĐĐĐĐĐĐĐŃŃŃŃŃŇŇŇŇŇŇÓÓÓÓÓÔÔÔÔÔŐŐŐŐÖÖÖÖÖ××××ŘŘŘŘŮŮŮÚÚÚÚŰŰŰÜÜÜÜÝÝÝŢŢŢßßßŕŕŕááâââăăăääĺĺĺććçççččééęęëëëěěííîîďďđđńńňňňóôôôőöö÷÷řřůůúúűűüüýýţţ˙  !!!"""####$$$%%%%&&&''''(((()))))****+++++,,,,,------....////////0000001111111222222222233333333344444444455555555555666666666666777777777777777788888888888888888889999999999999999999999999:::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<ĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĐĐĐĐĐŃŃŃŃŃŃŃŇŇŇŇŇÓÓÓÓÓÔÔÔÔÔŐŐŐŐÖÖÖÖÖ××××ŘŘŘŮŮŮŮÚÚÚÚŰŰŰÜÜÜÝÝÝÝŢŢßßßŕŕŕáááâââăăäääĺĺćććççččéééęęëëěěííîîîďďđđńńňňóóôôőőö÷÷řřůůúúűűüüýýţţ˙  !!""""###$$$%%%%&&&&'''(((()))))****+++++,,,,,-----......//////0000000011111112222222333333333344444444444555555555556666666666667777777777777777888888888888888888899999999999999999999999::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<ĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎĎĎĎĎĎĐĐĐĐĐĐĐŃŃŃŃŃŃŇŇŇŇŇÓÓÓÓÓÔÔÔÔÔŐŐŐŐÖÖÖÖ××××ŘŘŘŘŮŮŮŮÚÚÚÚŰŰÜÜÜÜÝÝÝŢŢŢßßßŕŕŕáááââăăăääĺĺĺććççčččééęęëëěěěííîîďďđđńńňňóóôôőőöö÷÷řůůúúűűüüýýţţ˙  !!!"""####$$%%%%&&&&''''(((())))****+++++,,,,,-----......///////00000111111111222222233333333444444444445555555555556666666666666677777777777777778888888888888888899999999999999999999:::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<ĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎĎĎĎĎĎĎĎĐĐĐĐĐĐŃŃŃŃŃŇŇŇŇŇŇÓÓÓÓÓÔÔÔÔÔŐŐŐŐÖÖÖÖ××××ŘŘŘŘŮŮŮŮÚÚÚŰŰŰŰÜÜÝÝÝÝŢŢŢßßŕŕŕŕááâââăăäääĺĺććçççččéééęęëëěěííîîďďđđńńňňóóôôőőöö÷÷řřůúúűűüüýýţţ˙  !!!""""##$$$$%%%&&&&''''(((())))****+++++,,,,,------.....//////0000000111111222222222233333333444444444555555555556666666666666777777777777777788888888888888888999999999999999999999::::::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<ĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÎÎÎÎÎÎÎĎĎĎĎĎĎĎĐĐĐĐĐŃŃŃŃŃŃŃŇŇŇŇÓÓÓÓÓÓÔÔÔÔŐŐŐŐŐÖÖÖÖ××××ŘŘŘŘŮŮŮÚÚÚÚŰŰŰÜÜÜÝÝÝŢŢŢßßßŕŕŕáááââăăăääĺĺćććççčččééęęëëěěííîîďďđđńńňňóóôôőőöö÷÷řřůůúűűüüýýţţ˙  !!!"""###$$$%%%%&&&''''(((())))*****++++,,,,,,----......./////00000001111111222222233333333334444444444555555555566666666666677777777777777788888888888888888899999999999999999999999::::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<ĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎĎĎĎĎĎĐĐĐĐĐĐĐĐŃŃŃŃŇŇŇŇŇŇŇÓÓÓÔÔÔÔÔÔŐŐŐÖÖÖÖÖ××××ŘŘŘŘŮŮŮÚÚÚŰŰŰŰÜÜÜÝÝÝŢŢŢßßŕŕŕŕááâââăăääĺĺĺććçççččééęęëëěěěííîîďďđđńńňňóôôőőöö÷÷řřůůúűűüüýýţţ˙  !!!"""###$$$$%%%&&&''''(((()))))***++++++,,,-------....////////00000111111112222222233333334444444444455555555555566666666666677777777777777788888888888888888899999999999999999999999:::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<ĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎĎĎĎĎĎĎĎĎĐĐĐĐĐŃŃŃŃŃŃŇŇŇŇŇÓÓÓÓÓÔÔÔÔŐŐŐŐŐÖÖÖ×××××ŘŘŘŮŮŮŮÚÚÚÚŰŰÜÜÜÜÝÝÝŢŢßßßŕŕŕáááââăăäääĺĺĺććççččéééęęëëěěííîîďďđđńńňňóóôôőöö÷÷řřůůúúűüüýýţţ˙  !!"""####$$%%%%&&&&'''((((()))*****++++,,,,,-----....../////0000000011111222222222233333333444444445555555555556666666666666677777777777777788888888888888888899999999999999999999::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<ĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘËËËËËËËËËĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÎÎÎÎÎÎÎĎĎĎĎĎĎĐĐĐĐĐĐŃŃŃŃŃŃŇŇŇŇŇÓÓÓÓÓÔÔÔÔÔŐŐŐÖÖÖÖÖ×××ŘŘŘŘŮŮŮŮÚÚÚŰŰŰÜÜÜÝÝÝŢŢŢßßßŕŕŕááââăăăäääĺĺććççčččééęęëëěěííîîďďđđńńňňóóôôőőö÷÷řřůůúúűüüýýţţ˙  !!!"""###$$$%%%&&&&''''((()))))***+++++,,,,,-----......//////00000011111112222222333333333344444444455555555556666666666667777777777777778888888888888888899999999999999999999:::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<ĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎĎĎĎĎĎĎĐĐĐĐĐĐĐŃŃŃŃŃŇŇŇŇŇÓÓÓÓÓÔÔÔÔŐŐŐŐŐÖÖÖ××××ŘŘŘŘŮŮŮÚÚÚÚŰŰŰÜÜÜÝÝÝŢŢßßßŕŕŕááâââăăăääĺĺĺććççččééęęëëëěíííîďďđđńńňňóóôôőőö÷÷řřůůúúűüüýýţţ˙  !!"""###$$$%%%%&&&''''(((()))*****++++,,,,,-----.....///////00000111111112222222233333334444444444455555555555666666666667777777777777788888888888888889999999999999999999999::::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<ÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÎÎÎÎÎĎĎĎĎĎĎĎĎĐĐĐĐĐŃŃŃŃŃŃŇŇŇŇŇÓÓÓÓÓÔÔÔÔŐŐŐŐÖÖÖÖÖ×××ŘŘŘŘŮŮŮÚÚÚŰŰŰŰÜÜÜÝÝŢŢŢßßßŕŕáááâââăăäääĺĺććççččéééęęëëěěííîîďďđđńńňóóôôőőöö÷řřůůúúűüüýýţţ˙  !!!""###$$$$%%%&&&''''((()))))****++++,,,,,-----....../////0000000111111222222222333333334444444455555555555566666666666667777777777777788888888888888889999999999999999999999::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<======ÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘËËËËËËËËĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÎÎÎÎÎÎÎÎĎĎĎĎĎĐĐĐĐĐĐĐŃŃŃŃŇŇŇŇŇŇÓÓÓÓÔÔÔÔÔŐŐŐŐÖÖÖÖ××××ŘŘŘŮŮŮŮÚÚÚŰŰŰÜÜÜÝÝÝŢŢŢßßŕŕŕáááââăăăääĺĺćććççččééęęëëěěííîîďďđđńńňňóôôőőöö÷÷řůůúúűűüýýţţ˙  !!!"""###$$$%%%&&&&'''(((())))****+++++,,,-------....///////000001111111122222233333333334444444455555555556666666666666777777777777777788888888888888888999999999999999999999::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<============ÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘËËËËËËËËËËËĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎĎĎĎĎĎĎĐĐĐĐĐĐŃŃŃŃŃŇŇŇŇÓÓÓÓÓÓÔÔÔÔŐŐŐŐÖÖÖÖÖ××ŘŘŘŘŮŮŮŮÚÚŰŰŰŰÜÜÜÝÝŢŢŢßßßŕŕŕááâââăăääĺĺĺććççččééęęëëëěííîîďďđđńńňňóôôőőöö÷÷řůůúúűűüýýţţ˙  !!!""###$$$$%%&&&&''''(()))))****++++,,,,,-----.....//////000000111111122222222333333344444444444555555555666666666667777777777777778888888888888888899999999999999999999::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<=============ÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÎÎÎÎÎĎĎĎĎĎĎĎĐĐĐĐĐĐŃŃŃŃŃŇŇŇŇŇÓÓÓÓÔÔÔÔÔŐŐŐŐÖÖÖÖ××××ŘŘŘŮŮŮÚÚÚÚŰŰŰÜÜÝÝÝŢŢŢßßŕŕŕáááââăăăääĺĺććçççččééęęëëěěííîďďđđńńňňóóôôőöö÷÷řřůúúűűüýýţţ˙  !!!"""##$$$%%%%&&&'''(((())))****+++++,,,,-----.....//////0000000111112222222223333333444444444555555555555666666666667777777777777888888888888888999999999999999999999:::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<=============ÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘËËËËËËËËĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÎÎÎÎÎÎÎÎĎĎĎĎĐĐĐĐĐĐĐŃŃŃŃŃŇŇŇŇŇÓÓÓÓÓÔÔÔŐŐŐŐŐÖÖÖÖ×××ŘŘŘŘŮŮŮÚÚÚŰŰŰÜÜÜÝÝÝŢŢŢßßŕŕŕááâââăăääĺĺĺććççččééęęëëěěííîîďďđńńňňóóôôőöö÷÷řřůúúűűüýýţţ˙  !!!"""###$$$%%%&&&''''((())))*****+++,,,,,-----.....///////0000111111112222223333333333444444445555555555666666666666677777777777777888888888888888999999999999999999999:::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<=============ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘËËËËËËËËËËËĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÎÎÎÎÎÎĎĎĎĎĎĎĎĐĐĐĐŃŃŃŃŃŃŃŇŇŇŇÓÓÓÓÔÔÔÔÔŐŐŐÖÖÖÖ××××ŘŘŘŮŮŮŮÚÚÚŰŰŰÜÜÝÝÝÝŢŢŢßßŕŕáááâââăäääĺĺććççčččééęëëěěííîîďďđđńńňóóôôőőö÷÷řřůúúűűüýýţţ˙  !!!""""##$$$%%%&&&&'''(((())))***+++++,,,,----.......////0000000111111222222223333333444444444445555555566666666666677777777777777788888888888888888999999999999999999999:::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<================ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÎÎÎÎÎÎĎĎĎĎĎĎĐĐĐĐĐĐĐŃŃŃŃŇŇŇŇŇÓÓÓÓÔÔÔÔŐŐŐŐŐÖÖÖ×××ŘŘŘŘŮŮŮŮÚÚŰŰŰÜÜÜÜÝÝŢŢŢßßŕŕŕááâââăăäääĺćććççččééęęëëěěííîîďđđńńňňóôôőőö÷÷řřůůúűűüüýţţ˙  !!!""####$$$%%&&&&''''((()))*****++++,,,,-----....///////000000111111222222223333333444444444555555555556666666666777777777777788888888888888888899999999999999999999::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<=======================ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘËËËËËËËĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÎÎÎÎÎÎÎÎĎĎĎĎĎĐĐĐĐĐĐŃŃŃŃŃŃŇŇŇÓÓÓÓÓÔÔÔÔÔŐŐŐÖÖÖÖ××××ŘŘŘŮŮŮÚÚÚŰŰŰŰÜÜÝÝÝŢŢŢßßŕŕáááââăăăääĺĺććççččééęęëëěěííîîďđđńńňňóóôőőöö÷řřůůúűűüüýţţ˙  !!!"""##$$$$%%%&&&'''(((())))***+++++,,,,,---......//////00000111111112222233333333334444444555555555556666666666667777777777778888888888888899999999999999999999::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<=============================ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘËËËËËËËËËËĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÎÎÎÎÎĎĎĎĎĎĎĎĐĐĐĐĐŃŃŃŃŃŇŇŇŇŇŇÓÓÓÔÔÔÔŐŐŐŐŐÖÖÖ×××ŘŘŘŘŮŮŮÚÚÚŰŰŰÜÜÜÝÝÝŢŢßßŕŕŕáááââăăääĺĺĺćççčččééęëëěěííîîďďđđńňňóóôőőöö÷řřůůúűűüüýţţ˙  !!"""###$$$%%%&&&''''((()))*****++++,,,------...../////000000011111222222223333333444444444455555555666666666666677777777777777888888888888889999999999999999999:::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<==================================ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘËËËËËËËËËĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÎÎÎÎÎÎĎĎĎĎĎĐĐĐĐĐĐĐŃŃŃŃŇŇŇŇŇÓÓÓÓÓÔÔÔŐŐŐŐÖÖÖÖ××××ŘŘŮŮŮÚÚÚÚŰŰŰÜÜÝÝÝŢŢŢßßŕŕŕááââăăăääĺĺććççččééęęëëěěíîîďďđđńńňóóôôőöö÷÷řůůúűűüüýţţ˙  !!!"""##$$$%%%%&&&''(((())))****+++,,,,,-----....///////000001111112222222233333334444444445555555555666666666777777777777777788888888888888889999999999999999999:::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<==================================ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÎÎÎÎÎÎÎÎĎĎĎĎĎĐĐĐĐĐŃŃŃŃŃŃŇŇŇŇÓÓÓÓÔÔÔÔŐŐŐŐŐÖÖ××××ŘŘŘŮŮŮŮÚÚŰŰŰÜÜÜÝÝÝŢŢŢßßŕŕáááââăăääĺĺćććçččééęęëëěěííîîďđđńńňóóôôőöö÷÷řůůúúűüüýţţ˙  !!!"""###$$$%%&&&&'''(((())*****++++,,,,----....../////000001111111122222333333333344444455555555555566666666667777777777778888888888888888899999999999999999999:::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<==================================ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘËËËËËËËËËĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÎÎÎÎÎĎĎĎĎĎĎĐĐĐĐĐĐŃŃŃŃŇŇŇŇŇÓÓÓÓÓÔÔÔŐŐŐŐÖÖÖÖ×××ŘŘŘŮŮŮÚÚÚŰŰŰÜÜÜÝÝŢŢŢßßŕŕŕááââăăäääĺĺććççččééęęëěěííîîďđđńńňňóôôőőö÷÷řůůúúűüüýţţ˙  !!!""###$$$%%%&&&'''((())))****+++,,,,,-----....//////000000111112222222233333334444444445555555566666666666666777777777778888888888888899999999999999999999::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<==================================ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘËËËËËËËËËËĚĚĚĚĚĚÍÍÍÍÍÍÍÎÎÎÎÎÎÎĎĎĎĎĎĐĐĐĐĐĐŃŃŃŃŃŇŇŇÓÓÓÓÓÔÔÔÔŐŐŐŐÖÖÖ×××ŘŘŘŘŮŮŮÚÚÚŰŰÜÜÜÝÝÝŢŢŢßßŕŕáááââăăääĺĺććççččééęęëëěěíîîďďđđńňňóóôőőö÷÷řřůúúűüüýţţ˙  !!!"""###$$%%%&&&''''((()))****++++,,,,,---.....//////000001111111222222233333344444444445555555566666666666677777777777777888888888888899999999999999999::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<========================================ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘËËËËËËËĚĚĚĚĚĚĚĚĚÍÍÍÍÍÎÎÎÎÎÎÎĎĎĎĎĎĎĐĐĐĐŃŃŃŃŃŇŇŇŇŇÓÓÓÓÔÔÔŐŐŐŐŐÖÖÖ××××ŘŘŮŮŮÚÚÚÚŰŰŰÜÜÝÝÝŢŢßßŕŕŕááââăăäääĺĺććççčééęęëëěěííîďďđđńňňóóôőőöö÷řřůúúűüüýţţ˙  !!"""##$$$%%%%&&&''(((()))*****+++,,,,-----.....////00000011111112222233333333344444445555555555566666666677777777777777888888888888888899999999999999999::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<===============================================ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘËËËËËËËËĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÎÎÎÎÎĎĎĎĎĎĐĐĐĐĐĐŃŃŃŃŇŇŇŇÓÓÓÓÓÔÔÔÔŐŐŐŐÖÖ××××ŘŘŘŘŮŮŮÚÚŰŰŰÜÜÜÝÝŢŢŢßßŕŕááâââăăääĺĺććççččééęęëěěííîîďđđńńňóóôőőöö÷řřůúúűüüýţţ˙  !!!""###$$$%%&&&''''(((())****++++,,,,,----....//////00000111112222222233333334444444455555555556666666666677777777778888888888888888889999999999999999999::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<=====================================================ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘËËËËËËËËËËĚĚĚĚĚĚÍÍÍÍÍÍÍÎÎÎÎÎÎÎĎĎĎĎĐĐĐĐĐŃŃŃŃŃŃŇŇŇŇÓÓÓÓÔÔÔŐŐŐŐÖÖÖÖ×××ŘŘŘŮŮŮÚÚÚŰŰŰÜÜÜÝÝŢŢßßŕŕŕááâââăăäĺĺććççččééęęëëěěíîîďđđńńňóóôôőöö÷řřůúúűüüýţţ˙  !!""###$$$%%%&&&'''((())))****+++,,,,----....../////00001111111222222233333344444444445555555666666666666677777777777888888888888899999999999999999999:::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<======================================================ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘËËËËËËËĚĚĚĚĚĚĚĚĚÍÍÍÍÍÎÎÎÎÎÎĎĎĎĎĎĎĐĐĐĐĐŃŃŃŃŇŇŇŇŇÓÓÓÓÔÔÔÔŐŐŐÖÖÖ××××ŘŘŘŮŮŮÚÚÚŰŰÜÜÜÝÝŢŢŢßßŕŕŕááââăăääĺĺććççččéęęëëěěíîîďďđđńňňóôôőöö÷řřůúúűüüýţţ˙  !!!""###$$%%%&&&'''(((()))***++++,,,,-----..../////000000111111222223333333334444444555555555566666666677777777777777788888888888899999999999999999::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<======================================================ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘËËËËËËËĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÎÎÎÎÎĎĎĎĎĐĐĐĐĐĐŃŃŃŃŃŇŇŇŇÓÓÓÔÔÔÔŐŐŐŐÖÖÖÖ×××ŘŘŮŮŮÚÚÚŰŰŰÜÜÜÝÝÝŢŢßßŕŕááââăăăääĺĺćççččééęęëëěííîďďđđńňňóôôőöö÷÷řůůúűűüýýţ˙  !!"""###$$$%%%&&&''((())))****++++,,,----.....//////00001111122222222333333344444445555555555566666666777777777777778888888888888889999999999999999:::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<======================================================ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘËËËËËËËËËĚĚĚĚĚĚÍÍÍÍÍÍÍÎÎÎÎÎÎĎĎĎĎĎĐĐĐĐŃŃŃŃŃŇŇŇŇŇÓÓÓÓÔÔÔÔŐŐŐÖÖÖ×××ŘŘŘŘŮŮŮÚÚŰŰŰÜÜÝÝÝŢŢßßŕŕŕááââăăääĺĺććççččéęęëëěííîîďđđńňňóóôőőö÷÷řůůúűűüýýţ˙  !!"""##$$$%%&&&''''((()))***++++,,,,-----.....////0000011111122222223333334444444445555555666666666666777777777788888888888888888999999999999999999:::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<==========================================================ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘËËËËËËËĚĚĚĚĚĚĚĚĚÍÍÍÍÍÎÎÎÎÎĎĎĎĎĎĎĐĐĐĐĐĐŃŃŃŃŇŇŇÓÓÓÓÔÔÔÔÔŐŐŐÖÖÖÖ××ŘŘŘŮŮŮÚÚÚŰŰÜÜÜÝÝÝŢŢßßŕŕááââăăääĺĺććççččéęęëëěěíîîďđđńńňóóôőőö÷÷řůůúűűüýýţ˙  !!"""###$$%%%&&&'''(())))***+++++,,,,---....//////000000111112222233333333344444445555555566666666666777777777777888888888888899999999999999999999:::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<================================================================ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘËËËËËËËĚĚĚĚĚĚĚÍÍÍÍÍÍÍÎÎÎÎÎÎĎĎĎĎĐĐĐĐĐŃŃŃŃŃŇŇŇŇÓÓÓÓÔÔÔŐŐŐÖÖÖÖ×××ŘŘŘŮŮŮÚÚÚŰŰÜÜÝÝÝŢŢßßŕŕŕááââăăääĺĺćççččééęęëěěííîďďđńńňňóôôőöö÷řůůúűűüýýţ˙  !!"""##$$%%%&&&'''((())))***+++,,,,----...../////000011111122222223333333444444555555555556666666677777777777777788888888888999999999999999999::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<=======================================================================ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘËËËËËËËËËËĚĚĚĚĚÍÍÍÍÍÍÎÎÎÎÎÎÎĎĎĎĎĎĐĐĐĐŃŃŃŃŇŇŇŇÓÓÓÓÓÔÔÔÔŐŐŐÖÖÖ×××ŘŘŮŮŮÚÚÚŰŰŰÜÜÝÝÝŢŢßßŕŕŕáââăăääĺĺććççččéęęëëěííîîďđđńňňóôôőöö÷řřůúűűüýýţ˙  !!"""##$$$%%%&&&''((()))***++++,,,,,----....////0000011111112222223333344444444455555555666666666677777777777788888888888888899999999999999:::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<==========================================================================ÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘËËËËËËËËĚĚĚĚĚĚĚĚÍÍÍÍÍÎÎÎÎÎĎĎĎĎĎĐĐĐĐĐĐŃŃŃŃŇŇŇŇÓÓÓÔÔÔÔŐŐŐÖÖÖÖ×××ŘŘŘŮŮŮÚÚŰŰŰÜÜÝÝŢŢßßŕŕŕááââăăääĺĺććçččééęëëěěíîîďđđńňňóôôőöö÷řřůúűűüýýţ˙  !!""##$$$%%&&&'''((())))***++++,,,----....//////0000011111222223333333344444444555555566666666666677777777788888888888888888999999999999999:::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<=====================================================================>>>>>ÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘËËËËËËĚĚĚĚĚĚĚÍÍÍÍÍÍÍÎÎÎÎÎĎĎĎĎĎĐĐĐĐŃŃŃŃŇŇŇŇŇÓÓÓÓÔÔÔÔŐŐŐÖÖÖ××ŘŘŘŮŮŮÚÚŰŰŰÜÜÜÝÝŢŢßßŕŕŕááââăăäĺĺććçččééęęëěěííîďďđńńňóóôőöö÷řřůúúűüýýţ˙  !!""###$$$%%&&&'''(()))***++++,,,,-----....////000001111122222223333333444444555555555566666666677777777777788888888888899999999999999999999:::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<=============================================================>>>>>>>>>>>>>ÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘËËËËËËËËËĚĚĚĚĚÍÍÍÍÍÍÎÎÎÎÎÎĎĎĎĎĎĐĐĐĐĐŃŃŃŃŇŇŇÓÓÓÓÔÔÔÔŐŐŐÖÖÖ×××ŘŘŘŮŮŮÚÚÚŰŰÜÜÝÝÝŢŢßßŕŕááââăăääĺććççččééęëëěííîďďđńńňóóôőőö÷řřůúúűüýýţ˙  !!"""##$$%%%&&&'''((()))***++++,,,,---..../////000001111112222223333344444444455555555666666667777777777777778888888888999999999999999999:::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<=========================================================>>>>>>>>>>>>>>>>>>ÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘËËËËËËËËĚĚĚĚĚĚĚĚÍÍÍÍÍÎÎÎÎÎĎĎĎĎĐĐĐĐĐŃŃŃŃŃŇŇŇŇÓÓÓÓÔÔÔŐŐŐÖÖÖ×××ŘŘŮŮŮÚÚÚŰŰÜÜÜÝÝŢŢßßŕŕŕááââăääĺĺććççčééęëëěííîîďđđńňňóôőőö÷÷řůúúűüýýţ˙  !!""###$$%%%&&&''((()))***+++,,,,----...../////00001111122222333333334444444455555566666666666777777777778888888888888899999999999999::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<================================================================>>>>>>>>>>>>>>>>>>ÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘËËËËËĚĚĚĚĚĚĚÍÍÍÍÍÍÎÎÎÎÎÎĎĎĎĎĎĐĐĐĐŃŃŃŇŇŇŇÓÓÓÓÔÔÔÔŐŐŐÖÖÖ×××ŘŘŘŮŮŮÚÚŰŰŰÜÜÝÝŢŢßßŕŕááââăăääĺććççčééęęëěěíîîďđđńňňóôôőö÷÷řůúúűüýýţ˙  !!""##$$$%%&&&'''((()))***++++,,,,----...////000001111112222223333333444445555555555666666666677777777788888888888888889999999999999:::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<======================================================================>>>>>>>>>>>>>>>>>>ÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘËËËËËËËËĚĚĚĚĚÍÍÍÍÍÎÎÎÎÎÎĎĎĎĎĎĐĐĐĐĐŃŃŃŃŃŇŇŇÓÓÓÔÔÔÔŐŐŐÖÖÖ××ŘŘŘŮŮŮÚÚŰŰŰÜÜÝÝŢŢßßŕŕŕáââăăääĺĺććçččééęëëěííîďďđńňňóôôőöö÷řůůúűüüýţ˙  !!""##$$$%%&&&'''(()))***++++,,,---...../////000001111112222233333444444445555555555666666777777777777788888888888899999999999999999:::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<=============================================================================>>>>>>>>>>>>>>>>>>ÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘËËËËËËËËĚĚĚĚĚĚĚĚÍÍÍÍÍÎÎÎÎÎĎĎĎĎĐĐĐĐŃŃŃŃŃŇŇŇŇÓÓÓÔÔÔÔŐŐŐÖÖÖ×××ŘŘŘŮŮÚÚÚŰŰÜÜÝÝŢŢßßŕŕááââăăääĺĺćççččéęęëěěíîďďđńńňóóôőöö÷řůůúűüüýţ˙  !!""##$$%%%&&'''((()))***++++,,,----.....////000011111222223333333344444444555555666666666777777777777788888888899999999999999999999::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<===========================================================================>>>>>>>>>>>>>>>>>>>>ÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘËËËËËĚĚĚĚĚĚĚÍÍÍÍÍÍÎÎÎÎÎĎĎĎĎĎĐĐĐĐŃŃŃŃŇŇŇŇÓÓÓÔÔÔŐŐŐÖÖÖ×××ŘŘŘŮŮÚÚÚŰŰÜÜÝÝŢŢßßŕŕááââăăääĺĺćççččéęęëěěíîîďđđńňóóôőöö÷řůůúűüüýţ˙  !!""##$$%%%&&'''((()))***+++,,,----....////000001111122222233333334444455555555666666666667777777778888888888888999999999999999:::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<===================================================================>>>>>>>>>>>>>>>>>>>>>>>>>>>>ÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘËËËËËËËĚĚĚĚĚÍÍÍÍÍÎÎÎÎÎĎĎĎĎĎĐĐĐĐĐŃŃŃŃŇŇŇŇÓÓÓÔÔÔÔŐŐŐÖÖÖ××ŘŘŘŮŮŮÚÚŰŰÜÜÝÝŢŢŢßŕŕááââăăääĺććçččééęëëěííîďđđńňňóôőőö÷řřůúűüüýţ˙  !!!""##$$%%&&&'''(()))***++++,,,----..../////000001111122222333334444444555555555666666666777777777888888888888888999999999999:::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<===========================================================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>ÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČÉÉÉÉÉÉÉĘĘĘĘĘĘĘËËËËËËËËĚĚĚĚĚĚĚÍÍÍÍÍÎÎÎÎÎĎĎĎĎĐĐĐĐŃŃŃŇŇŇŇÓÓÓÔÔÔÔŐŐŐÖÖÖ××ŘŘŘŮŮŮÚÚŰŰÜÜÜÝÝŢŢßßŕŕááââăääĺĺććçččéęęëěěíîďďđńňňóôőőö÷řřůúűüüýţ˙  !!""###$$%%&&&'''(()))***++++,,,----...////00001111122222333333344444444555555566666667777777777777888888888889999999999999999::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<=======================================================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>ÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘËËËËËËĚĚĚĚĚĚĚÍÍÍÍÍÎÎÎÎÎÎĎĎĎĎĐĐĐĐĐŃŃŃŃŇŇŇÓÓÓÓÔÔÔŐŐŐÖÖÖ××ŘŘŘŮŮÚÚŰŰÜÜÜÝÝŢŢßŕŕááââăăäĺĺććçččéęęëěěíîďďđńńňóôôőö÷řřůúűüüýţ˙  !!""###$$%%&&'''(()))***+++,,,,---..../////000011111122222333333344444455555566666666667777777777778888888899999999999999999999::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<========================================================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>ÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘËËËËËËĚĚĚĚĚÍÍÍÍÍÎÎÎÎÎĎĎĎĎĐĐĐĐĐŃŃŃŃŇŇŇÓÓÓÓÔÔÔŐŐŐÖÖÖ××ŘŘŘŮŮÚÚŰŰŰÜÜÝÝŢŢßßŕŕááââăääĺĺćççčééęëëěíîîďđđńňóôôőö÷÷řůúűűüýţ˙  !!""##$$$%%&&'''(()))***+++,,,,---..../////00001111122222333334444445555555556666666666777777778888888888889999999999999999::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<===============================================================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>ÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘËËËËËËËĚĚĚĚĚĚÍÍÍÍÍÍÎÎÎÎÎĎĎĎĎĐĐĐĐŃŃŃŇŇŇŇÓÓÓÔÔÔŐŐŐÖÖÖ×××ŘŘŮŮÚÚÚŰŰÜÜÝÝŢŢßŕŕááââăăäĺĺććçččéęęëěííîďđđńňóóôőöö÷řůúűűüýţ˙  !!""##$$%%%&&''((()))***+++,,,----...////000011111222222333333444444455555555666666677777777778888888888888899999999999:::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<==================================================================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>ÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČÉÉÉÉÉÉÉÉĘĘĘĘĘĘËËËËËËĚĚĚĚĚĚÍÍÍÍÍÍÎÎÎÎÎĎĎĎĎĐĐĐĐĐŃŃŃŇŇŇŇÓÓÓÔÔÔŐŐŐÖÖ×××ŘŘŮŮÚÚÚŰŰÜÜÜÝÝŢŢßŕŕááââăääĺććçččééęëěěíîîďđńňňóôőöö÷řůúűűüýţ˙  !!""###$$%%%&&''((())***+++,,,----.../////00001111122222233333344444455555566666666777777777777888888888889999999999999:::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<==================================================================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>ÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘËËËËËĚĚĚĚĚÍÍÍÍÍÎÎÎÎÎĎĎĎĎĐĐĐĐŃŃŃŃŇŇŇÓÓÓÓÔÔÔŐŐŐÖÖÖ××ŘŘŘŮŮÚÚŰŰÜÜÝÝŢŢßßŕŕáââăăäĺĺćççčééęëěěíîîďđńńňóôőőö÷řůúúűüýţ˙  !!""##$$%%&&'''(()))***+++,,,,---....////000011111222223333344444555555556666666666777777777788888888899999999999999999::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<================================================================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>ÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘËËËËËËĚĚĚĚĚĚÍÍÍÍÍÍÎÎÎÎĎĎĎĎĐĐĐĐŃŃŃŃŇŇŇÓÓÓÔÔÔŐŐŐÖÖ××ŘŘŘŮŮÚÚŰŰŰÜÜÝÝŢŢßŕŕááâăăääĺććçččéęęëěííîďđńńňóôőőö÷řůúúűüýţ˙  !!""##$$$%%&&'''(())***+++,,,---....////0000111122222233333344444455555555666666667777777788888888888899999999999999999::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<========================================================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>ÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČÉÉÉÉÉÉÉĘĘĘĘĘĘËËËËËËĚĚĚĚĚĚÍÍÍÍÍÍÎÎÎÎĎĎĎĎĐĐĐĐŃŃŃŃŇŇŇÓÓÓÔÔÔŐŐŐÖÖÖ×××ŘŘŮŮÚÚŰŰÜÜÝÝŢŢßßŕŕááâăăäĺĺćççčééęëěěíîďđđńňóôôőö÷řůůúűüýţ˙  !!""##$$%%&&''((()))***+++,,,---....////00001111222222333333444444555555666666677777777778888888888888999999999999:::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<=======================================================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>ÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘËËËËËĚĚĚĚĚÍÍÍÍÍÎÎÎÎÎĎĎĎĎĐĐĐĐŃŃŃŃŇŇŇÓÓÓÔÔÔŐŐÖÖÖ××ŘŘŮŮÚÚÚŰŰÜÜÝÝŢŢßŕŕááââăääĺććçčééęëëěíîîďđńňóóôőö÷řřůúűüýţ˙  !!""##$$%%%&&''(()))**+++,,,---....////000011111222223333344444555555566666666677777777777888888888899999999999:::::::::::::::::::::::;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<=====================================================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>ÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇČČČČČČČČČÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘËËËËËËËĚĚĚĚĚÍÍÍÍÍÎÎÎÎÎĎĎĎĐĐĐĐŃŃŃŇŇŇÓÓÓÔÔÔŐŐŐÖÖÖ××ŘŘŘŮŮÚÚŰŰÜÝÝŢŢßßŕŕááâăääĺĺćççčéęęëěíîîďđńńňóôőö÷řřůúűüýţ˙  !!""#$$%%&&'''(()))***+++,,,---...////0001111122222333334444444555555566666666677777777788888888999999999999999:::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<========================================================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>ÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČÉÉÉÉÉÉÉĘĘĘĘĘĘËËËËËËËĚĚĚĚĚÍÍÍÍÍÎÎÎÎÎĎĎĎĐĐĐĐŃŃŃŃŇŇŇŇÓÓÓÔÔÔŐŐÖÖÖ××ŘŘŮŮÚÚŰŰŰÜÜÝŢŢßŕŕááââăääĺćççčééęëěěíîďđńńňóôőöö÷řůúűüýţ˙  !!"##$$$%%&&''(()))**+++,,,----....////0001111122222333334444444555555666666677777777888888888889999999999999999::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<========================================================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>ÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČÉÉÉÉÉÉÉĘĘĘĘĘĘĘËËËËËĚĚĚĚĚÍÍÍÍÍÎÎÎÎÎĎĎĎĎĐĐĐĐŃŃŃŇŇŇÓÓÔÔÔŐŐÖÖÖ×××ŘŘŮŮÚÚŰŰÜÜÝÝŢŢßŕŕáââăăäĺĺćçččéęëëěíîďđđńňóôőőö÷řůúűüýţ˙  !!""##$$%%&&''((()))**+++,,---...////0000111112222233333444445555555666666677777777778888888888889999999999999::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<========================================================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>ÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘËËËËËËĚĚĚĚĚÍÍÍÍÍÎÎÎÎĎĎĎĐĐĐĐŃŃŃŇŇŇÓÓÓÔÔÔŐŐŐÖÖ××ŘŘŮŮÚÚÚŰŰÜÜÝŢŢßßŕŕáââăäĺĺććçčéęęëěííîďđńňóôőőö÷řůúűüýţ˙  !!"##$$%%%&&''(())***+++,,,---...////00011112222233333444444555555566666666777777777788888888889999999999:::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<========================================================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>ÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇČČČČČČČČÉÉÉÉÉÉÉĘĘĘĘĘĘĘËËËËËËĚĚĚĚĚÍÍÍÍÍÎÎÎÎĎĎĎĎĐĐĐĐŃŃŃŃŇŇŇÓÓÓÔÔŐŐÖÖÖ××ŘŘŘŮŮÚÚŰÜÜÝÝŢŢßŕŕáââăăäĺććçččéęëěííîďđńňóóôőö÷řůúűüýţ˙  !!""##$%%&&'''(()))**++,,,---....////00001111222223333344444455555556666666777777778888888889999999999999::::::::::::::::::::;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<=========================================================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>ÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇČČČČČČČÉÉÉÉÉÉÉĘĘĘĘĘĘĘËËËËËĚĚĚĚĚÍÍÍÍÍÎÎÎÎÎĎĎĎĐĐĐĐŃŃŇŇŇÓÓÓÔÔÔŐŐŐÖÖ××ŘŘŮŮÚÚŰŰÜÜÝÝŢßßŕŕáââăääĺćçččéęęëěíîďđđńňóôőö÷řůúűüýţ˙  !""##$$%%&&''(())***+++,,,---..////0001111122222333334444455555556666666777777788888888888999999999999999::::::::::::::::;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<========================================================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>ÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČÉÉÉÉÉÉÉÉĘĘĘĘĘĘËËËËËĚĚĚĚĚÍÍÍÍÎÎÎÎĎĎĎĐĐĐĐŃŃŃŇŇŇŇÓÓÓÔÔŐŐÖÖ×××ŘŘŮŮÚÚŰÜÜÜÝÝŢßŕŕááâăăäĺććçčéęęëěíîďđđńňóôőö÷řůúűüýţ˙  !""###$%%&&''((())**++,,,----...////00011112222333334444455555566666666777777777888888888889999999999999:::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<======================================================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>ÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇČČČČČČČČČÉÉÉÉÉÉÉÉĘĘĘĘĘĘËËËËËĚĚĚĚĚÍÍÍÍÎÎÎÎÎĎĎĎĎĐĐĐĐŃŃŃŇŇŇÓÓÔÔŐŐŐÖÖÖ××ŘŘŮÚÚÚŰŰÜÝÝŢŢßŕŕáâăăääĺćçččéęëěííîďđńňóôőö÷řůúűüýţ˙  !!""#$$%%%&''(()))***++,,---...////000011111222233333444445555556666666677777777788888888889999999999::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<=================================================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>ÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇČČČČČČČČÉÉÉÉÉÉÉĘĘĘĘĘĘĘËËËËËĚĚĚĚĚÍÍÍÍÍÎÎÎÎĎĎĎĐĐĐŃŃŃŇŇŇÓÓÓÔÔÔŐŐÖÖ××ŘŘŘŮŮÚŰŰÜÜÝÝŢßŕŕááâăääĺććçčéęęëěíîďđńňóôőö÷řůúűüýţ˙  !""##$$%&&'''(())**+++,,,---...///000111122222333334444455555556666666777777778888888899999999999::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<=============================================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>ÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇČČČČČČČČÉÉÉÉÉÉÉĘĘĘĘĘĘËËËËËĚĚĚĚÍÍÍÍÎÎÎĎĎĎĐĐĐĐŃŃŃŃŇŇŇÓÓÔÔŐŐŐÖÖÖ×ŘŘŮŮÚÚŰŰÜÝÝŢŢßŕŕáââăäĺććçčéęęëěíîďđńňóôôőö÷řůúűüýţ  !!""#$$%%&&''()))***++,,---....////000111222233334444455555566666667777777788888888889999999999999::::::::::::::::::;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<=============================================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>ÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇČČČČČČČČČÉÉÉÉÉÉĘĘĘĘĘĘËËËËËĚĚĚĚÍÍÍÍÎÎÎÎÎĎĎĎĎĐĐĐŃŃŃŇŇÓÓÓÔÔÔŐŐÖÖ××ŘŘŮŮÚÚŰÜÜÜÝŢßßŕŕáâăăäĺćççčéęëěíîďđđńňóôőö÷řůúűüýţ  !"###$%%&&''(())**+++,,,--...///000011111222233334444455555566666677777777788888888889999999999999::::::::::::::;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<==============================================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>ŔŔŔÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇČČČČČČČČČÉÉÉÉÉÉĘĘĘĘĘĘËËËËËËĚĚĚĚĚÍÍÍÍÍÎÎÎÎĎĎĐĐĐŃŃŃŇŇŇŇÓÓÔÔŐŐÖÖÖ××ŘŘŮÚÚÚŰÜÜÝŢŢßŕŕáââăäĺĺćçčéęëěěíîďđńňóôőö÷řůúűüýţ  !!"##$%%%&''(()))**++,,----...///00111122222333334444445555556666667777777778888888889999999999:::::::::::::;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<=====================================================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>??ŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇČČČČČČČČÉÉÉÉÉÉÉĘĘĘĘĘĘËËËËËĚĚĚĚÍÍÍÎÎÎÎĎĎĎĐĐĐĐŃŃŃŇŇÓÓÔÔÔÔŐŐÖÖ×ŘŘŘŮŮÚŰŰÜÜÝŢßßŕŕáâăăäĺćçčééęëěíîďđńňóôőö÷řůúűüýţ  !"##$$%&&'''())**++++,,--...////00011112223333444445555556666666777777778888888889999999999::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<=========================================================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>??????????????ŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇČČČČČČČČÉÉÉÉÉÉĘĘĘĘĘËËËËĚĚĚĚÍÍÍÍÎÎÎÎÎĎĎĎĐĐĐŃŃŇŇŇÓÓÓÔÔŐŐÖÖÖ××ŘŮŮÚÚŰŰÜÝÝŢßŕŕááâăäĺćççčéęëěíîďđńňóôőö÷řůúűüýţ  !""#$$%%&&'(()))**++,,,---..///00011111222233334444555556666667777777788888888899999999999::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<=====================================================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>??????????????????????????ŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇČČČČČČČČÉÉÉÉÉÉĘĘĘĘĘËËËËËĚĚĚĚĚÍÍÍÍÍÎÎÎĎĎĐĐĐĐŃŃŃŇŇŇÓÔÔÔŐŐŐÖ××ŘŘŮŮÚŰŰÜÜÝŢŢßŕŕáâăäĺĺćçčééëěěíîďđńňôôö÷řůúűüýţ  !!"##$$%&&''(()***+++,---...////00111222223333344444555556666667777777788888888899999999999::::::::::::::::;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<=============================================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>?????????????????????????????????????ŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇČČČČČČČČÉÉÉÉÉÉÉĘĘĘĘĘĘĘËËËËËĚĚĚĚÍÍÍÎÎÎĎĎĎĎĐĐĐĐŃŃŇŇÓÓÓÔÔŐŐÖÖ××ŘŘŮÚÚÚŰÜÝÝŢßŕŕáâăăäĺććçčéęëěíîďđńňóôő÷řůúűüýţ  !""#$%%%&''(())**++,,,--..////000011122233334444455555556666666777777778888888889999999999:::::::::::::;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<=====================================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>???????????????????????????????????????????ŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇČČČČČČČČÉÉÉÉÉÉÉĘĘĘĘĘËËËĚĚĚĚÍÍÍÍÎÎÎÎÎĎĎĐĐĐŃŃŃŇŇŇÓÓÔŐŐŐÖÖ×ŘŘŘŮŮÚŰŰÜÝŢŢßŕááâăääĺćçčéęëěíîďđńňóôőöřůúűüýţ  !!"#$$%&&'''())***+,,---...///001111122223333444555556666666777777778888888899999999999::::::::::::;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<==================================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>???????????????????????????????????????????ŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇČČČČČČČÉÉÉÉÉĘĘĘĘĘËËËËĚĚĚĚĚÍÍÍÍÍÎÎĎĎĎĐĐĐĐŃŃŇŇÓÓÓÔÔŐŐÖÖ××ŘŮŮÚÚŰÜÜÝŢßßŕáââăäĺćçččéëëěîďđńňóôőö÷ůúűüýţ  !"##$%%&&'(())**++,,,--..////000112222233333444455555666667777777888888899999999999:::::::::::::;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<=======================================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>???????????????????????????????????????????ŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇČČČČČČČÉÉÉÉÉĘĘĘĘĘĘĘËËËËËĚĚĚĚÍÍÍÎÎÎĎĎĎĎĐĐŃŃŇŇŇŇÓÓÔŐŐŐÖÖ×ŘŘŮŮÚŰŰÜÝÝŢŢŕŕáâăääĺćçčéęëěíîďđňóôőö÷řúűüýţ !!""#$$%&&''())***+,,----..//00001112223333444445555555666667777777888888899999999999:::::::::::::;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<==============================================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>???????????????????????????????????????????ŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇČČČČČČČČÉÉÉÉÉÉÉĘĘĘĘĘËËËËĚĚĚÍÍÍÍÎÎÎÎĎĎĐĐĐŃŃŃŃŇÓÓÔÔÔŐŐÖ×××ŘŮÚÚŰÜÜÝÝŢßŕáââăäĺćçčéęëěíîďđńňóőö÷řůűüýţ  !""##$%%&'((()**+++,,-....///00111122223334444555556666666777777778888888899999999999:::::::::::::;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<====================================================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>???????????????????????????????????????????ŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇČČČČČČČČÉÉÉÉĘĘĘĘËËËËĚĚĚĚĚÍÍÍÍÎÎĎĎĎĎĐĐĐŃŃŇŇÓÓÓÔŐŐÖÖÖ×ŘŘŮÚŰŰŰÜÝŢŢŕŕáâăăĺĺćččéëëíîďđńňóôö÷řůűüýţ !!"#$$$%&''()))**+,,,--..///000011222233333444455556666777777778888888889999999999:::::::::::::;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<===================================================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>???????????????????????????????????????????ŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇČČČČČČÉÉÉÉĘĘĘĘĘĘËËËËËĚĚĚĚÍÍÎÎÎÎĎĎĎĐĐŃŃŃŇŇŇÓÔÔÔŐŐÖ××ŘŮŮÚÚŰÜÜÝŢßŕááâăäĺćçčéęëěíîđńňóôő÷řůúüýţ  !"##$%%&&'(()**+++,---...//000111122333344444555555666677777788888889999999999::::::::::::;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<===========================================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>?????????????????????????????????????????????????ŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇČČČČČČÉÉÉÉÉÉÉĘĘĘĘĘĘËËËĚĚĚÍÍÍÍÎÎÎÎĎĐĐĐĐŃŃŇŇÓÓÓÔŐŐÖÖ×ŘŘŮŮÚŰŰÜÝÝŢßŕáâăäĺĺççéęëěíîďđňóôő÷řůúüýţ  !""#$$%&&''())**+,,,--..////0111122223334445555556666666777777888888999999999::::::::::::;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<===================================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>?????????????????????????????????????????????????????????????ŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇČČČČČČČČÉÉÉÉÉÉÉĘĘĘËËËĚĚĚĚĚÍÍÍÍÎÎĎĎĎĐĐĐŃŇŇŇŇÓÔÔŐŐÖ×××ŘŮÚÚŰÜÜÝŢßŕááâăäĺćçčéęëíîďđńóôőöřůúüýţ  !"##$%%&'((()**++,----.///000112222333334445556666666777777778888888999999999::::::::::::;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<============================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>?????????????????????????????????????????????????????????????????????????ŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇČČČČČČČÉÉÉÉĘĘĘĘËËËËËĚĚĚĚÍÍÎÎÎÎĎĎĐĐŃŃŃŃŇÓÓÔÔŐÖÖÖ×ŘŘŮÚŰŰÜÝÝŢßŕáâăääćçčéęëěîďđńňôőöřůúüýţ  !""#$$%&''()))*++,,-....//001111223333444445555666677777778888888899999999999:::::::::::::;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<================================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>?????????????????????????????????????????????????????????????????????????????????????ŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇČČČČČÉÉÉÉÉĘĘĘĘĘĘËËËĚĚĚÍÍÍÍÎÎÎĎĎĐĐĐĐŃŇŇÓÓÔÔŐŐÖ××ŘŘŮÚÚÜÜÝŢŢŕŕáâăäĺćçčęëěíîđńňóőö÷ůúűýţ !!"##%%&''(()**++,,--.////00111222233344455555566666777778888889999999999:::::::::::::;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<=======================================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>?????????????????????????????????????????????????????????????????????????????????????????????????ŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆÇÇÇÇÇČČČČČČÉÉÉÉÉÉÉĘĘĘĘËËĚĚĚĚĚÍÍÍÎÎĎĎĎĎĐĐŃŃŇŇÓÓÔÔÔÖÖÖ×ŘŮŮÚŰŰÝÝŢßŕáâăäĺćçčéęěíîďńňóôö÷ůúűýţ  !""$$%&&'()))+++,,--..//000011222333334455556666666777777888889999999::::::::::::;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<============================================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>???????????????????????????????????????????????????????????????????????????????????????????????????????????ŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆÇÇÇÇÇÇÇČČČČČČČČÉÉÉÉĘĘĘËËËËËĚĚĚĚÍÎÎÎÎÎĎĐĐĐŃŃŇŇÓÓÓÔŐŐÖ××ŘŮÚÚŰÜÜŢŢŕŕáâăäĺćčéęëěíďđňóôö÷řúűýţ !!##$%%&'(()**+,,,--..///011111233334444455566667777777788888889999999::::::::::;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<============================================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>???????????????????????????????????????????????????????????????????????????????????????????????????????????ŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇČČČČČÉÉÉĘĘĘĘĘĘËËËËĚĚÍÍÍÍÎÎĎĎĎĐĐŃŃŇŇŇÓÔÔŐÖÖ×ŘŘŮÚŰŰÜÝŢßŕááăäĺćçčéëěíîđńňôő÷řúűýţ  !"#$$%&''())*++,---..//00011222233444455555566677777888888888999999999::::::::::;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<========================================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>???????????????????????????????????????????????????????????????????????????????????????????????????????????ŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇČČČČÉÉÉÉÉÉĘĘĘĘĘËËĚĚĚĚÍÍÍÎÎĎĎĎĐŃŃŃŃŇÓÓÔÔŐŐÖ×ŘŘŮÚŰÜÜÝŢßŕáâăäććčéęěíîđńňôő÷řúűýţ  !"##$%&''()**++,,-..../000112223333445555566666677778888889999999999:::::::::::::;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<===============================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>???????????????????????????????????????????????????????????????????????????????????????????????????????????ŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆÇÇÇÇČČČČČČČÉÉÉÉÉÉĘĘËËËËĚĚĚĚÍÎÎÎÎĎĐĐĐĐŃŇŇŇÓÔÔŐÖÖ×ŘŮŮÚŰÜÝŢßŕáâăäĺćçéęëíîďńňóőöřůűüţ  !"#$%&&'())*++,---.////0111123333444455666666777777788889999999:::::::::::::;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<============================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>???????????????????????????????????????????????????????????????????????????????????????????????????????????ŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆÇÇÇÇÇÇČČČČČČČČÉÉÉĘĘĘĘËËËËĚĚÍÍÍÍÎĎĎĎĎĐŃŃŇŇÓÓÔÔŐÖÖ×ŘŮÚÚŰÜÝŢßŕáâăäĺçčéëěíďđňóőöřůűüţ  !"#$%%&'())*++,,--../000012222334444555566677777777888888999999:::::::::;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<===========================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>???????????????????????????????????????????????????????????????????????????????????????????????????????????ŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇČČČČÉÉÉÉĘĘĘĘĘĘËĚĚĚĚÍÍÎÎÎĎĎĐĐŃŃŃŇÓÓÔŐŐÖ××ŘŮÚŰÜÜÝŢŕŕââäĺćčéęěíîđńóôöřůűüţ !"##$%&'(()**+,,-...//001112233334555555666677778888888889999999:::::::;;;;;;;;;;;<<<<<<<<<<<<<<<<<<================================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>???????????????????????????????????????????????????????????????????????????????????????????????????????????ŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇČČČÉÉÉÉÉÉÉĘĘĘËËËĚĚĚÍÍÎÎÎÎĎĐĐĐŃŇŇŇÔÔÔŐÖ×ŘŘŮÚŰÜÝÝßŕáâăĺćçčęëěîđńňôö÷ůűüţ  ""#$%&''()*+++---.///011112233344455566666667778888899999999999::::::::;;;;;;;;;;;<<<<<<<<<<<<<<<<<=================================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????ŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆÇÇÇČČČČČČČÉÉÉÉĘĘĘËËËËĚÍÍÍÍÍÎĎĎĎĐŃŃŃŇÓÓÔŐŐÖ×ŘŮŮÚŰÜÝŢŕŕâăäĺçčéëěíďńňôő÷ůúüţ !"#$%&&'()**+,,-.../000122222344445556666777777788899999999:::::::::::::;;;;;;;;;;;<<<<<<<<<<<<<<<<<=================================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>?????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????ŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆÇÇÇÇÇÇČČČČČČÉÉĘĘĘĘĘĘËĚĚĚĚÍÍÎÎÎĎĐĐĐŃŇŇŇÔÔÔÖÖ×ŘŮÚŰŰÝÝßŕáâăäćčéęëíďđňóő÷ůúüţ  ""$$%&'())+++---.///01112233334555555667777778888889999::::::::::::;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<=================================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>?????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????ŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĆĆĆĆĆÇÇÇÇÇÇÇÇÇČČÉÉÉÉÉĘĘĘËËËĚĚĚÍÍÎÎÎĎĎĐĐŃŃŇÓÓÔŐŐ××ŘŮÚŰÜÜŢßŕáăäĺçčęëěîđńóő÷řúüţ  !##$%&'((**+,,-..//0011122333444555666667788888888899999:::::::;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<===============================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>?????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????ŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĆĆĆĆĆĆĆĆĆÇÇÇÇÇČČČČÉÉÉÉÉĘĘËËËËËÍÍÍÍÍĎĎĎĐĐŃŃŇÓÔÔŐÖÖŘŘŮÚŰÜÝŢŕŕâäĺćçéëěîđńóőöřúüţ !"#$%&''))*++,-..//00022222444445566666777788888999999999:::::;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<=============================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????ŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆÇÇČČČČČČČÉÉĘĘĘĘĘËËĚĚĚÍÍÎÎÎĐĐĐĐŇŇÓÓÔŐŐ××ŮŮÚÜÜŢßŕáăäćçčęëíďđňôöřúüţ  !##%&&((**+,,--////11122333445555566777777788999999999::::::::;;;;;;;;<<<<<<<<<<<<<<<<============================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????ŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆÇÇÇÇÇČČČČČÉÉÉĘĘĘĘËËĚĚĚÍÍÎÎĎĎĎĐŃŃŇÓÓÔŐÖÖŘŘÚŰŰÝŢŕŕâăĺćčęëíďđňôöřúüţ !"$$%''))*+,,-../000112233344555566677777888889999:::::::::::::;;;;;;;;<<<<<<<<<<<=========================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????ŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĆĆÇÇÇÇÇÇÇÇÇČÉÉÉÉÉÉÉËËËËËÍÍÍÍÎĎĎĐĐŃŇŇŇÔÔŐÖ×ŘŮÚŰÜÝßŕáâäćçéęěîđňôöřúüţ  "#$%&'()*++---.//0012222444446666666788888888899::::::::::;;;;;;;;;;;;;<<<<<<<<<<<=======================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????ŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĆĆĆĆĆĆĆÇÇÇÇČČČČÉÉÉÉĘĘĘËËĚĚĚÍÎÎÎÎĐĐŃŃŇÓÓŐŐÖŘŘŮÚŰÝŢßŕâăĺćčęěîđńóő÷ůűý  !"$%&'')**,,-..//11112333445556666777788889999999:::::;;;;;;;;;;;;;;;<<<<<<<<<<<<<<=======================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????ŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄĹĹĹĆĆĆĆĆĆĆĆĆĆÇČČČČČČČČĘĘĘĘĘËĚĚĚÍÍÎÎĎĎĐŃŃŇÓÔÔŐÖ×ŮŮÚÜÝßŕáâäćčęëíďńóő÷ůűý  "#%&&()*++,-../0011223334555557777777789999999999:::;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<=======================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????ŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄĹĹĹĹĹĹĹĹĆĆĆĆĆÇÇÇÇÇČČČČÉÉÉĘĘËËËËÍÍÍÍÎĎĐĐĐŇŇÓÓŐŐÖŘŘÚŰÜŢßŕâäĺçéęěîđňô÷ůűý  !#$%'')**,,--///01222244445566677778888899999::::::::;;;;;<<<<<<<<<<<<<<<<<<<<<============================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????ŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĆÇÇÇÇÇÇÇÇČÉÉÉÉÉĘĘËËĚĚĚÍÎÎÎĎĐŃŃŇÓÔÔÖ××ŮÚÜÝŢŕáăäćčéěîđňôöůűý !"#%&(()++,-../011123334455666667888888889::::::::::::;;;;;<<<<<<<<<<<<<<===============================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????ŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĆĆĆĆĆÇÇÇÇČČČČČÉĘĘĘĘĘĚĚĚÍÍÎĎĎĐĐŇŇÓÔŐŐ×ŘŮŰÜŢŢáâäĺçéëíďńôöřűý !!#$&'(**+,--//0012233355555677777888899999:::::::;;;;;;;;;;<<<<<<<=============================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>???????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????ŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĆĆĆĆĆĆĆĆĆČČČČČČČÉĘĘĘËËËÍÍÍÎÎĐĐĐŃÓÓÔÔÖ×ŮÚÚÜÝŕáăäćčęíîńóöřűý "#%%&()++,,.///1122244455567777777999999999::;;;;;;;;;;;;;;;<<<<<<=====================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????ŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĆĆĆĆĆĆĆÇÇÇČČČÉÉÉÉÉËËËĚĚÎÎÎĎĎŃŃŇŇÔŐÖŘŘÚŰÜŢŕâăĺčéěîđóőřúý !#$%'')*+--..0011133444666667778889999999::::;;;;;;;;;;;<<<<<<<<<<<=============>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????ŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄĹĹĹĹĹĹĹĹĹĆĆÇÇÇÇÇÇČÉÉÉÉĘĘĘĚĚĚÍÍĎĎĐĐĐŇÓÔÖÖ×ŮÚÜÝŢáâäçčëîđňőřúý !"#%&())+,-///00223335556666788888899:::::::::;;;;;<<<<<<<<<<<<<<<<<============>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????ŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄĹĹĹĹĹĹĹĹĹĹĆĆĆÇÇÇČČČČČĘĘĘĘËËÍÍÍÎÎĐĐŃŇÔÔŐ×ŘŮŰÜŢŕâăćčęíďňô÷úý !#$&'(*++-.//1122244555577777888999::::::::::;;<<<<<<<<<<<<<<<<<<<==============>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>?????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????ŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄĹĹĹĹĹĆĆĆĆĆĆĆČČČČČÉÉÉËËËĚĚÎÎĎĎĐŇŇÓŐÖÖŮÚŰÝŢáâĺćéěîńô÷úý !"$%&))*,--/001133444666777779999999:::::;;;;;;;<<<<<<<<<<<<=====================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>?????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????ŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄĆĆĆĆĆĆĆĆÇÇÇČÉÉÉÉĘĘĚĚĚÍÍĎĐĐŃÓÓÔŐ×ŘŰÜÝŕáäĺčëíđóöůü "#$'(*+,,.//022333556666788899999999;;;;;;;;;;;;<<<<<============================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>?????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????ŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĆĆĆÇÇÇÇÇČČÉĘĘĘĘËÍÍÎÎÎŃŃŇŇŐÖ×ŮÚÝŢŕâĺçęěđóöůü !"%&()*--..111224555567788888999::::;;;;;;;;;;;<<<============================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>?????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????ŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂÄÄÄÄÄĹĹĹĹĹĹĹĹĹÇÇÇČČČČČÉĘËËĚĚÎÎĎĐĐŇÓÔ×ŘŮŰÝŕáäćéëďňőůü "$&'(+,-//01133445677777888:::::::::;;;;;<<<<<<<<<===================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>?????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????ŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĹĹĹĹĹĹĹĹĆĆĆĆČČČČÉÉÉĘĚĚĚÍÍĐĐŃŃÔŐÖŮÚŰŢŕăĺčëîńőřü !$%&)*+..//22333566677779999::::::::<<<<<<<<<<<<<<<==========>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>?????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????ŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄĹĹĹĆĆĆĆĆĆÇÇÉÉÉĘĘĘËÍÍÎÎĎŃŇÓÔ×ŘŰÜŢáăçęíđôřü !#$'(+,-.01122455566688999999:::;;;;<<<<<<<<<<<<<<<<==>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>?????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????ŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄĆĆĆĆÇÇÇÇČČĘĘËËËĚÎĎĐĐŃÔŐÖŮÚŢŕăĺéěđôřü !%&)*+.//013444557788889999;;;;;;;;;<<<<<<<<<<=======>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>???????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????ŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂÄÄÄÄÄÄÄÄÄÄĹĹĹĹÇÇÇČČČČÉÉËĚĚÍÍĐŃŇÓÖ×ŘÜŢáäčęîó÷ű !#'(),-./22334667777888::::;;;;;;;;;;<<<==============>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????ŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÄÄÄÄÄÄĹĹĹĹĹĹĆĆĆČČÉÉÉĘĘÍÍÎĎĐÓÔŐŘÚÜŕâćéíňöű #%'*+,/01225566677999::::::;;;;;;=====================>>>>>>>>>>>>>>>>>>>>>>???????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????ŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĹĹĹĹĹĹĆĆĆĆĆÇÇÉĘĘËËĚĎĐĐŃŇÖŘÜÝŕäéěńöű "#')-.//03445568899999::::::<<<<<======================>>>>>>>>>????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????ŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂÄĆĆĆĆĆÇÇÇČČČËĚĚÍÎŃŇÔŐŮŰŕâçęđőú $&*+-.1233477788899999;<<<<<<<<<<====================>>>????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????ŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÇÇÇČČČÉÉĘÍÎĎĐŃŐÖŘÝŕĺčîôú "')*./012566777888;;;;;;<<<<<<<<<<=========>>>>>>>>>>>>>>????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????ŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄĹĹĹĹČÉÉĘĘËĚĐŃŇÓŘÚÝâćěóů "%',-./3455667::::;;;;;;;<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????ŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁĂÄÄÄÄÄÄÄĹĹĹĹĆĆĆÇĘËĚĚÍÎÓŐ×Ýŕăęńř"(*,1233458999::::;;;;;;;<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????ŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÄĹĹĹĹĆĆĆÇÇČČÉÍÎĐŃÓŮÜăčđř#&,./1267788999::::;=======>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????ŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂĂĂĂĆÇÇČČÉÉĘËŃÓŐŘŕäíö '*,.456677889<<<===========>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????ŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂÄÄÄÉÉĘËĚÎĐŘŰŕęő $'/134566;;;<<<<<===========>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????ŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂÄÄÄÄĹĹĆÇÎĐŇŐŕćó *-/189::;;;;<<<<<===========>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????ŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂÄÄÄÄĹĹĆÇČÉĘŐŮŕđ&*56789::;;;;<<<<<===========>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????ŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂÄÄÄÄĹĹĆÇČÉĘĚĐŐę*/356789::;;;;<<<<<===========>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????ŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂÄÄÄÄĹĹĆÇČÉĘĚĐŐŕ*/356789::;;;;<<<<<===========>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????żżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽ»»»»şşą¸·¶µłŻŞź`UPLJIHGFEEDDDDCCCCCBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@żżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽ»»»»şşą¸·¶µłŻŞź`UPLJIHGFEEDDDDCCCCCBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@żżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽ»»»»şşą¸·¶µłŻŞ•jUPLJIHGFEEDDDDCCCCCBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@żżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽ»»»»şşą¸·¶µŞ¦źŹp`YUJIHGFEEDDDDCCCCCBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@żżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽ»»»»şşą¸±Ż­Şź™Śsf`URPNGFEEDDDDCCCCCBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@żżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽ»»»¶¶µ´ł±Ż§¤ź•Šuj`[XPNLKJIIDDDCCCCCBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@żżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝ĽĽĽą¸¸··¶¶µ´®¬Ş§ź›’‰vmd`XUSQKJIIHHGGFCCCBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@żżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝»şşşşąąą¸¸··¶˛±Ż®¬¦Łś—ʇxphc\YSQPNMIHHGGFFFEEEEDBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@żżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľĽ»»»»»»»şşşşąąą¸µ´łł˛±¬Ş¨˘źś•އxqjc`]WUSNMLLKJGFFFEEEEDDDDDDDCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@żżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżľľľľľľľľľľľľľľľľľľľľľľľĽĽĽĽĽĽĽĽĽĽ»»»»»»»şşşş·¶¶µµ´łŻ®­¬§Ą˘ť™“چyslfb]ZXSRQPLKJJIIHEEEEDDDDDDDCCCCCCCCCCAAAAAAAAAAAAAAAAAAAAAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@żżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽ»»»»»»¸¸¸···¶¶µ˛±°Ż®Ş©§˘źš—‘‹…ztnhe`]XVUQPONMJIIHHHGGGDDDDDDCCCCCCCCCCBBBBBBBBBAAAAAAAAAAAAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@żżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽ»ąąąąą¸¸¸···´łł˛±®­«Ş¦¤źť•ŹŠ…zupjgb`[YUTRQNMLLKHHHGGGFFFFFDCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@żżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽşşşşşşąąąąą¸¸¶µµ´´ł°ŻŻ®­©§Ł˘ź›–“މ„{vqlid`]\XVRQPPOLKKJJIGGFFFFFEEEEEECCCCCBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@żżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝»»»»»»şşşşşşąąą··¶¶¶µµ˛˛±°Ż¬«Ş§ĄŁźť™–’Ť‰„{vrmifb`\ZXUTSPONMMJJIIIHHFFFEEEEEEDDDDDDBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@żżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽ»»»»»»»»»»şşşş¸¸¸····¶¶´łł˛˛Ż®­¬©¨§Łˇž›—•‘Ś„{wsnjhda^\XWVSRQPMMLLKIIHHHHGGGEEEEDDDDDDDDDDCCCBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@żżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»ąąąą¸¸¸¸··µµ´´´ł±°ŻŻ®«Ş©¦Ąˇźśš–“Ź‹‡|xtpliec`^ZYVUTQPPONLKKKJJHHGGGGFFFFDDDDDDDDDCCCCCCCCCCBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@żżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»şşşąąąąąą¸¸¶¶¶µµµ´˛˛±±°®­¬«¨§¤Łˇžś•’Ź‹‡|xtpmjgca^\[XWTSRQONNMMKJJJIIIGGFFFFFFEEEDDDDCCCCCCCCCCCCCCCCBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@żżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽşşşşşşşşąąąą····¶¶¶µłłł˛˛ŻŻ®®«Ş©¦Ą¤ˇźśš—”‘ŽŠ‡|xuqnkhec`^[ZYVUTQQPPMMLLLJIIIHHHHFFFFEEEEEEEECCCCCCCCCCCCCCCBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@żżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽ»»»»»şşşşşşşşş¸¸¸·····¶µ´´łł±±°ŻŻ­¬«¨§¦¤˘źž›™–”ŤŠ†|yurokifda`][YXWTSRPPONNLLKKJIHHHHHGGGEEEEEEEEEDDDDDCCCCCCCCCBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@żżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽ»»»»»»»»»»»şşşşąąą¸¸¸¸¸··¶µµµµ´˛˛±±±®®­­Ş©¨¦Ą˘ˇźťš•“ŹŚ‰†|yvspljgeb`^]ZYWVURRQQNNNMMKJJJJIHHGGGGGFFFEEEEDDDDDDDDDDDCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@żżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽ»»»»»»»»»»»»ąąąąąąąą¸¸¸·¶¶¶¶µµłłł˛˛°ŻŻ®¬¬«Ş¨§¤Ł˘źž›š—”’ŹŚ‰†|yvspmkheda`]\[XWUTSSQPPOMMLLLJJIIIIHGGGFFFFFFFFDDDDDDDDDDDDCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@żżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»şşşşşąąąąąąą·····¶¶¶´´´łł±±°°Ż­­¬Ş©©¦Ą¤˘ˇžťš™–“‘Ž‹…‚}zwtqnlifeba^][ZYVVUSRRPOONNLLKKKIIIHHHHHFFFFFFFEEEEEDDDDDDDCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@żżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»şşşşşşşşşşąąą¸¸¸·····µµµµ´´˛˛˛±±ŻŻ®­««Ş¨§¦¤Łˇźťś™—•’Ť‹…‚}zwtromjhfcb`^\[YXWUTTRQPPNNMMMKKJJJJHHHHHGGGFFFEEEEEEEEEEDDCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@żżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»şşşşşşşşşąą¸¸¸¸¸¸·¶¶¶¶µµµłłł˛˛°°ŻŻŻ­¬«©©¨¦ĄŁ˘ˇžť›—”‘ŹŤŠ‡…‚}zxurpnkhgdba^]\ZYWVVTSRPPPOOMMLLLJJJIIIIHGGGGGGFFEEEEEEEEEDDDDDCCCCCCCCCCCCCCCCCBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@żżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»şşşşąąąąąąą¸¸¸···¶¶¶¶¶´´´łł±±±°°®®­­«Ş©§§Ą¤Łˇźťśš—–“‘ŹŚŠ‡…‚}zxuspnlihecb`^\[ZXXVUTRRQQOONNNLLKKKIIIIIHHHGGGFFFFFFFEEEEDDDDDDDDDDDCCCCCCCCCCCBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@żżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽ»»»»»»»»»»»»»»»şşąąąąąąąąą·······¶µµµ´´´˛˛˛±±ŻŻŻ®¬¬««©¨¦ĄĄŁ˘źžś›™—•’‘ŽŚ‰‡„‚}{xvsqnmjhfdca`]\ZZYWVTTSSQPPPNNMMMKKKJJJIHHHHHHHFFFFFFFFFEEDDDDDDDDDDDDDDDCCCCCCBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@żżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽ»»»»»»»»»»şşşşşşşąąąąą¸¸¸¸·····¶µµµµµłłł˛˛±°°ŻŻ­­¬«ŞŞ¨§¦¤Łˇˇžť›š–”’Ž‹‰‡„‚}{xvtqomkigedba^^\[YXWUUTSRRPPOONMMLLLJJJJJIHHHHHGGGGFFFFFEEEEEEEDDDDDDDDDDCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@żżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»şşşşşşşşşşşşą¸¸¸¸¸¸¸¸·¶¶¶¶¶µµ´´łłł˛±±±°Ż®®­¬««©¨¨¦ĄŁ˘ˇźžś›™—–“‘ŹŤ‹‰†„‚}{yvtrpnlihfdca`^]\ZYWWVTTSRQQPONNNMLLLKKJJIIIIIHGGGGGGGGFEEEEEEEEEEEEDDDDDCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@żżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»şşşşşşşşąąąąą¸¸¸¸¸····¶¶¶µµ´´´´˛˛˛˛±°ŻŻŻ­­¬¬ŞŞ©§§Ą¤Łˇ źť›š–•“‘ŹŤ‹†„‚}{ywtrpnljigedb`_^\[ZXXVUUSSRRPPPONMMMMKKKKJJIIIHHHHGGGGGFFFFFEEEEEEEEDDDDDCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@żżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»şşşąąąąąąąąąą¸········µµµµµ´łłł˛˛±±°°Ż®®­¬««Ş©¨¦¦ĄŁ˘ źžť›™—•”’ŽŚŠ†„‚}{ywusqomkjhfdba`_]\ZYYWVUTTSRQQPOONNMMLLLKJJJJJHHHHHHHHGFFFFFFFFFFEEEDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@żżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»şşşşşąąąąąąą¸¸¸¸····¶¶¶¶µµµ´´łłł˛±±±±ŻŻ®®­¬¬ŞŞ©§§¦Ą¤˘ˇ źťśš™—•“‘ŹŽŚŠ†„‚}{ywusqpnljhfecb`_^][ZYXXVUUSSRQQPPNNNNMLLLKKJJJIIIIHHHHGGGGFFFFFFFEEEEEDDDDDDDDDDDDDDDCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@żżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»şşşşşşşşşşąą¸¸¸¸¸¸¸¸¸·¶¶¶¶¶¶¶´´´´´˛˛˛˛±°°ŻŻ®­­­««Ş©¨§¦Ą¤Ł˘ źžť›™–•“‘ŹŤ‹‰‡…~|zxvtrpnljigfdba`_]\[ZYXWVUTTRRRQPPOONMMMMKKKKKIIIIIIIHGGGGGGGGGFFEEEEEEEEEEDDDDDDDDDDDDDCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@żżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»şşşşşşşşşşşşşąąąą¸¸¸¸¸·····¶¶¶µµµµ´´łłł˛˛±±°°°Ż®®­¬¬«Ş©©§§Ą¤¤˘ˇźźťśš™—•”’ŹŤ‹‰‡…~|zxvtrpomkjhfecb``^][[ZXXVVUTSSRQQPOOONNMMLLLKKJJJJIIIHHHHHGGGGGFFFFEEEEEEEEEEEEEDDDDDDDDCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@żżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»şşşşşşşşąąąąąąąąą¸¸·······¶¶µµµµµ´´łłł˛˛±±±ŻŻŻŻ­­¬¬«ŞŞ¨¨¦¦ĄŁŁˇ źžś›™—•”’ŹŤ‹‰‡…~|zxvtrpomkjhgfdca`_^\\ZYYWWUUTSSRRPPPPNNNMMLLLKKJJJJJIIHHHHHHHGGFFFFFFFFFEEEEEEEEDDDDDDDDCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@żżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»şşşşşąąąąąąąąą¸¸¸¸¸····¶¶¶¶¶µµ´´´´´˛˛˛˛˛°°°ŻŻ®®­¬««Ş©©§§¦Ą¤Ł˘ˇźźť›š™–”“‘ŹŽŚŠ‰‡…~|zxvusqpnlkigfedb``^]\[ZYXXVVUTTSRQQPPOOOMMMMMKKKKKJJIIIIIHHHHGGGGGFFFFFFFFFEEEEEDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@żżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»şşşşşşşąąąąą¸¸¸¸¸¸¸¸¸··¶¶¶¶¶µµµ´´´łłł˛˛±±±°°ŻŻ®®­¬¬«ŞŞ¨¨§¦Ą¤ŁŁˇ źžś›š—•”“‘ŹŽŚŠ‡…~|zxwusqpnlkjhgedca`_^\\[ZYXWWUUTSSRQQPPOONNNMMLLLKKKJJJIIIIIHHGGGGGGGGGFFFFFEEEEEEEDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@żżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»şşşşşşşşşşşşąąąą¸¸¸¸¸¸······¶¶µµµµµµ´łłłł˛˛±±±°ŻŻŻ®­­­«««©©¨§¦Ą¤¤˘˘ źžťś›™—–•”’ŹŤŚŠ†…~|zywusrpomkjihfdcba`_]][[ZYXWVVTTTRRRQPPPONNNMMLLLLKJJJJJJIIHHHHHHGGGGGGFFFFEEEEEEEEEEEEDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@żżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»şşşşşşşşşşşşşąąąąąąąą¸¸¸·······¶¶¶¶µµµ´´´´ł˛˛˛˛˛±°°°Ż®®®­¬¬«ŞŞ©¨§¦¦Ą¤Ł˘ˇźźťś›š—–”“’ŽŤ‹Š†…~|zywutrqomlkihgedcb``^]\[ZYYXWVUUTSSRQQQPOOONMMMMMLKKKKJJJIIIIHHHHHHHGGGFFFFFFFFEEEEEEEEEEEEEDDDDDDDDDDDCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@żżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»şşşşşşşşąąąąąąąąąąą¸¸¸¸¸···¶¶¶¶¶¶¶µµµ´´´łłł˛˛±±±±°ŻŻŻ®­­­«««Ş©¨§§¦Ą¤Ł˘˘ źžťśš™—•”“‘ŹŽŤ‹‰†„~|{ywvtrqpnlkjhgfecba`_]]\[ZYXXWVUTTTRRRQPPPONNNNMMLLLKKKJJJIIIIIIIHHHGGGGGFFFFFFFFFFFEEEEEEEEDDDDDDDDDDDCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@żżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»şşşşşşşąąąąąąą¸¸¸¸¸¸¸¸¸····¶¶¶¶µµµµµµ´łłłł˛˛±±±°°ŻŻ®®®­¬¬«ŞŞ©¨¨§¦Ą¤ŁŁ˘ˇźźťť›š™—–•“’‘ŹŽŚ‹‰‡†„~|{yxvtsqpnmljihfedbb``^]\\[ZYXWWVUUTSSRQQQPPOONNNMMLLLLKJJJJJJIIIIHHHHGGGGGGGGGFFFFFFFEEEEEEEDDDDDDDDDDDCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@żżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»şşşşşşşşşąąąąąą¸¸¸¸¸¸········¶¶¶µµµµ´´´´łł˛˛˛˛±°°°°Ż®®­­¬¬««Ş©©¨§¦ĄĄ¤Ł˘ˇ źžťś›š—–”“’ŹŤŚŠ‰‡†„~|{yxvusrpomlkihgedcba`_^]\[ZZYXWVVUTTSSRRQQPOOOONMMMMLLKKKKJJJJIIIHHHHHHHHGGGGGGFFFFFFEEEEEEEEEDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@żżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»şşşşşşşşşşşşşąąąąąąą¸¸¸¸·······¶¶¶¶¶¶µµ´´´´łłłł˛±±±±°ŻŻŻŻ®­­­¬««Ş©©¨§¦¦Ą¤Ł˘ˇ źžťś›š™–•”’‘ŽŤŚŠ‰‡†„~|{yxvusrqonmkjigfedcba`_^]\[ZYYXWVVUTTSRRRQPPPPONNNNMLLLLKKKKJJIIIIIIHHHHHHHGGGGFFFFFFFEEEEEEEEEEEEEDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@żżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»şşşşşşşşşşşşşąąąąąąąąąą¸¸¸¸¸¸····¶¶¶¶¶¶µµµµµ´´łłłł˛˛˛±±°°°Ż®®®®­¬¬««ŞŞ©¨§§¦Ą¤ŁŁ˘ˇ źžťś›™™—–•“’‘ŹŽŤ‹Š‡…„‚~}{zxwutrqpnmljihffdcba`_^]\\[ZYXXWVUUTTSSRQQQQPOOONNMMMLLLLKKJJJJJIIIIIIHHHHGGGGGGFFFFFFFFFFEEEEEEEEEEEEEDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@żżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»şşşşşşşşşşąąąąąąąąą¸¸¸¸¸¸¸¸¸·····¶¶¶µµµµµµ´´´´łł˛˛˛˛±±°°°ŻŻ®®­­­¬««Ş©©¨§§¦Ą¤¤Ł˘ˇ źžžś›š™—–”“’‘ŹŽŤ‹Š‡…„‚~}{zxwutrqpnmlkihgfedcaa`_^]\[[ZYXXWVVUTTSRRRQQPPOOONNMMMMLLKKKKJJJJJJIIIHHHHHGGGGGGGGGFFFFFFFFFEEEEEEEEEEDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@żżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»şşşşşşşşşşąąąąąąą¸¸¸¸¸¸¸········¶¶¶¶µµµ´´´´´łłłł˛±±±±±°ŻŻŻ®®­­¬¬¬«ŞŞ©¨¨§¦ĄĄ¤ŁŁˇˇźźžťś›š™—–•”“’ŹŤŚ‹‰‡…„‚~}{zxwvtsrpomlkjihfedcba``^^\\[ZZYXWWVUUTSSSRRQQPPPONNNNNMLLLLKKKKKJJJIIIIHHHHHHHHGGGGGGGFFFFFFFEEEEEEEEEEDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@żżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»şşşşşşşşşşşşąąąąąąą¸¸¸¸¸······¶¶¶¶¶¶¶µµµµ´´łłłłł˛˛˛±±°°°°ŻŻ®®­­¬¬«««©©©¨§¦¦Ą¤¤˘˘ˇ źžťś›š™—–•“’‘ŽŤŚ‹‰†…„‚~}{zywvtsrqonmljihgfedcba`_^]][[ZYYXWVVVTTTSSRRQQPPOOOONNMMMLLLLLKKJJJJIIIIIIIHHHHHHGGGGGFFFFFFFEEEEEEEEEEEEDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@żżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»şşşşşşşşşşşşşąąąąąąąąąą¸¸¸¸¸¸·····¶¶¶¶¶µµµµµµ´´´łłł˛˛˛˛±±±°°ŻŻŻŻ®­­¬¬««ŞŞ©¨¨§§¦ĄĄŁŁ˘ˇˇźźžťś›š™—•”“’‘ŹŽŤŚŠ‰†…„‚~}{zywvusrqpnmlkjhgfedcba``^^]\\ZZYXXWWVUUTTSSRRQPPPPOONNNMMMMLLLKKKJJJJJJIIIIIHHHHHGGGGGGFFFFFFFFFFEEEEEEEEEEEEEDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@żżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»şşşşşşşşşşşşşąąąąąąąąąąą¸¸¸¸¸¸¸¸·······¶¶¶¶µµµµ´´´´´łłłł˛˛±±±±°°ŻŻ®®®®­¬¬««Ş©©©¨§§¦Ą¤¤Ł˘˘ˇ źžťś››™—–•”“‘ŹŽŤ‹Š‰‡†…‚~}|zyxvutrqponlkjihgfddcba`_^]]\[[ZYXXWVVVUTTSSRQQQQPPOONNNNMMLLLLKKKKKJJJJIIIIHHHHHHHGGGGGGGGFFFFFFFFFFFEEEEEEEEEEEEEDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@żżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»şşşşşşşşşşşşąąąąąąąąą¸¸¸¸¸¸¸········¶¶¶¶¶¶¶µµµ´´´łłłłł˛˛˛˛±±°°°ŻŻŻ®­­­­¬««ŞŞ©¨¨¨§¦ĄĄ¤ŁŁ˘ˇ źžžťś›š™—–•”’‘ŹŽŚ‹Š‰‡†…‚~}|zyxvutsqponmkjihgfedcbaa`_^]\\[ZZYXWWWVUUTTSRRRRQPPPOOONNMMMMLLLLLKKKJJJIIIIIIIHHHHHHHHGGGGGGGFFFFFFFFFEEEEEEEEEEEEDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@żżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»şşşşşşşşşşşşąąąąąąąąą¸¸¸¸¸¸······¶¶¶¶¶¶¶µµµµµµ´´´łłł˛˛˛˛±±±±°ŻŻŻŻ®®­­¬¬¬«ŞŞ©©¨§§¦¦Ą¤¤Ł˘˘ˇ źžťś›šš–•”“’‘ŹŤŚ‹Š‡†…‚~}|zyxwutsrponmlkjiggeedcba`_^]]\[[ZYYXXWVVUUTSSSRRQQPPPPONNNNMMMMLLLKKKJJJJJJIIIIIIIHHHHHHGGGGGGFFFFFFFFFEEEEEEEEEEEEDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@żżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»şşşşşşşşşşşşąąąąąąąąąą¸¸¸¸¸¸¸······¶¶¶¶µµµµµµ´´´´´łłłł˛˛±±±±°°°ŻŻ®®®­­­¬«««ŞŞ©¨¨§¦¦ĄĄ¤ŁŁ˘ˇ źžžťś›š™—–•”“’‘ŹŽŤŚ‹Š‡†…‚~}|zyxwutsrqpnmlkjihgfedcbaa`_^]\\[ZZYYXWWVUUTTTSRRRQQQPPOOONNNNMMLLLLKKKKKJJJJJJIIIIHHHHHHGGGGGGGFFFFFFFFFFEEEEEEEEEEEEDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@żżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»şşşşşşşşşşşşşąąąąąąąąąą¸¸¸¸¸¸¸¸¸········¶¶¶¶µµµµ´´´´łłłłł˛˛˛˛±±°°°°ŻŻŻ®®­­¬¬¬«ŞŞ©©©¨§§¦Ą¤¤¤Ł˘ˇˇźźžťśśšš™——–””’‘ŹŽŤŚ‹‰‡†„‚~}|{yxwvtsrqponmkkihhfeeccba``^^]\[[[ZYXXWVVVUUTSSSRRQQPPPOOOONNMMMMLLLLLKKKKJJJJIIIIHHHHHHHHGGGGGGGGGFFFFFFFFFFEEEEEEEEEEEEEDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@żżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»şşşşşşşşşşşşşşąąąąąąąąąąą¸¸¸¸¸¸¸¸········¶¶¶¶¶¶¶µµµµµ´´´´łłł˛˛˛˛±±±±°°ŻŻŻ®®®®­¬¬«««ŞŞ©¨¨¨§¦ĄĄ¤ŁŁ˘˘ˇ źžťťś›š™—–•”“’‘ŹŽŤŚŠ‰‡†„‚~}|{yxwvusrqponmlkjihgfedcbba`_^]]\\[ZZYXWWWVUUTTTSSRQQQQPPPOONNNNMMMMLLLKKKKJJJJJIIIIIIIHHHHHHHHGGGGGGGGFFFFFFFFFFFEEEEEEEEEEEEEDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@żżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşąąąąąąąąąąą¸¸¸¸¸¸¸·······¶¶¶¶¶µµµµµµµ´´´´´łłłł˛˛˛±±±°°°°ŻŻ®®­­­­¬¬«ŞŞŞ©©¨§§¦¦Ą¤¤Ł˘˘ˇˇźźžťś››š™—–•”“’‘ŹŤŚ‹Š‰‡…„‚~}|{zxwvutsrponmlkjihgfeddcba``^^]]\[[ZYYXXWVVUUUTSSRRRRQQPPOOOONNNMMMLLLLKKKKKJJJJJJJIIIIIHHHHHHHGGGGGGGFFFFFFFFFFFEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@żżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşąąąąąąąąąąą¸¸¸¸¸¸¸·······¶¶¶¶¶µµµµµ´´´´łłłłł˛˛˛˛˛±±°°°ŻŻŻŻ®®­­¬¬¬««ŞŞ©©¨¨§¦¦ĄĄ¤ŁŁ˘ˇ  źžťťś›š™——–””“‘ŹŽŤŚ‹Š‰†…„‚~}|{zywvutsrqponlkkihhgfedcbba`__^]\\[ZZYYXWWVVUUTTSSSRRQQPPPPOOONNMMMMMLLLLLKKKKJJJJJIIIIIHHHHHHHGGGGGGGFFFFFFFFFFFEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@żżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşąąąąąąąąąąą¸¸¸¸¸¸¸¸········¶¶¶¶¶¶¶µµµµµ´´´łłłł˛˛˛˛±±±±±°°ŻŻŻ®®®­­­¬¬«ŞŞŞ©©¨§§§¦¦Ą¤¤Ł˘ˇˇ źžžťś››š™—–•”“’‘ŹŽŤŚ‹Š‰‡†…„‚~}|{zyxvutsrqponmlkjihgfeddcbaa`_^^]\[[ZYYXXXWVVUUUTSSRRRQQQPPPOONNNNNMMMMLLLLKKKJJJJJIIIIIIIHHHHHHHHGGGGGGGGFFFFFFFFFFFEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@żżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»şşşşşşşşşşşşşąąąąąąąąąą¸¸¸¸¸¸¸¸¸········¶¶¶¶¶¶¶µµµµµµµ´´´´´łłłł˛˛˛±±±°°°°ŻŻŻŻ®®­­¬¬¬««ŞŞ©©¨¨§§¦ĄĄĄ¤Ł˘˘ˇ źźžťśś›š™™—–•”“’‘ŹŽŤŚ‹Š‡†…„‚~}|{zyxwutsrqponmlkjihgffedccba``_^]]\[ZZZYXXWWVVUUTTSSSRRQQPPPPOOOONNNMMMLLLLKKKKKJJJJJJJIIIIIIIHHHHHHHHGGGGGGGGGFFFFFFFFFFEEEEEEEEEEEEEDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@żżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşąąąąąąąąąąą¸¸¸¸¸¸¸¸¸········¶¶¶¶¶¶µµµµµ´´´´´łłłłł˛˛˛˛˛±±±°°ŻŻŻŻ®®®­­­¬«««ŞŞŞ©¨¨§§¦¦Ą¤¤ŁŁ˘ˇˇ źźžťś›šš™—––”““’‘ŹŽŤ‹‹‰‡†…„‚~}|{zyxwvttrqponmllkiihgfeedcba``_^^]\\[[ZYYXXWWVUUUTTTSRRRQQQPPPPOONNNMMMMMLLLLLKKKKKJJJJJIIIIIIHHHHHHHHGGGGGGGGGFFFFFFFFFFFEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@żżżżżżżżżżżżżżżżżżżżżżżżżżżľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşąąąąąąąąąąą¸¸¸¸¸¸¸¸¸········¶¶¶¶¶¶µµµµµ´´´´łłłł˛˛˛˛±±±±±°°°ŻŻŻ®®­­­¬¬¬««ŞŞ©©©¨¨§¦¦ĄĄ¤¤Ł˘˘ˇ źźžžťś›š™—–•”“’‘ŹŽŤŚ‹Š‰‡†…„‚~}|{zyxwvutsrqponmlkjihggfedcbaa``_^]]\[[ZZYYXWWVVVUUTTSSSRRRQQPPPOOONNNNNMMMMLLLLKKKKJJJJJIIIIIIHHHHHHHHGGGGGGGGGFFFFFFFFFFFEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@@@@@@@@@@@@@@@@@@@@@@@żżżżżżżżżżżżżżżľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşąąąąąąąąąą¸¸¸¸¸¸¸¸¸········¶¶¶¶¶¶¶µµµµµµ´´´´´łłłł˛˛˛±±±±°°°ŻŻŻŻ®®®­­¬¬««««ŞŞ©©¨§§§¦¦Ą¤¤ŁŁ˘ˇ  źźžťśś›š™—––•”“’‘ŹŽŤŚ‹Š‰‡†…„‚~}|{zyxwvutsrqponmlkjiihgfedccba``__^]\\[[ZYYXXXWVVUUTTTTSSRRQQQPPPPOOONNNNMMMLLLLKKKKKJJJJJJIIIIIIIHHHHHHHHGGGGGGGGGFFFFFFFFFFEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@@@@@@@@@@@żżżľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşąąąąąąąąąą¸¸¸¸¸¸¸¸¸·········¶¶¶¶¶¶µµµµµµ´´´´´´łłłłł˛˛˛˛˛±±±±°°ŻŻŻ®®®­­­­¬¬««ŞŞ©©©¨¨§§¦ĄĄĄ¤ŁŁ˘ˇˇ źźžťťś›šš™—–•”““’‘ŹŽŤŚ‹Š‰‡†…„‚~}|{zyxwvutsrqponmllkjihgfeedcbba``_^^]\\[ZZZYXXWWVVVUUTTSSRRRRQQQPPPOONNNNMMMMMLLLLLKKKKKKJJJJJJIIIIIIHHHHHHHHHGGGGGGGGGFFFFFFFFFFEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@ľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸·········¶¶¶¶¶¶µµµµµµ´´´´´łłłł˛˛˛˛±±±±±°°°°ŻŻŻ®®®­­¬¬¬«««ŞŞ©©¨¨§§¦¦ĄĄ¤ŁŁŁ˘ˇ  źźžťśś›š™—–•”“’‘ŹŹŽŤŚ‹Š‰‡†…„‚~}|{zyxwvutsrqpponmlkjihggfedccba``__^]\\\[ZZYYXXWWVVUUTTTSSSRRQQQPPPOOOONNNNNMMMMLLLLKKKKKJJJJJJIIIIIIHHHHHHHHHGGGGGGGGGGFFFFFFFFFFFFFEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸········¶¶¶¶¶¶¶µµµµµµ´´´´´łłłł˛˛˛˛±±±°°°ŻŻŻŻ®®®®­­­¬¬««ŞŞŞ©©©¨§§¦¦ĄĄ¤¤Ł˘˘ˇˇ źźžťťś›š™™—–••”“’‘ŹŽŤŚ‹‹Š‰‡†…„‚~}|{zyxwvuttsrqponmlkjjihgffedcbba``_^^]]\[[ZZYYXXWVVVUUUTTSSRRRQQQQPPPPOOONNNMMMMLLLLKKKKKJJJJJJIIIIIIIHHHHHHHHGGGGGGGGGGFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşąąąąąąąąąąą¸¸¸¸¸¸¸¸········¶¶¶¶¶¶¶µµµµµµµ´´´´´łłłłł˛˛˛˛˛±±±±°°°ŻŻŻ®®®­­­¬¬¬«««ŞŞ©©¨¨§§§¦¦Ą¤¤ŁŁ˘˘ˇ źźžžťś››š™™—–••”“’‘ŹŽŤŚ‹Š‰‡†…„‚€~}|{zyxwvutsrqponmlkjjihgffeddcbaa``_^]]\\[[ZYYXXXWWVVUUTTTSSSRRRQQQPPPOOONNNNMMMMMLLLLLKKKKKJJJJJJJIIIIIIIHHHHHHHHGGGGGGGGFFFFFFFFFFFEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸·········¶¶¶¶¶¶¶¶µµµµµµ´´´´´łłłłł˛˛˛˛±±±±±°°°°ŻŻŻŻ®®®­­­¬¬««ŞŞŞ©©©¨¨§§¦ĄĄĄ¤¤Ł˘˘ˇˇ źźžťśś››š™——–•”“’’‘ŹŽŤŚ‹Š‰‡†…„‚€~}|{zyxwvutsrqponmmlkjihhgfeddccba``_^^]]\[[ZZZYXXWWVVVUUUTTSSRRRQQQPPPPOOOONNNNNMMMMLLLLLKKKKKJJJJJJIIIIIIIIHHHHHHHHHGGGGGGGGGGFFFFFFFFFFEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸·········¶¶¶¶¶¶¶¶µµµµµµ´´´´´łłłłł˛˛˛˛±±±±°°°ŻŻŻŻ®®®­­­­¬¬¬««ŞŞ©©¨¨¨§§¦¦ĄĄ¤ŁŁŁ˘˘ˇ źźžžťśś›š™™—–••”“’‘ŹŹŽŤŚ‹Š‰‡†…„‚€~}|{zyxwvutsrqpponmlkjjihgffedccbaa``_^]]\\\[ZZYYXXWWWVVUUTTSSSRRRRQQQPPPPOOONNNNMMMMLLLLLKKKKKJJJJJJIIIIIIIIHHHHHHHHHGGGGGGGGGGGFFFFFFFFFFFFFEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸·······¶¶¶¶¶¶¶µµµµµµµ´´´´´łłłłł˛˛˛˛˛±±±±±°°°ŻŻŻŻ®®­­­¬¬¬«««ŞŞŞ©©¨¨§§¦¦ĄĄ¤¤ŁŁ˘˘ˇ  źźžťťś››š™——–••”“’‘ŹŹŽŤŚ‹Š‰‡†…„‚€~}|{zyxwvutsrqpponmlkjjihhgfeddcbba``__^]]\\[[ZZYYXXWWVVUUUTTTSSSRRRQQPPPPOOONNNNNMMMMMLLLLLKKKKKJJJJJJJIIIIIIIHHHHHHHGGGGGGGGGGGFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸········¶¶¶¶¶¶¶µµµµµµµ´´´´´´łłłłł˛˛˛˛˛±±±±°°°°ŻŻŻŻ®®®®­­­¬¬¬««ŞŞ©©©¨¨§§§¦¦ĄĄ¤ŁŁ˘˘ˇˇ źźžťťśś›š™™——–•”“’’‘ŹŽŤŚŚ‹Š‰‡†…„‚€~}|{zyxwvutssrqponmmlkjihhgffedccbba``_^^]]\\[ZZYYXXXWWVVVUUTTSSSRRRQQQQPPPPOOOONNNNMMMMMLLLLLKKKKKKJJJJJJJIIIIIIIHHHHHHHHGGGGGGGGGFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸··········¶¶¶¶¶¶¶¶µµµµµµµ´´´´´´łłłłł˛˛˛˛˛±±±±°°°ŻŻŻŻ®®®­­­¬¬¬«««ŞŞŞ©©¨¨§§¦¦ĄĄĄ¤¤ŁŁ˘ˇˇ  źźžťťś›šš™™—–••”“’’‘ŹŽŤŚ‹ŠŠ‰‡†…„‚€~}|{zyxwvuutsrqponmmlkjjihgffeedcbba``__^^]\\[[ZZZYYXXWWVVUUUTTTSSSRRRQQQPPPPOOONNNNMMMMMLLLLLKKKKKKJJJJJJJIIIIIIIIHHHHHHHHHHGGGGGGGGGGFFFFFFFFFFEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸··········¶¶¶¶¶¶¶µµµµµµµ´´´´´łłłłł˛˛˛˛˛±±±±±°°°°ŻŻŻŻ®®®­­­¬¬«««ŞŞ©©©¨¨¨§§¦¦ĄĄ¤¤ŁŁ˘˘ˇˇ źźžťťśś›šš™——–•””“’‘ŹŹŽŤŚ‹ŠŠ‰‡†…„‚€~}|{zyxwvuutsrqpponmlkkjihhgfeedccbba``_^^]]\\[[ZZYYXXWWWVVVUUTTTSSRRRQQQPPPPOOOONNNNNMMMMMLLLLLKKKKKJJJJJJJIIIIIIIHHHHHHHHHHGGGGGGGGGGGGFFFFFFFFFFFFFEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸········¶¶¶¶¶¶¶µµµµµµ´´´´´´´łłłłł˛˛˛˛˛±±±±±°°°ŻŻŻŻ®®®®­­­­¬¬¬«««ŞŞ©©©¨¨§§¦¦ĄĄ¤¤¤ŁŁ˘ˇˇ źźžžťťś››š™—––•”““’‘ŹŽŽŤŚ‹Š‰‰‡†…„‚€~}|{zyxwvvutsrqqponmllkjiihggfeddcbbaa``_^^]\\[[[ZZYYXXWWVVVUUTTTSSSRRRRQQQQPPPPOOONNNNNMMMMMLLLLLKKKKKKKJJJJJJIIIIIIIHHHHHHHHGGGGGGGGGGGFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸·········¶¶¶¶¶¶¶¶¶µµµµµµµ´´´´´´´łłłłł˛˛˛˛˛±±±±±°°°ŻŻŻŻ®®®­­­¬¬¬«««ŞŞŞ©©©¨¨§§§¦¦ĄĄ¤¤Ł˘˘ˇˇ  źźžžťś››šš™—–••”“’‘‘ŹŽŽŤŚ‹Š‰‡‡†…„‚€~}|{zyxxwvutsrqqponnmlkjjihggfeeddcbaa``__^^]]\[[ZZYYXXXWWVVVUUUTTTSSSRRRQQQPPPPOOONNNNNMMMMMLLLLLKKKKKKKJJJJJJJIIIIIIIIIHHHHHHHHHGGGGGGGGFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸···········¶¶¶¶¶¶¶¶¶µµµµµµµ´´´´´łłłłł˛˛˛˛˛±±±±±°°°°ŻŻŻŻ®®®®­­­¬¬¬«««ŞŞ©©©¨¨§§¦¦ĄĄĄ¤¤ŁŁ˘˘ˇˇ źźžžťťś››š™™—––•””“’‘‘ŹŽŤŚŚ‹Š‰‡‡†…„‚€~}|{zyxxwvutssrqponnmlkkjiihgffeddcbbaa``_^^]]\\[[ZZZYYXXWWVVVUUTTTSSSRRRQQQQPPPPOOOONNNNNMMMMMLLLLLKKKKKJJJJJJJIIIIIIIIIHHHHHHHHHHHGGGGGGGGGGFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸··········¶¶¶¶¶¶¶µµµµµµ´´´´´´łłłłłł˛˛˛˛˛˛±±±±°°°°ŻŻŻŻ®®®®­­­¬¬¬«««ŞŞŞ©©©¨¨¨§§¦¦ĄĄ¤¤ŁŁ˘˘ˇˇ  źźžžťśś›šš™—––•”““’‘ŹŹŽŤŚ‹‹Š‰‡††…„‚€~}|{zyyxwvuttsrqpponmllkjiihggfeedccbaa``__^^]]\\[[ZZYYXXWWWVVVUUUTTTSSSRRRQQQQPPPPOOOONNNNMMMMMMLLLLLLKKKKKKJJJJJJIIIIIIIHHHHHHHHHHGGGGGGGGGGGGGFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸········¶¶¶¶¶¶¶¶µµµµµµµµ´´´´´´łłłłłł˛˛˛˛˛˛±±±±°°°°ŻŻŻŻ®®®®­­­¬¬¬«««ŞŞŞ©©¨¨§§§¦¦ĄĄ¤¤¤ŁŁ˘˘ˇˇ źźžžťśś››š™™——–••”“’’‘ŹŽŽŤŚ‹ŠŠ‰‡†……„‚€~}|{zzyxwvuutsrqqponmmlkjjihhgffeddccbaa``_^^]]\\[[[ZZYYXXXWWVVUUUTTTSSSRRRQQQQPPPPOOOONNNNMMMMMMLLLLLLKKKKKKJJJJJJJJIIIIIIIIHHHHHHHHGGGGGGGGGGGGFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸··········¶¶¶¶¶¶¶¶¶¶µµµµµµµµ´´´´´łłłłł˛˛˛˛˛±±±±±°°°°ŻŻŻŻ®®®®­­­¬¬¬¬«««ŞŞŞ©©©¨¨§§§¦¦ĄĄ¤¤ŁŁ˘˘ˇˇ  źźžťťśś›šš™—––•”““’‘‘ŹŽŽŤŚ‹ŠŠ‰‡†……„‚€~}|{zzyxwvuutsrqqponnmllkjiihggfeedccbba``__^^]]\\[[ZZYYXXXWWVVVUUUTTTSSSSRRRQQQQPPPPOOOONNNNNMMMMMLLLLLKKKKKJJJJJJJJIIIIIIIIIIHHHHHHHHHHGGGGGGGGGFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸············¶¶¶¶¶¶¶¶µµµµµµ´´´´´´łłłłłł˛˛˛˛˛˛±±±±±°°°°ŻŻŻŻŻ®®®­­­­¬¬¬«««ŞŞŞ©©¨¨¨§§¦¦ĄĄĄ¤¤ŁŁŁ˘˘ˇˇ źźžžťťś››š™™——––•”““’‘‘ŹŽŤŤŚ‹Š‰‰‡†…„„‚€~}|{{zyxwvvutsrrqponnmllkjiihhgffeddcbbaa``_^^]]\\\[[ZZZYYXXWWWVVUUUTTTSSSRRRRQQQPPPPPOOOONNNNNMMMMMMLLLLLLKKKKKKJJJJJJIIIIIIIIHHHHHHHHHHHHGGGGGGGGGGGFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸··········¶¶¶¶¶¶¶µµµµµµµµ´´´´´´´łłłłłł˛˛˛˛˛˛±±±±±°°°°ŻŻŻŻ®®®­­­­¬¬¬«««ŞŞŞ©©©¨¨¨§§¦¦ĄĄĄ¤¤ŁŁ˘˘ˇˇ źźžžťťśś›šš™™——–••”“’’‘ŹŹŽŤŚŚ‹Š‰‰‡†…„„‚€~}|{{zyxwvvutssrqpponmmlkjjihhgffeedccbbaa``_^^]]\\[[ZZZYYXXWWWVVVUUUTTTSSSRRRRQQQPPPPOOOONNNNNMMMMMMLLLLLLKKKKKKKJJJJJJJJIIIIIIIHHHHHHHHHHGGGGGGGGGGGGGGFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸········¶¶¶¶¶¶¶¶¶¶µµµµµµµµµ´´´´´´łłłłł˛˛˛˛˛±±±±±°°°°ŻŻŻŻŻ®®®®­­­¬¬¬¬«««ŞŞŞ©©©¨¨§§§¦¦ĄĄ¤¤¤ŁŁ˘˘ˇˇ  źźžžťťś››šš™—––•””“’‘‘ŹŹŽŤŚ‹‹Š‰‡†…„„‚€~}|{{zyxwwvuttsrqpponnmlkkjiihggfeeddcbbaa``__^^]]\\[[[ZZYYXXXWWVVVUUUTTTSSSSRRRQQQQPPPPPOOOONNNNNMMMMMLLLLLKKKKKKJJJJJJJJJIIIIIIIIIIHHHHHHHHGGGGGGGGGGGGFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸············¶¶¶¶¶¶¶¶¶¶µµµµµµ´´´´´´łłłłłłł˛˛˛˛˛±±±±±±°°°°ŻŻŻŻŻ®®®®­­­¬¬¬¬«««ŞŞŞ©©©¨¨§§§¦¦ĄĄ¤¤ŁŁŁ˘˘ˇˇ źźžžťťśś›šš™™——–••”““’‘ŹŽŽŤŚ‹‹Š‰‡‡†…„‚€~}||{zyxxwvuttsrqqpoonmllkjjihhgffeedccbbaa``_^^]]\\\[[ZZYYXXXWWVVVUUUTTTSSSSRRRQQQQPPPPPOOOONNNNNNMMMMMLLLLLLLKKKKKKJJJJJJIIIIIIIIIIHHHHHHHHHHHHGGGGGGGGFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸·············¶¶¶¶¶¶¶µµµµµµµ´´´´´´´´łłłłłłł˛˛˛˛˛±±±±±°°°°ŻŻŻŻ®®®­­­­¬¬¬««««ŞŞŞ©©©¨¨§§§¦¦¦ĄĄ¤¤ŁŁŁ˘˘ˇˇ  źźžžťťś››šš™™——–••”““’‘ŹŽŤŤŚ‹ŠŠ‰‡‡†…„‚€~}||{zyxxwvuutsrrqpoonmllkjjihhgffeeddcbbaa``__^^]]\\\[[ZZYYYXXXWWVVVUUUTTTTSSSRRRRQQQPPPPOOOONNNNNMMMMMLLLLLLLKKKKKKKKJJJJJJJIIIIIIIHHHHHHHHHHHHHGGGGGGGGGGGFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸·········¶¶¶¶¶¶¶¶¶µµµµµµµµµ´´´´´´´łłłłł˛˛˛˛˛±±±±±°°°°°ŻŻŻŻŻ®®®®­­­­¬¬¬««««ŞŞŞ©©©¨¨§§§¦¦¦ĄĄ¤¤ŁŁ˘˘ˇˇˇ źźžžťťśś››š™™——––•””“’’‘ŹŹŽŤŤŚ‹ŠŠ‰‡‡†…„‚€~}||{zyxxwvuutsrrqpponmmlkkjiihhgffeddccbbaa``_^^^]]\\[[ZZYYYXXXWWVVVUUUTTTTSSSRRRRQQQQPPPPPOOOOONNNNNMMMMMLLLLLKKKKKKKJJJJJJJJJIIIIIIIIIHHHHHHHHHGGGGGGGGGGGGGGGFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸·········¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµ´´´´´łłłłłłł˛˛˛˛˛˛±±±±±°°°°°ŻŻŻŻ®®®®­­­­¬¬¬«««ŞŞŞ©©©¨¨¨§§§¦¦ĄĄĄ¤¤ŁŁ˘˘ˇˇ  źźžžťťśś››šš™——–••”““’‘‘ŹŹŽŤŚŚ‹Š‰‰‡††…„‚€~}||{zyyxwvvutssrqpponnmllkjjihhggfeeddccbbaa``__^^]]\\[[ZZZYYXXXWWWVVVUUUTTTSSSRRRRQQQQPPPPOOOOONNNNNMMMMMMLLLLLLLKKKKKJJJJJJJJIIIIIIIIIIIHHHHHHHHHGGGGGGGGGGGGGFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸·············¶¶¶¶¶¶¶¶¶µµµµµµ´´´´´´´´łłłłłłłł˛˛˛˛˛±±±±±°°°°ŻŻŻŻ®®®®®­­­­¬¬¬««««ŞŞŞ©©©¨¨¨§§§¦¦ĄĄĄ¤¤ŁŁ˘˘ˇˇ  źźžžťťśś››šš™——–••”““’‘ŹŽŽŤŚŚ‹Š‰‰‡††…„‚€~}||{zyyxwvvutssrqqpoonmllkjjihhggfeeddccbbaa``__^^]]\\[[ZZZYYXXXWWWVVVUUUTTTTSSSRRRRQQQQQPPPPOOOONNNNNMMMMMLLLLLLLLKKKKKKKKJJJJJJIIIIIIIIIHHHHHHHHHHHHHGGGGGGGGGFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸·············¶¶¶¶¶¶µµµµµµµµµµ´´´´´´´´łłłłł˛˛˛˛˛±±±±±±°°°°°ŻŻŻŻŻ®®®®®­­­¬¬¬««««ŞŞŞ©©©¨¨§§§¦¦¦ĄĄ¤¤¤ŁŁ˘˘ˇˇ  źźźžťťśś››šš™™——––•””“’’‘ŹŽŤŤŚ‹‹Š‰‰‡††…„‚€~}||{zyyxwvvuttsrrqpoonmmlkkjiihhgffeeddccbba```__^^]]\\[[[ZZYYYXXXWWVVVUUUTTTTSSSRRRQQQQQPPPPPOOOOONNNNNNMMMMMLLLLLKKKKKKKKJJJJJJJJJJIIIIIIHHHHHHHHHHHHHGGGGGGGGGGGGFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸·········¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµ´´´´´łłłłłłł˛˛˛˛˛˛±±±±±±°°°°°ŻŻŻŻ®®®­­­­¬¬¬¬««««ŞŞŞ©©©¨¨¨§§§¦¦¦ĄĄ¤¤¤ŁŁ˘˘ˇˇ  źźžžťťśś››š™™—––••”““’‘‘ŹŹŽŤŤŚ‹‹Š‰‡†……„‚‚€~}}|{zzyxwwvuttsrrqpponnmllkjjiihggffeddccbbaa``__^^]]\\[[[ZZYYYXXXWWWVVVUUUTTTTSSSSRRRRQQQPPPPOOOOONNNNNNMMMMMMLLLLLLLKKKKKJJJJJJJJJJIIIIIIIIIIHHHHHHHHHGGGGGGGGGGGGGGGGFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸···········¶¶¶¶¶¶¶¶¶¶¶µµµµµµ´´´´´´´´łłłłłłłł˛˛˛˛˛±±±±±°°°°ŻŻŻŻŻ®®®®®­­­­¬¬¬¬«««ŞŞŞ©©©¨¨¨§§¦¦¦ĄĄĄ¤¤ŁŁŁ˘˘ˇˇ  źźźžžťťś››šš™™—––•””“’’‘‘ŹŹŽŤŤŚ‹ŠŠ‰‡†……„‚‚€~}}|{zzyxwwvuutsrrqpponnmmlkkjiihggffeeddcbbaa```__^^]]\\\[[ZZZYYYXXWWWVVVUUUTTTSSSSRRRRQQQQQPPPPPOOOONNNNNMMMMMLLLLLLLLKKKKKKKKJJJJJJIIIIIIIIIIIHHHHHHHHHHHGGGGGGGGGGGGGGFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸···············¶¶¶¶¶¶¶¶µµµµµµµµ´´´´´´´´´łłłłł˛˛˛˛˛˛±±±±±±°°°°°ŻŻŻŻŻ®®®®­­­¬¬¬¬««««ŞŞŞ©©©¨¨¨§§§¦¦¦ĄĄĄ¤¤ŁŁ˘˘˘ˇˇ  źźžžťťśś››š™™——––•””“’’‘ŹŽŽŤŚŚ‹ŠŠ‰‡‡†……„‚‚€~}}|{zzyxxwvuutssrqqpoonmmlkkjiihhggffeddccbbaa``__^^]]]\\[[ZZZYYYXXXWWWVVVUUUTTTTSSSSRRRQQQQPPPPPOOOOONNNNNNMMMMMMLLLLLKKKKKKKKKJJJJJJJJIIIIIIIIHHHHHHHHHHHHHHHGGGGGGGGGGFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸············¶¶¶¶¶¶¶¶¶µµµµµµµµµµ´´´´´´łłłłłłł˛˛˛˛˛˛˛±±±±±°°°°°ŻŻŻŻ®®®®­­­­­¬¬¬¬««««ŞŞŞ©©©¨¨§§§¦¦¦ĄĄ¤¤¤ŁŁŁ˘˘ˇˇ  źźźžžťťśś›šš™™——––••”““’’‘ŹŽŽŤŚŚ‹Š‰‰‡‡†……„‚‚€~}}|{zzyxxwvvutssrqqpoonmmllkjjiihhgffeedccbbaa```__^^]]\\\[[[ZZYYYXXXWWVVVUUUTTTTSSSSRRRRRQQQQPPPPOOOOONNNNNMMMMMMMLLLLLLLKKKKKKJJJJJJJJJJIIIIIIIIIHHHHHHHHHHHHGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸·········¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµ´´´´´´´´łłłłłłłł˛˛˛˛˛±±±±±°°°°°ŻŻŻŻŻŻ®®®®­­­­¬¬¬««««ŞŞŞ©©©©¨¨¨§§§¦¦¦ĄĄ¤¤¤ŁŁ˘˘ˇˇ  źźźžžťťśś››šš™™——––•””““’‘‘ŹŹŽŤŤŚ‹‹Š‰‰‡‡†…„„‚‚€~}}|{{zyxxwvvuttsrrqpponnmllkkjiihhgffeeddccbbaa```__^^]]\\[[[ZZYYYXXXWWWVVVVUUUTTTTSSSRRRRQQQQPPPPPPOOOOONNNNNMMMMMLLLLLLLLKKKKKKKKJJJJJJJIIIIIIIIIIIIHHHHHHHHHGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAA˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸············¶¶¶¶¶¶¶¶¶¶µµµµµµµ´´´´´´´´´´łłłłł˛˛˛˛˛˛±±±±±±±°°°°°ŻŻŻŻ®®®®­­­­¬¬¬¬¬««««ŞŞŞ©©©¨¨¨§§¦¦¦ĄĄĄ¤¤¤ŁŁ˘˘˘ˇˇ  źźźžťťśś››šš™™——–••””“’’‘‘ŹŹŽŤŤŚ‹‹Š‰‰‡‡†…„„‚‚€~}}|{{zyxxwvvuttsrrqpponnmmlkkjjihhggffeeddccbba```__^^]]]\\[[[ZZZYYYXXWWWVVVUUUTTTTSSSSSRRRRQQQQPPPPOOOOONNNNNNNMMMMMMLLLLLKKKKKKKKKJJJJJJJJIIIIIIIIIIHHHHHHHHHHHHGGGGGGGGGGGGGGGFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸···············¶¶¶¶¶¶¶¶µµµµµµµµµµ´´´´´´´łłłłłłł˛˛˛˛˛˛˛±±±±±±°°°°ŻŻŻŻŻ®®®®®­­­­¬¬¬¬«««ŞŞŞ©©©©¨¨¨§§§¦¦¦ĄĄĄ¤¤ŁŁ˘˘˘ˇˇ  źźźžžťťśś››šš™——––••”““’’‘ŹŽŽŤŤŚ‹‹Š‰‰‡††…„„‚‚€~}}|{{zyyxwvvuttsrrqqpoonmmllkjjiihhggfeeddccbbaa```__^^]]]\\[[ZZZYYYXXXWWWVVVVUUUTTTSSSSRRRRQQQQQPPPPPOOOONNNNNNMMMMMMMLLLLLLLKKKKKKJJJJJJJJJJJIIIIIIIIHHHHHHHHHHHHHHHGGGGGGGGGGGFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸············¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµ´´´´´´´łłłłłłłłł˛˛˛˛˛±±±±±°°°°°°ŻŻŻŻŻŻ®®®®­­­¬¬¬¬«««««ŞŞŞ©©©©¨¨§§§¦¦¦ĄĄĄ¤¤ŁŁŁ˘˘˘ˇˇ  źźžžťťśś››šš™™——–••””““’‘‘ŹŹŽŽŤŚŚ‹ŠŠ‰‡††…„„‚‚€~}}|{{zyyxwwvuutssrqqpponnmllkkjjihhggffeeddccbbaa``__^^]]]\\\[[ZZZYYYXXXWWVVVVUUUTTTTTSSSSRRRQQQQPPPPPPOOOOOONNNNNMMMMMLLLLLLLLLKKKKKKKJJJJJJJJIIIIIIIIIIIHHHHHHHHHHHHGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸·········¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµ´´´´´´´´´łłłłłł˛˛˛˛˛˛˛±±±±±±°°°°°ŻŻŻŻ®®®®®­­­­­¬¬¬¬««««ŞŞŞ©©©¨¨¨§§§§¦¦¦ĄĄ¤¤¤ŁŁ˘˘˘ˇˇ  źźźžžťťśś››šš™™——–••””“’’‘‘ŹŹŽŤŤŚŚ‹ŠŠ‰‡††…„„‚‚€~}}|{{zyyxwwvuutssrrqpponnmmlkkjjihhggffeeddccbbaa```__^^]]]\\[[[ZZYYYXXXXWWWVVVUUUTTTTSSSSRRRRRQQQQQPPPPOOOOONNNNNNMMMMMMMLLLLLLKKKKKKKKKJJJJJJJIIIIIIIIIIIIHHHHHHHHHHGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸·············¶¶¶¶¶¶¶¶¶µµµµµµµµµµµ´´´´´´´łłłłłłł˛˛˛˛˛˛˛˛±±±±±°°°°ŻŻŻŻŻŻ®®®®®­­­­¬¬¬««««ŞŞŞŞ©©©©¨¨¨§§¦¦¦ĄĄĄ¤¤¤ŁŁŁ˘˘˘ˇˇ  źźžžťťśśś››šš™——––••””“’’‘ŹŹŽŤŤŚ‹‹Š‰‰‡††…„„‚‚€~}}|{{zyyxwwvvuttsrrqppoonmmlkkjjiihhggfeeddcccbbaa``__^^]]]\\\[[[ZZZYYYXXWWWVVVVUUUUTTTTSSSRRRRQQQQQPPPPPPOOOONNNNNMMMMMMMMLLLLLLLKKKKKKKJJJJJJJJJJJIIIIIIIIHHHHHHHHHHHHHHGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸···············¶¶¶¶¶¶¶¶¶µµµµµµµµµµ´´´´´´´łłłłłłłłł˛˛˛˛˛±±±±±±°°°°°°ŻŻŻŻŻ®®®®­­­­­¬¬¬¬««««ŞŞŞ©©©¨¨¨¨§§§¦¦¦ĄĄĄ¤¤ŁŁŁ˘˘ˇˇˇ  źźźžžťťśś››šš™™——–••””““’‘‘ŹŹŽŤŤŚ‹‹Š‰‰‡‡†……„‚€~~}||{zzyxxwvvuttsrrqppoonnmllkkjjihhggffeeddccbbaa```__^^^]]\\\[[ZZZYYYXXXWWWWVVVUUUTTTTSSSSRRRRRQQQQPPPPPOOOOOONNNNNNMMMMMLLLLLLLLLKKKKKKKJJJJJJJJJJIIIIIIIIIHHHHHHHHHHHHHHHGGGGGGGGGGGGFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸···········¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµ´´´´´´´´´´łłłłłł˛˛˛˛˛˛˛±±±±±±±°°°°ŻŻŻŻŻ®®®®®®­­­­¬¬¬¬«««ŞŞŞŞ©©©©¨¨¨§§§¦¦¦ĄĄĄ¤¤¤ŁŁŁ˘˘ˇˇ  źźźžžťťťśś›šš™™——––••””““’‘‘ŹŹŽŽŤŚŚ‹‹Š‰‰‡‡†……„‚€~~}||{zzyxxwvvuttssrqqpponnmllkkjjiihhggffeedccbbbaa```__^^]]\\\[[[ZZZYYYXXXWWWVVVVUUUUTTTSSSSRRRRQQQQQQPPPPPOOOONNNNNNNMMMMMMMLLLLLLKKKKKKKKKKJJJJJJJIIIIIIIIIIIIIHHHHHHHHHHHGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸··········¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµ´´´´´´´´łłłłłłł˛˛˛˛˛˛˛˛±±±±±°°°°°ŻŻŻŻŻŻ®®®®­­­­¬¬¬¬¬««««ŞŞŞŞ©©¨¨¨¨§§§§¦¦¦ĄĄ¤¤¤ŁŁŁ˘˘ˇˇˇ  źźžžťťťśś››šš™™——––••”““’’‘‘ŹŹŽŽŤŚŚ‹ŠŠ‰‰‡‡†……„‚€~~}||{zzyxxwvvuutssrqqpponnmmllkjjiihhggffeeddccbbbaa``__^^^]]\\\[[[ZZYYYXXXXWWWWVVUUUUTTTTSSSSSRRRRQQQQPPPPPPOOOOONNNNNMMMMMMMMLLLLLLLKKKKKKKKJJJJJJJJJJIIIIIIIIIIIHHHHHHHHHHGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸··············¶¶¶¶¶¶¶¶¶µµµµµµµµµµµ´´´´´´´łłłłłłłłł˛˛˛˛˛±±±±±±±°°°°°°ŻŻŻŻ®®®®®­­­­­¬¬¬¬«««ŞŞŞŞŞ©©©¨¨¨¨§§¦¦¦ĄĄĄĄ¤¤¤ŁŁ˘˘˘ˇˇ  źźźžžťťśś›››šš™™—––••””““’’‘ŹŹŽŤŤŚŚ‹ŠŠ‰‰‡‡†……„‚€~~}||{zzyxxwvvuutssrrqppoonmmllkkjjiihggffeedddccbbaa```__^^]]]\\[[[ZZZZYYYXXWWWWVVVUUUUUTTTSSSSRRRRRQQQQQPPPPOOOOOONNNNNNNMMMMMLLLLLLLLLKKKKKKKJJJJJJJJJJJIIIIIIIIIHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸··············¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµ´´´´´´´´´´łłłłłł˛˛˛˛˛˛˛±±±±±±±°°°°°ŻŻŻŻŻŻ®®®®®­­­¬¬¬¬¬««««ŞŞŞŞ©©©¨¨¨§§§§¦¦¦ĄĄĄ¤¤ŁŁŁ˘˘˘ˇˇˇ  źźžžžťťśś››šš™™——––••””““’‘‘ŹŹŽŤŤŚŚ‹ŠŠ‰‡‡†……„‚€~~}||{zzyxxwwvuutssrrqppoonnmllkkjjiihhggffeeddccbbaaa``__^^^]]]\\\[[ZZZYYYXXXXWWWVVVUUUUTTTTSSSSSRRRQQQQQPPPPPPOOOOONNNNNNNMMMMMMMLLLLLLKKKKKKKKKKJJJJJJJJIIIIIIIIIIIIHHHHHHHHHHHHHHGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸···········¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµ´´´´´´´´´łłłłłłł˛˛˛˛˛˛˛˛±±±±±°°°°°°ŻŻŻŻŻŻ®®®®­­­­­¬¬¬¬¬«««ŞŞŞŞ©©©©¨¨¨§§§¦¦¦ĄĄĄ¤¤¤ŁŁŁ˘˘ˇˇˇ  źźźžžťťśś›››šš™™——––••”““’’‘‘ŹŹŽŽŤŤŚ‹‹ŠŠ‰‡††……„‚€~~}||{zzyyxwwvuuttsrrqqpponnmmllkjjiihhggffeedddccbbaa```__^^^]]\\\[[[ZZZYYYXXXWWWVVVVUUUUTTTSSSSSRRRRRQQQQPPPPPPOOOOOONNNNNMMMMMMMMLLLLLLLKKKKKKKKKJJJJJJJJIIIIIIIIIIIIIIHHHHHHHHHHHGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸············¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµ´´´´´´łłłłłłłłłł˛˛˛˛˛±±±±±±±±°°°°°ŻŻŻŻŻ®®®®®®­­­­¬¬¬¬««««ŞŞŞŞŞ©©¨¨¨¨§§§¦¦¦¦ĄĄ¤¤¤ŁŁŁ˘˘˘ˇˇˇ  źźžžžťťśś››šš™™™——––••””““’’‘‘ŹŹŽŽŤŚŚ‹‹Š‰‰‡††……„‚€~~}||{zzyyxwwvvuttssrqqpponnmmllkkjjiihhgfffeeddccbbaaa``__^^^]]]\\\[[[ZZYYYYXXXWWWWVVUUUUUTTTTSSSSRRRRQQQQQQPPPPPOOOOONNNNNNNNMMMMMLLLLLLLLLLKKKKKKJJJJJJJJJJJJIIIIIIIIIIHHHHHHHHHHHHGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸················¶¶¶¶¶¶¶¶¶µµµµµµµµµµ´´´´´´´´´łłłłłłł˛˛˛˛˛˛˛˛±±±±±±°°°°°ŻŻŻŻŻŻŻ®®®®­­­­­¬¬¬¬¬«««ŞŞŞŞ©©©©¨¨¨¨§§¦¦¦ĄĄĄĄ¤¤¤ŁŁ˘˘˘ˇˇˇ  źźźžžťťśśś››šš™™——––••””““’‘‘ŹŹŽŽŤŚŚ‹‹Š‰‰‡††…„„‚€~~}||{{zyyxwwvvuttssrqqppoonnmllkkjjiihhggffeeddcccbbaa```__^^^]]]\\[[[ZZZZYYYXXWWWWVVVVUUUUTTTSSSSSRRRRRQQQQPPPPPPPOOOOONNNNNNMMMMMMMMLLLLLLLKKKKKKKKKJJJJJJJJJJIIIIIIIIIHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸··············¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµ´´´´´´´´´´łłłłłłł˛˛˛˛˛˛˛˛±±±±±°°°°°°°ŻŻŻŻŻ®®®®®­­­­­­¬¬¬««««ŞŞŞŞŞ©©©¨¨¨§§§§¦¦¦ĄĄĄ¤¤¤ŁŁŁ˘˘˘ˇˇ  źźźžžžťťśś››ššš™———––•””““’’‘‘ŹŹŽŤŤŚŚ‹ŠŠ‰‰‡‡††…„„‚€~~}||{{zyyxxwvvuutssrrqppoonnmmllkkjiihhhggfeeeddccbbaaa```__^^]]]\\\[[[ZZZYYYXXXXWWWVVVUUUUUTTTTSSSRRRRRRQQQQQPPPPPOOOOOOONNNNNMMMMMMMMLLLLLLLKKKKKKKKKKJJJJJJJJIIIIIIIIIIIIIHHHHHHHHHHHHHHGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸············¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµ´´´´´´´łłłłłłłłłł˛˛˛˛˛±±±±±±±±°°°°°ŻŻŻŻŻŻ®®®®®®­­­¬¬¬¬¬«««««ŞŞŞ©©©©¨¨¨¨§§§¦¦¦ĄĄĄ¤¤¤¤ŁŁ˘˘˘ˇˇˇ  źźžžžťťśśś››šš™™——––••””““’’‘‘ŹŹŽŽŤŤŚŚ‹ŠŠ‰‰‡‡††…„„‚€~~}||{{zyyxxwvvuutssrrqqpponnmmllkkjjiihhggffeeddcccbbaaa``__^^^]]]\\[[[[ZZZYYYXXXWWWWVVVVUUUTTTTTSSSSSRRRQQQQQQPPPPPPOOOOONNNNNNNNMMMMMLLLLLLLLLLKKKKKKKJJJJJJJJJJJIIIIIIIIIIIIHHHHHHHHHHHHGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBB˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸·············¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµ´´´´´´´´´łłłłłłł˛˛˛˛˛˛˛˛±±±±±±°°°°°°ŻŻŻŻŻŻŻ®®®®­­­­­¬¬¬¬««««ŞŞŞŞŞ©©©¨¨¨§§§§¦¦¦¦ĄĄ¤¤¤ŁŁŁŁ˘˘ˇˇˇ  źźźžžťťťśś›››š™™™——––••””““’’‘‘ŹŹŽŽŤŤŚ‹‹ŠŠ‰‡‡††…„„‚€~~}||{{zyyxxwwvuuttsrrqqpponnmmllkkjjiihhggfffedddccbbbaa```__^^^]]\\\\[[[ZZYYYYXXXXWWWVVVUUUUUTTTTSSSSRRRRRQQQQPPPPPPPOOOOOONNNNNNMMMMMMMMLLLLLLLKKKKKKKKKJJJJJJJJJJJIIIIIIIIIIHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBB˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸···············¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµ´´´´´´´´´´´łłłłłłł˛˛˛˛˛˛˛˛±±±±±±°°°°°°°ŻŻŻŻ®®®®®®®­­­­¬¬¬¬«««««ŞŞŞ©©©©¨¨¨¨§§§¦¦¦¦ĄĄĄ¤¤¤ŁŁ˘˘˘˘ˇˇˇ  źźžžžťťťś›››šš™™———––•””““’’‘‘ŹŹŽŽŤŚŚ‹‹ŠŠ‰‡‡†……„„‚‚€~~}}|{{zzyxxwwvuuttssrqqppoonnmmllkkjiihhhggffeedddcbbbaaa``__^^^]]]]\\[[[ZZZYYYYXXXWWWWVVVVUUUTTTTTSSSSRRRRQQQQQQQPPPPOOOOOOONNNNNNMMMMMMMMLLLLLLLKKKKKKKKKKKJJJJJJJJIIIIIIIIIIIIHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBB˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸··············¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµ´´´´´´´´łłłłłłłłłł˛˛˛˛˛˛±±±±±±±±°°°°ŻŻŻŻŻŻŻ®®®®®­­­­­¬¬¬¬¬«««ŞŞŞŞŞ©©©©¨¨¨§§§§¦¦¦ĄĄĄ¤¤¤ŁŁŁ˘˘˘ˇˇˇ  źźźžžťťťśś››ššš™™——––••””““’’‘‘ŹŽŽŤŤŚŚ‹‹Š‰‰‡‡†……„„‚‚€~~}}|{{zzyxxwwvvuttssrrqqpoonnmmllkkjjiihhggffeeeddccbbbaa```__^^^]]]\\\[[[ZZZYYYXXXXWWWVVVVUUUUUTTTSSSSSRRRRRQQQQQPPPPPPPOOOONNNNNNNNMMMMMMLLLLLLLLLLKKKKKKKKJJJJJJJJJJIIIIIIIIIIIIIHHHHHHHHHHHHHHGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBB˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸·············¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµ´´´´´´´´´łłłłłłł˛˛˛˛˛˛˛˛˛±±±±±°°°°°°°ŻŻŻŻŻŻ®®®®®­­­­­¬¬¬¬«««««ŞŞŞŞ©©©©¨¨¨¨§§§¦¦¦ĄĄĄĄ¤¤¤ŁŁ˘˘˘ˇˇˇ  źźźžžžťťśśś››šš™™——––••””““’’‘ŹŹŽŽŤŤŚŚ‹‹Š‰‰‡‡†……„„‚‚€~~}}|{{zzyxxwwvvuttssrrqqppoonmmllkkjjiihhgggffeeddcccbbaaa```__^^^]]]\\[[[ZZZZYYYXXXWWWWVVVVUUUUTTTTTSSSSRRRRRQQQQQPPPPPPOOOOOOONNNNNMMMMMMMMMLLLLLLLKKKKKKKKKJJJJJJJJJJJJIIIIIIIIIIIHHHHHHHHHHHHHGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBB˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸···············¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµ´´´´´´´´´´´łłłłłłł˛˛˛˛˛˛˛˛±±±±±±±°°°°°°ŻŻŻŻŻŻ®®®®®­­­­¬¬¬¬¬¬««««ŞŞŞŞ©©©©©¨¨§§§§¦¦¦¦ĄĄ¤¤¤¤ŁŁŁ˘˘ˇˇˇ   źźźžžťťťśś››ššš™™——––••”””“’’‘‘ŹŹŽŽŤŤŚ‹‹ŠŠ‰‰‡††……„„‚‚€~~}}|{{zzyyxwwvvuuttsrrqqppoonnmmlkkkjjiihhggffeeeddccbbbaa```___^^^]]\\\[[[[ZZYYYYXXXXWWVVVVVUUUUTTTTSSSSSRRRRRQQQQQPPPPPPOOOOOONNNNNNNMMMMMMMMLLLLLLLKKKKKKKKKKKJJJJJJJJJIIIIIIIIIIIHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBB˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸················¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµ´´´´´´´´łłłłłłłłłł˛˛˛˛˛˛±±±±±±±±°°°°°ŻŻŻŻŻŻŻ®®®®­­­­­­¬¬¬¬«««««ŞŞŞŞ©©©©¨¨¨¨§§§¦¦¦¦ĄĄĄ¤¤¤ŁŁŁ˘˘˘ˇˇˇ  źźźžžžťťśśś››šš™™™——––••””““’’‘‘ŹŹŽŽŤŤŚ‹‹ŠŠ‰‰‡††……„„‚‚€~~}}|{{zzyyxwwvvuuttsrrqqppoonnmmllkkjjiihhggfffeeddcccbbaaa```__^^^]]]\\\[[[ZZZYYYYXXXWWWWVVVVUUUUTTTTTSSSRRRRRRRQQQQPPPPPPPOOOOONNNNNNNNMMMMMMLLLLLLLLLLKKKKKKKKJJJJJJJJJJIIIIIIIIIIIIIHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBB˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸··············¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµ´´´´´´´´łłłłłłłł˛˛˛˛˛˛˛˛˛±±±±±°°°°°°°°ŻŻŻŻŻ®®®®®®­­­­­¬¬¬¬¬««««ŞŞŞŞ©©©©©¨¨¨§§§§¦¦¦ĄĄĄ¤¤¤¤ŁŁŁ˘˘ˇˇˇ   źźžžžťťťśś›››šš™™——–––••””““’’‘‘ŹŹŽŽŤŚŚ‹‹ŠŠ‰‰‡‡††……„‚‚€~~}}||{zzyyxxwvvuuttssrqqppoonnmmllkkjjiiihhggffeedddccbbbaaa``___^^^]]\\\[[[[ZZZYYYXXXXWWWVVVVVUUUUTTTTSSSSSRRRRRQQQQQQPPPPPOOOOOOONNNNNNMMMMMMMMMLLLLLLLLKKKKKKKKJJJJJJJJJJJJIIIIIIIIIIIIIHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸··············¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµ´´´´´´´´´´´łłłłłłł˛˛˛˛˛˛˛˛±±±±±±±°°°°°°ŻŻŻŻŻŻŻ®®®®®­­­­­¬¬¬¬¬««««ŞŞŞŞŞ©©©¨¨¨¨§§§§¦¦¦ĄĄĄĄ¤¤¤ŁŁŁ˘˘˘ˇˇ   źźźžžťťťśśś››ššš™™——––••”””“’’’‘ŹŹŽŽŤŤŚŚ‹‹ŠŠ‰‡‡††……„‚‚€~~}}||{zzyyxxwwvuuttssrrqqppoonmmmlkkkjjiihhggffeeeddcccbbbaa```___^^]]]\\\[[[ZZZZYYYXXXXWWWWVVVUUUUUTTTTSSSSSRRRRRQQQQQPPPPPPPOOOOONNNNNNNNMMMMMMMMLLLLLLLKKKKKKKKKKKJJJJJJJJJJJIIIIIIIIIIIHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸···············¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµ´´´´´´´´´łłłłłłłłłł˛˛˛˛˛˛˛±±±±±±±°°°°°°ŻŻŻŻŻŻ®®®®®®­­­­­¬¬¬¬¬«««««ŞŞŞ©©©©©¨¨¨§§§§¦¦¦¦ĄĄĄ¤¤¤ŁŁŁ˘˘˘ˇˇˇ   źźźžžťťśśś›››šš™™———––••””““’’‘‘ŹŹŽŽŤŤŚŚ‹‹ŠŠ‰‡‡††……„‚‚€~~}}||{zzyyxxwwvuuttssrrqqppoonnmmllkkjjiihhhggffeedddcccbbaa```___^^^]]]\\\[[[ZZZYYYYXXXXWWWVVVVVUUUTTTTTSSSSSRRRRRQQQQQQPPPPPPOOOOOONNNNNNNMMMMMMMLLLLLLLLLLKKKKKKKKKJJJJJJJJJJIIIIIIIIIIIIHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸···············¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµ´´´´´´´´łłłłłłłł˛˛˛˛˛˛˛˛˛˛±±±±±°°°°°°°°ŻŻŻŻŻ®®®®®®­­­­­¬¬¬¬¬««««ŞŞŞŞŞ©©©¨¨¨¨¨§§§¦¦¦¦ĄĄĄĄ¤¤ŁŁŁŁ˘˘˘ˇˇ   źźźžžžťťśś›››ššš™™——–––••””““’’‘‘ŹŹŽŽŤŤŚŚ‹‹Š‰‰‡‡††……„‚‚€~~}}||{zzyyxxwwvvuttssrrqqppoonnmmllkkjjiiihhggffeeedddccbbaaa```___^^]]]\\\\[[ZZZZYYYYXXXWWWWWVVVUUUUUTTTTSSSSSRRRRRQQQQQQPPPPPOOOOOOOONNNNNMMMMMMMMMMLLLLLLLLKKKKKKKKJJJJJJJJJJJJIIIIIIIIIIIIIIHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸···············¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµ´´´´´´´´´´´łłłłłłł˛˛˛˛˛˛˛˛±±±±±±±±°°°°°ŻŻŻŻŻŻŻŻ®®®®­­­­­­­¬¬¬««««««ŞŞŞ©©©©©¨¨¨¨§§§§¦¦¦ĄĄĄ¤¤¤¤ŁŁŁ˘˘˘ˇˇˇ  źźźźžžťťťśś››ššš™™——––••””“““’’‘‘ŹŹŽŽŤŤŚ‹‹ŠŠ‰‰‡‡††…„„‚‚€~~}}||{{zyyxxwwvvuuttsrrqqppoonnmmlllkkjjiihhgggffeeeddccbbbaa````__^^^]]]\\\[[[[ZZZYYYXXXXWWWWVVVVVUUUTTTTTTSSSRRRRRRRQQQQPPPPPPPPOOOOONNNNNNNNMMMMMMMMLLLLLLLKKKKKKKKKKKJJJJJJJJJJJJIIIIIIIIIIIIHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸···············¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµ´´´´´´´´´´łłłłłłłłłł˛˛˛˛˛˛˛±±±±±±±°°°°°°°ŻŻŻŻŻ®®®®®®®­­­­¬¬¬¬¬¬««««ŞŞŞŞŞ©©©©¨¨¨¨§§§§¦¦¦ĄĄĄĄ¤¤¤ŁŁŁ˘˘˘ˇˇˇ   źźźžžžťťśśś››šš™™™———––••””““’’‘‘ŹŹŽŽŤŤŚŚ‹‹ŠŠ‰‰‡‡††…„„‚‚€~~}}||{{zyyxxwwvvuuttssrrqqppoonnmmllkkjjiihhhggfffeeddcccbbaaa```___^^^]]]\\\[[[ZZZZYYYXXXXWWWWVVVVUUUUUTTTTSSSSSSRRRRQQQQQQQPPPPPOOOOOOONNNNNNNMMMMMMMLLLLLLLLLLKKKKKKKKKKJJJJJJJJJJIIIIIIIIIIIIHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸················¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµ´´´´´´´´´łłłłłłłł˛˛˛˛˛˛˛˛˛˛±±±±±±°°°°°°°ŻŻŻŻŻŻ®®®®®­­­­­­¬¬¬¬¬«««««ŞŞŞŞ©©©©¨¨¨¨§§§§¦¦¦¦ĄĄĄ¤¤¤¤ŁŁ˘˘˘˘ˇˇˇ  źźźźžžťťťśś›››šš™™——–––••””““’’‘‘ŹŹŽŽŤŤŚŚ‹‹ŠŠ‰‰‡‡†……„„‚‚€~~}}||{{zzyxxwwvvuuttssrrqqppoonnmmllkkjjiiihhgggffeedddccbbbaa````__^^^]]]]\\[[[[ZZZYYYYXXXXWWWWVVVVUUUUTTTTTSSSSSRRRRRRQQQQQPPPPPPOOOOOOONNNNNNMMMMMMMMMMLLLLLLLLKKKKKKKKKJJJJJJJJJJJIIIIIIIIIIIIIHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸················¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµ´´´´´´´´´´´łłłłłłł˛˛˛˛˛˛˛˛±±±±±±±±±°°°°°ŻŻŻŻŻŻŻ®®®®®®­­­­­¬¬¬¬¬«««««ŞŞŞŞ©©©©¨¨¨¨§§§§¦¦¦¦ĄĄĄĄ¤¤ŁŁŁŁ˘˘˘ˇˇˇ   źźźžžžťťśśś››ššš™™———––••””“““’’‘‘ŹŹŽŽŤŤŚŚ‹‹ŠŠ‰‰‡††……„„‚‚€~~}}||{{zzyyxwwvvuuttssrrqqppoonnmmlllkkjjiihhhggffeeeddcccbbaaa```___^^^]]]\\\\[[ZZZZYYYYXXXXWWWWVVVVUUUUTTTTTSSSSSRRRRRQQQQQQPPPPPPPOOOOONNNNNNNNNMMMMMMMLLLLLLLLKKKKKKKKKKKJJJJJJJJJJJJIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸················¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµ´´´´´´´´´´´łłłłłłłłłł˛˛˛˛˛˛˛±±±±±±±°°°°°°°°ŻŻŻŻŻ®®®®®®®­­­­­¬¬¬¬¬«««««ŞŞŞŞ©©©©©¨¨¨¨§§§¦¦¦¦ĄĄĄĄ¤¤¤ŁŁŁ˘˘˘˘ˇˇ   źźźžžžťťťśś›››šš™™™——–––••””““’’‘‘‘ŹŹŽŽŤŤŚŚ‹‹ŠŠ‰‡‡††……„„‚‚€~~}}||{{zzyyxxwwvuuttssrrqqppoonnnmmllkkjjiiihhggfffeedddccbbbaaa```___^^]]]]\\\[[[ZZZZYYYYXXXWWWWVVVVVUUUUTTTTTSSSSSRRRRRQQQQQQPPPPPPOOOOOOOONNNNNNNMMMMMMMLLLLLLLLLLKKKKKKKKKKKJJJJJJJJJJJIIIIIIIIIIIIHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸················¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµ´´´´´´´´´łłłłłłłłł˛˛˛˛˛˛˛˛˛˛±±±±±±±°°°°°°ŻŻŻŻŻŻŻ®®®®®­­­­­­¬¬¬¬¬«««««ŞŞŞŞ©©©©©¨¨¨¨§§§§¦¦¦ĄĄĄĄ¤¤¤ŁŁŁŁ˘˘˘ˇˇˇ   źźźžžťťťśśś››ššš™™——––••”””““’’‘‘ŹŹŽŽŤŤŤŚ‹‹‹Š‰‰‡‡††……„„‚‚€~~}}||{{zzyyxxwwvvutttsrrrqqppoonnmmllkkkjjiihhgggffeeeddcccbbbaa```___^^^]]]\\\\[[[ZZZZYYYXXXXWWWWVVVVVUUUUTTTTTSSSSSRRRRRRQQQQPPPPPPPPOOOOOONNNNNNNMMMMMMMMMMLLLLLLLLLKKKKKKKKKJJJJJJJJJJJIIIIIIIIIIIIHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸···············¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµ´´´´´´´´´´łłłłłłłłł˛˛˛˛˛˛˛±±±±±±±±±°°°°°°ŻŻŻŻŻŻ®®®®®®®­­­­­¬¬¬¬¬¬««««ŞŞŞŞŞ©©©©¨¨¨¨§§§§¦¦¦¦ĄĄĄ¤¤¤¤ŁŁŁ˘˘˘˘ˇˇ   źźźžžžťťťśś›››šš™™™———––••””“““’’‘‘ŹŹŽŽŤŤŚŚ‹‹ŠŠ‰‰‡‡††……„„‚‚€~~}}||{{zzyyxxwwvvuuttssrrqqppoonnmmlllkkjjiihhhggfffeedddccbbbaaa```___^^]]]]\\\[[[[ZZZYYYYXXXXWWWWVVVVUUUUUTTTTSSSSSSRRRRRQQQQQQQPPPPPPOOOOOONNNNNNNNNMMMMMMMLLLLLLLLLKKKKKKKKKKJJJJJJJJJJJJIIIIIIIIIIIIIIHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸·················¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµ´´´´´´´´´´´łłłłłłłłłł˛˛˛˛˛˛˛˛±±±±±±°°°°°°°°°ŻŻŻŻŻŻ®®®®®­­­­­­­¬¬¬¬««««««ŞŞŞŞ©©©©¨¨¨¨§§§§¦¦¦¦ĄĄĄĄ¤¤¤ŁŁŁŁ˘˘ˇˇˇˇ  źźźźžžťťťśśś›››šš™™™——––•••””““’’‘‘‘ŹŹŽŽŤŤŚŚ‹‹ŠŠ‰‰‡‡††……„„‚‚€~~}}||{{zzyyxxwwvvuuttssrrqqppoonnnmmllkkjjjiihhggfffeedddcccbbbaa````__^^^^]]\\\\[[[ZZZZYYYYXXXXWWWWVVVVUUUUTTTTTTSSSSRRRRRRRQQQQQPPPPPPOOOOOOOOONNNNNNMMMMMMMMLLLLLLLLLLKKKKKKKKKKKJJJJJJJJJJJJIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸·················¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµ´´´´´´´´´´łłłłłłłłł˛˛˛˛˛˛˛˛˛˛±±±±±±±°°°°°°ŻŻŻŻŻŻŻŻ®®®®®®­­­­­¬¬¬¬¬¬««««ŞŞŞŞŞ©©©©¨¨¨¨¨§§§¦¦¦¦ĄĄĄĄ¤¤¤¤ŁŁŁ˘˘˘ˇˇˇ   źźźžžžťťťśśś››ššš™™——––•••””““’’‘‘ŹŹŹŽŽŤŤŚŚ‹‹ŠŠ‰‰‡‡††……„„‚‚€~~}}||{{zzyyxxwwvvuuttssrrqqpppoonnmmllkkjjjiihhgggffeeeddcccbbbaaa```___^^^]]]\\\[[[[ZZZZYYYYXXXWWWWWVVVVUUUUUTTTTSSSSSSRRRRRQQQQQQPPPPPPPPOOOOOONNNNNNNMMMMMMMMMMLLLLLLLLLKKKKKKKKKKJJJJJJJJJJJIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸···············¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµ´´´´´´´´´´łłłłłłłłł˛˛˛˛˛˛˛±±±±±±±±±°°°°°°°ŻŻŻŻŻŻ®®®®®®®­­­­­¬¬¬¬¬««««««ŞŞŞŞ©©©©©¨¨¨§§§§§¦¦¦ĄĄĄĄ¤¤¤¤ŁŁŁ˘˘˘ˇˇˇˇ  źźźźžžťťťśśś›››šš™™™———––••”””““’’‘‘ŹŹŽŽŤŤŤŚŚ‹‹ŠŠ‰‰‡‡††……„„‚‚€~~}}||{{zzyyxxwwvvuuttssrrrqqppoonnmmllkkkjjiihhhggfffeedddcccbbbaa````__^^^^]]]\\\[[[[ZZZZYYYXXXXXWWWVVVVVUUUUTTTTTTSSSSSRRRRRQQQQQQQPPPPPPOOOOOOONNNNNNNNNMMMMMMMLLLLLLLLLKKKKKKKKKKJJJJJJJJJJJJIIIIIIIIIIIIIIHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸················¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµ´´´´´´´´´´´łłłłłłłłłł˛˛˛˛˛˛˛˛±±±±±±±°°°°°°°°ŻŻŻŻŻŻŻ®®®®®­­­­­­¬¬¬¬¬¬««««ŞŞŞŞŞŞ©©©¨¨¨¨¨§§§§¦¦¦¦ĄĄĄ¤¤¤¤ŁŁŁ˘˘˘˘ˇˇˇ   źźźžžžťťťśś›››ššš™™———––•••””“““’’‘‘ŹŹŽŽŤŤŚŚ‹‹‹ŠŠ‰‰‡‡††……„„‚‚€~~}}||{{zzyyxxwwvvuutttssrrqqppoonnmmlllkkjjjiihhhggffeeedddccbbbaaa```___^^^]]]]\\\[[[[ZZZYYYYXXXXWWWWWVVVUUUUUUTTTTSSSSSSRRRRRRQQQQQPPPPPPPOOOOOOOONNNNNNNMMMMMMMMLLLLLLLLLLKKKKKKKKKKKJJJJJJJJJJJJJIIIIIIIIIIIIIHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸·················¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµ´´´´´´´´´´´łłłłłłłłł˛˛˛˛˛˛˛˛˛˛±±±±±±±±°°°°°°ŻŻŻŻŻŻŻ®®®®®®®­­­­­¬¬¬¬¬««««««ŞŞŞŞ©©©©©¨¨¨¨§§§§¦¦¦¦ĄĄĄĄ¤¤¤ŁŁŁŁ˘˘˘ˇˇˇ   źźźžžžťťťśśś››ššš™™™———––•••””““’’‘‘‘ŹŹŽŽŤŤŚŚ‹‹ŠŠ‰‰‡‡‡††……„„‚‚€~~}}||{{zzyyxxxwwvvuuttssrrqqppoonnnmmllkkjjjiihhhggfffeeeddcccbbbaaa```___^^^]]]\\\\[[[ZZZZYYYYXXXXWWWWVVVVVUUUUTTTTTTSSSSSRRRRRQQQQQQQPPPPPPPOOOOOONNNNNNNNMMMMMMMMMMLLLLLLLLLKKKKKKKKKKJJJJJJJJJJJJIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸·················¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµ´´´´´´´´´´´łłłłłłłłł˛˛˛˛˛˛˛˛±±±±±±±±°°°°°°°°ŻŻŻŻŻŻ®®®®®®­­­­­­­¬¬¬¬«««««ŞŞŞŞŞŞ©©©¨¨¨¨¨¨§§§¦¦¦¦ĄĄĄĄ¤¤¤¤ŁŁŁ˘˘˘ˇˇˇˇ   źźźžžžťťťśś›››ššš™™——–––••”””““’’‘‘ŹŹŹŽŽŤŤŚŚ‹‹ŠŠ‰‰‡‡††……„„‚‚€€~~}}||{{zzyyxxwwvvuuttssrrqqpppoonnmmllkkkjjiiihhgggffeeedddccbbbaaa```___^^^^]]]\\\[[[[ZZZZYYYYXXXWWWWWVVVVUUUUUUTTTTTSSSSRRRRRRRQQQQQQPPPPPPOOOOOOOONNNNNNNNMMMMMMMMLLLLLLLLLKKKKKKKKKKJJJJJJJJJJJJIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸···············¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµ´´´´´´´´´´´łłłłłłłłłł˛˛˛˛˛˛˛˛˛±±±±±±±°°°°°°°ŻŻŻŻŻŻŻŻ®®®®®®­­­­­¬¬¬¬¬¬«««««ŞŞŞŞ©©©©©¨¨¨¨§§§§§¦¦¦ĄĄĄĄ¤¤¤¤ŁŁŁ˘˘˘˘ˇˇˇ   źźźžžžžťťśśś›››šš™™™———––•••””““’’’‘‘ŹŹŹŽŽŤŤŚŚ‹‹ŠŠ‰‰‡‡††……„„‚‚€€~~}}||{{zzyyxxwwvvuuttssrrqqpppoonnmmmllkkjjjiihhhggfffeedddcccbbaaaa```___^^^]]]]\\\[[[[ZZZZYYYXXXXXWWWVVVVVVUUUUTTTTTSSSSSSRRRRRQQQQQQPPPPPPPPOOOOOOONNNNNNNMMMMMMMMMLLLLLLLLLLKKKKKKKKKKKJJJJJJJJJJJJIIIIIIIIIIIIIIHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸···············¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµ´´´´´´´´´´łłłłłłłłłł˛˛˛˛˛˛˛˛˛˛±±±±±±±±±°°°°°°ŻŻŻŻŻŻ®®®®®®®­­­­­­­¬¬¬¬««««««ŞŞŞŞŞ©©©©¨¨¨¨¨§§§§¦¦¦¦ĄĄĄĄ¤¤¤ŁŁŁŁ˘˘˘ˇˇˇˇ  źźźźžžžťťťśś›››ššš™™———––•••””““’’’‘‘ŹŹŽŽŤŤŤŚŚ‹‹ŠŠ‰‰‡‡††……„„‚‚€€~~}}||{{zzyyxxwwvvuuttssrrrqqppoonnmmmllkkjjjiihhhgggffeeedddccbbbaaa````__^^^^]]]\\\\[[[ZZZZYYYYXXXXWWWWWVVVVUUUUUTTTTTTSSSSRRRRRRRQQQQQQQPPPPPPOOOOOONNNNNNNNNMMMMMMMMMMLLLLLLLLLLKKKKKKKKKKJJJJJJJJJJJJJIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸··················¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµ´´´´´´´´´´łłłłłłłłłł˛˛˛˛˛˛˛˛±±±±±±±±°°°°°°°°°ŻŻŻŻŻŻ®®®®®®­­­­­­¬¬¬¬¬¬«««««ŞŞŞŞŞ©©©©©¨¨¨§§§§§¦¦¦¦ĄĄĄĄ¤¤¤¤ŁŁŁ˘˘˘˘ˇˇˇ   źźźžžžťťťśśś›››šš™™™——–––••”””““’’‘‘‘ŹŹŽŽŤŤŚŚŚ‹‹ŠŠ‰‰‡‡††……„„‚‚€€~~}}||{{zzyyxxwwvvuuttsssrrqqppoonnnmmllkkkjjiiihhgggfffeedddcccbbbaaa```___^^^]]]]\\\[[[[ZZZZYYYYXXXXXWWWVVVVVUUUUUTTTTTSSSSSSRRRRRRQQQQQQPPPPPPOOOOOOOOONNNNNNNNMMMMMMMMLLLLLLLLLLKKKKKKKKKKJJJJJJJJJJJJJIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCC»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸··················¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµ´´´´´´´´´´´łłłłłłłłłł˛˛˛˛˛˛˛˛˛±±±±±±±±°°°°°°ŻŻŻŻŻŻŻŻ®®®®®®®­­­­­¬¬¬¬¬«««««««ŞŞŞŞ©©©©¨¨¨¨¨§§§§¦¦¦¦ĄĄĄĄ¤¤¤¤ŁŁŁŁ˘˘˘ˇˇˇˇ  źźźźžžžťťśśśś››ššš™™™———––•••””“““’’‘‘ŹŹŹŽŽŤŤŚŚ‹‹ŠŠŠ‰‰‡‡††……„„‚‚€€~~}}||{{zzyyxxwwvvuuuttssrrqqpppoonnmmlllkkjjjiihhhggfffeeeddccccbbaaa````__^^^^]]]\\\\[[[[ZZZZYYYYXXXXWWWWWVVVVUUUUTTTTTTTSSSSSRRRRRQQQQQQQPPPPPPPPOOOOOONNNNNNNNMMMMMMMMMLLLLLLLLLLKKKKKKKKKKKJJJJJJJJJJJJIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸···············¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµ´´´´´´´´´´´łłłłłłłłłł˛˛˛˛˛˛˛˛˛˛±±±±±±±±±°°°°°°°ŻŻŻŻŻŻŻ®®®®®®­­­­­­­¬¬¬¬¬«««««ŞŞŞŞŞ©©©©©¨¨¨¨§§§§§¦¦¦¦ĄĄĄ¤¤¤¤ŁŁŁŁ˘˘˘˘ˇˇˇ   źźźžžžťťťśśś›››ššš™™——–––•••””““’’’‘‘ŹŹŹŽŽŤŤŚŚ‹‹ŠŠŠ‰‰‡‡††……„„‚‚€€~~}}||{{zzyyxxwwvvuuuttssrrqqpppoonnmmmllkkjjjiiihhgggffeeedddcccbbbaaa```___^^^]]]]\\\\[[[[ZZZYYYYXXXXXWWWWVVVVVUUUUUTTTTTSSSSSRRRRRRRQQQQQQPPPPPPPOOOOOOONNNNNNNNNMMMMMMMMMMLLLLLLLLLLKKKKKKKKKKKJJJJJJJJJJJJJIIIIIIIIIIIIIIHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸·················¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµ´´´´´´´´´´´łłłłłłłłłł˛˛˛˛˛˛˛˛˛±±±±±±±°°°°°°°°°ŻŻŻŻŻŻŻ®®®®®®­­­­­­¬¬¬¬¬¬««««««ŞŞŞŞ©©©©©¨¨¨¨¨§§§¦¦¦¦¦ĄĄĄĄ¤¤¤ŁŁŁŁŁ˘˘˘ˇˇˇ   źźźžžžžťťťśś›››ššš™™™———–––••””“““’’‘‘‘ŹŹŽŽŽŤŤŚŚ‹‹ŠŠ‰‰‰‡‡††……„„‚‚€€~~}}||{{zzyyxxwwvvvuuttssrrqqqppoonnnmmlllkkjjiiihhhggfffeeedddccbbbaaaa```___^^^]]]\\\\\[[[ZZZZYYYYXXXXWWWWWVVVVVUUUUTTTTTTSSSSSSRRRRRRQQQQQQPPPPPPPOOOOOOOOONNNNNNNMMMMMMMMMLLLLLLLLLLKKKKKKKKKKKJJJJJJJJJJJJJIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸···················¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµ´´´´´´´´´´´łłłłłłłłłł˛˛˛˛˛˛˛˛˛˛±±±±±±±°°°°°°°ŻŻŻŻŻŻŻ®®®®®®®­­­­­­­¬¬¬¬¬«««««ŞŞŞŞŞŞ©©©©¨¨¨¨§§§§§¦¦¦¦ĄĄĄĄ¤¤¤¤ŁŁŁ˘˘˘˘ˇˇˇˇ  źźźźžžžťťťśśś›››ššš™™———––•••””“““’’‘‘ŹŹŽŽŤŤŤŚŚ‹‹ŠŠ‰‰‡‡‡††……„„‚‚€€~~}}||{{zzyyxxxwwvvuuttssrrrqqppooonnmmlllkkjjjiihhhgggffeeedddcccbbbaaa````__^^^^]]]]\\\[[[[ZZZZYYYYXXXXXWWWWVVVVUUUUUUTTTTTSSSSSRRRRRRRQQQQQQQPPPPPPPOOOOOOONNNNNNNMMMMMMMMMMLLLLLLLLLLKKKKKKKKKKKJJJJJJJJJJJJIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸··················¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµ´´´´´´´´´´´łłłłłłłłłł˛˛˛˛˛˛˛˛˛˛±±±±±±±±±°°°°°°°°ŻŻŻŻŻŻŻ®®®®®®­­­­­­¬¬¬¬¬¬««««««ŞŞŞŞ©©©©©¨¨¨¨¨§§§§¦¦¦¦ĄĄĄĄĄ¤¤¤ŁŁŁŁ˘˘˘ˇˇˇˇ   źźźžžžťťťťśś››››šš™™™——–––•••””““’’’‘‘ŹŹŹŽŽŤŤŚŚŚ‹‹ŠŠ‰‰‡‡‡††……„„‚‚€€~~}}||{{zzyyxxxwwvvuuttsssrrqqpppoonnmmmllkkjjjiiihhgggfffeeddddccbbbbaaa```___^^^^]]]\\\\[[[ZZZZZYYYYXXXXWWWWWVVVVVUUUUTTTTTTSSSSSSRRRRRRQQQQQQPPPPPPPOOOOOOOONNNNNNNNNMMMMMMMMMMLLLLLLLLLLKKKKKKKKKKKJJJJJJJJJJJJJIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸···············¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµ´´´´´´´´´´´´łłłłłłłłłł˛˛˛˛˛˛˛˛˛±±±±±±±±°°°°°°°°ŻŻŻŻŻŻŻŻ®®®®®®®­­­­­¬¬¬¬¬¬«««««ŞŞŞŞŞŞ©©©©¨¨¨¨¨§§§§§¦¦¦¦ĄĄĄ¤¤¤¤¤ŁŁŁ˘˘˘˘ˇˇˇˇ  źźźźžžžťťťśśś›››ššš™™™———–––••””“““’’’‘‘ŹŹŹŽŽŤŤŚŚ‹‹‹ŠŠ‰‰‡‡‡††……„„‚‚€€~~}}||{{zzyyxxxwwvvuutttssrrqqpppoonnmmmlllkkjjiiihhhggfffeeedddcccbbbaaa````__^^^^]]]]\\\[[[[[ZZZYYYYXXXXXWWWWWVVVVUUUUUUTTTTTSSSSSSRRRRRQQQQQQQPPPPPPPPOOOOOOOONNNNNNNNMMMMMMMMMLLLLLLLLLLKKKKKKKKKKKKJJJJJJJJJJJJJJIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸··················¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµ´´´´´´´´´´´łłłłłłłłłł˛˛˛˛˛˛˛˛˛˛±±±±±±±±°°°°°°°ŻŻŻŻŻŻŻ®®®®®®­­­­­­­¬¬¬¬¬¬¬«««««ŞŞŞŞ©©©©©©¨¨¨¨§§§§¦¦¦¦¦ĄĄĄĄ¤¤¤¤ŁŁŁŁ˘˘˘ˇˇˇ    źźźžžžťťťťśśś››šššš™™———––•••””“““’’‘‘‘ŹŹŽŽŽŤŤŚŚ‹‹ŠŠŠ‰‰‡‡†††……„„‚‚€€~~}}||{{zzyyyxxwwvvuuuttssrrqqqppoonnnmmlllkkjjjiihhhgggffeeeeddcccbbbbaaa```____^^^]]]\\\\[[[[ZZZZYYYYYXXXXWWWWVVVVVVUUUUTTTTTSSSSSSSRRRRRRRQQQQQQPPPPPPPOOOOOOONNNNNNNNMMMMMMMMMMLLLLLLLLLLKKKKKKKKKKKJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDD»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸····················¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµ´´´´´´´´´´´łłłłłłłłłł˛˛˛˛˛˛˛˛˛˛±±±±±±±±±°°°°°°°°ŻŻŻŻŻŻŻŻ®®®®®®­­­­­­¬¬¬¬¬¬««««««ŞŞŞŞŞ©©©©©¨¨¨¨§§§§§¦¦¦¦ĄĄĄĄ¤¤¤¤¤ŁŁŁ˘˘˘ˇˇˇˇ   źźźžžžžťťťśśś›››ššš™™™——–––••”””““’’’‘‘ŹŹŽŽŤŤŤŚŚ‹‹ŠŠŠ‰‰‡‡††………„„‚‚€€~~}}||{{zzzyyxxwwvvuuuttssrrrqqppooonnmmmllkkkjjiiihhgggfffeeedddcccbbbaaaa```___^^^^]]]\\\[[[[[ZZZZYYYYXXXXXWWWWVVVVVUUUUUTTTTTTSSSSSSRRRRRRQQQQQQPPPPPPPPOOOOOOOONNNNNNNNNMMMMMMMMMMLLLLLLLLLLKKKKKKKKKKKJJJJJJJJJJJJJIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDD»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸·················¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµ´´´´´´´´´´´´łłłłłłłłłłł˛˛˛˛˛˛˛˛˛±±±±±±±±±°°°°°°°ŻŻŻŻŻŻŻŻ®®®®®®®­­­­­­­¬¬¬¬¬«««««ŞŞŞŞŞŞ©©©©©¨¨¨¨¨§§§§¦¦¦¦ĄĄĄĄĄ¤¤¤ŁŁŁŁ˘˘˘˘ˇˇˇ    źźźžžžťťťśśśś›››šš™™™———––•••”””““’’’‘‘ŹŹŹŽŽŤŤŤŚŚ‹‹ŠŠ‰‰‰‡‡††………„„‚‚€€~~}}||{{zzzyyxxwwvvvuuttssrrrqqpppoonnmmmllkkkjjjiihhhgggfffeedddccccbbbaaa```____^^^]]]]\\\\[[[ZZZZZYYYYXXXXWWWWWVVVVVUUUUUTTTTTTSSSSSRRRRRRRQQQQQQQPPPPPPPPOOOOOOONNNNNNNNNMMMMMMMMMLLLLLLLLLLLKKKKKKKKKKKKJJJJJJJJJJJJJJIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDD»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸················¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµ´´´´´´´´´´´´łłłłłłłłłł˛˛˛˛˛˛˛˛˛˛±±±±±±±±±°°°°°°°ŻŻŻŻŻŻŻ®®®®®®®­­­­­­¬¬¬¬¬¬««««««ŞŞŞŞŞ©©©©©¨¨¨¨¨§§§§§¦¦¦¦ĄĄĄ¤¤¤¤¤ŁŁŁŁ˘˘˘ˇˇˇˇ   źźźźžžžťťťśśś›››ššš™™™———–––•••””“““’’‘‘‘ŹŹŹŽŽŤŤŚŚŚ‹‹ŠŠ‰‰‰‡‡††……„„„‚‚€€~~}}||{{{zzyyxxwwvvvuuttsssrrqqpppoonnnmmlllkkjjjiiihhhggfffeeedddcccbbbaaa````___^^^^]]]\\\\[[[[[ZZZYYYYXXXXXWWWWWVVVVVUUUUUTTTTTTSSSSSSRRRRRRQQQQQQQPPPPPPPOOOOOOONNNNNNNNNMMMMMMMMMMLLLLLLLLLLKKKKKKKKKKKKJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDD»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸····················¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµ´´´´´´´´´´´łłłłłłłłłł˛˛˛˛˛˛˛˛˛˛±±±±±±±±±°°°°°°°°ŻŻŻŻŻŻŻŻ®®®®®®®®­­­­­­¬¬¬¬¬«««««ŞŞŞŞŞŞ©©©©©©¨¨¨¨§§§§¦¦¦¦¦ĄĄĄĄ¤¤¤¤ŁŁŁŁ˘˘˘˘ˇˇˇ   źźźźžžžťťťśśśś›››ššš™™———–––••”””“““’’‘‘ŹŹŽŽŽŤŤŚŚ‹‹‹ŠŠ‰‰‡‡††……„„„‚‚€€~~}}||{{{zzyyxxwwwvvuutttssrrqqqppooonnmmlllkkkjjiiihhhgggffeeedddccccbbbaaa````___^^^]]]]\\\\[[[[ZZZZYYYYYXXXXWWWWVVVVVVUUUUUUTTTTTSSSSSRRRRRRQQQQQQQQPPPPPPPPOOOOOOOONNNNNNNNNMMMMMMMMMMLLLLLLLLLLKKKKKKKKKKKJJJJJJJJJJJJIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDD»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸····················¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµ´´´´´´´´´´´´´łłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛±±±±±±±±±°°°°°°°ŻŻŻŻŻŻŻŻ®®®®®®­­­­­­­¬¬¬¬¬¬¬«««««ŞŞŞŞŞ©©©©©¨¨¨¨¨§§§§§¦¦¦¦ĄĄĄĄ¤¤¤¤¤ŁŁŁŁ˘˘˘ˇˇˇ    źźźźžžžťťťśśś›››ššš™™™———––•••”””““’’’‘‘ŹŹŽŽŽŤŤŚŚ‹‹‹ŠŠ‰‰‡‡‡††……„„„‚‚€€~~}}||{{{zzyyxxxwwvvuutttssrrqqqppooonnmmmllkkkjjjiihhhgggfffeeedddcccbbbaaa````____^^^]]]\\\\[[[[[ZZZZYYYYXXXXXWWWWWVVVVVUUUUUTTTTTSSSSSSSRRRRRRRQQQQQQPPPPPPPPOOOOOOONNNNNNNNNMMMMMMMMMMLLLLLLLLLLLKKKKKKKKKKKKKJJJJJJJJJJJJJIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDD»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸·················¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµ´´´´´´´´´´´´´łłłłłłłłłł˛˛˛˛˛˛˛˛˛˛±±±±±±±±±°°°°°°°°ŻŻŻŻŻŻŻŻ®®®®®®­­­­­­¬¬¬¬¬¬««««««ŞŞŞŞŞŞ©©©©©¨¨¨¨§§§§§¦¦¦¦ĄĄĄĄĄ¤¤¤¤ŁŁŁ˘˘˘˘˘ˇˇˇ   źźźźžžžžťťťśś››››ššš™™™———–––•••””“““’’‘‘‘ŹŹŹŽŽŤŤŤŚŚ‹‹ŠŠŠ‰‰‡‡‡††……„„‚‚€€~~}}|||{{zzyyxxxwwvvuuuttssrrrqqpppoonnnmmlllkkjjjiiihhhggfffeeeddddccbbbaaaa````___^^^]]]]]\\\[[[[ZZZZZYYYYXXXXXWWWWVVVVVUUUUUUTTTTTTSSSSSSRRRRRRQQQQQQPPPPPPPPOOOOOOOONNNNNNNNNMMMMMMMMMMLLLLLLLLLLKKKKKKKKKKKKKJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDD»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸·················¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµ´´´´´´´´´´´łłłłłłłłłł˛˛˛˛˛˛˛˛˛˛±±±±±±±±±°°°°°°°°ŻŻŻŻŻŻŻŻ®®®®®®®®­­­­­­­¬¬¬¬¬¬«««««ŞŞŞŞŞ©©©©©¨¨¨¨¨¨§§§§§¦¦¦¦ĄĄĄĄ¤¤¤¤ŁŁŁŁ˘˘˘˘ˇˇˇ    źźźźžžžťťťśśś›››ššš™™™———–––••”””“““’’‘‘‘ŹŹŹŽŽŤŤŚŚŚ‹‹ŠŠŠ‰‰‡‡‡††……„„‚‚€€~~}}|||{{zzyyxxxwwvvuuuttsssrrqqpppoonnnmmlllkkkjjiiihhhgggfffeeedddcccbbbaaa````____^^^]]]]\\\\[[[[ZZZZYYYYXXXXXWWWWWWVVVVVUUUUUTTTTTSSSSSSRRRRRRRQQQQQQQQPPPPPPPPOOOOOOOONNNNNNNNNMMMMMMMMMMLLLLLLLLLLKKKKKKKKKKKJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDD»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸·····················¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµ´´´´´´´´´´´´´łłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛±±±±±±±±±°°°°°°°°ŻŻŻŻŻŻŻŻ®®®®®®®­­­­­­¬¬¬¬¬¬««««««ŞŞŞŞŞŞ©©©©©¨¨¨¨¨§§§§¦¦¦¦¦ĄĄĄĄĄ¤¤¤ŁŁŁŁ˘˘˘˘ˇˇˇˇ   źźźźžžžťťťťśśś›››ššš™™™——–––•••”””““’’’‘‘ŹŹŽŽŽŤŤŚŚŚ‹‹ŠŠŠ‰‰‡‡‡††……„„‚‚€€~~}}|||{{zzyyxxxwwvvuuuttsssrrqqqppooonnmmmllkkkjjjiiihhgggfffeeedddcccbbbbaaa````___^^^^]]]]\\\\[[[ZZZZZYYYYYXXXXWWWWWVVVVVUUUUUUTTTTTTSSSSSSRRRRRRQQQQQQQPPPPPPPPOOOOOOOONNNNNNNNNMMMMMMMMMMLLLLLLLLLLLKKKKKKKKKKKKKJJJJJJJJJJJJJIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDD»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸····················¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµ´´´´´´´´´´´´´łłłłłłłłłł˛˛˛˛˛˛˛˛˛˛±±±±±±±±±°°°°°°°°ŻŻŻŻŻŻŻŻ®®®®®®®­­­­­­­¬¬¬¬¬¬«««««ŞŞŞŞŞŞ©©©©©¨¨¨¨¨§§§§§¦¦¦¦ĄĄĄĄ¤¤¤¤¤ŁŁŁŁ˘˘˘˘ˇˇˇ    źźźźžžžťťťśśś›››ššš™™™———–––•••””“““’’‘‘‘ŹŹŽŽŽŤŤŚŚŚ‹‹ŠŠ‰‰‰‡‡†††……„„‚‚€€~~}}|||{{zzyyyxxwwvvvuuttsssrrqqqppooonnnmmlllkkjjjiiihhhgggfffeeedddcccbbbaaa````____^^^]]]]\\\\[[[[[ZZZZYYYYXXXXXWWWWWVVVVVUUUUUUTTTTTSSSSSSRRRRRRRQQQQQQQPPPPPPPPOOOOOOOONNNNNNNNNMMMMMMMMMMLLLLLLLLLLKKKKKKKKKKKKKJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDD»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸················¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµ´´´´´´´´´´´łłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±°°°°°°°°ŻŻŻŻŻŻŻŻ®®®®®®®­­­­­­­¬¬¬¬¬¬«««««««ŞŞŞŞŞŞ©©©©¨¨¨¨¨§§§§§¦¦¦¦ĄĄĄĄĄ¤¤¤¤ŁŁŁŁ˘˘˘˘ˇˇˇˇ   źźźźžžžťťťśśśś›››ššš™™™———–––••”””““’’’‘‘‘ŹŹŹŽŽŤŤŤŚŚ‹‹‹ŠŠ‰‰‰‡‡†††……„„‚‚€€~~}}|||{{zzyyyxxwwvvvuutttssrrrqqpppoonnnmmmllkkkjjiiihhhgggfffeeedddccccbbbaaa````___^^^^]]]]\\\\[[[[ZZZZZYYYYXXXXXWWWWWVVVVUUUUUUTTTTTTTSSSSSSRRRRRRRQQQQQQQPPPPPPPPOOOOOOOONNNNNNNNNMMMMMMMMMMMLLLLLLLLLLKKKKKKKKKKKJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDD»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸···················¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµ´´´´´´´´´´´´łłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±°°°°°°°°ŻŻŻŻŻŻŻŻ®®®®®®®­­­­­­­¬¬¬¬¬¬«««««ŞŞŞŞŞŞ©©©©©¨¨¨¨¨¨§§§§§¦¦¦¦ĄĄĄĄ¤¤¤¤ŁŁŁŁŁ˘˘˘˘ˇˇˇ   źźźźžžžžťťťśśś›››ššš™™™™———–––•••”””““’’’‘‘‘ŹŹŹŽŽŤŤŤŚŚ‹‹‹ŠŠ‰‰‡‡†††……„„‚‚€€~~}}|||{{zzyyyxxwwwvvuutttssrrrqqpppoonnnmmmllkkkjjjiiihhhggffffeeedddcccbbbaaaa````___^^^]]]]\\\\\[[[[ZZZZYYYYXXXXXWWWWWWVVVVVUUUUUUTTTTTSSSSSSRRRRRRRQQQQQQQPPPPPPPPOOOOOOOONNNNNNNNNMMMMMMMMMMMLLLLLLLLLLLLKKKKKKKKKKKKJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDD»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸·······················¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµ´´´´´´´´´´´´´łłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛±±±±±±±±±°°°°°°°°ŻŻŻŻŻŻŻŻ®®®®®®®­­­­­­­¬¬¬¬¬¬¬««««««ŞŞŞŞŞŞ©©©©©¨¨¨¨§§§§§¦¦¦¦¦ĄĄĄĄĄ¤¤¤¤ŁŁŁŁ˘˘˘ˇˇˇˇ    źźźźžžžťťťśśśś›››ššš™™™———–––•••””“““’’’‘‘ŹŹŽŽŽŤŤŚŚŚ‹‹ŠŠŠ‰‰‡‡††………„„‚‚‚€€~~}}}||{{zzzyyxxwwwvvuuuttsssrrqqqppooonnmmmlllkkjjjiiihhhgggfffeeedddccccbbbaaa````____^^^^]]]\\\\[[[[ZZZZZYYYYYXXXXXWWWWVVVVVUUUUUUTTTTTTSSSSSSSRRRRRRRQQQQQQQPPPPPPPPOOOOOOOONNNNNNNNNMMMMMMMMMMLLLLLLLLLLLKKKKKKKKKKKKKJJJJJJJJJJJJJJJIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸····················¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµ´´´´´´´´´´´łłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±°°°°°°°°ŻŻŻŻŻŻŻŻ®®®®®®®®­­­­­­¬¬¬¬¬¬««««««ŞŞŞŞŞŞ©©©©©©¨¨¨¨¨§§§§§¦¦¦¦ĄĄĄĄĄ¤¤¤¤ŁŁŁŁ˘˘˘˘ˇˇˇˇ   źźźźžžžťťťťśśś›››ššš™™™———––•••”””“““’’‘‘‘ŹŹŽŽŽŤŤŚŚŚ‹‹ŠŠŠ‰‰‡‡‡††………„„‚‚‚€€~~}}}||{{zzzyyxxxwwvvuuuttsssrrqqqppooonnnmmlllkkkjjjiihhhggggfffeeedddcccbbbbaaa````___^^^^]]]]\\\\[[[[ZZZZZYYYYXXXXXWWWWWVVVVVVUUUUUUTTTTTTSSSSSSRRRRRRQQQQQQQQPPPPPPPPOOOOOOOONNNNNNNNNNMMMMMMMMMMMLLLLLLLLLLKKKKKKKKKKKJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸·················¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµ´´´´´´´´´´´´łłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±°°°°°°°°ŻŻŻŻŻŻŻŻ®®®®®®®­­­­­­­¬¬¬¬¬¬««««««ŞŞŞŞŞŞ©©©©©¨¨¨¨¨§§§§¦¦¦¦¦ĄĄĄĄĄ¤¤¤¤¤ŁŁŁ˘˘˘˘ˇˇˇˇ    źźźźžžžťťťśśśś›››ššš™™™———–––•••””“““’’’‘‘‘ŹŹŹŽŽŽŤŤŚŚŚ‹‹ŠŠŠ‰‰‡‡‡††………„„‚‚‚€€~~}}}||{{zzzyyxxxwwvvuuuttsssrrqqqpppoonnnmmmlllkkjjjiiihhhgggfffeeedddccccbbbaaa````____^^^^]]]\\\\[[[[[ZZZZZYYYYYXXXXWWWWWVVVVVUUUUUUTTTTTTSSSSSSRRRRRRRQQQQQQQPPPPPPPPOOOOOOOONNNNNNNNNNMMMMMMMMMMMLLLLLLLLLLLLKKKKKKKKKKKKJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸····················¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµ´´´´´´´´´´´´´´łłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛±±±±±±±±±°°°°°°°°ŻŻŻŻŻŻŻŻŻ®®®®®®®­­­­­­­¬¬¬¬¬¬«««««««ŞŞŞŞŞŞ©©©©©¨¨¨¨¨¨§§§§¦¦¦¦¦ĄĄĄĄ¤¤¤¤ŁŁŁŁŁ˘˘˘˘ˇˇˇˇ   źźźźžžžťťťťśśś››››ššš™™™———–––•••””“““’’’‘‘‘ŹŹŹŽŽŤŤŤŚŚ‹‹‹ŠŠ‰‰‰‡‡‡††………„„‚‚‚€€~~}}}||{{zzzyyxxxwwvvvuutttssrrrqqpppoonnnmmmlllkkjjjiiihhhgggfffeeeddddcccbbbbaaa````___^^^^]]]]\\\\\[[[[ZZZZYYYYYXXXXWWWWWWVVVVVUUUUUUTTTTTTTSSSSSSRRRRRRRQQQQQQQPPPPPPPPPOOOOOOOONNNNNNNNNMMMMMMMMMMLLLLLLLLLLLKKKKKKKKKKKKKKJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸·······················¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµ´´´´´´´´´´´´łłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±°°°°°°°°°ŻŻŻŻŻŻŻŻŻ®®®®®®®­­­­­­­¬¬¬¬¬¬««««««ŞŞŞŞŞŞ©©©©©¨¨¨¨¨§§§§§¦¦¦¦¦ĄĄĄĄĄ¤¤¤¤ŁŁŁŁ˘˘˘˘ˇˇˇˇ   źźźźžžžžťťťťśśś›››ššš™™™————––•••”””“““’’‘‘‘ŹŹŽŽŽŤŤŤŚŚ‹‹‹ŠŠ‰‰‰‡‡‡††……„„„‚‚‚€€~~}}}||{{{zzyyxxxwwvvvuutttssrrrqqqppooonnnmmlllkkkjjjiihhhhgggfffeeedddcccbbbbaaaa````___^^^^]]]]\\\\[[[[ZZZZZYYYYYXXXXXWWWWWVVVVVUUUUUUTTTTTTSSSSSSRRRRRRRQQQQQQQPPPPPPPPPOOOOOOOONNNNNNNNNNMMMMMMMMMMMMLLLLLLLLLLKKKKKKKKKKKKJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸···················¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµ´´´´´´´´´´´´łłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±°°°°°°°°ŻŻŻŻŻŻŻŻ®®®®®®®­­­­­­­¬¬¬¬¬¬¬««««««ŞŞŞŞŞŞ©©©©©©¨¨¨¨¨§§§§§¦¦¦¦ĄĄĄĄĄ¤¤¤¤ŁŁŁŁ˘˘˘˘ˇˇˇˇˇ   źźźźžžžťťťťśśś›››šššš™™™———–––•••”””““’’’‘‘‘ŹŹŹŽŽŽŤŤŚŚŚ‹‹‹ŠŠ‰‰‰‡‡‡††……„„„‚‚‚€€~~}}}||{{{zzyyxxxwwvvvuutttsssrrqqqpppoonnnmmmllkkkjjjiiihhhgggfffeeeedddcccbbbbaaa````___^^^^^]]]]\\\\[[[[ZZZZZYYYYXXXXXWWWWWVVVVVVUUUUUUTTTTTTSSSSSSSRRRRRRRQQQQQQQPPPPPPPPOOOOOOOONNNNNNNNNMMMMMMMMMMMMLLLLLLLLLLLLKKKKKKKKKKKKJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸·················¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµ´´´´´´´´´´´´´´´łłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛±±±±±±±±±°°°°°°°°°ŻŻŻŻŻŻŻŻŻ®®®®®®®­­­­­­­¬¬¬¬¬¬¬««««««ŞŞŞŞŞŞ©©©©©¨¨¨¨¨§§§§§¦¦¦¦¦ĄĄĄĄĄ¤¤¤¤¤ŁŁŁŁ˘˘˘˘ˇˇˇˇ   źźźźžžžžťťťťśśś›››ššš™™™———–––•••”””“““’’’‘‘‘ŹŹŹŽŽŽŤŤŚŚŚ‹‹ŠŠŠ‰‰‡‡†††……„„„‚‚‚€€~~}}}||{{{zzyyyxxwwwvvuuuttsssrrqqqpppoonnnmmmlllkkkjjjiiihhhgggfffeeedddcccbbbbaaaa````___^^^^]]]]\\\\[[[[[ZZZZZYYYYYXXXXXWWWWWVVVVVUUUUUUTTTTTTSSSSSSRRRRRRRRQQQQQQQPPPPPPPPPOOOOOOOOONNNNNNNNNMMMMMMMMMMLLLLLLLLLLLKKKKKKKKKKKKKKKJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸·····················¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµ´´´´´´´´´´´´´łłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±°°°°°°°°°ŻŻŻŻŻŻŻŻŻ®®®®®®®­­­­­­­¬¬¬¬¬¬«««««««ŞŞŞŞŞŞ©©©©©¨¨¨¨¨§§§§§¦¦¦¦¦ĄĄĄĄ¤¤¤¤¤ŁŁŁŁ˘˘˘˘ˇˇˇˇ    źźźźžžžžťťťśśś››››ššš™™™———–––•••”””“““’’’‘‘ŹŹŹŽŽŤŤŤŚŚŚ‹‹ŠŠŠ‰‰‡‡†††……„„„‚‚‚€€~~}}}||{{{zzyyyxxwwwvvuuuttsssrrrqqpppooonnmmmlllkkkjjjiiihhhgggfffeeeddddcccbbbaaaa````____^^^^]]]]\\\\[[[[[ZZZZYYYYYXXXXXWWWWWVVVVVUUUUUUTTTTTTTSSSSSSRRRRRRRQQQQQQQPPPPPPPPPOOOOOOOOONNNNNNNNNNMMMMMMMMMMMLLLLLLLLLLLKKKKKKKKKKKKKJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸······················¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµ´´´´´´´´´´´łłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±°°°°°°°°ŻŻŻŻŻŻŻŻ®®®®®®®®­­­­­­­¬¬¬¬¬¬¬««««««ŞŞŞŞŞŞ©©©©©©¨¨¨¨¨§§§§§¦¦¦¦¦ĄĄĄĄĄ¤¤¤¤¤ŁŁŁŁ˘˘˘˘ˇˇˇˇ   źźźźžžžžťťťśśśś›››šššš™™™———–––•••”””“““’’‘‘‘ŹŹŹŽŽŤŤŤŚŚ‹‹‹ŠŠŠ‰‰‡‡†††……„„„‚‚‚€€~~}}}||{{{zzyyyxxwwwvvuuutttssrrrqqpppooonnnmmlllkkkjjjiiihhhgggfffeeeedddccccbbbaaaa````___^^^^]]]]\\\\[[[[[ZZZZZYYYYYXXXXXWWWWWVVVVVVUUUUUUTTTTTTSSSSSSSRRRRRRRQQQQQQQQPPPPPPPPOOOOOOOONNNNNNNNNMMMMMMMMMMMLLLLLLLLLLLLLLKKKKKKKKKKKJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸···················¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµ´´´´´´´´´´´´´´´łłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±°°°°°°°°°ŻŻŻŻŻŻŻŻŻ®®®®®®®®­­­­­­­¬¬¬¬¬¬¬««««««ŞŞŞŞŞŞ©©©©©©¨¨¨¨¨§§§§§¦¦¦¦¦ĄĄĄĄ¤¤¤¤ŁŁŁŁŁ˘˘˘˘ˇˇˇˇ    źźźźžžžžťťťśśś››››ššš™™™———–––•••”””“““’’’‘‘‘ŹŹŹŽŽŽŤŤŤŚŚ‹‹‹ŠŠ‰‰‰‡‡‡†††……„„„‚‚‚€€~~}}}||{{{zzyyyxxxwwvvvuutttssrrrqqqpppoonnnmmmlllkkkjjjiiihhhgggfffeeeddddcccbbbaaaa````____^^^^]]]]\\\\\[[[[ZZZZYYYYYXXXXXWWWWWVVVVVVUUUUUUTTTTTTSSSSSSSRRRRRRRQQQQQQQQPPPPPPPPPOOOOOOOOONNNNNNNNNNMMMMMMMMMMLLLLLLLLLLLLKKKKKKKKKKKKKKKJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸··················¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµ´´´´´´´´´´´´´´łłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±°°°°°°°°°ŻŻŻŻŻŻŻŻ®®®®®®®­­­­­­­¬¬¬¬¬¬¬««««««ŞŞŞŞŞŞ©©©©©©¨¨¨¨¨§§§§§¦¦¦¦¦ĄĄĄĄĄ¤¤¤¤¤ŁŁŁŁŁ˘˘˘˘ˇˇˇˇ   źźźźžžžžťťťśśśś›››ššš™™™™———–––•••”””“““’’’‘‘‘ŹŹŹŽŽŽŤŤŚŚŚ‹‹‹ŠŠ‰‰‰‡‡‡††………„„‚‚€€~~~}}|||{{zzzyyxxxwwvvvuutttsssrrqqqpppoonnnmmmlllkkkjjjiiihhhgggffffeeedddccccbbbaaaa````___^^^^]]]]\\\\\[[[[[ZZZZZYYYYYXXXXXWWWWWVVVVVVUUUUUUTTTTTTSSSSSSSRRRRRRRQQQQQQQPPPPPPPPOOOOOOOOONNNNNNNNNNNMMMMMMMMMMMLLLLLLLLLLLKKKKKKKKKKKKKKJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸······················¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµ´´´´´´´´´´´łłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±°°°°°°°°ŻŻŻŻŻŻŻŻŻ®®®®®®®®­­­­­­­­¬¬¬¬¬¬¬««««««ŞŞŞŞŞŞ©©©©©©¨¨¨¨¨§§§§§¦¦¦¦¦ĄĄĄĄĄ¤¤¤¤ŁŁŁŁ˘˘˘˘˘ˇˇˇˇ   źźźźžžžžťťťťśśś››››ššš™™™™———–––•••”””“““’’’‘‘ŹŹŹŽŽŤŤŤŚŚŚ‹‹ŠŠŠ‰‰‰‡‡‡††………„„‚‚€€~~~}}|||{{zzzyyxxxwwvvvuuuttsssrrrqqpppooonnmmmlllkkkjjjiiihhhgggffffeeeddddcccbbbbaaaa````___^^^^]]]]]\\\\[[[[ZZZZZYYYYYXXXXXWWWWWVVVVVVUUUUUUTTTTTTSSSSSSSRRRRRRRRQQQQQQQQPPPPPPPPPOOOOOOOONNNNNNNNNMMMMMMMMMMMLLLLLLLLLLLLLLKKKKKKKKKKKJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸······················¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµ´´´´´´´´´´´´´´´łłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±°°°°°°°°°ŻŻŻŻŻŻŻŻŻ®®®®®®®®­­­­­­­¬¬¬¬¬¬««««««ŞŞŞŞŞŞ©©©©©©¨¨¨¨¨¨§§§§§¦¦¦¦¦ĄĄĄĄĄ¤¤¤¤¤ŁŁŁŁ˘˘˘˘ˇˇˇˇˇ   źźźźžžžžťťťśśśś›››šššš™™™———–––•••”””“““’’’‘‘‘ŹŹŹŽŽŤŤŤŚŚŚ‹‹ŠŠŠ‰‰‰‡‡‡††………„„‚‚€€~~~}}|||{{zzzyyxxxwwvvvuuuttsssrrrqqpppooonnnmmmlllkkkjjjiiihhhgggfffeeeedddccccbbbaaaa````___^^^^^]]]]\\\\[[[[[ZZZZZYYYYYXXXXXWWWWWWVVVVVVUUUUUUTTTTTTSSSSSSRRRRRRRQQQQQQQQPPPPPPPPPOOOOOOOOONNNNNNNNNNNMMMMMMMMMMLLLLLLLLLLLLKKKKKKKKKKKKKKKJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸···················¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµ´´´´´´´´´´´´´´´łłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±°°°°°°°°ŻŻŻŻŻŻŻŻ®®®®®®®®­­­­­­­¬¬¬¬¬¬¬«««««««ŞŞŞŞŞŞ©©©©©©¨¨¨¨¨§§§§§§¦¦¦¦¦ĄĄĄĄ¤¤¤¤¤ŁŁŁŁ˘˘˘˘ˇˇˇˇˇ   źźźźžžžžťťťťśśś››››ššš™™™———–––•••”””“““’’’‘‘‘ŹŹŹŽŽŽŤŤŤŚŚŚ‹‹ŠŠŠ‰‰‡‡‡††………„„‚‚€€~~~}}|||{{zzzyyxxxwwwvvuuuttsssrrrqqqpppoonnnmmmlllkkkjjjiiihhhggggfffeeeddddcccbbbbaaaa````___^^^^^]]]]\\\\[[[[[ZZZZYYYYYXXXXXXWWWWWVVVVVVUUUUUUTTTTTTTSSSSSSSRRRRRRRQQQQQQQQPPPPPPPPOOOOOOOONNNNNNNNNNNMMMMMMMMMMMMLLLLLLLLLLLKKKKKKKKKKKKKKJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸····················¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´łłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±°°°°°°°°°ŻŻŻŻŻŻŻŻŻ®®®®®®®®®­­­­­­­¬¬¬¬¬¬¬««««««ŞŞŞŞŞŞ©©©©©©¨¨¨¨¨§§§§§¦¦¦¦¦ĄĄĄĄĄ¤¤¤¤¤ŁŁŁŁŁ˘˘˘˘ˇˇˇˇ    źźźźžžžžťťťťśśś››››ššš™™™————–––•••”””“““’’’‘‘ŹŹŹŽŽŽŤŤŤŚŚ‹‹‹ŠŠŠ‰‰‡‡‡††………„„‚‚€€~~~}}|||{{zzzyyxxxwwwvvuuutttssrrrqqqpppooonnmmmlllkkkjjjiiihhhhgggfffeeeddddcccbbbbaaaa````____^^^^]]]]\\\\\[[[[[ZZZZZYYYYYXXXXXWWWWWVVVVVVUUUUUUTTTTTTSSSSSSSRRRRRRRQQQQQQQQPPPPPPPPPPOOOOOOOOONNNNNNNNNMMMMMMMMMMMLLLLLLLLLLLLLLKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸·······················¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´łłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±°°°°°°°°°°ŻŻŻŻŻŻŻŻ®®®®®®®®­­­­­­­¬¬¬¬¬¬¬«««««««ŞŞŞŞŞŞ©©©©©©¨¨¨¨¨¨§§§§§¦¦¦¦¦ĄĄĄĄĄ¤¤¤¤¤ŁŁŁŁ˘˘˘˘ˇˇˇˇ    źźźźžžžžťťťťśśś››››ššš™™™™———–––•••”””“““’’’‘‘‘ŹŹŹŽŽŽŤŤŚŚŚ‹‹‹ŠŠ‰‰‰‡‡†††………„„‚‚€€~~~}}|||{{zzzyyyxxwwwvvvuutttsssrrqqqpppooonnnmmmlllkkkjjjiiihhhgggffffeeeddddcccbbbbaaaa````____^^^^]]]]\\\\[[[[[ZZZZZYYYYYXXXXXWWWWWWVVVVVVUUUUUUTTTTTTTSSSSSSSRRRRRRRQQQQQQQQPPPPPPPPOOOOOOOOOONNNNNNNNNNNMMMMMMMMMMLLLLLLLLLLLLKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEşşąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸·····················¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµ´´´´´´´´´´´´´´´łłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±°°°°°°°°ŻŻŻŻŻŻŻŻŻ®®®®®®®®­­­­­­­­¬¬¬¬¬¬¬«««««««ŞŞŞŞŞ©©©©©©¨¨¨¨¨¨§§§§§¦¦¦¦¦ĄĄĄĄĄ¤¤¤¤¤ŁŁŁŁ˘˘˘˘ˇˇˇˇˇ    źźźźžžžťťťťśśśś›››šššš™™™————–––•••”””“““’’’‘‘‘ŹŹŹŽŽŤŤŤŚŚŚ‹‹‹ŠŠ‰‰‰‡‡†††………„„‚‚€€~~~}}|||{{zzzyyyxxwwwvvvuutttsssrrrqqpppooonnnmmmlllkkkjjjiiihhhhgggfffeeeedddccccbbbbaaa````____^^^^^]]]]\\\\[[[[[ZZZZZYYYYYXXXXXWWWWWWVVVVVVUUUUUTTTTTTTSSSSSSSRRRRRRRRQQQQQQQQPPPPPPPPPOOOOOOOONNNNNNNNNNNMMMMMMMMMMMMLLLLLLLLLLLKKKKKKKKKKKKKKKJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEşşąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸····················¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´łłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻ®®®®®®®­­­­­­­¬¬¬¬¬¬¬«««««««ŞŞŞŞŞŞ©©©©©©¨¨¨¨¨¨§§§§§§¦¦¦¦¦ĄĄĄĄĄ¤¤¤¤ŁŁŁŁŁ˘˘˘˘ˇˇˇˇ    źźźźžžžžťťťťśśśś›››šššš™™™————–––•••”””“““’’’‘‘‘ŹŹŹŽŽŽŤŤŤŚŚŚ‹‹ŠŠŠ‰‰‰‡‡‡†††……„„„‚‚€€~~~}}|||{{{zzyyyxxxwwvvvuuuttsssrrrqqqpppoonnnmmmlllkkkjjjiiihhhhgggfffeeeedddccccbbbbaaaa````____^^^^]]]]\\\\\[[[[ZZZZZYYYYYXXXXXXWWWWWWVVVVVVUUUUUUTTTTTTTSSSSSSSRRRRRRRQQQQQQQPPPPPPPPPPOOOOOOOOOONNNNNNNNNMMMMMMMMMMMLLLLLLLLLLLLLLKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEşşąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸·····················¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´łłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±°°°°°°°°°°ŻŻŻŻŻŻŻ®®®®®®®®®­­­­­­­­¬¬¬¬¬¬¬«««««««ŞŞŞŞŞŞ©©©©©©¨¨¨¨¨§§§§§¦¦¦¦¦ĄĄĄĄĄ¤¤¤¤¤ŁŁŁŁŁ˘˘˘˘ˇˇˇˇ    źźźźžžžžťťťťśśśś›››šššš™™™———–––•••”””““““’’‘‘‘ŹŹŹŽŽŽŤŤŤŚŚ‹‹‹ŠŠŠ‰‰‰‡‡‡†††……„„„‚‚€€~~~}}|||{{{zzyyyxxxwwvvvuuutttssrrrqqqpppooonnnmmllllkkkjjjiiihhhggggfffeeeedddccccbbbbaaaa````____^^^^]]]]\\\\\[[[[[ZZZZZYYYYYXXXXXWWWWWVVVVVVUUUUUUTTTTTTTSSSSSSSRRRRRRRRQQQQQQQQQPPPPPPPOOOOOOOOOONNNNNNNNNNNMMMMMMMMMMMLLLLLLLLLLLLLKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸·······················¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´łłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±°°°°°°°°ŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®­­­­­­­¬¬¬¬¬¬«««««««ŞŞŞŞŞŞ©©©©©©©¨¨¨¨¨§§§§§§¦¦¦¦¦ĄĄĄĄĄ¤¤¤¤¤ŁŁŁŁŁ˘˘˘˘ˇˇˇˇ    źźźźžžžžťťťťśśśś›››šššš™™™———––––•••”””“““’’’‘‘‘ŹŹŹŽŽŽŤŤŤŚŚ‹‹‹ŠŠŠ‰‰‰‡‡‡†††……„„„‚‚€€~~~}}|||{{{zzyyyxxxwwvvvuuutttssrrrqqqpppooonnnmmmlllkkkjjjiiiihhhgggfffeeeedddccccbbbbaaaa````____^^^^]]]]\\\\\[[[[[ZZZZZYYYYYXXXXXXWWWWWVVVVVVVUUUUUUTTTTTTTSSSSSSRRRRRRRQQQQQQQQQPPPPPPPPPPOOOOOOOONNNNNNNNNNMMMMMMMMMMMMMLLLLLLLLLLLKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸·····················¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµ´´´´´´´´´´´´´łłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±°°°°°°°°°°ŻŻŻŻŻŻŻŻŻ®®®®®®®­­­­­­­­¬¬¬¬¬¬¬¬«««««««ŞŞŞŞŞŞ©©©©©©¨¨¨¨¨¨§§§§§¦¦¦¦¦ĄĄĄĄĄ¤¤¤¤¤ŁŁŁŁ˘˘˘˘˘ˇˇˇˇ    źźźźźžžžťťťťśśśś››››ššš™™™™———–––•••””””“““’’’‘‘‘ŹŹŹŽŽŤŤŤŚŚŚ‹‹‹ŠŠŠ‰‰‡‡‡†††……„„„‚‚€€~~~}}|||{{{zzyyyxxxwwwvvuuutttsssrrrqqpppooonnnmmmlllkkkkjjjiiihhhgggffffeeeddddccccbbbbaaa`````____^^^^]]]]]\\\\[[[[[ZZZZZYYYYYXXXXXWWWWWWVVVVVVUUUUUUTTTTTTTSSSSSSSSRRRRRRRRQQQQQQQPPPPPPPPPOOOOOOOOOONNNNNNNNNNMMMMMMMMMMMLLLLLLLLLLLLLLKKKKKKKKKKKKKJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸·····················¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´łłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±°°°°°°°°°ŻŻŻŻŻŻŻŻ®®®®®®®®®­­­­­­­­¬¬¬¬¬¬¬««««««ŞŞŞŞŞŞŞ©©©©©©¨¨¨¨¨¨§§§§§¦¦¦¦¦¦ĄĄĄĄĄ¤¤¤¤¤ŁŁŁŁ˘˘˘˘˘ˇˇˇˇ    źźźźžžžžťťťťśśś››››šššš™™™————–––•••””””““’’’‘‘‘ŹŹŹŽŽŽŤŤŤŚŚŚ‹‹‹ŠŠ‰‰‰‡‡‡††………„„„‚‚‚€€~~~}}}||{{{zzzyyxxxwwwvvvuutttsssrrrqqqpppooonnnmmmllkkkkjjjiiihhhhgggfffeeeeddddcccbbbbaaaa````____^^^^]]]]]\\\\[[[[[ZZZZZYYYYYYXXXXXWWWWWWVVVVVVUUUUUUUTTTTTTSSSSSSSRRRRRRRRQQQQQQQQQPPPPPPPPOOOOOOOOONNNNNNNNNNNNMMMMMMMMMMMLLLLLLLLLLLLLKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸·······················¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´łłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻ®®®®®®®®­­­­­­­¬¬¬¬¬¬¬¬«««««««ŞŞŞŞŞŞŞ©©©©©©¨¨¨¨¨§§§§§¦¦¦¦¦ĄĄĄĄĄ¤¤¤¤¤ŁŁŁŁŁ˘˘˘˘˘ˇˇˇˇ    źźźźźžžžžťťťśśśś››››ššš™™™™———––––•••”””“““’’’‘‘‘ŹŹŹŽŽŽŤŤŤŚŚŚ‹‹‹ŠŠ‰‰‰‡‡‡††………„„„‚‚‚€€~~~}}}||{{{zzzyyxxxwwwvvvuutttsssrrrqqqpppooonnnmmmlllkkkjjjiiiihhhgggffffeeeddddccccbbbaaaa`````____^^^^]]]]]\\\\\[[[[ZZZZZZYYYYYXXXXXWWWWWVVVVVVUUUUUUUTTTTTTTSSSSSSSSRRRRRRRQQQQQQQQPPPPPPPPPPOOOOOOOOONNNNNNNNNNMMMMMMMMMMMMMLLLLLLLLLLLKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸·······················¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµ´´´´´´´´´´´´´´łłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±°°°°°°°°°°ŻŻŻŻŻŻŻŻ®®®®®®®®®­­­­­­­­¬¬¬¬¬¬¬¬««««««ŞŞŞŞŞŞ©©©©©©¨¨¨¨¨¨§§§§§§¦¦¦¦¦ĄĄĄĄĄ¤¤¤¤¤ŁŁŁŁŁ˘˘˘˘ˇˇˇˇˇ    źźźźžžžžťťťťśśś››››šššš™™™———–––•••””””“““’’’‘‘‘ŹŹŹŽŽŽŤŤŤŚŚ‹‹‹ŠŠŠ‰‰‰‡‡†††………„„„‚‚‚€€~~~}}}||{{{zzzyyyxxwwwvvvuuutttssrrrqqqpppooonnnmmmlllkkkkjjjiiihhhggggfffeeeeddddcccbbbbaaaa````____^^^^^]]]]\\\\\[[[[ZZZZZZYYYYYXXXXXXWWWWWWVVVVVVUUUUUUTTTTTTSSSSSSSSRRRRRRRRQQQQQQQQQPPPPPPPPOOOOOOOOOONNNNNNNNNNNMMMMMMMMMMMLLLLLLLLLLLLLLKKKKKKKKKKKKKKJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸······················¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´łłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±°°°°°°°°°ŻŻŻŻŻŻŻŻŻ®®®®®®®®®®­­­­­­­¬¬¬¬¬¬««««««««ŞŞŞŞŞŞŞ©©©©©©¨¨¨¨¨¨§§§§§¦¦¦¦¦ĄĄĄĄĄ¤¤¤¤¤ŁŁŁŁŁ˘˘˘˘˘ˇˇˇˇ    źźźźźžžžžťťťśśśś›››šššš™™™™————–––•••”””“““’’’’‘‘‘ŹŹŹŽŽŽŤŤŤŚŚ‹‹‹ŠŠŠ‰‰‰‡‡†††………„„„‚‚‚€€~~~}}}||{{{zzzyyyxxwwwvvvuuutttssrrrqqqpppooonnnmmmmlllkkkjjjiiihhhhgggffffeeeedddccccbbbaaaa`````____^^^^]]]]]\\\\\[[[[[ZZZZZYYYYYXXXXXWWWWWWVVVVVVUUUUUUUTTTTTTTTSSSSSSRRRRRRRQQQQQQQQQQPPPPPPPPPOOOOOOOOONNNNNNNNNNNMMMMMMMMMMMMLLLLLLLLLLLLLKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸······················¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´łłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻ®®®®®®®­­­­­­­­­¬¬¬¬¬¬¬«««««««ŞŞŞŞŞŞ©©©©©©¨¨¨¨¨¨§§§§§§¦¦¦¦¦¦ĄĄĄĄĄ¤¤¤¤¤ŁŁŁŁ˘˘˘˘˘ˇˇˇˇ    źźźźźžžžžťťťťśśśś›››šššš™™™———––––•••”””“““’’’‘‘‘ŹŹŹŹŽŽŤŤŤŚŚŚ‹‹‹ŠŠŠ‰‰‰‡‡‡†††………„„„‚‚‚€€~~~}}}||{{{zzzyyyxxxwwvvvuuutttsssrrrqqppppooonnnmmmlllkkkjjjiiiihhhggggfffeeeedddccccbbbbaaaa`````____^^^^]]]]]\\\\[[[[[ZZZZZYYYYYYXXXXXXWWWWWWVVVVVVUUUUUUTTTTTTTSSSSSSSRRRRRRRRRQQQQQQQPPPPPPPPPPOOOOOOOOOONNNNNNNNNMMMMMMMMMMMMMMLLLLLLLLLLLKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸······················¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´łłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±°°°°°°°°°°ŻŻŻŻŻŻŻŻŻ®®®®®®®®®­­­­­­­­¬¬¬¬¬¬¬«««««««ŞŞŞŞŞŞŞ©©©©©©¨¨¨¨¨¨§§§§§¦¦¦¦¦ĄĄĄĄĄĄ¤¤¤¤¤ŁŁŁŁŁ˘˘˘˘˘ˇˇˇˇ    źźźźžžžžťťťťśśśś››››ššš™™™™———–––•••””””“““’’’‘‘‘ŹŹŹŽŽŽŤŤŤŚŚŚ‹‹‹ŠŠŠ‰‰‰‡‡‡†††………„„‚‚‚€€~~~}}}|||{{zzzyyyxxxwwvvvuuutttsssrrrqqqpppooonnnmmmlllkkkkjjjiiihhhggggffffeeeddddccccbbbbaaaa````____^^^^]]]]]\\\\\[[[[[ZZZZZZYYYYYXXXXXWWWWWWVVVVVVUUUUUUUTTTTTTTSSSSSSSRRRRRRRRQQQQQQQQQPPPPPPPPPOOOOOOOOOONNNNNNNNNNNMMMMMMMMMMMLLLLLLLLLLLLLLKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸·······················¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´łłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®­­­­­­­¬¬¬¬¬¬¬¬««««««««ŞŞŞŞŞŞ©©©©©©¨¨¨¨¨¨§§§§§§¦¦¦¦¦ĄĄĄĄĄĄ¤¤¤¤ŁŁŁŁŁ˘˘˘˘ˇˇˇˇˇ    źźźźźžžžžťťťťśśśś›››šššš™™™————–––•••”””““““’’’‘‘‘ŹŹŹŽŽŽŤŤŤŚŚŚ‹‹‹ŠŠŠ‰‰‡‡‡†††………„„‚‚‚€€~~~}}}|||{{zzzyyyxxxwwwvvuuutttsssrrrqqqpppooonnnmmmllllkkkjjjiiihhhhggggfffeeeedddccccbbbbaaaa`````____^^^^^]]]]\\\\\[[[[ZZZZZZYYYYYXXXXXXWWWWWWVVVVVVUUUUUUTTTTTTTTSSSSSSSSRRRRRRRQQQQQQQQPPPPPPPPPPPOOOOOOOOONNNNNNNNNNNMMMMMMMMMMMMLLLLLLLLLLLLLLKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸·······················¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´łłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻ®®®®®®®®­­­­­­­­­¬¬¬¬¬¬¬«««««««ŞŞŞŞŞŞŞ©©©©©©¨¨¨¨¨¨¨§§§§§¦¦¦¦¦ĄĄĄĄĄ¤¤¤¤¤¤ŁŁŁŁŁ˘˘˘˘ˇˇˇˇˇ    źźźźžžžžťťťťśśśś››››šššš™™™————––––•••”””“““’’’‘‘‘‘ŹŹŹŽŽŽŤŤŤŚŚŚ‹‹‹ŠŠ‰‰‰‡‡‡†††………„„‚‚‚€€~~~}}}|||{{zzzyyyxxxwwwvvvuutttsssrrrqqqpppooonnnnmmmlllkkkjjjiiiihhhhgggfffeeeeddddccccbbbbaaaa````____^^^^^]]]]\\\\\[[[[[[ZZZZZYYYYYXXXXXWWWWWWVVVVVVVUUUUUUUTTTTTTTSSSSSSSRRRRRRRRRQQQQQQQQPPPPPPPPPOOOOOOOOOOONNNNNNNNNMMMMMMMMMMMMMMLLLLLLLLLLLLKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸·······················¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´łłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±°°°°°°°°°°ŻŻŻŻŻŻŻŻŻ®®®®®®®®®­­­­­­­¬¬¬¬¬¬¬¬««««««««ŞŞŞŞŞŞ©©©©©©¨¨¨¨¨¨§§§§§§¦¦¦¦¦¦ĄĄĄĄĄ¤¤¤¤¤ŁŁŁŁ˘˘˘˘˘ˇˇˇˇˇ    źźźźźžžžžťťťťśśś››››šššš™™™™————–––•••””””“““’’’‘‘‘ŹŹŹŽŽŽŤŤŤŤŚŚ‹‹‹ŠŠŠ‰‰‰‡‡‡†††………„„‚‚‚€€~~~}}}|||{{zzzyyyxxxwwwvvvuuutttssrrrrqqqpppooonnnmmmlllkkkkjjjiiihhhhgggffffeeeeddddcccbbbbaaaa`````____^^^^^]]]]]\\\\[[[[[ZZZZZYYYYYYXXXXXXWWWWWWVVVVVVUUUUUUTTTTTTTTSSSSSSSSRRRRRRRQQQQQQQQQPPPPPPPPPOOOOOOOOOONNNNNNNNNNNNMMMMMMMMMMMLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸·······················¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´łłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®­­­­­­­­­¬¬¬¬¬¬¬¬««««««ŞŞŞŞŞŞŞ©©©©©©©¨¨¨¨¨¨§§§§§¦¦¦¦¦ĄĄĄĄĄĄ¤¤¤¤¤ŁŁŁŁŁ˘˘˘˘˘ˇˇˇˇ    źźźźźžžžžťťťťśśśś››››ššš™™™™———––––•••”””““““’’’‘‘‘ŹŹŹŽŽŽŤŤŤŚŚŚ‹‹‹ŠŠŠ‰‰‰‡‡‡†††……„„„‚‚‚€€~~~}}}|||{{{zzyyyxxxwwwvvvuuutttsssrrrqqqpppooonnnmmmllllkkkjjjiiiihhhggggffffeeeddddccccbbbbaaaa`````____^^^^]]]]]\\\\\[[[[[ZZZZZZYYYYYXXXXXWWWWWWVVVVVVVUUUUUUUTTTTTTSSSSSSSSRRRRRRRRRQQQQQQQPPPPPPPPPPPOOOOOOOOONNNNNNNNNNNNMMMMMMMMMMMMLLLLLLLLLLLLLLKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸························¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´łłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±°°°°°°°°°°°ŻŻŻŻŻŻŻŻ®®®®®®®®®®­­­­­­­­¬¬¬¬¬¬¬««««««««ŞŞŞŞŞŞŞ©©©©©©¨¨¨¨¨§§§§§§§¦¦¦¦¦ĄĄĄĄĄĄ¤¤¤¤ŁŁŁŁŁ˘˘˘˘˘ˇˇˇˇˇ    źźźźźžžžťťťťśśśśś››››ššš™™™™————–––••••”””“““’’’’‘‘‘ŹŹŹŽŽŽŤŤŤŚŚŚ‹‹‹ŠŠŠ‰‰‰‡‡‡†††……„„„‚‚‚€€~~~}}}|||{{{zzyyyxxxwwwvvvuuutttsssrrrqqqpppooonnnmmmmlllkkkjjjjiiihhhhgggffffeeeddddccccbbbbbaaa`````____^^^^^]]]]]\\\\\[[[[ZZZZZZYYYYYXXXXXXXWWWWWVVVVVVUUUUUUUTTTTTTTTSSSSSSSRRRRRRRRQQQQQQQQQQPPPPPPPPOOOOOOOOOOONNNNNNNNNNMMMMMMMMMMMMMMLLLLLLLLLLLLKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸························¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®­­­­­­­¬¬¬¬¬¬¬¬¬«««««««ŞŞŞŞŞŞ©©©©©©©¨¨¨¨¨¨§§§§§§¦¦¦¦¦ĄĄĄĄĄĄ¤¤¤¤¤ŁŁŁŁŁ˘˘˘˘˘ˇˇˇˇ    źźźźźžžžžťťťťśśśś››››šššš™™™™———––––•••””””“““’’’‘‘‘‘ŹŹŹŹŽŽŽŤŤŤŚŚŚ‹‹‹ŠŠŠ‰‰‰‡‡†††………„„„‚‚‚€€~~~}}}|||{{{zzzyyyxxwwwvvvuuutttsssrrrqqqppppoonnnnmmmlllkkkkjjjiiiihhhgggffffeeeeddddccccbbbbaaaa`````____^^^^]]]]]\\\\\[[[[[ZZZZZZYYYYYXXXXXXWWWWWWVVVVVVVUUUUUUTTTTTTTSSSSSSSSSRRRRRRRQQQQQQQQQPPPPPPPPPPOOOOOOOOONNNNNNNNNNNNNMMMMMMMMMMMLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸·······················¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´łłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻ®®®®®®®®­­­­­­­­­¬¬¬¬¬¬¬««««««««ŞŞŞŞŞŞŞ©©©©©©¨¨¨¨¨¨§§§§§§¦¦¦¦¦¦ĄĄĄĄĄ¤¤¤¤¤ŁŁŁŁŁ˘˘˘˘˘ˇˇˇˇˇ    źźźźžžžžťťťťťśśśś››››ššš™™™™———––––•••”””““““’’’‘‘‘ŹŹŹŽŽŽŽŤŤŤŚŚŚ‹‹‹ŠŠŠ‰‰‰‡‡‡†††………„„„‚‚‚€€~~~}}}|||{{{zzzyyyxxxwwvvvuuutttsssrrrqqqqpppooonnnmmmllllkkkjjjiiiihhhggggffffeeeddddccccbbbbbaaaa````____^^^^^]]]]]\\\\\[[[[[ZZZZZYYYYYYXXXXXXWWWWWWVVVVVVUUUUUUUTTTTTTTTSSSSSSSRRRRRRRRRQQQQQQQQPPPPPPPPPPOOOOOOOOOONNNNNNNNNNNMMMMMMMMMMMMMLLLLLLLLLLLLLLKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸·······················¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´łłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±°°°°°°°°°°ŻŻŻŻŻŻŻŻŻ®®®®®®®®®®­­­­­­­¬¬¬¬¬¬¬¬¬«««««««ŞŞŞŞŞŞ©©©©©©©¨¨¨¨¨¨¨§§§§§¦¦¦¦¦¦ĄĄĄĄĄ¤¤¤¤¤¤ŁŁŁŁŁ˘˘˘˘ˇˇˇˇˇ    źźźźźžžžžťťťťśśśś››››šššš™™™™————–––••••”””“““’’’’‘‘‘ŹŹŹŽŽŽŤŤŤŤŚŚ‹‹‹‹ŠŠ‰‰‰‡‡‡†††………„„„‚‚‚€€~~~}}}|||{{{zzzyyyxxxwwwvvvuuttttssrrrrqqqpppooonnnmmmmlllkkkjjjjiiihhhhgggffffeeeeddddccccbbbbaaaa`````____^^^^^]]]]\\\\\[[[[[[ZZZZZYYYYYYXXXXXWWWWWWWVVVVVVVUUUUUUTTTTTTTSSSSSSSSSRRRRRRRQQQQQQQQQQPPPPPPPPPOOOOOOOOOONNNNNNNNNNNMMMMMMMMMMMMMMLLLLLLLLLLLLKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGF¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸·························¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®­­­­­­­­­¬¬¬¬¬¬¬¬«««««««ŞŞŞŞŞŞŞ©©©©©©©¨¨¨¨¨§§§§§§¦¦¦¦¦¦¦ĄĄĄĄĄ¤¤¤¤ŁŁŁŁŁŁ˘˘˘˘˘ˇˇˇˇˇ    źźźźžžžžžťťťťśśśś›››šššš™™™™———––––•••””””“““’’’‘‘‘‘ŹŹŹŽŽŽŤŤŤŚŚŚ‹‹‹ŠŠŠ‰‰‰‡‡‡†††………„„„‚‚‚€€~~~}}}|||{{{zzzyyyxxxwwwvvvuuutttsssrrrqqqpppooonnnnmmmlllkkkjjjjiiiihhhggggffffeeeedddccccbbbbaaaaa````____^^^^^]]]]]\\\\\\[[[[ZZZZZYYYYYYXXXXXXXWWWWWVVVVVVVUUUUUUUTTTTTTTSSSSSSSSRRRRRRRRRQQQQQQQQPPPPPPPPPPPOOOOOOOOONNNNNNNNNNNNNMMMMMMMMMMMLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGG¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸·························¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´łłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻ®®®®®®®®®®­­­­­­­­¬¬¬¬¬¬¬«««««««««ŞŞŞŞŞŞ©©©©©©¨¨¨¨¨¨¨§§§§§§¦¦¦¦¦ĄĄĄĄĄĄ¤¤¤¤¤ŁŁŁŁŁ˘˘˘˘˘ˇˇˇˇˇ    źźźźźžžžžťťťťśśśś››››šššš™™™™————–––••••””””“““’’’‘‘‘ŹŹŹŹŽŽŽŤŤŤŚŚŚ‹‹‹ŠŠŠ‰‰‰‡‡‡†††………„„„‚‚‚€€~~~}}}|||{{{zzzyyyxxxwwwvvvuuutttsssrrrqqqppppooonnnmmmlllkkkkjjjjiiihhhhgggffffeeeeddddccccbbbbaaaa`````____^^^^^]]]]]\\\\\[[[[[ZZZZZZYYYYYXXXXXXWWWWWWWVVVVVVUUUUUUTTTTTTTTTSSSSSSSRRRRRRRRQQQQQQQQQQPPPPPPPPPOOOOOOOOOOONNNNNNNNNNNMMMMMMMMMMMMMLLLLLLLLLLLLLLLKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGG¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸·······················¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´łłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®­­­­­­­­­¬¬¬¬¬¬¬¬«««««««ŞŞŞŞŞŞŞ©©©©©©©¨¨¨¨¨¨§§§§§§¦¦¦¦¦¦ĄĄĄĄĄ¤¤¤¤¤ŁŁŁŁŁŁ˘˘˘˘˘ˇˇˇˇ    źźźźźžžžžžťťťťśśś›››››šššš™™™————–––••••”””““““’’’‘‘‘ŹŹŹŽŽŽŽŤŤŤŚŚŚ‹‹‹ŠŠŠ‰‰‰‡‡‡†††………„„„‚‚‚€€~~~}}}|||{{{zzzyyyxxxwwwvvvuuutttsssrrrqqqqpppooonnnmmmllllkkkjjjjiiihhhhggggfffeeeedddddcccbbbbaaaaa`````____^^^^]]]]]\\\\\\[[[[[ZZZZZYYYYYYXXXXXXWWWWWWVVVVVVVUUUUUUUTTTTTTTSSSSSSSSRRRRRRRRRQQQQQQQQPPPPPPPPPPPOOOOOOOOOONNNNNNNNNNNMMMMMMMMMMMMMMLLLLLLLLLLLLLKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGG¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸·······················¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®­­­­­­­­­¬¬¬¬¬¬¬««««««««ŞŞŞŞŞŞŞ©©©©©©©¨¨¨¨¨¨§§§§§§¦¦¦¦¦¦ĄĄĄĄĄĄ¤¤¤¤¤ŁŁŁŁŁ˘˘˘˘˘ˇˇˇˇˇ    źźźźźžžžžťťťťśśśś››››šššš™™™™———––––••••”””“““’’’’‘‘‘ŹŹŹŽŽŽŤŤŤŚŚŚŚ‹‹‹ŠŠŠ‰‰‰‡‡‡†††………„„„‚‚‚€€~~~}}}|||{{{zzzyyyxxxwwwvvvuuutttssssrrrqqqpppooonnnmmmmlllkkkjjjjiiiihhhggggffffeeeeddddccccbbbbaaaa`````____^^^^^]]]]]\\\\\[[[[[ZZZZZZYYYYYYXXXXXXWWWWWWVVVVVVVUUUUUUTTTTTTTTTSSSSSSSRRRRRRRRRQQQQQQQQQPPPPPPPPPPOOOOOOOOOONNNNNNNNNNNNMMMMMMMMMMMMLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGG¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸·························¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´łłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻ®®®®®®®®®®­­­­­­­¬¬¬¬¬¬¬¬¬«««««««ŞŞŞŞŞŞŞ©©©©©©©©¨¨¨¨¨§§§§§§¦¦¦¦¦¦¦ĄĄĄĄĄ¤¤¤¤¤ŁŁŁŁŁ˘˘˘˘˘˘ˇˇˇˇ    źźźźźžžžžžťťťśśśśś››››šššš™™™————–––••••””””“““’’’‘‘‘ŹŹŹŽŽŽŤŤŤŚŚŚ‹‹‹‹ŠŠŠ‰‰‰‡‡‡†††………„„„‚‚‚€€~~~}}}|||{{{zzzyyyxxxwwwvvvuuuttttsssrrrqqqpppoooonnnmmmlllkkkkjjjjiiihhhhggggfffeeeeddddcccccbbbaaaaa`````____^^^^]]]]]]\\\\\[[[[[ZZZZZYYYYYYYXXXXXXWWWWWVVVVVVVVUUUUUUUTTTTTTTSSSSSSSSSRRRRRRRQQQQQQQQQQPPPPPPPPPOOOOOOOOOOOONNNNNNNNNNMMMMMMMMMMMMMMLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGG¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸·························¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´łłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®­­­­­­­­­¬¬¬¬¬¬¬««««««««ŞŞŞŞŞŞŞŞ©©©©©©¨¨¨¨¨¨¨§§§§§§¦¦¦¦¦ĄĄĄĄĄĄ¤¤¤¤¤¤ŁŁŁŁ˘˘˘˘˘ˇˇˇˇˇˇ    źźźźžžžžžťťťťśśśś››››šššš™™™™————–––••••”””““““’’’‘‘‘ŹŹŹŹŽŽŽŤŤŤŚŚŚ‹‹‹ŠŠŠ‰‰‰‰‡‡‡†††………„„„‚‚‚€€~~~}}}|||{{{zzzyyyxxxwwwvvvvuuutttsssrrrqqqppppooonnnmmmllllkkkjjjjiiihhhhggggffffeeeeddddccccbbbbaaaaa````____^^^^^^]]]]]\\\\[[[[[[ZZZZZZYYYYYXXXXXXWWWWWWWVVVVVVUUUUUUUUTTTTTTTTSSSSSSSRRRRRRRRRQQQQQQQQPPPPPPPPPPPPOOOOOOOOONNNNNNNNNNNNMMMMMMMMMMMMMMLLLLLLLLLLLLLKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGG¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸······················¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®­­­­­­­­¬¬¬¬¬¬¬¬««««««««ŞŞŞŞŞŞ©©©©©©©©¨¨¨¨¨¨§§§§§§¦¦¦¦¦¦ĄĄĄĄĄ¤¤¤¤¤¤ŁŁŁŁŁ˘˘˘˘˘ˇˇˇˇ     źźźźźžžžžťťťťťśśśś››››šššš™™™™————––––•••””””“““’’’’‘‘‘ŹŹŹŹŽŽŽŤŤŤŚŚŚ‹‹‹ŠŠŠ‰‰‰‡‡‡††††………„„„‚‚‚€€~~~}}}|||{{{zzzyyyyxxxwwwvvvuuutttsssrrrqqqppppooonnnmmmmlllkkkkjjjiiiihhhhgggffffeeeeddddccccbbbbbaaaa`````____^^^^^]]]]]\\\\\[[[[[[ZZZZZYYYYYYXXXXXXWWWWWWVVVVVVVVUUUUUUTTTTTTTTSSSSSSSSRRRRRRRRQQQQQQQQQQPPPPPPPPPPOOOOOOOOOOONNNNNNNNNNNNMMMMMMMMMMMMLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGG¸¸¸¸¸¸¸¸¸¸¸¸¸·······················¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®­­­­­­­­­¬¬¬¬¬¬¬¬«««««««ŞŞŞŞŞŞŞŞ©©©©©©¨¨¨¨¨¨¨§§§§§§¦¦¦¦¦¦ĄĄĄĄĄĄ¤¤¤¤¤ŁŁŁŁŁ˘˘˘˘˘ˇˇˇˇˇ     źźźźžžžžžťťťťśśśś››››šššš™™™™————–––••••”””““““’’’‘‘‘‘ŹŹŹŽŽŽŽŤŤŤŚŚŚ‹‹‹ŠŠŠ‰‰‰‡‡‡†††………„„„‚‚‚€€€~~~}}}|||{{{zzzyyyxxxwwwvvvuuutttsssrrrqqqqpppooonnnnmmmllllkkkjjjjiiihhhhggggffffeeeeddddccccbbbbaaaaa````____^^^^^^]]]]]\\\\\[[[[[ZZZZZZYYYYYYXXXXXXWWWWWWWVVVVVVUUUUUUUUTTTTTTTSSSSSSSSRRRRRRRRRQQQQQQQQQPPPPPPPPPPOOOOOOOOOOONNNNNNNNNNNMMMMMMMMMMMMMMLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGG¸¸¸¸¸¸¸¸¸¸··························¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´łłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®­­­­­­­­­¬¬¬¬¬¬¬¬««««««««ŞŞŞŞŞŞ©©©©©©©©¨¨¨¨¨¨§§§§§§¦¦¦¦¦¦¦ĄĄĄĄĄ¤¤¤¤¤ŁŁŁŁŁŁ˘˘˘˘˘ˇˇˇˇ     źźźźźžžžžťťťťťśśśś››››šššš™™™™————––––••••”””““““’’’‘‘‘ŹŹŹŽŽŽŤŤŤŚŚŚŚ‹‹‹ŠŠŠ‰‰‰‡‡‡†††………„„„‚‚‚€€€~~~}}}|||{{{zzzyyyxxxwwwvvvuuutttssssrrrqqqpppoooonnnmmmllllkkkjjjjiiiihhhhgggffffeeeeddddccccbbbbbaaaa`````_____^^^^]]]]]\\\\\\[[[[[ZZZZZYYYYYYYXXXXXXWWWWWWVVVVVVVVUUUUUUTTTTTTTTSSSSSSSSRRRRRRRRRQQQQQQQQQPPPPPPPPPPPOOOOOOOOONNNNNNNNNNNNNMMMMMMMMMMMMMMLLLLLLLLLLLLLKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGG¸¸¸¸¸¸¸¸¸¸··························¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®­­­­­­­­¬¬¬¬¬¬¬¬¬««««««ŞŞŞŞŞŞŞŞ©©©©©©©¨¨¨¨¨¨§§§§§§§¦¦¦¦¦ĄĄĄĄĄĄ¤¤¤¤¤¤ŁŁŁŁŁ˘˘˘˘˘ˇˇˇˇˇ    źźźźźžžžžžťťťťśśśś›››››ššš™™™™————––––•••””””“““’’’’‘‘‘ŹŹŹŹŽŽŽŤŤŤŚŚŚŚ‹‹‹ŠŠŠ‰‰‰‡‡‡†††………„„„‚‚‚€€€~~~}}}|||{{{zzzyyyxxxwwwvvvuuutttssssrrrqqqppppooonnnmmmmlllkkkkjjjiiiihhhhggggffffeeedddddccccbbbbaaaaa`````____^^^^^]]]]]\\\\\[[[[[[ZZZZZZYYYYYXXXXXXXWWWWWWVVVVVVVUUUUUUUUTTTTTTSSSSSSSSSRRRRRRRRQQQQQQQQQQQPPPPPPPPPOOOOOOOOOOOONNNNNNNNNNNMMMMMMMMMMMMMLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGG¸¸¸¸¸¸¸¸¸·······················¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®­­­­­­­­­­¬¬¬¬¬¬¬««««««««ŞŞŞŞŞŞŞ©©©©©©©¨¨¨¨¨¨¨§§§§§§¦¦¦¦¦¦ĄĄĄĄĄ¤¤¤¤¤¤ŁŁŁŁŁŁ˘˘˘˘ˇˇˇˇˇ     źźźźžžžžžťťťťťśśśś››››šššš™™™™————–––••••”””““““’’’‘‘‘‘ŹŹŹŹŽŽŽŤŤŤŚŚŚ‹‹‹‹ŠŠŠ‰‰‰‡‡‡†††………„„„‚‚‚€€€~~~}}}|||{{{zzzyyyxxxwwwvvvuuuttttsssrrrqqqppppooonnnnmmmllllkkkjjjjiiihhhhggggffffeeeeddddccccbbbbbaaaaa````_____^^^^^]]]]\\\\\\[[[[[[ZZZZZYYYYYYXXXXXXWWWWWWWVVVVVVVUUUUUUUTTTTTTTTSSSSSSSRRRRRRRRRRQQQQQQQQQPPPPPPPPPPPOOOOOOOOOONNNNNNNNNNNMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGG¸¸¸¸¸·························¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´łłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®­­­­­­­¬¬¬¬¬¬¬¬¬¬««««««ŞŞŞŞŞŞŞŞŞ©©©©©©¨¨¨¨¨¨¨§§§§§§¦¦¦¦¦¦ĄĄĄĄĄĄ¤¤¤¤¤ŁŁŁŁŁŁ˘˘˘˘˘ˇˇˇˇˇ    źźźźźžžžžťťťťťśśśśś›››ššššš™™™™————––––••••”””““““’’’‘‘‘‘ŹŹŹŽŽŽŽŤŤŤŚŚŚ‹‹‹ŠŠŠŠ‰‰‰‡‡‡†††………„„„‚‚‚€€€~~~}}}|||{{{zzzyyyxxxwwwvvvuuuutttsssrrrqqqqpppooonnnnmmmllllkkkjjjjiiiihhhhgggffffeeeeedddcccccbbbbbaaaa`````____^^^^^]]]]]\\\\\\[[[[[ZZZZZZYYYYYYXXXXXXWWWWWWWVVVVVVUUUUUUUUUTTTTTTSSSSSSSSSSRRRRRRRQQQQQQQQQQQPPPPPPPPPPOOOOOOOOOONNNNNNNNNNNNNMMMMMMMMMMMMMMLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHGGGG¸¸¸···························¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®­­­­­­­­­¬¬¬¬¬¬¬¬««««««««ŞŞŞŞŞŞŞ©©©©©©©¨¨¨¨¨¨¨§§§§§§¦¦¦¦¦¦¦ĄĄĄĄĄ¤¤¤¤¤¤ŁŁŁŁŁ˘˘˘˘˘ˇˇˇˇˇ     źźźźžžžžžťťťťśśśśś››››šššš™™™™————––––•••””””“““’’’’‘‘‘ŹŹŹŽŽŽŽŤŤŤŚŚŚ‹‹‹ŠŠŠ‰‰‰‰‡‡‡†††………„„„‚‚‚€€€~~~}}}|||{{{zzzyyyxxxwwwvvvvuuutttsssrrrqqqqpppoooonnnmmmmlllkkkkjjjiiiihhhhggggffffeeeeddddcccccbbbbaaaa`````_____^^^^^]]]]]\\\\\[[[[[[ZZZZZYYYYYYYXXXXXXWWWWWWWVVVVVVVUUUUUUUTTTTTTTTSSSSSSSSRRRRRRRRRQQQQQQQQQPPPPPPPPPPOOOOOOOOOOOOONNNNNNNNNNNMMMMMMMMMMMMMLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHGG¸¸¸·························¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®­­­­­­­­­¬¬¬¬¬¬¬¬««««««««ŞŞŞŞŞŞŞŞ©©©©©©©¨¨¨¨¨¨§§§§§§§¦¦¦¦¦ĄĄĄĄĄĄĄ¤¤¤¤¤ŁŁŁŁŁ˘˘˘˘˘˘ˇˇˇˇ     źźźźźžžžžťťťťťśśśś›››››šššš™™™———––––••••”””““““’’’’‘‘‘ŹŹŹŹŽŽŽŤŤŤŚŚŚŚ‹‹‹ŠŠŠ‰‰‰‰‡‡‡†††………„„„‚‚‚€€€~~~}}}|||{{{zzzyyyxxxwwwvvvvuuutttssssrrrqqqppppooonnnmmmmllllkkkjjjjiiiihhhggggffffeeeedddddccccbbbbbaaaa`````_____^^^^]]]]]]\\\\\[[[[[ZZZZZZZYYYYYXXXXXXXWWWWWWVVVVVVVUUUUUUUUTTTTTTTTSSSSSSSSRRRRRRRRRQQQQQQQQQPPPPPPPPPPPPOOOOOOOOOONNNNNNNNNNNMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHGG¸¸······················¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®­­­­­­­­­¬¬¬¬¬¬¬¬««««««««ŞŞŞŞŞŞŞŞ©©©©©©©¨¨¨¨¨¨¨§§§§§§¦¦¦¦¦¦ĄĄĄĄĄ¤¤¤¤¤¤ŁŁŁŁŁŁ˘˘˘˘˘ˇˇˇˇˇ     źźźźźžžžžťťťťśśśśś››››šššš™™™™————––––••••”””““““’’’‘‘‘‘ŹŹŹŹŽŽŽŤŤŤŚŚŚŚ‹‹‹ŠŠŠ‰‰‰‡‡‡‡†††………„„„‚‚‚€€€~~~}}}|||{{{zzzyyyxxxxwwwvvvuuutttssssrrrqqqppppooonnnnmmmllllkkkjjjjiiiihhhhggggffffeeeeddddcccccbbbbaaaa`````_____^^^^^]]]]]\\\\\\[[[[[[ZZZZZYYYYYYXXXXXXWWWWWWWVVVVVVVUUUUUUUUTTTTTTTTSSSSSSSSRRRRRRRRRQQQQQQQQQQPPPPPPPPPPOOOOOOOOOOONNNNNNNNNNNNNMMMMMMMMMMMMMLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHG························¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®­­­­­­­­­­¬¬¬¬¬¬¬¬««««««««ŞŞŞŞŞŞŞŞ©©©©©©©¨¨¨¨¨¨§§§§§§§¦¦¦¦¦¦ĄĄĄĄĄĄ¤¤¤¤¤ŁŁŁŁŁŁ˘˘˘˘˘ˇˇˇˇˇ     źźźźźžžžžžťťťťśśśś›››››šššš™™™™————––––•••””””“““’’’’‘‘‘ŹŹŹŽŽŽŽŤŤŤŚŚŚ‹‹‹‹ŠŠŠ‰‰‰‡‡‡‡†††………„„„‚‚‚€€€~~~}}}|||{{{zzzyyyxxxxwwwvvvuuuttttsssrrrqqqqpppooonnnnmmmmlllkkkkjjjiiiihhhhggggffffeeeedddddccccbbbbaaaaa`````_____^^^^^]]]]]\\\\\\[[[[[ZZZZZZYYYYYYXXXXXXXWWWWWWVVVVVVVUUUUUUUUTTTTTTTTSSSSSSSSRRRRRRRRRRQQQQQQQQPPPPPPPPPPPOOOOOOOOOOOONNNNNNNNNNNNMMMMMMMMMMMMLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHH························¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®­­­­­­­­¬¬¬¬¬¬¬¬¬¬«««««««ŞŞŞŞŞŞŞŞ©©©©©©©¨¨¨¨¨¨¨§§§§§§¦¦¦¦¦¦¦ĄĄĄĄĄ¤¤¤¤¤¤ŁŁŁŁŁ˘˘˘˘˘˘ˇˇˇˇˇ    źźźźźźžžžžťťťťťśśśś››››ššššš™™™————––––••••””””“““’’’’‘‘‘ŹŹŹŽŽŽŤŤŤŤŚŚŚ‹‹‹ŠŠŠŠ‰‰‰‡‡‡††††………„„„‚‚‚€€€~~~}}}|||{{{zzzyyyyxxxwwwvvvuuuutttsssrrrrqqqpppoooonnnmmmmlllkkkkjjjjiiiihhhhggggfffeeeeeddddccccbbbbbaaaa``````____^^^^^]]]]]]\\\\\[[[[[[ZZZZZYYYYYYYXXXXXXWWWWWWWVVVVVVVUUUUUUUUTTTTTTTSSSSSSSSSRRRRRRRRRQQQQQQQQQQPPPPPPPPPPPOOOOOOOOOONNNNNNNNNNNNMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHH·····················¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®­­­­­­­­­¬¬¬¬¬¬¬¬«««««««««ŞŞŞŞŞŞŞ©©©©©©©©¨¨¨¨¨¨§§§§§§§¦¦¦¦¦ĄĄĄĄĄĄĄ¤¤¤¤¤ŁŁŁŁŁŁ˘˘˘˘˘ˇˇˇˇˇ     źźźźźžžžžžťťťťśśśś›››››šššš™™™™————––––••••”””““““’’’‘‘‘‘ŹŹŹŹŽŽŽŤŤŤŤŚŚŚ‹‹‹ŠŠŠŠ‰‰‰‡‡‡††††………„„„‚‚‚€€€~~~}}}|||{{{zzzyyyyxxxwwwvvvuuuutttsssrrrrqqqppppooonnnnmmmllllkkkjjjjiiiihhhhggggffffeeeedddddccccbbbbaaaaa`````_____^^^^^]]]]]\\\\\\[[[[[ZZZZZZZYYYYYXXXXXXXWWWWWWVVVVVVVVUUUUUUUTTTTTTTTTSSSSSSSRRRRRRRRRRQQQQQQQQQQPPPPPPPPPPOOOOOOOOOOOONNNNNNNNNNNNNMMMMMMMMMMMMMLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHH··················¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®­­­­­­­­­¬¬¬¬¬¬¬¬¬«««««««ŞŞŞŞŞŞŞŞŞ©©©©©©¨¨¨¨¨¨¨¨§§§§§¦¦¦¦¦¦¦ĄĄĄĄĄĄ¤¤¤¤¤¤ŁŁŁŁŁ˘˘˘˘˘˘ˇˇˇˇˇ    źźźźźźžžžžťťťťťśśśś››››šššš™™™™™————––––••••””””““““’’’‘‘‘‘ŹŹŹŹŽŽŽŤŤŤŚŚŚŚ‹‹‹ŠŠŠ‰‰‰‰‡‡‡†††…………„„„‚‚‚€€€~~~}}}|||{{{zzzzyyyxxxwwwvvvvuuutttssssrrrqqqppppooonnnnmmmllllkkkkjjjjiiihhhhhgggfffffeeeeddddccccbbbbbaaaa``````____^^^^^]]]]]]\\\\\[[[[[[ZZZZZZYYYYYYYXXXXXWWWWWWWWVVVVVVUUUUUUUUUTTTTTTTSSSSSSSSSRRRRRRRRRQQQQQQQQQPPPPPPPPPPPPOOOOOOOOOOONNNNNNNNNNNNMMMMMMMMMMMMMLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHH··················¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®­­­­­­­­­¬¬¬¬¬¬¬¬«««««««««ŞŞŞŞŞŞŞ©©©©©©©©¨¨¨¨¨¨§§§§§§§¦¦¦¦¦¦ĄĄĄĄĄĄĄ¤¤¤¤¤ŁŁŁŁŁŁ˘˘˘˘˘ˇˇˇˇˇ     źźźźźžžžžžťťťťśśśśś››››šššš™™™™————––––••••””””“““’’’’‘‘‘ŹŹŹŽŽŽŽŤŤŤŚŚŚŚ‹‹‹ŠŠŠ‰‰‰‰‡‡‡†††…………„„„‚‚‚€€€~~~}}}|||{{{zzzzyyyxxxwwwvvvvuuutttssssrrrqqqppppoooonnnmmmmlllkkkkjjjjiiiihhhhggggffffeeeeddddcccccbbbbaaaaa`````_____^^^^^]]]]]\\\\\\[[[[[ZZZZZZZYYYYYYXXXXXXXWWWWWWVVVVVVVVUUUUUUUTTTTTTTTTSSSSSSSSRRRRRRRRRQQQQQQQQQQQPPPPPPPPPPOOOOOOOOOONNNNNNNNNNNNNMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHH·················¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®­­­­­­­­­­¬¬¬¬¬¬¬¬¬«««««««ŞŞŞŞŞŞŞŞŞ©©©©©©¨¨¨¨¨¨¨¨§§§§§§¦¦¦¦¦¦¦ĄĄĄĄĄ¤¤¤¤¤¤¤ŁŁŁŁ˘˘˘˘˘˘ˇˇˇˇˇ     źźźźźžžžžžťťťťťśśśś››››šššš™™™™™————––––•••””””““““’’’’‘‘‘ŹŹŹŽŽŽŽŤŤŤŚŚŚ‹‹‹‹ŠŠŠ‰‰‰‡‡‡†††………„„„„‚‚‚€€€~~~}}}|||{{{{zzzyyyxxxwwwwvvvuuuttttsssrrrqqqqpppoooonnnmmmmllllkkkkjjjiiiihhhhggggfffffeeeeddddccccbbbbbaaaaa`````_____^^^^^]]]]]\\\\\[[[[[[[ZZZZZYYYYYYYXXXXXXWWWWWWWWVVVVVVUUUUUUUUUTTTTTTTSSSSSSSSSRRRRRRRRRRQQQQQQQQQPPPPPPPPPPOOOOOOOOOOOOONNNNNNNNNNNNNMMMMMMMMMMMMMLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHH·············¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®­­­­­­­­­¬¬¬¬¬¬¬¬¬««««««««ŞŞŞŞŞŞŞ©©©©©©©©¨¨¨¨¨¨§§§§§§§§¦¦¦¦¦ĄĄĄĄĄĄĄ¤¤¤¤¤ŁŁŁŁŁŁ˘˘˘˘˘ˇˇˇˇˇˇ    źźźźźźžžžžťťťťťśśśś›››››šššš™™™™————––––••••””””““““’’’‘‘‘‘ŹŹŹŹŽŽŽŤŤŤŤŚŚŚ‹‹‹‹ŠŠŠ‰‰‰‡‡‡†††………„„„„‚‚‚€€€~~~}}}|||{{{{zzzyyyxxxwwwwvvvuuuttttsssrrrrqqqppppooonnnnmmmllllkkkkjjjjiiiihhhhggggffffeeeedddddccccbbbbbaaaa``````____^^^^^^]]]]\\\\\\\[[[[[ZZZZZZZYYYYYXXXXXXXXWWWWWWVVVVVVVVUUUUUUUTTTTTTTTSSSSSSSSSRRRRRRRRRQQQQQQQQQQPPPPPPPPPPPPOOOOOOOOOOONNNNNNNNNNNNMMMMMMMMMMMMMLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHH············¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®­­­­­­­­­­­¬¬¬¬¬¬¬¬««««««««ŞŞŞŞŞŞŞŞ©©©©©©©¨¨¨¨¨¨¨§§§§§§¦¦¦¦¦¦¦ĄĄĄĄĄĄ¤¤¤¤¤¤ŁŁŁŁŁ˘˘˘˘˘˘ˇˇˇˇˇ     źźźźźžžžžžťťťťśśśśś››››ššššš™™™™————––––••••””””“““’’’’‘‘‘‘ŹŹŹŹŽŽŽŤŤŤŤŚŚŚ‹‹‹ŠŠŠŠ‰‰‰‡‡‡‡†††………„„„„‚‚‚€€€~~~}}}|||{{{{zzzyyyxxxxwwwvvvuuuutttsssrrrrqqqppppooonnnnmmmmlllkkkkjjjjiiiihhhhggggffffeeeeeddddcccccbbbbaaaaa`````_____^^^^^]]]]]]\\\\\[[[[[[ZZZZZZYYYYYYYXXXXXXWWWWWWWVVVVVVVUUUUUUUUTTTTTTTTSSSSSSSSRRRRRRRRRRRQQQQQQQQQPPPPPPPPPPPOOOOOOOOOOONNNNNNNNNNNNNMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHH············¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®­­­­­­­­¬¬¬¬¬¬¬¬¬¬««««««««ŞŞŞŞŞŞŞŞ©©©©©©©©¨¨¨¨¨¨§§§§§§§¦¦¦¦¦¦ĄĄĄĄĄĄĄ¤¤¤¤¤ŁŁŁŁŁŁ˘˘˘˘˘ˇˇˇˇˇˇ    źźźźźźžžžžťťťťťśśśś›››››šššš™™™™™—————––––•••””””““““’’’’‘‘‘ŹŹŹŹŽŽŽŤŤŤŚŚŚŚ‹‹‹ŠŠŠŠ‰‰‰‡‡‡‡†††………„„„‚‚‚€€€~~~}}}||||{{{zzzyyyxxxxwwwvvvuuuutttssssrrrqqqppppoooonnnmmmmllllkkkkjjjiiiihhhhhgggfffffeeeedddddccccbbbbbaaaa``````____^^^^^^]]]]]\\\\\\[[[[[ZZZZZZZYYYYYYXXXXXXXWWWWWWVVVVVVVVUUUUUUUUTTTTTTTTSSSSSSSSSSRRRRRRRRQQQQQQQQQQPPPPPPPPPPPOOOOOOOOOOOOONNNNNNNNNNNNMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHH·········¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®­­­­­­­­­¬¬¬¬¬¬¬¬«««««««««ŞŞŞŞŞŞŞŞ©©©©©©©¨¨¨¨¨¨¨¨§§§§§§¦¦¦¦¦¦¦ĄĄĄĄĄ¤¤¤¤¤¤¤ŁŁŁŁŁ˘˘˘˘˘˘ˇˇˇˇˇ     źźźźźžžžžžťťťťśśśśś››››ššššš™™™™————––––••••””””““““’’’‘‘‘‘ŹŹŹŽŽŽŽŤŤŤŚŚŚŚ‹‹‹ŠŠŠŠ‰‰‰‡‡‡‡†††………„„„‚‚‚€€€~~~}}}||||{{{zzzyyyxxxxwwwvvvuuuutttssssrrrqqqqpppoooonnnnmmmllllkkkkjjjjiiiihhhhggggffffeeeeeddddcccccbbbbaaaaa`````_____^^^^^]]]]]]\\\\\[[[[[[[ZZZZZYYYYYYYXXXXXXWWWWWWWWVVVVVVVUUUUUUUUTTTTTTTTTSSSSSSSSRRRRRRRRRQQQQQQQQQQQPPPPPPPPPPPOOOOOOOOOOONNNNNNNNNNNNMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIHHHHHHHH······¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®­­­­­­­­­­¬¬¬¬¬¬¬¬¬««««««««ŞŞŞŞŞŞŞŞ©©©©©©©©¨¨¨¨¨¨§§§§§§§§¦¦¦¦¦¦ĄĄĄĄĄĄ¤¤¤¤¤¤ŁŁŁŁŁŁ˘˘˘˘˘ˇˇˇˇˇ     źźźźźžžžžžťťťťťśśśśś››››šššš™™™™————––––••••”””““““’’’’‘‘‘‘ŹŹŹŹŽŽŽŤŤŤŤŚŚŚŚ‹‹‹ŠŠŠ‰‰‰‰‡‡‡††††………„„„‚‚‚€€€~~~}}}||||{{{zzzyyyyxxxwwwvvvvuuutttssssrrrrqqqppppooonnnnmmmmllllkkkjjjjiiiihhhhgggggffffeeeeddddcccccbbbbbaaaaa`````_____^^^^^]]]]]\\\\\\[[[[[[ZZZZZZYYYYYYXXXXXXXXWWWWWWVVVVVVVVUUUUUUUUTTTTTTTTSSSSSSSSSRRRRRRRRRRQQQQQQQQQPPPPPPPPPPPOOOOOOOOOOOONNNNNNNNNNNNNMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIHHHHH······¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®­­­­­­­­­¬¬¬¬¬¬¬¬««««««««««ŞŞŞŞŞŞŞ©©©©©©©¨¨¨¨¨¨¨¨§§§§§§¦¦¦¦¦¦¦ĄĄĄĄĄĄ¤¤¤¤¤¤ŁŁŁŁŁŁ˘˘˘˘˘ˇˇˇˇˇˇ     źźźźźžžžžťťťťťťśśśś›››››šššš™™™™————––––••••””””““““’’’’‘‘‘‘ŹŹŹŹŽŽŽŤŤŤŤŚŚŚ‹‹‹‹ŠŠŠ‰‰‰‰‡‡‡††††………„„„‚‚‚€€€~~~}}}||||{{{zzzyyyyxxxwwwvvvvuuuttttsssrrrrqqqppppooonnnnmmmmllllkkkkjjjjiiiihhhhggggffffeeeedddddccccbbbbbbaaaa`````_____^^^^^^]]]]]\\\\\\[[[[[[ZZZZZZYYYYYYYXXXXXXWWWWWWWWVVVVVVVUUUUUUUTTTTTTTTTTSSSSSSSSRRRRRRRRRQQQQQQQQQQPPPPPPPPPPPPOOOOOOOOOOOONNNNNNNNNNNNNMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIHHHHH······¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®­­­­­­­­­­­¬¬¬¬¬¬¬¬««««««««ŞŞŞŞŞŞŞŞŞ©©©©©©©¨¨¨¨¨¨¨§§§§§§§¦¦¦¦¦¦ĄĄĄĄĄĄĄ¤¤¤¤¤ŁŁŁŁŁŁ˘˘˘˘˘˘ˇˇˇˇˇ     źźźźźžžžžžťťťťťśśśśś››››ššššš™™™™————––––••••””””““““’’’’‘‘‘ŹŹŹŽŽŽŽŤŤŤŤŚŚŚ‹‹‹‹ŠŠŠ‰‰‰‡‡‡††††………„„„‚‚‚€€€~~~}}}||||{{{zzzyyyyxxxwwwwvvvuuuttttsssrrrrqqqqpppoooonnnmmmmllllkkkkjjjjiiiihhhhggggffffeeeeeddddcccccbbbbbaaaaa`````_____^^^^^]]]]]]\\\\\\[[[[[ZZZZZZZYYYYYYXXXXXXXWWWWWWWVVVVVVVUUUUUUUUUTTTTTTTTSSSSSSSSRRRRRRRRRRRQQQQQQQQQQPPPPPPPPPPPOOOOOOOOOOONNNNNNNNNNNNNMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIHHHHH··¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®­­­­­­­­­¬¬¬¬¬¬¬¬¬«««««««««ŞŞŞŞŞŞŞ©©©©©©©©¨¨¨¨¨¨¨§§§§§§§¦¦¦¦¦¦¦ĄĄĄĄĄĄ¤¤¤¤¤¤ŁŁŁŁŁŁ˘˘˘˘˘ˇˇˇˇˇˇ    źźźźźźžžžžžťťťťťśśśś›››››šššš™™™™————––––••••”””““““’’’’‘‘‘‘ŹŹŹŹŽŽŽŽŤŤŤŚŚŚŚ‹‹‹ŠŠŠŠ‰‰‰‡‡‡††††………„„„‚‚‚€€€~~~}}}||||{{{zzzyyyyxxxwwwwvvvuuuutttssssrrrqqqqppppooonnnnmmmmllllkkkjjjjiiiihhhhgggggffffeeeedddddccccbbbbbaaaaa``````____^^^^^^]]]]]\\\\\\[[[[[[ZZZZZZYYYYYYYXXXXXXXWWWWWWWVVVVVVVVUUUUUUUTTTTTTTTTSSSSSSSSSRRRRRRRRRQQQQQQQQQQPPPPPPPPPPPOOOOOOOOOOOOONNNNNNNNNNNNNMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIH¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®­­­­­­­­­­¬¬¬¬¬¬¬¬««««««««ŞŞŞŞŞŞŞŞŞ©©©©©©©¨¨¨¨¨¨¨§§§§§§§§¦¦¦¦¦¦ĄĄĄĄĄĄ¤¤¤¤¤¤ŁŁŁŁŁŁ˘˘˘˘˘˘ˇˇˇˇˇ     źźźźźžžžžžťťťťťśśśś›››››šššš™™™™™————––––••••””””““““’’’’‘‘‘‘ŹŹŹŹŽŽŽŽŤŤŤŚŚŚŚ‹‹‹ŠŠŠŠ‰‰‰‡‡‡†††…………„„„‚‚‚‚€€€~~~}}}}|||{{{zzzzyyyxxxwwwwvvvuuuutttssssrrrqqqqppppooonnnnmmmmllllkkkkjjjjiiiihhhhggggfffffeeeedddddccccbbbbbaaaaa`````_____^^^^^]]]]]]\\\\\\[[[[[[ZZZZZZYYYYYYXXXXXXXXWWWWWWWVVVVVVVUUUUUUUUUTTTTTTTTSSSSSSSSRRRRRRRRRRQQQQQQQQQQQPPPPPPPPPPPPOOOOOOOOOOONNNNNNNNNNNNNMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIII¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬««««««««ŞŞŞŞŞŞŞ©©©©©©©©©¨¨¨¨¨¨¨§§§§§§¦¦¦¦¦¦¦ĄĄĄĄĄĄ¤¤¤¤¤¤ŁŁŁŁŁŁ˘˘˘˘˘˘ˇˇˇˇˇˇ    źźźźźźžžžžžťťťťśśśśś››››ššššš™™™™—————––––••••””””““““’’’‘‘‘‘‘ŹŹŹŹŽŽŽŤŤŤŤŚŚŚŚ‹‹‹ŠŠŠŠ‰‰‰‡‡‡‡†††…………„„„‚‚‚‚€€€~~~}}}}|||{{{zzzzyyyxxxxwwwvvvuuuutttssssrrrrqqqppppooonnnnnmmmllllkkkkjjjjiiiihhhhhggggffffeeeedddddcccccbbbbaaaaa``````____^^^^^^]]]]]]\\\\\\[[[[[[ZZZZZZYYYYYYYXXXXXXWWWWWWWVVVVVVVVVUUUUUUUTTTTTTTTSSSSSSSSSSRRRRRRRRRRQQQQQQQQQQPPPPPPPPPPPOOOOOOOOOOONNNNNNNNNNNNNMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIII¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®­­­­­­­­­¬¬¬¬¬¬¬¬¬«««««««««ŞŞŞŞŞŞŞŞ©©©©©©©¨¨¨¨¨¨¨¨§§§§§§§¦¦¦¦¦¦ĄĄĄĄĄĄĄ¤¤¤¤¤¤ŁŁŁŁŁ˘˘˘˘˘˘˘ˇˇˇˇˇ     źźźźźžžžžžťťťťťśśśś›››››ššššš™™™™————––––•••••””””“““’’’’‘‘‘‘ŹŹŹŽŽŽŽŤŤŤŤŚŚŚ‹‹‹‹ŠŠŠ‰‰‰‰‡‡‡‡†††…………„„„‚‚‚‚€€€~~~}}}}|||{{{zzzzyyyxxxxwwwvvvvuuuttttsssrrrrqqqqpppoooonnnnmmmmlllkkkkjjjjjiiiihhhhggggffffeeeeedddddccccbbbbbaaaaa`````_____^^^^^]]]]]]]\\\\\[[[[[[ZZZZZZZYYYYYYXXXXXXXWWWWWWWWVVVVVVVUUUUUUUUTTTTTTTTTSSSSSSSSSRRRRRRRRRQQQQQQQQQQQPPPPPPPPPPPOOOOOOOOOOOOONNNNNNNNNNNNNMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIII¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬««««««««ŞŞŞŞŞŞŞ©©©©©©©©©¨¨¨¨¨¨¨§§§§§§§¦¦¦¦¦¦¦ĄĄĄĄĄĄ¤¤¤¤¤¤ŁŁŁŁŁŁ˘˘˘˘˘ˇˇˇˇˇˇ     źźźźźžžžžžžťťťťśśśśś›››››šššš™™™™™————––––••••””””““““’’’’‘‘‘‘ŹŹŹŹŽŽŽŽŤŤŤŤŚŚŚ‹‹‹‹ŠŠŠ‰‰‰‰‡‡‡‡†††…………„„„‚‚‚‚€€€~~~}}}}|||{{{zzzzyyyxxxxwwwvvvvuuuttttsssrrrrqqqqppppooonnnnmmmmllllkkkkjjjjiiiihhhhggggfffffeeeedddddcccccbbbbaaaaaa`````_____^^^^^^]]]]]\\\\\\[[[[[[ZZZZZZYYYYYYYXXXXXXXWWWWWWWVVVVVVVVVUUUUUUUTTTTTTTTSSSSSSSSSSRRRRRRRRRRQQQQQQQQQQQPPPPPPPPPPPOOOOOOOOOOOONNNNNNNNNNNNNMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIII¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®­­­­­­­­­­¬¬¬¬¬¬¬¬¬«««««««««ŞŞŞŞŞŞŞŞ©©©©©©©©¨¨¨¨¨¨¨§§§§§§§§¦¦¦¦¦¦ĄĄĄĄĄĄ¤¤¤¤¤¤¤ŁŁŁŁŁ˘˘˘˘˘˘ˇˇˇˇˇˇ     źźźźźžžžžžťťťťťśśśśś››››ššššš™™™™—————––––••••””””““““’’’’‘‘‘ŹŹŹŹŽŽŽŽŤŤŤŚŚŚŚ‹‹‹ŠŠŠŠ‰‰‰‰‡‡‡‡†††………„„„„‚‚‚‚€€€~~~}}}}|||{{{{zzzyyyxxxxwwwvvvvuuuttttssssrrrqqqqppppoooonnnmmmmllllkkkkjjjjiiiihhhhhggggffffeeeeeddddcccccbbbbbaaaaa`````_____^^^^^^]]]]]]\\\\\[[[[[[[ZZZZZZYYYYYYXXXXXXXXWWWWWWWVVVVVVVVUUUUUUUUTTTTTTTTTSSSSSSSSSRRRRRRRRRQQQQQQQQQQQPPPPPPPPPPPOOOOOOOOOOOONNNNNNNNNNNNNMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIII¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®­­­­­­­­­­­¬¬¬¬¬¬¬¬¬««««««««ŞŞŞŞŞŞŞŞ©©©©©©©©©¨¨¨¨¨¨§§§§§§§¦¦¦¦¦¦¦ĄĄĄĄĄĄĄ¤¤¤¤¤ŁŁŁŁŁŁŁ˘˘˘˘˘ˇˇˇˇˇˇ     źźźźźžžžžžžťťťťśśśśś›››››šššš™™™™™————––––•••••””””“““’’’’‘‘‘‘ŹŹŹŹŽŽŽŤŤŤŤŚŚŚŚ‹‹‹ŠŠŠŠ‰‰‰‡‡‡††††………„„„„‚‚‚‚€€€~~~}}}}|||{{{{zzzyyyyxxxwwwwvvvuuuutttssssrrrrqqqppppoooonnnnmmmllllkkkkjjjjjiiiihhhhggggfffffeeeedddddcccccbbbbaaaaaa`````_____^^^^^^]]]]]\\\\\\\[[[[[ZZZZZZZYYYYYYYXXXXXXXWWWWWWVVVVVVVVVUUUUUUUUTTTTTTTTSSSSSSSSSRRRRRRRRRRRQQQQQQQQQQQPPPPPPPPPPPOOOOOOOOOOOOONNNNNNNNNNNNNMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIII¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®­­­­­­­­­¬¬¬¬¬¬¬¬¬¬««««««««««ŞŞŞŞŞŞŞŞ©©©©©©©¨¨¨¨¨¨¨¨§§§§§§§¦¦¦¦¦¦ĄĄĄĄĄĄĄ¤¤¤¤¤¤ŁŁŁŁŁŁ˘˘˘˘˘˘ˇˇˇˇˇ     źźźźźźžžžžžťťťťťśśśśś››››ššššš™™™™™————––––••••””””““““’’’’‘‘‘‘ŹŹŹŹŽŽŽŤŤŤŤŚŚŚŚ‹‹‹ŠŠŠŠ‰‰‰‡‡‡††††………„„„„‚‚‚‚€€€~~~}}}}|||{{{{zzzyyyyxxxwwwwvvvuuuutttssssrrrrqqqppppoooonnnnmmmmllllkkkkjjjjiiiihhhhggggfffffeeeeeddddcccccbbbbbaaaaa``````_____^^^^^]]]]]]\\\\\\[[[[[[ZZZZZZZYYYYYYXXXXXXXWWWWWWWWVVVVVVVUUUUUUUUTTTTTTTTTTSSSSSSSSSRRRRRRRRRRQQQQQQQQQQPPPPPPPPPPPPOOOOOOOOOOOONNNNNNNNNNNNNNMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIII¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®­­­­­­­­­¬¬¬¬¬¬¬¬¬«««««««««ŞŞŞŞŞŞŞŞ©©©©©©©©©¨¨¨¨¨¨¨§§§§§§¦¦¦¦¦¦¦¦ĄĄĄĄĄĄ¤¤¤¤¤¤ŁŁŁŁŁŁŁ˘˘˘˘˘ˇˇˇˇˇ      źźźźźžžžžžťťťťťťśśśś›››››ššššš™™™™—————––––••••””””““““’’’’‘‘‘‘ŹŹŹŹŽŽŽŽŤŤŤŤŚŚŚ‹‹‹‹ŠŠŠŠ‰‰‰‡‡‡††††………„„„„‚‚‚‚€€€~~~}}}}|||{{{{zzzyyyyxxxwwwwvvvuuuuttttsssrrrrqqqqppppooonnnnmmmmllllkkkkjjjjiiiihhhhhggggffffeeeeedddddccccbbbbbbaaaaa`````______^^^^^]]]]]\\\\\\\[[[[[[ZZZZZZYYYYYYYYXXXXXXWWWWWWWVVVVVVVVVUUUUUUUUTTTTTTTTTSSSSSSSSRRRRRRRRRRQQQQQQQQQQQPPPPPPPPPPPPOOOOOOOOOOOONNNNNNNNNNNNNMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIII¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬«««««««««ŞŞŞŞŞŞŞŞ©©©©©©©¨¨¨¨¨¨¨¨§§§§§§§¦¦¦¦¦¦¦ĄĄĄĄĄĄ¤¤¤¤¤¤¤ŁŁŁŁŁŁ˘˘˘˘˘ˇˇˇˇˇˇ     źźźźźźžžžžžťťťťťśśśśś›››››šššš™™™™————––––••••”””””““““’’’’‘‘‘ŹŹŹŹŽŽŽŽŤŤŤŤŚŚŚ‹‹‹‹ŠŠŠ‰‰‰‰‡‡‡††††………„„„„‚‚‚‚€€€~~~}}}}|||{{{{zzzyyyyxxxwwwwvvvvuuuttttsssrrrrqqqqppppoooonnnmmmmllllkkkkkjjjjiiiihhhhgggggffffeeeedddddcccccbbbbbaaaaa``````_____^^^^^^]]]]]\\\\\\[[[[[[[ZZZZZZYYYYYYYXXXXXXXWWWWWWWWVVVVVVVUUUUUUUUTTTTTTTTTSSSSSSSSSSRRRRRRRRRRQQQQQQQQQQQPPPPPPPPPPPPOOOOOOOOOOOONNNNNNNNNNNNNNMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIII¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®­­­­­­­­­­¬¬¬¬¬¬¬¬¬«««««««««ŞŞŞŞŞŞŞŞŞ©©©©©©©©¨¨¨¨¨¨¨§§§§§§§¦¦¦¦¦¦¦ĄĄĄĄĄĄĄ¤¤¤¤¤¤ŁŁŁŁŁŁ˘˘˘˘˘˘ˇˇˇˇˇ      źźźźźžžžžžťťťťťśśśśś›››››šššš™™™™™—————––––••••””””““““’’’’‘‘‘‘ŹŹŹŹŽŽŽŽŤŤŤŚŚŚŚ‹‹‹‹ŠŠŠ‰‰‰‰‡‡‡‡††††………„„„„‚‚‚‚€€€~~~}}}}|||{{{{zzzyyyyxxxxwwwvvvvuuuttttssssrrrqqqqppppoooonnnnmmmmllllkkkkjjjjiiiihhhhhggggfffffeeeedddddcccccbbbbbaaaaa`````______^^^^^]]]]]]\\\\\\[[[[[[ZZZZZZZYYYYYYYXXXXXXXWWWWWWWVVVVVVVVUUUUUUUUUTTTTTTTTTSSSSSSSSSRRRRRRRRRRQQQQQQQQQQPPPPPPPPPPPPOOOOOOOOOOOONNNNNNNNNNNNNNMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJIIIIIIII¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®­­­­­­­­­­­¬¬¬¬¬¬¬¬¬«««««««««ŞŞŞŞŞŞŞŞ©©©©©©©¨¨¨¨¨¨¨¨¨§§§§§§§¦¦¦¦¦¦ĄĄĄĄĄĄĄ¤¤¤¤¤¤¤ŁŁŁŁŁ˘˘˘˘˘˘ˇˇˇˇˇˇ     źźźźźžžžžžžťťťťťśśśśś››››ššššš™™™™™————–––––••••””””““““’’’’‘‘‘‘ŹŹŹŹŽŽŽŤŤŤŤŚŚŚŚ‹‹‹ŠŠŠŠ‰‰‰‰‡‡‡‡†††…………„„„‚‚‚€€€~~~~}}}||||{{{zzzzyyyxxxxwwwvvvvuuuutttssssrrrrqqqppppoooonnnnmmmmllllkkkkjjjjiiiiihhhhggggfffffeeeeeddddcccccbbbbbaaaaaa`````_____^^^^^^]]]]]]\\\\\[[[[[[[ZZZZZZZYYYYYYXXXXXXXWWWWWWWWWVVVVVVVUUUUUUUUTTTTTTTTTSSSSSSSSSRRRRRRRRRRRQQQQQQQQQQQPPPPPPPPPPPPOOOOOOOOOOOONNNNNNNNNNNNNMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJIIIIIIII¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®­­­­­­­­­­¬¬¬¬¬¬¬¬¬««««««««««ŞŞŞŞŞŞŞŞŞ©©©©©©©¨¨¨¨¨¨¨§§§§§§§¦¦¦¦¦¦¦¦ĄĄĄĄĄĄ¤¤¤¤¤¤ŁŁŁŁŁŁŁ˘˘˘˘˘˘ˇˇˇˇˇ      źźźźźžžžžžťťťťťśśśśś›››››ššššš™™™™————––––••••”””””““““’’’’‘‘‘‘ŹŹŹŹŽŽŽŽŤŤŤŤŚŚŚŚ‹‹‹ŠŠŠŠ‰‰‰‰‡‡‡‡†††…………„„„‚‚‚€€€~~~~}}}||||{{{zzzzyyyxxxxwwwvvvvuuuutttssssrrrrqqqqppppooonnnnmmmmllllkkkkkjjjjiiiihhhhgggggffffeeeeedddddcccccbbbbbaaaaa`````______^^^^^]]]]]]\\\\\\\[[[[[ZZZZZZZYYYYYYYYXXXXXXXWWWWWWWVVVVVVVUUUUUUUUUTTTTTTTTTTSSSSSSSSSRRRRRRRRRRQQQQQQQQQQQPPPPPPPPPPPPOOOOOOOOOOOONNNNNNNNNNNNNNNMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJIIIIII¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®­­­­­­­­­­¬¬¬¬¬¬¬¬¬«««««««««ŞŞŞŞŞŞŞŞ©©©©©©©©¨¨¨¨¨¨¨¨§§§§§§§¦¦¦¦¦¦¦ĄĄĄĄĄĄ¤¤¤¤¤¤¤ŁŁŁŁŁŁ˘˘˘˘˘˘ˇˇˇˇˇˇ     źźźźźźžžžžťťťťťťśśśśś››››ššššš™™™™™—————––––••••””””““““’’’’‘‘‘‘ŹŹŹŹŽŽŽŽŤŤŤŤŚŚŚ‹‹‹‹ŠŠŠŠ‰‰‰‡‡‡‡†††…………„„„‚‚‚€€€~~~~}}}||||{{{zzzzyyyxxxxwwwwvvvuuuuttttsssrrrrqqqqppppoooonnnnmmmmllllkkkkjjjjiiiihhhhhggggfffffeeeeeddddcccccbbbbbbaaaa``````_____^^^^^^]]]]]]\\\\\\[[[[[[[ZZZZZZYYYYYYYXXXXXXXWWWWWWWWVVVVVVVVUUUUUUUUTTTTTTTTTSSSSSSSSSRRRRRRRRRRQQQQQQQQQQQPPPPPPPPPPPPOOOOOOOOOOOONNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJIII¶¶¶¶µµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬«««««««««ŞŞŞŞŞŞŞŞŞ©©©©©©©¨¨¨¨¨¨¨¨§§§§§§§¦¦¦¦¦¦¦ĄĄĄĄĄĄĄ¤¤¤¤¤¤ŁŁŁŁŁŁ˘˘˘˘˘˘˘ˇˇˇˇˇ     źźźźźźžžžžžťťťťťśśśśś›››››ššššš™™™™————–––––••••””””““““’’’’‘‘‘‘ŹŹŹŹŽŽŽŽŤŤŤŤŚŚŚ‹‹‹‹ŠŠŠŠ‰‰‰‡‡‡‡†††…………„„„‚‚‚€€€~~~~}}}||||{{{zzzzyyyxxxxwwwwvvvuuuuttttsssrrrrqqqqppppoooonnnnmmmmllllkkkkjjjjiiiiihhhhgggggffffeeeeedddddcccccbbbbbaaaaa``````_____^^^^^]]]]]]]\\\\\\[[[[[[ZZZZZZZYYYYYYYXXXXXXXWWWWWWWVVVVVVVVUUUUUUUUUTTTTTTTTTSSSSSSSSSSRRRRRRRRRRRQQQQQQQQQQQPPPPPPPPPPPOOOOOOOOOOOOONNNNNNNNNNNNNMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJIII¶¶¶µµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬«««««««««ŞŞŞŞŞŞŞŞ©©©©©©©©¨¨¨¨¨¨¨¨¨§§§§§§§¦¦¦¦¦¦ĄĄĄĄĄĄĄ¤¤¤¤¤¤¤ŁŁŁŁŁŁ˘˘˘˘˘˘ˇˇˇˇˇ      źźźźźźžžžžžťťťťťśśśśś›››››šššš™™™™™————––––••••””””“““““’’’’‘‘‘‘ŹŹŹŹŽŽŽŽŤŤŤŚŚŚŚ‹‹‹‹ŠŠŠŠ‰‰‰‡‡‡††††…………„„„‚‚‚€€€~~~~}}}||||{{{zzzzyyyyxxxwwwwvvvuuuuttttssssrrrqqqqppppoooonnnnmmmmlllllkkkkjjjjiiiihhhhgggggfffffeeeedddddcccccbbbbbaaaaa``````______^^^^^]]]]]]\\\\\\[[[[[[[ZZZZZZZYYYYYYXXXXXXXWWWWWWWWWVVVVVVVVUUUUUUUUTTTTTTTTTSSSSSSSSSSRRRRRRRRRRQQQQQQQQQQQPPPPPPPPPPPPOOOOOOOOOOOOONNNNNNNNNNNNNNMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJIIµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬««««««««««ŞŞŞŞŞŞŞŞ©©©©©©©©¨¨¨¨¨¨¨§§§§§§§¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄ¤¤¤¤¤¤ŁŁŁŁŁŁ˘˘˘˘˘˘ˇˇˇˇˇˇ     źźźźźźžžžžžťťťťťśśśśś›››››ššššš™™™™™————–––––••••””””“““““’’’’‘‘‘‘ŹŹŹŹŽŽŽŽŤŤŤŤŚŚŚŚ‹‹‹‹ŠŠŠ‰‰‰‰‡‡‡††††…………„„„‚‚‚€€€~~~~}}}||||{{{zzzzyyyyxxxwwwwvvvvuuuttttssssrrrrqqqqppppooonnnnmmmmlllllkkkkjjjjiiiiihhhhggggfffffeeeeedddddcccccbbbbbaaaaa``````_____^^^^^^]]]]]]\\\\\\[[[[[[ZZZZZZZYYYYYYYYXXXXXXXWWWWWWWVVVVVVVVUUUUUUUUTTTTTTTTTTSSSSSSSSSSRRRRRRRRRRQQQQQQQQQQQPPPPPPPPPPPPOOOOOOOOOOOONNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬«««««««««ŞŞŞŞŞŞŞŞ©©©©©©©©©¨¨¨¨¨¨¨¨§§§§§§§¦¦¦¦¦¦¦ĄĄĄĄĄĄ¤¤¤¤¤¤¤ŁŁŁŁŁŁŁ˘˘˘˘˘˘ˇˇˇˇˇ      źźźźźźžžžžžťťťťťśśśśś›››››ššššš™™™™—————–––––••••””””““““’’’’‘‘‘‘ŹŹŹŹŽŽŽŽŤŤŤŤŚŚŚŚ‹‹‹ŠŠŠŠ‰‰‰‰‡‡‡††††…………„„„‚‚‚€€€~~~~}}}||||{{{zzzzyyyyxxxwwwwvvvvuuuutttssssrrrrqqqqppppoooonnnnmmmmllllkkkkjjjjiiiiihhhhhggggffffeeeeedddddcccccbbbbbaaaaa``````______^^^^^]]]]]]\\\\\\\[[[[[[[ZZZZZZYYYYYYYXXXXXXXWWWWWWWWVVVVVVVVVUUUUUUUUTTTTTTTTTSSSSSSSSSSRRRRRRRRRRQQQQQQQQQQQPPPPPPPPPPPPPOOOOOOOOOOOOONNNNNNNNNNNNNMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬«««««««««ŞŞŞŞŞŞŞŞ©©©©©©©©¨¨¨¨¨¨¨§§§§§§§§¦¦¦¦¦¦¦ĄĄĄĄĄĄĄ¤¤¤¤¤¤¤ŁŁŁŁŁŁ˘˘˘˘˘˘ˇˇˇˇˇˇ     źźźźźźžžžžžťťťťťśśśśś›››››ššššš™™™™—————––––••••”””””““““’’’’‘‘‘‘ŹŹŹŹŽŽŽŽŤŤŤŤŚŚŚ‹‹‹‹ŠŠŠŠ‰‰‰‰‡‡‡‡††††………„„„„‚‚‚€€€~~~~}}}||||{{{{zzzyyyyxxxxwwwvvvvuuuuttttsssrrrrqqqqppppoooonnnnmmmmllllkkkkkjjjjiiiihhhhhgggggffffeeeeedddddcccccbbbbbaaaaa``````_____^^^^^^]]]]]]\\\\\\[[[[[[[ZZZZZZZYYYYYYYXXXXXXXXWWWWWWWVVVVVVVVUUUUUUUUTTTTTTTTTSSSSSSSSSSRRRRRRRRRRQQQQQQQQQQQPPPPPPPPPPPPPOOOOOOOOOOOOONNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®­­­­­­­­­­­¬¬¬¬¬¬¬¬¬««««««««««ŞŞŞŞŞŞŞŞŞ©©©©©©©©¨¨¨¨¨¨¨¨§§§§§§§§¦¦¦¦¦¦ĄĄĄĄĄĄĄ¤¤¤¤¤¤¤ŁŁŁŁŁŁŁ˘˘˘˘˘ˇˇˇˇˇˇ     źźźźźźžžžžžžťťťťťśśśśś››››šššššš™™™™————–––––••••””””“““““’’’’‘‘‘‘ŹŹŹŹŽŽŽŽŤŤŤŤŚŚŚ‹‹‹‹ŠŠŠŠ‰‰‰‰‡‡‡‡††††………„„„„‚‚‚€€€~~~~}}}||||{{{{zzzyyyyxxxxwwwvvvvuuuuttttsssrrrrqqqqppppoooonnnnmmmmlllllkkkkjjjjiiiiihhhhgggggffffeeeeeeddddcccccbbbbbaaaaaa``````_____^^^^^^]]]]]\\\\\\\[[[[[[[ZZZZZZZYYYYYYXXXXXXXXWWWWWWWWVVVVVVVVUUUUUUUUUTTTTTTTTTTSSSSSSSSSRRRRRRRRRRRQQQQQQQQQQQPPPPPPPPPPPPOOOOOOOOOOOONNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®­­­­­­­­­­­¬¬¬¬¬¬¬¬¬«««««««««ŞŞŞŞŞŞŞŞŞ©©©©©©©©¨¨¨¨¨¨¨¨§§§§§§§¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄ¤¤¤¤¤¤ŁŁŁŁŁŁ˘˘˘˘˘˘ˇˇˇˇˇˇˇ     źźźźźźžžžžžťťťťťśśśśś›››››ššššš™™™™™—————––––•••••””””““““’’’’‘‘‘‘ŹŹŹŹŽŽŽŽŤŤŤŤŚŚŚŚ‹‹‹‹ŠŠŠŠ‰‰‰‡‡‡‡††††………„„„„‚‚‚€€€~~~~}}}||||{{{{zzzyyyyxxxxwwwwvvvuuuuttttssssrrrrqqqqppppoooonnnnmmmmllllkkkkjjjjjiiiihhhhhggggfffffeeeeedddddcccccbbbbbaaaaa``````_____^^^^^^^]]]]]]\\\\\\[[[[[[ZZZZZZZYYYYYYYYXXXXXXXWWWWWWWWVVVVVVVVUUUUUUUUTTTTTTTTTTSSSSSSSSSRRRRRRRRRRRQQQQQQQQQQQPPPPPPPPPPPPPOOOOOOOOOOOOONNNNNNNNNNNNNNMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬«««««««««ŞŞŞŞŞŞŞŞŞ©©©©©©©©©¨¨¨¨¨¨¨¨§§§§§§§¦¦¦¦¦¦¦ĄĄĄĄĄĄĄ¤¤¤¤¤¤ŁŁŁŁŁŁŁ˘˘˘˘˘˘ˇˇˇˇˇˇ     źźźźźźžžžžžžťťťťťśśśśś›››››šššš™™™™™—————––––••••”””””““““’’’’‘‘‘‘ŹŹŹŹŽŽŽŽŤŤŤŤŚŚŚŚ‹‹‹‹ŠŠŠŠ‰‰‰‡‡‡‡†††…………„„„„‚‚‚€€€~~~~}}}||||{{{{zzzzyyyxxxxwwwwvvvuuuuttttssssrrrrqqqqppppoooonnnnmmmmllllkkkkkjjjjiiiihhhhhgggggfffffeeeedddddcccccbbbbbaaaaaa``````_____^^^^^^]]]]]]\\\\\\\[[[[[[ZZZZZZZYYYYYYYXXXXXXXWWWWWWWWVVVVVVVVVUUUUUUUUUTTTTTTTTTSSSSSSSSSSRRRRRRRRRRQQQQQQQQQQQPPPPPPPPPPPPOOOOOOOOOOOOONNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬«««««««««ŞŞŞŞŞŞŞŞŞ©©©©©©©©¨¨¨¨¨¨¨¨§§§§§§§§¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄ¤¤¤¤¤¤ŁŁŁŁŁŁ˘˘˘˘˘˘ˇˇˇˇˇˇ      źźźźźźžžžžžťťťťťśśśśś›››››ššššš™™™™™————–––––••••””””““““’’’’’‘‘‘‘ŹŹŹŹŽŽŽŽŤŤŤŤŚŚŚŚ‹‹‹‹ŠŠŠ‰‰‰‰‡‡‡‡†††…………„„„„‚‚‚‚€€€~~~~}}}}|||{{{{zzzzyyyxxxxwwwwvvvvuuuttttssssrrrrqqqqppppoooonnnnmmmmmllllkkkkjjjjiiiiihhhhgggggfffffeeeeedddddcccccbbbbbaaaaa``````______^^^^^^]]]]]]\\\\\\[[[[[[ZZZZZZZZYYYYYYYXXXXXXXXWWWWWWWWVVVVVVVVUUUUUUUUTTTTTTTTTTSSSSSSSSSSRRRRRRRRRRQQQQQQQQQQQQPPPPPPPPPPPPPOOOOOOOOOOOONNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬«««««««««ŞŞŞŞŞŞŞŞŞ©©©©©©©©¨¨¨¨¨¨¨¨§§§§§§§¦¦¦¦¦¦¦¦ĄĄĄĄĄĄ¤¤¤¤¤¤¤ŁŁŁŁŁŁŁ˘˘˘˘˘˘ˇˇˇˇˇˇ     źźźźźźžžžžžťťťťťťśśśśś›››››ššššš™™™™—————––––•••••””””““““’’’’’‘‘‘‘ŹŹŹŹŽŽŽŽŤŤŤŤŚŚŚ‹‹‹‹ŠŠŠŠ‰‰‰‰‡‡‡‡†††…………„„„„‚‚‚‚€€€~~~~}}}}|||{{{{zzzzyyyxxxxwwwwvvvvuuuuttttsssrrrrqqqqppppoooonnnnmmmmmllllkkkkjjjjjiiiihhhhhgggggffffeeeeedddddcccccbbbbbbaaaaa``````_____^^^^^^]]]]]]\\\\\\\[[[[[[[ZZZZZZYYYYYYYXXXXXXXXWWWWWWWWVVVVVVVVUUUUUUUUUTTTTTTTTTSSSSSSSSSSRRRRRRRRRRRQQQQQQQQQQQPPPPPPPPPPPPPOOOOOOOOOOOOOONNNNNNNNNNNNNNMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬«««««««««ŞŞŞŞŞŞŞŞŞ©©©©©©©©©¨¨¨¨¨¨¨¨§§§§§§§¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄ¤¤¤¤¤¤¤ŁŁŁŁŁŁ˘˘˘˘˘˘ˇˇˇˇˇˇ      źźźźźźžžžžžťťťťťśśśśś›››››ššššš™™™™™—————––––••••”””””““““’’’’‘‘‘‘ŹŹŹŹŹŽŽŽŽŤŤŤŚŚŚŚ‹‹‹‹ŠŠŠŠ‰‰‰‰‡‡‡††††…………„„„„‚‚‚‚€€€~~~~}}}}|||{{{{zzzzyyyyxxxwwwwvvvvuuuuttttssssrrrqqqqpppppoooonnnnmmmmllllkkkkkjjjjiiiihhhhhgggggfffffeeeeedddddcccccbbbbbaaaaa``````______^^^^^^]]]]]]\\\\\\[[[[[[[ZZZZZZZYYYYYYYYXXXXXXXWWWWWWWWVVVVVVVVVUUUUUUUUUTTTTTTTTTSSSSSSSSSSRRRRRRRRRRRQQQQQQQQQQQPPPPPPPPPPPPOOOOOOOOOOOOONNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬«««««««««ŞŞŞŞŞŞŞŞŞ©©©©©©©©©¨¨¨¨¨¨¨§§§§§§§§¦¦¦¦¦¦¦ĄĄĄĄĄĄĄ¤¤¤¤¤¤ŁŁŁŁŁŁŁ˘˘˘˘˘˘˘ˇˇˇˇˇˇ     źźźźźźžžžžžťťťťťśśśśśś›››››ššššš™™™™™—————–––––••••””””“““““’’’’‘‘‘‘ŹŹŹŹŽŽŽŽŤŤŤŤŚŚŚŚ‹‹‹‹ŠŠŠŠ‰‰‰‰‡‡‡††††…………„„„„‚‚‚‚€€€~~~~}}}}|||{{{{zzzzyyyyxxxwwwwvvvvuuuuttttssssrrrrqqqqppppoooonnnnmmmmlllllkkkkjjjjiiiiihhhhhggggfffffeeeeedddddcccccbbbbbbaaaaa``````_____^^^^^^]]]]]]]\\\\\\\[[[[[[ZZZZZZZYYYYYYYXXXXXXXXWWWWWWWVVVVVVVVVUUUUUUUUUTTTTTTTTTSSSSSSSSSSRRRRRRRRRRRQQQQQQQQQQQQPPPPPPPPPPPPPOOOOOOOOOOOOONNNNNNNNNNNNNMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKJJJJJJJJµµµµµµµµ´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬«««««««««ŞŞŞŞŞŞŞŞŞ©©©©©©©©©¨¨¨¨¨¨¨¨§§§§§§§§¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄ¤¤¤¤¤¤ŁŁŁŁŁŁŁ˘˘˘˘˘˘ˇˇˇˇˇˇ     źźźźźźžžžžžžťťťťťśśśśśś›››››šššš™™™™™————–––––••••”””””““““’’’’‘‘‘‘‘ŹŹŹŹŽŽŽŽŤŤŤŤŚŚŚŚ‹‹‹‹ŠŠŠŠ‰‰‰‰‡‡‡‡††††…………„„„„‚‚‚‚€€€~~~~}}}}|||{{{{zzzzyyyyxxxxwwwvvvvuuuuttttssssrrrrqqqqppppoooonnnnnmmmmllllkkkkkjjjjiiiiihhhhgggggfffffeeeedddddcccccbbbbbbaaaaaa``````_____^^^^^^]]]]]]\\\\\\\[[[[[[ZZZZZZZZYYYYYYYXXXXXXXXWWWWWWWWVVVVVVVVVUUUUUUUUUTTTTTTTTTSSSSSSSSSSRRRRRRRRRRRQQQQQQQQQQQPPPPPPPPPPPPOOOOOOOOOOOOOONNNNNNNNNNNNNNNMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKJJJJJJJJµµµµµµ´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬«««««««««ŞŞŞŞŞŞŞŞŞ©©©©©©©©©¨¨¨¨¨¨¨¨§§§§§§§¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄ¤¤¤¤¤¤ŁŁŁŁŁŁŁ˘˘˘˘˘˘ˇˇˇˇˇˇ      źźźźźźžžžžžťťťťťťśśśśś›››››ššššš™™™™™————–––––••••”””””““““’’’’‘‘‘‘‘ŹŹŹŹŽŽŽŽŤŤŤŤŚŚŚŚ‹‹‹‹ŠŠŠŠ‰‰‰‡‡‡‡††††…………„„„‚‚‚‚€€€~~~~}}}}||||{{{zzzzyyyyxxxxwwwwvvvuuuuttttssssrrrrqqqqppppoooonnnnnmmmmllllkkkkkjjjjiiiiihhhhgggggfffffeeeeedddddcccccbbbbbbaaaaa``````______^^^^^^]]]]]]\\\\\\\[[[[[[ZZZZZZZYYYYYYYYXXXXXXXWWWWWWWWVVVVVVVVVUUUUUUUUUTTTTTTTTTSSSSSSSSSSRRRRRRRRRRRQQQQQQQQQQQQPPPPPPPPPPPPPOOOOOOOOOOOONNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKJJJJJµµµµ´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬««««««««««ŞŞŞŞŞŞŞŞŞ©©©©©©©©¨¨¨¨¨¨¨¨¨§§§§§§§¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄ¤¤¤¤¤¤¤ŁŁŁŁŁŁŁ˘˘˘˘˘˘ˇˇˇˇˇˇ     źźźźźźžžžžžžťťťťťśśśśś››››››ššššš™™™™™—————––––•••••””””“““““’’’’‘‘‘‘ŹŹŹŹŹŽŽŽŤŤŤŤŤŚŚŚŚ‹‹‹‹ŠŠŠŠ‰‰‰‡‡‡‡††††…………„„„‚‚‚‚€€€~~~~}}}}||||{{{zzzzyyyyxxxxwwwwvvvuuuuttttssssrrrrrqqqpppppoooonnnnmmmmlllllkkkkjjjjjiiiihhhhhggggfffffeeeeeddddddcccccbbbbbaaaaaa``````_____^^^^^^]]]]]]\\\\\\\[[[[[[[ZZZZZZZYYYYYYYYXXXXXXXWWWWWWWWWVVVVVVVVUUUUUUUUUTTTTTTTTTTSSSSSSSSSSRRRRRRRRRRQQQQQQQQQQQPPPPPPPPPPPPPPOOOOOOOOOOOOOONNNNNNNNNNNNNMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKJJJµµµµ´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬«««««««««ŞŞŞŞŞŞŞŞŞ©©©©©©©©©¨¨¨¨¨¨¨¨§§§§§§§§¦¦¦¦¦¦¦ĄĄĄĄĄĄĄ¤¤¤¤¤¤¤ŁŁŁŁŁŁ˘˘˘˘˘˘˘ˇˇˇˇˇˇ      źźźźźźžžžžžžťťťťťśśśśś›››››ššššš™™™™™————–––––••••”””””““““’’’’’‘‘‘‘ŹŹŹŹŽŽŽŽŤŤŤŤŚŚŚŚ‹‹‹‹ŠŠŠŠ‰‰‰‰‡‡‡‡††††…………„„„‚‚‚‚€€€~~~~}}}}||||{{{zzzzyyyyxxxxwwwwvvvvuuuuttttssssrrrrqqqqppppoooonnnnmmmmmllllkkkkkjjjjiiiiihhhhgggggfffffeeeeedddddcccccbbbbbaaaaaa``````______^^^^^^]]]]]]]\\\\\\[[[[[[[ZZZZZZZYYYYYYYXXXXXXXXWWWWWWWWVVVVVVVVVUUUUUUUUUTTTTTTTTTSSSSSSSSSSSRRRRRRRRRRRQQQQQQQQQQQPPPPPPPPPPPPOOOOOOOOOOOOOONNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKJJJµµ´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬«««««««««ŞŞŞŞŞŞŞŞŞ©©©©©©©©©¨¨¨¨¨¨¨¨§§§§§§§§¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤ŁŁŁŁŁŁ˘˘˘˘˘˘˘ˇˇˇˇˇˇ     źźźźźźžžžžžžťťťťťśśśśś›››››ššššš™™™™™—————–––––••••”””””““““’’’’‘‘‘‘‘ŹŹŹŹŽŽŽŽŤŤŤŤŚŚŚŚ‹‹‹‹ŠŠŠŠ‰‰‰‰‡‡‡‡††††………„„„„‚‚‚‚€€€~~~~}}}}||||{{{zzzzyyyyxxxxwwwwvvvvuuuuttttssssrrrrqqqqppppoooonnnnnmmmmllllkkkkkjjjjiiiiihhhhhgggggfffffeeeeedddddcccccbbbbbaaaaaa``````_____^^^^^^]]]]]]]\\\\\\[[[[[[[ZZZZZZZZYYYYYYYXXXXXXXXWWWWWWWWVVVVVVVVVUUUUUUUUUTTTTTTTTTSSSSSSSSSSRRRRRRRRRRRQQQQQQQQQQQQPPPPPPPPPPPPPPOOOOOOOOOOOONNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKJ´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬««««««««««ŞŞŞŞŞŞŞŞŞ©©©©©©©©©¨¨¨¨¨¨¨¨§§§§§§§§¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄ¤¤¤¤¤¤¤ŁŁŁŁŁŁ˘˘˘˘˘˘ˇˇˇˇˇˇˇ      źźźźźźžžžžžťťťťťťśśśśś›››››ššššš™™™™™—————––––•••••””””“““““’’’’‘‘‘‘ŹŹŹŹŹŽŽŽŽŤŤŤŤŚŚŚŚ‹‹‹‹ŠŠŠŠ‰‰‰‰‡‡‡‡††††………„„„„‚‚‚‚€€€~~~~}}}}||||{{{{zzzyyyyxxxxwwwwvvvvuuuuttttssssrrrrqqqqpppppoooonnnnmmmmlllllkkkkjjjjjiiiihhhhhgggggfffffeeeeedddddcccccbbbbbbaaaaa``````______^^^^^^^]]]]]]\\\\\\[[[[[[[ZZZZZZZYYYYYYYYXXXXXXXXWWWWWWWWVVVVVVVVUUUUUUUUUUTTTTTTTTTTSSSSSSSSSSRRRRRRRRRRRQQQQQQQQQQQPPPPPPPPPPPPPOOOOOOOOOOOOOONNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKK´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬««««««««««ŞŞŞŞŞŞŞŞŞ©©©©©©©©¨¨¨¨¨¨¨¨¨§§§§§§§¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄ¤¤¤¤¤¤¤ŁŁŁŁŁŁŁ˘˘˘˘˘˘ˇˇˇˇˇˇˇ     źźźźźźžžžžžžťťťťťśśśśś››››››ššššš™™™™™—————–––––••••”””””““““’’’’’‘‘‘‘ŹŹŹŹŽŽŽŽŽŤŤŤŤŚŚŚŚ‹‹‹‹ŠŠŠŠ‰‰‰‰‡‡‡‡†††…………„„„„‚‚‚‚€€€~~~~}}}}||||{{{{zzzzyyyxxxxwwwwvvvvuuuuttttssssrrrrqqqqqppppoooonnnnmmmmmllllkkkkjjjjjiiiiihhhhhggggfffffeeeeeddddddcccccbbbbbaaaaaa``````_____^^^^^^^]]]]]]\\\\\\\[[[[[[[ZZZZZZZYYYYYYYYXXXXXXXWWWWWWWWWVVVVVVVVUUUUUUUUUTTTTTTTTTTSSSSSSSSSSRRRRRRRRRRRRQQQQQQQQQQQQPPPPPPPPPPPPOOOOOOOOOOOOONNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKK´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬««««««««««ŞŞŞŞŞŞŞŞŞŞ©©©©©©©©¨¨¨¨¨¨¨¨¨§§§§§§§§¦¦¦¦¦¦¦ĄĄĄĄĄĄĄ¤¤¤¤¤¤¤ŁŁŁŁŁŁŁ˘˘˘˘˘˘ˇˇˇˇˇˇ      źźźźźźžžžžžžťťťťťśśśśśś›››››ššššš™™™™™—————––––•••••”””””““““’’’’‘‘‘‘‘ŹŹŹŹŽŽŽŽŤŤŤŤŚŚŚŚŚ‹‹‹‹ŠŠŠŠ‰‰‰‰‡‡‡††††…………„„„„‚‚‚‚€€€~~~~}}}}||||{{{{zzzzyyyyxxxwwwwvvvvuuuuttttssssrrrrrqqqqppppoooonnnnnmmmmllllkkkkkjjjjjiiiihhhhhgggggfffffeeeeedddddccccccbbbbbaaaaaa``````_____^^^^^^^]]]]]]\\\\\\\[[[[[[[ZZZZZZZYYYYYYYXXXXXXXXWWWWWWWWWVVVVVVVVUUUUUUUUUUTTTTTTTTTTSSSSSSSSSSRRRRRRRRRRQQQQQQQQQQQQPPPPPPPPPPPPPPOOOOOOOOOOOOONNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKK´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬««««««««««ŞŞŞŞŞŞŞŞŞ©©©©©©©©©¨¨¨¨¨¨¨¨§§§§§§§§¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤ŁŁŁŁŁŁ˘˘˘˘˘˘˘ˇˇˇˇˇˇ      źźźźźźžžžžžžťťťťťśśśśśś›››››ššššš™™™™™—————––––•••••”””””““““’’’’‘‘‘‘ŹŹŹŹŽŽŽŽŤŤŤŤŚŚŚŚ‹‹‹‹‹ŠŠŠ‰‰‰‰‡‡‡‡††††…………„„„„‚‚‚‚€€€~~~~}}}}||||{{{{zzzzyyyyxxxxwwwwvvvvuuutttttssssrrrrqqqqppppooooonnnnmmmmllllkkkkkjjjjjiiiihhhhhgggggfffffeeeeedddddccccccbbbbbaaaaaa``````______^^^^^^]]]]]]]\\\\\\[[[[[[[ZZZZZZZZYYYYYYYXXXXXXXXWWWWWWWWVVVVVVVVVUUUUUUUUUTTTTTTTTTTSSSSSSSSSSRRRRRRRRRRRRQQQQQQQQQQQQPPPPPPPPPPPPOOOOOOOOOOOOOOONNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKK´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬«««««««««ŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©¨¨¨¨¨¨¨¨§§§§§§§§¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄ¤¤¤¤¤¤¤ŁŁŁŁŁŁŁ˘˘˘˘˘˘ˇˇˇˇˇˇ      źźźźźźžžžžžžťťťťťťśśśśś›››››ššššš™™™™™—————–––––•••••””””“““““’’’’‘‘‘‘ŹŹŹŹŹŽŽŽŽŤŤŤŤŚŚŚŚ‹‹‹‹ŠŠŠŠ‰‰‰‰‡‡‡‡††††…………„„„„‚‚‚‚€€€~~~~}}}}||||{{{{zzzzyyyyxxxxwwwwvvvvuuuuttttssssrrrrqqqqpppppoooonnnnmmmmlllllkkkkjjjjjiiiiihhhhhgggggfffffeeeeedddddcccccbbbbbbaaaaaa``````______^^^^^^]]]]]]\\\\\\\[[[[[[[ZZZZZZZYYYYYYYYXXXXXXXXWWWWWWWWVVVVVVVVVUUUUUUUUUUTTTTTTTTTSSSSSSSSSSRRRRRRRRRRRQQQQQQQQQQQQQPPPPPPPPPPPPPOOOOOOOOOOOOONNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKK´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬««««««««««ŞŞŞŞŞŞŞŞŞŞ©©©©©©©©¨¨¨¨¨¨¨¨¨§§§§§§§¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄ¤¤¤¤¤¤¤ŁŁŁŁŁŁŁ˘˘˘˘˘˘˘ˇˇˇˇˇˇ      źźźźźźžžžžžžťťťťťśśśśśś›››››ššššš™™™™™—————––––•••••”””””““““’’’’’‘‘‘‘ŹŹŹŹŽŽŽŽŽŤŤŤŤŚŚŚŚ‹‹‹‹ŠŠŠŠ‰‰‰‰‡‡‡‡††††…………„„„„‚‚‚‚€€€~~~~}}}}||||{{{{zzzzyyyyxxxxwwwwvvvvuuuuttttssssrrrrqqqqqppppoooonnnnmmmmmllllkkkkkjjjjjiiiihhhhhgggggfffffeeeeedddddccccccbbbbbaaaaaa``````______^^^^^^]]]]]]]\\\\\\\[[[[[[[ZZZZZZZYYYYYYYYXXXXXXXWWWWWWWWWVVVVVVVVUUUUUUUUUUTTTTTTTTTTSSSSSSSSSSSRRRRRRRRRRRQQQQQQQQQQQPPPPPPPPPPPPPPOOOOOOOOOOOOOONNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLKKKKKKKKKKKKK´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬««««««««««ŞŞŞŞŞŞŞŞŞ©©©©©©©©©¨¨¨¨¨¨¨¨§§§§§§§§§¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤ŁŁŁŁŁŁŁ˘˘˘˘˘˘ˇˇˇˇˇˇ      źźźźźźžžžžžžťťťťťťśśśśś›››››šššššš™™™™™—————––––•••••””””“““““’’’’‘‘‘‘‘ŹŹŹŹŽŽŽŽŤŤŤŤŤŚŚŚŚ‹‹‹‹ŠŠŠŠ‰‰‰‰‡‡‡‡††††…………„„„„‚‚‚‚€€€~~~~}}}}||||{{{{zzzzyyyyxxxxwwwwvvvvuuuuttttssssrrrrrqqqqppppoooonnnnnmmmmlllllkkkkjjjjjiiiihhhhhgggggfffffeeeeeedddddcccccbbbbbbaaaaaa``````______^^^^^^]]]]]]\\\\\\\[[[[[[[ZZZZZZZZYYYYYYYXXXXXXXXXWWWWWWWWVVVVVVVVVUUUUUUUUUTTTTTTTTTTSSSSSSSSSSRRRRRRRRRRRRQQQQQQQQQQQQQPPPPPPPPPPPOOOOOOOOOOOOOOONNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLKKKKKKKKKKKK´´´´´´´´´´łłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬««««««««««ŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©¨¨¨¨¨¨¨¨§§§§§§§§¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤ŁŁŁŁŁŁŁ˘˘˘˘˘˘ˇˇˇˇˇˇ      źźźźźźžžžžžžťťťťťťśśśśś››››››ššššš™™™™™—————–––––•••••””””“““““’’’’‘‘‘‘ŹŹŹŹŽŽŽŽŤŤŤŤŚŚŚŚŚ‹‹‹‹ŠŠŠŠ‰‰‰‰‡‡‡‡††††…………„„„„‚‚‚‚€€€~~~~}}}}||||{{{{zzzzyyyyxxxxwwwwvvvvuuuuttttsssssrrrrqqqqppppooooonnnnmmmmlllllkkkkjjjjjiiiiihhhhhgggggfffffeeeeeddddddcccccbbbbbbaaaaaa``````______^^^^^^]]]]]]\\\\\\\[[[[[[[ZZZZZZZZYYYYYYYXXXXXXXXWWWWWWWWVVVVVVVVVUUUUUUUUUUTTTTTTTTTTSSSSSSSSSSSRRRRRRRRRRQQQQQQQQQQQQQPPPPPPPPPPPPPPOOOOOOOOOOOONNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLKKKKKKKKK´´´´´´´´´łłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬«««««««««ŞŞŞŞŞŞŞŞŞ©©©©©©©©©¨¨¨¨¨¨¨¨¨§§§§§§§§¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄ¤¤¤¤¤¤¤ŁŁŁŁŁŁŁ˘˘˘˘˘˘˘ˇˇˇˇˇˇ      źźźźźźžžžžžžťťťťťťśśśśś››››››ššššš™™™™™————–––––•••••”””””““““’’’’’‘‘‘‘ŹŹŹŹŹŽŽŽŽŤŤŤŤŚŚŚŚ‹‹‹‹‹ŠŠŠŠ‰‰‰‰‡‡‡‡††††…………„„„„‚‚‚‚€€€~~~~}}}}||||{{{{zzzzyyyyxxxxwwwwvvvvuuuutttttssssrrrrqqqqpppppoooonnnnmmmmmllllkkkkkjjjjjiiiiihhhhgggggfffffeeeeeddddddcccccbbbbbbaaaaaa``````______^^^^^^]]]]]]]\\\\\\\[[[[[[[ZZZZZZZYYYYYYYYXXXXXXXXWWWWWWWWWVVVVVVVVVUUUUUUUUUTTTTTTTTTSSSSSSSSSSSRRRRRRRRRRRRQQQQQQQQQQQQPPPPPPPPPPPPPOOOOOOOOOOOOOOONNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLKKKKKKKK´´´´´´´´´łłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬««««««««««ŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©¨¨¨¨¨¨¨¨¨§§§§§§§¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄ¤¤¤¤¤¤¤ŁŁŁŁŁŁŁ˘˘˘˘˘˘˘ˇˇˇˇˇˇ      źźźźźźžžžžžžťťťťťťśśśśśś›››››ššššš™™™™™—————–––––•••••””””“““““’’’’’‘‘‘‘ŹŹŹŹŹŽŽŽŽŤŤŤŤŚŚŚŚ‹‹‹‹ŠŠŠŠ‰‰‰‰‰‡‡‡‡††††…………„„„„‚‚‚‚€€€~~~~}}}}||||{{{{zzzzyyyyxxxxwwwwvvvvvuuuuttttssssrrrrqqqqpppppoooonnnnmmmmmlllllkkkkjjjjjiiiiihhhhhgggggfffffeeeeedddddccccccbbbbbbaaaaa```````______^^^^^^]]]]]]]\\\\\\\[[[[[[[ZZZZZZZYYYYYYYYXXXXXXXWWWWWWWWWVVVVVVVVVUUUUUUUUUUTTTTTTTTTTSSSSSSSSSSRRRRRRRRRRRQQQQQQQQQQQQQPPPPPPPPPPPPPOOOOOOOOOOOOOONNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLKKKKKKKK´´´´´´łłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬««««««««««ŞŞŞŞŞŞŞŞŞ©©©©©©©©¨¨¨¨¨¨¨¨¨§§§§§§§§¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤ŁŁŁŁŁŁŁ˘˘˘˘˘˘˘ˇˇˇˇˇˇ      źźźźźźžžžžžžťťťťťťśśśśś›››››šššššš™™™™™—————–––––•••••””””“““““’’’’‘‘‘‘‘ŹŹŹŹŽŽŽŽŽŤŤŤŤŚŚŚŚ‹‹‹‹ŠŠŠŠ‰‰‰‰‡‡‡‡‡††††…………„„„„‚‚‚‚€€€~~~~}}}}||||{{{{zzzzyyyyxxxxxwwwwvvvvuuuuttttssssrrrrqqqqqppppoooonnnnnmmmmlllllkkkkjjjjjiiiiihhhhhgggggfffffeeeeeedddddcccccbbbbbbaaaaaa``````______^^^^^^]]]]]]]\\\\\\\[[[[[[[ZZZZZZZZYYYYYYYYXXXXXXXXWWWWWWWWWVVVVVVVVUUUUUUUUUTTTTTTTTTTSSSSSSSSSSSSRRRRRRRRRRRQQQQQQQQQQQPPPPPPPPPPPPPPPOOOOOOOOOOOOONNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLKKKKK´´´´´łłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬««««««««««ŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©¨¨¨¨¨¨¨¨¨§§§§§§§§¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤ŁŁŁŁŁŁŁ˘˘˘˘˘˘˘ˇˇˇˇˇˇ      źźźźźźźžžžžžťťťťťťśśśśśś›››››ššššš™™™™™—————–––––•••••”””””““““’’’’’‘‘‘‘ŹŹŹŹŽŽŽŽŤŤŤŤŤŚŚŚŚ‹‹‹‹ŠŠŠŠ‰‰‰‰‡‡‡‡††††……………„„„„‚‚‚‚€€€€~~~~}}}}||||{{{{zzzzzyyyyxxxxwwwwvvvvuuuuttttssssrrrrrqqqqppppooooonnnnmmmmmllllkkkkkjjjjjiiiiihhhhhgggggfffffeeeeedddddccccccbbbbbbaaaaa```````______^^^^^^]]]]]]]\\\\\\\[[[[[[[ZZZZZZZZYYYYYYYXXXXXXXXWWWWWWWWWVVVVVVVVVUUUUUUUUUUTTTTTTTTTTSSSSSSSSSSRRRRRRRRRRRRQQQQQQQQQQQQQPPPPPPPPPPPPOOOOOOOOOOOOOOONNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLKKKK´´´´´łłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬«««««««««««ŞŞŞŞŞŞŞŞŞ©©©©©©©©©¨¨¨¨¨¨¨¨§§§§§§§§§¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤ŁŁŁŁŁŁŁ˘˘˘˘˘˘ˇˇˇˇˇˇˇ      źźźźźźžžžžžžťťťťťśśśśśś››››››ššššš™™™™™—————–––––•••••””””“““““’’’’’‘‘‘‘ŹŹŹŹŹŽŽŽŽŤŤŤŤŚŚŚŚŚ‹‹‹‹ŠŠŠŠ‰‰‰‰‡‡‡‡††††…………„„„„‚‚‚‚€€€€~~~~}}}}||||{{{{zzzzyyyyxxxxwwwwvvvvuuuuttttsssssrrrrqqqqpppppoooonnnnmmmmmlllllkkkkjjjjjiiiiihhhhhgggggfffffeeeeeddddddccccccbbbbbaaaaaa``````______^^^^^^^]]]]]]\\\\\\\[[[[[[[ZZZZZZZZYYYYYYYYXXXXXXXXXWWWWWWWWVVVVVVVVVUUUUUUUUUTTTTTTTTTTTSSSSSSSSSSSRRRRRRRRRRRQQQQQQQQQQQQPPPPPPPPPPPPPPOOOOOOOOOOOOONNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLKKKK´´łłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬«««««««««ŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©¨¨¨¨¨¨¨¨¨§§§§§§§§¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁ˘˘˘˘˘˘˘ˇˇˇˇˇˇ      źźźźźźźžžžžžžťťťťťśśśśśś›››››ššššš™™™™™—————–––––••••”””””“““““’’’’‘‘‘‘‘ŹŹŹŹŹŽŽŽŽŤŤŤŤŚŚŚŚŚ‹‹‹‹ŠŠŠŠ‰‰‰‰‡‡‡‡††††…………„„„„‚‚‚‚€€€€~~~~}}}}||||{{{{zzzzyyyyxxxxwwwwvvvvuuuuttttsssssrrrrqqqqpppppoooonnnnnmmmmlllllkkkkkjjjjiiiiihhhhhgggggffffffeeeeedddddccccccbbbbbaaaaaa```````______^^^^^^]]]]]]]\\\\\\\[[[[[[[[ZZZZZZZYYYYYYYYXXXXXXXXWWWWWWWWVVVVVVVVVVUUUUUUUUUUTTTTTTTTTSSSSSSSSSSSRRRRRRRRRRRRQQQQQQQQQQQQPPPPPPPPPPPPPPOOOOOOOOOOOOOONNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLK´łłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬«««««««««««ŞŞŞŞŞŞŞŞŞŞ©©©©©©©©¨¨¨¨¨¨¨¨¨§§§§§§§§¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤ŁŁŁŁŁŁ˘˘˘˘˘˘˘ˇˇˇˇˇˇ      źźźźźźźžžžžžžťťťťťťśśśśś››››››ššššš™™™™™—————–––––•••••”””””““““’’’’’‘‘‘‘‘ŹŹŹŹŽŽŽŽŽŤŤŤŤŚŚŚŚ‹‹‹‹‹ŠŠŠŠ‰‰‰‰‡‡‡‡††††…………„„„„‚‚‚‚€€€€~~~~}}}}||||{{{{zzzzyyyyxxxxwwwwvvvvuuuutttttssssrrrrqqqqqppppoooonnnnnmmmmmllllkkkkkjjjjjiiiiihhhhhgggggfffffeeeeeddddddcccccbbbbbbaaaaaa```````______^^^^^^]]]]]]]\\\\\\[[[[[[[[ZZZZZZZZYYYYYYYYXXXXXXXXWWWWWWWWWVVVVVVVVUUUUUUUUUUTTTTTTTTTTTSSSSSSSSSSSRRRRRRRRRRQQQQQQQQQQQQQQPPPPPPPPPPPPOOOOOOOOOOOOOOOONNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLL´łłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬««««««««««ŞŞŞŞŞŞŞŞŞ©©©©©©©©©©¨¨¨¨¨¨¨¨¨§§§§§§§§¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤ŁŁŁŁŁŁŁ˘˘˘˘˘˘˘ˇˇˇˇˇˇˇ      źźźźźźźžžžžžťťťťťťśśśśś››››››šššššš™™™™™—————–––––•••••””””“““““’’’’’‘‘‘‘ŹŹŹŹŽŽŽŽŽŤŤŤŤŚŚŚŚ‹‹‹‹ŠŠŠŠŠ‰‰‰‰‡‡‡‡††††…………„„„„‚‚‚‚€€€€~~~~}}}}||||{{{{zzzzyyyyxxxxwwwwvvvvuuuuuttttssssrrrrqqqqqppppooooonnnnmmmmmlllllkkkkjjjjjiiiiihhhhhgggggfffffeeeeeeddddddcccccbbbbbbaaaaa```````______^^^^^^^]]]]]]\\\\\\\\[[[[[[[ZZZZZZZZYYYYYYYXXXXXXXXWWWWWWWWWVVVVVVVVVVUUUUUUUUUTTTTTTTTTTSSSSSSSSSSSRRRRRRRRRRRRRQQQQQQQQQQQPPPPPPPPPPPPPPPOOOOOOOOOOOOONNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬«««««««««««ŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©¨¨¨¨¨¨¨¨§§§§§§§§§¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤ŁŁŁŁŁŁŁ˘˘˘˘˘˘ˇˇˇˇˇˇˇ      źźźźźźźžžžžžžťťťťťťśśśśś››››››ššššš™™™™™——————–––––••••”””””“““““’’’’’‘‘‘‘ŹŹŹŹŹŽŽŽŽŤŤŤŤŤŚŚŚŚ‹‹‹‹ŠŠŠŠ‰‰‰‰‰‡‡‡‡††††…………„„„„‚‚‚‚€€€€~~~~}}}}||||{{{{zzzzyyyyxxxxwwwwvvvvvuuuuttttssssrrrrrqqqqpppppoooonnnnmmmmmlllllkkkkkjjjjiiiiihhhhhhgggggfffffeeeeeddddddcccccbbbbbbaaaaaa```````______^^^^^^^]]]]]]\\\\\\\[[[[[[[ZZZZZZZZYYYYYYYYXXXXXXXXXWWWWWWWWVVVVVVVVVUUUUUUUUUUTTTTTTTTTTTSSSSSSSSSSRRRRRRRRRRRRQQQQQQQQQQQQQPPPPPPPPPPPPPOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬««««««««««ŞŞŞŞŞŞŞŞŞ©©©©©©©©©©¨¨¨¨¨¨¨¨¨§§§§§§§§¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁ˘˘˘˘˘˘˘ˇˇˇˇˇˇˇ      źźźźźźžžžžžžťťťťťťśśśśśś›››››šššššš™™™™™—————–––––•••••”””””““““’’’’’‘‘‘‘‘ŹŹŹŹŹŽŽŽŽŤŤŤŤŚŚŚŚŚ‹‹‹‹ŠŠŠŠ‰‰‰‰‡‡‡‡††††…………„„„„‚‚‚‚€€€€~~~~}}}}||||{{{{zzzzyyyyxxxxwwwwvvvvvuuuuttttsssssrrrrqqqqpppppoooonnnnnmmmmmllllkkkkkjjjjjiiiiihhhhhgggggfffffeeeeeedddddccccccbbbbbbaaaaaa``````______^^^^^^^]]]]]]]\\\\\\\[[[[[[[[ZZZZZZZYYYYYYYYXXXXXXXXWWWWWWWWWVVVVVVVVVVUUUUUUUUUTTTTTTTTTTSSSSSSSSSSSSRRRRRRRRRRRQQQQQQQQQQQQQPPPPPPPPPPPPPOOOOOOOOOOOOOOONNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬«««««««««««ŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©¨¨¨¨¨¨¨¨¨§§§§§§§§¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤ŁŁŁŁŁŁ˘˘˘˘˘˘˘ˇˇˇˇˇˇˇ      źźźźźźźžžžžžžťťťťťťśśśśś››››››ššššš™™™™™™—————–––––•••••”””””““““’’’’’‘‘‘‘ŹŹŹŹŽŽŽŽŽŤŤŤŤŚŚŚŚŚ‹‹‹‹ŠŠŠŠ‰‰‰‰‡‡‡‡‡††††…………„„„„‚‚‚‚€€€€~~~~}}}}||||{{{{zzzzyyyyxxxxxwwwwvvvvuuuuttttsssssrrrrqqqqqppppoooonnnnnmmmmmllllkkkkkjjjjjiiiiihhhhhgggggffffffeeeeeddddddcccccbbbbbbaaaaaa```````______^^^^^^^]]]]]]]\\\\\\[[[[[[[[ZZZZZZZYYYYYYYYYXXXXXXXXWWWWWWWWWVVVVVVVVVUUUUUUUUUUTTTTTTTTTTTSSSSSSSSSSRRRRRRRRRRRRQQQQQQQQQQQQPPPPPPPPPPPPPPPOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬«««««««««««ŞŞŞŞŞŞŞŞŞ©©©©©©©©©¨¨¨¨¨¨¨¨¨§§§§§§§§§¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁ˘˘˘˘˘˘˘ˇˇˇˇˇˇˇ      źźźźźźžžžžžžťťťťťťśśśśśś›››››šššššš™™™™™—————–––––•••••”””””“““““’’’’‘‘‘‘‘ŹŹŹŹŽŽŽŽŽŤŤŤŤŚŚŚŚ‹‹‹‹‹ŠŠŠŠ‰‰‰‰‡‡‡‡‡††††…………„„„„‚‚‚‚€€€€~~~~}}}}||||{{{{zzzzyyyyxxxxxwwwwvvvvuuuutttttssssrrrrqqqqqppppooooonnnnnmmmmlllllkkkkkjjjjjiiiihhhhhhgggggfffffeeeeeedddddccccccbbbbbbaaaaaa``````______^^^^^^^]]]]]]]\\\\\\\[[[[[[[[ZZZZZZZZYYYYYYYXXXXXXXXXWWWWWWWWWVVVVVVVVVUUUUUUUUUTTTTTTTTTTTSSSSSSSSSSSSRRRRRRRRRRQQQQQQQQQQQQQQPPPPPPPPPPPPPOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬««««««««««ŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©¨¨¨¨¨¨¨¨§§§§§§§§§¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤ŁŁŁŁŁŁŁ˘˘˘˘˘˘˘ˇˇˇˇˇˇˇ      źźźźźźźžžžžžžťťťťťśśśśśś››››››ššššš™™™™™™—————–––––•••••”””””“““““’’’’‘‘‘‘‘ŹŹŹŹŹŽŽŽŽŤŤŤŤŤŚŚŚŚ‹‹‹‹‹ŠŠŠŠ‰‰‰‰‡‡‡‡†††††…………„„„„‚‚‚‚€€€€~~~~}}}}||||{{{{zzzzyyyyxxxxxwwwwvvvvuuuutttttssssrrrrrqqqqpppppoooonnnnnmmmmlllllkkkkkjjjjjiiiiihhhhhgggggffffffeeeeeddddddccccccbbbbbaaaaaa```````______^^^^^^^]]]]]]]\\\\\\\[[[[[[[ZZZZZZZZYYYYYYYYXXXXXXXXXWWWWWWWWVVVVVVVVVVUUUUUUUUUUTTTTTTTTTTSSSSSSSSSSSRRRRRRRRRRRRRQQQQQQQQQQQPPPPPPPPPPPPPPPOOOOOOOOOOOOOONNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬«««««««««««ŞŞŞŞŞŞŞŞŞ©©©©©©©©©¨¨¨¨¨¨¨¨¨¨§§§§§§§§¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘ˇˇˇˇˇˇ      źźźźźźźžžžžžžťťťťťťśśśśśś›››››šššššš™™™™™————––––––•••••”””””““““’’’’’‘‘‘‘‘ŹŹŹŹŹŽŽŽŽŤŤŤŤŤŚŚŚŚ‹‹‹‹ŠŠŠŠŠ‰‰‰‰‡‡‡‡†††††…………„„„„‚‚‚‚€€€€~~~~}}}}||||{{{{zzzzyyyyyxxxxwwwwvvvvuuuuuttttssssrrrrrqqqqpppppoooonnnnnmmmmmllllkkkkkjjjjjiiiiiihhhhggggggfffffeeeeeedddddccccccbbbbbbaaaaaa```````______^^^^^^]]]]]]]\\\\\\\\[[[[[[[ZZZZZZZZYYYYYYYYXXXXXXXXWWWWWWWWWWVVVVVVVVVUUUUUUUUUTTTTTTTTTTTSSSSSSSSSSSRRRRRRRRRRRRQQQQQQQQQQQQQPPPPPPPPPPPPPPOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMLLLLLLLLLLłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬««««««««««ŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©¨¨¨¨¨¨¨¨¨§§§§§§§§¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤ŁŁŁŁŁŁŁ˘˘˘˘˘˘˘ˇˇˇˇˇˇˇ      źźźźźźžžžžžžťťťťťťśśśśśś››››››ššššš™™™™™™—————–––––•••••”””””“““““’’’’’‘‘‘‘ŹŹŹŹŽŽŽŽŽŤŤŤŤŚŚŚŚŚ‹‹‹‹ŠŠŠŠ‰‰‰‰‰‡‡‡‡††††……………„„„„‚‚‚‚€€€€~~~~}}}}||||{{{{zzzzzyyyyxxxxwwwwvvvvvuuuuttttsssssrrrrqqqqpppppooooonnnnmmmmmlllllkkkkkjjjjjiiiiihhhhhgggggffffffeeeeeddddddccccccbbbbbbaaaaaa``````______^^^^^^^]]]]]]]\\\\\\\[[[[[[[ZZZZZZZZYYYYYYYYYXXXXXXXXWWWWWWWWWVVVVVVVVVVUUUUUUUUUUTTTTTTTTTTSSSSSSSSSSSRRRRRRRRRRRRQQQQQQQQQQQQQPPPPPPPPPPPPPPOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMLLLLLLLLLłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬««««««««««««ŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©¨¨¨¨¨¨¨¨¨§§§§§§§§§¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁ˘˘˘˘˘˘ˇˇˇˇˇˇˇ      źźźźźźźžžžžžžťťťťťťśśśśśś›››››šššššš™™™™™——————–––––•••••”””””“““““’’’’‘‘‘‘‘ŹŹŹŹŽŽŽŽŽŤŤŤŤŚŚŚŚŚ‹‹‹‹ŠŠŠŠ‰‰‰‰‰‡‡‡‡††††……………„„„„‚‚‚‚€€€€~~~~}}}}||||{{{{zzzzzyyyyxxxxwwwwvvvvvuuuuttttsssssrrrrqqqqqppppooooonnnnnmmmmlllllkkkkkjjjjjiiiiihhhhhhgggggfffffeeeeeedddddccccccbbbbbbaaaaaa```````______^^^^^^^]]]]]]\\\\\\\\[[[[[[[[ZZZZZZZZYYYYYYYXXXXXXXXXWWWWWWWWWVVVVVVVVVUUUUUUUUUUTTTTTTTTTTTTSSSSSSSSSSRRRRRRRRRRRRRQQQQQQQQQQQQPPPPPPPPPPPPPPPOOOOOOOOOOOOONNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMLLLLLLłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬««««««««««ŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©¨¨¨¨¨¨¨¨§§§§§§§§¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤ŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇ      źźźźźźžžžžžžťťťťťťťśśśśśś›››››ššššš™™™™™™—————––––––•••••””””“““““’’’’’‘‘‘‘‘ŹŹŹŹŹŽŽŽŽŽŤŤŤŤŚŚŚŚ‹‹‹‹‹ŠŠŠŠ‰‰‰‰‰‡‡‡‡††††……………„„„„‚‚‚‚€€€€~~~~}}}}||||{{{{zzzzzyyyyxxxxwwwwvvvvvuuuutttttssssrrrrqqqqqpppppoooonnnnnmmmmmlllllkkkkjjjjjiiiiiihhhhhgggggffffffeeeeedddddccccccbbbbbbbaaaaaa``````______^^^^^^^]]]]]]]]\\\\\\\[[[[[[[ZZZZZZZZYYYYYYYYXXXXXXXXXWWWWWWWWVVVVVVVVVVUUUUUUUUUUUTTTTTTTTTTSSSSSSSSSSSSRRRRRRRRRRRQQQQQQQQQQQQQQPPPPPPPPPPPPPOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMLLLLLLłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬«««««««««««ŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©¨¨¨¨¨¨¨¨¨§§§§§§§§§¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁ˘˘˘˘˘˘˘ˇˇˇˇˇˇˇ      źźźźźźźžžžžžžťťťťťťśśśśśś››››››ššššš™™™™™—————–––––•••••”””””“““““’’’’’‘‘‘‘ŹŹŹŹŹŽŽŽŽŤŤŤŤŤŚŚŚŚ‹‹‹‹‹ŠŠŠŠ‰‰‰‰‡‡‡‡††††…………„„„„„‚‚‚‚€€€€~~~~}}}}||||{{{{{zzzzyyyyxxxxwwwwwvvvvuuuutttttssssrrrrrqqqqpppppooooonnnnmmmmmlllllkkkkkjjjjjiiiiihhhhhggggggfffffeeeeeddddddccccccbbbbbbaaaaaa```````______^^^^^^^]]]]]]]\\\\\\\[[[[[[[[ZZZZZZZZYYYYYYYYXXXXXXXXXWWWWWWWWWVVVVVVVVVUUUUUUUUUUTTTTTTTTTTTSSSSSSSSSSSRRRRRRRRRRRRRQQQQQQQQQQQQPPPPPPPPPPPPPPPOOOOOOOOOOOOOOONNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMLLLLLłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬«««««««««««ŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©¨¨¨¨¨¨¨¨¨§§§§§§§§¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤ŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘ˇˇˇˇˇˇ       źźźźźźžžžžžžžťťťťťťśśśśś››››››šššššš™™™™™——————–––––•••••”””””“““““’’’’‘‘‘‘‘ŹŹŹŹŹŽŽŽŽŤŤŤŤŤŚŚŚŚ‹‹‹‹ŠŠŠŠŠ‰‰‰‰‡‡‡‡‡††††…………„„„„„‚‚‚‚€€€€~~~~}}}}||||{{{{{zzzzyyyyxxxxxwwwwvvvvuuuuuttttssssrrrrrqqqqpppppooooonnnnnmmmmlllllkkkkkjjjjjiiiiihhhhhhgggggfffffeeeeeeddddddcccccbbbbbbaaaaaaa``````_______^^^^^^]]]]]]]]\\\\\\\[[[[[[[ZZZZZZZZYYYYYYYYYXXXXXXXXWWWWWWWWWVVVVVVVVVVUUUUUUUUUUTTTTTTTTTTTSSSSSSSSSSSSRRRRRRRRRRRQQQQQQQQQQQQQQPPPPPPPPPPPPPPOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMLLłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬«««««««««««ŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©¨¨¨¨¨¨¨¨¨¨§§§§§§§§§¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁ˘˘˘˘˘˘ˇˇˇˇˇˇˇˇ      źźźźźźźžžžžžžťťťťťťśśśśśś››››››ššššš™™™™™™—————–––––••••••””””“““““’’’’’‘‘‘‘‘ŹŹŹŹŽŽŽŽŽŤŤŤŤŚŚŚŚŚ‹‹‹‹ŠŠŠŠŠ‰‰‰‰‡‡‡‡‡††††…………„„„„„‚‚‚‚€€€€~~~~}}}}||||{{{{{zzzzyyyyxxxxxwwwwvvvvuuuuuttttsssssrrrrqqqqqppppooooonnnnnmmmmmlllllkkkkjjjjjjiiiiihhhhhgggggffffffeeeeeddddddccccccbbbbbbaaaaaa```````______^^^^^^^^]]]]]]\\\\\\\[[[[[[[[ZZZZZZZZZYYYYYYYXXXXXXXXXWWWWWWWWWWVVVVVVVVUUUUUUUUUUUTTTTTTTTTTTSSSSSSSSSSSRRRRRRRRRRRRQQQQQQQQQQQQQQPPPPPPPPPPPPPOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMLLłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬«««««««««««ŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©¨¨¨¨¨¨¨¨¨§§§§§§§§¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘ˇˇˇˇˇˇˇ      źźźźźźźžžžžžžžťťťťťśśśśśś››››››šššššš™™™™™—————–––––•••••”””””“““““’’’’’‘‘‘‘‘ŹŹŹŹŹŽŽŽŽŽŤŤŤŤŚŚŚŚŚ‹‹‹‹ŠŠŠŠŠ‰‰‰‰‡‡‡‡‡††††…………„„„„‚‚‚‚€€€€~~~~}}}}|||||{{{{zzzzyyyyxxxxxwwwwvvvvuuuuuttttsssssrrrrqqqqqpppppoooonnnnnmmmmmlllllkkkkkjjjjjiiiiihhhhhggggggfffffeeeeeeddddddccccccbbbbbaaaaaaa```````______^^^^^^^]]]]]]]\\\\\\\\[[[[[[[ZZZZZZZZYYYYYYYYYXXXXXXXXWWWWWWWWWVVVVVVVVVVUUUUUUUUUUTTTTTTTTTTTSSSSSSSSSSSRRRRRRRRRRRRRQQQQQQQQQQQQPPPPPPPPPPPPPPPPOOOOOOOOOOOOOONNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMML˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««ŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©¨¨¨¨¨¨¨¨¨§§§§§§§§§¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁ˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇ      źźźźźźžžžžžžžťťťťťťśśśśśś›››››šššššš™™™™™™——————–––––•••••”””””“““““’’’’’‘‘‘‘ŹŹŹŹŹŽŽŽŽŤŤŤŤŤŚŚŚŚŚ‹‹‹‹ŠŠŠŠ‰‰‰‰‰‡‡‡‡‡††††…………„„„„‚‚‚‚€€€€~~~~}}}}|||||{{{{zzzzyyyyxxxxxwwwwvvvvvuuuuttttsssssrrrrrqqqqpppppooooonnnnmmmmmlllllkkkkkjjjjjiiiiihhhhhhgggggffffffeeeeeddddddccccccbbbbbbaaaaaaa``````______^^^^^^^^]]]]]]]\\\\\\\[[[[[[[[ZZZZZZZZYYYYYYYYXXXXXXXXXWWWWWWWWWVVVVVVVVVUUUUUUUUUUUTTTTTTTTTTSSSSSSSSSSSSSRRRRRRRRRRRQQQQQQQQQQQQQQPPPPPPPPPPPPPPOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMM˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬««««««««««««ŞŞŞŞŞŞŞŞŞ©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨§§§§§§§§¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘ˇˇˇˇˇˇˇ      źźźźźźźžžžžžžťťťťťťśśśśśś››››››šššššš™™™™™—————–––––•••••”””””““““““’’’’‘‘‘‘‘ŹŹŹŹŹŽŽŽŽŤŤŤŤŤŚŚŚŚ‹‹‹‹‹ŠŠŠŠ‰‰‰‰‰‡‡‡‡†††††…………„„„„‚‚‚‚€€€€~~~~}}}}|||||{{{{zzzzyyyyyxxxxwwwwvvvvvuuuutttttssssrrrrrqqqqpppppooooonnnnnmmmmllllllkkkkkjjjjjiiiiihhhhhggggggfffffeeeeeeddddddccccccbbbbbbaaaaaa```````______^^^^^^^]]]]]]]\\\\\\\\[[[[[[[ZZZZZZZZYYYYYYYYYXXXXXXXXWWWWWWWWWWVVVVVVVVVVUUUUUUUUUTTTTTTTTTTTTSSSSSSSSSSSRRRRRRRRRRRRRQQQQQQQQQQQQPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMM˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««ŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©¨¨¨¨¨¨¨¨¨§§§§§§§§§§¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁ˘˘˘˘˘˘˘ˇˇˇˇˇˇˇ       źźźźźźžžžžžžžťťťťťťśśśśśś››››››šššššš™™™™™——————–––––•••••”””””“““““’’’’’‘‘‘‘‘ŹŹŹŹŹŽŽŽŽŽŤŤŤŤŤŚŚŚŚ‹‹‹‹‹ŠŠŠŠ‰‰‰‰‰‡‡‡‡†††††…………„„„„‚‚‚‚€€€€~~~~}}}}|||||{{{{zzzzyyyyyxxxxwwwwvvvvvuuuutttttssssrrrrrqqqqqpppppoooonnnnnmmmmmlllllkkkkkjjjjjiiiiihhhhhhgggggfffffeeeeeeddddddccccccbbbbbbaaaaaaa``````_______^^^^^^^]]]]]]]\\\\\\\[[[[[[[[ZZZZZZZZZYYYYYYYXXXXXXXXXXWWWWWWWWWVVVVVVVVVUUUUUUUUUUUTTTTTTTTTTSSSSSSSSSSSSSRRRRRRRRRRRQQQQQQQQQQQQQQPPPPPPPPPPPPPPPOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMM˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬««««««««««««ŞŞŞŞŞŞŞŞŞ©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨§§§§§§§§¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘ˇˇˇˇˇˇ       źźźźźźźžžžžžžťťťťťťśśśśśśś›››››šššššš™™™™™™—————––––––•••••”””””“““““’’’’’‘‘‘‘‘ŹŹŹŹŹŽŽŽŽŽŤŤŤŤŚŚŚŚŚ‹‹‹‹ŠŠŠŠŠ‰‰‰‰‡‡‡‡†††††…………„„„„‚‚‚‚€€€€~~~~}}}}|||||{{{{zzzzyyyyyxxxxwwwwwvvvvuuuuuttttsssssrrrrqqqqqpppppoooonnnnnmmmmmlllllkkkkkjjjjjiiiiiihhhhhgggggffffffeeeeeedddddcccccccbbbbbbaaaaaa```````_______^^^^^^]]]]]]]\\\\\\\\[[[[[[[[ZZZZZZZZYYYYYYYYYXXXXXXXXWWWWWWWWWVVVVVVVVVVVUUUUUUUUUTTTTTTTTTTTTSSSSSSSSSSSRRRRRRRRRRRRRQQQQQQQQQQQQQPPPPPPPPPPPPPPOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNMMMMMMMMMMMMMM˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬««««««««««ŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©¨¨¨¨¨¨¨¨¨§§§§§§§§§¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁ˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇ      źźźźźźźžžžžžžžťťťťťťśśśśśś››››››ššššš™™™™™™—————–––––•••••””””””““““’’’’’‘‘‘‘‘ŹŹŹŹŹŽŽŽŽŽŤŤŤŤŚŚŚŚŚ‹‹‹‹ŠŠŠŠŠ‰‰‰‰‡‡‡‡††††……………„„„„‚‚‚‚‚€€€€~~~~}}}}|||||{{{{zzzzzyyyyxxxxwwwwwvvvvuuuuuttttsssssrrrrqqqqqpppppooooonnnnnmmmmmllllkkkkkkjjjjjiiiiihhhhhggggggffffffeeeeeddddddccccccbbbbbbaaaaaaa```````______^^^^^^^]]]]]]]]\\\\\\[[[[[[[[ZZZZZZZZZYYYYYYYYXXXXXXXXXWWWWWWWWWVVVVVVVVVVUUUUUUUUUUUTTTTTTTTTTSSSSSSSSSSSSRRRRRRRRRRRRRQQQQQQQQQQQQPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNMMMMMMMMMMMMM˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬«««««««««««ŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨§§§§§§§§¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘ˇˇˇˇˇˇˇ       źźźźźźźžžžžžžťťťťťťťśśśśś››››››šššššš™™™™™™——————–––––•••••”””””“““““’’’’’‘‘‘‘‘ŹŹŹŹŹŽŽŽŽŤŤŤŤŤŚŚŚŚ‹‹‹‹‹ŠŠŠŠŠ‰‰‰‰‡‡‡‡‡††††……………„„„„‚‚‚‚‚€€€€~~~~}}}}}||||{{{{zzzzzyyyyxxxxxwwwwvvvvuuuuutttttssssrrrrrqqqqpppppooooonnnnnmmmmmlllllkkkkkjjjjjiiiiihhhhhhgggggffffffeeeeeeddddddcccccbbbbbbbaaaaaa```````_______^^^^^^^]]]]]]]\\\\\\\\[[[[[[[ZZZZZZZZYYYYYYYYYXXXXXXXXWWWWWWWWWWVVVVVVVVVVUUUUUUUUUUTTTTTTTTTTTSSSSSSSSSSSSRRRRRRRRRRRRQQQQQQQQQQQQQQQPPPPPPPPPPPPPPOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNMMMMMMMMMMMM˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬««««««««««««ŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©¨¨¨¨¨¨¨¨¨§§§§§§§§§§¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇ      źźźźźźźžžžžžžžťťťťťťśśśśśś››››››šššššš™™™™™—————–––––••••••”””””“““““’’’’’‘‘‘‘‘ŹŹŹŹŹŽŽŽŽŽŤŤŤŤŤŚŚŚŚ‹‹‹‹‹ŠŠŠŠ‰‰‰‰‰‡‡‡‡‡††††……………„„„„‚‚‚‚‚€€€€~~~~}}}}}||||{{{{zzzzzyyyyxxxxxwwwwvvvvvuuuutttttssssrrrrrqqqqqpppppoooonnnnnmmmmmlllllkkkkkjjjjjjiiiiihhhhhggggggfffffeeeeeeddddddccccccbbbbbbaaaaaaa```````______^^^^^^^]]]]]]]]\\\\\\\[[[[[[[[ZZZZZZZZZYYYYYYYXXXXXXXXXXWWWWWWWWWVVVVVVVVVVUUUUUUUUUUTTTTTTTTTTTTSSSSSSSSSSSRRRRRRRRRRRRRQQQQQQQQQQQQQPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNMMMMMMMMMM˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©¨¨¨¨¨¨¨¨¨¨§§§§§§§§¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘ˇˇˇˇˇˇˇ       źźźźźźžžžžžžžťťťťťťśśśśśś›››››››ššššš™™™™™™——————–––––•••••””””””““““’’’’’’‘‘‘‘ŹŹŹŹŹŽŽŽŽŽŤŤŤŤŤŚŚŚŚ‹‹‹‹‹ŠŠŠŠ‰‰‰‰‰‡‡‡‡‡††††……………„„„„‚‚‚‚‚€€€€~~~~}}}}}||||{{{{zzzzzyyyyxxxxxwwwwvvvvvuuuutttttssssrrrrrqqqqqpppppooooonnnnmmmmmlllllkkkkkkjjjjjiiiiihhhhhhgggggffffffeeeeedddddddccccccbbbbbbaaaaaaa``````_______^^^^^^^]]]]]]]\\\\\\\\[[[[[[[[ZZZZZZZZYYYYYYYYYXXXXXXXXWWWWWWWWWWVVVVVVVVVUUUUUUUUUUUTTTTTTTTTTTSSSSSSSSSSSSSRRRRRRRRRRRQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNMMMMMMMMM˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬««««««««««««ŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©¨¨¨¨¨¨¨¨§§§§§§§§§§¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘ˇˇˇˇˇˇ       źźźźźźźžžžžžžťťťťťťťśśśśśś››››››šššššš™™™™™—————––––––•••••”””””“““““’’’’’‘‘‘‘‘ŹŹŹŹŹŽŽŽŽŤŤŤŤŤŚŚŚŚŚ‹‹‹‹ŠŠŠŠŠ‰‰‰‰‰‡‡‡‡‡††††……………„„„„‚‚‚‚‚€€€€~~~~}}}}}||||{{{{zzzzzyyyyxxxxxwwwwvvvvvuuuutttttsssssrrrrrqqqqpppppooooonnnnnmmmmmlllllkkkkkjjjjjiiiiiihhhhhggggggfffffeeeeeeddddddccccccbbbbbbbaaaaaa```````_______^^^^^^]]]]]]]]\\\\\\\[[[[[[[[ZZZZZZZZZYYYYYYYYXXXXXXXXXXWWWWWWWWVVVVVVVVVVVUUUUUUUUUUTTTTTTTTTTTTSSSSSSSSSSSRRRRRRRRRRRRRQQQQQQQQQQQQQQPPPPPPPPPPPPPPOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNMMMMMMMM˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©¨¨¨¨¨¨¨¨¨¨§§§§§§§§§¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘ˇˇˇˇˇˇˇ       źźźźźźźžžžžžžťťťťťťśśśśśśś›››››šššššš™™™™™™——————–––––••••••”””””“““““’’’’’‘‘‘‘‘ŹŹŹŹŹŽŽŽŽŤŤŤŤŤŚŚŚŚŚ‹‹‹‹ŠŠŠŠŠ‰‰‰‰‰‡‡‡‡‡††††…………„„„„„‚‚‚‚‚€€€€~~~~}}}}}||||{{{{{zzzzyyyyxxxxxwwwwvvvvvuuuuuttttsssssrrrrrqqqqpppppooooonnnnnmmmmmlllllkkkkkjjjjjjiiiiihhhhhhgggggffffffeeeeeedddddcccccccbbbbbbaaaaaa```````_______^^^^^^^]]]]]]]\\\\\\\\[[[[[[[[ZZZZZZZZYYYYYYYYYXXXXXXXXXWWWWWWWWWWVVVVVVVVVUUUUUUUUUUUTTTTTTTTTTTSSSSSSSSSSSSRRRRRRRRRRRRRQQQQQQQQQQQQQPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNMMMMMM˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬««««««««««««ŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©¨¨¨¨¨¨¨¨¨§§§§§§§§§§¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘ˇˇˇˇˇˇ       źźźźźźźžžžžžžťťťťťťťśśśśśś››››››šššššš™™™™™™——————–––––•••••””””””“““““’’’’’‘‘‘‘‘ŹŹŹŹŹŽŽŽŽŽŤŤŤŤŤŚŚŚŚ‹‹‹‹‹ŠŠŠŠŠ‰‰‰‰‡‡‡‡†††††…………„„„„„‚‚‚‚‚€€€€~~~~}}}}}||||{{{{{zzzzyyyyyxxxxwwwwwvvvvuuuuutttttssssrrrrrqqqqqpppppoooonnnnnmmmmmlllllkkkkkkjjjjjiiiiihhhhhhgggggffffffeeeeeeddddddccccccbbbbbbbaaaaaa```````_______^^^^^^]]]]]]]]\\\\\\\\[[[[[[[[ZZZZZZZZZYYYYYYYXXXXXXXXXXWWWWWWWWWVVVVVVVVVVVUUUUUUUUUTTTTTTTTTTTTTSSSSSSSSSSSRRRRRRRRRRRRQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNMMMMMM˛˛˛˛˛˛±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬«««««««««««ŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁ˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇ      źźźźźźźźžžžžžžťťťťťťśśśśśśś›››››ššššššš™™™™™—————––––––•••••”””””“““““’’’’’’‘‘‘‘ŹŹŹŹŹŽŽŽŽŽŤŤŤŤŤŚŚŚŚ‹‹‹‹‹ŠŠŠŠŠ‰‰‰‰‡‡‡‡†††††…………„„„„„‚‚‚‚‚€€€€~~~~}}}}}||||{{{{{zzzzyyyyyxxxxwwwwwvvvvuuuuutttttssssrrrrrqqqqqpppppooooonnnnmmmmmmlllllkkkkkjjjjjiiiiiihhhhhggggggfffffeeeeeeedddddcccccccbbbbbbaaaaaa````````______^^^^^^^]]]]]]]]\\\\\\\[[[[[[[[[ZZZZZZZZYYYYYYYYYXXXXXXXXWWWWWWWWWWWVVVVVVVVVUUUUUUUUUUUTTTTTTTTTTTSSSSSSSSSSSSRRRRRRRRRRRRRQQQQQQQQQQQQQPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNMMMMM˛˛˛±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬«««««««««««ŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©¨¨¨¨¨¨¨¨¨§§§§§§§§§§¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘ˇˇˇˇˇˇˇ       źźźźźźźžžžžžžžťťťťťťśśśśśś››››››šššššš™™™™™™——————–––––••••••”””””“““““’’’’’‘‘‘‘‘ŹŹŹŹŹŽŽŽŽŽŤŤŤŤŚŚŚŚŚ‹‹‹‹‹ŠŠŠŠ‰‰‰‰‰‡‡‡‡†††††…………„„„„„‚‚‚‚‚€€€€~~~~}}}}}||||{{{{{zzzzyyyyyxxxxwwwwwvvvvvuuuutttttsssssrrrrqqqqqpppppooooonnnnnmmmmmlllllkkkkkjjjjjjiiiiihhhhhhgggggffffffeeeeeeddddddccccccbbbbbbaaaaaaa```````_______^^^^^^^]]]]]]]\\\\\\\\[[[[[[[[ZZZZZZZZZYYYYYYYYXXXXXXXXXXWWWWWWWWWVVVVVVVVVVVUUUUUUUUUUTTTTTTTTTTTSSSSSSSSSSSSRRRRRRRRRRRRQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNMM˛˛˛±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬«««««««««««ŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨§§§§§§§§¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇ      źźźźźźźźžžžžžžťťťťťťśśśśśś›››››››ššššš™™™™™™——————–––––•••••”””””““““““’’’’’‘‘‘‘‘ŹŹŹŹŹŽŽŽŽŤŤŤŤŤŚŚŚŚŚ‹‹‹‹‹ŠŠŠŠ‰‰‰‰‰‡‡‡‡‡†††††…………„„„„„‚‚‚‚‚€€€€~~~~}}}}}||||{{{{{zzzzyyyyyxxxxxwwwwvvvvvuuuutttttsssssrrrrrqqqqpppppooooonnnnnmmmmmllllllkkkkkjjjjjiiiiihhhhhhggggggffffffeeeeedddddddccccccbbbbbbaaaaaa````````______^^^^^^^^]]]]]]]\\\\\\\\[[[[[[[[ZZZZZZZZYYYYYYYYYXXXXXXXXXWWWWWWWWWWVVVVVVVVVVUUUUUUUUUUUTTTTTTTTTTTSSSSSSSSSSSRRRRRRRRRRRRRRQQQQQQQQQQQQQQPPPPPPPPPPPPPPPOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNMM˛˛±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬«««««««««««ŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©¨¨¨¨¨¨¨¨§§§§§§§§§§¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇ       źźźźźźźžžžžžžžťťťťťťśśśśśś››››››šššššš™™™™™™—————––––––•••••”””””“““““’’’’’‘‘‘‘‘‘ŹŹŹŹŹŽŽŽŽŽŤŤŤŤŤŚŚŚŚŚ‹‹‹‹ŠŠŠŠŠ‰‰‰‰‰‡‡‡‡‡††††……………„„„„„‚‚‚‚‚€€€€~~~~}}}}}||||{{{{{zzzzzyyyyxxxxxwwwwvvvvvuuuuuttttsssssrrrrrqqqqqpppppoooonnnnnnmmmmmlllllkkkkkjjjjjiiiiiihhhhhggggggffffffeeeeeeddddddccccccbbbbbbaaaaaaa```````_______^^^^^^^]]]]]]]]\\\\\\\\[[[[[[[ZZZZZZZZZYYYYYYYYXXXXXXXXXXXWWWWWWWWVVVVVVVVVVVUUUUUUUUUUUTTTTTTTTTTTSSSSSSSSSSSSSRRRRRRRRRRRRQQQQQQQQQQQQQPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNM±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««ŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨§§§§§§§§§¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇ       źźźźźźźžžžžžžžťťťťťťśśśśśś›››››››šššššš™™™™™™——————–––––••••••”””””“““““’’’’’‘‘‘‘‘ŹŹŹŹŹŽŽŽŽŽŤŤŤŤŤŚŚŚŚŚ‹‹‹‹ŠŠŠŠŠ‰‰‰‰‰‡‡‡‡‡††††……………„„„„‚‚‚‚€€€€~~~~~}}}}|||||{{{{zzzzzyyyyxxxxxwwwwvvvvvuuuuuttttsssssrrrrrqqqqqpppppooooonnnnnmmmmmlllllkkkkkjjjjjjiiiiihhhhhhgggggffffffeeeeeedddddddccccccbbbbbbaaaaaaa```````_______^^^^^^^]]]]]]]]\\\\\\\[[[[[[[[[ZZZZZZZYYYYYYYYYYXXXXXXXXXWWWWWWWWWWVVVVVVVVVVUUUUUUUUUUTTTTTTTTTTTTSSSSSSSSSSSSRRRRRRRRRRRRRQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNmlt-7.22.0/demo/mlt_all000664 000000 000000 00000000022 14531534050 014643 0ustar00rootroot000000 000000 melt \ clip* \ $* mlt-7.22.0/demo/mlt_audio_stuff000664 000000 000000 00000000200 14531534050 016401 0ustar00rootroot000000 000000 melt \ clip*.dv \ -track music1.ogg \ -filter volume:0.5 normalise= track=0 \ -transition mix out=9999 a_track=0 b_track=1 \ $* mlt-7.22.0/demo/mlt_avantika_title000664 000000 000000 00000000026 14531534050 017076 0ustar00rootroot000000 000000 melt \ pango.mlt \ $* mlt-7.22.0/demo/mlt_bouncy000664 000000 000000 00000000314 14531534050 015376 0ustar00rootroot000000 000000 melt \ clip3.dv \ -filter \ watermark:clip1.dv \ composite.geometry="0=10%/10%:20%x20%; 33=30%/70%:25%x25%; 66=70%/30%:15%x15%; -1=70%/70%:20%x20%" \ composite.out=100 \ composite.sliced_composite=1 \ $* mlt-7.22.0/demo/mlt_clock_in_and_out000664 000000 000000 00000000417 14531534050 017375 0ustar00rootroot000000 000000 melt \ clip2.dv in=100 out=174 -blank 99 clip3.dv in=100 \ -track \ -blank 49 clip3.mpeg in=100 out=249 \ -transition luma:luma1.pgm softness=0.5 in=50 out=74 a_track=0 b_track=1 \ -transition luma:luma1.pgm softness=0.2 in=175 out=199 a_track=0 b_track=1 reverse=1 \ $* mlt-7.22.0/demo/mlt_composite_transition000664 000000 000000 00000000401 14531534050 020350 0ustar00rootroot000000 000000 melt \ clip1.dv out=74 \ -track \ -blank 49 clip2.mpeg \ -transition composite:"0=57%/10%:33%x33%; -1=0%/0%:100%x100%" progressive=1 distort=true in=50 out=74 a_track=0 b_track=1 sliced_composite=1 \ -transition mix:-1 in=50 out=74 a_track=0 b_track=1 \ $* mlt-7.22.0/demo/mlt_effect_in_middle000664 000000 000000 00000000111 14531534050 017332 0ustar00rootroot000000 000000 melt \ clip1.mpeg in=100 out=500 \ -filter greyscale in=100 out=199 \ $* mlt-7.22.0/demo/mlt_fade_black000664 000000 000000 00000000444 14531534050 016136 0ustar00rootroot000000 000000 melt \ colour:black out=199 \ -track \ clip3.mpeg in=100 out=299 \ -transition luma in=0 out=49 a_track=0 b_track=1 \ -transition luma in=150 out=199 a_track=0 b_track=1 reverse=1 \ -filter volume in=0 out=49 track=1 gain=0 end=1.0 \ -filter volume in=150 out=199 track=1 gain=1.0 end=0 \ $* mlt-7.22.0/demo/mlt_fade_in_and_out000664 000000 000000 00000000511 14531534050 017174 0ustar00rootroot000000 000000 melt \ clip1.dv out=74 -blank 99 clip3.dv in=25 \ -track \ -blank 49 clip2.mpeg out=149 \ -transition luma in=50 out=74 a_track=0 b_track=1 \ -transition luma in=175 out=199 a_track=0 b_track=1 reverse=1 \ -transition mix:-1 in=50 out=74 a_track=0 b_track=1 \ -transition mix:-1 in=175 out=199 a_track=0 b_track=1 reverse=1 \ $* mlt-7.22.0/demo/mlt_intro000664 000000 000000 00000000261 14531534050 015233 0ustar00rootroot000000 000000 melt \ music1.ogg in=100 out=224 \ -track \ watermark1.png out=124 \ clip3.mpeg \ -mix 25 \ -mixer luma resource=luma1.pgm softness=0.2 \ -transition mix:-1 in=100 out=124 \ $* mlt-7.22.0/demo/mlt_jcut000664 000000 000000 00000000275 14531534050 015052 0ustar00rootroot000000 000000 melt \ -blank 49 \ clip2.dv in=100 \ -track \ clip1.dv out=99 \ -transition \ mix start=0 end=1 in=49 out=50 a_track=1 b_track=0 \ -transition \ mix:1 in=51 out=99 a_track=1 b_track=0 \ $* mlt-7.22.0/demo/mlt_lcut000664 000000 000000 00000000365 14531534050 015054 0ustar00rootroot000000 000000 melt \ clip1.dv out=100 \ -track \ -blank 49 \ clip2.dv in=100 \ -transition \ luma in=50 out=55 a_track=0 b_track=1 \ -transition \ mix:1 in=50 out=98 a_track=1 b_track=0 \ -transition \ mix start=1 end=0 in=99 out=100 a_track=1 b_track=0 \ $* mlt-7.22.0/demo/mlt_levels000664 000000 000000 00000000106 14531534050 015370 0ustar00rootroot000000 000000 melt \ *.dv \ -filter gamma:1.5 \ -filter volume normalise=-20db \ $* mlt-7.22.0/demo/mlt_my_name_is000664 000000 000000 00000001023 14531534050 016215 0ustar00rootroot000000 000000 melt \ clip3.dv \ -track \ "+My name is Inigo Montoya.txt" out=99 -blank 49 "+Prepare to die!.txt" out=99 \ -track \ -blank 74 "+You killed my father.txt" out=74 \ -transition composite:"0=50%/20%:5%x4%; -1=10%/20%:80%x12%" distort=1 halign=centre valign=centre in=0 out=99 a_track=0 b_track=1 sliced_composite=1 \ -transition composite:"0=0%/70%:100%x10%; -1=100%/70%:100%x10%" in=75 out=149 a_track=0 b_track=2 sliced_composite=1 \ -transition composite:25%/25%:50%x50% in=150 out=249 a_track=0 b_track=1 sliced_composite=1 \ $* mlt-7.22.0/demo/mlt_news000664 000000 000000 00000001624 14531534050 015060 0ustar00rootroot000000 000000 melt \ colour:black out=199 \ -track \ clip1.dv in=0 out=0 -repeat 99 clip1.dv \ -track \ clip2.dv out=199 \ -track \ pango: text=" Breaking News MLT Rocks India" bgcolour=0xff000080 out=149 \ pango: text=" Breaking News MLT Rocks the World" bgcolour=0xff000080 out=349 \ -transition mix:0.5 always_active=1 a_track=0 b_track=2 \ -transition composite geometry=50%/15%:37.5%x40% a_track=0 b_track=1 in=0 out=174 sliced_composite=1 \ -transition composite geometry=10%/15%:37.5%x40% a_track=0 b_track=2 in=0 out=199 sliced_composite=1 \ -transition composite geometry="50%/15%:37.5%x40%; -1=0%/0%:100%x100%" a_track=0 b_track=1 in=175 out=199 distort=1 sliced_composite=1 \ -transition composite geometry=10%/65%:90%x20% a_track=0 b_track=3 in=0 out=199 sliced_composite=1 \ -transition composite geometry=10%/65%:90%x20% a_track=1 b_track=3 in=200 out=499 sliced_composite=1 \ $* mlt-7.22.0/demo/mlt_pango_keyframes000664 000000 000000 00000000204 14531534050 017247 0ustar00rootroot000000 000000 melt \ color:#03CF0 \ -filter watermark:pango_keyframes.mpl composite.halign=c composite.valign=m composite.sliced_composite=1 \ $* mlt-7.22.0/demo/mlt_push000664 000000 000000 00000000720 14531534050 015057 0ustar00rootroot000000 000000 melt \ -blank 49 colour:black out=25 -blank 999 \ -track \ clip3.dv in=200 out=275 \ -track \ -blank 49 \ clip2.dv in=200 \ -transition \ composite in=50 out=75 a_track=0 b_track=1 \ geometry="0=0/0:100%x100%:100%; -1=100%/0:100%x100%:100%" \ sliced_composite=1 \ -transition \ composite in=50 out=75 a_track=0 b_track=2 \ geometry="0=-100%/0:100%x100%:100%; -1=0/0:100%x100%:100%" \ sliced_composite=1 \ -transition \ mix:-1 in=50 out=75 a_track=1 b_track=2 \ $* mlt-7.22.0/demo/mlt_slideshow000664 000000 000000 00000000134 14531534050 016100 0ustar00rootroot000000 000000 melt \ photos/.all.jpg ttl=75 \ -filter luma:luma1.pgm luma.softness=0.1 luma.invert=0 \ $* mlt-7.22.0/demo/mlt_slideshow2000664 000000 000000 00000000467 14531534050 016173 0ustar00rootroot000000 000000 melt \ photos/.all.jpg ttl=75 \ -attach crop center=1 \ -attach affine transition.cycle=225 transition.geometry="0=0/0:100%x100%;74=-100/-100:120%x120%;75=-60/-60:110%x110%;149=0/0:110%x110%;150=0/-60:110%x110%;224=-60/0:110%x110%" \ -filter luma cycle=75 duration=25 \ -track music1.ogg \ -transition mix \ $* mlt-7.22.0/demo/mlt_slideshow_black000664 000000 000000 00000000411 14531534050 017232 0ustar00rootroot000000 000000 melt photos/.all.jpg ttl=100 \ -filter watermark:colour:black reverse=1 composite.geometry="0=15%/15%:10%/10%; 0.1625=0/0:100%x100%; -.1625=; -1=70%/70%:10%x10%" composite.mirror_off=1 composite.cycle=100 composite.fill=1 composite.valign=c composite.halign=c \ $* mlt-7.22.0/demo/mlt_squeeze000664 000000 000000 00000001177 14531534050 015570 0ustar00rootroot000000 000000 melt \ clip1.dv out=124 clip2.dv out=149 clip3.dv in=75 out=224 clip1.dv \ -track \ -blank 99 colour:black out=49 -blank 99 colour:black out=49 -blank 99 colour:black out=49 \ -group progressive=1 distort=1 \ -transition composite geometry="0%/0%:100%x100%;25=50%/0%:5%x100%;-1=0%/0%:100%x100%" a_track=1 b_track=0 in=100 out=149 sliced_composite=1 \ -transition composite geometry="0%/0%:100%x100%;25=0%/50%:100%x5%;-1=0%/0%:100%x100%" a_track=1 b_track=0 in=250 out=299 sliced_composite=1 \ -transition composite geometry="0%/0%:100%x100%;25=100%/0%:5%x100%;-1=0%/0%:100%x100%" a_track=1 b_track=0 in=400 out=449 sliced_composite=1 \ $* mlt-7.22.0/demo/mlt_squeeze_box000664 000000 000000 00000001146 14531534050 016434 0ustar00rootroot000000 000000 melt \ clip1.dv out=124 clip2.dv out=149 clip3.dv in=75 out=224 clip1.dv \ -track \ -blank 99 colour:black out=49 -blank 99 colour:black out=49 -blank 99 colour:black out=49 \ -group progressive=1 \ -transition composite:"0=0%/0%:100%x100%; 25=50%/0%:5%x100%; -1=0%/0%:100%x100%" a_track=1 b_track=0 in=100 out=149 sliced_composite=1 \ -transition composite:"0=0%/0%:100%x100%; 25=0%/50%:100%x5%; -1=0%/0%:100%x100%" a_track=1 b_track=0 in=250 out=299 sliced_composite=1 \ -transition composite:"0=0%/0%:100%x100%; 25=100%/0%:5%x100%; -1=0%/0%:100%x100%" a_track=1 b_track=0 in=400 out=449 sliced_composite=1 \ $* mlt-7.22.0/demo/mlt_ticker000664 000000 000000 00000000657 14531534050 015372 0ustar00rootroot000000 000000 melt \ clip1.dv out=299 \ -track \ colour:0 out=299 \ -track \ "+The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog..txt" \ out=299 \ -transition \ composite a_track=0 b_track=1 out=299 distort=1 \ geometry=0/70%:100%x11%:100% \ sliced_composite=1 \ -transition \ composite a_track=0 b_track=2 out=299 titles=1 \ geometry="0=100%/70%:999%x20%; -1=-299%/70%:999%x20%" \ sliced_composite=1 \ $* mlt-7.22.0/demo/mlt_title_over_gfx000664 000000 000000 00000000734 14531534050 017125 0ustar00rootroot000000 000000 melt \ watermark1.png out=9999 \ -track \ "+title over gfx.txt" fgcolour=0x000000ff \ -track \ clip1.dv \ -transition \ composite:30%/20%:40%x60% \ in=50 \ out=199 \ a_track=0 \ b_track=1 \ distort=1 \ sliced_composite=1 \ -transition \ composite:"0=0%/75%:100%x20%:0; 24=0%/75%:100%x20%:100%; -25=0%/75%:100%x20%:100%; -1=0%/75%:100%x20%:0" \ in=50 \ out=199 \ a_track=2 \ b_track=0 \ luma=luma1.pgm \ distort=1 \ sliced_composite=1 \ $* mlt-7.22.0/demo/mlt_titleshadow_watermark000664 000000 000000 00000001002 14531534050 020476 0ustar00rootroot000000 000000 melt \ "+hello~world.txt" align=1 out=1000 \ -track "+hello~world.txt" align=1 out=1000 fgcolour=0x000000ff \ -track watermark1.png out=1000 \ -track clip3.dv \ -filter greyscale track=2 \ -transition composite:"0=21%/11%:100%x100%:50; -1=61%/41%:100%x100%" out=99 a_track=3 b_track=1 sliced_composite=1 \ -transition composite:"0=20%/10%:100%x100%; -1=60%/40%:100%x100%" out=99 a_track=3 b_track=0 sliced_composite=1 \ -transition composite:85%/80%:10%x10%:30 out=1000 a_track=3 b_track=2 sliced_composite=1 \ $* mlt-7.22.0/demo/mlt_voiceover000664 000000 000000 00000001061 14531534050 016100 0ustar00rootroot000000 000000 melt \ "+voice over demo.txt" \ family="Sans" \ size="72" \ weight="700" \ fgcolour=0x00000000 \ bgcolour=0xff9933aa \ pad=10 \ -track music1.ogg \ -track clip1.dv out=149 clip2.mpeg \ -transition \ mix:0.0 \ end=0.6 \ in=75 \ out=99 \ a_track=2 \ b_track=1 \ -transition \ mix:0.6 \ in=100 \ out=299 \ a_track=2 \ b_track=1 \ -transition \ mix:0.6 \ end=0.0 \ in=300 \ out=324 \ a_track=2 \ b_track=1 \ -transition \ composite:0%/80%:100%x20% \ distort=1 \ in=100 \ out=299 \ a_track=2 \ b_track=0 \ sliced_composite=1 \ $* mlt-7.22.0/demo/mlt_watermark000664 000000 000000 00000000255 14531534050 016100 0ustar00rootroot000000 000000 melt \ clip2.dv out=1000 \ -track \ watermark1.png out=1000 \ -transition composite fill=1 in=0 out=1000 a_track=0 b_track=1 geometry=85%/5%:10%x10% sliced_composite=1 \ $* mlt-7.22.0/demo/new.mlt000664 000000 000000 00000002655 14531534050 014621 0ustar00rootroot000000 000000 clip2.mpeg clip3.mpeg greyscale luma luma 1 mlt-7.22.0/demo/pango.mlt000664 000000 000000 00000002475 14531534050 015134 0ustar00rootroot000000 000000 clip1.dv pango +.txt GJ-TTAvantika 36 1 0xffffddff 0x8c101080 8 composite 1 0 -70%/65%:100%x35%:0 0/65%:100%x35%:100 0/65%:100%x35%:100 0/65%:100%x35%:0 centre centre mlt-7.22.0/demo/pango_keyframes.mpl000664 000000 000000 00000000076 14531534050 017171 0ustar00rootroot000000 000000 0=Hello 40= 50=World~the end 90= mlt-7.22.0/demo/svg.mlt000664 000000 000000 00000005422 14531534050 014622 0ustar00rootroot000000 000000 ]> pixbuf mlt-7.22.0/demo/watermark1.png000664 000000 000000 00000002511 14531534050 016065 0ustar00rootroot000000 000000 ‰PNG  IHDRr ß”sBIT|dIDATxśĹ–klSe€źďś^Ö]ş˛K·Ř SŮÄýq QšEBĐ‚  Ô#Ę`D˘MH:&2A.M AÁ~((Śŕl#(Ţ ™®mŔşuëÚśµ”­]ő—ďŻsľËű|ďőűiDQ˝9ŔSŔlĐĘA8€ đö+ýŔQ·Ë9NźTĽ ¨€1ť"Ŕ4›Ü.g0Ő")l!h>`i2É(X97řp.°ř]Q˝U˙ ¨¨^YQ˝ť É6Ś‚Ć%EĚ}ĚJq^Ň%ŔٍŢçŇ ŔňT§WhdÚd S+-©–]Šę]¨¨Ţůé`^_¶®0¤‰ćRTŻ2¨¨Ţ"`{ĘăĘ‚ĺµyd=ÇNžÓsbje"eÚ3°[Q˝qßÇ,\ä$Űb2 _.b~M.ŤKě ‚o~ěŔ–-3iĽiT{…q ˘z­č©źÜ: ĎŞźëÁŞLŰąpe€ŽnÝ­ fŘČ4'MöDYűŠę}Ř3Úę‹Äćşb&O0Đüsöažžn …3ż9x:Ŕwç‚„ÂZ25Un—óĽě¨^¶<0Ö8îéC©°`·(-2R`“ÉĘĐ-“”ÚŤÔTgQű^.7Q:»#‰jţöyšNIŔ¤ÄѬ ‰u/Řůô­±|ü¦šę,ĐŰĺŐMWąpEď^v›žšÇ=}D˘şUÖ,‰ÚG¬ě~ĂÁ×JÉÉŚ»{€š#6RQjćŕ;ăy|JwŤ5QYff˝jgK]1ł4 ě<ŇÍŚ•­4îżÎo…âs{Źůé FcżĐc$‹YđÉš±Ś+4âď‹rđt/ł¦ecË–đu…yľÁG—?B¶EbKBL[.öS·ĺC±«(51±ÄÄáď ľÓÜnWů ´«‹fÚWhäzO„Y«ZŮôů ć­măÂeÝGíő%äĘúŁĽ’`éÔJ ‹ő’¸ÔC.í%zŹnë 30¨ź´ËáĄ÷Ú9űÇ-ĘŠŚl[Q‚5K}¨*“^+­´9płWϨű&š©ľ'#ľj`PcéűWi>Żw— ĹF>¬+!Ă$F@Oź˘%­qdGő2€y¶ 2gşŁAPŁdsâlÝ=ŕ‘(óą˙î Jň äĘdAËĹ~BaŤ#-ÚşÂ|v˘'KÓ@,ńyšüpčéčł~o&ÁŽúĘ·ŰV8˘Qżő­Ěݱ’oŐęVHă‹ćŢ”¦?¸]ÎVÉír†Đojť °ç¨€192ŰVß ôGiÜw,R݇ID¬Ť}ĹŞr#CAÝ|ŕNöŢoż}éË’ž·ăžFšÝ.çWńý>OSŘQ˝Ě<ȧ~ Rk ˛ĚŚĹ,Qű¨YŤ1đúĽ|,f‰}Ç{8ęîKëfúaC}rĚ-—ZXôn{ĽĐSHxŘír¶$ލšˇ·ČŔ 0ĄÂBýÜ<Ę&ÂŤ]‡ý|t¨›pdTX'0Çírž>‘´LŐ; ´/AäŢ/ÍFA8B:Ŕ·Ŕl·Ëy3ŮähďRX¬ĘÓQ†@ä?‡Á«€Z  ˝ëęďVá.[SYôżË?wdĆ|ŠpąIEND®B`‚mlt-7.22.0/docs/000775 000000 000000 00000000000 14531534050 013306 5ustar00rootroot000000 000000 mlt-7.22.0/docs/melt.1000664 000000 000000 00000006665 14531534050 014346 0ustar00rootroot000000 000000 .\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.38.4. .TH MELT "1" "November 2023" "melt 7.22.0" "User Commands" .SH NAME melt \- author, play, and encode multitrack audio/video compositions .SH SYNOPSIS .B melt [\fIoptions\fR] [\fIproducer \fR[\fIname=value\fR]\fI* \fR]\fI+\fR .SH OPTIONS .TP \fB\-attach\fR filter[:arg] [name=value]* Attach a filter to the output .TP \fB\-attach\-cut\fR filter[:arg] [name=value]* Attach a filter to a cut .HP \fB\-attach\-track\fR filter[:arg] [name=value]* Attach a filter to a track .TP \fB\-attach\-clip\fR filter[:arg] [name=value]* Attach a filter to a producer .TP \fB\-audio\-track\fR | \fB\-hide\-video\fR Add an audio\-only track .TP \fB\-blank\fR frames Add blank silence to a track .TP \fB\-chain\fR id[:arg] [name=value]* Add a producer as a chain .TP \fB\-consumer\fR id[:arg] [name=value]* Set the consumer (sink) .TP \fB\-debug\fR Set the logging level to debug .TP \fB\-filter\fR filter[:arg] [name=value]* Add a filter to the current track .TP \fB\-getc\fR Get keyboard input using getc .TP \fB\-group\fR [name=value]* Apply properties repeatedly .TP \fB\-help\fR Show this message .TP \fB\-jack\fR Enable JACK transport synchronization .TP \fB\-join\fR clips Join multiple clips into one cut .TP \fB\-link\fR id[:arg] [name=value]* Add a link to a chain .TP \fB\-mix\fR length Add a mix between the last two cuts .TP \fB\-mixer\fR transition Add a transition to the mix .TP \fB\-null\-track\fR | \fB\-hide\-track\fR Add a hidden track .TP \fB\-profile\fR name Set the processing settings .TP \fB\-progress\fR Display progress along with position .TP \fB\-query\fR List all of the registered services .TP \fB\-query\fR "consumers" | "consumer"=id List consumers or show info about one .TP \fB\-query\fR "filters" | "filter"=id List filters or show info about one .TP \fB\-query\fR "links" | "link"=id List links or show info about one .TP \fB\-query\fR "producers" | "producer"=id List producers or show info about one .TP \fB\-query\fR "transitions" | "transition"=id List transitions, show info about one .TP \fB\-query\fR "profiles" | "profile"=id List profiles, show info about one .TP \fB\-query\fR "presets" | "preset"=id List presets, show info about one .TP \fB\-query\fR "formats" List audio/video formats .TP \fB\-query\fR "audio_codecs" List audio codecs .TP \fB\-query\fR "video_codecs" List video codecs .TP \fB\-quiet\fR Set the logging level to quiet .TP \fB\-remove\fR Remove the most recent cut .TP \fB\-repeat\fR times Repeat the last cut .TP \fB\-repository\fR path Set the directory of MLT modules .TP \fB\-serialise\fR [filename] Write the commands to a text file .TP \fB\-setlocale\fR Make numeric strings locale-sensitive (legacy support) .TP \fB\-silent\fR Do not display position/transport .TP \fB\-split\fR relative\-frame Split the last cut into two cuts .TP \fB\-swap\fR Rearrange the last two cuts .TP \fB\-track\fR Add a track .TP \fB\-transition\fR id[:arg] [name=value]* Add a transition .TP \fB\-verbose\fR Set the logging level to verbose .TP \fB\-timings\fR Set the logging level to timings .TP \fB\-version\fR Show the version and copyright .TP \fB\-video\-track\fR | \fB\-hide\-audio\fR Add a video\-only track .PP For more help: .SH COPYRIGHT Copyright \(co 2002\-2023 Meltytech, LLC .br This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. mlt-7.22.0/makefile000664 000000 000000 00000001217 14531534050 014057 0ustar00rootroot000000 000000 default: @echo This Makefile is not used for building. Use CMake instead. @echo Rather, this makefile is purely for holding some maintenance routines. dist: git archive --format=tar --prefix=mlt-$(version)/ v$(version) | gzip >mlt-$(version).tar.gz validate-yml: for file in $$(find src/modules -maxdepth 2 -type f -name \*.yml \! -name resolution_scale.yml); do \ echo "validate: $$file"; \ kwalify -f src/framework/metaschema.yaml $$file || exit 1; \ done codespell: codespell -w -q 3 \ -L shotcut,sav,boundry,percentil,readded,uint,ith,sinc,amin,childs,seeked,writen \ -S ChangeLog,cJSON.c,cJSON.h,RtAudio.cpp,RtAudio.h,*.rej,mlt_wrap.* mlt-7.22.0/presets/000775 000000 000000 00000000000 14531534050 014043 5ustar00rootroot000000 000000 mlt-7.22.0/presets/consumer/000775 000000 000000 00000000000 14531534050 015676 5ustar00rootroot000000 000000 mlt-7.22.0/presets/consumer/avformat/000775 000000 000000 00000000000 14531534050 017515 5ustar00rootroot000000 000000 mlt-7.22.0/presets/consumer/avformat/AAC000664 000000 000000 00000000173 14531534050 020025 0ustar00rootroot000000 000000 f=mp4 acodec=aac ab=256k vn=1 video_off=1 meta.preset.extension=m4a meta.preset.note=audio only meta.preset.name=audio/AAC mlt-7.22.0/presets/consumer/avformat/ALAC000664 000000 000000 00000000166 14531534050 020143 0ustar00rootroot000000 000000 f=mov acodec=alac vn=1 video_off=1 meta.preset.extension=mov meta.preset.note=audio only meta.preset.name=audio/ALAC mlt-7.22.0/presets/consumer/avformat/AV1000664 000000 000000 00000000431 14531534050 020025 0ustar00rootroot000000 000000 f=webm acodec=libopus ar=48000 ab=128k vcodec=libaom-av1 vb=0 crf=45 row-mt=1 tile-columns=2 tile-rows=1 cpu-used=4 strict=experimental meta.preset.name=AV1 WebM meta.preset.extension=webm meta.preset.note=AV1 video with Opus audio in Matroska container: Just say no to patents mlt-7.22.0/presets/consumer/avformat/FLAC000664 000000 000000 00000000170 14531534050 020143 0ustar00rootroot000000 000000 f=flac acodec=flac vn=1 video_off=1 meta.preset.extension=flac meta.preset.note=audio only meta.preset.name=audio/FLAC mlt-7.22.0/presets/consumer/avformat/Flash000664 000000 000000 00000000365 14531534050 020501 0ustar00rootroot000000 000000 f=flv acodec=libmp3lame ab=128k ar=44100 vcodec=flv minrate=0 vb=1M bf=0 progressive=1 meta.preset.extension=flv meta.preset.note=This is the old Sorenson H.263-based codec. Most people use H.264 with Flash now. meta.preset.name=legacy/Flash mlt-7.22.0/presets/consumer/avformat/GIF000664 000000 000000 00000000147 14531534050 020047 0ustar00rootroot000000 000000 progressive=1 f=gif vcodec=gif an=1 g=1 bf=0 meta.preset.extension=gif meta.preset.name=GIF Animation mlt-7.22.0/presets/consumer/avformat/MJPEG000664 000000 000000 00000000132 14531534050 020276 0ustar00rootroot000000 000000 f=avi vcodec=mjpeg qscale=4 acodec=libmp3lame ab=256k g=1 bf=0 meta.preset.extension=avi mlt-7.22.0/presets/consumer/avformat/MP3000664 000000 000000 00000000203 14531534050 020032 0ustar00rootroot000000 000000 f=mp3 acodec=libmp3lame ab=256k vn=1 video_off=1 meta.preset.extension=mp3 meta.preset.note=audio only meta.preset.name=audio/MP3 mlt-7.22.0/presets/consumer/avformat/MPEG-2000664 000000 000000 00000000313 14531534050 020324 0ustar00rootroot000000 000000 f=mpeg acodec=mp2 ab=256k ar=48000 vcodec=mpeg2video minrate=0 vb=8M trellis=1 bf=2 b_strategy=1 mbd=rd cmp=satd subcmp=satd meta.preset.extension=mpg meta.preset.note=a general purpose MPEG-2 preset mlt-7.22.0/presets/consumer/avformat/MPEG-4000664 000000 000000 00000000413 14531534050 020327 0ustar00rootroot000000 000000 f=mp4 acodec=libmp3lame ab=128k ar=44100 vcodec=mpeg4 minrate=0 vb=2M mbd=rd trellis=1 flags=+mv4+aic cmp=satd subcmp=satd progressive=1 movflags=+faststart meta.preset.extension=mp4 meta.preset.note=Part 2 Simple Profile meta.preset.name=legacy/MPEG-4 Part 2 SP mlt-7.22.0/presets/consumer/avformat/MPEG-4-ASP000664 000000 000000 00000000421 14531534050 020747 0ustar00rootroot000000 000000 f=mp4 acodec=libmp3lame ab=128k ar=44100 vcodec=mpeg4 minrate=0 vb=2M mbd=rd trellis=1 cmp=satd subcmp=satd bf=2 flags=+mv4+aic+qpel movflags=+faststart meta.preset.extension=mp4 meta.preset.note=Part 2 Advanced Simple Profile meta.preset.name=legacy/MPEG-4 Part 2 ASP mlt-7.22.0/presets/consumer/avformat/Slide-Deck-H264000664 000000 000000 00000000601 14531534050 021762 0ustar00rootroot000000 000000 pix_fmt=yuv420p vcodec=libx264 vprofile=high crf=27 g=249 bf=8 preset=veryfast b-pyramid=0 refs=8 weightb=1 b_strategy=2 rc-lookahead=25 channels=1 acodec=ac3 ar=44100 ab=80k f=mp4 movflags=+faststart meta.preset.extension=mp4 meta.preset.note=Small file size for slide decks, video conferences, and screencasts. Hardware encoding not recommended. meta.preset.name=Slide Deck (H.264) mlt-7.22.0/presets/consumer/avformat/Slide-Deck-HEVC000664 000000 000000 00000000525 14531534050 022071 0ustar00rootroot000000 000000 pix_fmt=yuv420p vcodec=libx265 crf=31 g=249 bf=8 preset=slow x265-params=weightb=1:open-gop=0 channels=1 acodec=ac3 ar=44100 ab=80k f=mp4 movflags=+faststart meta.preset.extension=mp4 meta.preset.note=Small file size for slide decks, video conferences, and screencasts. Hardware encoding not recommended. meta.preset.name=Slide Deck (HEVC) mlt-7.22.0/presets/consumer/avformat/Sony-PSP000664 000000 000000 00000000456 14531534050 021035 0ustar00rootroot000000 000000 width=480 height=272 aspect=@16/9 progressive=1 f=psp acodec=aac ar=32000 ab=96k vcodec=libx264 threads=0 preset=medium vprofile=main flags2=-dct8x8 vb=1M refs=1 bf=1 x264opts=ref=1:bframes=1 meta.preset.extension=mp4 meta.preset.note=for Sony PlayStation Portable meta.preset.name=device/Sony PSP mlt-7.22.0/presets/consumer/avformat/Vorbis000664 000000 000000 00000000206 14531534050 020702 0ustar00rootroot000000 000000 f=ogg acodec=vorbis ab=256k vn=1 video_off=1 meta.preset.name=audio/Ogg Vorbis meta.preset.extension=ogg meta.preset.note=audio only mlt-7.22.0/presets/consumer/avformat/WAV000664 000000 000000 00000000172 14531534050 020075 0ustar00rootroot000000 000000 f=wav acodec=pcm_s16le vn=1 video_off=1 meta.preset.extension=wav meta.preset.note=audio only meta.preset.name=audio/WAV mlt-7.22.0/presets/consumer/avformat/WMA000664 000000 000000 00000000207 14531534050 020063 0ustar00rootroot000000 000000 f=asf acodec=wmav2 ab=256k vn=1 video_off=1 meta.preset.extension=wma meta.preset.note=Windows Media Audio meta.preset.name=audio/WMA mlt-7.22.0/presets/consumer/avformat/WMV000664 000000 000000 00000000230 14531534050 020104 0ustar00rootroot000000 000000 f=asf acodec=wmav2 ab=160k vcodec=wmv2 g=100 vb=2M bf=0 meta.preset.extension=wmv meta.preset.note=Windows Media Video (Windows Media Player 8 and up) mlt-7.22.0/presets/consumer/avformat/XDCAM-HD422000664 000000 000000 00000000573 14531534050 021022 0ustar00rootroot000000 000000 f=mxf vcodec=mpeg2video top_field_first=1 pix_fmt=yuv422p minrate=50M maxrate=50M vb=50M dc=10 intra_vlc=1 non_linear_quant=1 lmin=1*QP2LAMBDA rc_max_vbv_use=1 rc_min_vbv_use=1 qmin=1 qmax=12 bufsize=36408333 g=12 bf=2 vtag=xd5e acodec=pcm_s16le meta.preset.extension=mxf meta.preset.note=Sony "workflow innovation". Set vtag as needed. meta.preset.name=camcorder/XDCAM-HD422 mlt-7.22.0/presets/consumer/avformat/YouTube000664 000000 000000 00000000320 14531534050 021027 0ustar00rootroot000000 000000 f=mp4 movflags=+faststart acodec=aac ar=48000 ab=384k vcodec=libx264 progressive=1 preset=fast threads=0 crf=23 g=15 bf=2 vprofile=high meta.preset.extension=mp4 meta.preset.note=H.264/AAC MP4 for YouTube mlt-7.22.0/presets/consumer/avformat/alpha/000775 000000 000000 00000000000 14531534050 020602 5ustar00rootroot000000 000000 mlt-7.22.0/presets/consumer/avformat/alpha/Quicktime Animation000664 000000 000000 00000000233 14531534050 024356 0ustar00rootroot000000 000000 f=mov vcodec=qtrle g=1 bf=0 progressive=1 mlt_image_format=rgba pix_fmt=argb meta.preset.extension=mov meta.preset.note=lossless video with alpha channel mlt-7.22.0/presets/consumer/avformat/alpha/Ut Video000664 000000 000000 00000000301 14531534050 022136 0ustar00rootroot000000 000000 f=avi vcodec=utvideo mlt_image_format=rgba pix_fmt=gbrap g=1 bf=0 acodec=pcm_s24le meta.preset.extension=avi meta.preset.note=Ut Video with alpha channel and 24-bit PCM audio in AVI container mlt-7.22.0/presets/consumer/avformat/alpha/vp8000664 000000 000000 00000000646 14531534050 021250 0ustar00rootroot000000 000000 f=webm acodec=vorbis ab=128k vcodec=libvpx g=120 rc_lookahead=16 quality=good speed=0 vprofile=0 qmax=51 qmin=11 slices=4 vb=2M maxrate=24M minrate=100k arnr_max_frames=7 arnr_strength=5 arnr_type=3 auto-alt-ref=0 mlt_image_format=rgba pix_fmt=yuva420p meta.preset.name=alpha/WebM VP8 with alpha channel meta.preset.extension=webm meta.preset.note=VP8 video with Ogg Vorbis audio in Matroska container: "Don't be evil" mlt-7.22.0/presets/consumer/avformat/alpha/vp9000664 000000 000000 00000000657 14531534050 021253 0ustar00rootroot000000 000000 f=webm acodec=libopus ar=48000 ab=128k vcodec=libvpx-vp9 vb=2M g=120 bf=2 threads=0 rc_lookahead=16 quality=good speed=3 vprofile=0 qmax=51 qmin=4 slices=4 tile-columns=6 frame-parallel=1 lag-in-frames=25 row-mt=1 auto-alt-ref=0 mlt_image_format=rgba pix_fmt=yuva420p meta.preset.name=alpha/WebM VP9 with alpha channel meta.preset.extension=webm meta.preset.note=VP9 video with Opus audio in Matroska container: "Don't be evil" mlt-7.22.0/presets/consumer/avformat/atsc_1080i_50/000775 000000 000000 00000000000 14531534050 021574 5ustar00rootroot000000 000000 mlt-7.22.0/presets/consumer/avformat/atsc_1080i_50/DNxHD000664 000000 000000 00000000364 14531534050 022427 0ustar00rootroot000000 000000 f=mov vcodec=dnxhd vb=185M threads=2 acodec=pcm_s16le g=1 bf=0 meta.preset.extension=mov meta.preset.note=A lightly compressed intermediate codec developed by Avid also known as SMPTE VC-3 meta.preset.name=intermediate/DNxHD (HD 1080i 25 fps) mlt-7.22.0/presets/consumer/avformat/atsc_1080i_5994/000775 000000 000000 00000000000 14531534050 021762 5ustar00rootroot000000 000000 mlt-7.22.0/presets/consumer/avformat/atsc_1080i_5994/DNxHD000664 000000 000000 00000000367 14531534050 022620 0ustar00rootroot000000 000000 f=mov vcodec=dnxhd vb=220M threads=2 acodec=pcm_s16le g=1 bf=0 meta.preset.extension=mov meta.preset.note=A lightly compressed intermediate codec developed by Avid also known as SMPTE VC-3 meta.preset.name=intermediate/DNxHD (HD 1080i 29.97 fps) mlt-7.22.0/presets/consumer/avformat/atsc_1080p_2398/000775 000000 000000 00000000000 14531534050 021764 5ustar00rootroot000000 000000 mlt-7.22.0/presets/consumer/avformat/atsc_1080p_2398/DNxHD000664 000000 000000 00000000367 14531534050 022622 0ustar00rootroot000000 000000 f=mov vcodec=dnxhd vb=175M threads=2 acodec=pcm_s16le g=1 bf=0 meta.preset.extension=mov meta.preset.note=A lightly compressed intermediate codec developed by Avid also known as SMPTE VC-3 meta.preset.name=intermediate/DNxHD (HD 1080p 23.98 fps) mlt-7.22.0/presets/consumer/avformat/atsc_1080p_24/000775 000000 000000 00000000000 14531534050 021604 5ustar00rootroot000000 000000 mlt-7.22.0/presets/consumer/avformat/atsc_1080p_24/DNxHD000664 000000 000000 00000000364 14531534050 022437 0ustar00rootroot000000 000000 f=mov vcodec=dnxhd vb=175M threads=2 acodec=pcm_s16le g=1 bf=0 meta.preset.extension=mov meta.preset.note=A lightly compressed intermediate codec developed by Avid also known as SMPTE VC-3 meta.preset.name=intermediate/DNxHD (HD 1080p 24 fps) mlt-7.22.0/presets/consumer/avformat/atsc_1080p_25/000775 000000 000000 00000000000 14531534050 021605 5ustar00rootroot000000 000000 mlt-7.22.0/presets/consumer/avformat/atsc_1080p_25/DNxHD000664 000000 000000 00000000364 14531534050 022440 0ustar00rootroot000000 000000 f=mov vcodec=dnxhd vb=185M threads=2 acodec=pcm_s16le g=1 bf=0 meta.preset.extension=mov meta.preset.note=A lightly compressed intermediate codec developed by Avid also known as SMPTE VC-3 meta.preset.name=intermediate/DNxHD (HD 1080p 25 fps) mlt-7.22.0/presets/consumer/avformat/atsc_1080p_2997/000775 000000 000000 00000000000 14531534050 021771 5ustar00rootroot000000 000000 mlt-7.22.0/presets/consumer/avformat/atsc_1080p_2997/DNxHD000664 000000 000000 00000000367 14531534050 022627 0ustar00rootroot000000 000000 f=mov vcodec=dnxhd vb=220M threads=2 acodec=pcm_s16le g=1 bf=0 meta.preset.extension=mov meta.preset.note=A lightly compressed intermediate codec developed by Avid also known as SMPTE VC-3 meta.preset.name=intermediate/DNxHD (HD 1080p 29.97 fps) mlt-7.22.0/presets/consumer/avformat/atsc_1080p_30/000775 000000 000000 00000000000 14531534050 021601 5ustar00rootroot000000 000000 mlt-7.22.0/presets/consumer/avformat/atsc_1080p_30/DNxHD000664 000000 000000 00000000364 14531534050 022434 0ustar00rootroot000000 000000 f=mov vcodec=dnxhd vb=220M threads=2 acodec=pcm_s16le g=1 bf=0 meta.preset.extension=mov meta.preset.note=A lightly compressed intermediate codec developed by Avid also known as SMPTE VC-3 meta.preset.name=intermediate/DNxHD (HD 1080p 30 fps) mlt-7.22.0/presets/consumer/avformat/atsc_1080p_50/000775 000000 000000 00000000000 14531534050 021603 5ustar00rootroot000000 000000 mlt-7.22.0/presets/consumer/avformat/atsc_1080p_50/DNxHD000664 000000 000000 00000000364 14531534050 022436 0ustar00rootroot000000 000000 f=mov vcodec=dnxhd vb=185M threads=2 acodec=pcm_s16le g=1 bf=0 meta.preset.extension=mov meta.preset.note=A lightly compressed intermediate codec developed by Avid also known as SMPTE VC-3 meta.preset.name=intermediate/DNxHD (HD 1080p 50 fps) mlt-7.22.0/presets/consumer/avformat/atsc_1080p_5994/000775 000000 000000 00000000000 14531534050 021771 5ustar00rootroot000000 000000 mlt-7.22.0/presets/consumer/avformat/atsc_1080p_5994/DNxHD000664 000000 000000 00000000367 14531534050 022627 0ustar00rootroot000000 000000 f=mov vcodec=dnxhd vb=220M threads=2 acodec=pcm_s16le g=1 bf=0 meta.preset.extension=mov meta.preset.note=A lightly compressed intermediate codec developed by Avid also known as SMPTE VC-3 meta.preset.name=intermediate/DNxHD (HD 1080p 59.94 fps) mlt-7.22.0/presets/consumer/avformat/atsc_1080p_60/000775 000000 000000 00000000000 14531534050 021604 5ustar00rootroot000000 000000 mlt-7.22.0/presets/consumer/avformat/atsc_1080p_60/DNxHD000664 000000 000000 00000000364 14531534050 022437 0ustar00rootroot000000 000000 f=mov vcodec=dnxhd vb=220M threads=2 acodec=pcm_s16le g=1 bf=0 meta.preset.extension=mov meta.preset.note=A lightly compressed intermediate codec developed by Avid also known as SMPTE VC-3 meta.preset.name=intermediate/DNxHD (HD 1080p 60 fps) mlt-7.22.0/presets/consumer/avformat/atsc_720p_2398/000775 000000 000000 00000000000 14531534050 021704 5ustar00rootroot000000 000000 mlt-7.22.0/presets/consumer/avformat/atsc_720p_2398/DNxHD000664 000000 000000 00000000365 14531534050 022540 0ustar00rootroot000000 000000 f=mov vcodec=dnxhd vb=90M threads=2 acodec=pcm_s16le g=1 bf=0 meta.preset.extension=mov meta.preset.note=A lightly compressed intermediate codec developed by Avid also known as SMPTE VC-3 meta.preset.name=intermediate/DNxHD (HD 720p 23.98 fps) mlt-7.22.0/presets/consumer/avformat/atsc_720p_50/000775 000000 000000 00000000000 14531534050 021523 5ustar00rootroot000000 000000 mlt-7.22.0/presets/consumer/avformat/atsc_720p_50/DNxHD000664 000000 000000 00000000363 14531534050 022355 0ustar00rootroot000000 000000 f=mov vcodec=dnxhd vb=175M threads=2 acodec=pcm_s16le g=1 bf=0 meta.preset.extension=mov meta.preset.note=A lightly compressed intermediate codec developed by Avid also known as SMPTE VC-3 meta.preset.name=intermediate/DNxHD (HD 720p 50 fps) mlt-7.22.0/presets/consumer/avformat/atsc_720p_5994/000775 000000 000000 00000000000 14531534050 021711 5ustar00rootroot000000 000000 mlt-7.22.0/presets/consumer/avformat/atsc_720p_5994/DNxHD000664 000000 000000 00000000367 14531534050 022547 0ustar00rootroot000000 000000 f=mov vcodec=dnxhd vb=220M threads=2 acodec=pcm_s16le g=1 bf=0 meta.preset.extension=mov meta.preset.note=A lightly compressed intermediate codec developed by Avid also known as SMPTE VC-3 meta.preset.name=intermediate/DNxHD (HD 1080p 59.94 fps) mlt-7.22.0/presets/consumer/avformat/atsc_720p_60/000775 000000 000000 00000000000 14531534050 021524 5ustar00rootroot000000 000000 mlt-7.22.0/presets/consumer/avformat/atsc_720p_60/DNxHD000664 000000 000000 00000000363 14531534050 022356 0ustar00rootroot000000 000000 f=mov vcodec=dnxhd vb=220M threads=2 acodec=pcm_s16le g=1 bf=0 meta.preset.extension=mov meta.preset.note=A lightly compressed intermediate codec developed by Avid also known as SMPTE VC-3 meta.preset.name=intermediate/DNxHD (HD 720p 60 fps) mlt-7.22.0/presets/consumer/avformat/dv_ntsc/000775 000000 000000 00000000000 14531534050 021155 5ustar00rootroot000000 000000 mlt-7.22.0/presets/consumer/avformat/dv_ntsc/D10000664 000000 000000 00000000620 14531534050 021422 0ustar00rootroot000000 000000 f=mxf_d10 vcodec=mpeg2video pix_fmt=yuv422p minrate=50M maxrate=50M vb=50M g=1 bf=0 flags=+ildct+low_delay dc=10 intra_vlc=1 non_linear_quant=1 ps=1 qmin=1 qmax=3 bufsize=1669k rc_init_occupancy=1669k rc_buf_aggressivity=0.25 acodec=pcm_s16le meta.preset.extension=mxf meta.preset.note=Intra-frame only, 50 Mb/s MPEG-2 also known as Sony IMX or SMPTE 365M meta.preset.name=camcorder/D10 (SD NTSC) mlt-7.22.0/presets/consumer/avformat/dv_ntsc/DV000664 000000 000000 00000000320 14531534050 021404 0ustar00rootroot000000 000000 f=dv pix_fmt=yuv411p vcodec=dvvideo acodec=pcm_s16le g=1 bf=0 meta.preset.extension=dv meta.preset.note=The popular standard definition camcorder digital video format meta.preset.name=camcorder/DV (SD NTSC) mlt-7.22.0/presets/consumer/avformat/dv_ntsc/DVCPRO50000664 000000 000000 00000000300 14531534050 022233 0ustar00rootroot000000 000000 f=dv pix_fmt=yuv422p vcodec=dvvideo acodec=pcm_s16le g=1 bf=0 meta.preset.extension=dv meta.preset.note=Double the amount of chroma as normal DV meta.preset.name=camcorder/DVCPRO50 (SD NTSC) mlt-7.22.0/presets/consumer/avformat/dv_ntsc/DVD000664 000000 000000 00000000464 14531534050 021521 0ustar00rootroot000000 000000 f=dvd vcodec=mpeg2video acodec=ac3 vb=6000k maxrate=9000k minrate=0 bufsize=1835008 packetsize=2048 muxrate=10080000 ab=192k ar=48000 g=18 me_range=63 trellis=1 meta.preset.extension=vob meta.preset.note=Process the output with a DVD authoring tool such as dvdauthor. meta.preset.name=device/DVD (SD NTSC) mlt-7.22.0/presets/consumer/avformat/dv_ntsc_wide/000775 000000 000000 00000000000 14531534050 022165 5ustar00rootroot000000 000000 mlt-7.22.0/presets/consumer/avformat/dv_ntsc_wide/D10000664 000000 000000 00000000633 14531534050 022436 0ustar00rootroot000000 000000 f=mxf_d10 vcodec=mpeg2video pix_fmt=yuv422p minrate=50M maxrate=50M vb=50M g=1 bf=0 flags=+ildct+low_delay dc=10 intra_vlc=1 non_linear_quant=1 ps=1 qmin=1 qmax=3 bufsize=1669k rc_init_occupancy=1669k rc_buf_aggressivity=0.25 acodec=pcm_s16le meta.preset.extension=mxf meta.preset.note=Intra-frame only, 50 Mb/s MPEG-2 also known as Sony IMX or SMPTE 365M meta.preset.name=camcorder/D10 (SD Widescreen NTSC) mlt-7.22.0/presets/consumer/avformat/dv_ntsc_wide/DV000664 000000 000000 00000000333 14531534050 022420 0ustar00rootroot000000 000000 f=dv pix_fmt=yuv411p vcodec=dvvideo acodec=pcm_s16le g=1 bf=0 meta.preset.extension=dv meta.preset.note=The popular standard definition camcorder digital video format meta.preset.name=camcorder/DV (SD Widescreen NTSC) mlt-7.22.0/presets/consumer/avformat/dv_ntsc_wide/DVCPRO50000664 000000 000000 00000000313 14531534050 023247 0ustar00rootroot000000 000000 f=dv pix_fmt=yuv422p vcodec=dvvideo acodec=pcm_s16le g=1 bf=0 meta.preset.extension=dv meta.preset.note=Double the amount of chroma as normal DV meta.preset.name=camcorder/DVCPRO50 (SD Widescreen NTSC) mlt-7.22.0/presets/consumer/avformat/dv_ntsc_wide/DVD000664 000000 000000 00000000477 14531534050 022535 0ustar00rootroot000000 000000 f=dvd vcodec=mpeg2video acodec=ac3 vb=6000k maxrate=9000k minrate=0 bufsize=1835008 packetsize=2048 muxrate=10080000 ab=192k ar=48000 g=18 me_range=63 trellis=1 meta.preset.extension=vob meta.preset.note=Process the output with a DVD authoring tool such as dvdauthor. meta.preset.name=device/DVD (SD Widescreen NTSC) mlt-7.22.0/presets/consumer/avformat/dv_pal/000775 000000 000000 00000000000 14531534050 020762 5ustar00rootroot000000 000000 mlt-7.22.0/presets/consumer/avformat/dv_pal/D10000664 000000 000000 00000000640 14531534050 021231 0ustar00rootroot000000 000000 f=mxf_d10 top_field_first=1 vcodec=mpeg2video pix_fmt=yuv422p minrate=50M maxrate=50M vb=50M g=1 bf=0 flags=+ildct+low_delay dc=10 intra_vlc=1 non_linear_quant=1 ps=1 qmin=1 qmax=3 bufsize=2000k rc_init_occupancy=2000k rc_buf_aggressivity=0.25 acodec=pcm_s16le meta.preset.extension=mxf meta.preset.note=Intra-frame only, 50 Mb/s MPEG-2 also known as Sony IMX or SMPTE 365M meta.preset.name=camcorder/D10 (SD PAL) mlt-7.22.0/presets/consumer/avformat/dv_pal/DV000664 000000 000000 00000000317 14531534050 021217 0ustar00rootroot000000 000000 f=dv pix_fmt=yuv420p vcodec=dvvideo acodec=pcm_s16le g=1 bf=0 meta.preset.extension=dv meta.preset.note=The popular standard definition camcorder digital video format meta.preset.name=camcorder/DV (SD PAL) mlt-7.22.0/presets/consumer/avformat/dv_pal/DVCPRO50000664 000000 000000 00000000277 14531534050 022055 0ustar00rootroot000000 000000 f=dv pix_fmt=yuv422p vcodec=dvvideo acodec=pcm_s16le g=1 bf=0 meta.preset.extension=dv meta.preset.note=Double the amount of chroma as normal DV meta.preset.name=camcorder/DVCPRO50 (SD PAL) mlt-7.22.0/presets/consumer/avformat/dv_pal/DVD000664 000000 000000 00000000462 14531534050 021324 0ustar00rootroot000000 000000 f=dvd vcodec=mpeg2video acodec=ac3 vb=5000k maxrate=8000k minrate=0 bufsize=1835008 packetsize=2048 muxrate=10080000 ab=192k ar=48000 g=15 me_range=63 trellis=1 meta.preset.extension=vob meta.preset.note=Process the output with a DVD authoring tool such as dvdauthor. meta.preset.name=device/DVD (SD PAL) mlt-7.22.0/presets/consumer/avformat/dv_pal_wide/000775 000000 000000 00000000000 14531534050 021772 5ustar00rootroot000000 000000 mlt-7.22.0/presets/consumer/avformat/dv_pal_wide/D10000664 000000 000000 00000000653 14531534050 022245 0ustar00rootroot000000 000000 f=mxf_d10 top_field_first=1 vcodec=mpeg2video pix_fmt=yuv422p minrate=50M maxrate=50M vb=50M g=1 bf=0 flags=+ildct+low_delay dc=10 intra_vlc=1 non_linear_quant=1 ps=1 qmin=1 qmax=3 bufsize=2000k rc_init_occupancy=2000k rc_buf_aggressivity=0.25 acodec=pcm_s16le meta.preset.extension=mxf meta.preset.note=Intra-frame only, 50 Mb/s MPEG-2 also known as Sony IMX or SMPTE 365M meta.preset.name=camcorder/D10 (SD Widescreen PAL) mlt-7.22.0/presets/consumer/avformat/dv_pal_wide/DV000664 000000 000000 00000000332 14531534050 022224 0ustar00rootroot000000 000000 f=dv pix_fmt=yuv420p vcodec=dvvideo acodec=pcm_s16le g=1 bf=0 meta.preset.extension=dv meta.preset.note=The popular standard definition camcorder digital video format meta.preset.name=camcorder/DV (SD Widescreen PAL) mlt-7.22.0/presets/consumer/avformat/dv_pal_wide/DVCPRO50000664 000000 000000 00000000312 14531534050 023053 0ustar00rootroot000000 000000 f=dv pix_fmt=yuv422p vcodec=dvvideo acodec=pcm_s16le g=1 bf=0 meta.preset.extension=dv meta.preset.note=Double the amount of chroma as normal DV meta.preset.name=camcorder/DVCPRO50 (SD Widescreen PAL) mlt-7.22.0/presets/consumer/avformat/dv_pal_wide/DVD000664 000000 000000 00000000476 14531534050 022341 0ustar00rootroot000000 000000 f=dvd vcodec=mpeg2video acodec=ac3 vb=5000k maxrate=8000k minrate=0 bufsize=1835008 packetsize=2048 muxrate=10080000 ab=192k ar=48000 g=15 me_range=63 trellis=1 meta.preset.extension=vob meta.preset.note=Process the output with a DVD authoring tool such as dvdauthor. meta.preset.name=device/DVD (SD Widescreen PAL) mlt-7.22.0/presets/consumer/avformat/hdv_1080_25p/000775 000000 000000 00000000000 14531534050 021434 5ustar00rootroot000000 000000 mlt-7.22.0/presets/consumer/avformat/hdv_1080_25p/HDV000664 000000 000000 00000000372 14531534050 022002 0ustar00rootroot000000 000000 f=mpegts acodec=mp2 ab=384k ar=48000 ac=2 vcodec=mpeg2video vb=25M g=15 trellis=1 bf=2 b_strategy=1 mbd=rd cmp=satd subcmp=satd meta.preset.extension=m2t meta.preset.note=HD MPEG-2 camcorder format meta.preset.name=camcorder/HDV (HD 1080p 25 fps) mlt-7.22.0/presets/consumer/avformat/hdv_1080_30p/000775 000000 000000 00000000000 14531534050 021430 5ustar00rootroot000000 000000 mlt-7.22.0/presets/consumer/avformat/hdv_1080_30p/HDV000664 000000 000000 00000000375 14531534050 022001 0ustar00rootroot000000 000000 f=mpegts acodec=mp2 ab=384k ar=48000 ac=2 vcodec=mpeg2video vb=25M g=15 trellis=1 bf=2 b_strategy=1 mbd=rd cmp=satd subcmp=satd meta.preset.extension=m2t meta.preset.note=HD MPEG-2 camcorder format meta.preset.name=camcorder/HDV (HD 1080p 29.97 fps) mlt-7.22.0/presets/consumer/avformat/hdv_1080_50i/000775 000000 000000 00000000000 14531534050 021423 5ustar00rootroot000000 000000 mlt-7.22.0/presets/consumer/avformat/hdv_1080_50i/HDV000664 000000 000000 00000000372 14531534050 021771 0ustar00rootroot000000 000000 f=mpegts acodec=mp2 ab=384k ar=48000 ac=2 vcodec=mpeg2video vb=25M g=15 trellis=1 bf=2 b_strategy=1 mbd=rd cmp=satd subcmp=satd meta.preset.extension=m2t meta.preset.note=HD MPEG-2 camcorder format meta.preset.name=camcorder/HDV (HD 1080i 25 fps) mlt-7.22.0/presets/consumer/avformat/hdv_1080_60i/000775 000000 000000 00000000000 14531534050 021424 5ustar00rootroot000000 000000 mlt-7.22.0/presets/consumer/avformat/hdv_1080_60i/HDV000664 000000 000000 00000000375 14531534050 021775 0ustar00rootroot000000 000000 f=mpegts acodec=mp2 ab=384k ar=48000 ac=2 vcodec=mpeg2video vb=25M g=15 trellis=1 bf=2 b_strategy=1 mbd=rd cmp=satd subcmp=satd meta.preset.extension=m2t meta.preset.note=HD MPEG-2 camcorder format meta.preset.name=camcorder/HDV (HD 1080i 29.97 fps) mlt-7.22.0/presets/consumer/avformat/hdv_720_25p/000775 000000 000000 00000000000 14531534050 021354 5ustar00rootroot000000 000000 mlt-7.22.0/presets/consumer/avformat/hdv_720_25p/HDV000664 000000 000000 00000000371 14531534050 021721 0ustar00rootroot000000 000000 f=mpegts acodec=mp2 ab=384k ar=48000 ac=2 vcodec=mpeg2video vb=25M g=15 trellis=1 bf=2 b_strategy=1 mbd=rd cmp=satd subcmp=satd meta.preset.extension=m2t meta.preset.note=HD MPEG-2 camcorder format meta.preset.name=camcorder/HDV (HD 720p 25 fps) mlt-7.22.0/presets/consumer/avformat/hdv_720_30p/000775 000000 000000 00000000000 14531534050 021350 5ustar00rootroot000000 000000 mlt-7.22.0/presets/consumer/avformat/hdv_720_30p/HDV000664 000000 000000 00000000374 14531534050 021720 0ustar00rootroot000000 000000 f=mpegts acodec=mp2 ab=384k ar=48000 ac=2 vcodec=mpeg2video vb=25M g=15 trellis=1 bf=2 b_strategy=1 mbd=rd cmp=satd subcmp=satd meta.preset.extension=m2t meta.preset.note=HD MPEG-2 camcorder format meta.preset.name=camcorder/HDV (HD 720p 29.97 fps) mlt-7.22.0/presets/consumer/avformat/hdv_720_50p/000775 000000 000000 00000000000 14531534050 021352 5ustar00rootroot000000 000000 mlt-7.22.0/presets/consumer/avformat/hdv_720_50p/HDV000664 000000 000000 00000000371 14531534050 021717 0ustar00rootroot000000 000000 f=mpegts acodec=mp2 ab=384k ar=48000 ac=2 vcodec=mpeg2video vb=25M g=15 trellis=1 bf=2 b_strategy=1 mbd=rd cmp=satd subcmp=satd meta.preset.extension=m2t meta.preset.note=HD MPEG-2 camcorder format meta.preset.name=camcorder/HDV (HD 720p 50 fps) mlt-7.22.0/presets/consumer/avformat/hdv_720_60p/000775 000000 000000 00000000000 14531534050 021353 5ustar00rootroot000000 000000 mlt-7.22.0/presets/consumer/avformat/hdv_720_60p/HDV000664 000000 000000 00000000374 14531534050 021723 0ustar00rootroot000000 000000 f=mpegts acodec=mp2 ab=384k ar=48000 ac=2 vcodec=mpeg2video vb=25M g=15 trellis=1 bf=2 b_strategy=1 mbd=rd cmp=satd subcmp=satd meta.preset.extension=m2t meta.preset.note=HD MPEG-2 camcorder format meta.preset.name=camcorder/HDV (HD 720p 59.94 fps) mlt-7.22.0/presets/consumer/avformat/intermediate/000775 000000 000000 00000000000 14531534050 022167 5ustar00rootroot000000 000000 mlt-7.22.0/presets/consumer/avformat/intermediate/DNxHR-HQ000664 000000 000000 00000000425 14531534050 023344 0ustar00rootroot000000 000000 pix_fmt=yuv422p progressive=1 vcodec=dnxhd vprofile=dnxhr_hq qscale=1 vb=0 g=1 bf=0 acodec=pcm_s24le f=mov movflags=+write_colr+faststart meta.preset.extension=mov meta.preset.note=Avid's intermediate codec for resolutions up to UHD/C4K meta.preset.name=intermediate/DNxHR HQ mlt-7.22.0/presets/consumer/avformat/intermediate/MJPEG000664 000000 000000 00000000225 14531534050 022753 0ustar00rootroot000000 000000 progressive=1 f=avi acodec=pcm_s16le vcodec=mjpeg qscale=1 g=1 bf=0 meta.preset.extension=avi meta.preset.note=not lossless, but still high quality mlt-7.22.0/presets/consumer/avformat/intermediate/MPEG-2000664 000000 000000 00000000244 14531534050 023001 0ustar00rootroot000000 000000 f=mpeg acodec=ac3 ab=512k vcodec=mpeg2video vb=0 g=1 bf=0 qscale=1 meta.preset.extension=mpg meta.preset.note=a little lossy, but intra-frame only with AC-3 audio mlt-7.22.0/presets/consumer/avformat/intermediate/MPEG-4000664 000000 000000 00000000250 14531534050 023000 0ustar00rootroot000000 000000 f=avi acodec=pcm_s16le vcodec=mpeg4 qscale=1 g=1 vb=0 bf=0 meta.preset.extension=avi meta.preset.note=somewhat lossy, intra-frame only MPEG-4, with uncompressed audio mlt-7.22.0/presets/consumer/avformat/intermediate/ProRes000664 000000 000000 00000000345 14531534050 023326 0ustar00rootroot000000 000000 f=mov acodec=pcm_s16le vcodec=prores vb=0 g=1 bf=0 vprofile=2 vendor=apl0 movflags=+write_colr meta.preset.extension=mov meta.preset.note=Designed by Apple in California. Set vprofile=1 for LT or =3 for HQ. meta.preset.hidden=1 mlt-7.22.0/presets/consumer/avformat/intermediate/ProRes HQ000664 000000 000000 00000000405 14531534050 023614 0ustar00rootroot000000 000000 f=mov acodec=pcm_s16le vcodec=prores_ks vb=0 g=1 bf=0 vprofile=3 vendor=apl0 qscale=1 movflags=+write_colr meta.preset.extension=mov meta.preset.note=Designed by Apple in California. Set vprofile=1 for LT or =2 for 422. meta.preset.name=intermediate/ProRes HQ mlt-7.22.0/presets/consumer/avformat/intermediate/ProRes-Kostya000664 000000 000000 00000000405 14531534050 024573 0ustar00rootroot000000 000000 f=mov acodec=pcm_s16le vcodec=prores_ks vb=0 g=1 bf=0 vprofile=2 vendor=apl0 qscale=1 movflags=+write_colr meta.preset.extension=mov meta.preset.note=Designed by Apple in California. Set vprofile=1 for LT or =3 for HQ. meta.preset.name=intermediate/ProRes 422 mlt-7.22.0/presets/consumer/avformat/lossless/000775 000000 000000 00000000000 14531534050 021364 5ustar00rootroot000000 000000 mlt-7.22.0/presets/consumer/avformat/lossless/FFV1000664 000000 000000 00000000265 14531534050 022014 0ustar00rootroot000000 000000 f=matroska acodec=flac vcodec=ffv1 g=1 bf=0 coder=1 context=1 pix_fmt=yuv422p meta.preset.extension=mkv meta.preset.note=FFmpeg video codec 1 with FLAC audio in Matroska container mlt-7.22.0/presets/consumer/avformat/lossless/H.264000664 000000 000000 00000000300 14531534050 022001 0ustar00rootroot000000 000000 f=mp4 acodec=aac ab=384k vcodec=libx264 vb=0 g=25 bf=0 preset=medium crf=0 coder=ac meta.preset.extension=mp4 meta.preset.note=Intra-frame only, lossless compressed MPEG-4 AVC with AAC audio mlt-7.22.0/presets/consumer/avformat/lossless/HuffYUV000664 000000 000000 00000000201 14531534050 022574 0ustar00rootroot000000 000000 f=matroska acodec=flac vcodec=huffyuv g=1 bf=0 meta.preset.extension=mkv meta.preset.note=with FLAC audio in Matroska container mlt-7.22.0/presets/consumer/avformat/lossless/Ut Video000664 000000 000000 00000000306 14531534050 022725 0ustar00rootroot000000 000000 f=matroska vcodec=utvideo pix_fmt=yuv422p g=1 bf=0 color_range=mpeg pix_fmt=yuv422p acodec=pcm_s24le meta.preset.extension=mkv meta.preset.note=Ut Video with 24-bit PCM audio in Matroska container mlt-7.22.0/presets/consumer/avformat/stills/000775 000000 000000 00000000000 14531534050 021027 5ustar00rootroot000000 000000 mlt-7.22.0/presets/consumer/avformat/stills/BMP000664 000000 000000 00000000127 14531534050 021370 0ustar00rootroot000000 000000 progressive=1 f=image2 vcodec=bmp an=1 audio_off=1 g=1 bf=0 meta.preset.extension=bmp mlt-7.22.0/presets/consumer/avformat/stills/DPX000664 000000 000000 00000000127 14531534050 021405 0ustar00rootroot000000 000000 progressive=1 f=image2 vcodec=dpx an=1 audio_off=1 g=1 bf=0 meta.preset.extension=dpx mlt-7.22.0/presets/consumer/avformat/stills/JPEG000664 000000 000000 00000000142 14531534050 021474 0ustar00rootroot000000 000000 progressive=1 f=image2 vcodec=mjpeg qscale=1 an=1 audio_off=1 g=1 bf=0 meta.preset.extension=jpg mlt-7.22.0/presets/consumer/avformat/stills/PNG000664 000000 000000 00000000127 14531534050 021376 0ustar00rootroot000000 000000 progressive=1 f=image2 vcodec=png an=1 audio_off=1 g=1 bf=0 meta.preset.extension=png mlt-7.22.0/presets/consumer/avformat/stills/PPM000664 000000 000000 00000000127 14531534050 021406 0ustar00rootroot000000 000000 progressive=1 f=image2 vcodec=ppm an=1 audio_off=1 g=1 bf=0 meta.preset.extension=ppm mlt-7.22.0/presets/consumer/avformat/stills/TGA000664 000000 000000 00000000131 14531534050 021360 0ustar00rootroot000000 000000 progressive=1 f=image2 vcodec=targa an=1 audio_off=1 g=1 bf=0 meta.preset.extension=tga mlt-7.22.0/presets/consumer/avformat/stills/TIFF000664 000000 000000 00000000130 14531534050 021474 0ustar00rootroot000000 000000 progressive=1 f=image2 vcodec=tiff an=1 audio_off=1 g=1 bf=0 meta.preset.extension=tif mlt-7.22.0/presets/consumer/avformat/stills/webp000664 000000 000000 00000000346 14531534050 021712 0ustar00rootroot000000 000000 progressive=1 f=image2 vcodec=libwebp an=1 audio_off=1 g=1 bf=0 qscale=75 preset=default meta.preset.extension=webp meta.preset.name=stills/WebP meta.preset.note=Change preset to photo, picture, drawing, icon, or text as needed. mlt-7.22.0/presets/consumer/avformat/ten_bit/000775 000000 000000 00000000000 14531534050 021141 5ustar00rootroot000000 000000 mlt-7.22.0/presets/consumer/avformat/ten_bit/AV1000664 000000 000000 00000000503 14531534050 021451 0ustar00rootroot000000 000000 f=webm acodec=libopus ar=48000 ab=128k vcodec=libaom-av1 vb=0 crf=45 row-mt=1 tile-columns=2 tile-rows=1 cpu-used=4 strict=experimental pix_fmt=yuv420p10le meta.preset.name=ten_bit/10-bit AV1 WebM meta.preset.extension=webm meta.preset.note=10-bit AV1 video with Opus audio in Matroska container: Just say no to patents mlt-7.22.0/presets/consumer/avformat/ten_bit/DNxHR-HQ000664 000000 000000 00000000434 14531534050 022316 0ustar00rootroot000000 000000 progressive=1 vcodec=dnxhd vprofile=dnxhr_hqx qscale=1 vb=0 g=1 bf=0 acodec=pcm_s24le f=mov movflags=+write_colr+faststart pix_fmt=yuv422p10le meta.preset.extension=mov meta.preset.note=Avid's intermediate codec for resolutions up to UHD/C4K meta.preset.name=ten_bit/10-bit DNxHR HQ mlt-7.22.0/presets/consumer/avformat/ten_bit/FFV1000664 000000 000000 00000000316 14531534050 021566 0ustar00rootroot000000 000000 f=matroska acodec=flac vcodec=ffv1 g=1 bf=0 coder=1 context=1 pix_fmt=yuv444p10le meta.preset.extension=mkv meta.preset.note=FFmpeg video codec 1, 10 bit, Y'CbCr 4:4:4 meta.preset.name=ten_bit/10-bit FFV1 mlt-7.22.0/presets/consumer/avformat/ten_bit/ProRes 422000664 000000 000000 00000000313 14531534050 022563 0ustar00rootroot000000 000000 f=mov acodec=pcm_s16le vcodec=prores_ks vb=0 g=1 bf=0 vprofile=2 vendor=apl0 qscale=1 movflags=+write_colr pix_fmt=yuv422p10le meta.preset.extension=mov meta.preset.note=Designed by Apple in California mlt-7.22.0/presets/consumer/avformat/ten_bit/ProRes 444000664 000000 000000 00000000313 14531534050 022567 0ustar00rootroot000000 000000 f=mov acodec=pcm_s16le vcodec=prores_ks vb=0 g=1 bf=0 vprofile=4 vendor=apl0 qscale=1 movflags=+write_colr pix_fmt=yuv444p10le meta.preset.extension=mov meta.preset.note=Designed by Apple in California mlt-7.22.0/presets/consumer/avformat/ten_bit/ProRes HQ000664 000000 000000 00000000313 14531534050 022564 0ustar00rootroot000000 000000 f=mov acodec=pcm_s16le vcodec=prores_ks vb=0 g=1 bf=0 vprofile=3 vendor=apl0 qscale=1 movflags=+write_colr pix_fmt=yuv422p10le meta.preset.extension=mov meta.preset.note=Designed by Apple in California mlt-7.22.0/presets/consumer/avformat/ten_bit/x264-high10000664 000000 000000 00000000305 14531534050 022643 0ustar00rootroot000000 000000 f=mp4 acodec=aac ab=256k vcodec=libx264 threads=0 preset=medium vprofile=high10 pix_fmt=yuv420p10le movflags=+faststart meta.preset.extension=mp4 meta.preset.name=ten_bit/H.264 High 10 Profile mlt-7.22.0/presets/consumer/avformat/ten_bit/x265-main10000664 000000 000000 00000000304 14531534050 022650 0ustar00rootroot000000 000000 f=mp4 acodec=aac ab=256k vcodec=libx265 threads=0 preset=medium vprofile=main10 pix_fmt=yuv420p10le movflags=+faststart meta.preset.extension=mp4 meta.preset.name=ten_bit/HEVC Main 10 Profile mlt-7.22.0/presets/consumer/avformat/vp9000664 000000 000000 00000000557 14531534050 020165 0ustar00rootroot000000 000000 f=webm acodec=libopus ar=48000 ab=128k vcodec=libvpx-vp9 vb=2M g=120 bf=2 threads=0 rc_lookahead=16 quality=good speed=3 vprofile=0 qmax=51 qmin=4 slices=4 tile-columns=6 frame-parallel=1 auto-alt-ref=1 lag-in-frames=25 row-mt=1 meta.preset.name=WebM VP9 meta.preset.extension=webm meta.preset.note=VP9 video with Opus audio in Matroska container: "Don't be evil" mlt-7.22.0/presets/consumer/avformat/webm000664 000000 000000 00000000472 14531534050 020375 0ustar00rootroot000000 000000 f=webm acodec=vorbis ab=128k vcodec=libvpx g=120 rc_lookahead=16 quality=good speed=0 vprofile=0 qmax=51 qmin=11 slices=4 vb=2M arnr_max_frames=7 arnr_strength=5 arnr_type=3 meta.preset.name=WebM meta.preset.extension=webm meta.preset.note=VP8 video with Ogg Vorbis audio in Matroska container: "Don't be evil" mlt-7.22.0/presets/consumer/avformat/webm-pass1000664 000000 000000 00000000341 14531534050 021415 0ustar00rootroot000000 000000 f=webm an=1 audio_off=1 vcodec=libvpx g=120 rc_lookahead=16 quality=good speed=0 vprofile=0 qmax=51 qmin=11 slices=4 vb=2M maxrate=24M minrate=100k arnr_max_frames=7 arnr_strength=5 arnr_type=3 pass=1 meta.preset.hidden=1 mlt-7.22.0/presets/consumer/avformat/webp000664 000000 000000 00000000354 14531534050 020377 0ustar00rootroot000000 000000 progressive=1 f=webp vcodec=libwebp_anim an=1 audio_off=1 g=1 bf=0 qscale=75 preset=default meta.preset.extension=webp meta.preset.name=WebP Animation meta.preset.note=Change preset to photo, picture, drawing, icon, or text as needed. mlt-7.22.0/presets/consumer/avformat/x264-medium000664 000000 000000 00000000244 14531534050 021421 0ustar00rootroot000000 000000 f=mp4 acodec=aac ab=256k vcodec=libx264 threads=0 preset=medium vprofile=high movflags=+faststart meta.preset.extension=mp4 meta.preset.name=H.264 High Profile mlt-7.22.0/presets/consumer/avformat/x264-medium-baseline000664 000000 000000 00000000327 14531534050 023203 0ustar00rootroot000000 000000 f=mp4 acodec=aac ab=256k vcodec=libx264 threads=0 preset=medium vprofile=baseline coder=0 bf=0 flags2=-wpred-dct8x8 wpredp=0 movflags=+faststart meta.preset.extension=mp4 meta.preset.name=H.264 Baseline Profile mlt-7.22.0/presets/consumer/avformat/x264-medium-main000664 000000 000000 00000000263 14531534050 022344 0ustar00rootroot000000 000000 f=mp4 acodec=aac ab=256k vcodec=libx264 threads=0 preset=medium vprofile=main flags2=-dct8x8 movflags=+faststart meta.preset.extension=mp4 meta.preset.name=H.264 Main Profile mlt-7.22.0/presets/consumer/avformat/x264-medium-pass1000664 000000 000000 00000000430 14531534050 022443 0ustar00rootroot000000 000000 f=mp4 vcodec=libx264 threads=0 preset=medium vprofile=high fastfirstpass=1 partitions=-parti8x8-parti4x4-partp8x8-partb8x8 me_method=dia subq=2 refs=1 trellis=0 flags2=+bpyramid-mixed_refs+wpred-dct8x8+fastpskip pass=1 an=1 audio_off=1 movflags=+faststart meta.preset.hidden=1 mlt-7.22.0/presets/consumer/avformat/x265-medium000664 000000 000000 00000000225 14531534050 021421 0ustar00rootroot000000 000000 f=mp4 acodec=aac ab=256k vcodec=libx265 threads=0 preset=medium movflags=+faststart meta.preset.extension=mp4 meta.preset.name=HEVC Main Profile mlt-7.22.0/presets/consumer/avformat/x265-medium-pass1000664 000000 000000 00000000175 14531534050 022452 0ustar00rootroot000000 000000 f=mp4 vcodec=libx265 threads=0 preset=medium x265-params=pass=1 an=1 audio_off=1 movflags=+faststart meta.preset.hidden=1 mlt-7.22.0/presets/filter/000775 000000 000000 00000000000 14531534050 015330 5ustar00rootroot000000 000000 mlt-7.22.0/presets/filter/brightness/000775 000000 000000 00000000000 14531534050 017500 5ustar00rootroot000000 000000 mlt-7.22.0/presets/filter/brightness/from_black000664 000000 000000 00000000032 14531534050 021515 0ustar00rootroot000000 000000 out=50 in=0 start=0 end=1 mlt-7.22.0/presets/filter/brightness/to_black000664 000000 000000 00000000032 14531534050 021174 0ustar00rootroot000000 000000 out=50 in=0 start=1 end=0 mlt-7.22.0/presets/filter/movit.blur/000775 000000 000000 00000000000 14531534050 017431 5ustar00rootroot000000 000000 mlt-7.22.0/presets/filter/movit.blur/blur_in000664 000000 000000 00000000031 14531534050 021000 0ustar00rootroot000000 000000 radius=0=10.0; :1.0=0.0; mlt-7.22.0/presets/filter/movit.blur/blur_in_out000664 000000 000000 00000000053 14531534050 021673 0ustar00rootroot000000 000000 radius=0=10.0; :1.0=0; :-1.0=0.0; -1=10.0; mlt-7.22.0/presets/filter/movit.blur/blur_out000664 000000 000000 00000000040 14531534050 021201 0ustar00rootroot000000 000000 radius=0=0; :-1.0=0.0; -1=10.0; mlt-7.22.0/presets/filter/movit.opacity/000775 000000 000000 00000000000 14531534050 020135 5ustar00rootroot000000 000000 mlt-7.22.0/presets/filter/movit.opacity/fade_in000664 000000 000000 00000000035 14531534050 021443 0ustar00rootroot000000 000000 opacity=0=0;:2.0=1.0 alpha=1 mlt-7.22.0/presets/filter/movit.opacity/fade_in_out000664 000000 000000 00000000056 14531534050 022335 0ustar00rootroot000000 000000 opacity=0=0;:2.0=1.0; :-2.0=1.0; -1=0 alpha=1 mlt-7.22.0/presets/filter/movit.opacity/fade_out000664 000000 000000 00000000040 14531534050 021640 0ustar00rootroot000000 000000 opacity=:-2.0=1.0; -1=0 alpha=1 mlt-7.22.0/presets/filter/volume/000775 000000 000000 00000000000 14531534050 016637 5ustar00rootroot000000 000000 mlt-7.22.0/presets/filter/volume/fade_in000664 000000 000000 00000000031 14531534050 020141 0ustar00rootroot000000 000000 out=50 in=0 gain=0 end=1 mlt-7.22.0/presets/filter/volume/fade_out000664 000000 000000 00000000031 14531534050 020342 0ustar00rootroot000000 000000 out=50 in=0 gain=1 end=0 mlt-7.22.0/profiles/000775 000000 000000 00000000000 14531534050 014201 5ustar00rootroot000000 000000 mlt-7.22.0/profiles/atsc_1080i_50000664 000000 000000 00000000306 14531534050 016202 0ustar00rootroot000000 000000 description=HD 1080i 25 fps frame_rate_num=25 frame_rate_den=1 width=1920 height=1080 progressive=0 sample_aspect_num=1 sample_aspect_den=1 display_aspect_num=16 display_aspect_den=9 colorspace=709 mlt-7.22.0/profiles/atsc_1080i_5994000664 000000 000000 00000000317 14531534050 016372 0ustar00rootroot000000 000000 description=HD 1080i 29.97 fps frame_rate_num=30000 frame_rate_den=1001 width=1920 height=1080 progressive=0 sample_aspect_num=1 sample_aspect_den=1 display_aspect_num=16 display_aspect_den=9 colorspace=709 mlt-7.22.0/profiles/atsc_1080i_60000664 000000 000000 00000000306 14531534050 016203 0ustar00rootroot000000 000000 description=HD 1080i 30 fps frame_rate_num=30 frame_rate_den=1 width=1920 height=1080 progressive=0 sample_aspect_num=1 sample_aspect_den=1 display_aspect_num=16 display_aspect_den=9 colorspace=709 mlt-7.22.0/profiles/atsc_1080p_2398000664 000000 000000 00000000317 14531534050 016374 0ustar00rootroot000000 000000 description=HD 1080p 23.98 fps frame_rate_num=24000 frame_rate_den=1001 width=1920 height=1080 progressive=1 sample_aspect_num=1 sample_aspect_den=1 display_aspect_num=16 display_aspect_den=9 colorspace=709 mlt-7.22.0/profiles/atsc_1080p_24000664 000000 000000 00000000306 14531534050 016212 0ustar00rootroot000000 000000 description=HD 1080p 24 fps frame_rate_num=24 frame_rate_den=1 width=1920 height=1080 progressive=1 sample_aspect_num=1 sample_aspect_den=1 display_aspect_num=16 display_aspect_den=9 colorspace=709 mlt-7.22.0/profiles/atsc_1080p_25000664 000000 000000 00000000306 14531534050 016213 0ustar00rootroot000000 000000 description=HD 1080p 25 fps frame_rate_num=25 frame_rate_den=1 width=1920 height=1080 progressive=1 sample_aspect_num=1 sample_aspect_den=1 display_aspect_num=16 display_aspect_den=9 colorspace=709 mlt-7.22.0/profiles/atsc_1080p_2997000664 000000 000000 00000000317 14531534050 016401 0ustar00rootroot000000 000000 description=HD 1080p 29.97 fps frame_rate_num=30000 frame_rate_den=1001 width=1920 height=1080 progressive=1 sample_aspect_num=1 sample_aspect_den=1 display_aspect_num=16 display_aspect_den=9 colorspace=709 mlt-7.22.0/profiles/atsc_1080p_30000664 000000 000000 00000000306 14531534050 016207 0ustar00rootroot000000 000000 description=HD 1080p 30 fps frame_rate_num=30 frame_rate_den=1 width=1920 height=1080 progressive=1 sample_aspect_num=1 sample_aspect_den=1 display_aspect_num=16 display_aspect_den=9 colorspace=709 mlt-7.22.0/profiles/atsc_1080p_50000664 000000 000000 00000000306 14531534050 016211 0ustar00rootroot000000 000000 description=HD 1080p 50 fps frame_rate_num=50 frame_rate_den=1 width=1920 height=1080 progressive=1 sample_aspect_num=1 sample_aspect_den=1 display_aspect_num=16 display_aspect_den=9 colorspace=709 mlt-7.22.0/profiles/atsc_1080p_5994000664 000000 000000 00000000317 14531534050 016401 0ustar00rootroot000000 000000 description=HD 1080p 59.94 fps frame_rate_num=60000 frame_rate_den=1001 width=1920 height=1080 progressive=1 sample_aspect_num=1 sample_aspect_den=1 display_aspect_num=16 display_aspect_den=9 colorspace=709 mlt-7.22.0/profiles/atsc_1080p_60000664 000000 000000 00000000306 14531534050 016212 0ustar00rootroot000000 000000 description=HD 1080p 60 fps frame_rate_num=60 frame_rate_den=1 width=1920 height=1080 progressive=1 sample_aspect_num=1 sample_aspect_den=1 display_aspect_num=16 display_aspect_den=9 colorspace=709 mlt-7.22.0/profiles/atsc_720p_2398000664 000000 000000 00000000315 14531534050 016312 0ustar00rootroot000000 000000 description=HD 720p 23.98 fps frame_rate_num=24000 frame_rate_den=1001 width=1280 height=720 progressive=1 sample_aspect_num=1 sample_aspect_den=1 display_aspect_num=16 display_aspect_den=9 colorspace=709 mlt-7.22.0/profiles/atsc_720p_24000664 000000 000000 00000000304 14531534050 016130 0ustar00rootroot000000 000000 description=HD 720p 24 fps frame_rate_num=24 frame_rate_den=1 width=1280 height=720 progressive=1 sample_aspect_num=1 sample_aspect_den=1 display_aspect_num=16 display_aspect_den=9 colorspace=709 mlt-7.22.0/profiles/atsc_720p_25000664 000000 000000 00000000304 14531534050 016131 0ustar00rootroot000000 000000 description=HD 720p 25 fps frame_rate_num=25 frame_rate_den=1 width=1280 height=720 progressive=1 sample_aspect_num=1 sample_aspect_den=1 display_aspect_num=16 display_aspect_den=9 colorspace=709 mlt-7.22.0/profiles/atsc_720p_2997000664 000000 000000 00000000315 14531534050 016317 0ustar00rootroot000000 000000 description=HD 720p 29.97 fps frame_rate_num=30000 frame_rate_den=1001 width=1280 height=720 progressive=1 sample_aspect_num=1 sample_aspect_den=1 display_aspect_num=16 display_aspect_den=9 colorspace=709 mlt-7.22.0/profiles/atsc_720p_30000664 000000 000000 00000000304 14531534050 016125 0ustar00rootroot000000 000000 description=HD 720p 30 fps frame_rate_num=30 frame_rate_den=1 width=1280 height=720 progressive=1 sample_aspect_num=1 sample_aspect_den=1 display_aspect_num=16 display_aspect_den=9 colorspace=709 mlt-7.22.0/profiles/atsc_720p_50000664 000000 000000 00000000304 14531534050 016127 0ustar00rootroot000000 000000 description=HD 720p 50 fps frame_rate_num=50 frame_rate_den=1 width=1280 height=720 progressive=1 sample_aspect_num=1 sample_aspect_den=1 display_aspect_num=16 display_aspect_den=9 colorspace=709 mlt-7.22.0/profiles/atsc_720p_5994000664 000000 000000 00000000315 14531534050 016317 0ustar00rootroot000000 000000 description=HD 720p 59.94 fps frame_rate_num=60000 frame_rate_den=1001 width=1280 height=720 progressive=1 sample_aspect_num=1 sample_aspect_den=1 display_aspect_num=16 display_aspect_den=9 colorspace=709 mlt-7.22.0/profiles/atsc_720p_60000664 000000 000000 00000000304 14531534050 016130 0ustar00rootroot000000 000000 description=HD 720p 60 fps frame_rate_num=60 frame_rate_den=1 width=1280 height=720 progressive=1 sample_aspect_num=1 sample_aspect_den=1 display_aspect_num=16 display_aspect_den=9 colorspace=709 mlt-7.22.0/profiles/cif_15000664 000000 000000 00000000300 14531534050 015163 0ustar00rootroot000000 000000 description=CIF 15 fps frame_rate_num=15 frame_rate_den=1 width=352 height=288 progressive=1 sample_aspect_num=59 sample_aspect_den=54 display_aspect_num=4 display_aspect_den=3 colorspace=601 mlt-7.22.0/profiles/cif_ntsc000664 000000 000000 00000000304 14531534050 015711 0ustar00rootroot000000 000000 description=CIF NTSC frame_rate_num=30000 frame_rate_den=1001 width=352 height=288 progressive=1 sample_aspect_num=10 sample_aspect_den=11 display_aspect_num=4 display_aspect_den=3 colorspace=601 mlt-7.22.0/profiles/cif_pal000664 000000 000000 00000000275 14531534050 015525 0ustar00rootroot000000 000000 description=CIF PAL frame_rate_num=25 frame_rate_den=1 width=352 height=288 progressive=1 sample_aspect_num=59 sample_aspect_den=54 display_aspect_num=4 display_aspect_den=3 colorspace=601 mlt-7.22.0/profiles/cvd_ntsc000664 000000 000000 00000000304 14531534050 015724 0ustar00rootroot000000 000000 description=CVD NTSC frame_rate_num=30000 frame_rate_den=1001 width=352 height=480 progressive=0 sample_aspect_num=20 sample_aspect_den=11 display_aspect_num=4 display_aspect_den=3 colorspace=601 mlt-7.22.0/profiles/cvd_pal000664 000000 000000 00000000275 14531534050 015540 0ustar00rootroot000000 000000 description=CVD PAL frame_rate_num=25 frame_rate_den=1 width=352 height=576 progressive=0 sample_aspect_num=59 sample_aspect_den=27 display_aspect_num=4 display_aspect_den=3 colorspace=601 mlt-7.22.0/profiles/dv_ntsc000664 000000 000000 00000000305 14531534050 015562 0ustar00rootroot000000 000000 description=DV/DVD NTSC frame_rate_num=30000 frame_rate_den=1001 width=720 height=480 progressive=0 sample_aspect_num=8 sample_aspect_den=9 display_aspect_num=4 display_aspect_den=3 colorspace=601 mlt-7.22.0/profiles/dv_ntsc_wide000664 000000 000000 00000000323 14531534050 016572 0ustar00rootroot000000 000000 description=DV/DVD Widescreen NTSC frame_rate_num=30000 frame_rate_den=1001 width=720 height=480 progressive=0 sample_aspect_num=32 sample_aspect_den=27 display_aspect_num=16 display_aspect_den=9 colorspace=601 mlt-7.22.0/profiles/dv_pal000664 000000 000000 00000000300 14531534050 015362 0ustar00rootroot000000 000000 description=DV/DVD PAL frame_rate_num=25 frame_rate_den=1 width=720 height=576 progressive=0 sample_aspect_num=16 sample_aspect_den=15 display_aspect_num=4 display_aspect_den=3 colorspace=601 mlt-7.22.0/profiles/dv_pal_wide000664 000000 000000 00000000314 14531534050 016377 0ustar00rootroot000000 000000 description=DV/DVD Widescreen PAL frame_rate_num=25 frame_rate_den=1 width=720 height=576 progressive=0 sample_aspect_num=64 sample_aspect_den=45 display_aspect_num=16 display_aspect_den=9 colorspace=601 mlt-7.22.0/profiles/hdv_1080_25p000664 000000 000000 00000000314 14531534050 016041 0ustar00rootroot000000 000000 description=HDV 1440x1080p 25 fps frame_rate_num=25 frame_rate_den=1 width=1440 height=1080 progressive=1 sample_aspect_num=4 sample_aspect_den=3 display_aspect_num=16 display_aspect_den=9 colorspace=709 mlt-7.22.0/profiles/hdv_1080_30p000664 000000 000000 00000000325 14531534050 016037 0ustar00rootroot000000 000000 description=HDV 1440x1080p 29.97 fps frame_rate_num=30000 frame_rate_den=1001 width=1440 height=1080 progressive=1 sample_aspect_num=4 sample_aspect_den=3 display_aspect_num=16 display_aspect_den=9 colorspace=709 mlt-7.22.0/profiles/hdv_1080_50i000664 000000 000000 00000000314 14531534050 016030 0ustar00rootroot000000 000000 description=HDV 1440x1080i 25 fps frame_rate_num=25 frame_rate_den=1 width=1440 height=1080 progressive=0 sample_aspect_num=4 sample_aspect_den=3 display_aspect_num=16 display_aspect_den=9 colorspace=709 mlt-7.22.0/profiles/hdv_1080_60i000664 000000 000000 00000000325 14531534050 016033 0ustar00rootroot000000 000000 description=HDV 1440x1080i 29.97 fps frame_rate_num=30000 frame_rate_den=1001 width=1440 height=1080 progressive=0 sample_aspect_num=4 sample_aspect_den=3 display_aspect_num=16 display_aspect_den=9 colorspace=709 mlt-7.22.0/profiles/hdv_720_25p000664 000000 000000 00000000304 14531534050 015760 0ustar00rootroot000000 000000 description=HD 720p 25 fps frame_rate_num=25 frame_rate_den=1 width=1280 height=720 progressive=1 sample_aspect_num=1 sample_aspect_den=1 display_aspect_num=16 display_aspect_den=9 colorspace=709 mlt-7.22.0/profiles/hdv_720_30p000664 000000 000000 00000000315 14531534050 015756 0ustar00rootroot000000 000000 description=HD 720p 29.97 fps frame_rate_num=30000 frame_rate_den=1001 width=1280 height=720 progressive=1 sample_aspect_num=1 sample_aspect_den=1 display_aspect_num=16 display_aspect_den=9 colorspace=709 mlt-7.22.0/profiles/hdv_720_50p000664 000000 000000 00000000304 14531534050 015756 0ustar00rootroot000000 000000 description=HD 720p 50 fps frame_rate_num=50 frame_rate_den=1 width=1280 height=720 progressive=1 sample_aspect_num=1 sample_aspect_den=1 display_aspect_num=16 display_aspect_den=9 colorspace=709 mlt-7.22.0/profiles/hdv_720_60p000664 000000 000000 00000000315 14531534050 015761 0ustar00rootroot000000 000000 description=HD 720p 59.94 fps frame_rate_num=60000 frame_rate_den=1001 width=1280 height=720 progressive=1 sample_aspect_num=1 sample_aspect_den=1 display_aspect_num=16 display_aspect_den=9 colorspace=709 mlt-7.22.0/profiles/qcif_15000664 000000 000000 00000000301 14531534050 015345 0ustar00rootroot000000 000000 description=QCIF 15 fps frame_rate_num=15 frame_rate_den=1 width=176 height=144 progressive=1 sample_aspect_num=59 sample_aspect_den=54 display_aspect_num=4 display_aspect_den=3 colorspace=601 mlt-7.22.0/profiles/qcif_ntsc000664 000000 000000 00000000305 14531534050 016073 0ustar00rootroot000000 000000 description=QCIF NTSC frame_rate_num=30000 frame_rate_den=1001 width=176 height=144 progressive=1 sample_aspect_num=10 sample_aspect_den=11 display_aspect_num=4 display_aspect_den=3 colorspace=601 mlt-7.22.0/profiles/qcif_pal000664 000000 000000 00000000276 14531534050 015707 0ustar00rootroot000000 000000 description=QCIF PAL frame_rate_num=25 frame_rate_den=1 width=176 height=144 progressive=1 sample_aspect_num=59 sample_aspect_den=54 display_aspect_num=4 display_aspect_den=3 colorspace=601 mlt-7.22.0/profiles/qhd_1440p_2398000664 000000 000000 00000000325 14531534050 016215 0ustar00rootroot000000 000000 description=2.5K QHD 1440p 23.98 fps frame_rate_num=24000 frame_rate_den=1001 width=2560 height=1440 progressive=1 sample_aspect_num=1 sample_aspect_den=1 display_aspect_num=16 display_aspect_den=9 colorspace=709 mlt-7.22.0/profiles/qhd_1440p_24000664 000000 000000 00000000314 14531534050 016033 0ustar00rootroot000000 000000 description=2.5K QHD 1440p 24 fps frame_rate_num=24 frame_rate_den=1 width=2560 height=1440 progressive=1 sample_aspect_num=1 sample_aspect_den=1 display_aspect_num=16 display_aspect_den=9 colorspace=709 mlt-7.22.0/profiles/qhd_1440p_25000664 000000 000000 00000000314 14531534050 016034 0ustar00rootroot000000 000000 description=2.5K QHD 1440p 25 fps frame_rate_num=25 frame_rate_den=1 width=2560 height=1440 progressive=1 sample_aspect_num=1 sample_aspect_den=1 display_aspect_num=16 display_aspect_den=9 colorspace=709 mlt-7.22.0/profiles/qhd_1440p_2997000664 000000 000000 00000000325 14531534050 016222 0ustar00rootroot000000 000000 description=2.5K QHD 1440p 29.97 fps frame_rate_num=30000 frame_rate_den=1001 width=2560 height=1440 progressive=1 sample_aspect_num=1 sample_aspect_den=1 display_aspect_num=16 display_aspect_den=9 colorspace=709 mlt-7.22.0/profiles/qhd_1440p_30000664 000000 000000 00000000314 14531534050 016030 0ustar00rootroot000000 000000 description=2.5K QHD 1440p 30 fps frame_rate_num=30 frame_rate_den=1 width=2560 height=1440 progressive=1 sample_aspect_num=1 sample_aspect_den=1 display_aspect_num=16 display_aspect_den=9 colorspace=709 mlt-7.22.0/profiles/qhd_1440p_50000664 000000 000000 00000000314 14531534050 016032 0ustar00rootroot000000 000000 description=2.5K QHD 1440p 50 fps frame_rate_num=50 frame_rate_den=1 width=2560 height=1440 progressive=1 sample_aspect_num=1 sample_aspect_den=1 display_aspect_num=16 display_aspect_den=9 colorspace=709 mlt-7.22.0/profiles/qhd_1440p_5994000664 000000 000000 00000000325 14531534050 016222 0ustar00rootroot000000 000000 description=2.5K QHD 1440p 59.94 fps frame_rate_num=60000 frame_rate_den=1001 width=2560 height=1440 progressive=1 sample_aspect_num=1 sample_aspect_den=1 display_aspect_num=16 display_aspect_den=9 colorspace=709 mlt-7.22.0/profiles/qhd_1440p_60000664 000000 000000 00000000314 14531534050 016033 0ustar00rootroot000000 000000 description=2.5K QHD 1440p 60 fps frame_rate_num=60 frame_rate_den=1 width=2560 height=1440 progressive=1 sample_aspect_num=1 sample_aspect_den=1 display_aspect_num=16 display_aspect_den=9 colorspace=709 mlt-7.22.0/profiles/quarter_15000664 000000 000000 00000000277 14531534050 016122 0ustar00rootroot000000 000000 description=QVGA 15 fps frame_rate_num=15 frame_rate_den=1 width=320 height=240 progressive=1 sample_aspect_num=1 sample_aspect_den=1 display_aspect_num=4 display_aspect_den=3 colorspace=601 mlt-7.22.0/profiles/quarter_ntsc000664 000000 000000 00000000310 14531534050 016630 0ustar00rootroot000000 000000 description=QVGA 29.97 fps frame_rate_num=30000 frame_rate_den=1001 width=320 height=240 progressive=1 sample_aspect_num=1 sample_aspect_den=1 display_aspect_num=4 display_aspect_den=3 colorspace=601 mlt-7.22.0/profiles/quarter_ntsc_wide000664 000000 000000 00000000324 14531534050 017645 0ustar00rootroot000000 000000 description=QVGA Widescreen 29.97 fps frame_rate_num=30000 frame_rate_den=1001 width=426 height=240 progressive=1 sample_aspect_num=1 sample_aspect_den=1 display_aspect_num=16 display_aspect_den=9 colorspace=601 mlt-7.22.0/profiles/quarter_pal000664 000000 000000 00000000303 14531534050 016437 0ustar00rootroot000000 000000 description=384x288 4:3 PAL frame_rate_num=25 frame_rate_den=1 width=384 height=288 progressive=1 sample_aspect_num=1 sample_aspect_den=1 display_aspect_num=4 display_aspect_den=3 colorspace=601 mlt-7.22.0/profiles/quarter_pal_wide000664 000000 000000 00000000305 14531534050 017451 0ustar00rootroot000000 000000 description=512x288 16:9 PAL frame_rate_num=25 frame_rate_den=1 width=512 height=288 progressive=1 sample_aspect_num=1 sample_aspect_den=1 display_aspect_num=16 display_aspect_den=9 colorspace=601 mlt-7.22.0/profiles/sdi_486i_5994000664 000000 000000 00000000310 14531534050 016141 0ustar00rootroot000000 000000 description=NTSC 29.97 fps frame_rate_num=30000 frame_rate_den=1001 width=720 height=486 progressive=0 sample_aspect_num=8 sample_aspect_den=9 display_aspect_num=4 display_aspect_den=3 colorspace=601 mlt-7.22.0/profiles/sdi_486p_2398000664 000000 000000 00000000310 14531534050 016143 0ustar00rootroot000000 000000 description=NTSC 23.98 fps frame_rate_num=24000 frame_rate_den=1001 width=720 height=486 progressive=1 sample_aspect_num=8 sample_aspect_den=9 display_aspect_num=4 display_aspect_den=3 colorspace=601 mlt-7.22.0/profiles/square_1080p_30000664 000000 000000 00000000311 14531534050 016551 0ustar00rootroot000000 000000 description=Square 1080p 30 fps frame_rate_num=30 frame_rate_den=1 width=1080 height=1080 progressive=1 sample_aspect_num=1 sample_aspect_den=1 display_aspect_num=1 display_aspect_den=1 colorspace=709 mlt-7.22.0/profiles/square_1080p_60000664 000000 000000 00000000311 14531534050 016554 0ustar00rootroot000000 000000 description=Square 1080p 60 fps frame_rate_num=60 frame_rate_den=1 width=1080 height=1080 progressive=1 sample_aspect_num=1 sample_aspect_den=1 display_aspect_num=1 display_aspect_den=1 colorspace=709 mlt-7.22.0/profiles/square_ntsc000664 000000 000000 00000000302 14531534050 016446 0ustar00rootroot000000 000000 description=VGA NTSC frame_rate_num=30000 frame_rate_den=1001 width=640 height=480 progressive=1 sample_aspect_num=1 sample_aspect_den=1 display_aspect_num=4 display_aspect_den=3 colorspace=601 mlt-7.22.0/profiles/square_ntsc_wide000664 000000 000000 00000000316 14531534050 017463 0ustar00rootroot000000 000000 description=VGA Widescreen NTSC frame_rate_num=30000 frame_rate_den=1001 width=854 height=480 progressive=1 sample_aspect_num=1 sample_aspect_den=1 display_aspect_num=16 display_aspect_den=9 colorspace=601 mlt-7.22.0/profiles/square_pal000664 000000 000000 00000000303 14531534050 016254 0ustar00rootroot000000 000000 description=768x576 4:3 PAL frame_rate_num=25 frame_rate_den=1 width=768 height=576 progressive=1 sample_aspect_num=1 sample_aspect_den=1 display_aspect_num=4 display_aspect_den=3 colorspace=601 mlt-7.22.0/profiles/square_pal_wide000664 000000 000000 00000000307 14531534050 017270 0ustar00rootroot000000 000000 description=1024x576 16:9 PAL frame_rate_num=25 frame_rate_den=1 width=1024 height=576 progressive=1 sample_aspect_num=1 sample_aspect_den=1 display_aspect_num=16 display_aspect_den=9 colorspace=601 mlt-7.22.0/profiles/svcd_ntsc000664 000000 000000 00000000305 14531534050 016110 0ustar00rootroot000000 000000 description=SVCD NTSC frame_rate_num=30000 frame_rate_den=1001 width=480 height=480 progressive=0 sample_aspect_num=15 sample_aspect_den=11 display_aspect_num=4 display_aspect_den=3 colorspace=601 mlt-7.22.0/profiles/svcd_ntsc_wide000664 000000 000000 00000000321 14531534050 017116 0ustar00rootroot000000 000000 description=SVCD Widescreen NTSC frame_rate_num=30000 frame_rate_den=1001 width=480 height=480 progressive=0 sample_aspect_num=20 sample_aspect_den=11 display_aspect_num=16 display_aspect_den=9 colorspace=601 mlt-7.22.0/profiles/svcd_pal000664 000000 000000 00000000276 14531534050 015724 0ustar00rootroot000000 000000 description=SVCD PAL frame_rate_num=25 frame_rate_den=1 width=480 height=576 progressive=0 sample_aspect_num=59 sample_aspect_den=36 display_aspect_num=4 display_aspect_den=3 colorspace=601 mlt-7.22.0/profiles/svcd_pal_wide000664 000000 000000 00000000312 14531534050 016723 0ustar00rootroot000000 000000 description=SVCD Widescreen PAL frame_rate_num=25 frame_rate_den=1 width=480 height=576 progressive=0 sample_aspect_num=59 sample_aspect_den=27 display_aspect_num=16 display_aspect_den=9 colorspace=601 mlt-7.22.0/profiles/uhd_2160p_2398000664 000000 000000 00000000323 14531534050 016217 0ustar00rootroot000000 000000 description=4K UHD 2160p 23.98 fps frame_rate_num=24000 frame_rate_den=1001 width=3840 height=2160 progressive=1 sample_aspect_num=1 sample_aspect_den=1 display_aspect_num=16 display_aspect_den=9 colorspace=709 mlt-7.22.0/profiles/uhd_2160p_24000664 000000 000000 00000000312 14531534050 016035 0ustar00rootroot000000 000000 description=4K UHD 2160p 24 fps frame_rate_num=24 frame_rate_den=1 width=3840 height=2160 progressive=1 sample_aspect_num=1 sample_aspect_den=1 display_aspect_num=16 display_aspect_den=9 colorspace=709 mlt-7.22.0/profiles/uhd_2160p_25000664 000000 000000 00000000312 14531534050 016036 0ustar00rootroot000000 000000 description=4K UHD 2160p 25 fps frame_rate_num=25 frame_rate_den=1 width=3840 height=2160 progressive=1 sample_aspect_num=1 sample_aspect_den=1 display_aspect_num=16 display_aspect_den=9 colorspace=709 mlt-7.22.0/profiles/uhd_2160p_2997000664 000000 000000 00000000323 14531534050 016224 0ustar00rootroot000000 000000 description=4K UHD 2160p 29.97 fps frame_rate_num=30000 frame_rate_den=1001 width=3840 height=2160 progressive=1 sample_aspect_num=1 sample_aspect_den=1 display_aspect_num=16 display_aspect_den=9 colorspace=709 mlt-7.22.0/profiles/uhd_2160p_30000664 000000 000000 00000000312 14531534050 016032 0ustar00rootroot000000 000000 description=4K UHD 2160p 30 fps frame_rate_num=30 frame_rate_den=1 width=3840 height=2160 progressive=1 sample_aspect_num=1 sample_aspect_den=1 display_aspect_num=16 display_aspect_den=9 colorspace=709 mlt-7.22.0/profiles/uhd_2160p_50000664 000000 000000 00000000312 14531534050 016034 0ustar00rootroot000000 000000 description=4K UHD 2160p 50 fps frame_rate_num=50 frame_rate_den=1 width=3840 height=2160 progressive=1 sample_aspect_num=1 sample_aspect_den=1 display_aspect_num=16 display_aspect_den=9 colorspace=709 mlt-7.22.0/profiles/uhd_2160p_5994000664 000000 000000 00000000323 14531534050 016224 0ustar00rootroot000000 000000 description=4K UHD 2160p 59.94 fps frame_rate_num=60000 frame_rate_den=1001 width=3840 height=2160 progressive=1 sample_aspect_num=1 sample_aspect_den=1 display_aspect_num=16 display_aspect_den=9 colorspace=709 mlt-7.22.0/profiles/uhd_2160p_60000664 000000 000000 00000000312 14531534050 016035 0ustar00rootroot000000 000000 description=4K UHD 2160p 60 fps frame_rate_num=60 frame_rate_den=1 width=3840 height=2160 progressive=1 sample_aspect_num=1 sample_aspect_den=1 display_aspect_num=16 display_aspect_den=9 colorspace=709 mlt-7.22.0/profiles/vcd_ntsc000664 000000 000000 00000000304 14531534050 015724 0ustar00rootroot000000 000000 description=VCD NTSC frame_rate_num=30000 frame_rate_den=1001 width=352 height=240 progressive=1 sample_aspect_num=10 sample_aspect_den=11 display_aspect_num=4 display_aspect_den=3 colorspace=601 mlt-7.22.0/profiles/vcd_pal000664 000000 000000 00000000275 14531534050 015540 0ustar00rootroot000000 000000 description=VCD PAL frame_rate_num=25 frame_rate_den=1 width=352 height=288 progressive=1 sample_aspect_num=59 sample_aspect_den=54 display_aspect_num=4 display_aspect_den=3 colorspace=601 mlt-7.22.0/profiles/vertical_hd_30000664 000000 000000 00000000311 14531534050 016705 0ustar00rootroot000000 000000 description=Vertical HD 30 fps frame_rate_num=30 frame_rate_den=1 width=1080 height=1920 progressive=1 sample_aspect_num=1 sample_aspect_den=1 display_aspect_num=9 display_aspect_den=16 colorspace=709 mlt-7.22.0/profiles/vertical_hd_60000664 000000 000000 00000000311 14531534050 016710 0ustar00rootroot000000 000000 description=Vertical HD 60 fps frame_rate_num=60 frame_rate_den=1 width=1080 height=1920 progressive=1 sample_aspect_num=1 sample_aspect_den=1 display_aspect_num=9 display_aspect_den=16 colorspace=709 mlt-7.22.0/setenv000664 000000 000000 00000000664 14531534050 013613 0ustar00rootroot000000 000000 # Environment variable settings to allow execution without install export MLT_REPOSITORY="`pwd`/out/lib/mlt" export MLT_DATA="`pwd`/src/modules" export MLT_PROFILES_PATH="`pwd`/profiles" export MLT_PRESETS_PATH="`pwd`/presets" if [ "$(uname -s)" = "Darwin" ]; then export DYLD_LIBRARY_PATH="`pwd`/out/lib:$DYLD_LIBRARY_PATH" else export LD_LIBRARY_PATH="`pwd`/out/lib:$LD_LIBRARY_PATH" fi export PATH="`pwd`/out/bin:$PATH" mlt-7.22.0/src/000775 000000 000000 00000000000 14531534050 013145 5ustar00rootroot000000 000000 mlt-7.22.0/src/CMakeLists.txt000664 000000 000000 00000000313 14531534050 015702 0ustar00rootroot000000 000000 add_subdirectory(framework) add_subdirectory(melt) add_subdirectory(mlt++) add_subdirectory(modules) if(SWIG_FOUND) add_subdirectory(swig) endif() if(BUILD_TESTING) add_subdirectory(tests) endif() mlt-7.22.0/src/examples/000775 000000 000000 00000000000 14531534050 014763 5ustar00rootroot000000 000000 mlt-7.22.0/src/examples/CMakeLists.txt000664 000000 000000 00000000665 14531534050 017532 0ustar00rootroot000000 000000 cmake_minimum_required(VERSION 3.14) project(MLT-examples) option(WITH_PKGCONFIG "Use pkg-config to find MLT" OFF) add_executable(play play.cpp) if(WITH_PKGCONFIG) find_package(PkgConfig REQUIRED) pkg_check_modules(mlt++ REQUIRED IMPORTED_TARGET mlt++-7) target_link_libraries(play PRIVATE PkgConfig::mlt++) else() find_package(Mlt7 REQUIRED COMPONENTS avformat sdl) target_link_libraries(play PRIVATE Mlt7::mlt++) endif() mlt-7.22.0/src/examples/play.cpp000664 000000 000000 00000001135 14531534050 016434 0ustar00rootroot000000 000000 #include using namespace Mlt; void play(const char *filename) { Profile profile; // defaults to dv_pal Producer producer(profile, filename); Consumer consumer(profile); // defaults to sdl // Prevent scaling to the profile size. // Let the sdl consumer do all scaling. consumer.set("rescale", "none"); // Automatically exit at end of file. consumer.set("terminate_on_pause", 1); consumer.connect(producer); consumer.run(); consumer.stop(); } int main(int, char **argv) { Factory::init(); play(argv[1]); Factory::close(); return 0; } mlt-7.22.0/src/framework/000775 000000 000000 00000000000 14531534050 015142 5ustar00rootroot000000 000000 mlt-7.22.0/src/framework/CMakeLists.txt000664 000000 000000 00000006725 14531534050 017714 0ustar00rootroot000000 000000 set(MLT_PUBLIC_HEADERS mlt.h mlt_animation.h mlt_audio.h mlt_cache.h mlt_chain.h mlt_consumer.h mlt_deque.h mlt_events.h mlt_factory.h mlt_field.h mlt_filter.h mlt_frame.h mlt_image.h mlt_link.h mlt_log.h mlt_luma_map.h mlt_multitrack.h mlt_parser.h mlt_playlist.h mlt_pool.h mlt_producer.h mlt_profile.h mlt_properties.h mlt_property.h mlt_repository.h mlt_service.h mlt_slices.h mlt_tokeniser.h mlt_tractor.h mlt_transition.h mlt_types.h mlt_version.h ) add_library(mlt SHARED mlt_animation.c mlt_audio.c mlt_cache.c mlt_chain.c mlt_consumer.c mlt_deque.c mlt_events.c mlt_factory.c mlt_field.c mlt_filter.c mlt_frame.c mlt_image.c mlt_link.c mlt_log.c mlt_luma_map.c mlt_multitrack.c mlt_parser.c mlt_playlist.c mlt_pool.c mlt_producer.c mlt_profile.c mlt_properties.c mlt_property.c mlt_repository.c mlt_service.c mlt_slices.c mlt_tokeniser.c mlt_tractor.c mlt_transition.c mlt_types.c mlt_version.c ) add_custom_target("Other_mlt_Files" SOURCES chain_normalizers.ini metaschema.yaml mlt-framework.pc.in mlt.vers ) add_library(Mlt${MLT_VERSION_MAJOR}::mlt ALIAS mlt) target_sources(mlt PRIVATE ${MLT_PUBLIC_HEADERS}) target_compile_options(mlt PRIVATE ${MLT_COMPILE_OPTIONS}) target_link_libraries(mlt PRIVATE m Threads::Threads ${CMAKE_DL_LIBS}) target_include_directories(mlt PUBLIC $ $ ) set_target_properties(mlt PROPERTIES VERSION ${MLT_VERSION} SOVERSION ${MLT_VERSION_MAJOR} OUTPUT_NAME mlt-${MLT_VERSION_MAJOR} PUBLIC_HEADER "${MLT_PUBLIC_HEADERS}" ) if(WIN32) if(MINGW) install(FILES "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/libmlt-${MLT_VERSION_MAJOR}.dll" DESTINATION ${CMAKE_INSTALL_LIBDIR} RENAME libmlt.dll ) target_link_options(mlt PRIVATE -Wl,--output-def,libmlt.def) install(FILES "${CMAKE_BINARY_DIR}/libmlt.def" DESTINATION ${CMAKE_INSTALL_LIBDIR}) endif() target_sources(mlt PRIVATE ../win32/win32.c ../win32/strptime.c) target_link_libraries(mlt PRIVATE Iconv::Iconv) if(NOT WINDOWS_DEPLOY) target_compile_definitions(mlt PRIVATE NODEPLOY) endif() endif() if(NOT (WIN32 OR RELOCATABLE)) if(APPLE) target_compile_definitions(mlt PRIVATE PREFIX_DATA="${CMAKE_INSTALL_FULL_DATADIR}/mlt" PREFIX_LIB="${CMAKE_INSTALL_FULL_LIBDIR}/mlt") else() target_compile_definitions(mlt PRIVATE PREFIX_DATA="${CMAKE_INSTALL_FULL_DATADIR}/mlt-${MLT_VERSION_MAJOR}" PREFIX_LIB="${CMAKE_INSTALL_FULL_LIBDIR}/mlt-${MLT_VERSION_MAJOR}") target_link_options(mlt PRIVATE -Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/mlt.vers) set_target_properties(mlt PROPERTIES LINK_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/mlt.vers) endif() elseif(RELOCATABLE) target_compile_definitions(mlt PRIVATE RELOCATABLE) endif() install(TARGETS mlt EXPORT MltTargets RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/mlt-${MLT_VERSION_MAJOR}/framework ) install(FILES metaschema.yaml chain_normalizers.ini DESTINATION ${MLT_INSTALL_DATA_DIR}) configure_file(mlt-framework.pc.in mlt-framework-${MLT_VERSION_MAJOR}.pc @ONLY) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/mlt-framework-${MLT_VERSION_MAJOR}.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig COMPONENT Development ) mlt-7.22.0/src/framework/chain_normalizers.ini000664 000000 000000 00000001253 14531534050 021353 0ustar00rootroot000000 000000 # This file defines the chain normalizers, their fallbacks and the order they're attached # # The names on the left are arbitrary, but the order in which they occur is the # order in which they're applied. # # The names of the services on the right dictate the preference used (if unavailable # the second and third are applied as applicable). # image links deinterlace=avdeinterlace,deinterlace fieldorder=fieldorder crop=movit.crop,crop:1 rescaler=movit.resample,swscale,gtkrescale,rescale resizer=movit.resize,resize movitconvert=movit.convert imageconvert=avcolor_space,imageconvert # audio links audioconvert=audioconvert resampler=swresample,resample channels=audiochannels mlt-7.22.0/src/framework/metaschema.yaml000664 000000 000000 00000012016 14531534050 020135 0ustar00rootroot000000 000000 --- # A metadata schema in Kwalify: http://www.kuwata-lab.com/kwalify/ # Version: 7.0 type: map mapping: "schema_version": # This should match the version comment above type: float required: yes "LC_NUMERIC": # If not provided LC_NUMERIC=C is used, not the system's locale. type: str required: no "type": # A service type type: str required: yes enum: [consumer, filter, link, producer, transition] "identifier": # The same value used to register and create the service type: str required: yes unique: yes "title": # The UI can use this for a field label type: str "copyright": # Who owns the rights to the module and/or service? type: str "version": # The version of the service implementation type: text "license": # The software license for the service implementation type: str "language": # A 2 character ISO 639-1 language code type: str required: yes "url": # A hyperlink to a related website type: str "creator": # The name and/or e-mail address of the original author type: str "contributor": # The name and/or e-mail of all source code contributors type: seq sequence: - type: str "tags": # A set of categories, this might become an enum type: seq sequence: - type: str "description": # A slightly longer description than title type: str "icon": # A graphical representation of the effect type: map mapping: "filename": type: str "content-type": type: str "content-encoding": type: str "content": type: str "notes": # Details about the usage and/or implementation - can be long type: str "bugs": # A list of known problems that users can try to avoid type: seq sequence: - type: str # Can be a sentence or paragraph, preferably not a hyperlink "parameters": # A list of all of the options for the service type: seq sequence: - type: map mapping: "identifier": # The key that must be used to set the mlt_property type: str required: yes "type": # An mlt_property_type type: str enum: - boolean # 0 or 1; not 'true', 'false', 'yes', or 'no' strings at this time - float - geometry - integer - properties # for passing options to encapsulated services - string - time # time string values (clock, SMPTE) can be accepted in addition to frames - rect # "X Y W H" or "X/Y:WxH" - color # 0xrrggbbaa | #rrggbb | #aarrggbb "service-name": # for type: properties, a reference to another service type: str # format: type.service, e.g. transition.composite "title": # A UI can use this for a field label type: str "description": # A UI can use this for a tool tip or what's-this type: str "argument": # If this is also the service constructor argument. type: bool default: no "readonly": # If you set this property, it will be ignored type: bool default: no "required": # Is this property required? type: bool default: no "mutable": # The service will change behavior if this is set after # processing the first frame type: bool default: no "animation": # Does the property support the animation API for keyframes? type: bool default: no "widget": # A hint to the UI about how to let the user set this type: str enum: - checkbox - color - combo - curve - directory - fileopen - filesave - font - knob - listbox - dropdown # aka HTML select or GtkOptionMenu - radio - rectangle # for use with type: geometry - slider - spinner - text - textbox # multi-line - timecode "minimum": # For numeric types, the minimal value type: number "maximum": # For numeric types, the maximal value type: number "default": # The default value to be used in a UI type: scalar # If not specified, the UI might be able to display # this as blank "unit": # A UI can display this as a label after the widget (e.g. %) type: str "scale": # the number of digits after decimal point when type: float type: int "format": # A hint about a custom string encoding, possibly scanf "values": # A list of acceptable string values type: seq # A UI can allow something outside the list with # widget: combo or if "other" is in this sequence sequence: - type: scalar mlt-7.22.0/src/framework/mlt-framework.pc.in000664 000000 000000 00000000675 14531534050 020672 0ustar00rootroot000000 000000 prefix=@CMAKE_INSTALL_PREFIX@ exec_prefix=@CMAKE_INSTALL_PREFIX@ libdir=@CMAKE_INSTALL_FULL_LIBDIR@ includedir=@CMAKE_INSTALL_FULL_INCLUDEDIR@ datadir=@CMAKE_INSTALL_FULL_DATADIR@ moduledir=${prefix}/@MLT_INSTALL_MODULE_DIR@ mltdatadir=${datadir}/@MLT_SUBDIR@ Name: mlt-framework Description: MLT multimedia framework Version: @MLT_VERSION@ Requires: Libs: -L${libdir} -lmlt-@MLT_VERSION_MAJOR@ Cflags: -I${includedir}/mlt-@MLT_VERSION_MAJOR@ mlt-7.22.0/src/framework/mlt.h000664 000000 000000 00000003622 14531534050 016112 0ustar00rootroot000000 000000 /** * \file mlt.h * \brief header file for lazy client and implementation code :-) * * Copyright (C) 2003-2021 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef MLT_H #define MLT_H /** \mainpage MLT API Reference Documentation * \par * We recommend that you look in Data Structures * or Files. * \par * Additional documentation about MLT, in general, can be found on the * MLT website. */ #ifdef __cplusplus extern "C" { #endif #include "mlt_animation.h" #include "mlt_audio.h" #include "mlt_cache.h" #include "mlt_chain.h" #include "mlt_consumer.h" #include "mlt_deque.h" #include "mlt_factory.h" #include "mlt_field.h" #include "mlt_filter.h" #include "mlt_frame.h" #include "mlt_image.h" #include "mlt_link.h" #include "mlt_log.h" #include "mlt_multitrack.h" #include "mlt_parser.h" #include "mlt_playlist.h" #include "mlt_producer.h" #include "mlt_profile.h" #include "mlt_properties.h" #include "mlt_repository.h" #include "mlt_slices.h" #include "mlt_tokeniser.h" #include "mlt_tractor.h" #include "mlt_transition.h" #include "mlt_version.h" #ifdef __cplusplus } #endif #endif mlt-7.22.0/src/framework/mlt.vers000664 000000 000000 00000041126 14531534050 016643 0ustar00rootroot000000 000000 MLT_0.8.8 { global: default_callback; mlt_audio_format_name; mlt_audio_format_size; mlt_cache_close; mlt_cache_get; mlt_cache_get_frame; mlt_cache_get_size; mlt_cache_init; mlt_cache_item_close; mlt_cache_item_data; mlt_cache_purge; mlt_cache_put; mlt_cache_put_frame; mlt_cache_set_size; mlt_consumer_close; mlt_consumer_connect; mlt_consumer_get_frame; mlt_consumer_init; mlt_consumer_is_stopped; mlt_consumer_new; mlt_consumer_position; mlt_consumer_properties; mlt_consumer_purge; mlt_consumer_put_frame; mlt_consumer_rt_frame; mlt_consumer_service; mlt_consumer_start; mlt_consumer_stop; mlt_consumer_stopped; mlt_deque_close; mlt_deque_count; mlt_deque_init; mlt_deque_insert; mlt_deque_peek; mlt_deque_peek_back; mlt_deque_peek_back_double; mlt_deque_peek_back_int; mlt_deque_peek_front; mlt_deque_peek_front_double; mlt_deque_peek_front_int; mlt_deque_pop_back; mlt_deque_pop_back_double; mlt_deque_pop_back_int; mlt_deque_pop_front; mlt_deque_pop_front_double; mlt_deque_pop_front_int; mlt_deque_push_back; mlt_deque_push_back_double; mlt_deque_push_back_int; mlt_deque_push_front; mlt_deque_push_front_double; mlt_deque_push_front_int; mlt_environment; mlt_environment_set; mlt_event_block; mlt_event_close; mlt_event_inc_ref; mlt_events_block; mlt_events_close_wait_for; mlt_events_disconnect; mlt_events_fire; mlt_events_init; mlt_events_listen; mlt_events_register; mlt_events_setup_wait_for; mlt_events_unblock; mlt_events_wait_for; mlt_event_unblock; mlt_factory_close; mlt_factory_consumer; mlt_factory_directory; mlt_factory_event_object; mlt_factory_filter; mlt_factory_init; mlt_factory_producer; mlt_factory_register_for_clean_up; mlt_factory_transition; mlt_field_close; mlt_field_disconnect_service; mlt_field_init; mlt_field_multitrack; mlt_field_new; mlt_field_plant_filter; mlt_field_plant_transition; mlt_field_properties; mlt_field_service; mlt_field_tractor; mlt_filter_close; mlt_filter_connect; mlt_filter_get_in; mlt_filter_get_length; mlt_filter_get_length2; mlt_filter_get_out; mlt_filter_get_position; mlt_filter_get_progress; mlt_filter_get_track; mlt_filter_init; mlt_filter_new; mlt_filter_process; mlt_filter_properties; mlt_filter_service; mlt_filter_set_in_and_out; mlt_frame_clone; mlt_frame_close; mlt_frame_get_alpha_mask; mlt_frame_get_aspect_ratio; mlt_frame_get_audio; mlt_frame_get_image; mlt_frame_get_original_producer; mlt_frame_get_position; mlt_frame_get_waveform; mlt_frame_init; mlt_frame_is_test_audio; mlt_frame_is_test_card; mlt_frame_original_position; mlt_frame_pop_audio; mlt_frame_pop_frame; mlt_frame_pop_get_image; mlt_frame_pop_service; mlt_frame_pop_service_int; mlt_frame_properties; mlt_frame_push_audio; mlt_frame_push_frame; mlt_frame_push_get_image; mlt_frame_push_service; mlt_frame_push_service_int; mlt_frame_replace_image; mlt_frame_service_stack; mlt_frame_set_alpha; mlt_frame_set_aspect_ratio; mlt_frame_set_audio; mlt_frame_set_image; mlt_frame_set_position; mlt_frame_unique_properties; mlt_frame_write_ppm; mlt_geometry_close; mlt_geometry_fetch; mlt_geometry_get_length; mlt_geometry_init; mlt_geometry_insert; mlt_geometry_interpolate; mlt_geometry_next_key; mlt_geometry_parse; mlt_geometry_parse_item; mlt_geometry_prev_key; mlt_geometry_refresh; mlt_geometry_remove; mlt_geometry_serialise; mlt_geometry_serialise_cut; mlt_geometry_set_length; mlt_global_properties; mlt_image_format_name; mlt_image_format_size; mlt_log; mlt_log_get_level; mlt_log_set_callback; mlt_log_set_level; mlt_multitrack_clip; mlt_multitrack_close; mlt_multitrack_connect; mlt_multitrack_count; mlt_multitrack_init; mlt_multitrack_producer; mlt_multitrack_properties; mlt_multitrack_refresh; mlt_multitrack_service; mlt_multitrack_track; mlt_parser_close; mlt_parser_new; mlt_parser_properties; mlt_parser_start; mlt_playlist_append; mlt_playlist_append_io; mlt_playlist_blank; mlt_playlist_blanks_from; mlt_playlist_blank_time; mlt_playlist_clear; mlt_playlist_clip; mlt_playlist_clip_is_mix; mlt_playlist_clip_length; mlt_playlist_clip_start; mlt_playlist_close; mlt_playlist_consolidate_blanks; mlt_playlist_count; mlt_playlist_current; mlt_playlist_current_clip; mlt_playlist_get_clip; mlt_playlist_get_clip_at; mlt_playlist_get_clip_index_at; mlt_playlist_get_clip_info; mlt_playlist_init; mlt_playlist_insert; mlt_playlist_insert_at; mlt_playlist_insert_blank; mlt_playlist_is_blank; mlt_playlist_is_blank_at; mlt_playlist_join; mlt_playlist_mix; mlt_playlist_mix_add; mlt_playlist_move; mlt_playlist_move_region; mlt_playlist_new; mlt_playlist_pad_blanks; mlt_playlist_producer; mlt_playlist_properties; mlt_playlist_remove; mlt_playlist_remove_region; mlt_playlist_repeat_clip; mlt_playlist_replace_with_blank; mlt_playlist_resize_clip; mlt_playlist_service; mlt_playlist_split; mlt_playlist_split_at; mlt_pool_alloc; mlt_pool_close; mlt_pool_init; mlt_pool_purge; mlt_pool_realloc; mlt_pool_release; mlt_producer_attach; mlt_producer_clear; mlt_producer_close; mlt_producer_cut; mlt_producer_cut_parent; mlt_producer_detach; mlt_producer_filter; mlt_producer_frame; mlt_producer_frame_time; mlt_producer_get_fps; mlt_producer_get_in; mlt_producer_get_length; mlt_producer_get_length_time; mlt_producer_get_out; mlt_producer_get_playtime; mlt_producer_get_speed; mlt_producer_init; mlt_producer_is_blank; mlt_producer_is_cut; mlt_producer_is_mix; mlt_producer_new; mlt_producer_optimise; mlt_producer_position; mlt_producer_prepare_next; mlt_producer_properties; mlt_producer_seek; mlt_producer_seek_time; mlt_producer_service; mlt_producer_set_in_and_out; mlt_producer_set_speed; mlt_profile_clone; mlt_profile_close; mlt_profile_dar; mlt_profile_fps; mlt_profile_from_producer; mlt_profile_init; mlt_profile_list; mlt_profile_load_file; mlt_profile_load_properties; mlt_profile_load_string; mlt_profile_sar; mlt_properties_close; mlt_properties_count; mlt_properties_debug; mlt_properties_dec_ref; mlt_properties_dir_list; mlt_properties_dump; mlt_properties_get; mlt_properties_get_data; mlt_properties_get_data_at; mlt_properties_get_double; mlt_properties_get_int; mlt_properties_get_int64; mlt_properties_get_lcnumeric; mlt_properties_get_name; mlt_properties_get_position; mlt_properties_get_time; mlt_properties_get_value; mlt_properties_inc_ref; mlt_properties_inherit; mlt_properties_init; mlt_properties_is_sequence; mlt_properties_load; mlt_properties_lock; mlt_properties_mirror; mlt_properties_new; mlt_properties_parse; mlt_properties_parse_yaml; mlt_properties_pass; mlt_properties_pass_list; mlt_properties_pass_property; mlt_properties_preset; mlt_properties_ref_count; mlt_properties_rename; mlt_properties_save; mlt_properties_serialise_yaml; mlt_properties_set; mlt_properties_set_data; mlt_properties_set_double; mlt_properties_set_int; mlt_properties_set_int64; mlt_properties_set_lcnumeric; mlt_properties_set_or_default; mlt_properties_set_position; mlt_properties_unlock; mlt_property_close; mlt_property_get_data; mlt_property_get_double; mlt_property_get_int; mlt_property_get_int64; mlt_property_get_position; mlt_property_get_string; mlt_property_get_string_l; mlt_property_get_time; mlt_property_init; mlt_property_pass; mlt_property_set_data; mlt_property_set_double; mlt_property_set_int; mlt_property_set_int64; mlt_property_set_position; mlt_property_set_string; mlt_repository_close; mlt_repository_consumers; mlt_repository_create; mlt_repository_filters; mlt_repository_init; mlt_repository_languages; mlt_repository_metadata; mlt_repository_presets; mlt_repository_producers; mlt_repository_register; mlt_repository_register_metadata; mlt_repository_transitions; mlt_sample_calculator; mlt_sample_calculator_to_now; mlt_sdl_mutex; mlt_service_apply_filters; mlt_service_attach; mlt_service_cache_get; mlt_service_cache_get_size; mlt_service_cache_purge; mlt_service_cache_put; mlt_service_cache_set_size; mlt_service_close; mlt_service_connect_producer; mlt_service_consumer; mlt_service_detach; mlt_service_filter; mlt_service_get_frame; mlt_service_get_producer; mlt_service_identify; mlt_service_init; mlt_service_lock; mlt_service_producer; mlt_service_profile; mlt_service_properties; mlt_service_set_profile; mlt_service_unlock; mlt_tokeniser_close; mlt_tokeniser_count; mlt_tokeniser_get_input; mlt_tokeniser_get_string; mlt_tokeniser_init; mlt_tokeniser_parse_new; mlt_tractor_close; mlt_tractor_connect; mlt_tractor_field; mlt_tractor_get_track; mlt_tractor_init; mlt_tractor_multitrack; mlt_tractor_new; mlt_tractor_producer; mlt_tractor_properties; mlt_tractor_refresh; mlt_tractor_service; mlt_tractor_set_track; mlt_transition_close; mlt_transition_connect; mlt_transition_get_a_track; mlt_transition_get_b_track; mlt_transition_get_in; mlt_transition_get_length; mlt_transition_get_out; mlt_transition_get_position; mlt_transition_get_progress; mlt_transition_get_progress_delta; mlt_transition_init; mlt_transition_new; mlt_transition_process; mlt_transition_properties; mlt_transition_service; mlt_transition_set_in_and_out; mlt_version_get_int; mlt_version_get_major; mlt_version_get_minor; mlt_version_get_revision; mlt_version_get_string; mlt_vlog; local: *; }; MLT_0.9.0 { global: mlt_animation_new; mlt_animation_parse; mlt_animation_refresh; mlt_animation_get_length; mlt_animation_set_length; mlt_animation_parse_item; mlt_animation_get_item; mlt_animation_insert; mlt_animation_remove; mlt_animation_interpolate; mlt_animation_next_key; mlt_animation_prev_key; mlt_animation_serialize_cut; mlt_animation_serialize; mlt_animation_close; mlt_properties_get_color; mlt_properties_set_color; mlt_properties_anim_get; mlt_properties_anim_set; mlt_properties_anim_get_int; mlt_properties_anim_set_int; mlt_properties_anim_get_double; mlt_properties_anim_set_double; mlt_properties_get_animation; mlt_properties_set_rect; mlt_properties_get_rect; mlt_properties_anim_set_rect; mlt_properties_anim_get_rect; mlt_property_interpolate; mlt_property_anim_get_double; mlt_property_anim_get_int; mlt_property_anim_get_string; mlt_property_anim_set_double; mlt_property_anim_set_int; mlt_property_anim_set_string; mlt_property_get_animation; mlt_property_set_rect; mlt_property_get_rect; mlt_property_anim_set_rect; mlt_property_anim_get_rect; mlt_service_filter_count; mlt_service_move_filter; } MLT_0.8.8; MLT_0.9.2 { global: mlt_playlist_mix_in; mlt_playlist_mix_out; mlt_properties_frames_to_time; mlt_properties_time_to_frames; mlt_properties_from_utf8; } MLT_0.9.0; MLT_0.9.4 { global: mlt_pool_stat; mlt_frame_get_alpha; } MLT_0.9.2; MLT_0.9.8 { global: mlt_service_disconnect_producer; mlt_multitrack_disconnect; mlt_tractor_remove_track; mlt_service_insert_producer; mlt_multitrack_insert; mlt_tractor_insert_track; mlt_transition_set_tracks; mlt_animation_key_count; mlt_animation_key_get; } MLT_0.9.4; MLT_0.9.10 { global: mlt_factory_repository; mlt_properties_to_utf8; } MLT_0.9.8; MLT_6.4.0 { global: mlt_slices_init; mlt_slices_close; mlt_slices_run; } MLT_0.9.10; MLT_6.6.0 { global: mlt_image_format_planes; mlt_image_format_id; mlt_slices_count_normal; mlt_slices_count_rr; mlt_slices_count_fifo; mlt_slices_run_normal; mlt_slices_run_rr; mlt_slices_run_fifo; mlt_log_timings_now; mlt_service_disconnect_all_producers; } MLT_6.4.0; MLT_6.8.0 { global: mlt_channel_layout_name; mlt_channel_layout_id; mlt_channel_layout_channels; mlt_channel_layout_default; mlt_animation_key_set_type; mlt_animation_key_set_frame; } MLT_6.6.0; MLT_6.10.0 { global: mlt_animation_serialize_cut_tf; mlt_animation_serialize_tf; mlt_property_clear; mlt_property_get_string_tf; mlt_property_get_string_l_tf; mlt_properties_clear; mlt_properties_get_value_tf; } MLT_6.8.0; MLT_6.12.0 { global: mlt_profile_lumas_dir; } MLT_6.10.0; MLT_6.14.0 { global: mlt_frame_get_unique_properties; mlt_playlist_reorder; mlt_producer_set_creation_time; mlt_producer_get_creation_time; } MLT_6.12.0; MLT_6.18.0 { global: mlt_luma_map_init; mlt_luma_map_new; mlt_luma_map_render; mlt_luma_map_from_pgm; mlt_luma_map_from_yuv422; } MLT_6.14.0; MLT_6.20.0 { global: mlt_profile_scale_width; mlt_profile_scale_height; mlt_properties_set_string; } MLT_6.18.0; MLT_6.22.0 { global: mlt_property_is_clear; mlt_properties_exists; mlt_audio_new; mlt_audio_close; mlt_audio_set_values; mlt_audio_get_values; mlt_audio_alloc_data; mlt_audio_calculate_size; mlt_audio_plane_count; mlt_audio_plane_size; mlt_audio_get_planes; mlt_audio_shrink; mlt_audio_reverse; mlt_audio_copy; mlt_audio_calculate_frame_samples; mlt_audio_calculate_samples_to_position; mlt_audio_channel_layout_name; mlt_audio_channel_layout_id; mlt_audio_channel_layout_channels; mlt_audio_channel_layout_default; } MLT_6.20.0; MLT_7.0.0 { global: mlt_factory_link; mlt_chain_init; mlt_chain_set_source; mlt_chain_get_source; mlt_chain_attach; mlt_chain_detach; mlt_chain_link_count; mlt_chain_move_link; mlt_chain_link; mlt_chain_close; mlt_link_init; mlt_link_connect_next; mlt_link_close; mlt_repository_links; mlt_animation_shift_frames; mlt_animation_get_string; mlt_image_new; mlt_image_close; mlt_image_set_values; mlt_image_get_values; mlt_image_alloc_data; mlt_image_alloc_alpha; mlt_image_calculate_size; mlt_image_fill_black; mlt_image_fill_opaque; mlt_audio_silence; mlt_event_data_none; mlt_event_data_from_int; mlt_event_data_to_int; mlt_event_data_from_string; mlt_event_data_to_string; mlt_event_data_from_frame; mlt_event_data_to_frame; mlt_event_data_from_object; mlt_event_data_to_object; } MLT_6.22.0; MLT_7.1.0 { global: mlt_property_set_properties; mlt_property_get_properties; mlt_properties_set_properties; mlt_properties_get_properties; mlt_properties_get_properties_at; } MLT_7.0.0; MLT_7.4.0 { global: mlt_property_is_anim; mlt_properties_is_anim; } MLT_7.1.0; MLT_7.6.0 { global: mlt_properties_copy; mlt_slices_size_slice; } MLT_7.4.0; MLT_7.8.0 { global: mlt_frame_get_alpha_size; } MLT_7.6.0; MLT_7.10.0 { global: mlt_image_fill_checkerboard; mlt_image_fill_white; mlt_image_rgba_opaque; } MLT_7.8.0; MLT_7.12.0 { global: mlt_property_set_color; mlt_property_get_color; mlt_property_anim_set_color; mlt_property_anim_get_color; mlt_properties_anim_set_color; mlt_properties_anim_get_color; } MLT_7.10.0; MLT_7.14.0 { global: mlt_producer_probe; mlt_chain_attach_normalizers; } MLT_7.12.0; MLT_7.16.0 { global: mlt_deinterlacer_name; mlt_deinterlacer_id; mlt_link_filter_init; mlt_link_filter_metadata; mlt_cache_put_frame_audio; mlt_cache_put_frame_image; mlt_frame_clone_audio; mlt_frame_clone_image; } MLT_7.14.0; MLT_7.18.0 { global: mlt_audio_free_data; } MLT_7.16.0; MLT_7.22.0 { global: mlt_property_is_color; mlt_property_is_numeric; mlt_property_is_rect; } MLT_7.18.0;mlt-7.22.0/src/framework/mlt_animation.c000664 000000 000000 00000145325 14531534050 020153 0ustar00rootroot000000 000000 /** * \file mlt_animation.c * \brief Property Animation class definition * \see mlt_animation_s * * Copyright (C) 2004-2023 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "mlt_animation.h" #include "mlt_factory.h" #include "mlt_profile.h" #include "mlt_properties.h" #include "mlt_tokeniser.h" #include #include #include #include #include /** \brief animation list node pointer */ typedef struct animation_node_s *animation_node; /** \brief private animation list node */ struct animation_node_s { struct mlt_animation_item_s item; animation_node next, prev; }; /** \brief Property Animation class * * This is the animation engine for a Property object. It is dependent upon * the mlt_property API and used by the various mlt_property_anim_* functions. */ struct mlt_animation_s { char *data; /**< the string representing the animation */ int length; /**< the maximum number of frames to use when interpreting negative keyframe positions */ double fps; /**< framerate to use when converting time clock strings to frame units */ mlt_locale_t locale; /**< pointer to a locale to use when converting strings to numeric values */ animation_node nodes; /**< a linked list of keyframes (and possibly non-keyframe values) */ }; /** \brief Keyframe type to string mapping * * Used for serialization and deserialization of keyframe types */ struct { mlt_keyframe_type t; const char *s; } keyframe_type_map[] = { // Map keyframe type to any single character except numeric values. {mlt_keyframe_discrete, "|"}, {mlt_keyframe_discrete, "!"}, {mlt_keyframe_linear, ""}, {mlt_keyframe_smooth, "~"}, {mlt_keyframe_smooth_loose, "~"}, {mlt_keyframe_smooth_natural, "$"}, {mlt_keyframe_smooth_tight, "-"}, {mlt_keyframe_sinusoidal_in, "a"}, {mlt_keyframe_sinusoidal_out, "b"}, {mlt_keyframe_sinusoidal_in_out, "c"}, {mlt_keyframe_quadratic_in, "d"}, {mlt_keyframe_quadratic_out, "e"}, {mlt_keyframe_quadratic_in_out, "f"}, {mlt_keyframe_cubic_in, "g"}, {mlt_keyframe_cubic_out, "h"}, {mlt_keyframe_cubic_in_out, "i"}, {mlt_keyframe_quartic_in, "j"}, {mlt_keyframe_quartic_out, "k"}, {mlt_keyframe_quartic_in_out, "l"}, {mlt_keyframe_quintic_in, "m"}, {mlt_keyframe_quintic_out, "n"}, {mlt_keyframe_quintic_in_out, "o"}, {mlt_keyframe_exponential_in, "p"}, {mlt_keyframe_exponential_out, "q"}, {mlt_keyframe_exponential_in_out, "r"}, {mlt_keyframe_circular_in, "s"}, {mlt_keyframe_circular_out, "t"}, {mlt_keyframe_circular_in_out, "u"}, {mlt_keyframe_back_in, "v"}, {mlt_keyframe_back_out, "w"}, {mlt_keyframe_back_in_out, "x"}, {mlt_keyframe_elastic_in, "y"}, {mlt_keyframe_elastic_out, "z"}, {mlt_keyframe_elastic_in_out, "A"}, {mlt_keyframe_bounce_in, "B"}, {mlt_keyframe_bounce_out, "C"}, {mlt_keyframe_bounce_in_out, "D"}, }; static void mlt_animation_clear_string(mlt_animation self); static int interpolate_item(mlt_animation_item item, mlt_animation_item p[], double fps, mlt_locale_t locale); static const char *keyframe_type_to_str(mlt_keyframe_type t) { int map_count = sizeof(keyframe_type_map) / sizeof(*keyframe_type_map); for (int i = 0; i < map_count; i++) { if (keyframe_type_map[i].t == t) { return keyframe_type_map[i].s; } } return ""; } static mlt_keyframe_type str_to_keyframe_type(const char *s) { if (s && (s[0] < '0' || s[0] > '9')) { int map_count = sizeof(keyframe_type_map) / sizeof(*keyframe_type_map); for (int i = 0; i < map_count; i++) { if (!strncmp(s, keyframe_type_map[i].s, 1)) { return keyframe_type_map[i].t; } } } return mlt_keyframe_linear; } /** Create a new animation object. * * \public \memberof mlt_animation_s * \return an animation object */ mlt_animation mlt_animation_new() { mlt_animation self = calloc(1, sizeof(*self)); return self; } /** Re-interpolate non-keyframe nodes after a series of insertions or removals. * * \public \memberof mlt_animation_s * \param self an animation */ void mlt_animation_interpolate(mlt_animation self) { // Parse all items to ensure non-keyframes are calculated correctly. if (self && self->nodes) { animation_node current = self->nodes; while (current) { if (!current->item.is_key) { mlt_animation_item points[4]; animation_node prev = current->prev; animation_node next = current->next; while (prev && !prev->item.is_key) prev = prev->prev; while (next && !next->item.is_key) next = next->next; if (!prev) { current->item.is_key = 1; prev = current; } if (!next) { next = current; } points[0] = prev->prev ? &prev->prev->item : &prev->item; points[1] = &prev->item; points[2] = &next->item; points[3] = next->next ? &next->next->item : &next->item; interpolate_item(¤t->item, points, self->fps, self->locale); } // Move to the next item current = current->next; } } } /** Remove a node from the linked list. * * \private \memberof mlt_animation_s * \param self an animation * \param node the node to remove * \return false */ static int mlt_animation_drop(mlt_animation self, animation_node node) { if (node == self->nodes) { self->nodes = node->next; if (self->nodes) { self->nodes->prev = NULL; self->nodes->item.is_key = 1; } } else if (node->next && node->prev) { node->prev->next = node->next; node->next->prev = node->prev; } else if (node->next) { node->next->prev = node->prev; } else if (node->prev) { node->prev->next = node->next; } mlt_property_close(node->item.property); free(node); return 0; } /** Reset an animation and free all strings and properties. * * \private \memberof mlt_animation_s * \param self an animation */ static void mlt_animation_clean(mlt_animation self) { if (!self) return; free(self->data); self->data = NULL; while (self->nodes) mlt_animation_drop(self, self->nodes); } /** Parse a string representing an animation. * * A semicolon is the delimiter between keyframe=value items in the string. * \public \memberof mlt_animation_s * \param self an animation * \param data the string representing an animation * \param length the maximum number of frames when interpreting negative keyframe times, * <=0 if you don't care or need that * \param fps the framerate to use when evaluating time strings * \param locale the locale to use when converting strings to numbers * \return true if there was an error */ int mlt_animation_parse( mlt_animation self, const char *data, int length, double fps, mlt_locale_t locale) { if (!self) return 1; int error = 0; int i = 0; struct mlt_animation_item_s item; mlt_tokeniser tokens = mlt_tokeniser_init(); // Clean the existing geometry mlt_animation_clean(self); // Update the info on the data if (data) self->data = strdup(data); self->length = length; self->fps = fps; self->locale = locale; item.property = mlt_property_init(); item.frame = item.is_key = 0; item.keyframe_type = mlt_keyframe_discrete; // Tokenise if (data) mlt_tokeniser_parse_new(tokens, (char *) data, ";"); // Iterate through each token for (i = 0; i < mlt_tokeniser_count(tokens); i++) { char *value = mlt_tokeniser_get_string(tokens, i); // If no data in keyframe, drop it (trailing semicolon) if (!value || !strcmp(value, "")) continue; // Reset item item.frame = item.is_key = 0; // Do not parse a string enclosed entirely within quotes as animation. // (mlt_tokeniser already skips splitting on delimiter inside quotes). if (value[0] == '\"' && value[strlen(value) - 1] == '\"') { // Remove the quotes. value[strlen(value) - 1] = '\0'; mlt_property_set_string(item.property, &value[1]); } else { // Now parse the item mlt_animation_parse_item(self, &item, value); } // Now insert into place mlt_animation_insert(self, &item); } mlt_animation_interpolate(self); // Cleanup mlt_tokeniser_close(tokens); mlt_property_close(item.property); return error; } /** Conditionally refresh the animation if it is modified. * * \public \memberof mlt_animation_s * \param self an animation * \param data the string representing an animation * \param length the maximum number of frames when interpreting negative keyframe times, * <=0 if you don't care or need that * \return true if there was an error */ int mlt_animation_refresh(mlt_animation self, const char *data, int length) { if (!self) return 1; if ((length != self->length) || (data && (!self->data || strcmp(data, self->data)))) return mlt_animation_parse(self, data, length, self->fps, self->locale); return 0; } /** Get the length of the animation. * * If the animation was initialized with a zero or negative value, then this * gets the maximum frame number from animation's list of nodes. * \public \memberof mlt_animation_s * \param self an animation * \return the number of frames */ int mlt_animation_get_length(mlt_animation self) { int length = 0; if (self) { if (self->length > 0) { length = self->length; } else if (self->nodes) { animation_node node = self->nodes; while (node) { if (node->item.frame > length) length = node->item.frame; node = node->next; } } } return length; } /** Set the length of the animation. * * The length is used for interpreting negative keyframe positions as relative * to the length. It is also used when serializing an animation as a string. * \public \memberof mlt_animation_s * \param self an animation * \param length the length of the animation in frame units */ void mlt_animation_set_length(mlt_animation self, int length) { if (self) { self->length = length; mlt_animation_clear_string(self); } } /** Parse a string representing an animation keyframe=value. * * This function does not affect the animation itself! But it will use some state * of the animation for the parsing (e.g. fps, locale). * It parses into a mlt_animation_item that you provide. * \p item->frame should be specified if the string does not have an equal sign and time field. * If an exclamation point (!) or vertical bar (|) character precedes the equal sign, then * the keyframe interpolation is set to discrete. If a tilde (~) precedes the equal sign, * then the keyframe interpolation is set to smooth (spline). * * \public \memberof mlt_animation_s * \param self an animation * \param item an already allocated animation item * \param value the string representing an animation * \return true if there was an error */ int mlt_animation_parse_item(mlt_animation self, mlt_animation_item item, const char *value) { int error = 0; if (self && item && value && strcmp(value, "")) { // Determine if a position has been specified if (strchr(value, '=')) { // Parse an absolute time value. // Null terminate the string at the equal sign to prevent interpreting // a colon in the part to the right of the equal sign as indicative of a // a time value string. char *s = strdup(value); char *p = strchr(s, '='); p[0] = '\0'; mlt_property_set_string(item->property, s); item->frame = mlt_property_get_int(item->property, self->fps, self->locale); free(s); // The character preceding the equal sign indicates interpolation method. p = strchr(value, '=') - 1; item->keyframe_type = str_to_keyframe_type(p); value = &p[2]; // Check if the value is quoted. p = &p[2]; if (p && p[0] == '\"' && p[strlen(p) - 1] == '\"') { // Remove the quotes. p[strlen(p) - 1] = '\0'; value = &p[1]; } } // Special case - frame < 0 if (item->frame < 0) item->frame += mlt_animation_get_length(self); // Set remainder of string as item value. mlt_property_set_string(item->property, value); item->is_key = 1; } else { error = 1; } return error; } /** Load an animation item for an absolute position. * * This performs interpolation if there is no keyframe at the \p position. * \public \memberof mlt_animation_s * \param self an animation * \param item an already allocated animation item that will be filled in * \param position the frame number for the point in time * \return true if there was an error */ int mlt_animation_get_item(mlt_animation self, mlt_animation_item item, int position) { if (!self || !item) return 1; int error = 0; // Need to find the nearest keyframe to the position specified animation_node node = self->nodes; // Iterate through the keyframes until we reach last or have while (node && node->next && position >= node->next->item.frame) node = node->next; if (node) { item->keyframe_type = node->item.keyframe_type; // Position is before the first keyframe. if (position < node->item.frame) { item->is_key = 0; if (item->property) mlt_property_pass(item->property, node->item.property); } // Item exists. else if (position == node->item.frame) { item->is_key = node->item.is_key; if (item->property) mlt_property_pass(item->property, node->item.property); } // Position is after the last keyframe. else if (!node->next) { item->is_key = 0; if (item->property) mlt_property_pass(item->property, node->item.property); } // Interpolation needed. else { if (item->property) { mlt_animation_item points[4]; points[0] = node->prev ? &node->prev->item : &node->item; points[1] = &node->item; points[2] = &node->next->item; points[3] = node->next->next ? &node->next->next->item : &node->next->item; item->frame = position; interpolate_item(item, points, self->fps, self->locale); } item->is_key = 0; } } else { item->frame = item->is_key = 0; error = 1; } item->frame = position; return error; } /** Insert an animation item. * * \public \memberof mlt_animation_s * \param self an animation * \param item an animation item * \return true if there was an error * \see mlt_animation_parse_item */ int mlt_animation_insert(mlt_animation self, mlt_animation_item item) { if (!self || !item) return 1; int error = 0; animation_node node = calloc(1, sizeof(*node)); node->item.frame = item->frame; node->item.is_key = 1; node->item.keyframe_type = item->keyframe_type; node->item.property = mlt_property_init(); if (item->property) mlt_property_pass(node->item.property, item->property); // Determine if we need to insert or append to the list, or if it's a new list if (self->nodes) { // Get the first item animation_node current = self->nodes; // Locate an existing nearby item while (current->next && item->frame > current->item.frame) current = current->next; if (item->frame < current->item.frame) { if (current == self->nodes) self->nodes = node; if (current->prev) current->prev->next = node; node->next = current; node->prev = current->prev; current->prev = node; } else if (item->frame > current->item.frame) { if (current->next) current->next->prev = node; node->next = current->next; node->prev = current; current->next = node; } else { // Update matching node. current->item.frame = item->frame; current->item.is_key = 1; current->item.keyframe_type = item->keyframe_type; mlt_property_close(current->item.property); current->item.property = node->item.property; free(node); } } else { // Set the first item self->nodes = node; } mlt_animation_clear_string(self); return error; } /** Remove the keyframe at the specified position. * * \public \memberof mlt_animation_s * \param self an animation * \param position the frame number of the animation node to remove * \return true if there was an error */ int mlt_animation_remove(mlt_animation self, int position) { if (!self) return 1; int error = 1; animation_node node = self->nodes; while (node && position != node->item.frame) node = node->next; if (node && position == node->item.frame) error = mlt_animation_drop(self, node); mlt_animation_clear_string(self); return error; } /** Get the keyfame at the position or the next following. * * \public \memberof mlt_animation_s * \param self an animation * \param item an already allocated animation item which will be updated * \param position the frame number at which to start looking for the next animation node * \return true if there was an error */ int mlt_animation_next_key(mlt_animation self, mlt_animation_item item, int position) { if (!self || !item) return 1; animation_node node = self->nodes; while (node && position > node->item.frame) node = node->next; if (node) { item->frame = node->item.frame; item->is_key = node->item.is_key; item->keyframe_type = node->item.keyframe_type; if (item->property) mlt_property_pass(item->property, node->item.property); } return (node == NULL); } /** Get the keyfame at the position or the next preceding. * * \public \memberof mlt_animation_s * \param self an animation * \param item an already allocated animation item which will be updated * \param position the frame number at which to start looking for the previous animation node * \return true if there was an error */ int mlt_animation_prev_key(mlt_animation self, mlt_animation_item item, int position) { if (!self || !item) return 1; animation_node node = self->nodes; while (node && node->next && position >= node->next->item.frame) node = node->next; if (position < node->item.frame) node = NULL; if (node) { item->frame = node->item.frame; item->is_key = node->item.is_key; item->keyframe_type = node->item.keyframe_type; if (item->property) mlt_property_pass(item->property, node->item.property); } return (node == NULL); } /** Serialize a cut of the animation (with time format). * * The caller is responsible for free-ing the returned string. * \public \memberof mlt_animation_s * \param self an animation * \param in the frame at which to start serializing animation nodes * \param out the frame at which to stop serializing nodes * \param time_format the time format to use for the key frames * \return a string representing the animation */ char *mlt_animation_serialize_cut_tf(mlt_animation self, int in, int out, mlt_time_format time_format) { struct mlt_animation_item_s item; char *ret = calloc(1, 1000); size_t used = 0; size_t size = 1000; mlt_property time_property = mlt_property_init(); item.property = mlt_property_init(); item.frame = item.is_key = 0; item.keyframe_type = mlt_keyframe_discrete; if (in == -1) in = 0; if (out == -1) out = mlt_animation_get_length(self); if (self && ret) { item.frame = in; while (1) { size_t item_len = 0; // If it's the first frame, then it's not necessarily a key if (item.frame == in) { if (mlt_animation_get_item(self, &item, item.frame)) break; // If the first keyframe is larger than the current position // then do nothing here if (self->nodes->item.frame > item.frame) { item.frame++; continue; } // To ensure correct seeding item.is_key = 1; } // Typically, we move from keyframe to keyframe else if (item.frame <= out) { if (mlt_animation_next_key(self, &item, item.frame)) break; // Special case - crop at the out point if (item.frame > out) { mlt_animation_get_item(self, &item, out); // To ensure correct seeding item.is_key = 1; } } // We've handled the last keyframe else { break; } // Determine length of string to be appended. item_len += 100; const char *value = mlt_property_get_string_l(item.property, self->locale); if (item.is_key && value) { item_len += strlen(value); // Check if the value must be quoted. if (strchr(value, ';') || strchr(value, '=')) item_len += 2; } // Reallocate return string to be long enough. while (used + item_len + 2 > size) // +2 for ';' and NULL { size += 1000; ret = realloc(ret, size); } // Append item delimiter (;) if needed. if (ret && used > 0) { used++; strcat(ret, ";"); } if (ret) { // Append keyframe time and keyframe/value delimiter (=). const char *s = keyframe_type_to_str(item.keyframe_type); if (time_property && self->fps > 0.0) { mlt_property_set_int(time_property, item.frame - in); const char *time = mlt_property_get_time(time_property, time_format, self->fps, self->locale); sprintf(ret + used, "%s%s=", time, s); } else { sprintf(ret + used, "%d%s=", item.frame - in, s); } used = strlen(ret); // Append item value. if (item.is_key && value) { // Check if the value must be quoted. if (strchr(value, ';') || strchr(value, '=')) sprintf(ret + used, "\"%s\"", value); else strcat(ret, value); } used = strlen(ret); } item.frame++; } } mlt_property_close(item.property); mlt_property_close(time_property); return ret; } static mlt_time_format default_time_format() { const char *e = getenv("MLT_ANIMATION_TIME_FORMAT"); return e ? strtol(e, NULL, 10) : mlt_time_frames; } /** Serialize a cut of the animation. * * This version outputs the key frames' position as a frame number. * The caller is responsible for free-ing the returned string. * \public \memberof mlt_animation_s * \param self an animation * \param in the frame at which to start serializing animation nodes * \param out the frame at which to stop serializing nodes * \return a string representing the animation */ char *mlt_animation_serialize_cut(mlt_animation self, int in, int out) { return mlt_animation_serialize_cut_tf(self, in, out, default_time_format()); } /** Serialize the animation (with time format). * * The caller is responsible for free-ing the returned string. * \public \memberof mlt_animation_s * \param self an animation * \param time_format the time format to use for the key frames * \return a string representing the animation */ char *mlt_animation_serialize_tf(mlt_animation self, mlt_time_format time_format) { char *ret = mlt_animation_serialize_cut_tf(self, -1, -1, time_format); if (self && ret) { free(self->data); self->data = ret; ret = strdup(ret); } return ret; } /** Serialize the animation. * * This version outputs the key frames' position as a frame number. * The caller is responsible for free-ing the returned string. * \public \memberof mlt_animation_s * \param self an animation * \return a string representing the animation */ char *mlt_animation_serialize(mlt_animation self) { return mlt_animation_serialize_tf(self, default_time_format()); } /** Get the number of keyframes. * * \public \memberof mlt_animation_s * \param self an animation * \return the number of keyframes or -1 on error */ int mlt_animation_key_count(mlt_animation self) { int count = -1; if (self) { animation_node node = self->nodes; for (count = 0; node; ++count) node = node->next; } return count; } /** Get an animation item for the N-th keyframe. * * \public \memberof mlt_animation_s * \param self an animation * \param item an already allocated animation item that will be filled in * \param index the N-th keyframe (0 based) in this animation * \return true if there was an error */ int mlt_animation_key_get(mlt_animation self, mlt_animation_item item, int index) { if (!self || !item) return 1; int error = 0; animation_node node = self->nodes; // Iterate through the keyframes. int i = index; while (i-- && node) node = node->next; if (node) { item->is_key = node->item.is_key; item->frame = node->item.frame; item->keyframe_type = node->item.keyframe_type; if (item->property) mlt_property_pass(item->property, node->item.property); } else { item->frame = item->is_key = 0; error = 1; } return error; } /** Close the animation and deallocate all of its resources. * * \public \memberof mlt_animation_s * \param self the animation to destroy */ void mlt_animation_close(mlt_animation self) { if (self) { mlt_animation_clean(self); free(self); } } /** Change the interpolation for the N-th keyframe. * * \public \memberof mlt_animation_s * \param self an animation * \param index the N-th keyframe (0 based) in this animation * \param type the method of interpolation for this key frame * \return true if there was an error */ int mlt_animation_key_set_type(mlt_animation self, int index, mlt_keyframe_type type) { if (!self) return 1; int error = 0; animation_node node = self->nodes; // Iterate through the keyframes. int i = index; while (i-- && node) node = node->next; if (node) { node->item.keyframe_type = type; mlt_animation_interpolate(self); mlt_animation_clear_string(self); } else { error = 1; } return error; } /** Change the frame number for the N-th keyframe. * * \public \memberof mlt_animation_s * \param self an animation * \param index the N-th keyframe (0 based) in this animation * \param frame the position of this keyframe in frame units * \return true if there was an error */ int mlt_animation_key_set_frame(mlt_animation self, int index, int frame) { if (!self) return 1; int error = 0; animation_node node = self->nodes; // Iterate through the keyframes. int i = index; while (i-- && node) node = node->next; if (node) { node->item.frame = frame; mlt_animation_interpolate(self); mlt_animation_clear_string(self); } else { error = 1; } return error; } /** Shift the frame value for all nodes. * * \public \memberof mlt_animation_s * \param self an animation * \param shift the value to add to all frame values */ void mlt_animation_shift_frames(mlt_animation self, int shift) { animation_node node = self->nodes; while (node) { node->item.frame += shift; node = node->next; } mlt_animation_clear_string(self); mlt_animation_interpolate(self); } /** Get the cached serialization string. * * This can be used to determine if the animation has been modified because the * string is cleared whenever the animation is changed. * \public \memberof mlt_animation_s * \param self an animation * \return the cached serialization string */ const char *mlt_animation_get_string(mlt_animation self) { if (!self) return NULL; return self->data; } /** Clear the cached serialization string. * * \private \memberof mlt_animation_s * \param self an animation */ void mlt_animation_clear_string(mlt_animation self) { if (!self) return; free(self->data); self->data = NULL; } /** A linear interpolation function. * * \private \memberof mlt_animation_s */ static inline double linear_interpolate(double y1, double y2, double t) { return y1 + (y2 - y1) * t; } /** Calculate the distance between two points. * * \private \memberof mlt_animation_s */ static inline double distance(double x0, double y0, double x1, double y1) { return sqrt(pow(x1 - x0, 2) + pow(y1 - y0, 2)); } /** A Catmull–Rom interpolation function. * * As described here: * https://en.wikipedia.org/wiki/Centripetal_Catmull%E2%80%93Rom_spline * And further reduced here with tension added: * https://qroph.github.io/2018/07/30/smooth-paths-using-catmull-rom-splines.html * * This imlementation supports the alpha value which can be set to 0.5 to result in * centripetal Catmull–Rom splines. Centripital Catmull–Rom splines are guaranteed * to not have any cusps or loops. These are not desirable because they result in the * value reversing direction when interpolation from one point to the next. * * To use this function for animation item interpolation, provide 4 points: two points preceeding t * and two points following t. Use the item frame number as the x and the item value as y for each * point. t should represent the fractional progress between point 1 and point 2. * * If fewer than 2 points are available, then duplicate the first and/or last points as necessary to * meet the requirement for 4 points. * \private \memberof mlt_animation_s * \param alpha * 0.0 for the uniform spline * 0.5 for the centripetal spline (no cusps) * 1.0 for the chordal spline. * \param tension * 1.0 results in the most natural slope at x1,y1 and x2,y2 * 0.0 results in a horizontal tangent at x1,y1 and x2,y2 (slope of 0). * -1.0 results in the most natural slope at x1,y1 and x2,y2 unless x1 or x2 represents a peak. * In the case of peaks, a horizontal tangent will be used to avoid overshoot. */ static inline double catmull_rom_interpolate(double x0, double y0, double x1, double y1, double x2, double y2, double x3, double y3, double t, double alpha, double tension) { // Correct first and last values. // If points are duplicated (e.g. for the first and last segments) assume the duplicated point // is far away to create a horizontal segment. if (x0 == x1) { x0 -= 10000; } if (x3 == x2) { x3 += 10000; } double m1 = 0; double m2 = 0; double t12 = pow(distance(x1, y1, x2, y2), alpha); if (tension > 0.0 || (y1 < y0 && y1 > y2) || (y1 > y0 && y1 < y2)) { double t01 = pow(distance(x0, y0, x1, y1), alpha); m1 = abs(tension) * (y2 - y1 + t12 * ((y1 - y0) / t01 - (y2 - y0) / (t01 + t12))); } if (tension > 0.0 || (y2 < y1 && y2 > y3) || (y2 > y1 && y2 < y3)) { double t23 = pow(distance(x2, y2, x3, y3), alpha); m2 = abs(tension) * (y2 - y1 + t12 * ((y3 - y2) / t23 - (y3 - y1) / (t12 + t23))); } double a = 2.0 * (y1 - y2) + m1 + m2; double b = -3.0 * (y1 - y2) - m1 - m1 - m2; double c = m1; double d = y1; return a * t * t * t + b * t * t + c * t + d; } /** Easing functions * * The following easing functions are based on Robert Penner's Easing Functions * http://robertpenner.com/easing/ */ typedef enum { ease_in, ease_out, ease_inout, } ease_type; static inline double sinusoidal_interpolate(double y1, double y2, double t, ease_type ease) { double factor = 0; if (ease == ease_in) { factor = sin((t - 1) * M_PI_2) + 1; } else if (ease == ease_out) { factor = sin(t * M_PI_2); } else { // ease_inout factor = 0.5 * (1 - cos(t * M_PI)); } return y1 + (y2 - y1) * factor; } static inline double power_interpolate(double y1, double y2, double t, double order, ease_type ease) { double factor = 0; if (ease == ease_in) { factor = pow(t, order); } else if (ease == ease_out) { factor = 1 - pow(1 - t, order); } else { // ease_inout if (t < 0.5) { factor = pow(2, order) * pow(t, order) / 2; } else { factor = 1.0 - pow(-2 * t + 2, order) / 2; } } return y1 + (y2 - y1) * factor; } static inline double exponential_interpolate(double y1, double y2, double t, ease_type ease) { double factor = 0; if (t == 0.0) { factor = 0; } else if (t == 1.0) { factor = 1.0; } else if (ease == ease_in) { factor = pow(2.0, 10 * t - 10); } else if (ease == ease_out) { factor = 1.0 - pow(2.0, -10 * t); } else { // ease_inout if (t < 0.5) { factor = pow(2, 20 * t - 10) / 2; } else { factor = (2 - pow(2, -20 * t + 10)) / 2; } } return y1 + (y2 - y1) * factor; } static inline double circular_interpolate(double y1, double y2, double t, ease_type ease) { double factor = 0; if (ease == ease_in) { factor = 1.0 - sqrt(1.0 - pow(t, 2.0)); } else if (ease == ease_out) { factor = sqrt(1.0 - pow(t - 1.0, 2.0)); } else { // ease_inout if (t < 0.5) { factor = 0.5 * (1 - sqrt(1 - 4 * (t * t))); } else { factor = 0.5 * (sqrt(-((2 * t) - 3) * ((2 * t) - 1)) + 1); } } return y1 + (y2 - y1) * factor; } static inline double back_interpolate(double y1, double y2, double t, ease_type ease) { double factor = 0; if (ease == ease_in) { factor = t * t * t - t * sin(t * M_PI); } else if (ease == ease_out) { double f = (1 - t); factor = 1 - (f * f * f - f * sin(f * M_PI)); } else { // ease_inout if (t < 0.5) { double f = 2 * t; factor = 0.5 * (f * f * f - f * sin(f * M_PI)); } else { double f = (1 - (2 * t - 1)); factor = 0.5 * (1 - (f * f * f - f * sin(f * M_PI))) + 0.5; } } return y1 + (y2 - y1) * factor; } static inline double elastic_interpolate(double y1, double y2, double t, ease_type ease) { double factor = 0; if (ease == ease_in) { factor = sin(13 * M_PI_2 * t) * pow(2, 10 * (t - 1)); } else if (ease == ease_out) { factor = sin(-13 * M_PI_2 * (t + 1)) * pow(2, -10 * t) + 1; } else { // ease_inout if (t < 0.5) { factor = 0.5 * sin(13 * M_PI_2 * (2 * t)) * pow(2, 10 * ((2 * t) - 1)); } else { factor = 0.5 * (sin(-13 * M_PI_2 * ((2 * t - 1) + 1)) * pow(2, -10 * (2 * t - 1)) + 2); } } return y1 + (y2 - y1) * factor; } static inline double bounce_interpolate(double y1, double y2, double t, ease_type ease) { double factor = 0; if (ease == ease_in) { factor = 1.0 - bounce_interpolate(0.0, 1.0, 1.0 - t, ease_out); } else if (ease == ease_out) { if (t < 4 / 11.0) { factor = (121 * t * t) / 16.0; } else if (t < 8 / 11.0) { factor = (363 / 40.0 * t * t) - (99 / 10.0 * t) + 17 / 5.0; } else if (t < 9 / 10.0) { factor = (4356 / 361.0 * t * t) - (35442 / 1805.0 * t) + 16061 / 1805.0; } else { factor = (54 / 5.0 * t * t) - (513 / 25.0 * t) + 268 / 25.0; } } else { // ease_inout if (t < 0.5) { factor = 0.5 * bounce_interpolate(0.0, 1.0, t * 2, ease_in); } else { factor = 0.5 * bounce_interpolate(0.0, 1.0, 2.0 * t - 1.0, ease_out) + 0.5; } } return y1 + (y2 - y1) * factor; } static inline double interpolate_value(double x0, double y0, double x1, double y1, double x2, double y2, double x3, double y3, double t, mlt_keyframe_type type) { switch (type) { case mlt_keyframe_discrete: return y1; case mlt_keyframe_linear: return linear_interpolate(y1, y2, t); case mlt_keyframe_smooth_loose: return catmull_rom_interpolate(x0, y0, x1, y1, x2, y2, x3, y3, t, 0.0, 1.0); case mlt_keyframe_smooth_natural: return catmull_rom_interpolate(x0, y0, x1, y1, x2, y2, x3, y3, t, 0.5, -1.0); case mlt_keyframe_smooth_tight: return catmull_rom_interpolate(x0, y0, x1, y1, x2, y2, x3, y3, t, 0.5, 0.0); case mlt_keyframe_sinusoidal_in: return sinusoidal_interpolate(y1, y2, t, ease_in); case mlt_keyframe_sinusoidal_out: return sinusoidal_interpolate(y1, y2, t, ease_out); case mlt_keyframe_sinusoidal_in_out: return sinusoidal_interpolate(y1, y2, t, ease_inout); case mlt_keyframe_quadratic_in: return power_interpolate(y1, y2, t, 2, ease_in); case mlt_keyframe_quadratic_out: return power_interpolate(y1, y2, t, 2, ease_out); case mlt_keyframe_quadratic_in_out: return power_interpolate(y1, y2, t, 2, ease_inout); case mlt_keyframe_cubic_in: return power_interpolate(y1, y2, t, 3, ease_in); case mlt_keyframe_cubic_out: return power_interpolate(y1, y2, t, 3, ease_out); case mlt_keyframe_cubic_in_out: return power_interpolate(y1, y2, t, 3, ease_inout); case mlt_keyframe_quartic_in: return power_interpolate(y1, y2, t, 4, ease_in); case mlt_keyframe_quartic_out: return power_interpolate(y1, y2, t, 4, ease_out); case mlt_keyframe_quartic_in_out: return power_interpolate(y1, y2, t, 4, ease_inout); case mlt_keyframe_quintic_in: return power_interpolate(y1, y2, t, 5, ease_in); case mlt_keyframe_quintic_out: return power_interpolate(y1, y2, t, 5, ease_out); case mlt_keyframe_quintic_in_out: return power_interpolate(y1, y2, t, 5, ease_inout); case mlt_keyframe_exponential_in: return exponential_interpolate(y1, y2, t, ease_in); case mlt_keyframe_exponential_out: return exponential_interpolate(y1, y2, t, ease_out); case mlt_keyframe_exponential_in_out: return exponential_interpolate(y1, y2, t, ease_inout); case mlt_keyframe_circular_in: return circular_interpolate(y1, y2, t, ease_in); case mlt_keyframe_circular_out: return circular_interpolate(y1, y2, t, ease_out); case mlt_keyframe_circular_in_out: return circular_interpolate(y1, y2, t, ease_inout); case mlt_keyframe_back_in: return back_interpolate(y1, y2, t, ease_in); case mlt_keyframe_back_out: return back_interpolate(y1, y2, t, ease_out); case mlt_keyframe_back_in_out: return back_interpolate(y1, y2, t, ease_inout); case mlt_keyframe_elastic_in: return elastic_interpolate(y1, y2, t, ease_in); case mlt_keyframe_elastic_out: return elastic_interpolate(y1, y2, t, ease_out); case mlt_keyframe_elastic_in_out: return elastic_interpolate(y1, y2, t, ease_inout); case mlt_keyframe_bounce_in: return bounce_interpolate(y1, y2, t, ease_in); case mlt_keyframe_bounce_out: return bounce_interpolate(y1, y2, t, ease_out); case mlt_keyframe_bounce_in_out: return bounce_interpolate(y1, y2, t, ease_inout); } return y1; } /** Interpolate a new animation item given a set of other items. * * \private \memberof mlt_animation_s * * \param item an unpopulated animation item to be interpolated. * The frame and keyframe_type fields must already be set. The value for "frame" is the postion * at which the value will be interpolated. The value for "keyframe_type" determines which * interpolation will be used. * \param p a sequential array of 4 animation items. The frame value for item must lie between the frame values for p[1] and p[2]. * \param fps the frame rate, which may be needed for converting a time string to frame units * \param locale the locale, which may be needed for converting a string to a real number * \return true if there was an error */ static int interpolate_item(mlt_animation_item item, mlt_animation_item p[], double fps, mlt_locale_t locale) { int error = 0; double progress = (double) (item->frame - p[1]->frame) / (double) (p[2]->frame - p[1]->frame); if (item->keyframe_type == mlt_keyframe_discrete) { mlt_property_pass(item->property, p[1]->property); } else if (mlt_property_is_color(p[1]->property)) { mlt_color value = {0xff, 0xff, 0xff, 0xff}; mlt_color colors[4]; mlt_color zero = {0xff, 0xff, 0xff, 0xff}; colors[1] = p[1] ? mlt_property_get_color(p[1]->property, fps, locale) : zero; if (p[2]) { colors[0] = p[0] ? mlt_property_get_color(p[0]->property, fps, locale) : zero; colors[2] = p[2] ? mlt_property_get_color(p[2]->property, fps, locale) : zero; colors[3] = p[3] ? mlt_property_get_color(p[3]->property, fps, locale) : zero; value.r = CLAMP(interpolate_value(p[0]->frame, colors[0].r, p[1]->frame, colors[1].r, p[2]->frame, colors[2].r, p[3]->frame, colors[3].r, progress, item->keyframe_type), 0, 255); value.g = CLAMP(interpolate_value(p[0]->frame, colors[0].g, p[1]->frame, colors[1].g, p[2]->frame, colors[2].g, p[3]->frame, colors[3].g, progress, item->keyframe_type), 0, 255); value.b = CLAMP(interpolate_value(p[0]->frame, colors[0].b, p[1]->frame, colors[1].b, p[2]->frame, colors[2].b, p[3]->frame, colors[3].b, progress, item->keyframe_type), 0, 255); value.a = CLAMP(interpolate_value(p[0]->frame, colors[0].a, p[1]->frame, colors[1].a, p[2]->frame, colors[2].a, p[3]->frame, colors[3].a, progress, item->keyframe_type), 0, 255); } else { value = colors[1]; } error = mlt_property_set_color(item->property, value); } else if (mlt_property_is_rect(item->property)) { mlt_rect value = {DBL_MIN, DBL_MIN, DBL_MIN, DBL_MIN, DBL_MIN}; mlt_rect points[4]; mlt_rect zero = {0, 0, 0, 0, 0}; points[1] = p[1] ? mlt_property_get_rect(p[1]->property, locale) : zero; if (p[2]) { points[0] = p[0] ? mlt_property_get_rect(p[0]->property, locale) : zero; points[2] = p[2] ? mlt_property_get_rect(p[2]->property, locale) : zero; points[3] = p[3] ? mlt_property_get_rect(p[3]->property, locale) : zero; value.x = interpolate_value(p[0]->frame, points[0].x, p[1]->frame, points[1].x, p[2]->frame, points[2].x, p[3]->frame, points[3].x, progress, item->keyframe_type); value.y = interpolate_value(p[0]->frame, points[0].y, p[1]->frame, points[1].y, p[2]->frame, points[2].y, p[3]->frame, points[3].y, progress, item->keyframe_type); value.w = interpolate_value(p[0]->frame, points[0].w, p[1]->frame, points[1].w, p[2]->frame, points[2].w, p[3]->frame, points[3].w, progress, item->keyframe_type); value.h = interpolate_value(p[0]->frame, points[0].h, p[1]->frame, points[1].h, p[2]->frame, points[2].h, p[3]->frame, points[3].h, progress, item->keyframe_type); value.o = interpolate_value(p[0]->frame, points[0].o, p[1]->frame, points[1].o, p[2]->frame, points[2].o, p[3]->frame, points[3].o, progress, item->keyframe_type); } else { value = points[1]; } error = mlt_property_set_rect(item->property, value); } else if (mlt_property_is_numeric(p[1]->property, locale)) { double value = 0.0; double points[4]; points[0] = p[0] ? mlt_property_get_double(p[0]->property, fps, locale) : 0; points[1] = p[1] ? mlt_property_get_double(p[1]->property, fps, locale) : 0; points[2] = p[2] ? mlt_property_get_double(p[2]->property, fps, locale) : 0; points[3] = p[3] ? mlt_property_get_double(p[3]->property, fps, locale) : 0; value = p[2] ? interpolate_value(p[0]->frame, points[0], p[1]->frame, points[1], p[2]->frame, points[2], p[3]->frame, points[3], progress, item->keyframe_type) : points[1]; error = mlt_property_set_double(item->property, value); } else { mlt_property_pass(item->property, p[1]->property); } return error; }mlt-7.22.0/src/framework/mlt_animation.h000664 000000 000000 00000007316 14531534050 020155 0ustar00rootroot000000 000000 /** * \file mlt_animation.h * \brief Property Animation class declaration * \see mlt_animation_s * * Copyright (C) 2004-2018 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef MLT_ANIMATION_H #define MLT_ANIMATION_H #include "mlt_property.h" #include "mlt_types.h" /** \brief Animation class * * Once an animation has been constructed using mlt_properties_s, this interface * provides a to query and manipulate the animation except for values. One must * use mlt_properties_s still to get, set, and change values. * * \envvar \em MLT_ANIMATION_TIME_FORMAT the time value string format to use, * defaults to mlt_time_frames. Use the numeric value of mlt_time_format as * the value of this variable. */ /** \brief An animation item that represents a keyframe-property combination. */ struct mlt_animation_item_s { int is_key; /**< a boolean of whether this is a key frame or an interpolated item */ int frame; /**< the frame number for this instance of the property */ mlt_property property; /**< the property for this point in time */ mlt_keyframe_type keyframe_type; /**< the method of interpolation for this key frame */ }; typedef struct mlt_animation_item_s *mlt_animation_item; /**< pointer to an animation item */ extern mlt_animation mlt_animation_new(); extern int mlt_animation_parse( mlt_animation self, const char *data, int length, double fps, mlt_locale_t locale); extern int mlt_animation_refresh(mlt_animation self, const char *data, int length); extern int mlt_animation_get_length(mlt_animation self); extern void mlt_animation_set_length(mlt_animation self, int length); extern int mlt_animation_parse_item(mlt_animation self, mlt_animation_item item, const char *data); extern int mlt_animation_get_item(mlt_animation self, mlt_animation_item item, int position); extern int mlt_animation_insert(mlt_animation self, mlt_animation_item item); extern int mlt_animation_remove(mlt_animation self, int position); extern void mlt_animation_interpolate(mlt_animation self); extern int mlt_animation_next_key(mlt_animation self, mlt_animation_item item, int position); extern int mlt_animation_prev_key(mlt_animation self, mlt_animation_item item, int position); extern char *mlt_animation_serialize_cut_tf(mlt_animation self, int in, int out, mlt_time_format); extern char *mlt_animation_serialize_cut(mlt_animation self, int in, int out); extern char *mlt_animation_serialize_tf(mlt_animation self, mlt_time_format); extern char *mlt_animation_serialize(mlt_animation self); extern int mlt_animation_key_count(mlt_animation self); extern int mlt_animation_key_get(mlt_animation self, mlt_animation_item item, int index); extern void mlt_animation_close(mlt_animation self); extern int mlt_animation_key_set_type(mlt_animation self, int index, mlt_keyframe_type type); extern int mlt_animation_key_set_frame(mlt_animation self, int index, int frame); extern void mlt_animation_shift_frames(mlt_animation self, int shift); extern const char *mlt_animation_get_string(mlt_animation self); #endif mlt-7.22.0/src/framework/mlt_audio.c000664 000000 000000 00000052560 14531534050 017273 0ustar00rootroot000000 000000 /** * \file mlt_audio.c * \brief Audio class * \see mlt_mlt_audio_s * * Copyright (C) 2020 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "mlt_audio.h" #include "mlt_log.h" #include #include /** Allocate a new Audio object. * * \return a new audio object with default values set */ mlt_audio mlt_audio_new() { mlt_audio self = calloc(1, sizeof(struct mlt_audio_s)); self->close = free; return self; } /** Destroy an audio object created by mlt_audio_new(). * * \public \memberof mlt_audio_s * \param self the Audio object */ void mlt_audio_close(mlt_audio self) { if (self) { if (self->release_data) { self->release_data(self->data); } if (self->close) { self->close(self); } } } /** Set the most common values for the audio. * * Less common values will be set to reasonable defaults. * * You should use the \p mlt_audio_calculate_frame_samples to determine the number of samples you want. * \public \memberof mlt_audio_s * \param self the Audio object * \param data the buffer that contains the audio data * \param frequency the sample rate * \param format the audio format * \param samples the number of samples in the data * \param channels the number of audio channels */ void mlt_audio_set_values( mlt_audio self, void *data, int frequency, mlt_audio_format format, int samples, int channels) { self->data = data; self->frequency = frequency; self->format = format; self->samples = samples; self->channels = channels; self->layout = mlt_channel_auto; self->release_data = NULL; self->close = NULL; } /** Get the most common values for the audio. * * \public \memberof mlt_audio_s * \param self the Audio object * \param[out] data the buffer that contains the audio data * \param[out] frequency the sample rate * \param[out] format the audio format * \param[out] samples the number of samples in the data * \param[out] channels the number of audio channels */ void mlt_audio_get_values(mlt_audio self, void **data, int *frequency, mlt_audio_format *format, int *samples, int *channels) { *data = self->data; *frequency = self->frequency; *format = self->format; *samples = self->samples; *channels = self->channels; } /** Allocate the data field based on the other properties of the Audio. * * If the data field is already set, and a destructor function exists, the data * will be released. Else, the data pointer will be overwritten without being * released. * * After this function call, the release_data field will be set and can be used * to release the data when necessary. * * \public \memberof mlt_audio_s * \param self the Audio object */ void mlt_audio_alloc_data(mlt_audio self) { if (!self) return; mlt_audio_free_data(self); int size = mlt_audio_calculate_size(self); self->data = mlt_pool_alloc(size); self->release_data = mlt_pool_release; } /** Free the data field using the destructor function. * * If the constructor function does not exist, the value will be set to NULL * without being released. * * After this function call, the data and release_data fields will be NULL. * * \public \memberof mlt_audio_s * \param self the Audio object */ void mlt_audio_free_data(mlt_audio self) { if (!self) return; if (self->release_data) { self->release_data(self->data); } self->data = NULL; self->release_data = NULL; } /** Calculate the number of bytes needed for the Audio data. * * \public \memberof mlt_audio_s * \param self the Audio object * \return the number of bytes */ int mlt_audio_calculate_size(mlt_audio self) { if (!self) return 0; return mlt_audio_format_size(self->format, self->samples, self->channels); } /** Get the number of planes for the audio type * * \public \memberof mlt_audio_s * \param self the Audio object * \return the number of planes. */ int mlt_audio_plane_count(mlt_audio self) { switch (self->format) { case mlt_audio_none: return 0; case mlt_audio_s16: return 1; case mlt_audio_s32le: return 1; case mlt_audio_s32: return self->channels; case mlt_audio_f32le: return 1; case mlt_audio_float: return self->channels; case mlt_audio_u8: return 1; } return 0; } /** Get the size of an audio plane. * * \public \memberof mlt_audio_s * \param self the Audio object * \return the size of a plane. */ int mlt_audio_plane_size(mlt_audio self) { switch (self->format) { case mlt_audio_none: return 0; case mlt_audio_s16: return self->samples * self->channels * sizeof(int16_t); case mlt_audio_s32le: return self->samples * self->channels * sizeof(int32_t); case mlt_audio_s32: return self->samples * sizeof(int32_t); case mlt_audio_f32le: return self->samples * self->channels * sizeof(float); case mlt_audio_float: return self->samples * sizeof(float); case mlt_audio_u8: return self->samples * self->channels; } return 0; } /** Populate an array of pointers each pointing to the beginning of an audio plane. * * \public \memberof mlt_audio_s * \param self the Audio object * \param[out] planes the array of pointers to populate */ void mlt_audio_get_planes(mlt_audio self, uint8_t **planes) { int plane_count = mlt_audio_plane_count(self); int plane_size = mlt_audio_plane_size(self); int p = 0; for (p = 0; p < plane_count; p++) { planes[p] = (uint8_t *) self->data + (p * plane_size); } } /** Set a range of samples to silence. * * \public \memberof mlt_frame_s * \param self the Audio object * \param samples the new number of samples to silent * \param start the sample to begin the silence * \return none */ void mlt_audio_silence(mlt_audio self, int samples, int start) { if ((start + samples) > self->samples) { mlt_log_error(NULL, "mlt_audio_silence: avoid buffer overrun\n"); return; } switch (self->format) { case mlt_audio_none: mlt_log_error(NULL, "mlt_audio_silence: mlt_audio_none\n"); return; // Interleaved 8bit formats case mlt_audio_u8: { int8_t *s = (int8_t *) self->data + (start * self->channels); int size = self->channels * samples * sizeof(int8_t); memset(s, 127, size); return; } // Interleaved 16bit formats case mlt_audio_s16: { int16_t *s = (int16_t *) self->data + (start * self->channels); int size = self->channels * samples * sizeof(int16_t); memset(s, 0, size); return; } // Interleaved 32bit formats case mlt_audio_s32le: case mlt_audio_f32le: { int32_t *s = (int32_t *) self->data + (start * self->channels); int size = self->channels * samples * sizeof(int32_t); memset(s, 0, size); return; } // Planer 32bit formats case mlt_audio_s32: case mlt_audio_float: { int p = 0; for (p = 0; p < self->channels; p++) { int32_t *s = (int32_t *) self->data + (p * self->samples) + start; int size = samples * sizeof(int32_t); memset(s, 0, size); } return; } } } /** Shrink the audio to the new number of samples. * * Existing samples will be moved as necessary to ensure that the audio planes * immediately follow each other. The samples field will be updated to match the * new number. * * \public \memberof mlt_audio_s * \param self the Audio object * \param samples the new number of samples to shrink to */ void mlt_audio_shrink(mlt_audio self, int samples) { int plane_count = mlt_audio_plane_count(self); if (samples >= self->samples || samples < 0) { // Nothing to do; } else if (plane_count == 1 || samples == 0) { // No need to move any data. self->samples = samples; } if (plane_count > 1) { // Move data to remove gaps between planes. size_t src_plane_size = mlt_audio_plane_size(self); self->samples = samples; size_t dst_plane_size = mlt_audio_plane_size(self); // The first channel will always be in the correct place (0). int p = 1; for (p = 1; p < plane_count; p++) { uint8_t *src = (uint8_t *) self->data + (p * src_plane_size); uint8_t *dst = (uint8_t *) self->data + (p * dst_plane_size); memmove(dst, src, dst_plane_size); } } } /** Reverse the audio samples. * * \public \memberof mlt_audio_s * \param self the Audio object */ void mlt_audio_reverse(mlt_audio self) { int c = 0; if (!self || !self->data || self->samples <= 0) return; switch (self->format) { // Interleaved 8bit formats case mlt_audio_u8: { int8_t tmp; for (c = 0; c < self->channels; c++) { // Pointer to first sample int8_t *a = (int8_t *) self->data + c; // Pointer to last sample int8_t *b = (int8_t *) self->data + ((self->samples - 1) * self->channels) + c; while (a < b) { tmp = *a; *a = *b; *b = tmp; a += self->channels; b -= self->channels; } } break; } // Interleaved 16bit formats case mlt_audio_s16: { int16_t tmp; for (c = 0; c < self->channels; c++) { // Pointer to first sample int16_t *a = (int16_t *) self->data + c; // Pointer to last sample int16_t *b = (int16_t *) self->data + ((self->samples - 1) * self->channels) + c; while (a < b) { tmp = *a; *a = *b; *b = tmp; a += self->channels; b -= self->channels; } } break; } // Interleaved 32bit formats case mlt_audio_s32le: case mlt_audio_f32le: { int32_t tmp; for (c = 0; c < self->channels; c++) { // Pointer to first sample int32_t *a = (int32_t *) self->data + c; // Pointer to last sample int32_t *b = (int32_t *) self->data + ((self->samples - 1) * self->channels) + c; while (a < b) { tmp = *a; *a = *b; *b = tmp; a += self->channels; b -= self->channels; } } break; } // Planer 32bit formats case mlt_audio_s32: case mlt_audio_float: { int32_t tmp; for (c = 0; c < self->channels; c++) { // Pointer to first sample int32_t *a = (int32_t *) self->data + (c * self->samples); // Pointer to last sample int32_t *b = (int32_t *) self->data + ((c + 1) * self->samples) - 1; while (a < b) { tmp = *a; *a = *b; *b = tmp; a++; b--; } } break; } case mlt_audio_none: break; } } /** Copy audio samples from src to dst. * * \public \memberof mlt_frame_s * \param dst the destination object * \param src the source object * \param samples the number of samples to copy * \param src_offset the number of samples to skip from the source * \param dst_offset the number of samples to skip from the destination * \return none */ void mlt_audio_copy(mlt_audio dst, mlt_audio src, int samples, int src_start, int dst_start) { if ((dst_start + samples) > dst->samples) { mlt_log_error(NULL, "mlt_audio_copy: avoid dst buffer overrun\n"); return; } if ((src_start + samples) > src->samples) { mlt_log_error(NULL, "mlt_audio_copy: avoid src buffer overrun\n"); return; } if (src->format != dst->format || src->channels != dst->channels) { mlt_log_error(NULL, "mlt_audio_copy: src/dst mismatch\n"); return; } switch (src->format) { case mlt_audio_none: mlt_log_error(NULL, "mlt_audio_copy: mlt_audio_none\n"); return; // Interleaved 8bit formats case mlt_audio_u8: { int8_t *s = (int8_t *) src->data + (src_start * src->channels); int8_t *d = (int8_t *) dst->data + (dst_start * dst->channels); int size = src->channels * samples * sizeof(int8_t); memmove(d, s, size); return; } // Interleaved 16bit formats case mlt_audio_s16: { int16_t *s = (int16_t *) src->data + (src_start * src->channels); int16_t *d = (int16_t *) dst->data + (dst_start * dst->channels); int size = src->channels * samples * sizeof(int16_t); memmove(d, s, size); return; } // Interleaved 32bit formats case mlt_audio_s32le: case mlt_audio_f32le: { int32_t *s = (int32_t *) src->data + (src_start * src->channels); int32_t *d = (int32_t *) dst->data + (dst_start * dst->channels); int size = src->channels * samples * sizeof(int32_t); memmove(d, s, size); return; } // Planer 32bit formats case mlt_audio_s32: case mlt_audio_float: { int p = 0; for (p = 0; p < src->channels; p++) { int32_t *s = (int32_t *) src->data + (p * src->samples) + src_start; int32_t *d = (int32_t *) dst->data + (p * dst->samples) + dst_start; int size = samples * sizeof(int32_t); memmove(d, s, size); } return; } } } /** Determine the number of samples that belong in a frame at a time position. * * \public \memberof mlt_frame_s * \param fps the frame rate * \param frequency the sample rate * \param position the time position * \return the number of samples per channel */ int mlt_audio_calculate_frame_samples(float fps, int frequency, int64_t position) { /* Compute the cumulative number of samples until the start of this frame and the cumulative number of samples until the start of the next frame. Round each to the nearest integer and take the difference to determine the number of samples in this frame. This approach should prevent rounding errors that can accumulate over a large number of frames causing A/V sync problems. */ return mlt_audio_calculate_samples_to_position(fps, frequency, position + 1) - mlt_audio_calculate_samples_to_position(fps, frequency, position); } /** Determine the number of samples that belong before a time position. * * \public \memberof mlt_frame_s * \param fps the frame rate * \param frequency the sample rate * \param position the time position * \return the number of samples per channel */ int64_t mlt_audio_calculate_samples_to_position(float fps, int frequency, int64_t position) { int64_t samples = 0; if (fps) { samples = (int64_t) ((double) position * (double) frequency / (double) fps + (position < 0 ? -0.5 : 0.5)); } return samples; } /** Get the short name for an audio format. * * You do not need to deallocate the returned string. * \public \memberof mlt_frame_s * \param format an audio format enum * \return a string for the name of the image format */ const char *mlt_audio_format_name(mlt_audio_format format) { switch (format) { case mlt_audio_none: return "none"; case mlt_audio_s16: return "s16"; case mlt_audio_s32: return "s32"; case mlt_audio_s32le: return "s32le"; case mlt_audio_float: return "float"; case mlt_audio_f32le: return "f32le"; case mlt_audio_u8: return "u8"; } return "invalid"; } /** Get the amount of bytes needed for a block of audio. * * \public \memberof mlt_frame_s * \param format an audio format enum * \param samples the number of samples per channel * \param channels the number of channels * \return the number of bytes */ int mlt_audio_format_size(mlt_audio_format format, int samples, int channels) { switch (format) { case mlt_audio_none: return 0; case mlt_audio_s16: return samples * channels * sizeof(int16_t); case mlt_audio_s32le: case mlt_audio_s32: return samples * channels * sizeof(int32_t); case mlt_audio_f32le: case mlt_audio_float: return samples * channels * sizeof(float); case mlt_audio_u8: return samples * channels; } return 0; } /** Get the short name for a channel layout. * * You do not need to deallocate the returned string. * \public \member of mlt_frame_s * \param layout the channel layout * \return a string for the name of the channel layout */ const char *mlt_audio_channel_layout_name(mlt_channel_layout layout) { switch (layout) { case mlt_channel_auto: return "auto"; case mlt_channel_independent: return "independent"; case mlt_channel_mono: return "mono"; case mlt_channel_stereo: return "stereo"; case mlt_channel_2p1: return "2.1"; case mlt_channel_3p0: return "3.0"; case mlt_channel_3p0_back: return "3.0(back)"; case mlt_channel_4p0: return "4.0"; case mlt_channel_quad_back: return "quad"; case mlt_channel_quad_side: return "quad(side)"; case mlt_channel_3p1: return "3.1"; case mlt_channel_5p0_back: return "5.0"; case mlt_channel_5p0: return "5.0(side)"; case mlt_channel_4p1: return "4.1"; case mlt_channel_5p1_back: return "5.1"; case mlt_channel_5p1: return "5.1(side)"; case mlt_channel_6p0: return "6.0"; case mlt_channel_6p0_front: return "6.0(front)"; case mlt_channel_hexagonal: return "hexagonal"; case mlt_channel_6p1: return "6.1"; case mlt_channel_6p1_back: return "6.1(back)"; case mlt_channel_6p1_front: return "6.1(front)"; case mlt_channel_7p0: return "7.0"; case mlt_channel_7p0_front: return "7.0(front)"; case mlt_channel_7p1: return "7.1"; case mlt_channel_7p1_wide_side: return "7.1(wide-side)"; case mlt_channel_7p1_wide_back: return "7.1(wide)"; } return "invalid"; } /** Get the id of channel layout from short name. * * \public \memberof mlt_frame_s * \param name the channel layout short name * \return a channel layout */ mlt_channel_layout mlt_audio_channel_layout_id(const char *name) { if (name) { mlt_channel_layout c; for (c = mlt_channel_auto; c <= mlt_channel_7p1_wide_back; c++) { const char *v = mlt_audio_channel_layout_name(c); if (!strcmp(v, name)) return c; } } return mlt_channel_auto; } /** Get the number of channels for a channel layout. * * \public \memberof mlt_frame_s * \param layout the channel layout * \return the number of channels for the channel layout */ int mlt_audio_channel_layout_channels(mlt_channel_layout layout) { switch (layout) { case mlt_channel_auto: return 0; case mlt_channel_independent: return 0; case mlt_channel_mono: return 1; case mlt_channel_stereo: return 2; case mlt_channel_2p1: return 3; case mlt_channel_3p0: return 3; case mlt_channel_3p0_back: return 3; case mlt_channel_4p0: return 4; case mlt_channel_quad_back: return 4; case mlt_channel_quad_side: return 4; case mlt_channel_3p1: return 4; case mlt_channel_5p0_back: return 5; case mlt_channel_5p0: return 5; case mlt_channel_4p1: return 5; case mlt_channel_5p1_back: return 6; case mlt_channel_5p1: return 6; case mlt_channel_6p0: return 6; case mlt_channel_6p0_front: return 6; case mlt_channel_hexagonal: return 6; case mlt_channel_6p1: return 7; case mlt_channel_6p1_back: return 7; case mlt_channel_6p1_front: return 7; case mlt_channel_7p0: return 7; case mlt_channel_7p0_front: return 7; case mlt_channel_7p1: return 8; case mlt_channel_7p1_wide_back: return 8; case mlt_channel_7p1_wide_side: return 8; } return 0; } /** Get a default channel layout for a given number of channels. * * \public \memberof mlt_frame_s * \param channels the number of channels * \return the default channel layout */ mlt_channel_layout mlt_audio_channel_layout_default(int channels) { mlt_channel_layout c; for (c = mlt_channel_mono; c <= mlt_channel_7p1_wide_back; c++) { if (mlt_audio_channel_layout_channels(c) == channels) return c; } return mlt_channel_independent; } mlt-7.22.0/src/framework/mlt_audio.h000664 000000 000000 00000005663 14531534050 017302 0ustar00rootroot000000 000000 /** * \file mlt_audio.h * \brief Audio class * \see mlt_audio_s * * Copyright (C) 2020 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef MLT_AUDIO_H #define MLT_AUDIO_H #include "mlt_types.h" /** \brief Audio class * * Audio is the data object that represents audio for a period of time. */ struct mlt_audio_s { void *data; int frequency; mlt_audio_format format; int samples; int channels; mlt_channel_layout layout; mlt_destructor release_data; mlt_destructor close; }; extern mlt_audio mlt_audio_new(); extern void mlt_audio_close(mlt_audio self); extern void mlt_audio_set_values( mlt_audio self, void *data, int frequency, mlt_audio_format format, int samples, int channels); extern void mlt_audio_get_values(mlt_audio self, void **data, int *frequency, mlt_audio_format *format, int *samples, int *channels); extern void mlt_audio_alloc_data(mlt_audio self); extern void mlt_audio_free_data(mlt_audio self); extern int mlt_audio_calculate_size(mlt_audio self); extern int mlt_audio_plane_count(mlt_audio self); extern int mlt_audio_plane_size(mlt_audio self); extern void mlt_audio_get_planes(mlt_audio self, uint8_t **planes); extern void mlt_audio_silence(mlt_audio self, int samples, int start); extern void mlt_audio_shrink(mlt_audio self, int samples); extern void mlt_audio_reverse(mlt_audio self); extern void mlt_audio_copy(mlt_audio dst, mlt_audio src, int samples, int src_start, int dst_start); extern int mlt_audio_calculate_frame_samples(float fps, int frequency, int64_t position); extern int64_t mlt_audio_calculate_samples_to_position(float fps, int frequency, int64_t position); extern const char *mlt_audio_format_name(mlt_audio_format format); extern int mlt_audio_format_size(mlt_audio_format format, int samples, int channels); extern const char *mlt_audio_channel_layout_name(mlt_channel_layout layout); extern mlt_channel_layout mlt_audio_channel_layout_id(const char *name); extern int mlt_audio_channel_layout_channels(mlt_channel_layout layout); extern mlt_channel_layout mlt_audio_channel_layout_default(int channels); #endif mlt-7.22.0/src/framework/mlt_cache.c000664 000000 000000 00000050614 14531534050 017233 0ustar00rootroot000000 000000 /** * \file mlt_cache.c * \brief least recently used cache * \see mlt_profile_s * * Copyright (C) 2007-2023 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "mlt_cache.h" #include "mlt_frame.h" #include "mlt_log.h" #include "mlt_properties.h" #include "mlt_types.h" #include #include /** the maximum number of data objects to cache per line */ #define MAX_CACHE_SIZE (200) /** the default number of data objects to cache per line */ #define DEFAULT_CACHE_SIZE (4) /** \brief Cache item class * * A cache item is a structure holding information about a data object including * a reference count that is used to control its lifetime. When you get a * a cache item from the cache, you hold a reference that prevents the data * from being released when the cache is full and something new is added. * When you close the cache item, the reference count is decremented. * The data object is destroyed when all cache items are closed and the cache * releases its reference. */ typedef struct mlt_cache_item_s { mlt_cache cache; /**< a reference to the cache to which this belongs */ void *object; /**< a parent object to the cache data that uniquely identifies this cached item */ void *data; /**< the opaque pointer to the cached data */ int size; /**< the size of the cached data */ int refcount; /**< a reference counter to control when destructor is called */ mlt_destructor destructor; /**< a function to release or destroy the cached data */ } mlt_cache_item_s; /** \brief Cache class * * This is a utility class for implementing a Least Recently Used (LRU) cache * of data blobs indexed by the address of some other object (e.g., a service). * Instead of sorting and manipulating linked lists, it tries to be simple and * elegant by copying pointers between two arrays of fixed size to shuffle the * order of elements. * * This class is useful if you have a service that wants to cache something * somewhat large, but will not scale if there are many instances of the service. * Of course, the service will need to know how to recreate the cached element * if it gets flushed from the cache, * * The most obvious examples are the pixbuf and qimage producers that cache their * respective objects representing a picture read from a file. If the picture * is no longer in the cache, it can simply re-read it from file. However, a * picture is often repeated over many frames and makes sense to cache instead * of continually reading, parsing, and decoding. On the other hand, you might * want to load hundreds of pictures as individual producers, which would use * a lot of memory if every picture is held in memory! */ struct mlt_cache_s { int count; /**< the number of items currently in the cache */ int size; /**< the maximum number of items permitted in the cache <= \p MAX_CACHE_SIZE */ int is_frames; /**< indicates if this cache is used to cache frames */ void **current; /**< pointer to the current array of pointers */ void *A[MAX_CACHE_SIZE]; void *B[MAX_CACHE_SIZE]; pthread_mutex_t mutex; /**< a mutex to prevent multi-threaded race conditions */ mlt_properties active; /**< a list of cache items some of which may no longer be in \p current but to which there are outstanding references */ mlt_properties garbage; /**< a list cache items pending release. A cache item is copied to this list when it is updated but there are outstanding references to the old data object. */ }; /** Get the data pointer from the cache item. * * \public \memberof mlt_cache_s * \param item a cache item * \param[out] size the number of bytes pointed at, if supplied when putting the data into the cache * \return the data pointer */ void *mlt_cache_item_data(mlt_cache_item item, int *size) { if (size && item) *size = item->size; return item ? item->data : NULL; } /** Close a cache item given its parent object pointer. * * \private \memberof mlt_cache_s * \param cache a cache * \param object the object to which the data object belongs * \param data the data object, which might be in the garbage list (optional) */ static void cache_object_close(mlt_cache cache, void *object, void *data) { char key[19]; if (cache->is_frames) { // Frame caches are easy - just close the object as mlt_frame. mlt_frame_close(object); return; } // Fetch the cache item from the active list by its owner's address sprintf(key, "%p", object); mlt_cache_item item = mlt_properties_get_data(cache->active, key, NULL); if (item) { mlt_log(NULL, MLT_LOG_DEBUG, "%s: item %p object %p data %p refcount %d\n", __FUNCTION__, item, item->object, item->data, item->refcount); if (item->destructor && --item->refcount <= 0) { // Destroy the data object item->destructor(item->data); item->data = NULL; item->destructor = NULL; // Do not dispose of the cache item because it could likely be used // again. } } // Fetch the cache item from the garbage collection by its data address if (data) { sprintf(key, "%p", data); item = mlt_properties_get_data(cache->garbage, key, NULL); if (item) { mlt_log(NULL, MLT_LOG_DEBUG, "collecting garbage item %p object %p data %p refcount %d\n", item, item->object, item->data, item->refcount); if (item->destructor && --item->refcount <= 0) { item->destructor(item->data); item->data = NULL; item->destructor = NULL; // We do not need the garbage-collected cache item mlt_properties_set_data(cache->garbage, key, NULL, 0, NULL, NULL); } } } } /** Close a cache item. * * Release a reference and call the destructor on the data object when all * references are released. * * \public \memberof mlt_cache_item_s * \param item a cache item */ void mlt_cache_item_close(mlt_cache_item item) { if (item) { pthread_mutex_lock(&item->cache->mutex); cache_object_close(item->cache, item->object, item->data); pthread_mutex_unlock(&item->cache->mutex); } } /** Create a new cache. * * The default size is \p DEFAULT_CACHE_SIZE. * \public \memberof mlt_cache_s * \return a new cache or NULL if there was an error */ mlt_cache mlt_cache_init() { mlt_cache result = calloc(1, sizeof(struct mlt_cache_s)); if (result) { result->size = DEFAULT_CACHE_SIZE; result->current = result->A; pthread_mutex_init(&result->mutex, NULL); result->active = mlt_properties_new(); result->garbage = mlt_properties_new(); } return result; } /** Set the number of items to cache. * * This must be called before using the cache. The size can not be more * than \p MAX_CACHE_SIZE. * \public \memberof mlt_cache_s * \param cache the cache to adjust * \param size the new size of the cache */ void mlt_cache_set_size(mlt_cache cache, int size) { if (size <= MAX_CACHE_SIZE) cache->size = size; } /** Get the number of possible cache items. * * \public \memberof mlt_cache_s * \param cache the cache to check * \return the current maximum size of the cache */ int mlt_cache_get_size(mlt_cache cache) { return cache->size; } /** Destroy a cache. * * \public \memberof mlt_cache_s * \param cache the cache to destroy */ void mlt_cache_close(mlt_cache cache) { if (cache) { while (cache->count--) { void *object = cache->current[cache->count]; mlt_log(NULL, MLT_LOG_DEBUG, "%s: %d = %p\n", __FUNCTION__, cache->count, object); cache_object_close(cache, object, NULL); } mlt_properties_close(cache->active); mlt_properties_close(cache->garbage); pthread_mutex_destroy(&cache->mutex); free(cache); } } /** Remove cache entries for an object. * * \public \memberof mlt_cache_s * \param cache a cache * \param object the object that owns the cached data */ void mlt_cache_purge(mlt_cache cache, void *object) { if (!cache) return; pthread_mutex_lock(&cache->mutex); if (cache && object) { int i, j; void **alt = cache->current == cache->A ? cache->B : cache->A; for (i = 0, j = 0; i < cache->count; i++) { void *o = cache->current[i]; if (o == object) { cache_object_close(cache, o, NULL); } else { alt[j++] = o; } } cache->count = j; cache->current = alt; } pthread_mutex_unlock(&cache->mutex); } /** Shuffle the cache entries between the two arrays and return the cache entry for an object. * * \private \memberof mlt_cache_s * \param cache a cache object * \param object the object that owns the cached data * \return a cache entry if there was a hit or NULL for a miss */ static void **shuffle_get_hit(mlt_cache cache, void *object) { int i = cache->count; int j = cache->count - 1; void **hit = NULL; void **alt = cache->current == cache->A ? cache->B : cache->A; if (cache->count > 0 && cache->count < cache->size) { // first determine if we have a hit while (i-- && !hit) { void **o = &cache->current[i]; if (*o == object) hit = o; } // if there was no hit, we will not be shuffling out an entry // and are still filling the cache if (!hit) ++j; // reset these i = cache->count; hit = NULL; } // shuffle the existing entries to the alternate array while (i--) { void **o = &cache->current[i]; if (!hit && *o == object) { hit = o; } else if (j > 0) { alt[--j] = *o; // mlt_log( NULL, MLT_LOG_DEBUG, "%s: shuffle %d = %p\n", __FUNCTION__, j, alt[j] ); } } return hit; } /** Put a chunk of data in the cache. * * This function and mlt_cache_get() are not scalable with a large volume * of unique \p object parameter values. Therefore, it does not make sense * to use it for a frame/image cache using the frame position for \p object. * Instead, use mlt_cache_put_frame() for that. * * \public \memberof mlt_cache_s * \param cache a cache object * \param object the object to which this data belongs * \param data an opaque pointer to the data to cache * \param size the size of the data in bytes * \param destructor a pointer to a function that can destroy or release a reference to the data. */ void mlt_cache_put(mlt_cache cache, void *object, void *data, int size, mlt_destructor destructor) { pthread_mutex_lock(&cache->mutex); void **hit = shuffle_get_hit(cache, object); void **alt = cache->current == cache->A ? cache->B : cache->A; // add the object to the cache if (hit) { // release the old data cache_object_close(cache, *hit, NULL); // the MRU end gets the updated data hit = &alt[cache->count - 1]; } else if (cache->count < cache->size) { // more room in cache, add it to MRU end hit = &alt[cache->count++]; } else { // release the entry at the LRU end cache_object_close(cache, cache->current[0], NULL); // The MRU end gets the new item hit = &alt[cache->count - 1]; } *hit = object; mlt_log(NULL, MLT_LOG_DEBUG, "%s: put %d = %p, %p\n", __FUNCTION__, cache->count - 1, object, data); // Fetch the cache item char key[19]; sprintf(key, "%p", object); mlt_cache_item item = mlt_properties_get_data(cache->active, key, NULL); if (!item) { item = calloc(1, sizeof(mlt_cache_item_s)); if (item) mlt_properties_set_data(cache->active, key, item, 0, free, NULL); } if (item) { // If updating the cache item but not all references are released // copy the item to the garbage collection. if (item->refcount > 0 && item->data) { mlt_cache_item orphan = calloc(1, sizeof(mlt_cache_item_s)); if (orphan) { mlt_log(NULL, MLT_LOG_DEBUG, "adding to garbage collection object %p data %p\n", item->object, item->data); *orphan = *item; sprintf(key, "%p", orphan->data); // We store in the garbage collection by data address, not the owner's! mlt_properties_set_data(cache->garbage, key, orphan, 0, free, NULL); } } // Set/update the cache item item->cache = cache; item->object = object; item->data = data; item->size = size; item->destructor = destructor; item->refcount = 1; } // swap the current array cache->current = alt; pthread_mutex_unlock(&cache->mutex); } /** Get a chunk of data from the cache. * * \public \memberof mlt_cache_s * \param cache a cache object * \param object the object for which you are trying to locate the data * \return a mlt_cache_item if found or NULL if not found or has been flushed from the cache */ mlt_cache_item mlt_cache_get(mlt_cache cache, void *object) { mlt_cache_item result = NULL; pthread_mutex_lock(&cache->mutex); void **hit = shuffle_get_hit(cache, object); void **alt = cache->current == cache->A ? cache->B : cache->A; if (hit) { // copy the hit to the MRU end alt[cache->count - 1] = *hit; hit = &alt[cache->count - 1]; char key[19]; sprintf(key, "%p", *hit); result = mlt_properties_get_data(cache->active, key, NULL); if (result && result->data) { result->refcount++; mlt_log(NULL, MLT_LOG_DEBUG, "%s: get %d = %p, %p\n", __FUNCTION__, cache->count - 1, *hit, result->data); } // swap the current array cache->current = alt; } pthread_mutex_unlock(&cache->mutex); return result; } /** Shuffle the cache entries between the two arrays and return the frame for a position. * * \private \memberof mlt_cache_s * \param cache a cache object * \param position the position of the frame that you want * \return a frame if there was a hit or NULL for a miss */ static mlt_frame *shuffle_get_frame(mlt_cache cache, mlt_position position) { int i = cache->count; int j = cache->count - 1; mlt_frame *hit = NULL; mlt_frame *alt = (mlt_frame *) (cache->current == cache->A ? cache->B : cache->A); if (cache->count > 0 && cache->count < cache->size) { // first determine if we have a hit while (i-- && !hit) { mlt_frame *o = (mlt_frame *) &cache->current[i]; if (mlt_frame_original_position(*o) == position) hit = o; } // if there was no hit, we will not be shuffling out an entry // and are still filling the cache if (!hit) ++j; // reset these i = cache->count; hit = NULL; } // shuffle the existing entries to the alternate array while (i--) { mlt_frame *o = (mlt_frame *) &cache->current[i]; if (!hit && mlt_frame_original_position(*o) == position) { hit = o; } else if (j > 0) { alt[--j] = *o; // mlt_log( NULL, MLT_LOG_DEBUG, "%s: shuffle %d = %p\n", __FUNCTION__, j, alt[j] ); } } return hit; } static void cache_put_frame(mlt_cache cache, mlt_frame frame, int audio, int image) { pthread_mutex_lock(&cache->mutex); mlt_frame *hit = shuffle_get_frame(cache, mlt_frame_original_position(frame)); mlt_frame *alt = (mlt_frame *) (cache->current == cache->A ? cache->B : cache->A); // add the frame to the cache if (hit) { // release the old data mlt_frame_close(*hit); // the MRU end gets the updated data hit = &alt[cache->count - 1]; } else if (cache->count < cache->size) { // more room in cache, add it to MRU end hit = &alt[cache->count++]; } else { // release the entry at the LRU end mlt_frame_close(cache->current[0]); // The MRU end gets the new item hit = &alt[cache->count - 1]; } if (audio && image) { *hit = mlt_frame_clone(frame, 1); } else if (audio) { *hit = mlt_frame_clone_audio(frame, 1); } else if (image) { *hit = mlt_frame_clone_image(frame, 1); } mlt_log(NULL, MLT_LOG_DEBUG, "%s: put %d = %p\n", __FUNCTION__, cache->count - 1, frame); // swap the current array cache->current = (void **) alt; cache->is_frames = 1; pthread_mutex_unlock(&cache->mutex); } /** Put a frame in the cache with audio and video. * * Unlike mlt_cache_put() this version is more suitable for caching frames * and their data - like images. However, this version does not use reference * counting and garbage collection. Rather, frames are cloned with deep copy * to avoid those things. * * \public \memberof mlt_cache_s * \param cache a cache object * \param frame the frame to cache * \see mlt_frame_get_frame */ void mlt_cache_put_frame(mlt_cache cache, mlt_frame frame) { cache_put_frame(cache, frame, 1, 1); } /** Put a frame in the cache with audio. * * Unlike mlt_cache_put() this version is more suitable for caching frames * and their data - like images. However, this version does not use reference * counting and garbage collection. Rather, frames are cloned with deep copy * to avoid those things. * * \public \memberof mlt_cache_s * \param cache a cache object * \param frame the frame to cache * \see mlt_frame_get_frame */ void mlt_cache_put_frame_audio(mlt_cache cache, mlt_frame frame) { cache_put_frame(cache, frame, 1, 0); } /** Put a frame in the cache with image. * * Unlike mlt_cache_put() this version is more suitable for caching frames * and their data - like images. However, this version does not use reference * counting and garbage collection. Rather, frames are cloned with deep copy * to avoid those things. * * \public \memberof mlt_cache_s * \param cache a cache object * \param frame the frame to cache * \see mlt_frame_get_frame */ void mlt_cache_put_frame_image(mlt_cache cache, mlt_frame frame) { cache_put_frame(cache, frame, 0, 1); } /** Get a frame from the cache. * * You must call mlt_frame_close() on the frame you receive from this. * * \public \memberof mlt_cache_s * \param cache a cache object * \param position the position of the frame that you want * \return a frame if found or NULL if not found or has been flushed from the cache * \see mlt_frame_put_frame */ mlt_frame mlt_cache_get_frame(mlt_cache cache, mlt_position position) { mlt_frame result = NULL; pthread_mutex_lock(&cache->mutex); mlt_frame *hit = shuffle_get_frame(cache, position); mlt_frame *alt = (mlt_frame *) (cache->current == cache->A ? cache->B : cache->A); if (hit) { // copy the hit to the MRU end alt[cache->count - 1] = *hit; hit = &alt[cache->count - 1]; result = mlt_frame_clone(*hit, 1); mlt_log(NULL, MLT_LOG_DEBUG, "%s: get %d = %p\n", __FUNCTION__, cache->count - 1, *hit); // swap the current array cache->current = (void **) alt; } pthread_mutex_unlock(&cache->mutex); return result; } mlt-7.22.0/src/framework/mlt_cache.h000664 000000 000000 00000003375 14531534050 017242 0ustar00rootroot000000 000000 /** * \file mlt_cache.h * \brief least recently used cache * \see mlt_cache_s * * Copyright (C) 2007-2023 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef MLT_CACHE_H #define MLT_CACHE_H #include "mlt_types.h" extern void *mlt_cache_item_data(mlt_cache_item item, int *size); extern void mlt_cache_item_close(mlt_cache_item item); extern mlt_cache mlt_cache_init(); extern void mlt_cache_set_size(mlt_cache cache, int size); extern int mlt_cache_get_size(mlt_cache cache); extern void mlt_cache_close(mlt_cache cache); extern void mlt_cache_purge(mlt_cache cache, void *object); extern void mlt_cache_put( mlt_cache cache, void *object, void *data, int size, mlt_destructor destructor); extern mlt_cache_item mlt_cache_get(mlt_cache cache, void *object); extern void mlt_cache_put_frame(mlt_cache cache, mlt_frame frame); extern void mlt_cache_put_frame_audio(mlt_cache cache, mlt_frame frame); extern void mlt_cache_put_frame_image(mlt_cache cache, mlt_frame frame); extern mlt_frame mlt_cache_get_frame(mlt_cache cache, mlt_position position); #endif mlt-7.22.0/src/framework/mlt_chain.c000664 000000 000000 00000050051 14531534050 017245 0ustar00rootroot000000 000000 /** * \file mlt_chain.c * \brief link service class * \see mlt_chain_s * * Copyright (C) 2020-2023 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "mlt_chain.h" #include "mlt_factory.h" #include "mlt_frame.h" #include "mlt_log.h" #include "mlt_tokeniser.h" #include #include #include /** \brief private service definition */ typedef struct { int link_count; int link_size; mlt_link *links; mlt_producer source; mlt_profile source_profile; mlt_properties source_parameters; mlt_producer begin; int relink_required; } mlt_chain_base; /* Forward references to static methods. */ static int producer_get_frame(mlt_producer parent, mlt_frame_ptr frame, int index); static int producer_probe(mlt_producer parent); static void relink_chain(mlt_chain self); static void chain_property_changed(mlt_service owner, mlt_chain self, char *name); static void source_property_changed(mlt_service owner, mlt_chain self, char *name); /** Construct a chain. * * \public \memberof mlt_chain_s * \return the new chain */ mlt_chain mlt_chain_init(mlt_profile profile) { mlt_chain self = calloc(1, sizeof(struct mlt_chain_s)); if (self != NULL) { mlt_producer producer = &self->parent; if (mlt_producer_init(producer, self) == 0) { mlt_properties properties = MLT_PRODUCER_PROPERTIES(producer); mlt_properties_set(properties, "mlt_type", "chain"); mlt_properties_clear(properties, "resource"); mlt_properties_clear(properties, "mlt_service"); mlt_properties_clear(properties, "in"); mlt_properties_clear(properties, "out"); mlt_properties_clear(properties, "length"); producer->get_frame = producer_get_frame; producer->close = (mlt_destructor) mlt_chain_close; producer->close_object = self; mlt_properties_set_data(properties, "mlt_producer_probe", producer_probe, 0, NULL, NULL); mlt_service_set_profile(MLT_CHAIN_SERVICE(self), profile); // Generate local space self->local = calloc(1, sizeof(mlt_chain_base)); mlt_chain_base *base = self->local; base->source_profile = NULL; // Listen to property changes to pass along to the source mlt_events_listen(MLT_CHAIN_PROPERTIES(self), self, "property-changed", (mlt_listener) chain_property_changed); } else { free(self); self = NULL; } } return self; } /** Set the source producer. * * \public \memberof mlt_chain_s * \param self a chain * \param source the new source producer */ void mlt_chain_set_source(mlt_chain self, mlt_producer source) { int error = self == NULL || source == NULL; if (error == 0) { mlt_chain_base *base = self->local; mlt_properties source_properties = MLT_PRODUCER_PROPERTIES(source); int n = 0; int i = 0; // Clean up from previous source mlt_producer_close(base->source); mlt_properties_close(base->source_parameters); mlt_profile_close(base->source_profile); // Save the source producer base->source = source; mlt_properties_inc_ref(source_properties); // Save the native source producer frame rate base->source_profile = mlt_profile_clone(mlt_service_profile(MLT_CHAIN_SERVICE(self))); mlt_frame frame = NULL; if (!mlt_properties_exists(source_properties, "meta.media.frame_rate_num") || !mlt_properties_exists(source_properties, "meta.media.frame_rate_den")) { mlt_service_get_frame(MLT_PRODUCER_SERVICE(source), &frame, 0); mlt_frame_close(frame); } if (mlt_properties_get_int(source_properties, "meta.media.frame_rate_num") > 0 && mlt_properties_get_int(source_properties, "meta.media.frame_rate_den") > 0) { base->source_profile->frame_rate_num = mlt_properties_get_int(source_properties, "meta.media.frame_rate_num"); base->source_profile->frame_rate_den = mlt_properties_get_int(source_properties, "meta.media.frame_rate_den"); } // Create a list of all parameters used by the source producer so that // they can be passed between the source producer and this chain. base->source_parameters = mlt_properties_new(); mlt_repository repository = mlt_factory_repository(); char *source_metadata_name = strdup(mlt_properties_get(source_properties, "mlt_service")); // If the service name ends in "-novalidate", then drop the ending and search for the metadata of the main service. // E.g. "avformat-novalidate" -> "avformat" char *novalidate_ptr = strstr(source_metadata_name, "-novalidate"); if (novalidate_ptr) *novalidate_ptr = '\0'; mlt_properties source_metadata = mlt_repository_metadata(repository, mlt_service_producer_type, source_metadata_name); free(source_metadata_name); if (source_metadata) { mlt_properties params = (mlt_properties) mlt_properties_get_data(source_metadata, "parameters", NULL); if (params) { n = mlt_properties_count(params); for (i = 0; i < n; i++) { mlt_properties param = (mlt_properties) mlt_properties_get_data(params, mlt_properties_get_name(params, i), NULL); char *identifier = mlt_properties_get(param, "identifier"); if (identifier) { // Set the value to 1 to indicate the parameter exists. mlt_properties_set_int(base->source_parameters, identifier, 1); } } } } // Pass parameters and properties from the source producer to this chain. // Some properties may have been set during source initialization. n = mlt_properties_count(source_properties); mlt_events_block(MLT_CHAIN_PROPERTIES(self), self); for (i = 0; i < n; i++) { char *name = mlt_properties_get_name(source_properties, i); if (mlt_properties_get_int(base->source_parameters, name) || !strcmp(name, "mlt_service") || !strcmp(name, "_mlt_service_hidden") || !strcmp(name, "seekable") || !strcmp(name, "creation_time") || !strncmp(name, "meta.", 5)) { mlt_properties_pass_property(MLT_CHAIN_PROPERTIES(self), source_properties, name); } } // If a length has not been specified for this chain, copy in/out/length from the source producer if (!mlt_producer_get_length(MLT_CHAIN_PRODUCER(self))) { mlt_properties_set_position(MLT_CHAIN_PROPERTIES(self), "length", mlt_producer_get_length(base->source)); mlt_producer_set_in_and_out(MLT_CHAIN_PRODUCER(self), mlt_producer_get_in(base->source), mlt_producer_get_out(base->source)); } mlt_events_unblock(MLT_CHAIN_PROPERTIES(self), self); // Monitor property changes from the source to pass to the chain. mlt_events_listen(source_properties, self, "property-changed", (mlt_listener) source_property_changed); // This chain will control the speed and in/out mlt_producer_set_speed(base->source, 0.0); // Approximate infinite length mlt_properties_set_position(MLT_PRODUCER_PROPERTIES(base->source), "length", 0x7fffffff); mlt_producer_set_in_and_out(base->source, 0, mlt_producer_get_length(base->source) - 1); // Reconfigure the chain base->relink_required = 1; mlt_events_fire(MLT_CHAIN_PROPERTIES(self), "chain-changed", mlt_event_data_none()); } } /** Get the source producer. * * \public \memberof mlt_chain_s * \param self a chain * \return the source producer */ extern mlt_producer mlt_chain_get_source(mlt_chain self) { mlt_producer source = NULL; if (self && self->local) { mlt_chain_base *base = self->local; source = base->source; } return source; } /** Attach a link. * * \public \memberof mlt_chain_s * \param self a chain * \param link the link to attach * \return true if there was an error */ int mlt_chain_attach(mlt_chain self, mlt_link link) { int error = self == NULL || link == NULL; if (error == 0) { int i = 0; mlt_chain_base *base = self->local; for (i = 0; error == 0 && i < base->link_count; i++) if (base->links[i] == link) error = 1; if (error == 0) { if (base->link_count == base->link_size) { base->link_size += 10; base->links = realloc(base->links, base->link_size * sizeof(mlt_link)); } if (base->links != NULL) { mlt_properties_inc_ref(MLT_LINK_PROPERTIES(link)); mlt_properties_set_data(MLT_LINK_PROPERTIES(link), "chain", self, 0, NULL, NULL); base->links[base->link_count++] = link; base->relink_required = 1; mlt_events_fire(MLT_CHAIN_PROPERTIES(self), "chain-changed", mlt_event_data_none()); } else { error = 2; } } } return error; } /** Detach a link. * * \public \memberof mlt_chain_s * \param self a chain * \param link the link to detach * \return true if there was an error */ int mlt_chain_detach(mlt_chain self, mlt_link link) { int error = self == NULL || link == NULL; if (error == 0) { int i = 0; mlt_chain_base *base = self->local; for (i = 0; i < base->link_count; i++) if (base->links[i] == link) break; if (i < base->link_count) { base->links[i] = NULL; for (i++; i < base->link_count; i++) base->links[i - 1] = base->links[i]; base->link_count--; mlt_link_close(link); base->relink_required = 1; mlt_events_fire(MLT_CHAIN_PROPERTIES(self), "chain-changed", mlt_event_data_none()); } } return error; } /** Get the number of links attached. * * \public \memberof mlt_chain_s * \param self a chain * \return the number of attached links or -1 if there was an error */ int mlt_chain_link_count(mlt_chain self) { int result = -1; if (self) { mlt_chain_base *base = self->local; result = base->link_count; } return result; } /** Reorder the attached links. * * \public \memberof mlt_chain_s * \param self a chain * \param from the current index value of the link to move * \param to the new index value for the link specified in \p from * \return true if there was an error */ int mlt_chain_move_link(mlt_chain self, int from, int to) { int error = -1; if (self) { mlt_chain_base *base = self->local; if (from < 0) from = 0; if (from >= base->link_count) from = base->link_count - 1; if (to < 0) to = 0; if (to >= base->link_count) to = base->link_count - 1; if (from != to && base->link_count > 1) { mlt_link link = base->links[from]; int i; if (from > to) { for (i = from; i > to; i--) base->links[i] = base->links[i - 1]; } else { for (i = from; i < to; i++) base->links[i] = base->links[i + 1]; } base->links[to] = link; base->relink_required = 1; mlt_events_fire(MLT_CHAIN_PROPERTIES(self), "chain-changed", mlt_event_data_none()); error = 0; } } return error; } /** Retrieve an attached link. * * \public \memberof mlt_chain_s * \param self a chain * \param index which one of potentially multiple links * \return the link or null if there was an error */ mlt_link mlt_chain_link(mlt_chain self, int index) { mlt_link link = NULL; if (self != NULL) { mlt_chain_base *base = self->local; if (index >= 0 && index < base->link_count) link = base->links[index]; } return link; } /** Close the chain and free its resources. * * \public \memberof mlt_chain_s * \param self a chain */ void mlt_chain_close(mlt_chain self) { if (self != NULL && mlt_properties_dec_ref(MLT_CHAIN_PROPERTIES(self)) <= 0) { int i = 0; mlt_chain_base *base = self->local; mlt_events_block(MLT_CHAIN_PROPERTIES(self), self); for (i = 0; i < base->link_count; i++) mlt_link_close(base->links[i]); free(base->links); mlt_producer_close(base->source); mlt_properties_close(base->source_parameters); mlt_profile_close(base->source_profile); free(base); self->parent.close = NULL; mlt_producer_close(&self->parent); free(self); } } /** Attach normalizer links. * * \public \memberof mlt_chain_s * \param self a chain */ extern void mlt_chain_attach_normalizers(mlt_chain self) { if (!self) return; if (mlt_chain_link_count(self) && mlt_properties_get_int(MLT_LINK_PROPERTIES(mlt_chain_link(self, 0)), "_loader")) { // Chain already has normalizers return; } mlt_chain_base *base = self->local; // Remove any normalizer filters on the source for (int i = 0; i < mlt_service_filter_count(MLT_PRODUCER_SERVICE(base->source)); i++) { mlt_filter filter = mlt_service_filter(MLT_PRODUCER_SERVICE(base->source), i); if (filter && mlt_properties_get_int(MLT_FILTER_PROPERTIES(filter), "_loader") == 1) { mlt_service_detach(MLT_PRODUCER_SERVICE(base->source), filter); i--; } } // Remove any normalizer filters on this chain for (int i = 0; i < mlt_service_filter_count(MLT_CHAIN_SERVICE(self)); i++) { mlt_filter filter = mlt_service_filter(MLT_CHAIN_SERVICE(self), i); if (filter && mlt_properties_get_int(MLT_FILTER_PROPERTIES(filter), "_loader") == 1) { mlt_service_detach(MLT_CHAIN_SERVICE(self), filter); i--; } } static mlt_properties normalisers = NULL; mlt_tokeniser tokenizer = mlt_tokeniser_init(); // We only need to load the normalising properties once if (normalisers == NULL) { char temp[PATH_MAX]; snprintf(temp, PATH_MAX, "%s/chain_normalizers.ini", mlt_environment("MLT_DATA")); normalisers = mlt_properties_load(temp); mlt_factory_register_for_clean_up(normalisers, (mlt_destructor) mlt_properties_close); } // Apply normalisers int norm_count = 0; for (int i = 0; i < mlt_properties_count(normalisers); i++) { char *value = mlt_properties_get_value(normalisers, i); mlt_tokeniser_parse_new(tokenizer, value, ","); for (int j = 0; j < mlt_tokeniser_count(tokenizer); j++) { char *id = strdup(mlt_tokeniser_get_string(tokenizer, j)); char *arg = strchr(id, ':'); if (arg != NULL) *arg++ = '\0'; mlt_link link = mlt_factory_link(id, arg); free(id); if (link) { mlt_properties_set_int(MLT_LINK_PROPERTIES(link), "_loader", 1); mlt_chain_attach(self, link); mlt_chain_move_link(self, mlt_chain_link_count(self) - 1, norm_count); norm_count++; break; } } } mlt_tokeniser_close(tokenizer); } static int producer_get_frame(mlt_producer parent, mlt_frame_ptr frame, int index) { int result = 1; if (parent && parent->child) { mlt_chain self = parent->child; if (self) { mlt_chain_base *base = self->local; if (base->relink_required) { relink_chain(self); base->relink_required = 0; } mlt_producer_seek(base->begin, mlt_producer_frame(parent)); result = mlt_service_get_frame(MLT_PRODUCER_SERVICE(base->begin), frame, index); mlt_producer_prepare_next(parent); } } return result; } static int producer_probe(mlt_producer parent) { if (parent && parent->child) { mlt_chain self = parent->child; mlt_chain_base *base = self->local; if (base && base->source) { return mlt_producer_probe(base->source); } } return 1; } static void relink_chain(mlt_chain self) { mlt_chain_base *base = self->local; mlt_profile profile = mlt_service_profile(MLT_CHAIN_SERVICE(self)); int i = 0; int frc = 0; if (!base->source) { return; } for (i = 0; i < base->link_count; i++) { if (mlt_properties_get_int(MLT_LINK_PROPERTIES(base->links[i]), "_frc")) { // A link will perform frame rate conversion. frc = 1; break; } } if (frc) { // Set the producer to be in native frame rate mlt_service_set_profile(MLT_PRODUCER_SERVICE(base->source), base->source_profile); } else { // The producer can operate in the final frame rate. mlt_service_set_profile(MLT_PRODUCER_SERVICE(base->source), profile); } if (base->link_count == 0) { base->begin = base->source; } else { base->begin = MLT_LINK_PRODUCER(base->links[base->link_count - 1]); mlt_link_connect_next(base->links[0], base->source, profile); for (i = 1; i < base->link_count; i++) { mlt_link_connect_next(base->links[i], MLT_LINK_PRODUCER(base->links[i - 1]), profile); } } } static void chain_property_changed(mlt_service owner, mlt_chain self, char *name) { mlt_chain_base *base = self->local; if (!base->source) return; if (mlt_properties_get_int(base->source_parameters, name) || !strncmp(name, "meta.", 5)) { // Pass parameter changes from this chain to the encapsulated source producer. mlt_properties chain_properties = MLT_CHAIN_PROPERTIES(self); mlt_properties source_properties = MLT_PRODUCER_PROPERTIES(base->source); mlt_events_block(source_properties, self); mlt_properties_pass_property(source_properties, chain_properties, name); mlt_events_unblock(source_properties, self); } } static void source_property_changed(mlt_service owner, mlt_chain self, char *name) { mlt_chain_base *base = self->local; if (mlt_properties_get_int(base->source_parameters, name) || !strncmp(name, "meta.", 5)) { // The source producer might change its own parameters. // Pass those changes to this producer. mlt_properties chain_properties = MLT_CHAIN_PROPERTIES(self); mlt_properties source_properties = MLT_PRODUCER_PROPERTIES(base->source); mlt_events_block(chain_properties, self); mlt_properties_pass_property(chain_properties, source_properties, name); mlt_events_unblock(chain_properties, self); } } mlt-7.22.0/src/framework/mlt_chain.h000664 000000 000000 00000003742 14531534050 017257 0ustar00rootroot000000 000000 /** * \file mlt_chain.h * \brief chain service class * \see mlt_chain_s * * Copyright (C) 2020 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef MLT_CHAIN_H #define MLT_CHAIN_H #include "mlt_link.h" #include "mlt_producer.h" /** \brief Chain class * * The chain is a producer class that that can connect multiple link producers in a sequence. * * \extends mlt_producer_s */ struct mlt_chain_s { struct mlt_producer_s parent; void *local; /**< \private instance object */ }; #define MLT_CHAIN_PRODUCER(chain) (&(chain)->parent) #define MLT_CHAIN_SERVICE(chain) MLT_PRODUCER_SERVICE(MLT_CHAIN_PRODUCER(chain)) #define MLT_CHAIN_PROPERTIES(chain) MLT_SERVICE_PROPERTIES(MLT_CHAIN_SERVICE(chain)) extern mlt_chain mlt_chain_init(mlt_profile); extern void mlt_chain_set_source(mlt_chain self, mlt_producer source); extern mlt_producer mlt_chain_get_source(mlt_chain self); extern int mlt_chain_attach(mlt_chain self, mlt_link link); extern int mlt_chain_detach(mlt_chain self, mlt_link link); extern int mlt_chain_link_count(mlt_chain self); extern int mlt_chain_move_link(mlt_chain self, int from, int to); extern mlt_link mlt_chain_link(mlt_chain self, int index); extern void mlt_chain_close(mlt_chain self); extern void mlt_chain_attach_normalizers(mlt_chain self); #endif mlt-7.22.0/src/framework/mlt_consumer.c000664 000000 000000 00000202242 14531534050 020017 0ustar00rootroot000000 000000 /** * \file mlt_consumer.c * \brief abstraction for all consumer services * \see mlt_consumer_s * * Copyright (C) 2003-2023 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "mlt_consumer.h" #include "mlt_factory.h" #include "mlt_frame.h" #include "mlt_log.h" #include "mlt_producer.h" #include "mlt_profile.h" #include #include #include #include #include /** Define this if you want an automatic deinterlace (if necessary) when the * consumer's producer is not running at normal speed. */ #undef DEINTERLACE_ON_NOT_NORMAL_SPEED /** This is not the ideal place for this, but it is needed by VDPAU as well. */ pthread_mutex_t mlt_sdl_mutex = PTHREAD_MUTEX_INITIALIZER; /** mlt_frame_s::is_processing can not be made atomic, so protect it with a mutex. */ pthread_mutex_t mlt_frame_processing_mutex = PTHREAD_MUTEX_INITIALIZER; /** \brief private members of mlt_consumer */ typedef struct { int real_time; atomic_int ahead; int preroll; mlt_image_format image_format; mlt_audio_format audio_format; mlt_deque queue; void *ahead_thread; pthread_mutex_t queue_mutex; pthread_cond_t queue_cond; pthread_mutex_t put_mutex; pthread_cond_t put_cond; mlt_frame put; int put_active; mlt_event event_listener; mlt_position position; pthread_mutex_t position_mutex; int is_purge; int aud_counter; double fps; int channels; int frequency; atomic_int speed; /* additional fields added for the parallel work queue */ mlt_deque worker_threads; pthread_mutex_t done_mutex; pthread_cond_t done_cond; int consecutive_dropped; int consecutive_rendered; int process_head; atomic_int started; pthread_t *threads; /**< used to deallocate all threads */ } consumer_private; static void mlt_consumer_property_changed(mlt_properties owner, mlt_consumer self, mlt_event_data); static void apply_profile_properties(mlt_consumer self, mlt_profile profile, mlt_properties properties); static void on_consumer_frame_show(mlt_properties owner, mlt_consumer self, mlt_event_data); static void mlt_thread_create(mlt_consumer self, mlt_thread_function_t function); static void mlt_thread_join(mlt_consumer self); static void consumer_read_ahead_start(mlt_consumer self); /** Initialize a consumer service. * * \public \memberof mlt_consumer_s * \param self the consumer to initialize * \param child a pointer to the object for the subclass * \param profile the \p mlt_profile_s to use (optional but recommended, * uses the environment variable MLT if self is NULL) * \return true if there was an error */ int mlt_consumer_init(mlt_consumer self, void *child, mlt_profile profile) { int error = 0; memset(self, 0, sizeof(struct mlt_consumer_s)); self->child = child; consumer_private *priv = self->local = calloc(1, sizeof(consumer_private)); error = mlt_service_init(&self->parent, self); if (error == 0) { // Get the properties from the service mlt_properties properties = MLT_SERVICE_PROPERTIES(&self->parent); // Apply profile to properties if (profile == NULL) { // Normally the application creates the profile and controls its lifetime // This is the fallback exception handling profile = mlt_profile_init(NULL); mlt_properties self_props = MLT_CONSUMER_PROPERTIES(self); mlt_properties_set_data(self_props, "_profile", profile, 0, (mlt_destructor) mlt_profile_close, NULL); } apply_profile_properties(self, profile, properties); mlt_properties_set(properties, "mlt_type", "consumer"); // Default rescaler for all consumers mlt_properties_set(properties, "rescale", "bilinear"); // Default read ahead buffer size mlt_properties_set_int(properties, "buffer", 25); mlt_properties_set_int(properties, "drop_max", 5); // Default audio frequency and channels mlt_properties_set_int(properties, "frequency", 48000); mlt_properties_set_int(properties, "channels", 2); // Default of all consumers is real time mlt_properties_set_int(properties, "real_time", 1); // Default to environment test card mlt_properties_set(properties, "test_card", mlt_environment("MLT_TEST_CARD")); // Hmm - default all consumers to yuv422 with s16 :-/ priv->image_format = mlt_image_yuv422; priv->audio_format = mlt_audio_s16; mlt_events_register(properties, "consumer-frame-show"); mlt_events_register(properties, "consumer-frame-render"); mlt_events_register(properties, "consumer-thread-started"); mlt_events_register(properties, "consumer-thread-stopped"); mlt_events_register(properties, "consumer-stopping"); mlt_events_register(properties, "consumer-stopped"); mlt_events_register(properties, "consumer-thread-create"); mlt_events_register(properties, "consumer-thread-join"); mlt_events_listen(properties, self, "consumer-frame-show", (mlt_listener) on_consumer_frame_show); // Register a property-changed listener to handle the profile property - // subsequent properties can override the profile priv->event_listener = mlt_events_listen(properties, self, "property-changed", (mlt_listener) mlt_consumer_property_changed); // Create the push mutex and condition pthread_mutex_init(&priv->put_mutex, NULL); pthread_cond_init(&priv->put_cond, NULL); pthread_mutex_init(&priv->position_mutex, NULL); } return error; } /** Convert the profile into properties on the consumer. * * \private \memberof mlt_consumer_s * \param self a consumer * \param profile a profile * \param properties a properties list (typically, the consumer's) */ static void apply_profile_properties(mlt_consumer self, mlt_profile profile, mlt_properties properties) { consumer_private *priv = self->local; mlt_event_block(priv->event_listener); mlt_properties_set_double(properties, "fps", mlt_profile_fps(profile)); mlt_properties_set_int(properties, "frame_rate_num", profile->frame_rate_num); mlt_properties_set_int(properties, "frame_rate_den", profile->frame_rate_den); mlt_properties_set_int(properties, "width", profile->width); mlt_properties_set_int(properties, "height", profile->height); mlt_properties_set_int(properties, "progressive", profile->progressive); mlt_properties_set_double(properties, "aspect_ratio", mlt_profile_sar(profile)); mlt_properties_set_int(properties, "sample_aspect_num", profile->sample_aspect_num); mlt_properties_set_int(properties, "sample_aspect_den", profile->sample_aspect_den); mlt_properties_set_double(properties, "display_ratio", mlt_profile_dar(profile)); mlt_properties_set_int(properties, "display_aspect_num", profile->display_aspect_num); mlt_properties_set_int(properties, "display_aspect_den", profile->display_aspect_den); mlt_properties_set_int(properties, "colorspace", profile->colorspace); mlt_event_unblock(priv->event_listener); } /** The property-changed event listener * * \private \memberof mlt_consumer_s * \param owner the events object * \param self the consumer * \param name the name of the property that changed */ static void mlt_consumer_property_changed(mlt_properties owner, mlt_consumer self, mlt_event_data event_data) { const char *name = mlt_event_data_to_string(event_data); if (name && !strcmp(name, "mlt_profile")) { // Get the properties mlt_properties properties = MLT_CONSUMER_PROPERTIES(self); // Get the current profile mlt_profile profile = mlt_service_profile(MLT_CONSUMER_SERVICE(self)); // Load the new profile mlt_profile new_profile = mlt_profile_init(mlt_properties_get(properties, name)); if (new_profile) { // Copy the profile if (profile != NULL) { free(profile->description); memcpy(profile, new_profile, sizeof(struct mlt_profile_s)); profile->description = strdup(new_profile->description); } else { profile = new_profile; } // Apply to properties apply_profile_properties(self, profile, properties); mlt_profile_close(new_profile); } } else if (!strcmp(name, "frame_rate_num")) { mlt_properties properties = MLT_CONSUMER_PROPERTIES(self); mlt_profile profile = mlt_service_profile(MLT_CONSUMER_SERVICE(self)); if (profile) { profile->frame_rate_num = mlt_properties_get_int(properties, "frame_rate_num"); mlt_properties_set_double(properties, "fps", mlt_profile_fps(profile)); } } else if (!strcmp(name, "frame_rate_den")) { mlt_properties properties = MLT_CONSUMER_PROPERTIES(self); mlt_profile profile = mlt_service_profile(MLT_CONSUMER_SERVICE(self)); if (profile) { profile->frame_rate_den = mlt_properties_get_int(properties, "frame_rate_den"); mlt_properties_set_double(properties, "fps", mlt_profile_fps(profile)); } } else if (!strcmp(name, "width")) { mlt_properties properties = MLT_CONSUMER_PROPERTIES(self); mlt_profile profile = mlt_service_profile(MLT_CONSUMER_SERVICE(self)); if (profile) profile->width = mlt_properties_get_int(properties, "width"); } else if (!strcmp(name, "height")) { mlt_properties properties = MLT_CONSUMER_PROPERTIES(self); mlt_profile profile = mlt_service_profile(MLT_CONSUMER_SERVICE(self)); if (profile) profile->height = mlt_properties_get_int(properties, "height"); } else if (!strcmp(name, "progressive")) { mlt_properties properties = MLT_CONSUMER_PROPERTIES(self); mlt_profile profile = mlt_service_profile(MLT_CONSUMER_SERVICE(self)); if (profile) profile->progressive = mlt_properties_get_int(properties, "progressive"); } else if (!strcmp(name, "sample_aspect_num")) { mlt_properties properties = MLT_CONSUMER_PROPERTIES(self); mlt_profile profile = mlt_service_profile(MLT_CONSUMER_SERVICE(self)); if (profile) { profile->sample_aspect_num = mlt_properties_get_int(properties, "sample_aspect_num"); mlt_properties_set_double(properties, "aspect_ratio", mlt_profile_sar(profile)); } } else if (!strcmp(name, "sample_aspect_den")) { mlt_properties properties = MLT_CONSUMER_PROPERTIES(self); mlt_profile profile = mlt_service_profile(MLT_CONSUMER_SERVICE(self)); if (profile) { profile->sample_aspect_den = mlt_properties_get_int(properties, "sample_aspect_den"); mlt_properties_set_double(properties, "aspect_ratio", mlt_profile_sar(profile)); } } else if (!strcmp(name, "display_aspect_num")) { mlt_properties properties = MLT_CONSUMER_PROPERTIES(self); mlt_profile profile = mlt_service_profile(MLT_CONSUMER_SERVICE(self)); if (profile) { profile->display_aspect_num = mlt_properties_get_int(properties, "display_aspect_num"); mlt_properties_set_double(properties, "display_ratio", mlt_profile_dar(profile)); } } else if (!strcmp(name, "display_aspect_den")) { mlt_properties properties = MLT_CONSUMER_PROPERTIES(self); mlt_profile profile = mlt_service_profile(MLT_CONSUMER_SERVICE(self)); if (profile) { profile->display_aspect_den = mlt_properties_get_int(properties, "display_aspect_den"); mlt_properties_set_double(properties, "display_ratio", mlt_profile_dar(profile)); } } else if (!strcmp(name, "colorspace")) { mlt_properties properties = MLT_CONSUMER_PROPERTIES(self); mlt_profile profile = mlt_service_profile(MLT_CONSUMER_SERVICE(self)); if (profile) profile->colorspace = mlt_properties_get_int(properties, "colorspace"); } } /** A listener on the consumer-frame-show event * * Saves the position of the frame shown. * * \private \memberof mlt_consumer_s * \param owner the events object * \param consumer the consumer on which this event occurred * \param frame the frame that was shown */ static void on_consumer_frame_show(mlt_properties owner, mlt_consumer consumer, mlt_event_data event_data) { mlt_frame frame = mlt_event_data_to_frame(event_data); if (frame) { consumer_private *priv = consumer->local; pthread_mutex_lock(&priv->position_mutex); priv->position = mlt_frame_get_position(frame); pthread_mutex_unlock(&priv->position_mutex); } } /** Create a new consumer. * * \public \memberof mlt_consumer_s * \param profile a profile (optional, but recommended) * \return a new consumer */ mlt_consumer mlt_consumer_new(mlt_profile profile) { // Create the memory for the structure mlt_consumer self = malloc(sizeof(struct mlt_consumer_s)); // Initialise it if (self != NULL && mlt_consumer_init(self, NULL, profile) == 0) { // Return it return self; } else { free(self); return NULL; } } /** Get the parent service object. * * \public \memberof mlt_consumer_s * \param self a consumer * \return the parent service class * \see MLT_CONSUMER_SERVICE */ mlt_service mlt_consumer_service(mlt_consumer self) { return self != NULL ? &self->parent : NULL; } /** Get the consumer properties. * * \public \memberof mlt_consumer_s * \param self a consumer * \return the consumer's properties list * \see MLT_CONSUMER_PROPERTIES */ mlt_properties mlt_consumer_properties(mlt_consumer self) { return self != NULL ? MLT_SERVICE_PROPERTIES(&self->parent) : NULL; } /** Connect the consumer to the producer. * * \public \memberof mlt_consumer_s * \param self a consumer * \param producer a producer * \return > 0 warning, == 0 success, < 0 serious error, * 1 = this service does not accept input, * 2 = the producer is invalid, * 3 = the producer is already registered with this consumer */ int mlt_consumer_connect(mlt_consumer self, mlt_service producer) { return mlt_service_connect_producer(&self->parent, producer, 0); } /** Set the audio format to use in the render thread. * * \private \memberof mlt_consumer_s * \param self a consumer */ static void set_audio_format(mlt_consumer self) { // Get the audio format to use for rendering threads. consumer_private *priv = self->local; mlt_properties properties = MLT_CONSUMER_PROPERTIES(self); const char *format = mlt_properties_get(properties, "mlt_audio_format"); if (format) { if (!strcmp(format, "none")) priv->audio_format = mlt_audio_none; else if (!strcmp(format, "s32")) priv->audio_format = mlt_audio_s32; else if (!strcmp(format, "s32le")) priv->audio_format = mlt_audio_s32le; else if (!strcmp(format, "float")) priv->audio_format = mlt_audio_float; else if (!strcmp(format, "f32le")) priv->audio_format = mlt_audio_f32le; else if (!strcmp(format, "u8")) priv->audio_format = mlt_audio_u8; } } /** Set the image format to use in render threads. * * \private \memberof mlt_consumer_s * \param self a consumer */ static void set_image_format(mlt_consumer self) { // Get the image format to use for rendering threads. consumer_private *priv = self->local; mlt_properties properties = MLT_CONSUMER_PROPERTIES(self); const char *format = mlt_properties_get(properties, "mlt_image_format"); if (format) { priv->image_format = mlt_image_format_id(format); if (mlt_image_invalid == priv->image_format) priv->image_format = mlt_image_yuv422; // mlt_image_movit is for internal use only. // Remapping it glsl_texture prevents breaking existing apps // using the legacy "glsl" name. else if (mlt_image_movit == priv->image_format) priv->image_format = mlt_image_opengl_texture; } } /** Start the consumer. * * \public \memberof mlt_consumer_s * \param self a consumer * \return true if there was an error */ int mlt_consumer_start(mlt_consumer self) { if (!self) { return 1; } int error = 0; if (!mlt_consumer_is_stopped(self)) return error; consumer_private *priv = self->local; // Stop listening to the property-changed event mlt_event_block(priv->event_listener); // Get the properties mlt_properties properties = MLT_CONSUMER_PROPERTIES(self); // Determine if there's a test card producer char *test_card = mlt_properties_get(properties, "test_card"); // Just to make sure nothing is hanging around... pthread_mutex_lock(&priv->put_mutex); priv->put = NULL; priv->put_active = 1; pthread_mutex_unlock(&priv->put_mutex); // Deal with it now. if (test_card != NULL) { if (mlt_properties_get_data(properties, "test_card_producer", NULL) == NULL) { // Create a test card producer mlt_profile profile = mlt_service_profile(MLT_CONSUMER_SERVICE(self)); mlt_producer producer = mlt_factory_producer(profile, NULL, test_card); // Do we have a producer if (producer != NULL) { // Test card should loop I guess... mlt_properties_set(MLT_PRODUCER_PROPERTIES(producer), "eof", "loop"); //mlt_producer_set_speed( producer, 0 ); //mlt_producer_set_in_and_out( producer, 0, 0 ); // Set the test card on the consumer mlt_properties_set_data(properties, "test_card_producer", producer, 0, (mlt_destructor) mlt_producer_close, NULL); } } } else { // Allow the hash table to speed things up mlt_properties_set_data(properties, "test_card_producer", NULL, 0, NULL, NULL); } // The profile could have changed between a stop and a restart. apply_profile_properties(self, mlt_service_profile(MLT_CONSUMER_SERVICE(self)), properties); // Set the frame duration in microseconds for the frame-dropping heuristic int frame_rate_num = mlt_properties_get_int(properties, "frame_rate_num"); int frame_rate_den = mlt_properties_get_int(properties, "frame_rate_den"); int frame_duration = 0; if (frame_rate_num && frame_rate_den) { frame_duration = 1000000.0 / frame_rate_num * frame_rate_den; } mlt_properties_set_int(properties, "frame_duration", frame_duration); mlt_properties_set_int(properties, "drop_count", 0); // Check and run an ante command if (mlt_properties_get(properties, "ante")) if (system(mlt_properties_get(properties, "ante")) == -1) mlt_log(MLT_CONSUMER_SERVICE(self), MLT_LOG_ERROR, "system(%s) failed!\n", mlt_properties_get(properties, "ante")); // Set the real_time preference priv->real_time = mlt_properties_get_int(properties, "real_time"); // For worker threads implementation, buffer must be at least # threads if (abs(priv->real_time) > 1 && mlt_properties_get_int(properties, "buffer") <= abs(priv->real_time)) mlt_properties_set_int(properties, "_buffer", abs(priv->real_time) + 1); // Store the parameters for audio processing. priv->aud_counter = 0; priv->fps = mlt_properties_get_double(properties, "fps"); priv->channels = mlt_properties_get_int(properties, "channels"); priv->frequency = mlt_properties_get_int(properties, "frequency"); priv->preroll = 1; #ifdef _WIN32 if (priv->real_time == 1 || priv->real_time == -1) consumer_read_ahead_start(self); #endif // Start the service if (self->start != NULL) error = self->start(self); return error; } /** An alternative method to feed frames into the consumer. * * Only valid if the consumer itself is not connected. * * \public \memberof mlt_consumer_s * \param self a consumer * \param frame a frame * \return true (ignore self for now) */ int mlt_consumer_put_frame(mlt_consumer self, mlt_frame frame) { int error = 1; // Get the service associated to the consumer mlt_service service = MLT_CONSUMER_SERVICE(self); if (mlt_service_producer(service) == NULL) { struct timeval now; struct timespec tm; consumer_private *priv = self->local; mlt_properties_set_int(MLT_CONSUMER_PROPERTIES(self), "put_pending", 1); pthread_mutex_lock(&priv->put_mutex); while (priv->put_active && priv->put != NULL) { gettimeofday(&now, NULL); tm.tv_sec = now.tv_sec + 1; tm.tv_nsec = now.tv_usec * 1000; pthread_cond_timedwait(&priv->put_cond, &priv->put_mutex, &tm); } mlt_properties_set_int(MLT_CONSUMER_PROPERTIES(self), "put_pending", 0); if (priv->put_active && priv->put == NULL) priv->put = frame; else mlt_frame_close(frame); pthread_cond_broadcast(&priv->put_cond); pthread_mutex_unlock(&priv->put_mutex); } else { mlt_frame_close(frame); } return error; } /** Protected method for consumer to get frames from connected service * * \public \memberof mlt_consumer_s * \param self a consumer * \return a frame */ mlt_frame mlt_consumer_get_frame(mlt_consumer self) { // Frame to return mlt_frame frame = NULL; // Get the service associated to the consumer mlt_service service = MLT_CONSUMER_SERVICE(self); // Get the consumer properties mlt_properties properties = MLT_CONSUMER_PROPERTIES(self); // Get the frame if (mlt_service_producer(service) == NULL && mlt_properties_get_int(properties, "put_mode")) { struct timeval now; struct timespec tm; consumer_private *priv = self->local; pthread_mutex_lock(&priv->put_mutex); while (priv->put_active && priv->put == NULL) { gettimeofday(&now, NULL); tm.tv_sec = now.tv_sec + 1; tm.tv_nsec = now.tv_usec * 1000; pthread_cond_timedwait(&priv->put_cond, &priv->put_mutex, &tm); } frame = priv->put; priv->put = NULL; pthread_cond_broadcast(&priv->put_cond); pthread_mutex_unlock(&priv->put_mutex); if (frame != NULL) mlt_service_apply_filters(service, frame, 0); } else if (mlt_service_producer(service) != NULL) { mlt_service_get_frame(service, &frame, 0); } else { frame = mlt_frame_init(service); } if (frame != NULL) { // Get the frame properties mlt_properties frame_properties = MLT_FRAME_PROPERTIES(frame); // Get the test card producer mlt_producer test_card = mlt_properties_get_data(properties, "test_card_producer", NULL); // Attach the test frame producer to it. if (test_card != NULL) mlt_properties_set_data(frame_properties, "test_card_producer", test_card, 0, NULL, NULL); // Pass along the interpolation and deinterlace options // TODO: get rid of consumer_deinterlace and use profile.progressive mlt_properties_set(frame_properties, "consumer.rescale", mlt_properties_get(properties, "rescale")); mlt_properties_set_int(frame_properties, "consumer.progressive", mlt_properties_get_int(properties, "progressive") | mlt_properties_get_int(properties, "deinterlace")); mlt_properties_set(frame_properties, "consumer.deinterlacer", mlt_properties_get(properties, "deinterlacer") ? mlt_properties_get(properties, "deinterlacer") : mlt_properties_get(properties, "deinterlace_method")); mlt_properties_set_int(frame_properties, "consumer.top_field_first", mlt_properties_get_int(properties, "top_field_first")); mlt_properties_set(frame_properties, "consumer.color_trc", mlt_properties_get(properties, "color_trc")); mlt_properties_set(frame_properties, "consumer.channel_layout", mlt_properties_get(properties, "channel_layout")); mlt_properties_set(frame_properties, "consumer.color_range", mlt_properties_get(properties, "color_range")); } // Return the frame return frame; } /** Compute the time difference between now and a time value. * * \private \memberof mlt_consumer_s * \param time1 a time value to be compared against now * \return the difference in microseconds */ static inline long time_difference(struct timeval *time1) { struct timeval time2; time2.tv_sec = time1->tv_sec; time2.tv_usec = time1->tv_usec; gettimeofday(time1, NULL); return time1->tv_sec * 1000000 + time1->tv_usec - time2.tv_sec * 1000000 - time2.tv_usec; } /** The thread procedure for asynchronously pulling frames through the service * network connected to a consumer. * * \private \memberof mlt_consumer_s * \param arg a consumer */ static void *consumer_read_ahead_thread(void *arg) { // The argument is the consumer mlt_consumer self = arg; consumer_private *priv = self->local; // Get the properties of the consumer mlt_properties properties = MLT_CONSUMER_PROPERTIES(self); // Get the width and height int width = mlt_properties_get_int(properties, "width"); int height = mlt_properties_get_int(properties, "height"); // See if video is turned off int video_off = mlt_properties_get_int(properties, "video_off"); int preview_off = mlt_properties_get_int(properties, "preview_off"); int preview_format = mlt_properties_get_int(properties, "preview_format"); // Audio processing variables int samples = 0; void *audio = NULL; // See if audio is turned off int audio_off = mlt_properties_get_int(properties, "audio_off"); // General frame variable mlt_frame frame = NULL; uint8_t *image = NULL; // Time structures struct timeval ante; // Average time for get_frame and get_image int count = 0; int skipped = 0; int64_t time_process = 0; int skip_next = 0; mlt_position pos = 0; mlt_position start_pos = 0; mlt_position last_pos = 0; int frame_duration = mlt_properties_get_int(properties, "frame_duration"); int drop_max = mlt_properties_get_int(properties, "drop_max"); if (preview_off && preview_format != 0) priv->image_format = preview_format; set_audio_format(self); set_image_format(self); mlt_events_fire(properties, "consumer-thread-started", mlt_event_data_none()); // Get the first frame frame = mlt_consumer_get_frame(self); if (priv->speed != mlt_properties_get_int(MLT_FRAME_PROPERTIES(frame), "_speed")) { priv->speed = mlt_properties_get_int(MLT_FRAME_PROPERTIES(frame), "_speed"); // get_frame might want to recalculate the minimum queue size if the speed has changed. pthread_cond_broadcast(&priv->queue_cond); } if (frame) { // Get the audio of the first frame if (!audio_off) { samples = mlt_audio_calculate_frame_samples(priv->fps, priv->frequency, priv->aud_counter++); mlt_frame_get_audio(frame, &audio, &priv->audio_format, &priv->frequency, &priv->channels, &samples); } // Get the image of the first frame if (!video_off) { mlt_events_fire(MLT_CONSUMER_PROPERTIES(self), "consumer-frame-render", mlt_event_data_from_frame(frame)); mlt_frame_get_image(frame, &image, &priv->image_format, &width, &height, 0); } // Mark as rendered mlt_properties_set_int(MLT_FRAME_PROPERTIES(frame), "rendered", 1); last_pos = start_pos = pos = mlt_frame_get_position(frame); } // Get the starting time (can ignore the times above) gettimeofday(&ante, NULL); // Continue to read ahead while (priv->ahead) { // Get the maximum size of the buffer int buffer = (priv->speed == 0) ? 1 : MAX(mlt_properties_get_int(properties, "buffer"), 0) + 1; // Put the current frame into the queue pthread_mutex_lock(&priv->queue_mutex); while (priv->ahead && mlt_deque_count(priv->queue) >= buffer) pthread_cond_wait(&priv->queue_cond, &priv->queue_mutex); if (priv->is_purge) { mlt_frame_close(frame); priv->is_purge = 0; } else { mlt_deque_push_back(priv->queue, frame); } pthread_cond_broadcast(&priv->queue_cond); pthread_mutex_unlock(&priv->queue_mutex); mlt_log_timings_begin(); // Get the next frame frame = mlt_consumer_get_frame(self); mlt_log_timings_end(NULL, "mlt_consumer_get_frame"); // If there's no frame, we're probably stopped... if (frame == NULL) continue; pos = mlt_frame_get_position(frame); priv->speed = mlt_properties_get_int(MLT_FRAME_PROPERTIES(frame), "_speed"); // WebVfx uses this to setup a consumer-stopping event handler. mlt_properties_set_data(MLT_FRAME_PROPERTIES(frame), "consumer", self, 0, NULL, NULL); // Increment the counter used for averaging processing cost count++; // Always process audio if (!audio_off) { samples = mlt_audio_calculate_frame_samples(priv->fps, priv->frequency, priv->aud_counter++); mlt_frame_get_audio(frame, &audio, &priv->audio_format, &priv->frequency, &priv->channels, &samples); } // All non-normal playback frames should be shown if (priv->speed != 1) { #ifdef DEINTERLACE_ON_NOT_NORMAL_SPEED mlt_properties_set_int(MLT_FRAME_PROPERTIES(frame), "consumer.progressive", 1); #endif // Indicate seeking or trick-play start_pos = pos; } // If skip flag not set or frame-dropping disabled if (!skip_next || priv->real_time == -1) { if (!video_off) { // Reset width/height - could have been changed by previous mlt_frame_get_image width = mlt_properties_get_int(properties, "width"); height = mlt_properties_get_int(properties, "height"); // Get the image mlt_events_fire(MLT_CONSUMER_PROPERTIES(self), "consumer-frame-render", mlt_event_data_from_frame(frame)); mlt_log_timings_begin(); mlt_frame_get_image(frame, &image, &priv->image_format, &width, &height, 0); mlt_log_timings_end(NULL, "mlt_frame_get_image"); } // Indicate the rendered image is available. mlt_properties_set_int(MLT_FRAME_PROPERTIES(frame), "rendered", 1); // Reset consecutively-skipped counter skipped = 0; } else // Skip image processing { // Increment the number of consecutively-skipped frames skipped++; // If too many (1 sec) consecutively-skipped frames if (skipped > drop_max) { // Reset cost tracker time_process = 0; count = 1; mlt_log_verbose(self, "too many frames dropped - forcing next frame\n"); } } // Get the time to process this frame int64_t time_current = time_difference(&ante); // If the current time is not suddenly some large amount if (time_current < time_process / count * 20 || !time_process || count < 5) { // Accumulate the cost for processing this frame time_process += time_current; } else { mlt_log_debug(self, "current %" PRId64 " threshold %" PRId64 " count %d\n", time_current, (int64_t) (time_process / count * 20), count); // Ignore the cost of this frame's time count--; } // Determine if we started, resumed, or seeked if (pos != last_pos + 1) { start_pos = pos; if (priv->speed) { priv->preroll = 1; } } last_pos = pos; // Do not skip the first 20% of buffer at start, resume, or seek if (pos - start_pos <= buffer / 5 + 1) { // Reset cost tracker time_process = 0; count = 1; } // Reset skip flag skip_next = 0; // Only consider skipping if the buffer level is low (or really small) if (mlt_deque_count(priv->queue) <= buffer / 5 + 1 && count > 1) { // Skip next frame if average cost exceeds frame duration. if (time_process / count > frame_duration) skip_next = 1; if (skip_next) mlt_log_debug(self, "avg usec %" PRId64 " (%" PRId64 "/%d) duration %d\n", time_process / count, time_process, count, frame_duration); } } // Remove the last frame mlt_frame_close(frame); // Wipe the queue pthread_mutex_lock(&priv->queue_mutex); while (mlt_deque_count(priv->queue)) mlt_frame_close(mlt_deque_pop_back(priv->queue)); // Close the queue mlt_deque_close(priv->queue); priv->queue = NULL; pthread_mutex_unlock(&priv->queue_mutex); mlt_events_fire(MLT_CONSUMER_PROPERTIES(self), "consumer-thread-stopped", mlt_event_data_none()); return NULL; } /** Locate the first unprocessed frame in the queue. * * When playing with realtime behavior, we do not use the true head, but * rather an adjusted process_head. The process_head is adjusted based on * the rate of frame-dropping or recovery from frame-dropping. The idea is * that as the level of frame-dropping increases to move the process_head * closer to the tail because the frames are not completing processing prior * to their playout! Then, as frames are not dropped the process_head moves * back closer to the head of the queue so that worker threads can work * ahead of the playout point (queue head). * * \private \memberof mlt_consumer_s * \param self a consumer * \return an index into the queue */ static inline int first_unprocessed_frame(mlt_consumer self) { consumer_private *priv = self->local; int index = priv->real_time <= 0 ? 0 : priv->process_head; pthread_mutex_lock(&mlt_frame_processing_mutex); while (index < mlt_deque_count(priv->queue) && MLT_FRAME(mlt_deque_peek(priv->queue, index))->is_processing) index++; pthread_mutex_unlock(&mlt_frame_processing_mutex); return index; } /** The worker thread procedure for parallel processing frames. * * \private \memberof mlt_consumer_s * \param arg a consumer */ static void *consumer_worker_thread(void *arg) { // The argument is the consumer mlt_consumer self = arg; consumer_private *priv = self->local; // Get the properties of the consumer mlt_properties properties = MLT_CONSUMER_PROPERTIES(self); // Get the width and height int width = mlt_properties_get_int(properties, "width"); int height = mlt_properties_get_int(properties, "height"); mlt_image_format format = priv->image_format; // See if video is turned off int video_off = mlt_properties_get_int(properties, "video_off"); int preview_off = mlt_properties_get_int(properties, "preview_off"); int preview_format = mlt_properties_get_int(properties, "preview_format"); // General frame variable mlt_frame frame = NULL; uint8_t *image = NULL; if (preview_off && preview_format != 0) format = preview_format; mlt_events_fire(properties, "consumer-thread-started", mlt_event_data_none()); // Continue to read ahead while (priv->ahead) { // Get the next unprocessed frame from the work queue pthread_mutex_lock(&priv->queue_mutex); int index = first_unprocessed_frame(self); while (priv->ahead && index >= mlt_deque_count(priv->queue)) { mlt_log_debug(MLT_CONSUMER_SERVICE(self), "waiting in worker index = %d queue count = %d\n", index, mlt_deque_count(priv->queue)); pthread_cond_wait(&priv->queue_cond, &priv->queue_mutex); index = first_unprocessed_frame(self); } // Mark the frame for processing frame = mlt_deque_peek(priv->queue, index); if (frame) { mlt_log_debug(MLT_CONSUMER_SERVICE(self), "worker processing index = %d frame " MLT_POSITION_FMT " queue count = %d\n", index, mlt_frame_get_position(frame), mlt_deque_count(priv->queue)); pthread_mutex_lock(&mlt_frame_processing_mutex); frame->is_processing = 1; pthread_mutex_unlock(&mlt_frame_processing_mutex); mlt_properties_inc_ref(MLT_FRAME_PROPERTIES(frame)); } pthread_mutex_unlock(&priv->queue_mutex); // If there's no frame, we're probably stopped... if (frame == NULL) continue; // WebVfx uses this to setup a consumer-stopping event handler. mlt_properties_set_data(MLT_FRAME_PROPERTIES(frame), "consumer", self, 0, NULL, NULL); #ifdef DEINTERLACE_ON_NOT_NORMAL_SPEED // All non normal playback frames should be shown if (mlt_properties_get_int(MLT_FRAME_PROPERTIES(frame), "_speed") != 1) mlt_properties_set_int(MLT_FRAME_PROPERTIES(frame), "consumer.progressive", 1); #endif // Get the image if (!video_off) { // Fetch width/height again width = mlt_properties_get_int(properties, "width"); height = mlt_properties_get_int(properties, "height"); mlt_events_fire(MLT_CONSUMER_PROPERTIES(self), "consumer-frame-render", mlt_event_data_from_frame(frame)); mlt_frame_get_image(frame, &image, &format, &width, &height, 0); } mlt_properties_set_int(MLT_FRAME_PROPERTIES(frame), "rendered", 1); mlt_frame_close(frame); // Tell a waiting thread (non-realtime main consumer thread) that we are done. pthread_mutex_lock(&priv->done_mutex); pthread_cond_broadcast(&priv->done_cond); pthread_mutex_unlock(&priv->done_mutex); } return NULL; } /** Start the read/render thread. * * \private \memberof mlt_consumer_s * \param self a consumer */ static void consumer_read_ahead_start(mlt_consumer self) { consumer_private *priv = self->local; if (priv->started) return; // We're running now priv->ahead = 1; // Create the frame queue priv->queue = mlt_deque_init(); // Create the queue mutex pthread_mutex_init(&priv->queue_mutex, NULL); // Create the condition pthread_cond_init(&priv->queue_cond, NULL); // Create the read ahead mlt_thread_create(self, (mlt_thread_function_t) consumer_read_ahead_thread); priv->started = 1; } /** Start the worker threads. * * \private \memberof mlt_consumer_s * \param self a consumer */ static void consumer_work_start(mlt_consumer self) { consumer_private *priv = self->local; int n = abs(priv->real_time); pthread_t *thread; if (priv->started) return; thread = calloc(1, sizeof(pthread_t) * n); // We're running now priv->ahead = 1; priv->threads = thread; // These keep track of the acceleration of frame dropping or recovery. priv->consecutive_dropped = 0; priv->consecutive_rendered = 0; // This is the position in the queue from which to look for a frame to process. // If we always start from the head, then we may likely not complete processing // before the frame is played out. priv->process_head = 0; // Create the queues priv->queue = mlt_deque_init(); priv->worker_threads = mlt_deque_init(); // Create the mutexes pthread_mutex_init(&priv->queue_mutex, NULL); pthread_mutex_init(&priv->done_mutex, NULL); // Create the conditions pthread_cond_init(&priv->queue_cond, NULL); pthread_cond_init(&priv->done_cond, NULL); // Create the read ahead if (mlt_properties_get(MLT_CONSUMER_PROPERTIES(self), "priority")) { struct sched_param priority; pthread_attr_t thread_attributes; priority.sched_priority = mlt_properties_get_int(MLT_CONSUMER_PROPERTIES(self), "priority"); pthread_attr_init(&thread_attributes); pthread_attr_setschedpolicy(&thread_attributes, SCHED_OTHER); pthread_attr_setschedparam(&thread_attributes, &priority); #if !defined(__ANDROID__) || (defined(__ANDROID__) && __ANDROID_API__ >= 28) pthread_attr_setinheritsched(&thread_attributes, PTHREAD_EXPLICIT_SCHED); #endif pthread_attr_setscope(&thread_attributes, PTHREAD_SCOPE_SYSTEM); while (n--) { if (pthread_create(thread, &thread_attributes, consumer_worker_thread, self) < 0) { if (pthread_create(thread, NULL, consumer_worker_thread, self) == 0) mlt_deque_push_back(priv->worker_threads, thread); } else { mlt_deque_push_back(priv->worker_threads, thread); } thread++; } pthread_attr_destroy(&thread_attributes); } else { while (n--) { if (pthread_create(thread, NULL, consumer_worker_thread, self) == 0) mlt_deque_push_back(priv->worker_threads, thread); thread++; } } priv->started = 1; } /** Stop the read/render thread. * * \private \memberof mlt_consumer_s * \param self a consumer */ static void consumer_read_ahead_stop(mlt_consumer self) { consumer_private *priv = self->local; // Make sure we're running int expected = 1; if (atomic_compare_exchange_strong(&priv->started, &expected, 0)) { // Inform thread to stop priv->ahead = 0; mlt_events_fire(MLT_CONSUMER_PROPERTIES(self), "consumer-stopping", mlt_event_data_none()); // Broadcast to the condition in case it's waiting pthread_mutex_lock(&priv->queue_mutex); pthread_cond_broadcast(&priv->queue_cond); pthread_mutex_unlock(&priv->queue_mutex); // Broadcast to the put condition in case it's waiting pthread_mutex_lock(&priv->put_mutex); pthread_cond_broadcast(&priv->put_cond); pthread_mutex_unlock(&priv->put_mutex); // Join the thread mlt_thread_join(self); // Destroy the frame queue mutex pthread_mutex_destroy(&priv->queue_mutex); // Destroy the condition pthread_cond_destroy(&priv->queue_cond); } } /** Stop the worker threads. * * \private \memberof mlt_consumer_s * \param self a consumer */ static void consumer_work_stop(mlt_consumer self) { consumer_private *priv = self->local; // Make sure we're running int expected = 1; if (atomic_compare_exchange_strong(&priv->started, &expected, 0)) { // Inform thread to stop priv->ahead = 0; mlt_events_fire(MLT_CONSUMER_PROPERTIES(self), "consumer-stopping", mlt_event_data_none()); // Broadcast to the queue condition in case it's waiting pthread_mutex_lock(&priv->queue_mutex); pthread_cond_broadcast(&priv->queue_cond); pthread_mutex_unlock(&priv->queue_mutex); // Broadcast to the put condition in case it's waiting pthread_mutex_lock(&priv->put_mutex); pthread_cond_broadcast(&priv->put_cond); pthread_mutex_unlock(&priv->put_mutex); // Broadcast to the done condition in case it's waiting pthread_mutex_lock(&priv->done_mutex); pthread_cond_broadcast(&priv->done_cond); pthread_mutex_unlock(&priv->done_mutex); // Join the threads pthread_t *thread; while ((thread = mlt_deque_pop_back(priv->worker_threads))) pthread_join(*thread, NULL); // Deallocate the array of threads free(priv->threads); // Destroy the mutexes pthread_mutex_destroy(&priv->queue_mutex); pthread_mutex_destroy(&priv->done_mutex); // Destroy the conditions pthread_cond_destroy(&priv->queue_cond); pthread_cond_destroy(&priv->done_cond); // Wipe the queues while (mlt_deque_count(priv->queue)) mlt_frame_close(mlt_deque_pop_back(priv->queue)); // Close the queues mlt_deque_close(priv->queue); mlt_deque_close(priv->worker_threads); mlt_events_fire(MLT_CONSUMER_PROPERTIES(self), "consumer-thread-stopped", mlt_event_data_none()); } } /** Flush the read/render thread's buffer. * * \public \memberof mlt_consumer_s * \param self a consumer */ void mlt_consumer_purge(mlt_consumer self) { if (self) { consumer_private *priv = self->local; pthread_mutex_lock(&priv->put_mutex); if (priv->put) { mlt_frame_close(priv->put); priv->put = NULL; } pthread_cond_broadcast(&priv->put_cond); pthread_mutex_unlock(&priv->put_mutex); if (self->purge) self->purge(self); if (priv->started && priv->real_time) pthread_mutex_lock(&priv->queue_mutex); while (priv->started && mlt_deque_count(priv->queue)) mlt_frame_close(mlt_deque_pop_back(priv->queue)); if (priv->started && priv->real_time) { priv->is_purge = 1; pthread_cond_broadcast(&priv->queue_cond); pthread_mutex_unlock(&priv->queue_mutex); if (abs(priv->real_time) > 1) { pthread_mutex_lock(&priv->done_mutex); pthread_cond_broadcast(&priv->done_cond); pthread_mutex_unlock(&priv->done_mutex); } } pthread_mutex_lock(&priv->put_mutex); if (priv->put) { mlt_frame_close(priv->put); priv->put = NULL; } pthread_cond_broadcast(&priv->put_cond); pthread_mutex_unlock(&priv->put_mutex); } } /** Use multiple worker threads and a work queue. */ static mlt_frame worker_get_frame(mlt_consumer self, mlt_properties properties) { // Frame to return mlt_frame frame = NULL; consumer_private *priv = self->local; int threads = abs(priv->real_time); int audio_off = mlt_properties_get_int(properties, "audio_off"); int samples = 0; void *audio = NULL; int buffer = mlt_properties_get_int(properties, "_buffer"); buffer = buffer > 0 ? buffer : mlt_properties_get_int(properties, "buffer"); // This is a heuristic to determine a suitable minimum buffer size for the number of threads. int headroom = (priv->real_time < 0) ? threads : (2 + threads * threads); buffer = MAX(buffer, headroom); // Start worker threads if not already started. if (!priv->ahead) { int prefill = mlt_properties_get_int(properties, "prefill"); prefill = prefill > 0 && prefill < buffer ? prefill : buffer; set_audio_format(self); set_image_format(self); consumer_work_start(self); // Fill the work queue. int i = buffer; while (priv->ahead && i--) { frame = mlt_consumer_get_frame(self); if (frame) { // Process the audio if (!audio_off) { samples = mlt_audio_calculate_frame_samples(priv->fps, priv->frequency, priv->aud_counter++); mlt_frame_get_audio(frame, &audio, &priv->audio_format, &priv->frequency, &priv->channels, &samples); } pthread_mutex_lock(&priv->queue_mutex); mlt_deque_push_back(priv->queue, frame); pthread_cond_signal(&priv->queue_cond); pthread_mutex_unlock(&priv->queue_mutex); priv->speed = mlt_properties_get_int(MLT_FRAME_PROPERTIES(frame), "_speed"); buffer = (priv->speed == 0) ? 1 : buffer; } } // Wait for prefill while (priv->ahead && first_unprocessed_frame(self) < prefill) { pthread_mutex_lock(&priv->done_mutex); pthread_cond_wait(&priv->done_cond, &priv->done_mutex); pthread_mutex_unlock(&priv->done_mutex); } priv->process_head = threads; } // mlt_log_verbose( MLT_CONSUMER_SERVICE(self), "size %d done count %d work count %d process_head %d\n", // threads, first_unprocessed_frame( self ), mlt_deque_count( priv->queue ), priv->process_head ); // Feed the work queupriv->speede while (priv->ahead && mlt_deque_count(priv->queue) < buffer) { frame = mlt_consumer_get_frame(self); if (frame) { // Process the audio if (!audio_off) { samples = mlt_audio_calculate_frame_samples(priv->fps, priv->frequency, priv->aud_counter++); mlt_frame_get_audio(frame, &audio, &priv->audio_format, &priv->frequency, &priv->channels, &samples); } pthread_mutex_lock(&priv->queue_mutex); mlt_deque_push_back(priv->queue, frame); pthread_cond_signal(&priv->queue_cond); pthread_mutex_unlock(&priv->queue_mutex); priv->speed = mlt_properties_get_int(MLT_FRAME_PROPERTIES(frame), "_speed"); buffer = (priv->speed == 0) ? 1 : buffer; } } // Wait if not realtime. while (priv->ahead && priv->real_time < 0 && !priv->is_purge && !(mlt_properties_get_int(MLT_FRAME_PROPERTIES( MLT_FRAME(mlt_deque_peek_front(priv->queue))), "rendered"))) { pthread_mutex_lock(&priv->done_mutex); pthread_cond_wait(&priv->done_cond, &priv->done_mutex); pthread_mutex_unlock(&priv->done_mutex); } // Get the frame from the queue. pthread_mutex_lock(&priv->queue_mutex); frame = mlt_deque_pop_front(priv->queue); pthread_mutex_unlock(&priv->queue_mutex); if (!frame) { priv->is_purge = 0; return frame; } // Adapt the worker process head to the runtime conditions. if (priv->real_time > 0) { if (mlt_properties_get_int(MLT_FRAME_PROPERTIES(frame), "rendered")) { priv->consecutive_dropped = 0; if (priv->process_head > threads && priv->consecutive_rendered >= priv->process_head) priv->process_head--; else priv->consecutive_rendered++; } else { priv->consecutive_rendered = 0; if (priv->process_head < buffer - threads && priv->consecutive_dropped > threads) priv->process_head++; else priv->consecutive_dropped++; } // mlt_log_verbose( MLT_CONSUMER_SERVICE(self), "dropped %d rendered %d process_head %d\n", // priv->consecutive_dropped, priv->consecutive_rendered, priv->process_head ); // Check for too many consecutively dropped frames if (priv->consecutive_dropped > mlt_properties_get_int(properties, "drop_max")) { int orig_buffer = mlt_properties_get_int(properties, "buffer"); int prefill = mlt_properties_get_int(properties, "prefill"); mlt_log_verbose(self, "too many frames dropped - "); // If using a default low-latency buffer level (SDL) and below the limit if ((orig_buffer == 1 || prefill == 1) && buffer < (threads + 1) * 10) { // Auto-scale the buffer to compensate mlt_log_verbose(self, "increasing buffer to %d\n", buffer + threads); mlt_properties_set_int(properties, "_buffer", buffer + threads); priv->consecutive_dropped = priv->fps / 2; } else { // Tell the consumer to render it mlt_log_verbose(self, "forcing next frame\n"); mlt_properties_set_int(MLT_FRAME_PROPERTIES(frame), "rendered", 1); priv->consecutive_dropped = 0; } } if (!mlt_properties_get_int(MLT_FRAME_PROPERTIES(frame), "rendered")) { int dropped = mlt_properties_get_int(properties, "drop_count"); mlt_properties_set_int(properties, "drop_count", ++dropped); mlt_log_verbose(MLT_CONSUMER_SERVICE(self), "dropped video frame %d\n", dropped); } } if (priv->is_purge) { priv->is_purge = 0; mlt_frame_close(frame); frame = NULL; } return frame; } /** Get the next frame from the producer connected to a consumer. * * Typically, one uses this instead of \p mlt_consumer_get_frame to make * the asynchronous/real-time behavior configurable at runtime. * You should close the frame returned from this when you are done with it. * * \public \memberof mlt_consumer_s * \param self a consumer * \return a frame */ mlt_frame mlt_consumer_rt_frame(mlt_consumer self) { // Frame to return mlt_frame frame = NULL; // Get the properties mlt_properties properties = MLT_CONSUMER_PROPERTIES(self); consumer_private *priv = self->local; // Check if the user has requested real time or not if (priv->real_time > 1 || priv->real_time < -1) { // see above return worker_get_frame(self, properties); } else if (priv->real_time == 1 || priv->real_time == -1) { int size = 1; int buffer = mlt_properties_get_int(properties, "buffer"); int prefill = mlt_properties_get_int(properties, "prefill"); int preroll_size = prefill > 0 && prefill < buffer ? prefill : buffer; if (priv->preroll) { #ifndef _WIN32 consumer_read_ahead_start(self); #endif if (buffer > 1 && priv->speed) size = preroll_size; priv->preroll = 0; } // Get frame from queue pthread_mutex_lock(&priv->queue_mutex); mlt_log_timings_begin(); while (priv->ahead && mlt_deque_count(priv->queue) < size) { pthread_cond_wait(&priv->queue_cond, &priv->queue_mutex); if (priv->speed == 0) { size = 1; } else if (priv->preroll) { if (buffer > 1 && priv->speed) size = preroll_size; priv->preroll = 0; } } frame = mlt_deque_pop_front(priv->queue); mlt_log_timings_end(NULL, "wait_for_frame_queue"); pthread_cond_broadcast(&priv->queue_cond); pthread_mutex_unlock(&priv->queue_mutex); if (priv->real_time == 1 && frame && !mlt_properties_get_int(MLT_FRAME_PROPERTIES(frame), "rendered")) { int dropped = mlt_properties_get_int(properties, "drop_count"); mlt_properties_set_int(properties, "drop_count", ++dropped); mlt_log_verbose(MLT_CONSUMER_SERVICE(self), "dropped video frame %d\n", dropped); } } else // real_time == 0 { if (!priv->ahead) { priv->ahead = 1; mlt_events_fire(properties, "consumer-thread-started", mlt_event_data_none()); } // Get the frame in non real time frame = mlt_consumer_get_frame(self); // This isn't true, but from the consumers perspective it is if (frame != NULL) { mlt_properties_set_int(MLT_FRAME_PROPERTIES(frame), "rendered", 1); // WebVfx uses this to setup a consumer-stopping event handler. mlt_properties_set_data(MLT_FRAME_PROPERTIES(frame), "consumer", self, 0, NULL, NULL); } } return frame; } /** Callback for the implementation to indicate a stopped condition. * * \public \memberof mlt_consumer_s * \param self a consumer */ void mlt_consumer_stopped(mlt_consumer self) { mlt_properties_set_int(MLT_CONSUMER_PROPERTIES(self), "running", 0); mlt_events_fire(MLT_CONSUMER_PROPERTIES(self), "consumer-stopped", mlt_event_data_none()); mlt_event_unblock(((consumer_private *) self->local)->event_listener); } /** Stop the consumer. * * \public \memberof mlt_consumer_s * \param self a consumer * \return true if there was an error */ int mlt_consumer_stop(mlt_consumer self) { if (!self) { return 1; } // Get the properties mlt_properties properties = MLT_CONSUMER_PROPERTIES(self); consumer_private *priv = self->local; // Just in case... mlt_log(MLT_CONSUMER_SERVICE(self), MLT_LOG_DEBUG, "stopping put waiting\n"); pthread_mutex_lock(&priv->put_mutex); priv->put_active = 0; pthread_cond_broadcast(&priv->put_cond); pthread_mutex_unlock(&priv->put_mutex); // Stop the consumer mlt_log(MLT_CONSUMER_SERVICE(self), MLT_LOG_DEBUG, "stopping consumer\n"); // Cancel the read ahead threads if (priv->started) { // Unblock the consumer calling mlt_consumer_rt_frame pthread_mutex_lock(&priv->queue_mutex); pthread_cond_broadcast(&priv->queue_cond); pthread_mutex_unlock(&priv->queue_mutex); } // Invoke the child callback if (self->stop != NULL) self->stop(self); // Check if the user has requested real time or not and stop if necessary mlt_log(MLT_CONSUMER_SERVICE(self), MLT_LOG_DEBUG, "stopping read_ahead\n"); if (abs(priv->real_time) == 1) consumer_read_ahead_stop(self); else if (abs(priv->real_time) > 1) consumer_work_stop(self); // Kill the test card mlt_properties_set_data(properties, "test_card_producer", NULL, 0, NULL, NULL); // Check and run a post command if (mlt_properties_get(properties, "post")) if (system(mlt_properties_get(properties, "post")) == -1) mlt_log(MLT_CONSUMER_SERVICE(self), MLT_LOG_ERROR, "system(%s) failed!\n", mlt_properties_get(properties, "post")); mlt_log(MLT_CONSUMER_SERVICE(self), MLT_LOG_DEBUG, "stopped\n"); return 0; } /** Determine if the consumer is stopped. * * \public \memberof mlt_consumer_s * \param self a consumer * \return true if the consumer is stopped */ int mlt_consumer_is_stopped(mlt_consumer self) { // Check if the consumer is stopped if (self && self->is_stopped) return self->is_stopped(self); return 0; } /** Close and destroy the consumer. * * \public \memberof mlt_consumer_s * \param self a consumer */ void mlt_consumer_close(mlt_consumer self) { if (self != NULL && mlt_properties_dec_ref(MLT_CONSUMER_PROPERTIES(self)) <= 0) { // Get the childs close function void (*consumer_close)() = self->close; if (consumer_close) { // Just in case... //mlt_consumer_stop( self ); self->close = NULL; consumer_close(self); } else { consumer_private *priv = self->local; // Make sure it only gets called once self->parent.close = NULL; // Destroy the push mutex and condition pthread_mutex_destroy(&priv->put_mutex); pthread_cond_destroy(&priv->put_cond); pthread_mutex_destroy(&priv->position_mutex); mlt_service_close(&self->parent); free(priv); } } } /** Get the position of the last frame shown. * * \public \memberof mlt_consumer_s * \param consumer a consumer * \return the position */ mlt_position mlt_consumer_position(mlt_consumer consumer) { consumer_private *priv = consumer->local; pthread_mutex_lock(&priv->position_mutex); mlt_position result = priv->position; pthread_mutex_unlock(&priv->position_mutex); return result; } static void mlt_thread_create(mlt_consumer self, mlt_thread_function_t function) { consumer_private *priv = self->local; mlt_properties properties = MLT_CONSUMER_PROPERTIES(self); if (mlt_properties_get(MLT_CONSUMER_PROPERTIES(self), "priority")) { struct sched_param priority; priority.sched_priority = mlt_properties_get_int(MLT_CONSUMER_PROPERTIES(self), "priority"); mlt_event_data_thread data = {.thread = &priv->ahead_thread, .priority = &priority.sched_priority, .function = function, .data = self}; if (mlt_events_fire(properties, "consumer-thread-create", mlt_event_data_from_object(&data)) < 1) { pthread_attr_t thread_attributes; pthread_attr_init(&thread_attributes); pthread_attr_setschedpolicy(&thread_attributes, SCHED_OTHER); pthread_attr_setschedparam(&thread_attributes, &priority); #if !defined(__ANDROID__) \ || (defined(__ANDROID__) \ && __ANDROID_API__ \ >= 28) // pthread_attr_setinheritsched is not available until API level 28 pthread_attr_setinheritsched(&thread_attributes, PTHREAD_EXPLICIT_SCHED); #endif pthread_attr_setscope(&thread_attributes, PTHREAD_SCOPE_SYSTEM); priv->ahead_thread = malloc(sizeof(pthread_t)); pthread_t *handle = priv->ahead_thread; if (pthread_create((pthread_t *) &(*handle), &thread_attributes, function, self) < 0) pthread_create((pthread_t *) &(*handle), NULL, function, self); pthread_attr_destroy(&thread_attributes); } } else { int priority = -1; mlt_event_data_thread data = {.thread = &priv->ahead_thread, .priority = &priority, .function = function, .data = self}; if (mlt_events_fire(properties, "consumer-thread-create", mlt_event_data_from_object(&data)) < 1) { priv->ahead_thread = malloc(sizeof(pthread_t)); pthread_t *handle = priv->ahead_thread; pthread_create((pthread_t *) &(*handle), NULL, function, self); } } } static void mlt_thread_join(mlt_consumer self) { consumer_private *priv = self->local; mlt_event_data_thread data = {.thread = &priv->ahead_thread, .priority = NULL, .function = NULL, .data = self}; if (mlt_events_fire(MLT_CONSUMER_PROPERTIES(self), "consumer-thread-join", mlt_event_data_from_object(&data)) < 1) { pthread_t *handle = priv->ahead_thread; pthread_join(*handle, NULL); free(priv->ahead_thread); } priv->ahead_thread = NULL; } mlt-7.22.0/src/framework/mlt_consumer.h000664 000000 000000 00000017523 14531534050 020032 0ustar00rootroot000000 000000 /** * \file mlt_consumer.h * \brief abstraction for all consumer services * \see mlt_consumer_s * * Copyright (C) 2003-2022 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef MLT_CONSUMER_H #define MLT_CONSUMER_H #include "mlt_events.h" #include "mlt_service.h" #include /** \brief Consumer abstract service class * * A consumer is a service that pulls audio and video from the connected * producers, filters, and transitions. Typically a consumer is used to * output audio and/or video to a device, file, or socket. * * \extends mlt_service_s * \properties \em rescale the scaling algorithm to pass on to all scaling * filters, defaults to "bilinear" * \properties \em buffer the number of frames to use in the asynchronous * render thread, defaults to 25 * \properties \em prefill the number of frames to render before commencing * output when real_time <> 0, defaults to the size of buffer * \properties \em drop_max the maximum number of consecutively dropped frames, defaults to 5 * \properties \em frequency the audio sample rate to use in Hertz, defaults to 48000 * \properties \em channels the number of audio channels to use, defaults to 2 * \properties \em channel_layout the layout of the audio channels, defaults to auto. * other options include: mono, stereo, 5.1, 7.1, etc. * \properties \em real_time the asynchronous behavior: 1 (default) for asynchronous * with frame dropping, -1 for asynchronous without frame dropping, 0 to disable (synchronous) * \properties \em test_card the name of a resource to use as the test card, defaults to * environment variable MLT_TEST_CARD. If undefined, the hard-coded default test card is * white silence. A test card is what appears when nothing is produced. * \event \em consumer-frame-show Subclass implementations fire this immediately after showing a frame * or when a frame should be shown (if audio-only consumer). The event data is a frame. * \event \em consumer-frame-render The base class fires this immediately before rendering a frame; * the event data is a frame. * \event \em consumer-thread-create Override the implementation of creating and * starting a thread by listening and responding to this (real_time 1 or -1 only). * The event data is a pointer to mlt_event_data_thread. * \event \em consumer-thread-join Override the implementation of waiting and * joining a terminated thread by listening and responding to this (real_time 1 or -1 only). * The event data is a pointer to mlt_event_data_thread. * \event \em consumer-thread-started The base class fires when beginning execution of a rendering thread. * \event \em consumer-thread-stopped The base class fires when a rendering thread has ended. * \event \em consumer-stopping This is fired when stop was requested, but before render threads are joined. * \event \em consumer-stopped This is fired when the subclass implementation calls mlt_consumer_stopped(). * \properties \em fps video frames per second as floating point (read only) * \properties \em frame_rate_num the numerator of the video frame rate, overrides \p mlt_profile_s * \properties \em frame_rate_den the denominator of the video frame rate, overrides \p mlt_profile_s * \properties \em width the horizontal video resolution, overrides \p mlt_profile_s * \properties \em height the vertical video resolution, overrides \p mlt_profile_s * \properties \em progressive a flag that indicates if the video is interlaced * or progressive, overrides \p mlt_profile_s * \properties \em aspect_ratio the video sample (pixel) aspect ratio as floating point (read only) * \properties \em sample_aspect_num the numerator of the sample aspect ratio, overrides \p mlt_profile_s * \properties \em sample_aspect_den the denominator of the sample aspect ratio, overrides \p mlt_profile_s * \properties \em display_ratio the video frame aspect ratio as floating point (read only) * \properties \em display_aspect_num the numerator of the video frame aspect ratio, overrides \p mlt_profile_s * \properties \em display_aspect_den the denominator of the video frame aspect ratio, overrides \p mlt_profile_s * \properties \em priority the OS scheduling priority for the render threads when real_time is not 0. * \properties \em top_field_first when not progressive, whether interlace field order is top-field-first, defaults to 0. * Set this to -1 if the consumer does not care about the field order. * \properties \em mlt_image_format the image format to request in rendering threads, defaults to yuv422 * \properties \em mlt_audio_format the audio format to request in rendering threads, defaults to S16 * \properties \em audio_off set non-zero to disable audio processing * \properties \em video_off set non-zero to disable video processing * \properties \em drop_count the number of video frames not rendered since starting consumer * \properties \em color_range the color range as tv/mpeg (limited) or pc/jpeg (full); default is unset, which implies tv/mpeg * \properties \em color_trc the color transfer characteristic (gamma), default is unset * \properties \em deinterlacer the deinterlace algorithm to pass to deinterlace filters, defaults to "yadif" */ struct mlt_consumer_s { /** A consumer is a service. */ struct mlt_service_s parent; /** Start the consumer to pull frames (virtual function). * * \param mlt_consumer a consumer * \return true if there was an error */ int (*start)(mlt_consumer); /** Stop the consumer (virtual function). * * \param mlt_consumer a consumer * \return true if there was an error */ int (*stop)(mlt_consumer); /** Get whether the consumer is running or stopped (virtual function). * * \param mlt_consumer a consumer * \return true if the consumer is stopped */ int (*is_stopped)(mlt_consumer); /** Purge the consumer of buffered data (virtual function). * * \param mlt_consumer a consumer */ void (*purge)(mlt_consumer); /** The destructor virtual function * * \param mlt_consumer a consumer */ void (*close)(mlt_consumer); void *local; /**< \private instance object */ void *child; /**< \private the object of a subclass */ }; #define MLT_CONSUMER_SERVICE(consumer) (&(consumer)->parent) #define MLT_CONSUMER_PROPERTIES(consumer) MLT_SERVICE_PROPERTIES(MLT_CONSUMER_SERVICE(consumer)) extern int mlt_consumer_init(mlt_consumer self, void *child, mlt_profile profile); extern mlt_consumer mlt_consumer_new(mlt_profile profile); extern mlt_service mlt_consumer_service(mlt_consumer self); extern mlt_properties mlt_consumer_properties(mlt_consumer self); extern int mlt_consumer_connect(mlt_consumer self, mlt_service producer); extern int mlt_consumer_start(mlt_consumer self); extern void mlt_consumer_purge(mlt_consumer self); extern int mlt_consumer_put_frame(mlt_consumer self, mlt_frame frame); extern mlt_frame mlt_consumer_get_frame(mlt_consumer self); extern mlt_frame mlt_consumer_rt_frame(mlt_consumer self); extern int mlt_consumer_stop(mlt_consumer self); extern int mlt_consumer_is_stopped(mlt_consumer self); extern void mlt_consumer_stopped(mlt_consumer self); extern void mlt_consumer_close(mlt_consumer); extern mlt_position mlt_consumer_position(mlt_consumer); #endif mlt-7.22.0/src/framework/mlt_deque.c000664 000000 000000 00000021712 14531534050 017270 0ustar00rootroot000000 000000 /** * \file mlt_deque.c * \brief double ended queue * \see mlt_deque_s * * Copyright (C) 2003-2019 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ // Local header files #include "mlt_deque.h" // System header files #include #include #include /** \brief Deque entry class * */ typedef union { void *addr; int value; double floating; } deque_entry; /** \brief Double-Ended Queue (deque) class * * The double-ended queue is a very versatile data structure. MLT uses it as * list, stack, and circular queue. */ struct mlt_deque_s { deque_entry *list; int size; atomic_int count; }; /** Create a deque. * * \public \memberof mlt_deque_s * \return a new deque */ mlt_deque mlt_deque_init() { mlt_deque self = calloc(1, sizeof(struct mlt_deque_s)); return self; } /** Return the number of items in the deque. * * \public \memberof mlt_deque_s * \param self a deque * \return the number of items */ int mlt_deque_count(mlt_deque self) { if (self) return self->count; else return 0; } /** Allocate space on the deque. * * \private \memberof mlt_deque_s * \param self a deque * \return true if there was an error */ static int mlt_deque_allocate(mlt_deque self) { if (self->count == self->size) { self->list = realloc(self->list, sizeof(deque_entry) * (self->size + 20)); self->size += 20; } return self->list == NULL; } /** Push an item to the end. * * \public \memberof mlt_deque_s * \param self a deque * \param item an opaque pointer * \return true if there was an error */ int mlt_deque_push_back(mlt_deque self, void *item) { int error = mlt_deque_allocate(self); if (error == 0) self->list[self->count++].addr = item; return error; } /** Pop an item. * * \public \memberof mlt_deque_s * \param self a pointer * \return an opaque pointer */ void *mlt_deque_pop_back(mlt_deque self) { return self->count > 0 ? self->list[--self->count].addr : NULL; } /** Queue an item at the start. * * \public \memberof mlt_deque_s * \param self a deque * \param item an opaque pointer * \return true if there was an error */ int mlt_deque_push_front(mlt_deque self, void *item) { int error = mlt_deque_allocate(self); if (error == 0) { memmove(&self->list[1], self->list, (self->count++) * sizeof(deque_entry)); self->list[0].addr = item; } return error; } /** Remove an item from the start. * * \public \memberof mlt_deque_s * \param self a pointer * \return an opaque pointer */ void *mlt_deque_pop_front(mlt_deque self) { void *item = NULL; if (self->count > 0) { item = self->list[0].addr; memmove(self->list, &self->list[1], (--self->count) * sizeof(deque_entry)); } return item; } /** Inquire on item at back of deque but don't remove. * * \public \memberof mlt_deque_s * \param self a deque * \return an opaque pointer */ void *mlt_deque_peek_back(mlt_deque self) { return self->count > 0 ? self->list[self->count - 1].addr : NULL; } /** Inquire on item at front of deque but don't remove. * * \public \memberof mlt_deque_s * \param self a deque * \return an opaque pointer */ void *mlt_deque_peek_front(mlt_deque self) { return self->count > 0 ? self->list[0].addr : NULL; } /** Inquire on item in deque but don't remove. * * \public \memberof mlt_deque_s * \param self a deque * \param index the position in the deque * \return an opaque pointer */ void *mlt_deque_peek(mlt_deque self, int index) { return self->count > index ? self->list[index].addr : NULL; } /** Insert an item in a sorted fashion. * * Optimized for the equivalent of \p mlt_deque_push_back. * * \public \memberof mlt_deque_s * \param self a deque * \param item an opaque pointer * \param cmp a function pointer to the comparison function * \return true if there was an error */ int mlt_deque_insert(mlt_deque self, void *item, mlt_deque_compare cmp) { int error = mlt_deque_allocate(self); if (error == 0) { int n = self->count + 1; while (--n) if (cmp(item, self->list[n - 1].addr) >= 0) break; memmove(&self->list[n + 1], &self->list[n], (self->count - n) * sizeof(deque_entry)); self->list[n].addr = item; self->count++; } return error; } /** Push an integer to the end. * * \public \memberof mlt_deque_s * \param self a deque * \param item an integer * \return true if there was an error */ int mlt_deque_push_back_int(mlt_deque self, int item) { int error = mlt_deque_allocate(self); if (error == 0) self->list[self->count++].value = item; return error; } /** Pop an integer. * * \public \memberof mlt_deque_s * \param self a deque * \return an integer */ int mlt_deque_pop_back_int(mlt_deque self) { return self->count > 0 ? self->list[--self->count].value : 0; } /** Queue an integer at the start. * * \public \memberof mlt_deque_s * \param self a deque * \param item an integer * \return true if there was an error */ int mlt_deque_push_front_int(mlt_deque self, int item) { int error = mlt_deque_allocate(self); if (error == 0) { memmove(&self->list[1], self->list, (self->count++) * sizeof(deque_entry)); self->list[0].value = item; } return error; } /** Remove an integer from the start. * * \public \memberof mlt_deque_s * \param self a deque * \return an integer */ int mlt_deque_pop_front_int(mlt_deque self) { int item = 0; if (self->count > 0) { item = self->list[0].value; memmove(self->list, &self->list[1], (--self->count) * sizeof(deque_entry)); } return item; } /** Inquire on an integer at back of deque but don't remove. * * \public \memberof mlt_deque_s * \param self a deque * \return an integer */ int mlt_deque_peek_back_int(mlt_deque self) { return self->count > 0 ? self->list[self->count - 1].value : 0; } /** Inquire on an integer at front of deque but don't remove. * * \public \memberof mlt_deque_s * \param self a deque * \return an integer */ int mlt_deque_peek_front_int(mlt_deque self) { return self->count > 0 ? self->list[0].value : 0; } /** Push a double float to the end. * * \public \memberof mlt_deque_s * \param self a deque * \param item a double float * \return true if there was an error */ int mlt_deque_push_back_double(mlt_deque self, double item) { int error = mlt_deque_allocate(self); if (error == 0) self->list[self->count++].floating = item; return error; } /** Pop a double float. * * \public \memberof mlt_deque_s * \param self a deque * \return a double float */ double mlt_deque_pop_back_double(mlt_deque self) { return self->count > 0 ? self->list[--self->count].floating : 0; } /** Queue a double float at the start. * * \public \memberof mlt_deque_s * \param self a deque * \param item a double float * \return true if there was an error */ int mlt_deque_push_front_double(mlt_deque self, double item) { int error = mlt_deque_allocate(self); if (error == 0) { memmove(&self->list[1], self->list, (self->count++) * sizeof(deque_entry)); self->list[0].floating = item; } return error; } /** Remove a double float from the start. * * \public \memberof mlt_deque_s * \param self a deque * \return a double float */ double mlt_deque_pop_front_double(mlt_deque self) { double item = 0; if (self->count > 0) { item = self->list[0].floating; memmove(self->list, &self->list[1], (--self->count) * sizeof(deque_entry)); } return item; } /** Inquire on a double float at back of deque but don't remove. * * \public \memberof mlt_deque_s * \param self a deque * \return a double float */ double mlt_deque_peek_back_double(mlt_deque self) { return self->count > 0 ? self->list[self->count - 1].floating : 0; } /** Inquire on a double float at front of deque but don't remove. * * \public \memberof mlt_deque_s * \param self a deque * \return a double float */ double mlt_deque_peek_front_double(mlt_deque self) { return self->count > 0 ? self->list[0].floating : 0; } /** Destroy the queue. * * \public \memberof mlt_deque_s * \param self a deque */ void mlt_deque_close(mlt_deque self) { free(self->list); free(self); } mlt-7.22.0/src/framework/mlt_deque.h000664 000000 000000 00000004675 14531534050 017306 0ustar00rootroot000000 000000 /** * \file mlt_deque.h * \brief double ended queue * \see mlt_deque_s * * Copyright (C) 2003-2014 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef MLT_DEQUE_H #define MLT_DEQUE_H #include "mlt_types.h" /** The callback function used to compare items for insert sort. * * \public \memberof mlt_deque_s * \param a the first object * \param b the second object * \returns 0 if equal, < 0 if a < b, or > 0 if a > b */ typedef int (*mlt_deque_compare)(void *a, void *b); extern mlt_deque mlt_deque_init(); extern int mlt_deque_count(mlt_deque self); extern int mlt_deque_push_back(mlt_deque self, void *item); extern void *mlt_deque_pop_back(mlt_deque self); extern int mlt_deque_push_front(mlt_deque self, void *item); extern void *mlt_deque_pop_front(mlt_deque self); extern void *mlt_deque_peek_back(mlt_deque self); extern void *mlt_deque_peek_front(mlt_deque self); extern void *mlt_deque_peek(mlt_deque self, int index); extern int mlt_deque_insert(mlt_deque self, void *item, mlt_deque_compare); extern int mlt_deque_push_back_int(mlt_deque self, int item); extern int mlt_deque_pop_back_int(mlt_deque self); extern int mlt_deque_push_front_int(mlt_deque self, int item); extern int mlt_deque_pop_front_int(mlt_deque self); extern int mlt_deque_peek_back_int(mlt_deque self); extern int mlt_deque_peek_front_int(mlt_deque self); extern int mlt_deque_push_back_double(mlt_deque self, double item); extern double mlt_deque_pop_back_double(mlt_deque self); extern int mlt_deque_push_front_double(mlt_deque self, double item); extern double mlt_deque_pop_front_double(mlt_deque self); extern double mlt_deque_peek_back_double(mlt_deque self); extern double mlt_deque_peek_front_double(mlt_deque self); extern void mlt_deque_close(mlt_deque self); #endif mlt-7.22.0/src/framework/mlt_events.c000664 000000 000000 00000037712 14531534050 017500 0ustar00rootroot000000 000000 /** * \file mlt_events.c * \brief event handling * \see mlt_events_struct * * Copyright (C) 2004-2021 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include "mlt_events.h" #include "mlt_properties.h" /* Memory leak checks. */ #undef _MLT_EVENT_CHECKS_ #ifdef _MLT_EVENT_CHECKS_ static int events_created = 0; static int events_destroyed = 0; #endif /** \brief Events class * * Events provide messages and notifications between services and the application. * A service can register an event and fire/send it upon certain conditions or times. * Likewise, a service or an application can listen/receive specific events on specific * services. */ struct mlt_events_struct { mlt_properties owner; mlt_properties listeners; }; typedef struct mlt_events_struct *mlt_events; /** \brief Event class * */ struct mlt_event_struct { mlt_events parent; atomic_int_fast32_t ref_count; atomic_int_fast32_t block_count; mlt_listener listener; void *listener_data; }; /** Increment the reference count on self event. * * \public \memberof mlt_event_struct * \param self an event */ void mlt_event_inc_ref(mlt_event self) { if (self != NULL) self->ref_count++; } /** Increment the block count on self event. * * \public \memberof mlt_event_struct * \param self an event */ void mlt_event_block(mlt_event self) { if (self != NULL && self->parent != NULL) self->block_count++; } /** Decrement the block count on self event. * * \public \memberof mlt_event_struct * \param self an event */ void mlt_event_unblock(mlt_event self) { if (self != NULL && self->parent != NULL) self->block_count--; } /** Close self event. * * \public \memberof mlt_event_struct * \param self an event */ void mlt_event_close(mlt_event self) { if (self != NULL) { if (--self->ref_count == 1) self->parent = NULL; if (self->ref_count <= 0) { #ifdef _MLT_EVENT_CHECKS_ mlt_log(NULL, MLT_LOG_DEBUG, "Events created %d, destroyed %d\n", events_created, ++events_destroyed); #endif free(self); } } } /* Forward declaration to private functions. */ static mlt_events mlt_events_fetch(mlt_properties); static void mlt_events_close(mlt_events); /** Initialise the events structure. * * \public \memberof mlt_events_struct * \param self a properties list */ void mlt_events_init(mlt_properties self) { mlt_events events = mlt_events_fetch(self); if (!events && self) { events = calloc(1, sizeof(struct mlt_events_struct)); if (events) { events->listeners = mlt_properties_new(); events->owner = self; mlt_properties_set_data(self, "_events", events, 0, (mlt_destructor) mlt_events_close, NULL); } } } /** Register an event. * * \public \memberof mlt_events_struct * \param self a properties list * \param id the name of an event * \return true if there was an error */ int mlt_events_register(mlt_properties self, const char *id) { int error = 1; mlt_events events = mlt_events_fetch(self); if (events != NULL) { mlt_properties list = events->listeners; char temp[128]; sprintf(temp, "list:%s", id); if (mlt_properties_get_data(list, temp, NULL) == NULL) mlt_properties_set_data(list, temp, mlt_properties_new(), 0, (mlt_destructor) mlt_properties_close, NULL); } return error; } /** Fire an event. * * \public \memberof mlt_events_struct * \param self a properties list * \param id the name of an event * \param event_data an event data object * \return the number of listeners */ int mlt_events_fire(mlt_properties self, const char *id, mlt_event_data event_data) { int result = 0; mlt_events events = mlt_events_fetch(self); if (events != NULL) { mlt_properties list = events->listeners; mlt_properties listeners = NULL; char temp[128]; sprintf(temp, "list:%s", id); listeners = mlt_properties_get_data(list, temp, NULL); if (listeners != NULL) { for (int i = 0; i < mlt_properties_count(listeners); i++) { mlt_event event = mlt_properties_get_data_at(listeners, i, NULL); if (event != NULL && event->parent != NULL && event->block_count == 0) { event->listener(event->parent->owner, event->listener_data, event_data); ++result; } } } } return result; } /** Register a listener. * * \public \memberof mlt_events_struct * \param self a properties list * \param listener_data an opaque pointer * \param id the name of the event to listen for * \param listener the callback to receive an event message * \return an event */ mlt_event mlt_events_listen(mlt_properties self, void *listener_data, const char *id, mlt_listener listener) { mlt_event event = NULL; mlt_events events = mlt_events_fetch(self); if (events != NULL) { mlt_properties list = events->listeners; mlt_properties listeners = NULL; char temp[128]; sprintf(temp, "list:%s", id); listeners = mlt_properties_get_data(list, temp, NULL); if (listeners != NULL) { int first_null = -1; int i = 0; for (i = 0; event == NULL && i < mlt_properties_count(listeners); i++) { mlt_event entry = mlt_properties_get_data_at(listeners, i, NULL); if (entry != NULL && entry->parent != NULL) { if (entry->listener_data == listener_data && entry->listener == listener) event = entry; } else if ((entry == NULL || entry->parent == NULL) && first_null == -1) { first_null = i; } } if (event == NULL) { event = malloc(sizeof(struct mlt_event_struct)); if (event != NULL) { #ifdef _MLT_EVENT_CHECKS_ events_created++; #endif sprintf(temp, "%d", first_null == -1 ? mlt_properties_count(listeners) : first_null); event->parent = events; event->ref_count = 0; event->block_count = 0; event->listener = listener; event->listener_data = listener_data; mlt_properties_set_data(listeners, temp, event, 0, (mlt_destructor) mlt_event_close, NULL); mlt_event_inc_ref(event); } } } } return event; } /** Block all events for a given listener_data. * * \public \memberof mlt_events_struct * \param self a properties list * \param listener_data the listener's opaque data pointer */ void mlt_events_block(mlt_properties self, void *listener_data) { mlt_events events = mlt_events_fetch(self); if (events != NULL) { int i = 0, j = 0; mlt_properties list = events->listeners; for (j = 0; j < mlt_properties_count(list); j++) { char *temp = mlt_properties_get_name(list, j); if (!strncmp(temp, "list:", 5)) { mlt_properties listeners = mlt_properties_get_data(list, temp, NULL); for (i = 0; i < mlt_properties_count(listeners); i++) { mlt_event entry = mlt_properties_get_data_at(listeners, i, NULL); if (entry != NULL && entry->listener_data == listener_data) mlt_event_block(entry); } } } } } /** Unblock all events for a given listener_data. * * \public \memberof mlt_events_struct * \param self a properties list * \param listener_data the listener's opaque data pointer */ void mlt_events_unblock(mlt_properties self, void *listener_data) { mlt_events events = mlt_events_fetch(self); if (events != NULL) { int i = 0, j = 0; mlt_properties list = events->listeners; for (j = 0; j < mlt_properties_count(list); j++) { char *temp = mlt_properties_get_name(list, j); if (!strncmp(temp, "list:", 5)) { mlt_properties listeners = mlt_properties_get_data(list, temp, NULL); for (i = 0; i < mlt_properties_count(listeners); i++) { mlt_event entry = mlt_properties_get_data_at(listeners, i, NULL); if (entry != NULL && entry->listener_data == listener_data) mlt_event_unblock(entry); } } } } } /** Disconnect all events for a given listener_data. * * \public \memberof mlt_events_struct * \param self a properties list * \param listener_data the listener's opaque data pointer */ void mlt_events_disconnect(mlt_properties self, void *listener_data) { mlt_events events = mlt_events_fetch(self); if (events != NULL) { int i = 0, j = 0; mlt_properties list = events->listeners; for (j = 0; j < mlt_properties_count(list); j++) { char *temp = mlt_properties_get_name(list, j); if (!strncmp(temp, "list:", 5)) { mlt_properties listeners = mlt_properties_get_data(list, temp, NULL); for (i = 0; i < mlt_properties_count(listeners); i++) { mlt_event entry = mlt_properties_get_data_at(listeners, i, NULL); char *name = mlt_properties_get_name(listeners, i); if (entry != NULL && entry->listener_data == listener_data) mlt_properties_set_data(listeners, name, NULL, 0, NULL, NULL); } } } } } /** \brief private to mlt_events_struct, used by mlt_events_wait_for() */ typedef struct { pthread_cond_t cond; pthread_mutex_t mutex; } condition_pair; /** The event listener callback for the wait functions. * * \private \memberof mlt_events_struct * \param self a properties list * \param pair a condition pair */ static void mlt_events_listen_for(mlt_properties self, condition_pair *pair) { pthread_mutex_lock(&pair->mutex); pthread_cond_signal(&pair->cond); pthread_mutex_unlock(&pair->mutex); } /** Prepare to wait for an event. * * \public \memberof mlt_events_struct * \param self a properties list * \param id the name of the event to wait for * \return an event */ mlt_event mlt_events_setup_wait_for(mlt_properties self, const char *id) { condition_pair *pair = malloc(sizeof(condition_pair)); pthread_cond_init(&pair->cond, NULL); pthread_mutex_init(&pair->mutex, NULL); pthread_mutex_lock(&pair->mutex); return mlt_events_listen(self, pair, id, (mlt_listener) mlt_events_listen_for); } /** Wait for an event. * * \public \memberof mlt_events_struct * \param self a properties list * \param event an event */ void mlt_events_wait_for(mlt_properties self, mlt_event event) { if (event != NULL) { condition_pair *pair = event->listener_data; pthread_cond_wait(&pair->cond, &pair->mutex); } } /** Cleanup after waiting for an event. * * \public \memberof mlt_events_struct * \param self a properties list * \param event an event */ void mlt_events_close_wait_for(mlt_properties self, mlt_event event) { if (event != NULL) { condition_pair *pair = event->listener_data; event->parent = NULL; pthread_mutex_unlock(&pair->mutex); pthread_mutex_destroy(&pair->mutex); pthread_cond_destroy(&pair->cond); free(pair); } } /** Fetch the events object. * * \private \memberof mlt_events_struct * \param self a properties list * \return an events object */ static mlt_events mlt_events_fetch(mlt_properties self) { mlt_events events = NULL; if (self != NULL) events = mlt_properties_get_data(self, "_events", NULL); return events; } /** Close the events object. * * \private \memberof mlt_events_struct * \param events an events object */ static void mlt_events_close(mlt_events events) { if (events != NULL) { mlt_properties_close(events->listeners); free(events); } } /** Initialize an empty event data. * * \public \memberof mlt_event_data * \return an event data object */ mlt_event_data mlt_event_data_none() { mlt_event_data event_data; event_data.u.p = NULL; return event_data; } /** Initialize event data with an integer. * * \public \memberof mlt_event_data * \param value the integer with which to initialize the event data * \return an event data object */ mlt_event_data mlt_event_data_from_int(int value) { mlt_event_data event_data; event_data.u.i = value; return event_data; } /** Get an integer from the event data. * * \public \memberof mlt_event_data * \param event_data an event data object * \return an integer */ int mlt_event_data_to_int(mlt_event_data event_data) { return event_data.u.i; } /** Initialize event data with a string. * * \public \memberof mlt_event_data * \param value the string with which to initialize the event data * \return an event data object */ mlt_event_data mlt_event_data_from_string(const char *value) { mlt_event_data event_data; event_data.u.p = (void *) value; return event_data; } /** Get a string from the event data. * * \public \memberof mlt_event_data * \param event_data an event data object * \return a string */ const char *mlt_event_data_to_string(mlt_event_data event_data) { return event_data.u.p; } /** Initialize event data with a frame. * * \public \memberof mlt_event_data * \param frame the frame with which to initialize the event data * \return an event data object */ mlt_event_data mlt_event_data_from_frame(mlt_frame frame) { mlt_event_data event_data; event_data.u.p = frame; return event_data; } /** Get a frame from the event data. * * \public \memberof mlt_event_data * \param event_data an event data object * \return a frame */ mlt_frame mlt_event_data_to_frame(mlt_event_data event_data) { return (mlt_frame) event_data.u.p; } /** Initialize event data with opaque data. * * \public \memberof mlt_event_data * \param value the pointer with which to initialize the event data * \return an event data object */ mlt_event_data mlt_event_data_from_object(void *value) { mlt_event_data event_data; event_data.u.p = value; return event_data; } /** Get a pointer from the event data. * * \public \memberof mlt_event_data * \param event_data an event data object * \return a pointer */ void *mlt_event_data_to_object(mlt_event_data event_data) { return event_data.u.p; } mlt-7.22.0/src/framework/mlt_events.h000664 000000 000000 00000006446 14531534050 017505 0ustar00rootroot000000 000000 /** * \file mlt_events.h * \brief event handling * \see mlt_events_struct * * Copyright (C) 2004-2021 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef MLT_EVENTS_H #define MLT_EVENTS_H #include "mlt_types.h" /** A container for data that may be supplied with an event */ typedef struct { union { int i; void *p; } u; } mlt_event_data; /** An event data structure to convey thread parameters */ typedef struct { void **thread; /**< a pointer to a thread object or handle as determined by you */ int *priority; /**< a priority level for the thread */ mlt_thread_function_t function; /**< a pointer to the function that thread will run */ void *data; /**< an opaque data pointer to pass along */ } mlt_event_data_thread; /** event handler when receiving an event message * \param the properties object on which the event was registered * \param an opaque pointer to the listener's data * \param an event data object */ typedef void (*mlt_listener)(mlt_properties, void *, mlt_event_data); extern void mlt_events_init(mlt_properties self); extern int mlt_events_register(mlt_properties self, const char *id); extern int mlt_events_fire(mlt_properties self, const char *id, mlt_event_data); extern mlt_event mlt_events_listen(mlt_properties self, void *listener_data, const char *id, mlt_listener listener); extern void mlt_events_block(mlt_properties self, void *listener_data); extern void mlt_events_unblock(mlt_properties self, void *listener_data); extern void mlt_events_disconnect(mlt_properties self, void *listener_data); extern mlt_event mlt_events_setup_wait_for(mlt_properties self, const char *id); extern void mlt_events_wait_for(mlt_properties self, mlt_event event); extern void mlt_events_close_wait_for(mlt_properties self, mlt_event event); extern void mlt_event_inc_ref(mlt_event self); extern void mlt_event_block(mlt_event self); extern void mlt_event_unblock(mlt_event self); extern void mlt_event_close(mlt_event self); extern mlt_event_data mlt_event_data_none(); extern mlt_event_data mlt_event_data_from_int(int value); extern int mlt_event_data_to_int(mlt_event_data); extern mlt_event_data mlt_event_data_from_string(const char *value); extern const char *mlt_event_data_to_string(mlt_event_data); extern mlt_event_data mlt_event_data_from_frame(mlt_frame); extern mlt_frame mlt_event_data_to_frame(mlt_event_data); extern mlt_event_data mlt_event_data_from_object(void *); extern void *mlt_event_data_to_object(mlt_event_data); #endif mlt-7.22.0/src/framework/mlt_factory.c000664 000000 000000 00000044006 14531534050 017635 0ustar00rootroot000000 000000 /** * \file mlt_factory.c * \brief the factory method interfaces * * Copyright (C) 2003-2022 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "mlt.h" #include "mlt_repository.h" #include #include #include #include /** the default subdirectory of the datadir for holding presets */ #define PRESETS_DIR "/presets" #ifdef _WIN32 #include #ifdef PREFIX_LIB #undef PREFIX_LIB #endif #ifdef PREFIX_DATA #undef PREFIX_DATA #endif /** the default subdirectory of the libdir for holding modules (plugins) */ #define PREFIX_LIB "\\lib\\mlt" /** the default subdirectory of the install prefix for holding module (plugin) data */ #define PREFIX_DATA "\\share\\mlt" #elif defined(RELOCATABLE) #ifdef PREFIX_LIB #undef PREFIX_LIB #endif #ifdef PREFIX_DATA #undef PREFIX_DATA #endif #ifdef __APPLE__ #include /** the default subdirectory of the libdir for holding modules (plugins) */ #define PREFIX_LIB "/PlugIns/mlt" /** the default subdirectory of the install prefix for holding module (plugin) data */ #define PREFIX_DATA "/Resources/mlt" #else #include #define PREFIX_LIB "/lib/mlt-7" /** the default subdirectory of the install prefix for holding module (plugin) data */ #define PREFIX_DATA "/share/mlt-7" #endif #endif /** holds the full path to the modules directory - initialized and retained for the entire session */ static char *mlt_directory = NULL; /** a global properties list for holding environment config data and things needing session-oriented cleanup */ static mlt_properties global_properties = NULL; /** the global repository singleton */ static mlt_repository repository = NULL; /** the events object for the factory events */ static mlt_properties event_object = NULL; /** for tracking the unique_id set on each constructed service */ static int unique_id = 0; #if defined(_WIN32) || defined(RELOCATABLE) // Replacement for buggy dirname() on some systems. // https://github.com/mltframework/mlt/issues/285 static char *mlt_dirname(char *path) { if (path && strlen(path)) { char *dirsep = strrchr(path, '/'); // Handle back slash on Windows. if (!dirsep) dirsep = strrchr(path, '\\'); // Handle trailing slash. if (dirsep == &path[strlen(path) - 1]) { dirsep[0] = '\0'; dirsep = strrchr(path, '/'); if (!dirsep) dirsep = strrchr(path, '\\'); } // Truncate string at last directory separator. if (dirsep) dirsep[0] = '\0'; } return path; } #endif /** Construct the repository and factories. * * \param directory an optional full path to a directory containing the modules that overrides the default and * the MLT_REPOSITORY environment variable * \return the repository */ mlt_repository mlt_factory_init(const char *directory) { if (!global_properties) global_properties = mlt_properties_new(); // Allow property refresh on a subsequent initialisation if (global_properties) { mlt_properties_set_or_default(global_properties, "MLT_NORMALISATION", getenv("MLT_NORMALISATION"), "PAL"); mlt_properties_set_or_default(global_properties, "MLT_PRODUCER", getenv("MLT_PRODUCER"), "loader"); mlt_properties_set_or_default(global_properties, "MLT_CONSUMER", getenv("MLT_CONSUMER"), "sdl2"); mlt_properties_set(global_properties, "MLT_TEST_CARD", getenv("MLT_TEST_CARD")); mlt_properties_set_or_default(global_properties, "MLT_PROFILE", getenv("MLT_PROFILE"), "dv_pal"); mlt_properties_set_or_default(global_properties, "MLT_DATA", getenv("MLT_DATA"), PREFIX_DATA); #if defined(_WIN32) char path[1024]; DWORD size = sizeof(path); GetModuleFileName(NULL, path, size); #ifndef NODEPLOY char *appdir = mlt_dirname(strdup(path)); #else char *appdir = mlt_dirname(mlt_dirname(strdup(path))); #endif mlt_properties_set(global_properties, "MLT_APPDIR", appdir); free(appdir); #elif defined(RELOCATABLE) char path[1024]; uint32_t size = sizeof(path); #ifdef __APPLE__ _NSGetExecutablePath(path, &size); #else readlink("/proc/self/exe", path, size); #endif char *appdir = mlt_dirname(mlt_dirname(strdup(path))); mlt_properties_set(global_properties, "MLT_APPDIR", appdir); free(appdir); #endif } // Only initialise once if (mlt_directory == NULL) { #if !defined(_WIN32) && !defined(RELOCATABLE) // Allow user overrides if (directory == NULL || !strcmp(directory, "")) directory = getenv("MLT_REPOSITORY"); // If no directory is specified, default to install directory if (directory == NULL) directory = PREFIX_LIB; // Store the prefix for later retrieval mlt_directory = strdup(directory); #else if (directory) { mlt_directory = strdup(directory); } else { char *exedir = mlt_environment("MLT_APPDIR"); size_t size = strlen(exedir); if (global_properties && !getenv("MLT_DATA")) { mlt_directory = calloc(1, size + strlen(PREFIX_DATA) + 1); strcpy(mlt_directory, exedir); strcat(mlt_directory, PREFIX_DATA); mlt_properties_set(global_properties, "MLT_DATA", mlt_directory); free(mlt_directory); } mlt_directory = calloc(1, size + strlen(PREFIX_LIB) + 1); strcpy(mlt_directory, exedir); strcat(mlt_directory, PREFIX_LIB); } #endif // Initialise the pool mlt_pool_init(); // Create and set up the events object event_object = mlt_properties_new(); mlt_events_init(event_object); mlt_events_register(event_object, "producer-create-request"); mlt_events_register(event_object, "producer-create-done"); mlt_events_register(event_object, "filter-create-request"); mlt_events_register(event_object, "filter-create-done"); mlt_events_register(event_object, "transition-create-request"); mlt_events_register(event_object, "transition-create-done"); mlt_events_register(event_object, "consumer-create-request"); mlt_events_register(event_object, "consumer-create-done"); // Create the repository of services repository = mlt_repository_init(mlt_directory); // Force a clean up when app closes atexit(mlt_factory_close); } if (global_properties) { char *path = getenv("MLT_PRESETS_PATH"); if (path) { mlt_properties_set(global_properties, "MLT_PRESETS_PATH", path); } else { path = malloc(strlen(mlt_environment("MLT_DATA")) + strlen(PRESETS_DIR) + 1); strcpy(path, mlt_environment("MLT_DATA")); strcat(path, PRESETS_DIR); mlt_properties_set(global_properties, "MLT_PRESETS_PATH", path); free(path); } } return repository; } /** Fetch the repository. * * \return the global repository object */ mlt_repository mlt_factory_repository() { return repository; } /** Fetch the events object. * * \return the global factory event object */ mlt_properties mlt_factory_event_object() { return event_object; } /** Fetch the module directory used in this instance. * * \return the full path to the module directory that this session is using */ const char *mlt_factory_directory() { return mlt_directory; } /** Get a value from the environment. * * \param name the name of a MLT (runtime configuration) environment variable * \return the value of the variable */ char *mlt_environment(const char *name) { if (global_properties) return mlt_properties_get(global_properties, name); else return NULL; } /** Set a value in the environment. * * \param name the name of a MLT environment variable * \param value the value of the variable * \return true on error */ int mlt_environment_set(const char *name, const char *value) { if (global_properties) return mlt_properties_set(global_properties, name, value); else return -1; } /** Set some properties common to all services. * * This sets _unique_id, \p mlt_type, \p mlt_service (unless _mlt_service_hidden), and _profile. * * \param properties a service's properties list * \param profile the \p mlt_profile supplied to the factory function * \param type the MLT service class * \param service the name of the service */ static void set_common_properties(mlt_properties properties, mlt_profile profile, const char *type, const char *service) { mlt_properties_set_int(properties, "_unique_id", ++unique_id); mlt_properties_set(properties, "mlt_type", type); if (mlt_properties_get_int(properties, "_mlt_service_hidden") == 0) mlt_properties_set(properties, "mlt_service", service); if (profile != NULL) mlt_properties_set_data(properties, "_profile", profile, 0, NULL, NULL); } /** Fetch a producer from the repository. * * If you give NULL to \p service, then it will use core module's special * "loader"producer to load \p resource. One can override this default producer * by setting the environment variable MLT_PRODUCER. * * \param profile the \p mlt_profile to use * \param service the name of the producer (optional, defaults to MLT_PRODUCER) * \param resource an optional argument to the producer constructor, typically a string * \return a new producer */ mlt_producer mlt_factory_producer(mlt_profile profile, const char *service, const void *resource) { mlt_producer obj = NULL; // Pick up the default normalizing producer if necessary if (service == NULL) service = mlt_environment("MLT_PRODUCER"); // Offer the application the chance to 'create' mlt_factory_event_data data = {.name = service, .input = resource, .service = &obj}; mlt_events_fire(event_object, "producer-create-request", mlt_event_data_from_object(&data)); // Try to instantiate via the specified service if (obj == NULL) { obj = mlt_repository_create(repository, profile, mlt_service_producer_type, service, resource); mlt_events_fire(event_object, "producer-create-done", mlt_event_data_from_object(&data)); if (obj != NULL) { mlt_properties properties = MLT_PRODUCER_PROPERTIES(obj); if (mlt_service_identify(MLT_PRODUCER_SERVICE(obj)) == mlt_service_chain_type) { // XML producer may return a chain set_common_properties(properties, profile, "chain", service); } else { set_common_properties(properties, profile, "producer", service); } } } return obj; } /** Fetch a filter from the repository. * * \param profile the \p mlt_profile to use * \param service the name of the filter * \param input an optional argument to the filter constructor, typically a string * \return a new filter */ mlt_filter mlt_factory_filter(mlt_profile profile, const char *service, const void *input) { mlt_filter obj = NULL; // Offer the application the chance to 'create' mlt_factory_event_data data = {.name = service, .input = input, .service = &obj}; mlt_events_fire(event_object, "filter-create-request", mlt_event_data_from_object(&data)); if (obj == NULL) { obj = mlt_repository_create(repository, profile, mlt_service_filter_type, service, input); mlt_events_fire(event_object, "filter-create-done", mlt_event_data_from_object(&data)); } if (obj != NULL) { mlt_properties properties = MLT_FILTER_PROPERTIES(obj); set_common_properties(properties, profile, "filter", service); } return obj; } /** Fetch a link from the repository. * * \param service the name of the link * \param input an optional argument to the link constructor, typically a string * \return a new link */ mlt_link mlt_factory_link(const char *service, const void *input) { mlt_link obj = NULL; // Offer the application the chance to 'create' mlt_factory_event_data data = {.name = service, .input = input, .service = &obj}; mlt_events_fire(event_object, "link-create-request", mlt_event_data_from_object(&data)); if (obj == NULL) { obj = mlt_repository_create(repository, NULL, mlt_service_link_type, service, input); mlt_events_fire(event_object, "link-create-done", mlt_event_data_from_object(&data)); } if (obj != NULL) { mlt_properties properties = MLT_LINK_PROPERTIES(obj); set_common_properties(properties, NULL, "link", service); } return obj; } /** Fetch a transition from the repository. * * \param profile the \p mlt_profile to use * \param service the name of the transition * \param input an optional argument to the transition constructor, typically a string * \return a new transition */ mlt_transition mlt_factory_transition(mlt_profile profile, const char *service, const void *input) { mlt_transition obj = NULL; // Offer the application the chance to 'create' mlt_factory_event_data data = {.name = service, .input = input, .service = &obj}; mlt_events_fire(event_object, "transition-create-request", mlt_event_data_from_object(&data)); if (obj == NULL) { obj = mlt_repository_create(repository, profile, mlt_service_transition_type, service, input); mlt_events_fire(event_object, "transition-create-done", mlt_event_data_from_object(&data)); } if (obj != NULL) { mlt_properties properties = MLT_TRANSITION_PROPERTIES(obj); set_common_properties(properties, profile, "transition", service); } return obj; } /** Fetch a consumer from the repository. * * \param profile the \p mlt_profile to use * \param service the name of the consumer (optional, defaults to MLT_CONSUMER) * \param input an optional argument to the consumer constructor, typically a string * \return a new consumer */ mlt_consumer mlt_factory_consumer(mlt_profile profile, const char *service, const void *input) { mlt_consumer obj = NULL; if (service == NULL) service = mlt_environment("MLT_CONSUMER"); // Offer the application the chance to 'create' mlt_factory_event_data data = {.name = service, .input = input, .service = &obj}; mlt_events_fire(event_object, "consumer-create-request", mlt_event_data_from_object(&data)); if (obj == NULL) { obj = mlt_repository_create(repository, profile, mlt_service_consumer_type, service, input); } if (obj == NULL) { if (!strcmp(service, "sdl2")) { service = "sdl"; obj = mlt_repository_create(repository, profile, mlt_service_consumer_type, service, input); } else if (!strcmp(service, "sdl_audio")) { service = "sdl2_audio"; obj = mlt_repository_create(repository, profile, mlt_service_consumer_type, service, input); } } if (obj != NULL) { mlt_properties properties = MLT_CONSUMER_PROPERTIES(obj); mlt_events_fire(event_object, "consumer-create-done", mlt_event_data_from_object(&data)); set_common_properties(properties, profile, "consumer", service); } return obj; } /** Register an object for clean up. * * \param ptr an opaque pointer to anything allocated on the heap * \param destructor the function pointer of the deallocation subroutine (e.g., free or \p mlt_pool_release) */ void mlt_factory_register_for_clean_up(void *ptr, mlt_destructor destructor) { char unique[256]; sprintf(unique, "%08d", mlt_properties_count(global_properties)); mlt_properties_set_data(global_properties, unique, ptr, 0, destructor, NULL); } /** Close the factory. * * Cleanup all resources for the session. */ void mlt_factory_close() { if (mlt_directory != NULL) { mlt_properties_close(event_object); event_object = NULL; #if !defined(_WIN32) // XXX something in here is causing Shotcut/Win32 to not exit completely // under certain conditions: e.g. play a playlist. mlt_properties_close(global_properties); global_properties = NULL; #endif if (repository) { mlt_repository_close(repository); repository = NULL; } free(mlt_directory); mlt_directory = NULL; mlt_pool_close(); } } mlt_properties mlt_global_properties() { return global_properties; } mlt-7.22.0/src/framework/mlt_factory.h000664 000000 000000 00000011575 14531534050 017647 0ustar00rootroot000000 000000 /** * \file mlt_factory.h * \brief the factory method interfaces * * Copyright (C) 2003-2021 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef MLT_FACTORY_H #define MLT_FACTORY_H #include "mlt_profile.h" #include "mlt_repository.h" #include "mlt_types.h" /** * \envvar \em MLT_PRODUCER the name of a default producer often used by other services, defaults to "loader" * \envvar \em MLT_CONSUMER the name of a default consumer, defaults to "sdl2" followed by "sdl" * \envvar \em MLT_TEST_CARD the name of a producer or file to be played when nothing is available (all tracks blank) * \envvar \em MLT_DATA overrides the default full path to the MLT and module supplemental data files, defaults to \p PREFIX_DATA * \envvar \em MLT_PROFILE selects the default mlt_profile_s, defaults to "dv_pal" * \envvar \em MLT_REPOSITORY overrides the default location of the plugin modules, defaults to \p PREFIX_LIB. * MLT_REPOSITORY is ignored on Windows and OS X relocatable builds. * \envvar \em MLT_PRESETS_PATH overrides the default full path to the properties preset files, defaults to \p MLT_DATA/presets * \envvar \em MLT_REPOSITORY_DENY colon separated list of modules to skip. Example: libmltplus:libmltavformat:libmltfrei0r * In case both qt5 and qt6 modules are found and none of both is blocked by MLT_REPOSITORY_DENY, qt6 will be blocked * \event \em producer-create-request fired when mlt_factory_producer is called; * the event data is a pointer to mlt_factory_event_data * \event \em producer-create-done fired when a producer registers itself; * the event data is a pointer to mlt_factory_event_data * \event \em filter-create-request fired when mlt_factory_filter is called; * the event data is a pointer to mlt_factory_event_data * \event \em filter-create-done fired when a filter registers itself; * the event data is a pointer to mlt_factory_event_data * \event \em transition-create-request fired when mlt_factory_transition is called; * the event data is a pointer to mlt_factory_event_data * \event \em transition-create-done fired when a transition registers itself; * the event data is a pointer to mlt_factory_event_data * \event \em consumer-create-request fired when mlt_factory_consumer is called; * the event data is a pointer to mlt_factory_event_data * \event \em consumer-create-done fired when a consumer registers itself; * the event data is a pointer to mlt_factory_event_data * \event \em link-create-request fired when mlt_factory_link is called; * the event data is a pointer to mlt_factory_event_data * \event \em link-create-done fired when a link registers itself; * the event data is a pointer to mlt_factory_event_data */ extern mlt_repository mlt_factory_init(const char *directory); extern mlt_repository mlt_factory_repository(); extern const char *mlt_factory_directory(); extern char *mlt_environment(const char *name); extern int mlt_environment_set(const char *name, const char *value); extern mlt_properties mlt_factory_event_object(); extern mlt_producer mlt_factory_producer(mlt_profile profile, const char *service, const void *resource); extern mlt_filter mlt_factory_filter(mlt_profile profile, const char *service, const void *input); extern mlt_link mlt_factory_link(const char *service, const void *input); extern mlt_transition mlt_factory_transition(mlt_profile profile, const char *service, const void *input); extern mlt_consumer mlt_factory_consumer(mlt_profile profile, const char *service, const void *input); extern void mlt_factory_register_for_clean_up(void *ptr, mlt_destructor destructor); extern void mlt_factory_close(); extern mlt_properties mlt_global_properties(); /** The event data for all factory-related events */ typedef struct { const char *name; /**< the name of the service requested */ const void *input; /**< an argument supplied to initialize the service, typically a string */ void *service; /**< the service being created */ } mlt_factory_event_data; #endif mlt-7.22.0/src/framework/mlt_field.c000664 000000 000000 00000015503 14531534050 017251 0ustar00rootroot000000 000000 /** * \file mlt_field.c * \brief a field for planting multiple transitions and filters * \see mlt_field_s * * Copyright (C) 2003-2018 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "mlt_field.h" #include "mlt_filter.h" #include "mlt_multitrack.h" #include "mlt_service.h" #include "mlt_tractor.h" #include "mlt_transition.h" #include #include /** \brief Field class * * The field is a convenience class that works with the tractor and multitrack classes to manage track filters and transitions. */ struct mlt_field_s { /// This is the producer we're connected to mlt_service producer; /// Multitrack mlt_multitrack multitrack; /// Tractor mlt_tractor tractor; }; /** Construct a field, mulitrack, and tractor. * * \public \memberof mlt_field_s * \return a new field */ mlt_field mlt_field_init() { // Initialise the field mlt_field self = calloc(1, sizeof(struct mlt_field_s)); // Initialise it if (self != NULL) { // Construct a multitrack self->multitrack = mlt_multitrack_init(); // Construct a tractor self->tractor = mlt_tractor_init(); // The first plant will be connected to the mulitrack self->producer = MLT_MULTITRACK_SERVICE(self->multitrack); // Connect the tractor to the multitrack mlt_tractor_connect(self->tractor, self->producer); } // Return self return self; } /** Construct a field and initialize with supplied multitrack and tractor. * * \public \memberof mlt_field_s * \param multitrack a multitrack * \param tractor a tractor * \return a new field */ mlt_field mlt_field_new(mlt_multitrack multitrack, mlt_tractor tractor) { // Initialise the field mlt_field self = calloc(1, sizeof(struct mlt_field_s)); // Initialise it if (self != NULL) { // Construct a multitrack self->multitrack = multitrack; // Construct a tractor self->tractor = tractor; // The first plant will be connected to the mulitrack self->producer = MLT_MULTITRACK_SERVICE(self->multitrack); // Connect the tractor to the multitrack mlt_tractor_connect(self->tractor, self->producer); } // Return self return self; } /** Get the service associated to this field. * * \public \memberof mlt_field_s * \param self a field * \return the tractor as a service */ mlt_service mlt_field_service(mlt_field self) { return self != NULL ? MLT_TRACTOR_SERVICE(self->tractor) : NULL; } /** Get the multitrack. * * \public \memberof mlt_field_s * \param self a field * \return the multitrack */ mlt_multitrack mlt_field_multitrack(mlt_field self) { return self != NULL ? self->multitrack : NULL; } /** Get the tractor. * * \public \memberof mlt_field_s * \param self a field * \return the tractor */ mlt_tractor mlt_field_tractor(mlt_field self) { return self != NULL ? self->tractor : NULL; } /** Get the properties associated to this field. * * \public \memberof mlt_field_s * \param self a field * \return a properties list */ mlt_properties mlt_field_properties(mlt_field self) { return MLT_SERVICE_PROPERTIES(mlt_field_service(self)); } /** Plant a filter. * * \public \memberof mlt_field_s * \param self a field * \param that a filter * \param track the track index * \return true if there was an error */ int mlt_field_plant_filter(mlt_field self, mlt_filter that, int track) { // Connect the filter to the last producer int result = mlt_filter_connect(that, self->producer, track); // If successful, then we'll use this for connecting in the future if (result == 0) { // This is now the new producer self->producer = MLT_FILTER_SERVICE(that); // Reconnect tractor to new producer mlt_tractor_connect(self->tractor, self->producer); // Fire an event mlt_events_fire(mlt_field_properties(self), "service-changed", mlt_event_data_none()); } return result; } /** Plant a transition. * * \public \memberof mlt_field_s * \param self a field * \param that a transition * \param a_track input A's track index * \param b_track input B's track index * \return true if there was an error */ int mlt_field_plant_transition(mlt_field self, mlt_transition that, int a_track, int b_track) { // Connect the transition to the last producer int result = mlt_transition_connect(that, self->producer, a_track, b_track); // If successful, then we'll use self for connecting in the future if (result == 0) { // This is now the new producer self->producer = MLT_TRANSITION_SERVICE(that); // Reconnect tractor to new producer mlt_tractor_connect(self->tractor, self->producer); // Fire an event mlt_events_fire(mlt_field_properties(self), "service-changed", mlt_event_data_none()); } return result; } /** Close the field. * * \public \memberof mlt_field_s * \param self a field */ void mlt_field_close(mlt_field self) { if (self != NULL && mlt_properties_dec_ref(mlt_field_properties(self)) <= 0) { //mlt_tractor_close( self->tractor ); //mlt_multitrack_close( self->multitrack ); free(self); } } /** Remove a filter or transition from the field. * * \public \memberof mlt_field_s * \param self a field * \param service the filter or transition to remove */ void mlt_field_disconnect_service(mlt_field self, mlt_service service) { mlt_service p = mlt_service_producer(service); mlt_service c = mlt_service_consumer(service); int i; switch (mlt_service_identify(c)) { case mlt_service_filter_type: i = mlt_filter_get_track(MLT_FILTER(c)); mlt_service_connect_producer(c, p, i); break; case mlt_service_transition_type: i = mlt_transition_get_a_track(MLT_TRANSITION(c)); mlt_service_connect_producer(c, p, i); MLT_TRANSITION(c)->producer = p; break; case mlt_service_tractor_type: self->producer = p; mlt_tractor_connect(MLT_TRACTOR(c), p); default: break; } mlt_events_fire(mlt_field_properties(self), "service-changed", mlt_event_data_none()); } mlt-7.22.0/src/framework/mlt_field.h000664 000000 000000 00000003131 14531534050 017250 0ustar00rootroot000000 000000 /** * \file mlt_field.h * \brief a field for planting multiple transitions and services * \see mlt_field_s * * Copyright (C) 2003-2014 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef MLT_FIELD_H #define MLT_FIELD_H #include "mlt_types.h" extern mlt_field mlt_field_init(); extern mlt_field mlt_field_new(mlt_multitrack multitrack, mlt_tractor tractor); extern mlt_service mlt_field_service(mlt_field self); extern mlt_tractor mlt_field_tractor(mlt_field self); extern mlt_multitrack mlt_field_multitrack(mlt_field self); extern mlt_properties mlt_field_properties(mlt_field self); extern int mlt_field_plant_filter(mlt_field self, mlt_filter that, int track); extern int mlt_field_plant_transition(mlt_field self, mlt_transition that, int a_track, int b_track); extern void mlt_field_close(mlt_field self); extern void mlt_field_disconnect_service(mlt_field self, mlt_service service); #endif mlt-7.22.0/src/framework/mlt_filter.c000664 000000 000000 00000025637 14531534050 017464 0ustar00rootroot000000 000000 /** * \file mlt_filter.c * \brief abstraction for all filter services * \see mlt_filter_s * * Copyright (C) 2003-2020 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "mlt_filter.h" #include "mlt_frame.h" #include "mlt_producer.h" #include #include #include static int filter_get_frame(mlt_service self, mlt_frame_ptr frame, int index); /** Initialize a new filter. * * \public \memberof mlt_filter_s * \param self a filter * \param child the object of a subclass * \return true if there was an error */ int mlt_filter_init(mlt_filter self, void *child) { mlt_service service = &self->parent; memset(self, 0, sizeof(struct mlt_filter_s)); self->child = child; if (mlt_service_init(service, self) == 0) { mlt_properties properties = MLT_SERVICE_PROPERTIES(service); // Override the get_frame method service->get_frame = filter_get_frame; // Define the destructor service->close = (mlt_destructor) mlt_filter_close; service->close_object = self; mlt_properties_set(properties, "mlt_type", "filter"); mlt_properties_set_position(properties, "in", 0); mlt_properties_set_position(properties, "out", 0); return 0; } return 1; } /** Create a new filter and initialize it. * * \public \memberof mlt_filter_s * \return a new filter */ mlt_filter mlt_filter_new() { mlt_filter self = calloc(1, sizeof(struct mlt_filter_s)); if (self != NULL && mlt_filter_init(self, NULL) == 0) { return self; } else { free(self); return NULL; } } /** Get the service class interface. * * \public \memberof mlt_filter_s * \param self a filter * \return the service parent class * \see MLT_FILTER_SERVICE */ mlt_service mlt_filter_service(mlt_filter self) { return self != NULL ? &self->parent : NULL; } /** Get the filter properties. * * \public \memberof mlt_filter_s * \param self a filter * \return the properties list for the filter * \see MLT_FILTER_PROPERTIES */ mlt_properties mlt_filter_properties(mlt_filter self) { return MLT_SERVICE_PROPERTIES(MLT_FILTER_SERVICE(self)); } /** Connect this filter to a producers track. Note that a filter only operates * on a single track, and by default it operates on the entirety of that track. * * \public \memberof mlt_filter_s * \param self a filter * \param producer the producer to which to connect this filter * \param index which of potentially multiple producers to this service (0 based) */ int mlt_filter_connect(mlt_filter self, mlt_service producer, int index) { int ret = mlt_service_connect_producer(&self->parent, producer, index); // If the connection was successful, grab the producer, track and reset in/out if (ret == 0) { mlt_properties properties = MLT_SERVICE_PROPERTIES(&self->parent); mlt_properties_set_position(properties, "in", 0); mlt_properties_set_position(properties, "out", 0); mlt_properties_set_int(properties, "track", index); } return ret; } /** Set the starting and ending time. * * \public \memberof mlt_filter_s * \param self a filter * \param in the time relative to the producer at which start applying the filter * \param out the time relative to the producer at which to stop applying the filter */ void mlt_filter_set_in_and_out(mlt_filter self, mlt_position in, mlt_position out) { mlt_properties properties = MLT_SERVICE_PROPERTIES(&self->parent); mlt_properties_set_position(properties, "in", in); mlt_properties_set_position(properties, "out", out); } /** Return the track that this filter is operating on. * * \public \memberof mlt_filter_s * \param self a filter * \return true on error */ int mlt_filter_get_track(mlt_filter self) { mlt_properties properties = MLT_SERVICE_PROPERTIES(&self->parent); return mlt_properties_get_int(properties, "track"); } /** Get the in point. * * \public \memberof mlt_filter_s * \param self a filter * \return the start time for the filter relative to the producer */ mlt_position mlt_filter_get_in(mlt_filter self) { mlt_properties properties = MLT_SERVICE_PROPERTIES(&self->parent); return mlt_properties_get_position(properties, "in"); } /** Get the out point. * * \public \memberof mlt_filter_s * \param self a filter * \return the ending time for the filter relative to the producer */ mlt_position mlt_filter_get_out(mlt_filter self) { mlt_properties properties = MLT_SERVICE_PROPERTIES(&self->parent); return mlt_properties_get_position(properties, "out"); } /** Get the duration. * * \public \memberof mlt_filter_s * \param self a filter * \return the duration or zero if unlimited */ mlt_position mlt_filter_get_length(mlt_filter self) { mlt_properties properties = MLT_SERVICE_PROPERTIES(&self->parent); mlt_position in = mlt_properties_get_position(properties, "in"); mlt_position out = mlt_properties_get_position(properties, "out"); return (out > 0) ? (out - in + 1) : 0; } /** Get the duration. * * This version works with filters with no explicit in and out by getting the * length of the frame's producer. * * \public \memberof mlt_filter_s * \param self a filter * \param frame a frame from which to get its producer * \return the duration or zero if unlimited */ mlt_position mlt_filter_get_length2(mlt_filter self, mlt_frame frame) { mlt_properties properties = MLT_SERVICE_PROPERTIES(&self->parent); mlt_position in = mlt_properties_get_position(properties, "in"); mlt_position out = mlt_properties_get_position(properties, "out"); if (out == 0 && frame) { // If always active, use the frame's producer mlt_producer producer = mlt_frame_get_original_producer(frame); if (producer) { producer = mlt_producer_cut_parent(producer); in = mlt_producer_get_in(producer); out = mlt_producer_get_out(producer); } } return (out > 0) ? (out - in + 1) : 0; } /** Get the position within the filter. * * The position is relative to the in point. * This will only be valid once mlt_filter_process is called. * * \public \memberof mlt_filter_s * \param self a filter * \param frame a frame * \return the position */ mlt_position mlt_filter_get_position(mlt_filter self, mlt_frame frame) { mlt_properties properties = MLT_FILTER_PROPERTIES(self); mlt_position in = mlt_properties_get_position(properties, "in"); const char *unique_id = mlt_properties_get(properties, "_unique_id"); char name[64]; // Make the properties key from unique id snprintf(name, sizeof(name), "pos.%s", unique_id); return mlt_properties_get_position(MLT_FRAME_PROPERTIES(frame), name) - in; } /** Get the percent complete. * * This will only be valid once mlt_filter_process is called. * * \public \memberof mlt_filter_s * \param self a filter * \param frame a frame * \return the progress in the range 0.0 to 1.0 */ double mlt_filter_get_progress(mlt_filter self, mlt_frame frame) { double position = mlt_filter_get_position(self, frame); double length = mlt_filter_get_length2(self, frame); if (length > 1) return position / (length - 1); else return 1.0; } /** Process the frame. * * When fetching the frame position in a subclass process method, the frame's * position is relative to the filter's producer - not the filter's in point * or timeline. * * \public \memberof mlt_filter_s * \param self a filter * \param frame a frame * \return a frame */ mlt_frame mlt_filter_process(mlt_filter self, mlt_frame frame) { mlt_properties properties = MLT_FILTER_PROPERTIES(self); int disable = mlt_properties_get_int(properties, "disable"); const char *unique_id = mlt_properties_get(properties, "_unique_id"); mlt_position position = mlt_frame_get_position(frame); char name[64]; // Make the properties key from unique id snprintf(name, sizeof(name), "pos.%s", unique_id); // Save the position on the frame mlt_properties_set_position(MLT_FRAME_PROPERTIES(frame), name, position); if (disable || !self || !self->process) { return frame; } else { // Add a reference to this filter on the frame mlt_properties_inc_ref(MLT_FILTER_PROPERTIES(self)); snprintf(name, sizeof(name), "filter.%s", unique_id); name[sizeof(name) - 1] = '\0'; mlt_properties_set_data(MLT_FRAME_PROPERTIES(frame), name, self, 0, (mlt_destructor) mlt_filter_close, NULL); return self->process(self, frame); } } /** Get a frame from this filter. * * \private \memberof mlt_filter_s * \param service a service * \param[out] frame a frame by reference * \param index as determined by the producer * \return true on error */ static int filter_get_frame(mlt_service service, mlt_frame_ptr frame, int index) { mlt_filter self = service->child; // Get coords in/out/track int track = mlt_filter_get_track(self); int in = mlt_filter_get_in(self); int out = mlt_filter_get_out(self); // Get the producer this is connected to mlt_service producer = mlt_service_producer(&self->parent); // If the frame request is for this filters track, we need to process it if (index == track || track == -1) { int ret = mlt_service_get_frame(producer, frame, index); if (ret == 0) { mlt_position position = mlt_frame_get_position(*frame); if (position >= in && (out == 0 || position <= out)) *frame = mlt_filter_process(self, *frame); return 0; } else { *frame = mlt_frame_init(service); return 0; } } else { return mlt_service_get_frame(producer, frame, index); } } /** Close and destroy the filter. * * \public \memberof mlt_filter_s * \param self a filter */ void mlt_filter_close(mlt_filter self) { if (self != NULL && mlt_properties_dec_ref(MLT_FILTER_PROPERTIES(self)) <= 0) { if (self->close != NULL) { self->close(self); } else { self->parent.close = NULL; mlt_service_close(&self->parent); } free(self); } } mlt-7.22.0/src/framework/mlt_filter.h000664 000000 000000 00000005423 14531534050 017460 0ustar00rootroot000000 000000 /** * \file mlt_filter.h * \brief abstraction for all filter services * \see mlt_filter_s * * Copyright (C) 2003-2014 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef MLT_FILTER_H #define MLT_FILTER_H #include "mlt_service.h" /** \brief Filter abstract service class * * A filter is a service that may modify the output of a single producer. * * \extends mlt_service_s * \properties \em track the index of the track of a multitrack on which the filter is applied * \properties \em service a reference to the service to which this filter is attached. * \properties \em disable Set this to disable the filter while keeping it in the object model. * Currently this is not cleared when the filter is detached. */ struct mlt_filter_s { /** We're implementing service here */ struct mlt_service_s parent; /** public virtual */ void (*close)(mlt_filter); /** protected filter method */ mlt_frame (*process)(mlt_filter, mlt_frame); /** Protected */ void *child; }; #define MLT_FILTER_SERVICE(filter) (&(filter)->parent) #define MLT_FILTER_PROPERTIES(filter) MLT_SERVICE_PROPERTIES(MLT_FILTER_SERVICE(filter)) extern int mlt_filter_init(mlt_filter self, void *child); extern mlt_filter mlt_filter_new(); extern mlt_service mlt_filter_service(mlt_filter self); extern mlt_properties mlt_filter_properties(mlt_filter self); extern mlt_frame mlt_filter_process(mlt_filter self, mlt_frame that); extern int mlt_filter_connect(mlt_filter self, mlt_service producer, int index); extern void mlt_filter_set_in_and_out(mlt_filter self, mlt_position in, mlt_position out); extern int mlt_filter_get_track(mlt_filter self); extern mlt_position mlt_filter_get_in(mlt_filter self); extern mlt_position mlt_filter_get_out(mlt_filter self); extern mlt_position mlt_filter_get_length(mlt_filter self); extern mlt_position mlt_filter_get_length2(mlt_filter self, mlt_frame frame); extern mlt_position mlt_filter_get_position(mlt_filter self, mlt_frame frame); extern double mlt_filter_get_progress(mlt_filter self, mlt_frame frame); extern void mlt_filter_close(mlt_filter); #endif mlt-7.22.0/src/framework/mlt_frame.c000664 000000 000000 00000123041 14531534050 017255 0ustar00rootroot000000 000000 /** * \file mlt_frame.c * \brief interface for all frame classes * \see mlt_frame_s * * Copyright (C) 2003-2023 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "mlt_frame.h" #include "mlt_factory.h" #include "mlt_image.h" #include "mlt_log.h" #include "mlt_producer.h" #include "mlt_profile.h" #include #include #include /** Construct a frame object. * * \public \memberof mlt_frame_s * \param service the pointer to any service that can provide access to the profile * \return a frame object on success or NULL if there was an allocation error */ mlt_frame mlt_frame_init(mlt_service service) { // Allocate a frame mlt_frame self = calloc(1, sizeof(struct mlt_frame_s)); if (self != NULL) { mlt_profile profile = mlt_service_profile(service); // Initialise the properties mlt_properties properties = &self->parent; mlt_properties_init(properties, self); // Set default properties on the frame mlt_properties_set_position(properties, "_position", 0.0); mlt_properties_set_data(properties, "image", NULL, 0, NULL, NULL); mlt_properties_set_int(properties, "width", profile ? profile->width : 720); mlt_properties_set_int(properties, "height", profile ? profile->height : 576); mlt_properties_set_double(properties, "aspect_ratio", mlt_profile_sar(NULL)); mlt_properties_set_data(properties, "audio", NULL, 0, NULL, NULL); mlt_properties_set_data(properties, "alpha", NULL, 0, NULL, NULL); // Construct stacks for frames and methods self->stack_image = mlt_deque_init(); self->stack_audio = mlt_deque_init(); self->stack_service = mlt_deque_init(); } return self; } /** Get a frame's properties. * * \public \memberof mlt_frame_s * \param self a frame * \return the frame's properties or NULL if an invalid frame is supplied */ mlt_properties mlt_frame_properties(mlt_frame self) { return self != NULL ? &self->parent : NULL; } /** Determine if the frame will produce a test card image. * * \public \memberof mlt_frame_s * \param self a frame * \return true (non-zero) if this will produce from a test card */ int mlt_frame_is_test_card(mlt_frame self) { mlt_properties properties = MLT_FRAME_PROPERTIES(self); return (mlt_deque_count(self->stack_image) == 0 && !mlt_properties_get_data(properties, "image", NULL)) || mlt_properties_get_int(properties, "test_image"); } /** Determine if the frame will produce audio from a test card. * * \public \memberof mlt_frame_s * \param self a frame * \return true (non-zero) if this will produce from a test card */ int mlt_frame_is_test_audio(mlt_frame self) { mlt_properties properties = MLT_FRAME_PROPERTIES(self); return (mlt_deque_count(self->stack_audio) == 0 && !mlt_properties_get_data(properties, "audio", NULL)) || mlt_properties_get_int(properties, "test_audio"); } /** Get the sample aspect ratio of the frame. * * \public \memberof mlt_frame_s * \param self a frame * \return the aspect ratio */ double mlt_frame_get_aspect_ratio(mlt_frame self) { return mlt_properties_get_double(MLT_FRAME_PROPERTIES(self), "aspect_ratio"); } /** Set the sample aspect ratio of the frame. * * \public \memberof mlt_frame_s * \param self a frame * \param value the new image sample aspect ratio * \return true if error */ int mlt_frame_set_aspect_ratio(mlt_frame self, double value) { return mlt_properties_set_double(MLT_FRAME_PROPERTIES(self), "aspect_ratio", value); } /** Get the time position of this frame. * * This position is not necessarily the position as the original * producer knows it. It could be the position that the playlist, * multitrack, or tractor producer set. * * \public \memberof mlt_frame_s * \param self a frame * \return the position * \see mlt_frame_original_position */ mlt_position mlt_frame_get_position(mlt_frame self) { int pos = mlt_properties_get_position(MLT_FRAME_PROPERTIES(self), "_position"); return pos < 0 ? 0 : pos; } /** Get the original time position of this frame. * * This is the position that the original producer set on the frame. * * \public \memberof mlt_frame_s * \param self a frame * \return the position */ mlt_position mlt_frame_original_position(mlt_frame self) { int pos = mlt_properties_get_position(MLT_FRAME_PROPERTIES(self), "original_position"); return pos < 0 ? 0 : pos; } /** Set the time position of this frame. * * \public \memberof mlt_frame_s * \param self a frame * \param value the position * \return true if error */ int mlt_frame_set_position(mlt_frame self, mlt_position value) { // Only set the original_position the first time. if (!mlt_properties_get(MLT_FRAME_PROPERTIES(self), "original_position")) mlt_properties_set_position(MLT_FRAME_PROPERTIES(self), "original_position", value); return mlt_properties_set_position(MLT_FRAME_PROPERTIES(self), "_position", value); } /** Stack a get_image callback. * * \public \memberof mlt_frame_s * \param self a frame * \param get_image the get_image callback * \return true if error */ int mlt_frame_push_get_image(mlt_frame self, mlt_get_image get_image) { return mlt_deque_push_back(self->stack_image, get_image); } /** Pop a get_image callback. * * \public \memberof mlt_frame_s * \param self a frame * \return the get_image callback */ mlt_get_image mlt_frame_pop_get_image(mlt_frame self) { return mlt_deque_pop_back(self->stack_image); } /** Push a frame. * * \public \memberof mlt_frame_s * \param self a frame * \param that the frame to push onto \p self * \return true if error */ int mlt_frame_push_frame(mlt_frame self, mlt_frame that) { return mlt_deque_push_back(self->stack_image, that); } /** Pop a frame. * * \public \memberof mlt_frame_s * \param self a frame * \return a frame that was previously pushed */ mlt_frame mlt_frame_pop_frame(mlt_frame self) { return mlt_deque_pop_back(self->stack_image); } /** Push a service. * * \public \memberof mlt_frame_s * \param self a frame * \param that an opaque pointer * \return true if error */ int mlt_frame_push_service(mlt_frame self, void *that) { return mlt_deque_push_back(self->stack_image, that); } /** Pop a service. * * \public \memberof mlt_frame_s * \param self a frame * \return an opaque pointer to something previously pushed */ void *mlt_frame_pop_service(mlt_frame self) { return mlt_deque_pop_back(self->stack_image); } /** Push a number. * * \public \memberof mlt_frame_s * \param self a frame * \param that an integer * \return true if error */ int mlt_frame_push_service_int(mlt_frame self, int that) { return mlt_deque_push_back_int(self->stack_image, that); } /** Pop a number. * * \public \memberof mlt_frame_s * \param self a frame * \return an integer that was previously pushed */ int mlt_frame_pop_service_int(mlt_frame self) { return mlt_deque_pop_back_int(self->stack_image); } /** Push an audio item on the stack. * * \public \memberof mlt_frame_s * \param self a frame * \param that an opaque pointer * \return true if error */ int mlt_frame_push_audio(mlt_frame self, void *that) { return mlt_deque_push_back(self->stack_audio, that); } /** Pop an audio item from the stack * * \public \memberof mlt_frame_s * \param self a frame * \return an opaque pointer to something that was pushed onto the frame's audio stack */ void *mlt_frame_pop_audio(mlt_frame self) { return mlt_deque_pop_back(self->stack_audio); } /** Return the service stack * * \public \memberof mlt_frame_s * \param self a frame * \return the service stack */ mlt_deque mlt_frame_service_stack(mlt_frame self) { return self->stack_service; } /** Set a new image on the frame. * * \public \memberof mlt_frame_s * \param self a frame * \param image a pointer to the raw image data * \param size the size of the image data in bytes (optional) * \param destroy a function to deallocate \p image when the frame is closed (optional) * \return true if error */ int mlt_frame_set_image(mlt_frame self, uint8_t *image, int size, mlt_destructor destroy) { return mlt_properties_set_data(MLT_FRAME_PROPERTIES(self), "image", image, size, destroy, NULL); } /** Set a new alpha channel on the frame. * * \public \memberof mlt_frame_s * \param self a frame * \param alpha a pointer to the alpha channel * \param size the size of the alpha channel in bytes (optional) * \param destroy a function to deallocate \p alpha when the frame is closed (optional) * \return true if error */ int mlt_frame_set_alpha(mlt_frame self, uint8_t *alpha, int size, mlt_destructor destroy) { return mlt_properties_set_data(MLT_FRAME_PROPERTIES(self), "alpha", alpha, size, destroy, NULL); } /** Replace image stack with the information provided. * * This might prove to be unreliable and restrictive - the idea is that a transition * which normally uses two images may decide to only use the b frame (ie: in the case * of a composite where the b frame completely obscures the a frame). * * The image must be writable and the destructor for the image itself must be taken * care of on another frame and that frame cannot have a replace applied to it... * Further it assumes that no alpha mask is in use. * * For these reasons, it can only be used in a specific situation - when you have * multiple tracks each with their own transition and these transitions are applied * in a strictly reversed order (ie: highest numbered [lowest track] is processed * first). * * More reliable approach - the cases should be detected during the process phase * and the upper tracks should simply not be invited to stack... * * \public \memberof mlt_frame_s * \param self a frame * \param image a new image * \param format the image format * \param width the width of the new image * \param height the height of the new image */ void mlt_frame_replace_image( mlt_frame self, uint8_t *image, mlt_image_format format, int width, int height) { // Remove all items from the stack while (mlt_deque_pop_back(self->stack_image)) ; // Update the information mlt_properties_set_data(MLT_FRAME_PROPERTIES(self), "image", image, 0, NULL, NULL); mlt_properties_set_int(MLT_FRAME_PROPERTIES(self), "width", width); mlt_properties_set_int(MLT_FRAME_PROPERTIES(self), "height", height); mlt_properties_set_int(MLT_FRAME_PROPERTIES(self), "format", format); } static int generate_test_image(mlt_properties properties, uint8_t **buffer, mlt_image_format *format, int *width, int *height, int writable) { mlt_producer producer = mlt_properties_get_data(properties, "test_card_producer", NULL); mlt_image_format requested_format = *format; int error = 1; if (producer) { mlt_frame test_frame = NULL; mlt_service_get_frame(MLT_PRODUCER_SERVICE(producer), &test_frame, 0); if (test_frame) { mlt_properties test_properties = MLT_FRAME_PROPERTIES(test_frame); mlt_properties_set_data(properties, "test_card_frame", test_frame, 0, (mlt_destructor) mlt_frame_close, NULL); mlt_properties_set(test_properties, "consumer.rescale", mlt_properties_get(properties, "consumer.rescale")); error = mlt_frame_get_image(test_frame, buffer, format, width, height, writable); if (!error && buffer && *buffer) { mlt_properties_set_double(properties, "aspect_ratio", mlt_frame_get_aspect_ratio(test_frame)); mlt_properties_set_int(properties, "width", *width); mlt_properties_set_int(properties, "height", *height); if (test_frame->convert_image && requested_format != mlt_image_none) test_frame->convert_image(test_frame, buffer, format, requested_format); mlt_properties_set_int(properties, "format", *format); } } else { mlt_properties_set_data(properties, "test_card_producer", NULL, 0, NULL, NULL); } } if (error && buffer) { *width = *width == 0 ? 720 : *width; *height = *height == 0 ? 576 : *height; switch (*format) { case mlt_image_rgb: case mlt_image_rgba: case mlt_image_yuv422: case mlt_image_yuv420p: case mlt_image_yuv422p16: case mlt_image_yuv420p10: case mlt_image_yuv444p10: break; case mlt_image_none: case mlt_image_movit: case mlt_image_opengl_texture: *format = mlt_image_yuv422; break; case mlt_image_invalid: *format = mlt_image_invalid; break; } struct mlt_image_s img; mlt_image_set_values(&img, NULL, *format, *width, *height); mlt_image_alloc_data(&img); if (mlt_properties_get_int(properties, "test_audio")) { const char *color_range = mlt_properties_get(properties, "consumer.color_range"); int full_range = color_range && (!strcmp("pc", color_range) || !strcmp("jpeg", color_range)); mlt_image_fill_white(&img, full_range); } else { mlt_image_fill_checkerboard(&img, mlt_properties_get_double(properties, "aspect_ratio")); } *buffer = img.data; mlt_properties_set_int(properties, "format", *format); mlt_properties_set_int(properties, "width", *width); mlt_properties_set_int(properties, "height", *height); mlt_properties_set_data(properties, "image", *buffer, 0, img.release_data, NULL); mlt_properties_set_int(properties, "test_image", 1); error = 0; } return error; } /** Get the image associated to the frame. * * You should express the desired format, width, and height as inputs. As long * as the loader producer was used to generate this or the imageconvert filter * was attached, then you will get the image back in the format you desire. * However, you do not always get the width and height you request depending * on properties and filters. You do not need to supply a pre-allocated * buffer, but you should always supply the desired image format. * * \public \memberof mlt_frame_s * \param self a frame * \param[out] buffer an image buffer * \param[in,out] format the image format * \param[in,out] width the horizontal size in pixels * \param[in,out] height the vertical size in pixels * \param writable whether or not you will need to be able to write to the memory returned in \p buffer * \return true if error * \todo Better describe the width and height as inputs. */ int mlt_frame_get_image(mlt_frame self, uint8_t **buffer, mlt_image_format *format, int *width, int *height, int writable) { mlt_properties properties = MLT_FRAME_PROPERTIES(self); mlt_get_image get_image = mlt_frame_pop_get_image(self); mlt_image_format requested_format = *format; int error = 0; if (get_image) { mlt_properties_set_int(properties, "image_count", mlt_properties_get_int(properties, "image_count") - 1); error = get_image(self, buffer, format, width, height, writable); if (!error && buffer && *buffer) { mlt_properties_set_int(properties, "width", *width); mlt_properties_set_int(properties, "height", *height); if (self->convert_image && requested_format != mlt_image_none) self->convert_image(self, buffer, format, requested_format); mlt_properties_set_int(properties, "format", *format); } else { error = generate_test_image(properties, buffer, format, width, height, writable); } } else if (mlt_properties_get_data(properties, "image", NULL) && buffer) { *format = mlt_properties_get_int(properties, "format"); *buffer = mlt_properties_get_data(properties, "image", NULL); *width = mlt_properties_get_int(properties, "width"); *height = mlt_properties_get_int(properties, "height"); if (self->convert_image && *buffer && requested_format != mlt_image_none) { self->convert_image(self, buffer, format, requested_format); mlt_properties_set_int(properties, "format", *format); } } else { error = generate_test_image(properties, buffer, format, width, height, writable); } return error; } /** Get the alpha channel associated to the frame (without creating if it has not). * * This returns NULL if the frame's image format is \p mlt_image_rgba. * \public \memberof mlt_frame_s * \param self a frame * \return the alpha channel or NULL */ uint8_t *mlt_frame_get_alpha(mlt_frame self) { uint8_t *alpha = NULL; if (self != NULL) { alpha = mlt_properties_get_data(&self->parent, "alpha", NULL); if (alpha) { mlt_image_format format = mlt_properties_get_int(&self->parent, "format"); if (mlt_image_rgba == format) { alpha = NULL; } } } return alpha; } /** Get the alpha channel associated to the frame and its size. * * This returns NULL and sets \p size to 0 if the frame's image format is * \p mlt_image_rgba. * * \public \memberof mlt_frame_s * \param self a frame * \return the alpha channel or NULL */ uint8_t *mlt_frame_get_alpha_size(mlt_frame self, int *size) { uint8_t *alpha = NULL; if (self) { alpha = mlt_properties_get_data(&self->parent, "alpha", size); if (alpha) { mlt_image_format format = mlt_properties_get_int(&self->parent, "format"); if (mlt_image_rgba == format) { alpha = NULL; if (size) { size = 0; } } } } return alpha; } /** Get the audio associated to the frame. * * You should express the desired format, frequency, channels, and samples as inputs. As long * as the loader producer was used to generate this or the audioconvert filter * was attached, then you will get the audio back in the format you desire. * However, you do not always get the channels and samples you request depending * on properties and filters. You do not need to supply a pre-allocated * buffer, but you should always supply the desired audio format. * The audio is always in interleaved format. * You should use the \p mlt_audio_sample_calculator to determine the number of samples you want. * * \public \memberof mlt_frame_s * \param self a frame * \param[out] buffer an audio buffer * \param[in,out] format the audio format * \param[in,out] frequency the sample rate * \param[in,out] channels * \param[in,out] samples the number of samples per frame * \return true if error */ int mlt_frame_get_audio(mlt_frame self, void **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples) { mlt_get_audio get_audio = mlt_frame_pop_audio(self); mlt_properties properties = MLT_FRAME_PROPERTIES(self); int hide = mlt_properties_get_int(properties, "test_audio"); mlt_audio_format requested_format = *format; if (hide == 0 && get_audio != NULL) { get_audio(self, buffer, format, frequency, channels, samples); mlt_properties_set_int(properties, "audio_frequency", *frequency); mlt_properties_set_int(properties, "audio_channels", *channels); mlt_properties_set_int(properties, "audio_samples", *samples); mlt_properties_set_int(properties, "audio_format", *format); if (self->convert_audio && *buffer && requested_format != mlt_audio_none) self->convert_audio(self, buffer, format, requested_format); } else if (mlt_properties_get_data(properties, "audio", NULL)) { *buffer = mlt_properties_get_data(properties, "audio", NULL); *format = mlt_properties_get_int(properties, "audio_format"); *frequency = mlt_properties_get_int(properties, "audio_frequency"); *channels = mlt_properties_get_int(properties, "audio_channels"); *samples = mlt_properties_get_int(properties, "audio_samples"); if (self->convert_audio && *buffer && requested_format != mlt_audio_none) self->convert_audio(self, buffer, format, requested_format); } else { int size = 0; *samples = *samples <= 0 ? 1920 : *samples; *channels = *channels <= 0 ? 2 : *channels; *frequency = *frequency <= 0 ? 48000 : *frequency; *format = *format == mlt_audio_none ? mlt_audio_s16 : *format; mlt_properties_set_int(properties, "audio_frequency", *frequency); mlt_properties_set_int(properties, "audio_channels", *channels); mlt_properties_set_int(properties, "audio_samples", *samples); mlt_properties_set_int(properties, "audio_format", *format); size = mlt_audio_format_size(*format, *samples, *channels); if (size) *buffer = mlt_pool_alloc(size); else *buffer = NULL; if (*buffer) memset(*buffer, 0, size); mlt_properties_set_data(properties, "audio", *buffer, size, (mlt_destructor) mlt_pool_release, NULL); mlt_properties_set_int(properties, "test_audio", 1); } // TODO: This does not belong here if (*format == mlt_audio_s16 && mlt_properties_get(properties, "meta.volume") && *buffer) { double value = mlt_properties_get_double(properties, "meta.volume"); if (value == 0.0) { memset(*buffer, 0, *samples * *channels * 2); } else if (value != 1.0) { int total = *samples * *channels; int16_t *p = *buffer; while (total--) { *p = *p * value; p++; } } mlt_properties_set(properties, "meta.volume", NULL); } return 0; } /** Set the audio on a frame. * * \public \memberof mlt_frame_s * \param self a frame * \param buffer an buffer containing audio samples * \param format the format of the audio in the \p buffer * \param size the total size of the buffer (optional) * \param destructor a function that releases or deallocates the \p buffer * \return true if error */ int mlt_frame_set_audio( mlt_frame self, void *buffer, mlt_audio_format format, int size, mlt_destructor destructor) { mlt_properties_set_int(MLT_FRAME_PROPERTIES(self), "audio_format", format); return mlt_properties_set_data(MLT_FRAME_PROPERTIES(self), "audio", buffer, size, destructor, NULL); } /** Get audio on a frame as a waveform image. * * This generates an 8-bit grayscale image representation of the audio in a * frame. Currently, this only really works for 2 channels. * This allocates the bitmap using mlt_pool so you should release the return * value with \p mlt_pool_release. * * \public \memberof mlt_frame_s * \param self a frame * \param w the width of the image * \param h the height of the image to create * \return a pointer to a new bitmap */ unsigned char *mlt_frame_get_waveform(mlt_frame self, int w, int h) { int16_t *pcm = NULL; mlt_properties properties = MLT_FRAME_PROPERTIES(self); mlt_audio_format format = mlt_audio_s16; int frequency = 16000; int channels = 2; mlt_producer producer = mlt_frame_get_original_producer(self); double fps = mlt_producer_get_fps(mlt_producer_cut_parent(producer)); int samples = mlt_audio_calculate_frame_samples(fps, frequency, mlt_frame_get_position(self)); // Increase audio resolution proportional to requested image size while (samples < w) { frequency += 16000; samples = mlt_audio_calculate_frame_samples(fps, frequency, mlt_frame_get_position(self)); } // Get the pcm data mlt_frame_get_audio(self, (void **) &pcm, &format, &frequency, &channels, &samples); // Make an 8-bit buffer large enough to hold rendering int size = w * h; if (size <= 0) return NULL; unsigned char *bitmap = (unsigned char *) mlt_pool_alloc(size); if (bitmap != NULL) memset(bitmap, 0, size); else return NULL; mlt_properties_set_data(properties, "waveform", bitmap, size, (mlt_destructor) mlt_pool_release, NULL); // Render vertical lines int16_t *ubound = pcm + samples * channels; int skip = samples / w; skip = !skip ? 1 : skip; unsigned char gray = 0xFF / skip; int i, j, k; // Iterate sample stream and along x coordinate for (i = 0; pcm < ubound; i++) { // pcm data has channels interleaved for (j = 0; j < channels; j++, pcm++) { // Determine sample's magnitude from 2s complement; int pcm_magnitude = *pcm < 0 ? ~(*pcm) + 1 : *pcm; // The height of a line is the ratio of the magnitude multiplied by // the vertical resolution of a single channel int height = h * pcm_magnitude / channels / 2 / 32768; // Determine the starting y coordinate - left top, right bottom int displacement = h * (j * 2 + 1) / channels / 2 - (*pcm < 0 ? 0 : height); // Position buffer pointer using y coordinate, stride, and x coordinate unsigned char *p = bitmap + i / skip + displacement * w; // Draw vertical line for (k = 0; k < height + 1; k++) if (*pcm < 0) p[w * k] = (k == 0) ? 0xFF : p[w * k] + gray; else p[w * k] = (k == height) ? 0xFF : p[w * k] + gray; } } return bitmap; } /** Get the end service that produced self frame. * * This fetches the first producer of the frame and not any producers that * encapsulate it. * * \public \memberof mlt_frame_s * \param self a frame * \return a producer */ mlt_producer mlt_frame_get_original_producer(mlt_frame self) { if (self != NULL) return mlt_properties_get_data(MLT_FRAME_PROPERTIES(self), "_producer", NULL); return NULL; } /** Destroy the frame. * * \public \memberof mlt_frame_s * \param self a frame */ void mlt_frame_close(mlt_frame self) { if (self != NULL && mlt_properties_dec_ref(MLT_FRAME_PROPERTIES(self)) <= 0) { mlt_deque_close(self->stack_image); mlt_deque_close(self->stack_audio); while (mlt_deque_peek_back(self->stack_service)) mlt_service_close(mlt_deque_pop_back(self->stack_service)); mlt_deque_close(self->stack_service); mlt_properties_close(&self->parent); free(self); } } /***** convenience functions *****/ void mlt_frame_write_ppm(mlt_frame frame) { int width = 0; int height = 0; mlt_image_format format = mlt_image_rgb; uint8_t *image; if (mlt_frame_get_image(frame, &image, &format, &width, &height, 0) == 0) { FILE *file; char filename[16]; sprintf(filename, "frame-%05d.ppm", (int) mlt_frame_get_position(frame)); file = mlt_fopen(filename, "wb"); if (!file) return; fprintf(file, "P6\n%d %d\n255\n", width, height); fwrite(image, width * height * 3, 1, file); fclose(file); } } /** Get or create a properties object unique to this service instance. * * Use this function to hold a service's processing parameters for this * particular frame. Set the parameters in the service's process function. * Then, get the parameters in the function it pushes to the frame's audio * or image stack. This makes the service more parallel by reducing race * conditions and less sensitive to multiple instances (by not setting a * non-unique property on the frame). Creation and destruction of the * properties object is handled automatically. * * \public \memberof mlt_frame_s * \param self a frame * \param service a service * \return a properties object */ mlt_properties mlt_frame_unique_properties(mlt_frame self, mlt_service service) { mlt_properties frame_props = MLT_FRAME_PROPERTIES(self); mlt_properties service_props = MLT_SERVICE_PROPERTIES(service); char *unique = mlt_properties_get(service_props, "_unique_id"); mlt_properties instance_props = mlt_properties_get_data(frame_props, unique, NULL); if (!instance_props) { instance_props = mlt_properties_new(); mlt_properties_set_data(frame_props, unique, instance_props, 0, (mlt_destructor) mlt_properties_close, NULL); mlt_properties_set_lcnumeric(instance_props, mlt_properties_get_lcnumeric(service_props)); mlt_properties_set_data(instance_props, "_profile", mlt_service_profile(service), 0, NULL, NULL); } return instance_props; } /** Get a properties object unique to this service instance. * * Unlike \p mlt_frame_unique_properties, this function does not create the * service-unique properties object if it does not exist. * * \public \memberof mlt_frame_s * \param self a frame * \param service a service * \return a properties object or NULL if it does not exist */ mlt_properties mlt_frame_get_unique_properties(mlt_frame self, mlt_service service) { char *unique = mlt_properties_get(MLT_SERVICE_PROPERTIES(service), "_unique_id"); return mlt_properties_get_data(MLT_FRAME_PROPERTIES(self), unique, NULL); } /** Make a copy of a frame. * * This does not copy the get_image/get_audio processing stacks or any * data properties other than the audio and image. * * \public \memberof mlt_frame_s * \param self the frame to clone * \param is_deep a boolean to indicate whether to make a deep copy of the audio * and video data chunks or to make a shallow copy by pointing to the supplied frame * \return a almost-complete copy of the frame * \todo copy the processing deques */ mlt_frame mlt_frame_clone(mlt_frame self, int is_deep) { mlt_frame new_frame = mlt_frame_init(NULL); mlt_properties properties = MLT_FRAME_PROPERTIES(self); mlt_properties new_props = MLT_FRAME_PROPERTIES(new_frame); void *data, *copy; int size = 0; mlt_properties_inherit(new_props, properties); // Carry over some special data properties for the multi consumer. mlt_properties_set_data(new_props, "_producer", mlt_frame_get_original_producer(self), 0, NULL, NULL); mlt_properties_set_data(new_props, "movit.convert", mlt_properties_get_data(properties, "movit.convert", NULL), 0, NULL, NULL); mlt_properties_set_data(new_props, "_movit cpu_convert", mlt_properties_get_data(properties, "_movit cpu_convert", NULL), 0, NULL, NULL); if (is_deep) { data = mlt_properties_get_data(properties, "audio", &size); if (data) { if (!size) size = mlt_audio_format_size(mlt_properties_get_int(properties, "audio_format"), mlt_properties_get_int(properties, "audio_samples"), mlt_properties_get_int(properties, "audio_channels")); copy = mlt_pool_alloc(size); memcpy(copy, data, size); mlt_properties_set_data(new_props, "audio", copy, size, mlt_pool_release, NULL); } size = 0; data = mlt_properties_get_data(properties, "image", &size); if (data && mlt_image_movit != mlt_properties_get_int(properties, "format")) { int width = mlt_properties_get_int(properties, "width"); int height = mlt_properties_get_int(properties, "height"); if (!size) size = mlt_image_format_size(mlt_properties_get_int(properties, "format"), width, height, NULL); copy = mlt_pool_alloc(size); memcpy(copy, data, size); mlt_properties_set_data(new_props, "image", copy, size, mlt_pool_release, NULL); size = 0; data = mlt_frame_get_alpha_size(self, &size); if (data) { if (!size) size = width * height; copy = mlt_pool_alloc(size); memcpy(copy, data, size); mlt_properties_set_data(new_props, "alpha", copy, size, mlt_pool_release, NULL); }; } } else { // This frame takes a reference on the original frame since the data is a shallow copy. mlt_properties_inc_ref(properties); mlt_properties_set_data(new_props, "_cloned_frame", self, 0, (mlt_destructor) mlt_frame_close, NULL); // Copy properties data = mlt_properties_get_data(properties, "audio", &size); mlt_properties_set_data(new_props, "audio", data, size, NULL, NULL); size = 0; data = mlt_properties_get_data(properties, "image", &size); mlt_properties_set_data(new_props, "image", data, size, NULL, NULL); size = 0; data = mlt_frame_get_alpha_size(self, &size); mlt_properties_set_data(new_props, "alpha", data, size, NULL, NULL); } return new_frame; } /** Make a copy of a frame and audio. * * This does not copy the get_image/get_audio processing stacks or any * data properties other than the audio. * * \public \memberof mlt_frame_s * \param self the frame to clone * \param is_deep a boolean to indicate whether to make a deep copy of the audio * data chunks or to make a shallow copy by pointing to the supplied frame * \return a almost-complete copy of the frame * \todo copy the processing deques */ mlt_frame mlt_frame_clone_audio(mlt_frame self, int is_deep) { mlt_frame new_frame = mlt_frame_init(NULL); mlt_properties properties = MLT_FRAME_PROPERTIES(self); mlt_properties new_props = MLT_FRAME_PROPERTIES(new_frame); void *data, *copy; int size = 0; mlt_properties_inherit(new_props, properties); // Carry over some special data properties for the multi consumer. mlt_properties_set_data(new_props, "_producer", mlt_frame_get_original_producer(self), 0, NULL, NULL); mlt_properties_set_data(new_props, "movit.convert", mlt_properties_get_data(properties, "movit.convert", NULL), 0, NULL, NULL); mlt_properties_set_data(new_props, "_movit cpu_convert", mlt_properties_get_data(properties, "_movit cpu_convert", NULL), 0, NULL, NULL); if (is_deep) { data = mlt_properties_get_data(properties, "audio", &size); if (data) { if (!size) size = mlt_audio_format_size(mlt_properties_get_int(properties, "audio_format"), mlt_properties_get_int(properties, "audio_samples"), mlt_properties_get_int(properties, "audio_channels")); copy = mlt_pool_alloc(size); memcpy(copy, data, size); mlt_properties_set_data(new_props, "audio", copy, size, mlt_pool_release, NULL); } } else { // This frame takes a reference on the original frame since the data is a shallow copy. mlt_properties_inc_ref(properties); mlt_properties_set_data(new_props, "_cloned_frame", self, 0, (mlt_destructor) mlt_frame_close, NULL); // Copy properties data = mlt_properties_get_data(properties, "audio", &size); mlt_properties_set_data(new_props, "audio", data, size, NULL, NULL); } return new_frame; } /** Make a copy of a frame and image. * * This does not copy the get_image/get_audio processing stacks or any * data properties other than the image. * * \public \memberof mlt_frame_s * \param self the frame to clone * \param is_deep a boolean to indicate whether to make a deep copy of the * video data chunks or to make a shallow copy by pointing to the supplied frame * \return a almost-complete copy of the frame * \todo copy the processing deques */ mlt_frame mlt_frame_clone_image(mlt_frame self, int is_deep) { mlt_frame new_frame = mlt_frame_init(NULL); mlt_properties properties = MLT_FRAME_PROPERTIES(self); mlt_properties new_props = MLT_FRAME_PROPERTIES(new_frame); void *data, *copy; int size = 0; mlt_properties_inherit(new_props, properties); // Carry over some special data properties for the multi consumer. mlt_properties_set_data(new_props, "_producer", mlt_frame_get_original_producer(self), 0, NULL, NULL); mlt_properties_set_data(new_props, "movit.convert", mlt_properties_get_data(properties, "movit.convert", NULL), 0, NULL, NULL); mlt_properties_set_data(new_props, "_movit cpu_convert", mlt_properties_get_data(properties, "_movit cpu_convert", NULL), 0, NULL, NULL); if (is_deep) { data = mlt_properties_get_data(properties, "image", &size); if (data && mlt_image_movit != mlt_properties_get_int(properties, "format")) { int width = mlt_properties_get_int(properties, "width"); int height = mlt_properties_get_int(properties, "height"); if (!size) size = mlt_image_format_size(mlt_properties_get_int(properties, "format"), width, height, NULL); copy = mlt_pool_alloc(size); memcpy(copy, data, size); mlt_properties_set_data(new_props, "image", copy, size, mlt_pool_release, NULL); size = 0; data = mlt_frame_get_alpha_size(self, &size); if (data) { if (!size) size = width * height; copy = mlt_pool_alloc(size); memcpy(copy, data, size); mlt_properties_set_data(new_props, "alpha", copy, size, mlt_pool_release, NULL); }; } } else { // This frame takes a reference on the original frame since the data is a shallow copy. mlt_properties_inc_ref(properties); mlt_properties_set_data(new_props, "_cloned_frame", self, 0, (mlt_destructor) mlt_frame_close, NULL); // Copy properties size = 0; data = mlt_properties_get_data(properties, "image", &size); mlt_properties_set_data(new_props, "image", data, size, NULL, NULL); size = 0; data = mlt_frame_get_alpha_size(self, &size); mlt_properties_set_data(new_props, "alpha", data, size, NULL, NULL); } return new_frame; } mlt-7.22.0/src/framework/mlt_frame.h000664 000000 000000 00000022412 14531534050 017262 0ustar00rootroot000000 000000 /** * \file mlt_frame.h * \brief interface for all frame classes * \see mlt_frame_s * * Copyright (C) 2003-2023 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef MLT_FRAME_H #define MLT_FRAME_H #include "mlt_audio.h" #include "mlt_deque.h" #include "mlt_image.h" #include "mlt_properties.h" #include "mlt_service.h" /** Callback function to get video data. * */ typedef int (*mlt_get_image)(mlt_frame self, uint8_t **buffer, mlt_image_format *format, int *width, int *height, int writable); /** Callback function to get audio data. * */ typedef int (*mlt_get_audio)(mlt_frame self, void **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples); /** \brief Frame class * * The frame is the primary data object that gets passed around to and through services. * * \extends mlt_properties * \properties \em test_image set if the frame holds a "test card" image * \properties \em test_audio set if the frame holds "test card" audio * \properties \em _producer holds a reference to the frame's end producer * \properties \em _speed the current speed of the producer that generated the frame * \properties \em _position the position of the frame * \properties \em meta.* holds metadata * \properties \em hide set to 1 to hide the video, 2 to mute the audio * \properties \em last_track a flag to indicate an end-of-tracks frame * \properties \em previous \em frame a reference to the unfiltered preceding frame * (no speed factor applied, only available when \em _need_previous_next is set on the producer) * \properties \em next \em frame a reference to the unfiltered following frame * (no speed factor applied, only available when \em _need_previous_next is set on the producer) * \properties \em colorspace the standard for the YUV coefficients * \properties \em force_full_luma luma range handling: 1 for full range, 0 for scaling (DEPRECATED) * \properties \em color_trc the color transfer characteristic (gamma) * \properties \em audio_frequency the sample rate of the audio * \properties \em audio_channels the number of audio channels * \properties \em audio_samples the number of audio samples * \properties \em audio_format the mlt_audio_format for the audio on this frame * \properties \em format the mlt_image_format of the image on this frame * \properties \em width the horizontal resolution of the image * \properties \em height the vertical resolution of the image * \properties \em aspect_ratio the sample aspect ratio of the image * \properties \em full_range set if the video is full range - only applies to Y'CbCr * \properties \em meta.playlist.clip_position mlt_playlist sets this property * to the time position of this frame's clip in the playlist * \properties \em meta.playlist.clip_length mlt_playlist sets this property to * the playlist index of this frame's clip in the playlist */ struct mlt_frame_s { struct mlt_properties_s parent; /**< \private A frame extends properties. */ /** Convert the image format (callback function). * \param self a frame * \param[in,out] image a buffer of image data * \param[in,out] input the image format of supplied image data * \param output the image format to which to convert * \return true if error */ int (*convert_image)(mlt_frame self, uint8_t **image, mlt_image_format *input, mlt_image_format output); /** Convert the audio format (callback function). * \param self a frame * \param[in,out] audio a buffer of audio data * \param[in,out] input the audio format of supplied data * \param output the audio format to which to convert * \return true if error */ int (*convert_audio)(mlt_frame self, void **audio, mlt_audio_format *input, mlt_audio_format output); mlt_deque stack_image; /**< \private the image processing stack of operations and data */ mlt_deque stack_audio; /**< \private the audio processing stack of operations and data */ mlt_deque stack_service; /**< \private a general purpose data stack */ int is_processing; /**< \private indicates if a frame is or was processed by the parallel consumer */ }; #define MLT_FRAME_PROPERTIES(frame) (&(frame)->parent) #define MLT_FRAME_SERVICE_STACK(frame) ((frame)->stack_service) #define MLT_FRAME_IMAGE_STACK(frame) ((frame)->stack_image) #define MLT_FRAME_AUDIO_STACK(frame) ((frame)->stack_audio) extern mlt_frame mlt_frame_init(mlt_service service); extern mlt_properties mlt_frame_properties(mlt_frame self); extern int mlt_frame_is_test_card(mlt_frame self); extern int mlt_frame_is_test_audio(mlt_frame self); extern double mlt_frame_get_aspect_ratio(mlt_frame self); extern int mlt_frame_set_aspect_ratio(mlt_frame self, double value); extern mlt_position mlt_frame_get_position(mlt_frame self); extern mlt_position mlt_frame_original_position(mlt_frame self); extern int mlt_frame_set_position(mlt_frame self, mlt_position value); extern int mlt_frame_set_image(mlt_frame self, uint8_t *image, int size, mlt_destructor destroy); extern int mlt_frame_set_alpha(mlt_frame self, uint8_t *alpha, int size, mlt_destructor destroy); extern void mlt_frame_replace_image( mlt_frame self, uint8_t *image, mlt_image_format format, int width, int height); extern int mlt_frame_get_image(mlt_frame self, uint8_t **buffer, mlt_image_format *format, int *width, int *height, int writable); extern uint8_t *mlt_frame_get_alpha(mlt_frame self); extern uint8_t *mlt_frame_get_alpha_size(mlt_frame self, int *size); extern int mlt_frame_get_audio(mlt_frame self, void **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples); extern int mlt_frame_set_audio( mlt_frame self, void *buffer, mlt_audio_format, int size, mlt_destructor); extern unsigned char *mlt_frame_get_waveform(mlt_frame self, int w, int h); extern int mlt_frame_push_get_image(mlt_frame self, mlt_get_image get_image); extern mlt_get_image mlt_frame_pop_get_image(mlt_frame self); extern int mlt_frame_push_frame(mlt_frame self, mlt_frame that); extern mlt_frame mlt_frame_pop_frame(mlt_frame self); extern int mlt_frame_push_service(mlt_frame self, void *that); extern void *mlt_frame_pop_service(mlt_frame self); extern int mlt_frame_push_service_int(mlt_frame self, int that); extern int mlt_frame_pop_service_int(mlt_frame self); extern int mlt_frame_push_audio(mlt_frame self, void *that); extern void *mlt_frame_pop_audio(mlt_frame self); extern mlt_deque mlt_frame_service_stack(mlt_frame self); extern mlt_producer mlt_frame_get_original_producer(mlt_frame self); extern void mlt_frame_close(mlt_frame self); extern mlt_properties mlt_frame_unique_properties(mlt_frame self, mlt_service service); extern mlt_properties mlt_frame_get_unique_properties(mlt_frame self, mlt_service service); extern mlt_frame mlt_frame_clone(mlt_frame self, int is_deep); extern mlt_frame mlt_frame_clone_audio(mlt_frame self, int is_deep); extern mlt_frame mlt_frame_clone_image(mlt_frame self, int is_deep); /* convenience functions */ extern void mlt_frame_write_ppm(mlt_frame frame); /** This macro scales RGB into the YUV gamut - y is scaled by 219/255 and uv by 224/255. */ #define RGB2YUV_601_SCALED(r, g, b, y, u, v) \ y = ((263 * r + 516 * g + 100 * b) >> 10) + 16; \ u = ((-152 * r - 300 * g + 450 * b) >> 10) + 128; \ v = ((450 * r - 377 * g - 73 * b) >> 10) + 128; /** This macro scales RGB into the YUV gamut - uv is scaled by 224/255 (y unused). */ #define RGB2UV_601_SCALED(r, g, b, u, v) \ u = ((-152 * r - 300 * g + 450 * b) >> 10) + 128; \ v = ((450 * r - 377 * g - 73 * b) >> 10) + 128; /** This macro scales YUV up into the full gamut of the RGB color space. */ #define YUV2RGB_601_SCALED(y, u, v, r, g, b) \ r = ((1192 * (y - 16) + 1634 * (v - 128)) >> 10); \ g = ((1192 * (y - 16) - 832 * (v - 128) - 401 * (u - 128)) >> 10); \ b = ((1192 * (y - 16) + 2066 * (u - 128)) >> 10); \ r = r < 0 ? 0 : r > 255 ? 255 : r; \ g = g < 0 ? 0 : g > 255 ? 255 : g; \ b = b < 0 ? 0 : b > 255 ? 255 : b; #endif mlt-7.22.0/src/framework/mlt_image.c000664 000000 000000 00000047107 14531534050 017255 0ustar00rootroot000000 000000 /** * \file mlt_image.c * \brief Image class * \see mlt_mlt_image_s * * Copyright (C) 2020-2023 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "mlt_image.h" #include "mlt_log.h" #include #include /** Allocate a new Image object. * * \return a new image object with default values set */ mlt_image mlt_image_new() { mlt_image self = calloc(1, sizeof(struct mlt_image_s)); self->close = free; return self; } /** Destroy an image object created by mlt_image_new(). * * \public \memberof mlt_image_s * \param self the Image object */ void mlt_image_close(mlt_image self) { if (self) { if (self->release_data) { self->release_data(self->data); } if (self->release_alpha) { self->release_alpha(self->alpha); } if (self->close) { self->close(self); } } } /** Set the most common values for the image. * * Less common values will be set to reasonable defaults. * * \public \memberof mlt_image_s * \param self the Image object * \param data the buffer that contains the image data * \param format the image format * \param width the width of the image * \param height the height of the image */ void mlt_image_set_values(mlt_image self, void *data, mlt_image_format format, int width, int height) { self->data = data; self->format = format; self->width = width; self->height = height; self->colorspace = mlt_colorspace_unspecified; self->release_data = NULL; self->release_alpha = NULL; self->close = NULL; mlt_image_format_planes(self->format, self->width, self->height, self->data, self->planes, self->strides); } /** Get the most common values for the image. * * \public \memberof mlt_image_s * \param self the Image object * \param[out] data the buffer that contains the image data * \param[out] format the image format * \param[out] width the width of the image * \param[out] height the height of the image */ void mlt_image_get_values( mlt_image self, void **data, mlt_image_format *format, int *width, int *height) { *data = self->data; *format = self->format; *width = self->width; *height = self->height; } /** Allocate the data field based on the other properties of the Image. * * If the data field is already set, and a destructor function exists, the data * will be released. Else, the data pointer will be overwritten without being * released. * * After this function call, the release_data field will be set and can be used * to release the data when necessary. * * \public \memberof mlt_image_s * \param self the Image object */ void mlt_image_alloc_data(mlt_image self) { if (!self) return; if (self->release_data) { self->release_data(self->data); } int size = mlt_image_calculate_size(self); self->data = mlt_pool_alloc(size); self->release_data = mlt_pool_release; mlt_image_format_planes(self->format, self->width, self->height, self->data, self->planes, self->strides); } /** Allocate the alpha field based on the other properties of the Image. * * If the alpha field is already set, and a destructor function exists, the data * will be released. Else, the data pointer will be overwritten without being * released. * * After this function call, the release_data field will be set and can be used * to release the data when necessary. * * \public \memberof mlt_image_s * \param self the Image object */ void mlt_image_alloc_alpha(mlt_image self) { if (!self) return; if (self->release_alpha) { self->release_alpha(self->alpha); } self->alpha = mlt_pool_alloc(self->width * self->height); self->release_alpha = mlt_pool_release; self->strides[3] = self->width; self->planes[3] = self->alpha; } /** Calculate the number of bytes needed for the Image data. * * \public \memberof mlt_image_s * \param self the Image object * \return the number of bytes */ int mlt_image_calculate_size(mlt_image self) { switch (self->format) { case mlt_image_rgb: return self->width * self->height * 3; case mlt_image_rgba: return self->width * self->height * 4; case mlt_image_yuv422: return self->width * self->height * 2; case mlt_image_yuv420p: return self->width * self->height * 3 / 2; case mlt_image_movit: case mlt_image_opengl_texture: return 4; case mlt_image_yuv422p16: return self->width * self->height * 4; case mlt_image_yuv420p10: return self->width * self->height * 3; case mlt_image_yuv444p10: return self->width * self->height * 6; case mlt_image_none: case mlt_image_invalid: return 0; } return 0; } /** Get the short name for an image format. * * \public \memberof mlt_image_s * \param format the image format * \return a string */ const char *mlt_image_format_name(mlt_image_format format) { switch (format) { case mlt_image_none: return "none"; case mlt_image_rgb: return "rgb"; case mlt_image_rgba: return "rgba"; case mlt_image_yuv422: return "yuv422"; case mlt_image_yuv420p: return "yuv420p"; case mlt_image_movit: return "glsl"; case mlt_image_opengl_texture: return "opengl_texture"; case mlt_image_yuv422p16: return "yuv422p16"; case mlt_image_yuv420p10: return "yuv420p10"; case mlt_image_yuv444p10: return "yuv444p10"; case mlt_image_invalid: return "invalid"; } return "invalid"; } /** Get the id of image format from short name. * * \public \memberof mlt_image_s * \param name the image format short name * \return a image format */ mlt_image_format mlt_image_format_id(const char *name) { mlt_image_format f; for (f = mlt_image_none; name && f < mlt_image_invalid; f++) { const char *v = mlt_image_format_name(f); if (!strcmp(v, name)) return f; } return mlt_image_invalid; } /** Fill an image with black. * * \bug This does not respect full range YUV if needed. * \public \memberof mlt_image_s * \param self a mlt_image */ void mlt_image_fill_black(mlt_image self) { if (!self->data) return; switch (self->format) { case mlt_image_invalid: case mlt_image_none: case mlt_image_movit: case mlt_image_opengl_texture: return; case mlt_image_rgb: case mlt_image_rgba: { int size = mlt_image_calculate_size(self); memset(self->planes[0], 0, size); break; } case mlt_image_yuv422: { int size = mlt_image_calculate_size(self); register uint8_t *p = self->planes[0]; register uint8_t *q = p + size; while (p != NULL && p != q) { *p++ = 16; *p++ = 128; } } break; case mlt_image_yuv422p16: { for (int plane = 0; plane < 3; plane++) { uint16_t value = 16 << 8; size_t width = self->width; if (plane > 0) { value = 128 << 8; width /= 2; } uint16_t *p = (uint16_t *) self->planes[plane]; for (int i = 0; i < width * self->height; i++) { p[i] = value; } } } break; case mlt_image_yuv420p10: case mlt_image_yuv444p10: { for (int plane = 0; plane < 3; plane++) { uint16_t value = 16 << 2; size_t width = self->width; size_t height = self->height; if (plane > 0) { value = 128 << 2; if (self->format == mlt_image_yuv420p10) { width /= 2; height /= 2; } } uint16_t *p = (uint16_t *) self->planes[plane]; for (int i = 0; i < width * height; i++) { p[i] = value; } } } break; case mlt_image_yuv420p: { memset(self->planes[0], 16, self->height * self->strides[0]); memset(self->planes[1], 128, self->height * self->strides[1] / 2); memset(self->planes[2], 128, self->height * self->strides[2] / 2); } break; } } /** Fill an image with a checkerboard pattern. * * \public \memberof mlt_image_s * \param self a mlt_image * \param sample_aspect_ratio the pixel aspect ratio */ void mlt_image_fill_checkerboard(mlt_image self, double sample_aspect_ratio) { if (!self->data) return; if (sample_aspect_ratio == 0) sample_aspect_ratio = 1.0; int h = 0.025 * MAX(self->width * sample_aspect_ratio, self->height); int w = h / sample_aspect_ratio; if (w <= 0 || h <= 0) return; // compute center offsets int ox = w * 2 - (self->width / 2) % (w * 2); int oy = h * 2 - (self->height / 2) % (h * 2); int bpp = self->strides[0] / self->width; uint8_t color, gray1 = 0x7F, gray2 = 0xB2; switch (self->format) { case mlt_image_invalid: case mlt_image_none: case mlt_image_movit: case mlt_image_opengl_texture: return; case mlt_image_rgb: case mlt_image_rgba: { uint8_t *p = self->planes[0]; for (int i = 0; i < self->height; i++) { for (int j = 0; j < self->width; j++) { color = ((((i + oy) / h) % 2) ^ (((j + ox) / w) % 2)) ? gray1 : gray2; memset(&p[i * self->strides[0] + j * bpp], color, bpp); } } break; } case mlt_image_yuv422: { uint8_t *p = self->planes[0]; for (int i = 0; i < self->height; i++) { for (int j = 0; j < self->width; j++) { color = ((((i + oy) / h) % 2) ^ (((j + ox) / w) % 2)) ? gray1 : gray2; p[i * self->strides[0] + j * bpp] = color; p[i * self->strides[0] + j * bpp + 1] = 128; } } } break; case mlt_image_yuv422p16: { for (int plane = 0; plane < 3; plane++) { int width = plane > 0 ? self->width / 2 : self->width; uint16_t *p = (uint16_t *) self->planes[plane]; uint16_t color; for (int i = 0; i < self->height; i++) { for (int j = 0; j < width; j++) { color = plane > 0 ? 128 : ((((i + oy) / h) % 2) ^ (((j + ox) / w) % 2)) ? gray1 : gray2; p[i * width + j] = color << 8; } } } } break; case mlt_image_yuv420p10: case mlt_image_yuv444p10: { for (int plane = 0; plane < 3; plane++) { uint16_t *p = (uint16_t *) self->planes[plane]; uint16_t color; int width = self->width; int height = self->height; if (plane > 0 && self->format == mlt_image_yuv420p10) { width /= 2; height /= 2; } for (int i = 0; i < height; i++) { for (int j = 0; j < width; j++) { color = plane > 0 ? 128 : ((((i + oy) / h) % 2) ^ (((j + ox) / w) % 2)) ? gray1 : gray2; p[i * width + j] = color << 2; } } } } break; case mlt_image_yuv420p: { uint8_t *p = self->planes[0]; for (int i = 0; i < self->height; i++) { for (int j = 0; j < self->width; j++) { color = ((((i + oy) / h) % 2) ^ (((j + ox) / w) % 2)) ? gray1 : gray2; p[i * self->width + j] = color; } } memset(self->planes[1], 128, self->height * self->strides[1] / 2); memset(self->planes[2], 128, self->height * self->strides[2] / 2); } break; } } /** Fill an image with white. * * \public \memberof mlt_image_s * \param self a mlt_image * \param full_range whether to use full color range */ void mlt_image_fill_white(mlt_image self, int full_range) { if (!self->data) return; uint8_t white = full_range ? 255 : 235; switch (self->format) { case mlt_image_invalid: case mlt_image_none: case mlt_image_movit: case mlt_image_opengl_texture: return; case mlt_image_rgb: case mlt_image_rgba: { int size = mlt_image_calculate_size(self); memset(self->planes[0], 255, size); break; } case mlt_image_yuv422: { int size = mlt_image_calculate_size(self); register uint8_t *p = self->planes[0]; register uint8_t *q = p + size; while (p != NULL && p != q) { *p++ = white; *p++ = 128; } } break; case mlt_image_yuv422p16: { for (int plane = 0; plane < 3; plane++) { uint16_t value = white << 8; size_t width = self->width; if (plane > 0) { value = 128 << 8; width /= 2; } uint16_t *p = (uint16_t *) self->planes[plane]; for (int i = 0; i < width * self->height; i++) { p[i] = value; } } } break; case mlt_image_yuv420p10: case mlt_image_yuv444p10: for (int plane = 0; plane < 3; plane++) { uint16_t value = white << 2; size_t width = self->width; size_t height = self->height; if (plane > 0) { value = 128 << 2; if (self->format == mlt_image_yuv420p10) { width /= 2; height /= 2; } } uint16_t *p = (uint16_t *) self->planes[plane]; for (int i = 0; i < width * height; i++) { p[i] = value; } } break; case mlt_image_yuv420p: { memset(self->planes[0], white, self->height * self->strides[0]); memset(self->planes[1], 128, self->height * self->strides[1] / 2); memset(self->planes[2], 128, self->height * self->strides[2] / 2); } break; } } /** Fill an image alpha channel with opaque if it exists. * * \public \memberof mlt_image_s */ void mlt_image_fill_opaque(mlt_image self) { if (!self->data) return; if (self->format == mlt_image_rgba && self->planes[0] != NULL) { for (int line = 0; line < self->height; line++) { uint8_t *pLine = self->planes[0] + (self->strides[0] * line) + 3; for (int pixel = 0; pixel < self->width; pixel++) { *pLine = 0xff; *pLine += 4; } } } else if (self->planes[3] != NULL) { memset(self->planes[3], 255, self->height * self->strides[3]); } } /** Get the number of bytes needed for an image. * * \public \memberof mlt_image_s * \param format the image format * \param width width of the image in pixels * \param height height of the image in pixels * \param[out] bpp the number of bytes per pixel (optional) * \return the number of bytes */ int mlt_image_format_size(mlt_image_format format, int width, int height, int *bpp) { switch (format) { case mlt_image_rgb: if (bpp) *bpp = 3; return width * height * 3; case mlt_image_rgba: if (bpp) *bpp = 4; return width * height * 4; case mlt_image_yuv422: if (bpp) *bpp = 2; return width * height * 2; case mlt_image_yuv420p: if (bpp) *bpp = 3 / 2; return width * height * 3 / 2; case mlt_image_movit: case mlt_image_opengl_texture: if (bpp) *bpp = 0; return 4; case mlt_image_yuv422p16: if (bpp) *bpp = 4; return 4 * height * width; case mlt_image_yuv420p10: if (bpp) *bpp = 3; return 3 * height * width; case mlt_image_yuv444p10: if (bpp) *bpp = 6; return 6 * height * width; default: if (bpp) *bpp = 0; return 0; } return 0; } /** Build a planes pointers of image mapping * * For proper and unified planar image processing, planes sizes and planes pointers should * be provides to processing code. * * \public \memberof mlt_image_s * \param format the image format * \param width width of the image in pixels * \param height height of the image in pixels * \param[in] data pointer to allocated image * \param[out] planes pointers to plane's pointers will be set * \param[out] strides pointers to plane's strides will be set */ void mlt_image_format_planes( mlt_image_format format, int width, int height, void *data, uint8_t *planes[4], int strides[4]) { switch (format) { case mlt_image_yuv422p16: strides[0] = width * 2; strides[1] = width; strides[2] = width; strides[3] = 0; planes[0] = (unsigned char *) data; planes[1] = planes[0] + height * strides[0]; planes[2] = planes[1] + height * strides[1]; planes[3] = 0; break; case mlt_image_yuv420p10: strides[0] = width * 2; strides[1] = width; strides[2] = width; strides[3] = 0; planes[0] = (unsigned char *) data; planes[1] = planes[0] + height * strides[0]; planes[2] = planes[1] + (height >> 1) * strides[1]; planes[3] = 0; break; case mlt_image_yuv444p10: strides[0] = width * 2; strides[1] = width * 2; strides[2] = width * 2; strides[3] = 0; planes[0] = (unsigned char *) data; planes[1] = planes[0] + height * strides[0]; planes[2] = planes[1] + height * strides[1]; planes[3] = 0; break; case mlt_image_yuv420p: strides[0] = width; strides[1] = width >> 1; strides[2] = width >> 1; strides[3] = 0; planes[0] = (unsigned char *) data; planes[1] = planes[0] + width * height; planes[2] = planes[1] + (width >> 1) * (height >> 1); planes[3] = 0; break; default: planes[0] = data; planes[1] = 0; planes[2] = 0; planes[3] = 0; strides[0] = mlt_image_format_size(format, width, 1, NULL); strides[1] = 0; strides[2] = 0; strides[3] = 0; break; }; } /** Check if the alpha channel of an rgba image is opaque * * \public \memberof mlt_image_s * \param image the image buffer * \param width width of the image in pixels * \param height height of the image in pixels */ int mlt_image_rgba_opaque(uint8_t *image, int width, int height) { for (int i = 0; i < width * height; ++i) { if (image[4 * i + 3] != 0xff) return 0; } return 1; } mlt-7.22.0/src/framework/mlt_image.h000664 000000 000000 00000004771 14531534050 017262 0ustar00rootroot000000 000000 /** * \file mlt_image.h * \brief Image class * \see mlt_image_s * * Copyright (C) 2022 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef MLT_IMAGE_H #define MLT_IMAGE_H #include "mlt_types.h" /** \brief Image class * * Image is the data object that represents image for a period of time. */ #define MLT_IMAGE_MAX_PLANES 4 struct mlt_image_s { mlt_image_format format; int width; int height; int colorspace; uint8_t *planes[MLT_IMAGE_MAX_PLANES]; int strides[MLT_IMAGE_MAX_PLANES]; void *data; mlt_destructor release_data; void *alpha; mlt_destructor release_alpha; mlt_destructor close; }; extern mlt_image mlt_image_new(); extern void mlt_image_close(mlt_image self); extern void mlt_image_set_values( mlt_image self, void *data, mlt_image_format format, int width, int height); extern void mlt_image_get_values( mlt_image self, void **data, mlt_image_format *format, int *width, int *height); extern void mlt_image_alloc_data(mlt_image self); extern void mlt_image_alloc_alpha(mlt_image self); extern int mlt_image_calculate_size(mlt_image self); extern void mlt_image_fill_black(mlt_image self); extern void mlt_image_fill_checkerboard(mlt_image self, double sample_aspect_ratio); extern void mlt_image_fill_white(mlt_image self, int full_range); extern void mlt_image_fill_opaque(mlt_image self); extern const char *mlt_image_format_name(mlt_image_format format); extern mlt_image_format mlt_image_format_id(const char *name); extern int mlt_image_rgba_opaque(uint8_t *image, int width, int height); // Deprecated functions extern int mlt_image_format_size(mlt_image_format format, int width, int height, int *bpp); extern void mlt_image_format_planes( mlt_image_format format, int width, int height, void *data, uint8_t *planes[4], int strides[4]); #endif mlt-7.22.0/src/framework/mlt_link.c000664 000000 000000 00000017342 14531534050 017126 0ustar00rootroot000000 000000 /** * \file mlt_link.c * \brief link service class * \see mlt_link_s * * Copyright (C) 2020 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "mlt_link.h" #include "mlt_factory.h" #include "mlt_frame.h" #include "mlt_log.h" #include #include /* Forward references to static methods. */ static int producer_get_frame(mlt_producer parent, mlt_frame_ptr frame, int track); static int producer_seek(mlt_producer parent, mlt_position position); static int producer_set_in_and_out(mlt_producer, mlt_position, mlt_position); /** Construct a link. * * Sets the mlt_type to "link" * * \public \memberof mlt_link_s * \return the new link */ mlt_link mlt_link_init() { mlt_link self = calloc(1, sizeof(struct mlt_link_s)); if (self != NULL) { mlt_producer producer = &self->parent; if (mlt_producer_init(producer, self) == 0) { mlt_properties properties = MLT_PRODUCER_PROPERTIES(producer); mlt_properties_set(properties, "mlt_type", "link"); mlt_properties_clear(properties, "mlt_service"); mlt_properties_clear(properties, "resource"); mlt_properties_clear(properties, "in"); mlt_properties_clear(properties, "out"); mlt_properties_clear(properties, "length"); mlt_properties_clear(properties, "eof"); producer->get_frame = producer_get_frame; producer->seek = producer_seek; producer->set_in_and_out = producer_set_in_and_out; producer->close = (mlt_destructor) mlt_link_close; producer->close_object = self; } else { free(self); self = NULL; } } return self; } /** Connect this link to the next producer. * * \public \memberof mlt_link_s * \param self a link * \param next the producer to get frames from * \param chain_profile a profile to use if needed (some links derive their frame rate from the next producer) * \return true on error */ int mlt_link_connect_next(mlt_link self, mlt_producer next, mlt_profile chain_profile) { self->next = next; if (self->configure) { self->configure(self, chain_profile); } return 0; } /** Close the link and free its resources. * * \public \memberof mlt_link_s * \param self a link */ void mlt_link_close(mlt_link self) { if (self != NULL && mlt_properties_dec_ref(MLT_LINK_PROPERTIES(self)) <= 0) { if (self->close) { self->close(self); } else { self->parent.close = NULL; mlt_producer_close(&self->parent); } } } static int producer_get_frame(mlt_producer parent, mlt_frame_ptr frame, int index) { if (parent && parent->child) { mlt_link self = parent->child; if (self->get_frame != NULL) { return self->get_frame(self, frame, index); } else { /* Default implementation: get a frame from the next producer */ return mlt_service_get_frame(MLT_PRODUCER_SERVICE(self->next), frame, index); } } return 1; } int producer_seek(mlt_producer parent, mlt_position position) { // Unlike mlt_producer_seek(), a link does not do bounds checking when seeking if (parent && parent->child) { mlt_link self = parent->child; mlt_properties properties = MLT_LINK_PROPERTIES(self); int use_points = 1 - mlt_properties_get_int(properties, "ignore_points"); // Set the position mlt_properties_set_position(properties, "_position", position); // Calculate the absolute frame mlt_properties_set_position(properties, "_frame", use_points * mlt_producer_get_in(parent) + position); } return 0; } int producer_set_in_and_out(mlt_producer parent, mlt_position in, mlt_position out) { // Unlike mlt_producer_set_in_and_out(), a link does not do bounds checking against length if (parent && parent->child) { mlt_link self = parent->child; mlt_properties properties = MLT_LINK_PROPERTIES(self); mlt_events_block(properties, properties); mlt_properties_set_position(properties, "in", in); mlt_events_unblock(properties, properties); mlt_properties_set_position(properties, "out", out); } return 0; } // Link filter wrapper functions void link_filter_configure(mlt_link self, mlt_profile profile) { // Operate at the same frame rate as the next link if (self) { mlt_service_set_profile(MLT_LINK_SERVICE(self), mlt_service_profile(MLT_PRODUCER_SERVICE(self->next))); if (self->child) { mlt_service_set_profile(MLT_SERVICE(self->child), mlt_service_profile(MLT_PRODUCER_SERVICE(self->next))); } } } void link_filter_close(mlt_link self) { if (self) { mlt_filter_close((mlt_filter) self->child); self->close = NULL; self->child = NULL; mlt_link_close(self); free(self); } } int link_filter_get_frame(mlt_link self, mlt_frame_ptr frame, int index) { int error = 1; if (self && self->child) { // Get the frame from the next link and apply the filter to it. mlt_producer_seek(self->next, mlt_producer_position(MLT_LINK_PRODUCER(self))); error = mlt_service_get_frame(MLT_PRODUCER_SERVICE(self->next), frame, index); mlt_producer_prepare_next(MLT_LINK_PRODUCER(self)); mlt_filter_process((mlt_filter) self->child, *frame); } return error; } /** Construct a link as a wrapper for the specified filter * * The returned link will be the owner of the supplied filter * * \public \memberof mlt_link_s * \return the new link */ mlt_link mlt_link_filter_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_link self = mlt_link_init(); mlt_filter filter = mlt_factory_filter(profile, id, arg); if (self && filter) { self->child = filter; // Callback registration self->close = link_filter_close; self->configure = link_filter_configure; self->get_frame = link_filter_get_frame; } else { mlt_link_close(self); self = NULL; mlt_filter_close(filter); } return self; } /** Get the metadata about a link that is wrapping a filter. * * Returns NULL if link or its metadata are unavailable. * * \public \memberof mlt_link_s * \param type this must be mlt_service_type_link * \param service the name of the filter that this link is wrapping * \return the service metadata as a structured properties list */ extern mlt_properties mlt_link_filter_metadata(mlt_service_type type, const char *id, void *data) { mlt_repository repository = mlt_factory_repository(); mlt_properties filter_metadata = mlt_repository_metadata(repository, mlt_service_filter_type, id); mlt_properties_set(filter_metadata, "type", "link"); return filter_metadata; } mlt-7.22.0/src/framework/mlt_link.h000664 000000 000000 00000005065 14531534050 017132 0ustar00rootroot000000 000000 /** * \file mlt_link.h * \brief link service class * \see mlt_link_s * * Copyright (C) 2020 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef MLT_LINK_H #define MLT_LINK_H #include "mlt_producer.h" /** \brief Link class * * The link is a producer class that that can be connected to other link producers in a Chain. * * \extends mlt_producer_s * \properties \em next holds a reference to the next producer in the chain */ struct mlt_link_s { /** \publicsection */ struct mlt_producer_s parent; /** \protectedsection */ /** Get a frame of data (virtual function). * * \param mlt_link a link * \param mlt_frame_ptr a frame pointer by reference * \param int an index * \return true if there was an error */ int (*get_frame)(mlt_link, mlt_frame_ptr, int); /** Configure the link (virtual function). * * \param mlt_link a link * \param mlt_profile a default profile to use */ void (*configure)(mlt_link, mlt_profile); /** Virtual close function */ void (*close)(mlt_link); /** \privatesection */ mlt_producer next; /** the object of a subclass */ void *child; }; #define MLT_LINK_PRODUCER(link) (&(link)->parent) #define MLT_LINK_SERVICE(link) MLT_PRODUCER_SERVICE(MLT_LINK_PRODUCER(link)) #define MLT_LINK_PROPERTIES(link) MLT_SERVICE_PROPERTIES(MLT_LINK_SERVICE(link)) extern mlt_link mlt_link_init(); extern int mlt_link_connect_next(mlt_link self, mlt_producer next, mlt_profile chain_profile); extern void mlt_link_close(mlt_link self); // Link filter wrapper functions extern mlt_link mlt_link_filter_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_properties mlt_link_filter_metadata(mlt_service_type type, const char *id, void *data); #endif mlt-7.22.0/src/framework/mlt_log.c000664 000000 000000 00000006231 14531534050 016745 0ustar00rootroot000000 000000 /** * \file mlt_log.c * \brief logging functions * * Copyright (C) 2004-2014 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "mlt_log.h" #include "mlt_service.h" #include #ifndef NDEBUG #include #include #endif static int log_level = MLT_LOG_WARNING; void default_callback(void *ptr, int level, const char *fmt, va_list vl) { static int print_prefix = 1; mlt_properties properties = ptr ? MLT_SERVICE_PROPERTIES((mlt_service) ptr) : NULL; if (level > log_level) return; #ifndef NDEBUG if (print_prefix && level >= MLT_LOG_TIMINGS) { struct timeval tv; time_t ltime; struct tm *rtime; char buf[32]; gettimeofday(&tv, NULL); ltime = tv.tv_sec; rtime = localtime(<ime); strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", rtime); fprintf(stderr, "| %s.%.3d | ", buf, (int) (tv.tv_usec / 1000)); } #endif if (print_prefix && properties) { char *mlt_type = mlt_properties_get(properties, "mlt_type"); char *mlt_service = mlt_properties_get(properties, "mlt_service"); char *resource = mlt_properties_get(properties, "resource"); if (!(resource && *resource && resource[0] == '<' && resource[strlen(resource) - 1] == '>')) mlt_type = mlt_properties_get(properties, "mlt_type"); if (mlt_service) fprintf(stderr, "[%s %s] ", mlt_type, mlt_service); else fprintf(stderr, "[%s %p] ", mlt_type, ptr); if (resource) fprintf(stderr, "%s\n ", resource); } print_prefix = strstr(fmt, "\n") != NULL; vfprintf(stderr, fmt, vl); } static void (*callback)(void *, int, const char *, va_list) = default_callback; void mlt_log(void *service, int level, const char *fmt, ...) { va_list vl; va_start(vl, fmt); mlt_vlog(service, level, fmt, vl); va_end(vl); } void mlt_vlog(void *service, int level, const char *fmt, va_list vl) { if (callback) callback(service, level, fmt, vl); } int mlt_log_get_level(void) { return log_level; } void mlt_log_set_level(int level) { log_level = level; } void mlt_log_set_callback(void (*new_callback)(void *, int, const char *, va_list)) { callback = new_callback; } int64_t mlt_log_timings_now(void) { int64_t r = 0; #ifndef NDEBUG struct timeval tv; gettimeofday(&tv, NULL); r = tv.tv_sec; r *= 1000000LL; r += tv.tv_usec; #endif return r; } mlt-7.22.0/src/framework/mlt_log.h000664 000000 000000 00000007620 14531534050 016755 0ustar00rootroot000000 000000 /** * \file mlt_log.h * \brief logging functions * * Copyright (C) 2004-2014 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef MLT_LOG_H #define MLT_LOG_H #include #include #define MLT_LOG_QUIET -8 /** * something went really wrong and we will crash now */ #define MLT_LOG_PANIC 0 /** * something went wrong and recovery is not possible * like no header in a format which depends on it or a combination * of parameters which are not allowed */ #define MLT_LOG_FATAL 8 /** * something went wrong and cannot losslessly be recovered * but not all future data is affected */ #define MLT_LOG_ERROR 16 /** * something somehow does not look correct / something which may or may not * lead to some problems */ #define MLT_LOG_WARNING 24 #define MLT_LOG_INFO 32 #define MLT_LOG_VERBOSE 40 #define MLT_LOG_TIMINGS 44 /** * stuff which is only useful for MLT developers */ #define MLT_LOG_DEBUG 48 /** * Send the specified message to the log if the level is less than or equal to * the current logging level. By default, all logging messages are sent to * stderr. This behavior can be altered by setting a different mlt_vlog callback * function. * * \param service An optional pointer to a \p mlt_service_s. * \param level The importance level of the message, lower values signifying * higher importance. * \param fmt The format string (printf-compatible) that specifies how * subsequent arguments are converted to output. * \see mlt_vlog */ #ifdef __GNUC__ void mlt_log(void *service, int level, const char *fmt, ...) __attribute__((__format__(__printf__, 3, 4))); #else void mlt_log(void *service, int level, const char *fmt, ...); #endif #define mlt_log_panic(service, format, args...) mlt_log((service), MLT_LOG_PANIC, (format), ##args) #define mlt_log_fatal(service, format, args...) mlt_log((service), MLT_LOG_FATAL, (format), ##args) #define mlt_log_error(service, format, args...) mlt_log((service), MLT_LOG_ERROR, (format), ##args) #define mlt_log_warning(service, format, args...) \ mlt_log((service), MLT_LOG_WARNING, (format), ##args) #define mlt_log_info(service, format, args...) mlt_log((service), MLT_LOG_INFO, (format), ##args) #define mlt_log_verbose(service, format, args...) \ mlt_log((service), MLT_LOG_VERBOSE, (format), ##args) #define mlt_log_timings(service, format, args...) \ mlt_log((service), MLT_LOG_TIMINGS, (format), ##args) #define mlt_log_debug(service, format, args...) mlt_log((service), MLT_LOG_DEBUG, (format), ##args) void mlt_vlog(void *service, int level, const char *fmt, va_list); int mlt_log_get_level(void); void mlt_log_set_level(int); void mlt_log_set_callback(void (*)(void *, int, const char *, va_list)); #define mlt_log_timings_begin() \ { \ int64_t _mlt_log_timings_begin = mlt_log_timings_now(), _mlt_log_timings_end; #define mlt_log_timings_end(service, msg) \ _mlt_log_timings_end = mlt_log_timings_now(); \ mlt_log_timings(service, \ "%s:%d: T(%s)=%" PRId64 " us\n", \ __FILE__, \ __LINE__, \ msg, \ _mlt_log_timings_end - _mlt_log_timings_begin); \ } int64_t mlt_log_timings_now(void); #endif /* MLT_LOG_H */ mlt-7.22.0/src/framework/mlt_luma_map.c000664 000000 000000 00000033340 14531534050 017760 0ustar00rootroot000000 000000 /** * \file mlt_luma_map.c * \brief functions to generate and read luma-wipe transition maps * * Copyright (C) 2003-2019 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "mlt_luma_map.h" #include "mlt_pool.h" #include "mlt_types.h" #include #include #include #define HALF_USHRT_MAX (1 << 15) void mlt_luma_map_init(mlt_luma_map self) { memset(self, 0, sizeof(struct mlt_luma_map_s)); self->type = 0; self->w = 720; self->h = 576; self->bands = 1; self->rband = 0; self->vmirror = 0; self->hmirror = 0; self->dmirror = 0; self->invert = 0; self->offset = 0; self->flip = 0; self->flop = 0; self->quart = 0; self->pflop = 0; self->pflip = 0; } static inline int sqrti(int n) { int p = 0; int q = 1; int r = n; int h = 0; while (q <= n) q = 4 * q; while (q != 1) { q = q / 4; h = p + q; p = p / 2; if (r >= h) { p = p + q; r = r - h; } } return p; } uint16_t *mlt_luma_map_render(mlt_luma_map self) { int i = 0; int j = 0; int k = 0; if (self->quart) { self->w *= 2; self->h *= 2; } if (self->rotate) { int t = self->w; self->w = self->h; self->h = t; } if (self->bands < 0) self->bands = self->h; int max = (1 << 16) - 1; uint16_t *image = mlt_pool_alloc(self->w * self->h * sizeof(uint16_t)); uint16_t *end = image + self->w * self->h; uint16_t *p = image; uint16_t *r = image; int lower = 0; int lpb = self->h / self->bands; int rpb = max / self->bands; int direction = 1; int half_w = self->w / 2; int half_h = self->h / 2; if (!self->dmirror && (self->hmirror || self->vmirror)) rpb *= 2; for (i = 0; i < self->bands; i++) { lower = i * rpb; direction = 1; if (self->rband && i % 2 == 1) { direction = -1; lower += rpb; } switch (self->type) { case 1: { int length = sqrti(half_w * half_w + lpb * lpb / 4); int value; int x = 0; int y = 0; for (j = 0; j < lpb; j++) { y = j - lpb / 2; for (k = 0; k < self->w; k++) { x = k - half_w; value = sqrti(x * x + y * y); *p++ = lower + (direction * rpb * ((max * value) / length) / max) + (j * self->offset * 2 / lpb) + (j * self->offset / lpb); } } } break; case 2: { for (j = 0; j < lpb; j++) { int value = ((j * self->w) / lpb) - half_w; if (value > 0) value = -value; for (k = -half_w; k < value; k++) *p++ = lower + (direction * rpb * ((max * abs(k)) / half_w) / max); for (k = value; k < abs(value); k++) *p++ = lower + (direction * rpb * ((max * abs(value)) / half_w) / max) + (j * self->offset * 2 / lpb) + (j * self->offset / lpb); for (k = abs(value); k < half_w; k++) *p++ = lower + (direction * rpb * ((max * abs(k)) / half_w) / max); } } break; case 3: { int length; for (j = -half_h; j < half_h; j++) { if (j < 0) { for (k = -half_w; k < half_w; k++) { length = sqrti(k * k + j * j); *p++ = (max / 4 * k) / (length + 1); } } else { for (k = half_w; k > -half_w; k--) { length = sqrti(k * k + j * j); *p++ = (max / 2) + (max / 4 * k) / (length + 1); } } } } break; default: for (j = 0; j < lpb; j++) for (k = 0; k < self->w; k++) *p++ = lower + (direction * (rpb * ((k * max) / self->w) / max)) + (j * self->offset * 2 / lpb); break; } } if (self->quart) { self->w /= 2; self->h /= 2; for (i = 1; i < self->h; i++) { p = image + i * self->w; r = image + i * 2 * self->w; j = self->w; while (j-- > 0) *p++ = *r++; } } if (self->dmirror) { for (i = 0; i < self->h; i++) { p = image + i * self->w; r = end - i * self->w; j = (self->w * (self->h - i)) / self->h; while (j--) *(--r) = *p++; } } if (self->flip) { uint16_t t; for (i = 0; i < self->h; i++) { p = image + i * self->w; r = p + self->w; while (p != r) { t = *p; *p++ = *(--r); *r = t; } } } if (self->flop) { uint16_t t; r = end; for (i = 1; i < self->h / 2; i++) { p = image + i * self->w; j = self->w; while (j--) { t = *(--p); *p = *(--r); *r = t; } } } if (self->hmirror) { p = image; while (p < end) { r = p + self->w; while (p != r) *(--r) = *p++; p += self->w / 2; } } if (self->vmirror) { p = image; r = end; while (p != r) *(--r) = *p++; } if (self->invert) { p = image; r = image; while (p < end) *p++ = max - *r++; } if (self->pflip) { uint16_t t; for (i = 0; i < self->h; i++) { p = image + i * self->w; r = p + self->w; while (p != r) { t = *p; *p++ = *(--r); *r = t; } } } if (self->pflop) { uint16_t t; end = image + self->w * self->h; r = end; for (i = 1; i < self->h / 2; i++) { p = image + i * self->w; j = self->w; while (j--) { t = *(--p); *p = *(--r); *r = t; } } } if (self->rotate) { uint16_t *image2 = mlt_pool_alloc(self->w * self->h * sizeof(uint16_t)); for (i = 0; i < self->h; i++) { p = image + i * self->w; r = image2 + self->h - i - 1; for (j = 0; j < self->w; j++) { *r = *(p++); r += self->h; } } i = self->w; self->w = self->h; self->h = i; mlt_pool_release(image); image = image2; } return image; } /** Load the luma map from PGM stream. */ int mlt_luma_map_from_pgm(const char *filename, uint16_t **map, int *width, int *height) { uint8_t *data = NULL; FILE *f = mlt_fopen(filename, "rb"); int error = f == NULL; while (!error) { char line[128]; char comment[128]; int i = 2; int maxval; int bpp; uint16_t *p; line[127] = '\0'; // get the magic code if (fgets(line, 127, f) == NULL) break; // skip comments while (sscanf(line, " #%s", comment) > 0) if (fgets(line, 127, f) == NULL) break; if (line[0] != 'P' || line[1] != '5') break; // skip white space and see if a new line must be fetched for (i = 2; i < 127 && line[i] != '\0' && isspace(line[i]); i++) ; if ((line[i] == '\0' || line[i] == '#') && fgets(line, 127, f) == NULL) break; // skip comments while (sscanf(line, " #%s", comment) > 0) if (fgets(line, 127, f) == NULL) break; // get the dimensions if (line[0] == 'P') i = sscanf(line, "P5 %d %d %d", width, height, &maxval); else i = sscanf(line, "%d %d %d", width, height, &maxval); // get the height value, if not yet if (i < 2) { if (fgets(line, 127, f) == NULL) break; // skip comments while (sscanf(line, " #%s", comment) > 0) if (fgets(line, 127, f) == NULL) break; i = sscanf(line, "%d", height); if (i == 0) break; else i = 2; } // get the maximum gray value, if not yet if (i < 3) { if (fgets(line, 127, f) == NULL) break; // skip comments while (sscanf(line, " #%s", comment) > 0) if (fgets(line, 127, f) == NULL) break; i = sscanf(line, "%d", &maxval); if (i == 0) break; } // determine if this is one or two bytes per pixel bpp = maxval > 255 ? 2 : 1; // allocate temporary storage for the raw data data = mlt_pool_alloc(*width * *height * bpp); if (!data) { error = 1; break; } // read the raw data if (fread(data, *width * *height * bpp, 1, f) != 1) break; // allocate the luma bitmap *map = p = (uint16_t *) mlt_pool_alloc(*width * *height * sizeof(uint16_t)); if (!*map) { error = 1; break; } // process the raw data into the luma bitmap for (i = 0; i < *width * *height * bpp; i += bpp) { if (bpp == 1) *p++ = data[i] << 8; else *p++ = (data[i] << 8) + data[i + 1]; } break; } if (f) fclose(f); mlt_pool_release(data); return error; } mlt_luma_map mlt_luma_map_new(const char *path) { mlt_luma_map self = malloc(sizeof(struct mlt_luma_map_s)); if (self) { mlt_luma_map_init(self); if (strstr(path, "luma02.pgm")) { self->bands = -1; // use height } else if (strstr(path, "luma03.pgm")) { self->hmirror = 1; } else if (strstr(path, "luma04.pgm")) { self->bands = -1; // use height self->vmirror = 1; } else if (strstr(path, "luma05.pgm")) { self->offset = HALF_USHRT_MAX; self->dmirror = 1; } else if (strstr(path, "luma06.pgm")) { self->offset = HALF_USHRT_MAX; self->dmirror = 1; self->flip = 1; } else if (strstr(path, "luma07.pgm")) { self->offset = HALF_USHRT_MAX; self->dmirror = 1; self->quart = 1; } else if (strstr(path, "luma08.pgm")) { self->offset = HALF_USHRT_MAX; self->dmirror = 1; self->quart = 1; self->flip = 1; } else if (strstr(path, "luma09.pgm")) { self->bands = 12; } else if (strstr(path, "luma10.pgm")) { self->bands = 12; self->rotate = 1; } else if (strstr(path, "luma11.pgm")) { self->bands = 12; self->rband = 1; } else if (strstr(path, "luma12.pgm")) { self->bands = 12; self->rband = 1; self->vmirror = 1; } else if (strstr(path, "luma13.pgm")) { self->bands = 12; self->rband = 1; self->rotate = 1; self->flop = 1; } else if (strstr(path, "luma14.pgm")) { self->bands = 12; self->rband = 1; self->rotate = 1; self->vmirror = 1; } else if (strstr(path, "luma15.pgm")) { self->offset = HALF_USHRT_MAX; self->dmirror = 1; self->hmirror = 1; } else if (strstr(path, "luma16.pgm")) { self->type = 1; } else if (strstr(path, "luma17.pgm")) { self->type = 1; self->bands = 2; self->rband = 1; } else if (strstr(path, "luma18.pgm")) { self->type = 2; } else if (strstr(path, "luma19.pgm")) { self->type = 2; self->quart = 1; } else if (strstr(path, "luma20.pgm")) { self->type = 2; self->quart = 1; self->flip = 1; } else if (strstr(path, "luma21.pgm")) { self->type = 2; self->quart = 1; self->bands = 2; } else if (strstr(path, "luma22.pgm")) { self->type = 3; } } return self; } /** Generate a 16-bit luma map from an 8-bit image. */ void mlt_luma_map_from_yuv422(uint8_t *image, uint16_t **map, int width, int height) { int i; int size = width * height * 2; // allocate the luma bitmap uint16_t *p = *map = (uint16_t *) mlt_pool_alloc(width * height * sizeof(uint16_t)); if (*map == NULL) return; // process the image data into the luma bitmap for (i = 0; i < size; i += 2) *p++ = (image[i] - 16) * 299; // 299 = 65535 / 219 } mlt-7.22.0/src/framework/mlt_luma_map.h000664 000000 000000 00000003265 14531534050 017770 0ustar00rootroot000000 000000 /* * \file mlt_luma_map.h * \brief functions to generate and read luma-wipe transition maps * * Copyright (C) 2003-2019 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef MLT_LUMA_MAP_H #define MLT_LUMA_MAP_H #include #include #ifdef __cplusplus extern "C" { #endif struct mlt_luma_map_s { int type; int w; int h; int bands; int rband; int vmirror; int hmirror; int dmirror; int invert; int offset; int flip; int flop; int pflip; int pflop; int quart; int rotate; }; typedef struct mlt_luma_map_s *mlt_luma_map; extern void mlt_luma_map_init(mlt_luma_map self); extern mlt_luma_map mlt_luma_map_new(const char *path); extern uint16_t *mlt_luma_map_render(mlt_luma_map self); extern int mlt_luma_map_from_pgm(const char *filename, uint16_t **map, int *width, int *height); extern void mlt_luma_map_from_yuv422(uint8_t *image, uint16_t **map, int width, int height); #ifdef __cplusplus } #endif #endif mlt-7.22.0/src/framework/mlt_multitrack.c000664 000000 000000 00000051124 14531534050 020344 0ustar00rootroot000000 000000 /** * \file mlt_multitrack.c * \brief multitrack service class * \see mlt_multitrack_s * * Copyright (C) 2003-2020 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "mlt_multitrack.h" #include "mlt_cache.h" #include "mlt_factory.h" #include "mlt_frame.h" #include "mlt_playlist.h" #include #include #include /* Forward reference. */ static int producer_get_frame(mlt_producer producer, mlt_frame_ptr frame, int index); /** Construct and initialize a new multitrack. * * Sets the resource property to "". * * \public \memberof mlt_multitrack_s * \return a new multitrack */ mlt_multitrack mlt_multitrack_init() { // Allocate the multitrack object mlt_multitrack self = calloc(1, sizeof(struct mlt_multitrack_s)); if (self != NULL) { mlt_producer producer = &self->parent; if (mlt_producer_init(producer, self) == 0) { mlt_properties properties = MLT_MULTITRACK_PROPERTIES(self); producer->get_frame = producer_get_frame; mlt_properties_set_data(properties, "multitrack", self, 0, NULL, NULL); mlt_properties_set(properties, "log_id", "multitrack"); mlt_properties_set(properties, "resource", ""); mlt_properties_set_int(properties, "in", 0); mlt_properties_set_int(properties, "out", -1); mlt_properties_set_int(properties, "length", 0); producer->close = (mlt_destructor) mlt_multitrack_close; } else { free(self); self = NULL; } } return self; } /** Get the producer associated to this multitrack. * * \public \memberof mlt_multitrack_s * \param self a multitrack * \return the producer object * \see MLT_MULTITRACK_PRODUCER */ mlt_producer mlt_multitrack_producer(mlt_multitrack self) { return self != NULL ? &self->parent : NULL; } /** Get the service associated this multitrack. * * \public \memberof mlt_multitrack_s * \param self a multitrack * \return the service object * \see MLT_MULTITRACK_SERVICE */ mlt_service mlt_multitrack_service(mlt_multitrack self) { return MLT_MULTITRACK_SERVICE(self); } /** Get the properties associated this multitrack. * * \public \memberof mlt_multitrack_s * \param self a multitrack * \return the multitrack's property list * \see MLT_MULTITRACK_PROPERTIES */ mlt_properties mlt_multitrack_properties(mlt_multitrack self) { return MLT_MULTITRACK_PROPERTIES(self); } /** Initialize position related information. * * \public \memberof mlt_multitrack_s * \param self a multitrack */ void mlt_multitrack_refresh(mlt_multitrack self) { int i = 0; // Obtain the properties of this multitrack mlt_properties properties = MLT_MULTITRACK_PROPERTIES(self); // We need to ensure that the multitrack reports the longest track as its length mlt_position length = 0; // Obtain stats on all connected services for (i = 0; i < self->count; i++) { // Get the producer from this index mlt_track track = self->list[i]; mlt_producer producer = track->producer; // If it's allocated then, update our stats if (producer != NULL) { // If we have more than 1 track, we must be in continue mode if (self->count > 1) mlt_properties_set(MLT_PRODUCER_PROPERTIES(producer), "eof", "continue"); // Determine the longest length //if ( !mlt_properties_get_int( MLT_PRODUCER_PROPERTIES( producer ), "hide" ) ) length = mlt_producer_get_playtime(producer) > length ? mlt_producer_get_playtime(producer) : length; } } // Update multitrack properties now - we'll not destroy the in point here mlt_events_block(properties, properties); mlt_properties_set_position(properties, "length", length); mlt_events_unblock(properties, properties); mlt_properties_set_position(properties, "out", length - 1); } /** Listener for producers on the playlist. * * \private \memberof mlt_multitrack_s * \param producer a producer * \param self a multitrack */ static void mlt_multitrack_listener(mlt_producer producer, mlt_multitrack self) { mlt_multitrack_refresh(self); } static void resize_service_caches(mlt_multitrack self) { mlt_properties caches = mlt_properties_get_data(mlt_global_properties(), "caches", NULL); if (caches) { int i; for (i = 0; i < mlt_properties_count(caches); ++i) { mlt_cache cache = mlt_properties_get_data_at(caches, i, NULL); if (self->count * 2 > mlt_cache_get_size(cache)) mlt_cache_set_size(cache, self->count * 2); } } } /** Connect a producer to a given track. * * Note that any producer can be connected here, but see special case treatment * of playlist in clip point determination below. * * \public \memberof mlt_multitrack_s * \param self a multitrack * \param producer the producer to connect to the multitrack producer * \param track the 0-based index of the track on which to connect the multitrack * \return true on error */ int mlt_multitrack_connect(mlt_multitrack self, mlt_producer producer, int track) { // Connect to the producer to ourselves at the specified track int result = mlt_service_connect_producer(MLT_MULTITRACK_SERVICE(self), MLT_PRODUCER_SERVICE(producer), track); if (result == 0) { mlt_track current_track = (track < self->count) ? self->list[track] : NULL; // Resize the producer list if need be if (track >= self->size) { int i; self->list = realloc(self->list, (track + 10) * sizeof(mlt_track)); for (i = self->size; i < track + 10; i++) self->list[i] = NULL; self->size = track + 10; } if (current_track) { mlt_event_close(current_track->event); mlt_producer_close(current_track->producer); } else { self->list[track] = malloc(sizeof(struct mlt_track_s)); } // Assign the track in our list here self->list[track]->producer = producer; self->list[track]->event = mlt_events_listen(MLT_PRODUCER_PROPERTIES(producer), self, "producer-changed", (mlt_listener) mlt_multitrack_listener); mlt_properties_inc_ref(MLT_PRODUCER_PROPERTIES(producer)); mlt_event_inc_ref(self->list[track]->event); // Increment the track count if need be if (track >= self->count) { self->count = track + 1; resize_service_caches(self); } // Refresh our stats mlt_multitrack_refresh(self); } return result; } /** Insert a producer to a given track. * * \public \memberof mlt_multitrack_s * \param self a multitrack * \param producer the producer to connect to the multitrack producer * \param track the 0-based index of the track on which to connect the multitrack * \return true on error */ int mlt_multitrack_insert(mlt_multitrack self, mlt_producer producer, int track) { if (track >= self->count) return mlt_multitrack_connect(self, producer, track); // Connect to the producer to ourselves at the specified track int result = mlt_service_insert_producer(MLT_MULTITRACK_SERVICE(self), MLT_PRODUCER_SERVICE(producer), track); if (result == 0) { // Resize the producer list if needed. if (self->count + 1 > self->size) { int new_size = self->size + 10; self->list = realloc(self->list, new_size * sizeof(mlt_track)); if (self->list) { memset(&self->list[self->size], 0, new_size - self->size); self->size = new_size; } } if (self->list) { // Move all of the list elements following track N down by 1. memmove(&self->list[track + 1], &self->list[track], (self->count - track) * sizeof(mlt_track)); self->count++; resize_service_caches(self); // Assign the track in our list. self->list[track] = malloc(sizeof(struct mlt_track_s)); self->list[track]->producer = producer; self->list[track]->event = mlt_events_listen(MLT_PRODUCER_PROPERTIES(producer), self, "producer-changed", (mlt_listener) mlt_multitrack_listener); mlt_properties_inc_ref(MLT_PRODUCER_PROPERTIES(producer)); mlt_event_inc_ref(self->list[track]->event); // Refresh our stats mlt_multitrack_refresh(self); } else { result = -1; } } return result; } /** Remove the N-th track. * * \public \memberof mlt_multitrack_s * \param self a multitrack * \param track the index of the track to remove * \return true if there was an error */ int mlt_multitrack_disconnect(mlt_multitrack self, int track) { int error = -1; if (self && self->list && track >= 0 && track < self->count) { // Disconnect the track producer. error = mlt_service_disconnect_producer(MLT_MULTITRACK_SERVICE(self), track); if (!error) { // Release references on track. if (self->list[track]) { mlt_producer_close(self->list[track]->producer); mlt_event_close(self->list[track]->event); } // Contract the list of tracks. for (; track + 1 < self->count; track++) { if (self->list[track] && self->list[track + 1]) { self->list[track]->producer = self->list[track + 1]->producer; self->list[track]->event = self->list[track + 1]->event; } } if (self->list[self->count - 1]) { free(self->list[self->count - 1]); self->list[self->count - 1] = NULL; } self->count--; // Recalculate the duration. mlt_multitrack_refresh(self); } } return error; } /** Get the number of tracks. * * \public \memberof mlt_multitrack_s * \param self a multitrack * \return the number of tracks */ int mlt_multitrack_count(mlt_multitrack self) { if (self == NULL) return 0; else return self->count; } /** Get an individual track as a producer. * * \public \memberof mlt_multitrack_s * \param self a multitrack * \param track the 0-based index of the producer to get * \return the producer or NULL if not valid */ mlt_producer mlt_multitrack_track(mlt_multitrack self, int track) { mlt_producer producer = NULL; if (self->list != NULL && track >= 0 && track < self->count) producer = self->list[track]->producer; return producer; } /** Position comparison function for sorting. * * \private \memberof mlt_multitrack_s * \param p1 a position * \param p2 another position * \return <0 if \p p1 is less than \p p2, 0 if equal, >0 if greater */ static int position_compare(const void *p1, const void *p2) { return *(const mlt_position *) p1 - *(const mlt_position *) p2; } /** Add a position to a set. * * \private \memberof mlt_multitrack_s * \param array an array of positions (the set) * \param size the current number of positions in the array (not the capacity of the array) * \param position the position to add * \return the new size of the array */ static int add_unique(mlt_position *array, int size, mlt_position position) { int i = 0; for (i = 0; i < size; i++) if (array[i] == position) break; if (i == size) array[size++] = position; return size; } /** Increase the capacity of a set of mlt_position. * * \private \memberof mlt_multitrack_s * \param array an array of positions (the set) * \param count the current number of elements in the array (not the capacity) * \param[out] size the current capacity of the array * \return the new address of the array */ static mlt_position *resize_set(mlt_position *map, int count, int *size) { // Resize only if needed. if (count + 1 >= *size) { map = realloc(map, (*size + 1000) * sizeof(*map)); memset(map + *size, 0, 1000 * sizeof(*map)); *size += 1000; } return map; } /** Determine the clip point. * *
 * Special case here: a 'producer' has no concept of multiple clips - only the
 * playlist and multitrack producers have clip functionality. Further to that a
 * multitrack determines clip information from any connected tracks that happen
 * to be playlists.
 *
 * Additionally, it must locate clips in the correct order, for example, consider
 * the following track arrangement:
 *
 * playlist1 |0.0     |b0.0      |0.1          |0.1         |0.2           |
 * playlist2 |b1.0  |1.0           |b1.1     |1.1             |
 *
 * Note - b clips represent blanks. They are also reported as clip positions.
 *
 * When extracting clip positions from these playlists, we should get a sequence of:
 *
 * 0.0, 1.0, b0.0, 0.1, b1.1, 1.1, 0.1, 0.2, [out of playlist2], [out of playlist1]
 * 
* * \public \memberof mlt_multitrack_s * \param self a multitrack * \param whence from where to extract * \param index the 0-based index of which clip to extract * \return the position of clip \p index relative to \p whence */ mlt_position mlt_multitrack_clip(mlt_multitrack self, mlt_whence whence, int index) { mlt_position position = 0; int i = 0; int j = 0; int size = 1000; mlt_position *map = calloc(size, sizeof(*map)); int count = 0; for (i = 0; i < self->count; i++) { // Get the producer for this track mlt_producer producer = self->list[i]->producer; // If it's assigned and not a hidden track if (producer != NULL) { // Get the properties of this producer mlt_properties properties = MLT_PRODUCER_PROPERTIES(producer); // Determine if it's a playlist mlt_playlist playlist = mlt_properties_get_data(properties, "playlist", NULL); map = resize_set(map, count, &size); // Special case consideration of playlists if (playlist != NULL) { for (j = 0; j < mlt_playlist_count(playlist); j++) { count = add_unique(map, count, mlt_playlist_clip(playlist, mlt_whence_relative_start, j)); map = resize_set(map, count, &size); } count = add_unique(map, count, mlt_producer_get_out(producer) + 1); } else { count = add_unique(map, count, 0); count = add_unique(map, count, mlt_producer_get_out(producer) + 1); } } } // Now sort the map qsort(map, count, sizeof(mlt_position), position_compare); // Now locate the requested index switch (whence) { case mlt_whence_relative_start: if (index < count) position = map[index]; else position = map[count - 1]; break; case mlt_whence_relative_current: position = mlt_producer_position(MLT_MULTITRACK_PRODUCER(self)); for (i = 0; i < count - 2; i++) if (position >= map[i] && position < map[i + 1]) break; index += i; if (index >= 0 && index < count) position = map[index]; else if (index < 0) position = map[0]; else position = map[count - 1]; break; case mlt_whence_relative_end: if (index < count) position = map[count - index - 1]; else position = map[0]; break; } // Free the map free(map); return position; } /** Get frame method. * *
 * Special case here: The multitrack must be used in a conjunction with a downstream
 * tractor-type service, ie:
 *
 * Producer1 \
 * Producer2 - multitrack - { filters/transitions } - tractor - consumer
 * Producer3 /
 *
 * The get_frame of a tractor pulls frames from it's connected service on all tracks and
 * will terminate as soon as it receives a test card with a last_track property. The
 * important case here is that the mulitrack does not move to the next frame until all
 * tracks have been pulled.
 *
 * Reasoning: In order to seek on a network such as above, the multitrack needs to ensure
 * that all producers are positioned on the same frame. It uses the 'last track' logic
 * to determine when to move to the next frame.
 *
 * Flaw: if a transition is configured to read from a b-track which happens to trigger
 * the last frame logic (ie: it's configured incorrectly), then things are going to go
 * out of sync.
 *
 * See playlist logic too.
 * 
* * \private \memberof mlt_multitrack_s * \param parent the producer interface to a mulitrack * \param[out] frame a frame by reference * \param index the 0-based track index * \return true if there was an error */ static int producer_get_frame(mlt_producer parent, mlt_frame_ptr frame, int index) { // Get the mutiltrack object mlt_multitrack self = parent->child; // Check if we have a track for this index if (index >= 0 && index < self->count && self->list[index] != NULL) { // Get the producer for this track mlt_producer producer = self->list[index]->producer; // Get the track hide property int hide = mlt_properties_get_int(MLT_PRODUCER_PROPERTIES(mlt_producer_cut_parent(producer)), "hide"); // Obtain the current position mlt_position position = mlt_producer_frame(parent); // Get the parent properties mlt_properties producer_properties = MLT_PRODUCER_PROPERTIES(parent); // Get the speed double speed = mlt_properties_get_double(producer_properties, "_speed"); // Make sure we're at the same point mlt_producer_seek(producer, position); // Get the frame from the producer mlt_service_get_frame(MLT_PRODUCER_SERVICE(producer), frame, 0); // Indicate speed of this producer mlt_properties properties = MLT_FRAME_PROPERTIES(*frame); mlt_properties_set_double(properties, "_speed", speed); mlt_frame_set_position(*frame, position); mlt_properties_set_int(properties, "hide", hide); } else { // Generate a test frame *frame = mlt_frame_init(MLT_PRODUCER_SERVICE(parent)); // Update position on the frame we're creating mlt_frame_set_position(*frame, mlt_producer_position(parent)); // Move on to the next frame if (index >= self->count) { // Let tractor know if we've reached the end mlt_properties_set_int(MLT_FRAME_PROPERTIES(*frame), "last_track", 1); // Move to the next frame mlt_producer_prepare_next(parent); } } return 0; } /** Close this instance and free its resources. * * \public \memberof mlt_multitrack_s * \param self a multitrack */ void mlt_multitrack_close(mlt_multitrack self) { if (self != NULL && mlt_properties_dec_ref(MLT_MULTITRACK_PROPERTIES(self)) <= 0) { int i = 0; for (i = 0; i < self->count; i++) { if (self->list[i] != NULL) { mlt_event_close(self->list[i]->event); mlt_producer_close(self->list[i]->producer); free(self->list[i]); } } // Close the producer self->parent.close = NULL; mlt_producer_close(&self->parent); // Free the list free(self->list); // Free the object free(self); } } mlt-7.22.0/src/framework/mlt_multitrack.h000664 000000 000000 00000005071 14531534050 020351 0ustar00rootroot000000 000000 /** * \file mlt_multitrack.h * \brief multitrack service class * \see mlt_multitrack_s * * Copyright (C) 2003-2015 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef MLT_MULITRACK_H #define MLT_MULITRACK_H #include "mlt_producer.h" /** \brief Track class used by mlt_multitrack_s */ struct mlt_track_s { mlt_producer producer; mlt_event event; }; typedef struct mlt_track_s *mlt_track; /** \brief Multitrack class * * A multitrack is a parallel container of producers that acts a single producer. * * \extends mlt_producer_s * \properties \em log_id not currently used, but sets it to "mulitrack" */ struct mlt_multitrack_s { /** We're extending producer here */ struct mlt_producer_s parent; mlt_track *list; int size; int count; }; #define MLT_MULTITRACK_PRODUCER(multitrack) (&(multitrack)->parent) #define MLT_MULTITRACK_SERVICE(multitrack) MLT_PRODUCER_SERVICE(MLT_MULTITRACK_PRODUCER(multitrack)) #define MLT_MULTITRACK_PROPERTIES(multitrack) \ MLT_SERVICE_PROPERTIES(MLT_MULTITRACK_SERVICE(multitrack)) extern mlt_multitrack mlt_multitrack_init(); extern mlt_producer mlt_multitrack_producer(mlt_multitrack self); extern mlt_service mlt_multitrack_service(mlt_multitrack self); extern mlt_properties mlt_multitrack_properties(mlt_multitrack self); extern int mlt_multitrack_connect(mlt_multitrack self, mlt_producer producer, int track); extern int mlt_multitrack_insert(mlt_multitrack self, mlt_producer producer, int track); extern int mlt_multitrack_disconnect(mlt_multitrack self, int track); extern mlt_position mlt_multitrack_clip(mlt_multitrack self, mlt_whence whence, int index); extern void mlt_multitrack_close(mlt_multitrack self); extern int mlt_multitrack_count(mlt_multitrack self); extern void mlt_multitrack_refresh(mlt_multitrack self); extern mlt_producer mlt_multitrack_track(mlt_multitrack self, int track); #endif mlt-7.22.0/src/framework/mlt_parser.c000664 000000 000000 00000023113 14531534050 017456 0ustar00rootroot000000 000000 /** * \file mlt_parser.c * \brief service parsing functionality * \see mlt_parser_s * * Copyright (C) 2003-2014 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "mlt.h" #include static int on_invalid(mlt_parser self, mlt_service object) { return 0; } static int on_unknown(mlt_parser self, mlt_service object) { return 0; } static int on_start_producer(mlt_parser self, mlt_producer object) { return 0; } static int on_end_producer(mlt_parser self, mlt_producer object) { return 0; } static int on_start_playlist(mlt_parser self, mlt_playlist object) { return 0; } static int on_end_playlist(mlt_parser self, mlt_playlist object) { return 0; } static int on_start_tractor(mlt_parser self, mlt_tractor object) { return 0; } static int on_end_tractor(mlt_parser self, mlt_tractor object) { return 0; } static int on_start_multitrack(mlt_parser self, mlt_multitrack object) { return 0; } static int on_end_multitrack(mlt_parser self, mlt_multitrack object) { return 0; } static int on_start_track(mlt_parser self) { return 0; } static int on_end_track(mlt_parser self) { return 0; } static int on_start_filter(mlt_parser self, mlt_filter object) { return 0; } static int on_end_filter(mlt_parser self, mlt_filter object) { return 0; } static int on_start_transition(mlt_parser self, mlt_transition object) { return 0; } static int on_end_transition(mlt_parser self, mlt_transition object) { return 0; } static int on_start_chain(mlt_parser self, mlt_chain object) { return 0; } static int on_end_chain(mlt_parser self, mlt_chain object) { return 0; } static int on_start_link(mlt_parser self, mlt_link object) { return 0; } static int on_end_link(mlt_parser self, mlt_link object) { return 0; } mlt_parser mlt_parser_new() { mlt_parser self = calloc(1, sizeof(struct mlt_parser_s)); if (self != NULL && mlt_properties_init(&self->parent, self) == 0) { self->on_invalid = on_invalid; self->on_unknown = on_unknown; self->on_start_producer = on_start_producer; self->on_end_producer = on_end_producer; self->on_start_playlist = on_start_playlist; self->on_end_playlist = on_end_playlist; self->on_start_tractor = on_start_tractor; self->on_end_tractor = on_end_tractor; self->on_start_multitrack = on_start_multitrack; self->on_end_multitrack = on_end_multitrack; self->on_start_track = on_start_track; self->on_end_track = on_end_track; self->on_start_filter = on_start_filter; self->on_end_filter = on_end_filter; self->on_start_transition = on_start_transition; self->on_end_transition = on_end_transition; self->on_start_chain = on_start_chain; self->on_end_chain = on_end_chain; self->on_start_link = on_start_link; self->on_end_link = on_end_link; } return self; } mlt_properties mlt_parser_properties(mlt_parser self) { return &self->parent; } int mlt_parser_start(mlt_parser self, mlt_service object) { int error = 0; mlt_service_type type = mlt_service_identify(object); switch (type) { case mlt_service_invalid_type: error = self->on_invalid(self, object); break; case mlt_service_unknown_type: error = self->on_unknown(self, object); break; case mlt_service_producer_type: if (mlt_producer_is_cut((mlt_producer) object)) error = mlt_parser_start(self, (mlt_service) mlt_producer_cut_parent((mlt_producer) object)); error = self->on_start_producer(self, (mlt_producer) object); if (error == 0) { int i = 0; while (error == 0 && mlt_producer_filter((mlt_producer) object, i) != NULL) error = mlt_parser_start(self, (mlt_service) mlt_producer_filter((mlt_producer) object, i++)); } error = self->on_end_producer(self, (mlt_producer) object); break; case mlt_service_playlist_type: error = self->on_start_playlist(self, (mlt_playlist) object); if (error == 0) { int i = 0; while (error == 0 && i < mlt_playlist_count((mlt_playlist) object)) mlt_parser_start(self, (mlt_service) mlt_playlist_get_clip((mlt_playlist) object, i++)); i = 0; while (error == 0 && mlt_producer_filter((mlt_producer) object, i) != NULL) error = mlt_parser_start(self, (mlt_service) mlt_producer_filter((mlt_producer) object, i++)); } error = self->on_end_playlist(self, (mlt_playlist) object); break; case mlt_service_tractor_type: error = self->on_start_tractor(self, (mlt_tractor) object); if (error == 0) { int i = 0; mlt_service next = mlt_service_producer(object); mlt_parser_start(self, (mlt_service) mlt_tractor_multitrack((mlt_tractor) object)); while (next != (mlt_service) mlt_tractor_multitrack((mlt_tractor) object)) { mlt_parser_start(self, next); next = mlt_service_producer(next); } while (error == 0 && mlt_producer_filter((mlt_producer) object, i) != NULL) error = mlt_parser_start(self, (mlt_service) mlt_producer_filter((mlt_producer) object, i++)); } error = self->on_end_tractor(self, (mlt_tractor) object); break; case mlt_service_multitrack_type: error = self->on_start_multitrack(self, (mlt_multitrack) object); if (error == 0) { int i = 0; while (i < mlt_multitrack_count((mlt_multitrack) object)) { self->on_start_track(self); mlt_parser_start(self, (mlt_service) mlt_multitrack_track((mlt_multitrack) object, i++)); self->on_end_track(self); } i = 0; while (error == 0 && mlt_producer_filter((mlt_producer) object, i) != NULL) error = mlt_parser_start(self, (mlt_service) mlt_producer_filter((mlt_producer) object, i++)); } error = self->on_end_multitrack(self, (mlt_multitrack) object); break; case mlt_service_filter_type: error = self->on_start_filter(self, (mlt_filter) object); if (error == 0) { int i = 0; while (error == 0 && mlt_producer_filter((mlt_producer) object, i) != NULL) error = mlt_parser_start(self, (mlt_service) mlt_producer_filter((mlt_producer) object, i++)); } error = self->on_end_filter(self, (mlt_filter) object); break; case mlt_service_transition_type: error = self->on_start_transition(self, (mlt_transition) object); if (error == 0) { int i = 0; while (error == 0 && mlt_producer_filter((mlt_producer) object, i) != NULL) error = mlt_parser_start(self, (mlt_service) mlt_producer_filter((mlt_producer) object, i++)); } error = self->on_end_transition(self, (mlt_transition) object); break; case mlt_service_field_type: break; case mlt_service_consumer_type: break; case mlt_service_chain_type: error = self->on_start_chain(self, (mlt_chain) object); if (error == 0) { int i = 0; while (error == 0 && mlt_chain_link((mlt_chain) object, i) != NULL) mlt_parser_start(self, (mlt_service) mlt_chain_link((mlt_chain) object, i++)); i = 0; while (error == 0 && mlt_producer_filter((mlt_producer) object, i) != NULL) error = mlt_parser_start(self, (mlt_service) mlt_producer_filter((mlt_producer) object, i++)); } error = self->on_end_chain(self, (mlt_chain) object); break; case mlt_service_link_type: error = self->on_start_link(self, (mlt_link) object); error = self->on_end_link(self, (mlt_link) object); break; } return error; } void mlt_parser_close(mlt_parser self) { if (self != NULL) { mlt_properties_close(&self->parent); free(self); } } mlt-7.22.0/src/framework/mlt_parser.h000664 000000 000000 00000004760 14531534050 017472 0ustar00rootroot000000 000000 /** * \file mlt_parser.h * \brief service parsing functionality * \see mlt_parser_s * * Copyright (C) 2003-2014 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef MLT_PARSER_H #define MLT_PARSER_H #include "mlt_types.h" /** \brief Parser class * * \extends mlt_properties_s */ struct mlt_parser_s { struct mlt_properties_s parent; int (*on_invalid)(mlt_parser self, mlt_service object); int (*on_unknown)(mlt_parser self, mlt_service object); int (*on_start_producer)(mlt_parser self, mlt_producer object); int (*on_end_producer)(mlt_parser self, mlt_producer object); int (*on_start_playlist)(mlt_parser self, mlt_playlist object); int (*on_end_playlist)(mlt_parser self, mlt_playlist object); int (*on_start_tractor)(mlt_parser self, mlt_tractor object); int (*on_end_tractor)(mlt_parser self, mlt_tractor object); int (*on_start_multitrack)(mlt_parser self, mlt_multitrack object); int (*on_end_multitrack)(mlt_parser self, mlt_multitrack object); int (*on_start_track)(mlt_parser self); int (*on_end_track)(mlt_parser self); int (*on_start_filter)(mlt_parser self, mlt_filter object); int (*on_end_filter)(mlt_parser self, mlt_filter object); int (*on_start_transition)(mlt_parser self, mlt_transition object); int (*on_end_transition)(mlt_parser self, mlt_transition object); int (*on_start_chain)(mlt_parser self, mlt_chain object); int (*on_end_chain)(mlt_parser self, mlt_chain object); int (*on_start_link)(mlt_parser self, mlt_link object); int (*on_end_link)(mlt_parser self, mlt_link object); }; extern mlt_parser mlt_parser_new(); extern mlt_properties mlt_parser_properties(mlt_parser self); extern int mlt_parser_start(mlt_parser self, mlt_service object); extern void mlt_parser_close(mlt_parser self); #endif mlt-7.22.0/src/framework/mlt_playlist.c000664 000000 000000 00000230324 14531534050 020027 0ustar00rootroot000000 000000 /** * \file mlt_playlist.c * \brief playlist service class * \see mlt_playlist_s * * Copyright (C) 2003-2023 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "mlt_playlist.h" #include "mlt_factory.h" #include "mlt_field.h" #include "mlt_frame.h" #include "mlt_log.h" #include "mlt_multitrack.h" #include "mlt_tractor.h" #include "mlt_transition.h" #include #include #include /** \brief Virtual playlist entry used by mlt_playlist_s */ struct playlist_entry_s { mlt_producer producer; mlt_position frame_in; mlt_position frame_out; mlt_position frame_count; int repeat; mlt_position producer_length; mlt_event event; int preservation_hack; }; /* Forward declarations */ static int producer_get_frame(mlt_producer producer, mlt_frame_ptr frame, int index); static int mlt_playlist_unmix(mlt_playlist self, int clip); static int mlt_playlist_resize_mix(mlt_playlist self, int clip, int in, int out); static mlt_producer blank_producer(mlt_playlist self); mlt_playlist mlt_playlist_alloc() { mlt_playlist self = calloc(1, sizeof(struct mlt_playlist_s)); if (self != NULL) { mlt_producer producer = &self->parent; // Construct the producer if (mlt_producer_init(producer, self) != 0) goto error1; // Override the producer get_frame producer->get_frame = producer_get_frame; // Define the destructor producer->close = (mlt_destructor) mlt_playlist_close; producer->close_object = self; // Indicate that this producer is a playlist mlt_properties_set_data(MLT_PLAYLIST_PROPERTIES(self), "playlist", self, 0, NULL, NULL); // Specify the eof condition mlt_properties_set(MLT_PLAYLIST_PROPERTIES(self), "eof", "pause"); mlt_properties_set(MLT_PLAYLIST_PROPERTIES(self), "resource", ""); mlt_properties_set(MLT_PLAYLIST_PROPERTIES(self), "mlt_type", "mlt_producer"); mlt_properties_set_position(MLT_PLAYLIST_PROPERTIES(self), "in", 0); mlt_properties_set_position(MLT_PLAYLIST_PROPERTIES(self), "out", -1); mlt_properties_set_position(MLT_PLAYLIST_PROPERTIES(self), "length", 0); self->size = 10; self->list = calloc(self->size, sizeof(playlist_entry *)); if (self->list == NULL) goto error2; mlt_events_register(MLT_PLAYLIST_PROPERTIES(self), "playlist-next"); } return self; error2: free(self->list); error1: free(self); return NULL; } /** Construct a playlist. * * Sets the resource property to "". * Set the mlt_type to property to "mlt_producer". * \deprecated use mlt_playlist_new() * \public \memberof mlt_playlist_s * \return a new playlist */ mlt_playlist mlt_playlist_init() { return mlt_playlist_alloc(); } /** Construct a playlist with a profile. * * Sets the resource property to "". * Set the mlt_type to property to "mlt_producer". * \public \memberof mlt_playlist_s * \param profile the profile to use with the profile * \return a new playlist */ mlt_playlist mlt_playlist_new(mlt_profile profile) { mlt_playlist self = mlt_playlist_alloc(); if (self) mlt_properties_set_data(MLT_PLAYLIST_PROPERTIES(self), "_profile", profile, 0, NULL, NULL); return self; } /** Get the producer associated to this playlist. * * \public \memberof mlt_playlist_s * \param self a playlist * \return the producer interface * \see MLT_PLAYLIST_PRODUCER */ mlt_producer mlt_playlist_producer(mlt_playlist self) { return self != NULL ? &self->parent : NULL; } /** Get the service associated to this playlist. * * \public \memberof mlt_playlist_s * \param self a playlist * \return the service interface * \see MLT_PLAYLIST_SERVICE */ mlt_service mlt_playlist_service(mlt_playlist self) { return MLT_PRODUCER_SERVICE(&self->parent); } /** Get the properties associated to this playlist. * * \public \memberof mlt_playlist_s * \param self a playlist * \return the playlist's properties list * \see MLT_PLAYLIST_PROPERTIES */ mlt_properties mlt_playlist_properties(mlt_playlist self) { return MLT_PRODUCER_PROPERTIES(&self->parent); } /** Refresh the playlist after a clip has been changed. * * \private \memberof mlt_playlist_s * \param self a playlist * \return false */ static int mlt_playlist_virtual_refresh(mlt_playlist self) { // Obtain the properties mlt_properties properties = MLT_PLAYLIST_PROPERTIES(self); int i = 0; mlt_position frame_count = 0; for (i = 0; i < self->count; i++) { // Get the producer mlt_producer producer = self->list[i]->producer; if (producer) { int current_length = mlt_producer_get_playtime(producer); // Check if the length of the producer has changed if (self->list[i]->frame_in != mlt_producer_get_in(producer) || self->list[i]->frame_out != mlt_producer_get_out(producer)) { // This clip should be removed... if (current_length < 1) { self->list[i]->frame_in = 0; self->list[i]->frame_out = -1; self->list[i]->frame_count = 0; } else { self->list[i]->frame_in = mlt_producer_get_in(producer); self->list[i]->frame_out = mlt_producer_get_out(producer); self->list[i]->frame_count = current_length; } // Update the producer_length self->list[i]->producer_length = current_length; } } // Calculate the frame_count self->list[i]->frame_count = (self->list[i]->frame_out - self->list[i]->frame_in + 1) * self->list[i]->repeat; // Update the frame_count for self clip frame_count += self->list[i]->frame_count; } // Refresh all properties mlt_events_block(properties, properties); mlt_properties_set_position(properties, "length", frame_count); mlt_events_unblock(properties, properties); mlt_properties_set_position(properties, "out", frame_count - 1); return 0; } /** Listener for producers on the playlist. * * Refreshes the playlist whenever an entry receives producer-changed. * \private \memberof mlt_playlist_s * \param producer a producer * \param self a playlist */ static void mlt_playlist_listener(mlt_producer producer, mlt_playlist self) { mlt_playlist_virtual_refresh(self); } /** Append to the virtual playlist. * * \private \memberof mlt_playlist_s * \param self a playlist * \param source a producer * \param in the producer's starting time * \param out the producer's ending time * \return true if there was an error */ static int mlt_playlist_virtual_append(mlt_playlist self, mlt_producer source, mlt_position in, mlt_position out) { mlt_producer producer = NULL; mlt_properties properties = NULL; mlt_properties parent = NULL; // If we have a cut, then use the in/out points from the cut if (mlt_producer_is_blank(source)) { mlt_position length = out - in + 1; mlt_producer blank = blank_producer(self); // Make sure the blank is long enough to accommodate the length specified if (length > mlt_producer_get_length(blank)) { mlt_properties blank_props = MLT_PRODUCER_PROPERTIES(blank); mlt_events_block(blank_props, blank_props); mlt_producer_set_in_and_out(blank, in, out); mlt_events_unblock(blank_props, blank_props); } // Now make sure the cut comes from this blank if (source == NULL) { producer = mlt_producer_cut(blank, in, out); } else if (!mlt_producer_is_cut(source) || mlt_producer_cut_parent(source) != blank) { producer = mlt_producer_cut(blank, in, out); } else { producer = source; mlt_properties_inc_ref(MLT_PRODUCER_PROPERTIES(producer)); } properties = MLT_PRODUCER_PROPERTIES(producer); // Make sure this cut of blank is long enough if (length > mlt_producer_get_length(producer)) mlt_properties_set_int(properties, "length", length); } else if (mlt_producer_is_cut(source)) { producer = source; if (in < 0) in = mlt_producer_get_in(producer); if (out < 0 || out > mlt_producer_get_out(producer)) out = mlt_producer_get_out(producer); properties = MLT_PRODUCER_PROPERTIES(producer); mlt_properties_inc_ref(properties); } else { producer = mlt_producer_cut(source, in, out); if (in < 0 || in < mlt_producer_get_in(producer)) in = mlt_producer_get_in(producer); if (out < 0 || out > mlt_producer_get_out(producer)) out = mlt_producer_get_out(producer); properties = MLT_PRODUCER_PROPERTIES(producer); } // Fetch the cuts parent properties parent = MLT_PRODUCER_PROPERTIES(mlt_producer_cut_parent(producer)); // Remove loader normalizers for fx cuts if (mlt_properties_get_int(parent, "meta.fx_cut")) { mlt_service service = MLT_PRODUCER_SERVICE(mlt_producer_cut_parent(producer)); mlt_filter filter = mlt_service_filter(service, 0); while (filter != NULL && mlt_properties_get_int(MLT_FILTER_PROPERTIES(filter), "_loader")) { mlt_service_detach(service, filter); filter = mlt_service_filter(service, 0); } mlt_properties_set_int(MLT_PRODUCER_PROPERTIES(producer), "meta.fx_cut", 1); } // Check that we have room if (self->count >= self->size) { int i; self->list = realloc(self->list, (self->size + 10) * sizeof(playlist_entry *)); for (i = self->size; i < self->size + 10; i++) self->list[i] = NULL; self->size += 10; } // Create the entry self->list[self->count] = calloc(1, sizeof(playlist_entry)); if (self->list[self->count] != NULL) { self->list[self->count]->producer = producer; self->list[self->count]->frame_in = in; self->list[self->count]->frame_out = out; self->list[self->count]->frame_count = out - in + 1; self->list[self->count]->repeat = 1; self->list[self->count]->producer_length = mlt_producer_get_playtime(producer); self->list[self->count]->event = mlt_events_listen(parent, self, "producer-changed", (mlt_listener) mlt_playlist_listener); mlt_event_inc_ref(self->list[self->count]->event); mlt_properties_set(properties, "eof", "pause"); mlt_producer_set_speed(producer, 0); self->count++; } return mlt_playlist_virtual_refresh(self); } /** Locate a producer by index. * * \private \memberof mlt_playlist_s * \param self a playlist * \param[in, out] position the time at which to locate the producer, returns the time relative to the producer's starting point * \param[out] clip the index of the playlist entry * \param[out] total the duration of the playlist up to and including this producer * \return a producer or NULL if not found */ static mlt_producer mlt_playlist_locate(mlt_playlist self, mlt_position *position, int *clip, int *total) { // Default producer to NULL mlt_producer producer = NULL; // Loop for each producer until found for (*clip = 0; *clip < self->count; *clip += 1) { // Increment the total *total += self->list[*clip]->frame_count; // Check if the position indicates that we have found the clip // Note that 0 length clips get skipped automatically if (*position < self->list[*clip]->frame_count) { // Found it, now break producer = self->list[*clip]->producer; break; } else { // Decrement position by length of self entry *position -= self->list[*clip]->frame_count; } } return producer; } /** Seek in the virtual playlist. * * This gets the producer at the current position and seeks on the producer * while doing repeat and end-of-file handling. This is also responsible for * closing producers previous to the preceding playlist if the autoclose * property is set. * \private \memberof mlt_playlist_s * \param self a playlist * \param[out] progressive true if the producer should be displayed progressively * \param[out] clip_index the index of the returned service * \param[out] clip_position the position in the returned service relative to the beginning * \return the service interface of the producer at the play head * \see producer_get_frame */ static mlt_service mlt_playlist_virtual_seek(mlt_playlist self, int *progressive, int *clip_index, int *clip_position) { // Map playlist position to real producer in virtual playlist mlt_position position = mlt_producer_frame(&self->parent); // Keep the original position since we change it while iterating through the list mlt_position original = position; // Clip index and total int i = 0; int total = 0; // Locate the producer for the position mlt_producer producer = mlt_playlist_locate(self, &position, &i, &total); // Get the properties mlt_properties properties = MLT_PLAYLIST_PROPERTIES(self); // Automatically close previous producers if requested if (i > 1 // keep immediate previous in case app wants to get info about what just finished && position < 2 // tolerate off-by-one error on going to next clip && mlt_properties_get_int(properties, "autoclose")) { int j; // They might have jumped ahead! for (j = 0; j < i - 1; j++) { mlt_service_lock(MLT_PRODUCER_SERVICE(self->list[j]->producer)); mlt_producer p = self->list[j]->producer; if (p) { self->list[j]->producer = NULL; mlt_service_unlock(MLT_PRODUCER_SERVICE(p)); mlt_producer_close(p); } // If p is null, the lock will not have been "taken" } } // Get the eof handling char *eof = mlt_properties_get(properties, "eof"); // Seek in real producer to relative position if (producer != NULL) { int count = self->list[i]->frame_count / self->list[i]->repeat; *progressive = count == 1; mlt_producer_seek(producer, (int) position % count); } else if (!strcmp(eof, "pause") && total > 0) { playlist_entry *entry = self->list[self->count - 1]; int count = entry->frame_count / entry->repeat; mlt_producer self_producer = MLT_PLAYLIST_PRODUCER(self); mlt_producer_seek(self_producer, original - 1); producer = entry->producer; mlt_producer_seek(producer, (int) entry->frame_out % count); mlt_producer_set_speed(self_producer, 0); mlt_producer_set_speed(producer, 0); *progressive = count == 1; } else if (!strcmp(eof, "loop") && total > 0) { playlist_entry *entry = self->list[0]; mlt_producer self_producer = MLT_PLAYLIST_PRODUCER(self); mlt_producer_seek(self_producer, 0); producer = entry->producer; mlt_producer_seek(producer, 0); } else { producer = blank_producer(self); } if (i < self->count && clip_index && clip_position) { *clip_index = i; *clip_position = position; } // Determine if we have moved to the next entry in the playlist. if (original == total - 2) { mlt_events_fire(properties, "playlist-next", mlt_event_data_from_int(i)); } return MLT_PRODUCER_SERVICE(producer); } /** Invoked when a producer indicates that it has prematurely reached its end. * * \private \memberof mlt_playlist_s * \param self a playlist * \return a producer * \see producer_get_frame */ static mlt_producer mlt_playlist_virtual_set_out(mlt_playlist self) { mlt_producer producer = NULL; // Map playlist position to real producer in virtual playlist mlt_position position = mlt_producer_frame(&self->parent); // Loop through the virtual playlist int i = 0; for (i = 0; i < self->count; i++) { if (position < self->list[i]->frame_count) { // Found it, now break producer = self->list[i]->producer; break; } else { // Decrement position by length of this entry position -= self->list[i]->frame_count; } } if (!producer) { producer = blank_producer(self); } // Seek in real producer to relative position if (i < self->count && self->list[i]->frame_out != position) { // Update the frame_count for the changed clip (hmmm) self->list[i]->frame_out = position; self->list[i]->frame_count = self->list[i]->frame_out - self->list[i]->frame_in + 1; // Refresh the playlist mlt_playlist_virtual_refresh(self); } return producer; } /** Obtain the current clips index. * * \public \memberof mlt_playlist_s * \param self a playlist * \return the index of the playlist entry at the current position */ int mlt_playlist_current_clip(mlt_playlist self) { // Map playlist position to real producer in virtual playlist mlt_position position = mlt_producer_frame(&self->parent); // Loop through the virtual playlist int i = 0; for (i = 0; i < self->count; i++) { if (position < self->list[i]->frame_count) { // Found it, now break break; } else { // Decrement position by length of this entry position -= self->list[i]->frame_count; } } return i; } /** Obtain the current clips producer. * * \public \memberof mlt_playlist_s * \param self a playlist * \return the producer at the current position */ mlt_producer mlt_playlist_current(mlt_playlist self) { int i = mlt_playlist_current_clip(self); if (i < self->count) return self->list[i]->producer; else return blank_producer(self); } /** Get the position which corresponds to the start of the next clip. * * \public \memberof mlt_playlist_s * \param self a playlist * \param whence the location from which to make the index relative: * start of playlist, end of playlist, or current position * \param index the playlist entry index relative to whence * \return the time at which the referenced clip starts */ mlt_position mlt_playlist_clip(mlt_playlist self, mlt_whence whence, int index) { mlt_position position = 0; int absolute_clip = index; int i = 0; // Determine the absolute clip switch (whence) { case mlt_whence_relative_start: absolute_clip = index; break; case mlt_whence_relative_current: absolute_clip = mlt_playlist_current_clip(self) + index; break; case mlt_whence_relative_end: absolute_clip = self->count - index; break; } // Check that we're in a valid range if (absolute_clip < 0) absolute_clip = 0; else if (absolute_clip > self->count) absolute_clip = self->count; // Now determine the position for (i = 0; i < absolute_clip; i++) position += self->list[i]->frame_count; return position; } /** Get all the info about the clip specified. * * \public \memberof mlt_playlist_s * \param self a playlist * \param info a clip info struct * \param index a playlist entry index * \return true if there was an error */ int mlt_playlist_get_clip_info(mlt_playlist self, mlt_playlist_clip_info *info, int index) { int error = index < 0 || index >= self->count || self->list[index]->producer == NULL; memset(info, 0, sizeof(mlt_playlist_clip_info)); if (!error) { mlt_producer producer = mlt_producer_cut_parent(self->list[index]->producer); mlt_properties properties = MLT_PRODUCER_PROPERTIES(producer); info->clip = index; info->producer = producer; info->cut = self->list[index]->producer; info->start = mlt_playlist_clip(self, mlt_whence_relative_start, index); info->resource = mlt_properties_get(properties, "resource"); info->frame_in = self->list[index]->frame_in; info->frame_out = self->list[index]->frame_out; info->frame_count = self->list[index]->frame_count; info->repeat = self->list[index]->repeat; info->length = mlt_producer_get_length(producer); info->fps = mlt_producer_get_fps(producer); } return error; } /** Get number of clips in the playlist. * * \public \memberof mlt_playlist_s * \param self a playlist * \return the number of playlist entries */ int mlt_playlist_count(mlt_playlist self) { return self->count; } /** Clear the playlist. * * \public \memberof mlt_playlist_s * \param self a playlist * \return true if there was an error */ int mlt_playlist_clear(mlt_playlist self) { int i; for (i = 0; i < self->count; i++) { mlt_event_close(self->list[i]->event); mlt_producer_close(self->list[i]->producer); } self->count = 0; return mlt_playlist_virtual_refresh(self); } /** Append a producer to the playlist. * * \public \memberof mlt_playlist_s * \param self a playlist * \param producer the producer to append * \return true if there was an error */ int mlt_playlist_append(mlt_playlist self, mlt_producer producer) { // Append to virtual list return mlt_playlist_virtual_append(self, producer, 0, mlt_producer_get_playtime(producer) - 1); } /** Append a producer to the playlist with in/out points. * * \public \memberof mlt_playlist_s * \param self a playlist * \param producer the producer to append * \param in the starting point on the producer; a negative value is the same as 0 * \param out the ending point on the producer; a negative value is the same as producer length - 1 * \return true if there was an error */ int mlt_playlist_append_io(mlt_playlist self, mlt_producer producer, mlt_position in, mlt_position out) { // Append to virtual list if (in < 0 && out < 0) return mlt_playlist_append(self, producer); else return mlt_playlist_virtual_append(self, producer, in, out); } /** Append a blank to the playlist of a given length. * * \public \memberof mlt_playlist_s * \param self a playlist * \param out the ending time of the blank entry, not its duration * \return true if there was an error */ int mlt_playlist_blank(mlt_playlist self, mlt_position out) { // Append to the virtual list if (out >= 0) { return mlt_playlist_virtual_append(self, blank_producer(self), 0, out); } else return 1; } /** Append a blank item to the playlist with duration as a time string. * * \public \memberof mlt_playlist_s * \param self a playlist * \param length the duration of the blank entry as a time string * \return true if there was an error */ int mlt_playlist_blank_time(mlt_playlist self, const char *length) { if (self && length) { mlt_properties properties = MLT_PLAYLIST_PROPERTIES(self); mlt_properties_set(properties, "_blank_time", length); mlt_position duration = mlt_properties_get_position(properties, "_blank_time"); return mlt_playlist_blank(self, duration - 1); } else return 1; } /** Insert a producer into the playlist. * * \public \memberof mlt_playlist_s * \param self a playlist * \param producer the producer to insert * \param where the producer's playlist entry index * \param in the starting point on the producer * \param out the ending point on the producer * \return true if there was an error */ int mlt_playlist_insert( mlt_playlist self, mlt_producer producer, int where, mlt_position in, mlt_position out) { // Append to end mlt_events_block(MLT_PLAYLIST_PROPERTIES(self), self); mlt_playlist_append_io(self, producer, in, out); // Move to the position specified mlt_playlist_move(self, self->count - 1, where); mlt_events_unblock(MLT_PLAYLIST_PROPERTIES(self), self); return mlt_playlist_virtual_refresh(self); } /** Remove an entry in the playlist. * * \public \memberof mlt_playlist_s * \param self a playlist * \param where the playlist entry index * \return true if there was an error */ int mlt_playlist_remove(mlt_playlist self, int where) { int error = where < 0 || where >= self->count; if (error == 0 && mlt_playlist_unmix(self, where) != 0) { // We need to know the current clip and the position within the playlist int current = mlt_playlist_current_clip(self); mlt_position position = mlt_producer_position(MLT_PLAYLIST_PRODUCER(self)); // We need all the details about the clip we're removing mlt_playlist_clip_info where_info; playlist_entry *entry = self->list[where]; mlt_properties properties = MLT_PRODUCER_PROPERTIES(entry->producer); // Loop variable int i = 0; // Get the clip info mlt_playlist_get_clip_info(self, &where_info, where); // Reorganise the list for (i = where + 1; i < self->count; i++) self->list[i - 1] = self->list[i]; self->count--; if (entry->preservation_hack == 0) { // Decouple from mix_in/out if necessary if (mlt_properties_get_data(properties, "mix_in", NULL) != NULL) { mlt_properties mix = mlt_properties_get_data(properties, "mix_in", NULL); mlt_properties_set_data(mix, "mix_out", NULL, 0, NULL, NULL); } if (mlt_properties_get_data(properties, "mix_out", NULL) != NULL) { mlt_properties mix = mlt_properties_get_data(properties, "mix_out", NULL); mlt_properties_set_data(mix, "mix_in", NULL, 0, NULL, NULL); } if (mlt_properties_ref_count(MLT_PRODUCER_PROPERTIES(entry->producer)) == 1) mlt_producer_clear(entry->producer); } // Close the producer associated to the clip info mlt_event_close(entry->event); mlt_producer_close(entry->producer); // Correct position if (where == current) mlt_producer_seek(MLT_PLAYLIST_PRODUCER(self), where_info.start); else if (where < current && self->count > 0) mlt_producer_seek(MLT_PLAYLIST_PRODUCER(self), position - where_info.frame_count); else if (self->count == 0) mlt_producer_seek(MLT_PLAYLIST_PRODUCER(self), 0); // Free the entry free(entry); // Refresh the playlist mlt_playlist_virtual_refresh(self); } return error; } /** Move an entry in the playlist. * * \public \memberof mlt_playlist_s * \param self a playlist * \param src an entry index * \param dest an entry index * \return false */ int mlt_playlist_move(mlt_playlist self, int src, int dest) { int i; /* We need to ensure that the requested indexes are valid and correct it as necessary */ if (src < 0) src = 0; if (src >= self->count) src = self->count - 1; if (dest < 0) dest = 0; if (dest >= self->count) dest = self->count - 1; if (src != dest && self->count > 1) { int current = mlt_playlist_current_clip(self); mlt_position position = mlt_producer_position(MLT_PLAYLIST_PRODUCER(self)); playlist_entry *src_entry = NULL; // We need all the details about the current clip mlt_playlist_clip_info current_info; mlt_playlist_get_clip_info(self, ¤t_info, current); position -= current_info.start; if (current == src) current = dest; else if (src < current && current < dest) current--; else if (dest < current && current < src) current++; else if (current == dest) current = src; src_entry = self->list[src]; if (src > dest) { for (i = src; i > dest; i--) self->list[i] = self->list[i - 1]; } else { for (i = src; i < dest; i++) self->list[i] = self->list[i + 1]; } self->list[dest] = src_entry; mlt_playlist_get_clip_info(self, ¤t_info, current); mlt_producer_seek(MLT_PLAYLIST_PRODUCER(self), current_info.start + position); mlt_playlist_virtual_refresh(self); } return 0; } /** Reorder the entries in the playlist. * * \public \memberof mlt_playlist_s * \param self a playlist * \param indices a list of current indices mapped to the new desired index * \return true if there was an error */ int mlt_playlist_reorder(mlt_playlist self, const int *indices) { // Check that the playlist is sortable. if (self->count < 2) return 1; // Sanity check the indices. The values must be in range and unique. int i, j; for (i = 0; i < self->count - 1; i++) for (j = i + 1; j < self->count; j++) if (indices[i] < 0 || indices[i] >= self->count || indices[j] < 0 || indices[j] >= self->count || indices[i] == indices[j]) return 1; // Create a new list to copy entries in a new order. playlist_entry **new_list = calloc(self->size, sizeof(playlist_entry *)); if (new_list == NULL) return 1; // Copy entries according to the new indices int new_index; for (new_index = 0; new_index < self->count; new_index++) { int old_index = indices[new_index]; new_list[new_index] = self->list[old_index]; } // Delete the old list and save the new list free(self->list); self->list = new_list; mlt_playlist_virtual_refresh(self); return 0; } /** Repeat the specified clip n times. * * \public \memberof mlt_playlist_s * \param self a playlist * \param clip a playlist entry index * \param repeat the number of times to repeat the clip * \return true if there was an error */ int mlt_playlist_repeat_clip(mlt_playlist self, int clip, int repeat) { int error = repeat < 1 || clip < 0 || clip >= self->count; if (error == 0) { playlist_entry *entry = self->list[clip]; entry->repeat = repeat; mlt_playlist_virtual_refresh(self); } return error; } /** Resize the specified clip. * * \public \memberof mlt_playlist_s * \param self a playlist * \param clip the index of the playlist entry * \param in the new starting time on the clip's producer; a negative value is the same as 0 * \param out the new ending time on the clip's producer; a negative value is the same as length - 1 * \return true if there was an error */ int mlt_playlist_resize_clip(mlt_playlist self, int clip, mlt_position in, mlt_position out) { int error = clip < 0 || clip >= self->count; if (error == 0 && mlt_playlist_resize_mix(self, clip, in, out) != 0) { playlist_entry *entry = self->list[clip]; mlt_producer producer = entry->producer; mlt_properties properties = MLT_PLAYLIST_PROPERTIES(self); mlt_events_block(properties, properties); if (mlt_producer_is_blank(producer)) { mlt_position length = out - in + 1; mlt_producer blank = blank_producer(self); // Make sure the parent blank is long enough to accommodate the length specified if (length > mlt_producer_get_length(blank)) { mlt_properties blank_props = MLT_PRODUCER_PROPERTIES(blank); mlt_properties_set_int(blank_props, "length", length); mlt_producer_set_in_and_out(blank, 0, out - in); } // Make sure this cut of blank is long enough if (length > mlt_producer_get_length(producer)) mlt_properties_set_int(MLT_PRODUCER_PROPERTIES(producer), "length", length); } if (in < 0) in = 0; if (out < 0 || out >= mlt_producer_get_length(producer)) out = mlt_producer_get_length(producer) - 1; if (out < in) { mlt_position t = in; in = out; out = t; } mlt_producer_set_in_and_out(producer, in, out); mlt_events_unblock(properties, properties); mlt_playlist_virtual_refresh(self); } return error; } /** Split a clip on the playlist at the given position. * * This splits after the specified frame. * \public \memberof mlt_playlist_s * \param self a playlist * \param clip the index of the playlist entry * \param position the time at which to split relative to the beginning of the clip or its end if negative * \return true if there was an error */ int mlt_playlist_split(mlt_playlist self, int clip, mlt_position position) { int error = clip < 0 || clip >= self->count; if (error == 0) { playlist_entry *entry = self->list[clip]; position = position < 0 ? entry->frame_count + position - 1 : position; if (position >= 0 && position < entry->frame_count - 1) { int in = entry->frame_in; int out = entry->frame_out; mlt_events_block(MLT_PLAYLIST_PROPERTIES(self), self); mlt_playlist_resize_clip(self, clip, in, in + position); if (!mlt_producer_is_blank(entry->producer)) { mlt_properties entry_properties = MLT_PRODUCER_PROPERTIES(entry->producer); mlt_producer split = mlt_producer_cut(entry->producer, in + position + 1, out); mlt_properties split_properties = MLT_PRODUCER_PROPERTIES(split); mlt_playlist_insert(self, split, clip + 1, 0, -1); mlt_properties_lock(entry_properties); mlt_properties_copy(split_properties, entry_properties, "meta."); mlt_properties_unlock(entry_properties); mlt_producer_close(split); } else { mlt_playlist_insert(self, blank_producer(self), clip + 1, 0, out - position - 1); } mlt_events_unblock(MLT_PLAYLIST_PROPERTIES(self), self); mlt_playlist_virtual_refresh(self); } else { error = 1; } } return error; } /** Split the playlist at the absolute position. * * \public \memberof mlt_playlist_s * \param self a playlist * \param position the time at which to split relative to the beginning of the clip * \param left true to split before the frame starting at position * \return true if there was an error */ int mlt_playlist_split_at(mlt_playlist self, mlt_position position, int left) { int result = self == NULL ? -1 : 0; if (!result) { if (position >= 0 && position < mlt_producer_get_playtime(MLT_PLAYLIST_PRODUCER(self))) { int clip = mlt_playlist_get_clip_index_at(self, position); mlt_playlist_clip_info info; mlt_playlist_get_clip_info(self, &info, clip); if (left && position != info.start) mlt_playlist_split(self, clip, position - info.start - 1); else if (!left) mlt_playlist_split(self, clip, position - info.start); result = position; } else if (position <= 0) { result = 0; } else { result = mlt_producer_get_playtime(MLT_PLAYLIST_PRODUCER(self)); } } return result; } /** Join 1 or more consecutive clips. * * \public \memberof mlt_playlist_s * \param self a playlist * \param clip the starting playlist entry index * \param count the number of entries to merge * \param merge ignored * \return true if there was an error */ int mlt_playlist_join(mlt_playlist self, int clip, int count, int merge) { int error = clip < 0 || clip >= self->count; if (error == 0) { int i = clip; mlt_playlist new_clip = mlt_playlist_new(mlt_service_profile(MLT_PLAYLIST_SERVICE(self))); mlt_properties_set_lcnumeric(MLT_PLAYLIST_PROPERTIES(new_clip), mlt_properties_get_lcnumeric(MLT_PLAYLIST_PROPERTIES(self))); mlt_events_block(MLT_PLAYLIST_PROPERTIES(self), self); if (clip + count >= self->count) count = self->count - clip - 1; for (i = 0; i <= count; i++) { playlist_entry *entry = self->list[clip]; mlt_playlist_append(new_clip, entry->producer); mlt_playlist_repeat_clip(new_clip, i, entry->repeat); entry->preservation_hack = 1; mlt_playlist_remove(self, clip); } mlt_events_unblock(MLT_PLAYLIST_PROPERTIES(self), self); mlt_playlist_insert(self, MLT_PLAYLIST_PRODUCER(new_clip), clip, 0, -1); mlt_playlist_close(new_clip); } return error; } /** Mix consecutive clips for a specified length and apply transition if specified. * * This version of the mix function does not utilize any frames beyond the out of * clip A or before the in point of clip B. It takes the frames needed for the length * of the transition by adjusting the duration of both clips - the out point for clip A * and the in point for clip B. * \public \memberof mlt_playlist_s * \param self a playlist * \param clip the index of the playlist entry * \param length the number of frames over which to create the mix * \param transition the transition to use for the mix * \return true if there was an error */ int mlt_playlist_mix(mlt_playlist self, int clip, int length, mlt_transition transition) { int error = (clip < 0 || clip + 1 >= self->count); if (error == 0) { playlist_entry *clip_a = self->list[clip]; playlist_entry *clip_b = self->list[clip + 1]; mlt_producer track_a = NULL; mlt_producer track_b = NULL; mlt_tractor tractor = mlt_tractor_new(); mlt_service_set_profile(MLT_TRACTOR_SERVICE(tractor), mlt_service_profile(MLT_PLAYLIST_SERVICE(self))); mlt_properties_set_lcnumeric(MLT_TRACTOR_PROPERTIES(tractor), mlt_properties_get_lcnumeric(MLT_PLAYLIST_PROPERTIES(self))); mlt_events_block(MLT_PLAYLIST_PROPERTIES(self), self); // Check length is valid for both clips and resize if necessary. int max_size = clip_a->frame_count > clip_b->frame_count ? clip_a->frame_count : clip_b->frame_count; length = length > max_size ? max_size : length; // Create the a and b tracks/cuts if necessary - note that no cuts are required if the length matches if (length != clip_a->frame_count) track_a = mlt_producer_cut(clip_a->producer, clip_a->frame_out - length + 1, clip_a->frame_out); else track_a = clip_a->producer; if (length != clip_b->frame_count) track_b = mlt_producer_cut(clip_b->producer, clip_b->frame_in, clip_b->frame_in + length - 1); else track_b = clip_b->producer; // Set the tracks on the tractor mlt_tractor_set_track(tractor, track_a, 0); mlt_tractor_set_track(tractor, track_b, 1); // Insert the mix object into the playlist mlt_playlist_insert(self, MLT_TRACTOR_PRODUCER(tractor), clip + 1, -1, -1); mlt_properties_set_data(MLT_TRACTOR_PROPERTIES(tractor), "mlt_mix", tractor, 0, NULL, NULL); // Attach the transition if (transition != NULL) { mlt_field field = mlt_tractor_field(tractor); mlt_field_plant_transition(field, transition, 0, 1); mlt_transition_set_in_and_out(transition, 0, length - 1); } // Close our references to the tracks if we created new cuts above (the tracks can still be used here) if (track_a != clip_a->producer) mlt_producer_close(track_a); if (track_b != clip_b->producer) mlt_producer_close(track_b); // Check if we have anything left on the right hand clip if (track_b == clip_b->producer) { clip_b->preservation_hack = 1; mlt_playlist_remove(self, clip + 2); } else if (clip_b->frame_out - clip_b->frame_in >= length) { mlt_playlist_resize_clip(self, clip + 2, clip_b->frame_in + length, clip_b->frame_out); mlt_properties_set_data(MLT_PRODUCER_PROPERTIES(clip_b->producer), "mix_in", tractor, 0, NULL, NULL); mlt_properties_set_data(MLT_TRACTOR_PROPERTIES(tractor), "mix_out", clip_b->producer, 0, NULL, NULL); } else { mlt_producer_clear(clip_b->producer); mlt_playlist_remove(self, clip + 2); } // Check if we have anything left on the left hand clip if (track_a == clip_a->producer) { clip_a->preservation_hack = 1; mlt_playlist_remove(self, clip); } else if (clip_a->frame_out - clip_a->frame_in >= length) { mlt_playlist_resize_clip(self, clip, clip_a->frame_in, clip_a->frame_out - length); mlt_properties_set_data(MLT_PRODUCER_PROPERTIES(clip_a->producer), "mix_out", tractor, 0, NULL, NULL); mlt_properties_set_data(MLT_TRACTOR_PROPERTIES(tractor), "mix_in", clip_a->producer, 0, NULL, NULL); } else { mlt_producer_clear(clip_a->producer); mlt_playlist_remove(self, clip); } // Unblock and force a fire off of change events to listeners mlt_events_unblock(MLT_PLAYLIST_PROPERTIES(self), self); mlt_playlist_virtual_refresh(self); mlt_tractor_close(tractor); } return error; } /** Mix consecutive clips for a specified length. * * This version of the mix function maintains the out point of the clip A by occupying the * beginning of clip B before its current in point. Therefore, it ends up adjusting the in * point and duration of clip B without affecting the duration of clip A. * Also, therefore, there must be enough frames after the out point of clip A. * \public \memberof mlt_playlist_s * \param self a playlist * \param clip the index of the playlist entry * \param length the number of frames over which to create the mix * \return true if there was an error */ int mlt_playlist_mix_in(mlt_playlist self, int clip, int length) { int error = (clip < 0 || clip + 1 >= self->count); if (error == 0) { playlist_entry *clip_a = self->list[clip]; playlist_entry *clip_b = self->list[clip + 1]; mlt_producer track_a = NULL; mlt_producer track_b = NULL; mlt_tractor tractor = mlt_tractor_new(); mlt_service_set_profile(MLT_TRACTOR_SERVICE(tractor), mlt_service_profile(MLT_PLAYLIST_SERVICE(self))); mlt_properties_set_lcnumeric(MLT_TRACTOR_PROPERTIES(tractor), mlt_properties_get_lcnumeric(MLT_PLAYLIST_PROPERTIES(self))); mlt_events_block(MLT_PLAYLIST_PROPERTIES(self), self); // Check length is valid for both clips and resize if necessary. int max_size = (clip_a->frame_out + 1) > clip_b->frame_count ? (clip_a->frame_out + 1) : clip_b->frame_count; length = length > max_size ? max_size : length; // Create the a and b tracks/cuts if necessary - note that no cuts are required if the length matches if (length != clip_a->frame_out + 1) track_a = mlt_producer_cut(clip_a->producer, clip_a->frame_out + 1, clip_a->frame_out + length); else track_a = clip_a->producer; if (length != clip_b->frame_count) track_b = mlt_producer_cut(clip_b->producer, clip_b->frame_in, clip_b->frame_in + length - 1); else track_b = clip_b->producer; // Set the tracks on the tractor mlt_tractor_set_track(tractor, track_a, 0); mlt_tractor_set_track(tractor, track_b, 1); // Insert the mix object into the playlist mlt_playlist_insert(self, MLT_TRACTOR_PRODUCER(tractor), clip + 1, -1, -1); mlt_properties_set_data(MLT_TRACTOR_PROPERTIES(tractor), "mlt_mix", tractor, 0, NULL, NULL); // Close our references to the tracks if we created new cuts above (the tracks can still be used here) if (track_a != clip_a->producer) mlt_producer_close(track_a); if (track_b != clip_b->producer) mlt_producer_close(track_b); // Check if we have anything left on the right hand clip if (track_b == clip_b->producer) { clip_b->preservation_hack = 1; mlt_playlist_remove(self, clip + 2); } else if (clip_b->frame_out - clip_b->frame_in >= length) { mlt_playlist_resize_clip(self, clip + 2, clip_b->frame_in + length, clip_b->frame_out); mlt_properties_set_data(MLT_PRODUCER_PROPERTIES(clip_b->producer), "mix_in", tractor, 0, NULL, NULL); mlt_properties_set_data(MLT_TRACTOR_PROPERTIES(tractor), "mix_out", clip_b->producer, 0, NULL, NULL); } else { mlt_producer_clear(clip_b->producer); mlt_playlist_remove(self, clip + 2); } // Check if we have anything left on the left hand clip if (track_a == clip_a->producer) { clip_a->preservation_hack = 1; mlt_playlist_remove(self, clip); } else if (clip_a->frame_out - clip_a->frame_in > 0) { mlt_properties_set_data(MLT_PRODUCER_PROPERTIES(clip_a->producer), "mix_out", tractor, 0, NULL, NULL); mlt_properties_set_data(MLT_TRACTOR_PROPERTIES(tractor), "mix_in", clip_a->producer, 0, NULL, NULL); } else { mlt_producer_clear(clip_a->producer); mlt_playlist_remove(self, clip); } // Unblock and force a fire off of change events to listeners mlt_events_unblock(MLT_PLAYLIST_PROPERTIES(self), self); mlt_playlist_virtual_refresh(self); mlt_tractor_close(tractor); } return error; } /** Mix consecutive clips for a specified length. * * This version of the mix function maintains the in point of the B clip by occupying the * end of clip A before its current out point. Therefore, it ends up adjusting the out * point and duration of clip A without affecting the duration or starting frame of clip B. * Also, therefore, there must be enough frames before the in point of clip B. * \public \memberof mlt_playlist_s * \param self a playlist * \param clip the index of the playlist entry * \param length the number of frames over which to create the mix * \return true if there was an error */ int mlt_playlist_mix_out(mlt_playlist self, int clip, int length) { int error = (clip < 0 || clip + 1 >= self->count); if (error == 0) { playlist_entry *clip_a = self->list[clip]; playlist_entry *clip_b = self->list[clip + 1]; mlt_producer track_a = NULL; mlt_producer track_b = NULL; mlt_tractor tractor = mlt_tractor_new(); mlt_service_set_profile(MLT_TRACTOR_SERVICE(tractor), mlt_service_profile(MLT_PLAYLIST_SERVICE(self))); mlt_properties_set_lcnumeric(MLT_TRACTOR_PROPERTIES(tractor), mlt_properties_get_lcnumeric(MLT_PLAYLIST_PROPERTIES(self))); mlt_events_block(MLT_PLAYLIST_PROPERTIES(self), self); // Check length is valid for both clips and resize if necessary. int max_size = clip_a->frame_count > clip_b->frame_in ? clip_a->frame_count : clip_b->frame_in; length = length > max_size ? max_size : length; // Create the a and b tracks/cuts if necessary - note that no cuts are required if the length matches if (length != clip_a->frame_count) track_a = mlt_producer_cut(clip_a->producer, clip_a->frame_out - length + 1, clip_a->frame_out); else track_a = clip_a->producer; if (length != clip_b->frame_in) track_b = mlt_producer_cut(clip_b->producer, clip_b->frame_in - length, clip_b->frame_in - 1); else track_b = clip_b->producer; // Set the tracks on the tractor mlt_tractor_set_track(tractor, track_a, 0); mlt_tractor_set_track(tractor, track_b, 1); // Insert the mix object into the playlist mlt_playlist_insert(self, MLT_TRACTOR_PRODUCER(tractor), clip + 1, -1, -1); mlt_properties_set_data(MLT_TRACTOR_PROPERTIES(tractor), "mlt_mix", tractor, 0, NULL, NULL); // Close our references to the tracks if we created new cuts above (the tracks can still be used here) if (track_a != clip_a->producer) mlt_producer_close(track_a); if (track_b != clip_b->producer) mlt_producer_close(track_b); // Check if we have anything left on the right hand clip if (track_b == clip_b->producer) { clip_b->preservation_hack = 1; mlt_playlist_remove(self, clip + 2); } else if (clip_b->frame_out - clip_b->frame_in > 0) { mlt_properties_set_data(MLT_PRODUCER_PROPERTIES(clip_b->producer), "mix_in", tractor, 0, NULL, NULL); mlt_properties_set_data(MLT_TRACTOR_PROPERTIES(tractor), "mix_out", clip_b->producer, 0, NULL, NULL); } else { mlt_producer_clear(clip_b->producer); mlt_playlist_remove(self, clip + 2); } // Check if we have anything left on the left hand clip if (track_a == clip_a->producer) { clip_a->preservation_hack = 1; mlt_playlist_remove(self, clip); } else if (clip_a->frame_out - clip_a->frame_in >= length) { mlt_playlist_resize_clip(self, clip, clip_a->frame_in, clip_a->frame_out - length); mlt_properties_set_data(MLT_PRODUCER_PROPERTIES(clip_a->producer), "mix_out", tractor, 0, NULL, NULL); mlt_properties_set_data(MLT_TRACTOR_PROPERTIES(tractor), "mix_in", clip_a->producer, 0, NULL, NULL); } else { mlt_producer_clear(clip_a->producer); mlt_playlist_remove(self, clip); } // Unblock and force a fire off of change events to listeners mlt_events_unblock(MLT_PLAYLIST_PROPERTIES(self), self); mlt_playlist_virtual_refresh(self); mlt_tractor_close(tractor); } return error; } /** Add a transition to an existing mix. * * \public \memberof mlt_playlist_s * \param self a playlist * \param clip the index of the playlist entry * \param transition a transition * \return true if there was an error */ int mlt_playlist_mix_add(mlt_playlist self, int clip, mlt_transition transition) { mlt_producer producer = mlt_producer_cut_parent(mlt_playlist_get_clip(self, clip)); mlt_properties properties = producer != NULL ? MLT_PRODUCER_PROPERTIES(producer) : NULL; mlt_tractor tractor = properties != NULL ? mlt_properties_get_data(properties, "mlt_mix", NULL) : NULL; int error = transition == NULL || tractor == NULL; if (error == 0) { mlt_field field = mlt_tractor_field(tractor); mlt_field_plant_transition(field, transition, 0, 1); mlt_transition_set_in_and_out(transition, 0, self->list[clip]->frame_count - 1); } return error; } /** Return the clip at the clip index. * * \public \memberof mlt_playlist_s * \param self a playlist * \param clip the index of a playlist entry * \return a producer or NULL if there was an error */ mlt_producer mlt_playlist_get_clip(mlt_playlist self, int clip) { if (clip >= 0 && clip < self->count) return self->list[clip]->producer; return NULL; } /** Return the clip at the specified position. * * \public \memberof mlt_playlist_s * \param self a playlist * \param position a time relative to the beginning of the playlist * \return a producer or NULL if not found */ mlt_producer mlt_playlist_get_clip_at(mlt_playlist self, mlt_position position) { int index = 0, total = 0; return mlt_playlist_locate(self, &position, &index, &total); } /** Return the clip index of the specified position. * * \public \memberof mlt_playlist_s * \param self a playlist * \param position a time relative to the beginning of the playlist * \return the index of the playlist entry */ int mlt_playlist_get_clip_index_at(mlt_playlist self, mlt_position position) { int index = 0, total = 0; mlt_playlist_locate(self, &position, &index, &total); return index; } /** Determine if the clip is a mix. * * \public \memberof mlt_playlist_s * \param self a playlist * \param clip the index of the playlist entry * \return true if the producer is a mix */ int mlt_playlist_clip_is_mix(mlt_playlist self, int clip) { mlt_producer producer = mlt_producer_cut_parent(mlt_playlist_get_clip(self, clip)); mlt_properties properties = producer != NULL ? MLT_PRODUCER_PROPERTIES(producer) : NULL; mlt_tractor tractor = properties != NULL ? mlt_properties_get_data(properties, "mlt_mix", NULL) : NULL; return tractor != NULL; } /** Remove a mixed clip - ensure that the cuts included in the mix find their way * back correctly on to the playlist. * * \private \memberof mlt_playlist_s * \param self a playlist * \param clip the index of the playlist entry * \return true if there was an error */ static int mlt_playlist_unmix(mlt_playlist self, int clip) { int error = (clip < 0 || clip >= self->count); // Ensure that the clip request is actually a mix if (error == 0) { mlt_producer producer = mlt_producer_cut_parent(self->list[clip]->producer); mlt_properties properties = MLT_PRODUCER_PROPERTIES(producer); error = mlt_properties_get_data(properties, "mlt_mix", NULL) == NULL || self->list[clip]->preservation_hack; } if (error == 0) { playlist_entry *mix = self->list[clip]; mlt_tractor tractor = (mlt_tractor) mlt_producer_cut_parent(mix->producer); mlt_properties properties = MLT_TRACTOR_PROPERTIES(tractor); mlt_producer clip_a = mlt_properties_get_data(properties, "mix_in", NULL); mlt_producer clip_b = mlt_properties_get_data(properties, "mix_out", NULL); int length = mlt_producer_get_playtime(MLT_TRACTOR_PRODUCER(tractor)); mlt_events_block(MLT_PLAYLIST_PROPERTIES(self), self); if (clip_a != NULL) { mlt_producer_set_in_and_out(clip_a, mlt_producer_get_in(clip_a), mlt_producer_get_out(clip_a) + length); } else { mlt_producer cut = mlt_tractor_get_track(tractor, 0); mlt_playlist_insert(self, cut, clip, -1, -1); clip++; } if (clip_b != NULL) { mlt_producer_set_in_and_out(clip_b, mlt_producer_get_in(clip_b) - length, mlt_producer_get_out(clip_b)); } else { mlt_producer cut = mlt_tractor_get_track(tractor, 1); mlt_playlist_insert(self, cut, clip + 1, -1, -1); } mlt_properties_set_data(properties, "mlt_mix", NULL, 0, NULL, NULL); mlt_playlist_remove(self, clip); mlt_events_unblock(MLT_PLAYLIST_PROPERTIES(self), self); mlt_playlist_virtual_refresh(self); } return error; } /** Resize a mix clip. * * \private \memberof mlt_playlist_s * \param self a playlist * \param clip the index of the playlist entry * \param in the new starting point * \param out the new ending point * \return true if there was an error */ static int mlt_playlist_resize_mix(mlt_playlist self, int clip, int in, int out) { int error = (clip < 0 || clip >= self->count); // Ensure that the clip request is actually a mix if (error == 0) { mlt_producer producer = mlt_producer_cut_parent(self->list[clip]->producer); mlt_properties properties = MLT_PRODUCER_PROPERTIES(producer); error = mlt_properties_get_data(properties, "mlt_mix", NULL) == NULL; } if (error == 0) { playlist_entry *mix = self->list[clip]; mlt_tractor tractor = (mlt_tractor) mlt_producer_cut_parent(mix->producer); mlt_properties properties = MLT_TRACTOR_PROPERTIES(tractor); mlt_producer clip_a = mlt_properties_get_data(properties, "mix_in", NULL); mlt_producer clip_b = mlt_properties_get_data(properties, "mix_out", NULL); mlt_producer track_a = mlt_tractor_get_track(tractor, 0); mlt_producer track_b = mlt_tractor_get_track(tractor, 1); int length = out - in + 1; int length_diff = length - mlt_producer_get_playtime(MLT_TRACTOR_PRODUCER(tractor)); mlt_events_block(MLT_PLAYLIST_PROPERTIES(self), self); if (clip_a != NULL) mlt_producer_set_in_and_out(clip_a, mlt_producer_get_in(clip_a), mlt_producer_get_out(clip_a) - length_diff); if (clip_b != NULL) mlt_producer_set_in_and_out(clip_b, mlt_producer_get_in(clip_b) + length_diff, mlt_producer_get_out(clip_b)); mlt_producer_set_in_and_out(track_a, mlt_producer_get_in(track_a) - length_diff, mlt_producer_get_out(track_a)); mlt_producer_set_in_and_out(track_b, mlt_producer_get_in(track_b), mlt_producer_get_out(track_b) + length_diff); mlt_producer_set_in_and_out(MLT_MULTITRACK_PRODUCER(mlt_tractor_multitrack(tractor)), in, out); mlt_producer_set_in_and_out(MLT_TRACTOR_PRODUCER(tractor), in, out); mlt_properties_set_position(MLT_PRODUCER_PROPERTIES(mix->producer), "length", out - in + 1); mlt_producer_set_in_and_out(mix->producer, in, out); mlt_events_unblock(MLT_PLAYLIST_PROPERTIES(self), self); mlt_playlist_virtual_refresh(self); } return error; } /** Consolidate adjacent blank producers. * * \public \memberof mlt_playlist_s * \param self a playlist * \param keep_length set false to remove the last entry if it is blank */ void mlt_playlist_consolidate_blanks(mlt_playlist self, int keep_length) { if (self != NULL) { int i = 0; mlt_properties properties = MLT_PLAYLIST_PROPERTIES(self); mlt_events_block(properties, properties); for (i = 1; i < self->count; i++) { playlist_entry *left = self->list[i - 1]; playlist_entry *right = self->list[i]; if (mlt_producer_is_blank(left->producer) && mlt_producer_is_blank(right->producer)) { mlt_playlist_resize_clip(self, i - 1, 0, left->frame_count + right->frame_count - 1); mlt_playlist_remove(self, i--); } } if (!keep_length && self->count > 0) { playlist_entry *last = self->list[self->count - 1]; if (mlt_producer_is_blank(last->producer)) mlt_playlist_remove(self, self->count - 1); } mlt_events_unblock(properties, properties); mlt_playlist_virtual_refresh(self); } } /** Determine if the specified clip index is a blank. * * \public \memberof mlt_playlist_s * \param self a playlist * \param clip the index of the playlist entry * \return true if \p clip is a "blank" producer */ int mlt_playlist_is_blank(mlt_playlist self, int clip) { return self == NULL || mlt_producer_is_blank(mlt_playlist_get_clip(self, clip)); } /** Determine if the specified position is a blank. * * \public \memberof mlt_playlist_s * \param self a playlist * \param position a time relative to the start or end (negative) of the playlist * \return true if there was an error */ int mlt_playlist_is_blank_at(mlt_playlist self, mlt_position position) { return self == NULL || mlt_producer_is_blank(mlt_playlist_get_clip_at(self, position)); } /** Replace the specified clip with a blank and return the clip. * * \public \memberof mlt_playlist_s * \param self a playlist * \param clip the index of the playlist entry * \return a producer or NULL if there was an error */ mlt_producer mlt_playlist_replace_with_blank(mlt_playlist self, int clip) { mlt_producer producer = NULL; if (!mlt_playlist_is_blank(self, clip)) { playlist_entry *entry = self->list[clip]; int in = entry->frame_in; int out = entry->frame_out; mlt_properties properties = MLT_PLAYLIST_PROPERTIES(self); producer = entry->producer; mlt_properties_inc_ref(MLT_PRODUCER_PROPERTIES(producer)); mlt_events_block(properties, properties); mlt_playlist_remove(self, clip); mlt_playlist_blank(self, out - in); mlt_playlist_move(self, self->count - 1, clip); mlt_events_unblock(properties, properties); mlt_playlist_virtual_refresh(self); mlt_producer_set_in_and_out(producer, in, out); } return producer; } /** Insert blank space. * * \public \memberof mlt_playlist_s * \param self a playlist * \param clip the index of the new blank section * \param out the ending time of the new blank section (duration - 1) */ void mlt_playlist_insert_blank(mlt_playlist self, int clip, int out) { if (self != NULL && out >= 0) { mlt_properties properties = MLT_PLAYLIST_PROPERTIES(self); mlt_events_block(properties, properties); mlt_playlist_blank(self, out); mlt_playlist_move(self, self->count - 1, clip); mlt_events_unblock(properties, properties); mlt_playlist_virtual_refresh(self); } } /** Resize a blank entry. * * \public \memberof mlt_playlist_s * \param self a playlist * \param position the time at which the blank entry exists relative to the start or end (negative) of the playlist. * \param length the additional amount of blank frames to add * \param find true to fist locate the blank after the clip at position */ void mlt_playlist_pad_blanks(mlt_playlist self, mlt_position position, int length, int find) { if (self != NULL && length != 0) { int clip = mlt_playlist_get_clip_index_at(self, position); mlt_properties properties = MLT_PLAYLIST_PROPERTIES(self); mlt_events_block(properties, properties); if (find && clip < self->count && !mlt_playlist_is_blank(self, clip)) clip++; if (clip < self->count && mlt_playlist_is_blank(self, clip)) { mlt_playlist_clip_info info; mlt_playlist_get_clip_info(self, &info, clip); if (info.frame_out + length > info.frame_in) mlt_playlist_resize_clip(self, clip, info.frame_in, info.frame_out + length); else mlt_playlist_remove(self, clip); } else if (find && clip < self->count && length > 0) { mlt_playlist_insert_blank(self, clip, length); } mlt_events_unblock(properties, properties); mlt_playlist_virtual_refresh(self); } } /** Insert a clip at a specific time. * * \public \memberof mlt_playlist_s * \param self a playlist * \param position the time at which to insert * \param producer the producer to insert * \param mode true if you want to overwrite any blank section * \return true if there was an error */ int mlt_playlist_insert_at(mlt_playlist self, mlt_position position, mlt_producer producer, int mode) { int ret = self == NULL || position < 0 || producer == NULL; if (ret == 0) { mlt_properties properties = MLT_PLAYLIST_PROPERTIES(self); int length = mlt_producer_get_playtime(producer); int clip = mlt_playlist_get_clip_index_at(self, position); mlt_playlist_clip_info info; mlt_playlist_get_clip_info(self, &info, clip); mlt_events_block(properties, self); if (clip < self->count && mlt_playlist_is_blank(self, clip)) { // Split and move to new clip if need be if (position != info.start && mlt_playlist_split(self, clip, position - info.start - 1) == 0) mlt_playlist_get_clip_info(self, &info, ++clip); // Split again if need be if (length < info.frame_count) mlt_playlist_split(self, clip, length - 1); // Remove mlt_playlist_remove(self, clip); // Insert mlt_playlist_insert(self, producer, clip, -1, -1); ret = clip; } else if (clip < self->count) { if (position > info.start + info.frame_count / 2) clip++; if (mode == 1 && clip < self->count && mlt_playlist_is_blank(self, clip)) { mlt_playlist_get_clip_info(self, &info, clip); if (length < info.frame_count) mlt_playlist_split(self, clip, length); mlt_playlist_remove(self, clip); } mlt_playlist_insert(self, producer, clip, -1, -1); ret = clip; } else { if (mode == 1) { if (position == info.start) mlt_playlist_remove(self, clip); else mlt_playlist_blank(self, position - mlt_properties_get_int(properties, "length") - 1); } mlt_playlist_append(self, producer); ret = self->count - 1; } mlt_events_unblock(properties, self); mlt_playlist_virtual_refresh(self); } else { ret = -1; } return ret; } /** Get the time at which the clip starts relative to the playlist. * * \public \memberof mlt_playlist_s * \param self a playlist * \param clip the index of the playlist entry * \return the starting time */ int mlt_playlist_clip_start(mlt_playlist self, int clip) { mlt_playlist_clip_info info; if (mlt_playlist_get_clip_info(self, &info, clip) == 0) return info.start; return clip < 0 ? 0 : mlt_producer_get_playtime(MLT_PLAYLIST_PRODUCER(self)); } /** Get the playable duration of the clip. * * \public \memberof mlt_playlist_s * \param self a playlist * \param clip the index of the playlist entry * \return the duration of the playlist entry */ int mlt_playlist_clip_length(mlt_playlist self, int clip) { mlt_playlist_clip_info info; if (mlt_playlist_get_clip_info(self, &info, clip) == 0) return info.frame_count; return 0; } /** Get the duration of a blank space. * * \public \memberof mlt_playlist_s * \param self a playlist * \param clip the index of the playlist entry * \param bounded the maximum number of blank entries or 0 for all * \return the duration of a blank section */ int mlt_playlist_blanks_from(mlt_playlist self, int clip, int bounded) { int count = 0; mlt_playlist_clip_info info; if (self != NULL && clip < self->count) { mlt_playlist_get_clip_info(self, &info, clip); if (mlt_playlist_is_blank(self, clip)) count += info.frame_count; if (bounded == 0) bounded = self->count; for (clip++; clip < self->count && bounded >= 0; clip++) { mlt_playlist_get_clip_info(self, &info, clip); if (mlt_playlist_is_blank(self, clip)) count += info.frame_count; else bounded--; } } return count; } /** Remove a portion of the playlist by time. * * \public \memberof mlt_playlist_s * \param self a playlist * \param position the starting time * \param length the duration of time to remove * \return the new entry index at the position */ int mlt_playlist_remove_region(mlt_playlist self, mlt_position position, int length) { int index = mlt_playlist_get_clip_index_at(self, position); if (index >= 0 && index < self->count) { mlt_properties properties = MLT_PLAYLIST_PROPERTIES(self); int clip_start = mlt_playlist_clip_start(self, index); int list_length = mlt_producer_get_playtime(MLT_PLAYLIST_PRODUCER(self)); mlt_events_block(properties, self); if (position + length > list_length) length -= (position + length - list_length); if (clip_start < position) { mlt_playlist_split(self, index++, position - clip_start - 1); } while (length > 0) { if (mlt_playlist_clip_length(self, index) > length) mlt_playlist_split(self, index, length - 1); length -= mlt_playlist_clip_length(self, index); mlt_playlist_remove(self, index); } mlt_playlist_consolidate_blanks(self, 0); mlt_events_unblock(properties, self); mlt_playlist_virtual_refresh(self); // Just to be sure, we'll get the clip index again... index = mlt_playlist_get_clip_index_at(self, position); } return index; } /** Get the current frame. * * The implementation of the get_frame virtual function. * \private \memberof mlt_playlist_s * \param producer a producer * \param frame a frame by reference * \param index the time at which to get the frame * \return false */ static int producer_get_frame(mlt_producer producer, mlt_frame_ptr frame, int index) { // Check that we have a producer if (producer == NULL) { *frame = NULL; return -1; } // Get this mlt_playlist mlt_playlist self = producer->child; // Need to ensure the frame is deinterlaced when repeating 1 frame int progressive = 0; int clip_index = -1; int clip_position = -1; // Get the real producer mlt_service real = mlt_playlist_virtual_seek(self, &progressive, &clip_index, &clip_position); // Check that we have a producer if (real == NULL) { *frame = mlt_frame_init(MLT_PRODUCER_SERVICE(producer)); return 0; } // Get the frame mlt_properties_inc_ref(MLT_SERVICE_PROPERTIES(real)); if (!mlt_properties_get_int(MLT_SERVICE_PROPERTIES(real), "meta.fx_cut")) { mlt_service_get_frame(real, frame, index); } else { mlt_producer parent = mlt_producer_cut_parent((mlt_producer) real); *frame = mlt_frame_init(MLT_PRODUCER_SERVICE(parent)); mlt_properties_set_int(MLT_FRAME_PROPERTIES(*frame), "fx_cut", 1); mlt_frame_push_service(*frame, NULL); mlt_frame_push_audio(*frame, NULL); mlt_service_apply_filters(MLT_PRODUCER_SERVICE(parent), *frame, 0); mlt_service_apply_filters(real, *frame, 0); mlt_deque_pop_front(MLT_FRAME_IMAGE_STACK(*frame)); mlt_deque_pop_front(MLT_FRAME_AUDIO_STACK(*frame)); } mlt_properties_dec_ref(MLT_SERVICE_PROPERTIES(real)); // Check if we're at the end of the clip mlt_properties properties = MLT_FRAME_PROPERTIES(*frame); if (mlt_properties_get_int(properties, "end_of_clip")) mlt_playlist_virtual_set_out(self); // Set the consumer progressive property if (progressive) { mlt_properties_set_int(properties, "consumer.progressive", progressive); mlt_properties_set_int(properties, "test_audio", 1); } if (clip_index >= 0 && clip_index < self->size) { mlt_properties_set_int(properties, "meta.playlist.clip_position", clip_position); mlt_properties_set_int(properties, "meta.playlist.clip_length", self->list[clip_index]->frame_count); } // Check for notifier and call with appropriate argument mlt_properties playlist_properties = MLT_PRODUCER_PROPERTIES(producer); void (*notifier)(void *) = mlt_properties_get_data(playlist_properties, "notifier", NULL); if (notifier != NULL) { void *argument = mlt_properties_get_data(playlist_properties, "notifier_arg", NULL); notifier(argument); } // Update position on the frame we're creating mlt_frame_set_position(*frame, mlt_producer_frame(producer)); // Position ourselves on the next frame mlt_producer_prepare_next(producer); return 0; } /** Close the playlist. * * \public \memberof mlt_playlist_s * \param self a playlist */ void mlt_playlist_close(mlt_playlist self) { if (self != NULL && mlt_properties_dec_ref(MLT_PLAYLIST_PROPERTIES(self)) <= 0) { int i = 0; self->parent.close = NULL; for (i = 0; i < self->count; i++) { mlt_event_close(self->list[i]->event); mlt_producer_close(self->list[i]->producer); free(self->list[i]); } mlt_producer_close(&self->parent); free(self->list); free(self); } } mlt_producer blank_producer(mlt_playlist self) { mlt_producer blank = (mlt_producer) mlt_properties_get_data(MLT_PLAYLIST_PROPERTIES(self), "_blank", NULL); if (!blank) { mlt_profile profile = mlt_service_profile(MLT_PLAYLIST_SERVICE(self)); if (!profile) { mlt_log_error(self, "Playlist can not create blank producer without profile\n"); } else { blank = mlt_factory_producer(profile, NULL, "blank"); mlt_properties_set_data(MLT_PLAYLIST_PROPERTIES(self), "_blank", blank, 0, (mlt_destructor) mlt_producer_close, NULL); } } return blank; } mlt-7.22.0/src/framework/mlt_playlist.h000664 000000 000000 00000015640 14531534050 020036 0ustar00rootroot000000 000000 /** * \file mlt_playlist.h * \brief playlist service class * \see mlt_playlist_s * * Copyright (C) 2003-2022 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef MLT_PLAYLIST_H #define MLT_PLAYLIST_H #include "mlt_producer.h" /** \brief structure for returning clip information from a playlist entry */ typedef struct { int clip; /**< the index of the clip within the playlist */ mlt_producer producer; /**< the clip's producer (or parent producer of a cut) */ mlt_producer cut; /**< the clips' cut producer */ mlt_position start; /**< the time this begins relative to the beginning of the playlist */ char *resource; /**< the file name or address of the clip */ mlt_position frame_in; /**< the clip's in point */ mlt_position frame_out; /**< the clip's out point */ mlt_position frame_count; /**< the duration of the clip */ mlt_position length; /**< the unedited duration of the clip */ float fps; /**< the frame rate of the clip */ int repeat; /**< the number of times the clip is repeated */ } mlt_playlist_clip_info; /** Playlist Entry */ typedef struct playlist_entry_s playlist_entry; /** \brief Playlist class * * A playlist is a sequential container of producers and blank spaces. The class provides all * sorts of playlist assembly and manipulation routines. A playlist is also a producer within * the framework. * * \extends mlt_producer_s * \properties \em autoclose Set this true if you are doing sequential processing and want to * automatically close producers as they are finished being used to free resources. * \properties \em meta.fx_cut Set true on a producer to indicate that it is a "fx_cut," * which is a way to add filters as a playlist entry - useful only in a multitrack. See FxCut in the docs. * \properties \em mix_in * \properties \em mix_out * \properties \em hide Set to 1 to hide the video (make it an audio-only track), * 2 to hide the audio (make it a video-only track), or 3 to hide audio and video (hidden track). * This property only applies when using a multitrack or transition. * \event \em playlist-next The playlist fires this when it moves to the next item in the list. * The event data is an integer of the index of the entry that just completed. */ struct mlt_playlist_s { struct mlt_producer_s parent; struct mlt_producer_s blank; /// Deprecated int size; int count; playlist_entry **list; }; #define MLT_PLAYLIST_PRODUCER(playlist) (&(playlist)->parent) #define MLT_PLAYLIST_SERVICE(playlist) MLT_PRODUCER_SERVICE(MLT_PLAYLIST_PRODUCER(playlist)) #define MLT_PLAYLIST_PROPERTIES(playlist) MLT_SERVICE_PROPERTIES(MLT_PLAYLIST_SERVICE(playlist)) extern mlt_playlist mlt_playlist_init(); extern mlt_playlist mlt_playlist_new(mlt_profile profile); extern mlt_producer mlt_playlist_producer(mlt_playlist self); extern mlt_service mlt_playlist_service(mlt_playlist self); extern mlt_properties mlt_playlist_properties(mlt_playlist self); extern int mlt_playlist_count(mlt_playlist self); extern int mlt_playlist_clear(mlt_playlist self); extern int mlt_playlist_append(mlt_playlist self, mlt_producer producer); extern int mlt_playlist_append_io(mlt_playlist self, mlt_producer producer, mlt_position in, mlt_position out); extern int mlt_playlist_blank(mlt_playlist self, mlt_position out); extern int mlt_playlist_blank_time(mlt_playlist self, const char *length); extern mlt_position mlt_playlist_clip(mlt_playlist self, mlt_whence whence, int index); extern int mlt_playlist_current_clip(mlt_playlist self); extern mlt_producer mlt_playlist_current(mlt_playlist self); extern int mlt_playlist_get_clip_info(mlt_playlist self, mlt_playlist_clip_info *info, int index); extern int mlt_playlist_insert( mlt_playlist self, mlt_producer producer, int where, mlt_position in, mlt_position out); extern int mlt_playlist_remove(mlt_playlist self, int where); extern int mlt_playlist_move(mlt_playlist self, int from, int to); extern int mlt_playlist_reorder(mlt_playlist self, const int *indices); extern int mlt_playlist_resize_clip(mlt_playlist self, int clip, mlt_position in, mlt_position out); extern int mlt_playlist_repeat_clip(mlt_playlist self, int clip, int repeat); extern int mlt_playlist_split(mlt_playlist self, int clip, mlt_position position); extern int mlt_playlist_split_at(mlt_playlist self, mlt_position position, int left); extern int mlt_playlist_join(mlt_playlist self, int clip, int count, int merge); extern int mlt_playlist_mix(mlt_playlist self, int clip, int length, mlt_transition transition); extern int mlt_playlist_mix_in(mlt_playlist self, int clip, int length); extern int mlt_playlist_mix_out(mlt_playlist self, int clip, int length); extern int mlt_playlist_mix_add(mlt_playlist self, int clip, mlt_transition transition); extern mlt_producer mlt_playlist_get_clip(mlt_playlist self, int clip); extern mlt_producer mlt_playlist_get_clip_at(mlt_playlist self, mlt_position position); extern int mlt_playlist_get_clip_index_at(mlt_playlist self, mlt_position position); extern int mlt_playlist_clip_is_mix(mlt_playlist self, int clip); extern void mlt_playlist_consolidate_blanks(mlt_playlist self, int keep_length); extern int mlt_playlist_is_blank(mlt_playlist self, int clip); extern int mlt_playlist_is_blank_at(mlt_playlist self, mlt_position position); extern void mlt_playlist_insert_blank(mlt_playlist self, int clip, int out); extern void mlt_playlist_pad_blanks(mlt_playlist self, mlt_position position, int length, int find); extern mlt_producer mlt_playlist_replace_with_blank(mlt_playlist self, int clip); extern int mlt_playlist_insert_at(mlt_playlist self, mlt_position position, mlt_producer producer, int mode); extern int mlt_playlist_clip_start(mlt_playlist self, int clip); extern int mlt_playlist_clip_length(mlt_playlist self, int clip); extern int mlt_playlist_blanks_from(mlt_playlist self, int clip, int bounded); extern int mlt_playlist_remove_region(mlt_playlist self, mlt_position position, int length); extern void mlt_playlist_close(mlt_playlist self); #endif mlt-7.22.0/src/framework/mlt_pool.c000664 000000 000000 00000025057 14531534050 017144 0ustar00rootroot000000 000000 /** * \file mlt_pool.c * \brief memory pooling functionality * \see mlt_pool_s * * Copyright (C) 2003-2018 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "mlt_deque.h" #include "mlt_log.h" #include "mlt_properties.h" #include #include #include // Not nice - memalign is defined here apparently? #ifdef linux #include #endif // Macros to re-assign system functions. #ifdef _WIN32 #define mlt_free _aligned_free #define mlt_alloc(X) _aligned_malloc((X), 16) #define mlt_realloc(X, Y) _aligned_realloc((X), (Y), 16) #else #define mlt_free free #ifdef linux #define mlt_alloc(X) memalign(16, (X)) #else #define mlt_alloc(X) malloc((X)) #endif #define mlt_realloc realloc #endif // We now require a compile-time define to use mlt_pool. #ifndef USE_MLT_POOL #define USE_MLT_POOL 1 #endif #if !USE_MLT_POOL void mlt_pool_init() {} void *mlt_pool_alloc(int size) { return mlt_alloc(size); } void *mlt_pool_realloc(void *ptr, int size) { return mlt_realloc(ptr, size); } void mlt_pool_release(void *release) { return mlt_free(release); } void mlt_pool_purge() {} void mlt_pool_close() {} void mlt_pool_stat() {} #else /** global singleton for tracking pools */ static mlt_properties pools = NULL; /** \brief Pool (memory) class */ typedef struct mlt_pool_s { pthread_mutex_t lock; ///< lock to prevent race conditions mlt_deque stack; ///< a stack of addresses to memory blocks int size; ///< the size of the memory block as a power of 2 int count; ///< the number of blocks in the pool } * mlt_pool; /** \brief private to mlt_pool_s, for tracking items to release * * Aligned to 16 byte in case we toss buffers to external assembly * optimized libraries (sse/altivec). */ typedef struct __attribute__((aligned(16))) mlt_release_s { mlt_pool pool; int references; } * mlt_release; /** Create a pool. * * \private \memberof mlt_pool_s * \param size the size of the memory blocks to hold as some power of two * \return a new pool object */ static mlt_pool pool_init(int size) { // Create the pool mlt_pool self = calloc(1, sizeof(struct mlt_pool_s)); // Initialise it if (self != NULL) { // Initialise the mutex pthread_mutex_init(&self->lock, NULL); // Create the stack self->stack = mlt_deque_init(); // Assign the size self->size = size; } // Return it return self; } /** Get an item from the pool. * * \private \memberof mlt_pool_s * \param self a pool * \return an opaque pointer */ static void *pool_fetch(mlt_pool self) { // We will generate a release object void *ptr = NULL; // Sanity check if (self != NULL) { // Lock the pool pthread_mutex_lock(&self->lock); // Check if the stack is empty if (mlt_deque_count(self->stack) != 0) { // Pop the top of the stack ptr = mlt_deque_pop_back(self->stack); // Assign the reference ((mlt_release) ptr)->references = 1; } else { // We need to generate a release item mlt_release release = mlt_alloc(self->size); // If out of memory, log it, reclaim memory, and try again. if (!release && self->size > 0) { mlt_log_fatal(NULL, "[mlt_pool] out of memory\n"); mlt_pool_purge(); release = mlt_alloc(self->size); } // Initialise it if (release != NULL) { // Increment the number of items allocated to this pool self->count++; // Assign the pool release->pool = self; // Assign the reference release->references = 1; // Determine the ptr ptr = (char *) release + sizeof(struct mlt_release_s); } } // Unlock the pool pthread_mutex_unlock(&self->lock); } // Return the generated release object return ptr; } /** Return an item to the pool. * * \private \memberof mlt_pool_s * \param ptr an opaque pointer */ static void pool_return(void *ptr) { // Sanity checks if (ptr != NULL) { // Get the release pointer mlt_release that = (void *) ((char *) ptr - sizeof(struct mlt_release_s)); // Get the pool mlt_pool self = that->pool; if (self != NULL) { // Lock the pool pthread_mutex_lock(&self->lock); // Push the that back back on to the stack mlt_deque_push_back(self->stack, ptr); // Unlock the pool pthread_mutex_unlock(&self->lock); return; } // Free the release itself mlt_free((char *) ptr - sizeof(struct mlt_release_s)); } } /** Destroy a pool. * * \private \memberof mlt_pool_s * \param self a pool */ static void pool_close(mlt_pool self) { if (self != NULL) { // We need to free up all items in the pool void *release = NULL; // Iterate through the stack until depleted while ((release = mlt_deque_pop_back(self->stack)) != NULL) { // We'll free this item now mlt_free((char *) release - sizeof(struct mlt_release_s)); } // We can now close the stack mlt_deque_close(self->stack); // Destroy the mutex pthread_mutex_destroy(&self->lock); // Close the pool free(self); } } /** Initialise the global pool. * * \public \memberof mlt_pool_s */ void mlt_pool_init() { // Loop variable used to create the pools int i = 0; // Create the pools pools = mlt_properties_new(); // Create the pools for (i = 8; i < 31; i++) { // Each properties item needs a name char name[32]; // Construct a pool mlt_pool pool = pool_init(1 << i); // Generate a name sprintf(name, "%d", i); // Register with properties mlt_properties_set_data(pools, name, pool, 0, (mlt_destructor) pool_close, NULL); } } /** Allocate size bytes from the pool. * * \public \memberof mlt_pool_s * \param size the number of bytes */ void *mlt_pool_alloc(int size) { // This will be used to obtain the pool to use mlt_pool pool = NULL; // Determines the index of the pool to use int index = 8; // Minimum size pooled is 256 bytes size += sizeof(struct mlt_release_s); while ((1 << index) < size) index++; // Now get the pool at the index pool = mlt_properties_get_data_at(pools, index - 8, NULL); // Now get the real item return pool_fetch(pool); } /** Allocate size bytes from the pool. * * \public \memberof mlt_pool_s * \param ptr an opaque pointer - can be in the pool or a new block to allocate * \param size the number of bytes */ void *mlt_pool_realloc(void *ptr, int size) { // Result to return void *result = NULL; // Check if we actually have an address if (ptr != NULL) { // Get the release pointer mlt_release that = (void *) ((char *) ptr - sizeof(struct mlt_release_s)); // If the current pool this ptr belongs to is big enough if (size > that->pool->size - sizeof(struct mlt_release_s)) { // Allocate result = mlt_pool_alloc(size); // Copy memcpy(result, ptr, that->pool->size - sizeof(struct mlt_release_s)); // Release mlt_pool_release(ptr); } else { // Nothing to do result = ptr; } } else { // Simply allocate result = mlt_pool_alloc(size); } return result; } /** Purge unused items in the pool. * * A form of garbage collection. * \public \memberof mlt_pool_s */ void mlt_pool_purge() { int i = 0; // For each pool for (i = 0; i < mlt_properties_count(pools); i++) { // Get the pool mlt_pool self = mlt_properties_get_data_at(pools, i, NULL); // Pointer to unused memory void *release = NULL; // Lock the pool pthread_mutex_lock(&self->lock); // We'll free all unused items now while ((release = mlt_deque_pop_back(self->stack)) != NULL) { mlt_free((char *) release - sizeof(struct mlt_release_s)); self->count--; } // Unlock the pool pthread_mutex_unlock(&self->lock); } } /** Release the allocated memory. * * \public \memberof mlt_pool_s * \param release an opaque pointer of a block in the pool */ void mlt_pool_release(void *release) { // Return to the pool pool_return(release); } /** Close the pool. * * \public \memberof mlt_pool_s */ void mlt_pool_close() { #ifdef _MLT_POOL_CHECKS_ mlt_pool_stat(); #endif // Close the properties mlt_properties_close(pools); } void mlt_pool_stat() { // Stats dump uint64_t allocated = 0, used = 0, s; int i = 0, c = mlt_properties_count(pools); mlt_log(NULL, MLT_LOG_VERBOSE, "%s: count %d\n", __FUNCTION__, c); for (i = 0; i < c; i++) { mlt_pool pool = mlt_properties_get_data_at(pools, i, NULL); if (pool->count) mlt_log_verbose(NULL, "%s: size %d allocated %d returned %d %c\n", __FUNCTION__, pool->size, pool->count, mlt_deque_count(pool->stack), pool->count != mlt_deque_count(pool->stack) ? '*' : ' '); s = pool->size; s *= pool->count; allocated += s; s = pool->count - mlt_deque_count(pool->stack); s *= pool->size; used += s; } mlt_log_verbose(NULL, "%s: allocated %" PRIu64 " bytes, used %" PRIu64 " bytes \n", __FUNCTION__, allocated, used); } #endif // NO_MLT_POOL mlt-7.22.0/src/framework/mlt_pool.h000664 000000 000000 00000002231 14531534050 017136 0ustar00rootroot000000 000000 /** * \file mlt_pool.h * \brief memory pooling functionality * \see mlt_pool_s * * Copyright (C) 2003-2014 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef MLT_POOL_H #define MLT_POOL_H extern void mlt_pool_init(); extern void *mlt_pool_alloc(int size); extern void *mlt_pool_realloc(void *ptr, int size); extern void mlt_pool_release(void *release); extern void mlt_pool_purge(); extern void mlt_pool_close(); extern void mlt_pool_stat(); #endif mlt-7.22.0/src/framework/mlt_producer.c000664 000000 000000 00000125470 14531534050 020016 0ustar00rootroot000000 000000 /** * \file mlt_producer.c * \brief abstraction for all producer services * \see mlt_producer_s * * Copyright (C) 2003-2022 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "mlt_producer.h" #include "mlt_factory.h" #include "mlt_frame.h" #include "mlt_log.h" #include "mlt_parser.h" #include "mlt_profile.h" #include #include #include #include // for stat() #include // for stat() #include // for strftime() and gtime() #include // for stat() /* Forward references. */ static int producer_get_frame(mlt_service self, mlt_frame_ptr frame, int index); static void mlt_producer_property_changed(mlt_service owner, mlt_producer self, mlt_event_data); static void mlt_producer_service_changed(mlt_service owner, mlt_producer self); /* for debugging */ //#define _MLT_PRODUCER_CHECKS_ 1 #ifdef _MLT_PRODUCER_CHECKS_ static int producers_created = 0; static int producers_destroyed = 0; #endif /** Initialize a producer service. * * \public \memberof mlt_producer_s * \param self the producer structure to initialize * \param child a pointer to the child object for the subclass * \return true if there was an error */ int mlt_producer_init(mlt_producer self, void *child) { // Check that we haven't received NULL int error = self == NULL; // Continue if no error if (error == 0) { #ifdef _MLT_PRODUCER_CHECKS_ producers_created++; #endif // Initialise the producer memset(self, 0, sizeof(struct mlt_producer_s)); // Associate with the child self->child = child; // Initialise the service if (mlt_service_init(&self->parent, self) == 0) { // The parent is the service mlt_service parent = &self->parent; // Define the parent close parent->close = (mlt_destructor) mlt_producer_close; parent->close_object = self; // For convenience, we'll assume the close_object is self self->close_object = self; // Get the properties of the parent mlt_properties properties = MLT_SERVICE_PROPERTIES(parent); // Set the default properties mlt_properties_set(properties, "mlt_type", "mlt_producer"); mlt_properties_set_position(properties, "_position", 0.0); mlt_properties_set_double(properties, "_frame", 0); mlt_properties_set_double(properties, "_speed", 1.0); mlt_properties_set_position(properties, "in", 0); char *e = getenv("MLT_DEFAULT_PRODUCER_LENGTH"); int p = e ? atoi(e) : 15000; mlt_properties_set_position(properties, "out", MAX(0, p - 1)); mlt_properties_set_position(properties, "length", p); mlt_properties_set(properties, "eof", "pause"); mlt_properties_set(properties, "resource", ""); // Override service get_frame parent->get_frame = producer_get_frame; mlt_events_listen(properties, self, "service-changed", (mlt_listener) mlt_producer_service_changed); mlt_events_listen(properties, self, "property-changed", (mlt_listener) mlt_producer_property_changed); mlt_events_register(properties, "producer-changed"); } } return error; } /** Listener for property changes. * * If the in, out, or length properties changed, fire a "producer-changed" event. * * \private \memberof mlt_producer_s * \param owner a service (ignored) * \param self the producer * \param name the property that changed */ static void mlt_producer_property_changed(mlt_service owner, mlt_producer self, mlt_event_data event_data) { const char *name = mlt_event_data_to_string(event_data); if (!name) return; if (!strcmp(name, "in") || !strcmp(name, "out") || !strcmp(name, "length")) mlt_events_fire(MLT_PRODUCER_PROPERTIES(mlt_producer_cut_parent(self)), "producer-changed", mlt_event_data_none()); } /** Listener for service changes. * * Fires the "producer-changed" event. * * \private \memberof mlt_producer_s * \param owner a service (ignored) * \param self the producer */ static void mlt_producer_service_changed(mlt_service owner, mlt_producer self) { mlt_events_fire(MLT_PRODUCER_PROPERTIES(mlt_producer_cut_parent(self)), "producer-changed", mlt_event_data_none()); } /** Create and initialize a new producer. * * \public \memberof mlt_producer_s * \return the new producer */ mlt_producer mlt_producer_new(mlt_profile profile) { mlt_producer self = malloc(sizeof(struct mlt_producer_s)); if (self) { if (mlt_producer_init(self, NULL) == 0) { mlt_properties_set_data(MLT_PRODUCER_PROPERTIES(self), "_profile", profile, 0, NULL, NULL); mlt_properties_set_double(MLT_PRODUCER_PROPERTIES(self), "aspect_ratio", mlt_profile_sar(profile)); } else { free(self); return NULL; } } return self; } /** Determine if producer is a cut. * * \public \memberof mlt_producer_s * \param self a producer * \return true if \p self is a "cut" producer * \see mlt_producer_cut */ int mlt_producer_is_cut(mlt_producer self) { return mlt_properties_get_int(MLT_PRODUCER_PROPERTIES(self), "_cut"); } /** Determine if producer is a mix. * * \public \memberof mlt_producer_s * \param self a producer * \return true if \p self is a "mix" producer * \todo Define a mix producer. */ int mlt_producer_is_mix(mlt_producer self) { mlt_properties properties = self != NULL ? MLT_PRODUCER_PROPERTIES(self) : NULL; mlt_tractor tractor = properties != NULL ? mlt_properties_get_data(properties, "mlt_mix", NULL) : NULL; return tractor != NULL; } /** Determine if the producer is a blank. * * Blank producers should only appear as an item in a playlist. * \public \memberof mlt_producer_s * \param self a producer * \return true if \p self is a "blank" producer * \see mlt_playlist_insert_blank */ int mlt_producer_is_blank(mlt_producer self) { if (self) { const char *resource = mlt_properties_get(MLT_PRODUCER_PROPERTIES( mlt_producer_cut_parent(self)), "resource"); return (resource && !strcmp("blank", resource)); } return (self == NULL); } /** Obtain the parent producer. * * \public \memberof mlt_producer_s * \param self a producer * \return either the parent producer if \p self is a "cut" producer or \p self otherwise. */ mlt_producer mlt_producer_cut_parent(mlt_producer self) { mlt_properties properties = MLT_PRODUCER_PROPERTIES(self); if (mlt_producer_is_cut(self)) return mlt_properties_get_data(properties, "_cut_parent", NULL); else return self; } /** Create a cut of this producer. * * A "cut" is a portion of another (parent) producer. * * \public \memberof mlt_producer_s * \param self a producer * \param in the beginning * \param out the end * \return the new producer * \todo Expand on the value of a cut. */ mlt_producer mlt_producer_cut(mlt_producer self, int in, int out) { mlt_producer result = mlt_producer_new(mlt_service_profile(MLT_PRODUCER_SERVICE(self))); mlt_producer parent = mlt_producer_cut_parent(self); mlt_properties properties = MLT_PRODUCER_PROPERTIES(result); mlt_properties parent_props = MLT_PRODUCER_PROPERTIES(parent); mlt_properties_set_lcnumeric(properties, mlt_properties_get_lcnumeric(MLT_PRODUCER_PROPERTIES(self))); mlt_events_block(MLT_PRODUCER_PROPERTIES(result), MLT_PRODUCER_PROPERTIES(result)); // Special case - allow for a cut of the entire producer (this will squeeze all other cuts to 0) if (in <= 0) in = 0; if ((out < 0 || out >= mlt_producer_get_length(parent)) && !mlt_producer_is_blank(self)) out = MAX(0, mlt_producer_get_length(parent) - 1); mlt_properties_inc_ref(parent_props); mlt_properties_set_int(properties, "_cut", 1); mlt_properties_set_data(properties, "_cut_parent", parent, 0, (mlt_destructor) mlt_producer_close, NULL); mlt_properties_set_position(properties, "length", mlt_properties_get_position(parent_props, "length")); mlt_properties_set_double(properties, "aspect_ratio", mlt_properties_get_double(parent_props, "aspect_ratio")); mlt_producer_set_in_and_out(result, in, out); return result; } /** Get the parent service object. * * \public \memberof mlt_producer_s * \param self a producer * \return the service parent class * \see MLT_PRODUCER_SERVICE */ mlt_service mlt_producer_service(mlt_producer self) { return self != NULL ? &self->parent : NULL; } /** Get the producer properties. * * \public \memberof mlt_producer_s * \param self a producer * \return the producer's property list * \see MLT_PRODUCER_PROPERTIES */ mlt_properties mlt_producer_properties(mlt_producer self) { return MLT_SERVICE_PROPERTIES(&self->parent); } /** Seek to a specified position. * * \public \memberof mlt_producer_s * \param self a producer * \param position set the "play head" position of the producer * \return false * \todo Document how the properties affect behavior. * \see mlt_producer_seek_time */ int mlt_producer_seek(mlt_producer self, mlt_position position) { if (self->seek) { return self->seek(self, position); } // Determine eof handling mlt_properties properties = MLT_PRODUCER_PROPERTIES(self); char *eof = mlt_properties_get(properties, "eof"); int use_points = 1 - mlt_properties_get_int(properties, "ignore_points"); // Recursive behaviour for cuts - repositions parent and then repositions cut // hence no return on this condition if (mlt_producer_is_cut(self)) mlt_producer_seek(mlt_producer_cut_parent(self), position + mlt_producer_get_in(self)); // Check bounds if (mlt_service_identify(MLT_PRODUCER_SERVICE(self)) == mlt_service_link_type) { // Do not bounds check a link. } else if (position < 0 || mlt_producer_get_playtime(self) == 0) { position = 0; } else if (use_points && (eof == NULL || !strcmp(eof, "pause")) && position >= mlt_producer_get_playtime(self)) { mlt_producer_set_speed(self, 0); position = mlt_producer_get_playtime(self) - 1; } else if (use_points && eof && !strcmp(eof, "loop") && position >= mlt_producer_get_playtime(self)) { position = (int) position % (int) mlt_producer_get_playtime(self); } // Set the position mlt_properties_set_position(MLT_PRODUCER_PROPERTIES(self), "_position", position); // Calculate the absolute frame mlt_properties_set_position(MLT_PRODUCER_PROPERTIES(self), "_frame", use_points * mlt_producer_get_in(self) + position); return 0; } /** Seek to a specified time string. * * \public \memberof mlt_producer_s * \param self a producer * \param time set the "play head" position of the producer to the time string * \return false * \see mlt_producer_seek */ int mlt_producer_seek_time(mlt_producer self, const char *time) { mlt_properties_set(MLT_PRODUCER_PROPERTIES(self), "_seek_time", time); mlt_position position = mlt_properties_get_position(MLT_PRODUCER_PROPERTIES(self), "_seek_time"); return mlt_producer_seek(self, position); } /** Get the current position (relative to in point). * * \public \memberof mlt_producer_s * \param self a producer * \return the position of the "play head" relative to its beginning */ mlt_position mlt_producer_position(mlt_producer self) { return mlt_properties_get_position(MLT_PRODUCER_PROPERTIES(self), "_position"); } /** Get the current position (relative to start of producer). * * \public \memberof mlt_producer_s * \param self a producer * \return the position of the "play head" regardless of the in point */ mlt_position mlt_producer_frame(mlt_producer self) { return mlt_properties_get_position(MLT_PRODUCER_PROPERTIES(self), "_frame"); } /** Get the current position (relative to start of producer) as a time string. * * \public \memberof mlt_producer_s * \param self a producer * \param format the time value format * \return the position of the "play head" regardless of the in point */ char *mlt_producer_frame_time(mlt_producer self, mlt_time_format format) { return mlt_properties_get_time(MLT_PRODUCER_PROPERTIES(self), "_frame", format); } /** Set the playing speed. * * \public \memberof mlt_producer_s * \param self a producer * \param speed the new speed as a relative factor (1.0 = normal) * \return true if error */ int mlt_producer_set_speed(mlt_producer self, double speed) { return mlt_properties_set_double(MLT_PRODUCER_PROPERTIES(self), "_speed", speed); } /** Get the playing speed. * * \public \memberof mlt_producer_s * \param self a producer * \return the speed as a relative factor (1.0 = normal) */ double mlt_producer_get_speed(mlt_producer self) { return mlt_properties_get_double(MLT_PRODUCER_PROPERTIES(self), "_speed"); } /** Get the frames per second. * * This is determined by the producer's profile. * * \public \memberof mlt_producer_s * \param self a producer * \return the video refresh rate */ double mlt_producer_get_fps(mlt_producer self) { mlt_profile profile = mlt_service_profile(MLT_PRODUCER_SERVICE(self)); return mlt_profile_fps(profile); } /** Set the in and out points. * * The in point is where play out should start relative to the natural start * of the underlying file. The out point is where play out should end, also * relative to the start of the underlying file. If the underlying resource is * a live stream, then the in point is an offset relative to first usable * sample. * * \public \memberof mlt_producer_s * \param self a producer * \param in the relative starting time; a negative value is the same as 0 * \param out the relative ending time; a negative value is the same as length - 1 * \return false */ int mlt_producer_set_in_and_out(mlt_producer self, mlt_position in, mlt_position out) { if (self->set_in_and_out) { return self->set_in_and_out(self, in, out); } mlt_properties properties = MLT_PRODUCER_PROPERTIES(self); // Correct ins and outs if necessary if (in < 0) in = 0; else if (in >= mlt_producer_get_length(self)) in = MAX(0, mlt_producer_get_length(self) - 1); if (mlt_producer_is_blank(self) && out >= mlt_producer_get_length(self)) // Extend the blank producer if needed. mlt_properties_set_position(MLT_PRODUCER_PROPERTIES(self), "length", out + 1); else if (out < 0 || out >= mlt_producer_get_length(self)) // Get the out point from the length. out = MAX(0, mlt_producer_get_length(self) - 1); // Swap ins and outs if wrong if (out < in) { mlt_position t = in; in = out; out = t; } // Set the values mlt_events_block(properties, properties); mlt_properties_set_position(properties, "in", in); mlt_events_unblock(properties, properties); mlt_properties_set_position(properties, "out", out); return 0; } /** Physically reduce the producer (typically a cut) to a 0 length. * Essentially, all 0 length cuts should be immediately removed by containers. * * \public \memberof mlt_producer_s * \param self a producer * \return false */ int mlt_producer_clear(mlt_producer self) { if (self != NULL) { mlt_properties properties = MLT_PRODUCER_PROPERTIES(self); mlt_events_block(properties, properties); mlt_properties_set_position(properties, "in", 0); mlt_events_unblock(properties, properties); mlt_properties_set_position(properties, "out", -1); } return 0; } /** Get the in point. * * \public \memberof mlt_producer_s * \param self a producer * \return the in point */ mlt_position mlt_producer_get_in(mlt_producer self) { return mlt_properties_get_position(MLT_PRODUCER_PROPERTIES(self), "in"); } /** Get the out point. * * \public \memberof mlt_producer_s * \param self a producer * \return the out point */ mlt_position mlt_producer_get_out(mlt_producer self) { return mlt_properties_get_position(MLT_PRODUCER_PROPERTIES(self), "out"); } /** Get the total play time. * * \public \memberof mlt_producer_s * \param self a producer * \return the playable (based on in and out points) duration */ mlt_position mlt_producer_get_playtime(mlt_producer self) { return mlt_producer_get_out(self) - mlt_producer_get_in(self) + 1; } /** Get the total, unedited length of the producer. * * The value returned by a live streaming producer is unknown. * * \public \memberof mlt_producer_s * \param self a producer * \return the duration of the producer regardless of in and out points */ mlt_position mlt_producer_get_length(mlt_producer self) { return mlt_properties_get_position(MLT_PRODUCER_PROPERTIES(self), "length"); } /** Get the total, unedited length of the producer as a time string. * * The value returned by a live streaming producer is unknown. * * \public \memberof mlt_producer_s * \param self a producer * \param format the time value format * \return the duration of the producer regardless of in and out points */ char *mlt_producer_get_length_time(mlt_producer self, mlt_time_format format) { return mlt_properties_get_time(MLT_PRODUCER_PROPERTIES(self), "length", format); } /** Prepare for next frame. * * Advance the play out position. If the speed is less than zero, it will * move the play out position in the reverse direction. * * \public \memberof mlt_producer_s * \param self a producer */ void mlt_producer_prepare_next(mlt_producer self) { if (mlt_producer_get_speed(self) != 0) mlt_producer_seek(self, mlt_producer_position(self) + mlt_producer_get_speed(self)); } /** Get a frame. * * This is the implementation of the \p get_frame virtual function. * It requests a new frame object from the actual producer for the current * play out position. The producer and its filters can add information and * operations to the frame object in their get_frame handlers. * * \private \memberof mlt_producer_s * \param service a service * \param[out] frame a frame by reference * \param index as determined by the actual producer * \return true if there was an error * \todo Learn more about the details and document how certain properties affect * its behavior. */ static int producer_get_frame(mlt_service service, mlt_frame_ptr frame, int index) { int result = 1; mlt_producer self = service != NULL ? service->child : NULL; if (self != NULL && !mlt_producer_is_cut(self)) { // Get the properties of this producer mlt_properties properties = MLT_PRODUCER_PROPERTIES(self); // Determine eof handling char *eof = mlt_properties_get(MLT_PRODUCER_PROPERTIES(self), "eof"); // Get the speed of the producer double speed = mlt_producer_get_speed(self); // We need to use the clone if it's specified mlt_producer clone = mlt_properties_get_data(properties, "use_clone", NULL); // If no clone is specified, use self clone = clone == NULL ? self : clone; // A properly instatiated producer will have a get_frame method... if (self->get_frame == NULL || (eof && !strcmp(eof, "continue") && mlt_producer_position(self) > mlt_producer_get_out(self))) { // Generate a test frame *frame = mlt_frame_init(service); // Set the position result = mlt_frame_set_position(*frame, mlt_producer_position(self)); // Mark as a test card mlt_properties_set_int(MLT_FRAME_PROPERTIES(*frame), "test_image", 1); mlt_properties_set_int(MLT_FRAME_PROPERTIES(*frame), "test_audio", 1); // Calculate the next position mlt_producer_prepare_next(self); } else { // Get the frame from the implementation result = self->get_frame(clone, frame, index); } // Copy the fps and speed of the producer onto the frame properties = MLT_FRAME_PROPERTIES(*frame); mlt_properties_set_double(properties, "_speed", speed); mlt_properties_set_int(properties, "test_audio", mlt_frame_is_test_audio(*frame)); mlt_properties_set_int(properties, "test_image", mlt_frame_is_test_card(*frame)); if (mlt_properties_get_data(properties, "_producer", NULL) == NULL) mlt_properties_set_data(properties, "_producer", service, 0, NULL, NULL); } else if (self != NULL) { // Get the speed of the cut double speed = mlt_producer_get_speed(self); // Get the parent of the cut mlt_producer parent = mlt_producer_cut_parent(self); // Get the properties of the parent mlt_properties parent_properties = MLT_PRODUCER_PROPERTIES(parent); // Get the properties of the cut mlt_properties properties = MLT_PRODUCER_PROPERTIES(self); // Determine the clone index int clone_index = mlt_properties_get_int(properties, "_clone"); // Determine the clone to use mlt_producer clone = self; if (clone_index > 0) { char key[25]; sprintf(key, "_clone.%d", clone_index - 1); clone = mlt_properties_get_data(MLT_PRODUCER_PROPERTIES(mlt_producer_cut_parent(self)), key, NULL); if (clone == NULL) mlt_log(service, MLT_LOG_ERROR, "requested clone doesn't exist %d\n", clone_index); clone = clone == NULL ? self : clone; } else { clone = parent; } // We need to seek to the correct position in the clone mlt_producer_seek(clone, mlt_producer_get_in(self) + mlt_properties_get_int(properties, "_position")); // Assign the clone property to the parent mlt_properties_set_data(parent_properties, "use_clone", clone, 0, NULL, NULL); // Now get the frame from the parents service result = mlt_service_get_frame(MLT_PRODUCER_SERVICE(parent), frame, index); // We're done with the clone now mlt_properties_set_data(parent_properties, "use_clone", NULL, 0, NULL, NULL); // This is useful and required by always_active transitions to determine in/out points of the cut if (mlt_properties_get_data(MLT_FRAME_PROPERTIES(*frame), "_producer", NULL) == MLT_PRODUCER_SERVICE(parent)) mlt_properties_set_data(MLT_FRAME_PROPERTIES(*frame), "_producer", self, 0, NULL, NULL); mlt_properties_set_double(MLT_FRAME_PROPERTIES(*frame), "_speed", speed); mlt_producer_prepare_next(self); } else { *frame = mlt_frame_init(service); result = 0; } // Pass on all meta properties from the producer/cut on to the frame if (*frame != NULL && self != NULL) { mlt_properties p_props = MLT_PRODUCER_PROPERTIES(self); mlt_properties f_props = MLT_FRAME_PROPERTIES(*frame); mlt_properties_lock(p_props); mlt_properties_copy(f_props, p_props, "meta."); mlt_properties_pass(f_props, p_props, "set."); mlt_properties_unlock(p_props); } return result; } /** Attach a filter. * * \public \memberof mlt_producer_s * \param self a producer * \param filter the filter to attach * \return true if there was an error */ int mlt_producer_attach(mlt_producer self, mlt_filter filter) { return mlt_service_attach(MLT_PRODUCER_SERVICE(self), filter); } /** Detach a filter. * * \public \memberof mlt_producer_s * \param self a service * \param filter the filter to detach * \return true if there was an error */ int mlt_producer_detach(mlt_producer self, mlt_filter filter) { return mlt_service_detach(MLT_PRODUCER_SERVICE(self), filter); } /** Retrieve a filter. * * \public \memberof mlt_producer_s * \param self a service * \param index which filter to retrieve * \return the filter or null if there was an error */ mlt_filter mlt_producer_filter(mlt_producer self, int index) { return mlt_service_filter(MLT_PRODUCER_SERVICE(self), index); } /** Clone a producer. * * \private \memberof mlt_producer_s * \param self a producer * \return a new producer that is a copy of \p self * \see mlt_producer_set_clones */ static mlt_producer mlt_producer_clone(mlt_producer self) { mlt_producer clone = NULL; mlt_properties properties = MLT_PRODUCER_PROPERTIES(self); char *resource = mlt_properties_get(properties, "resource"); char *service = mlt_properties_get(properties, "mlt_service"); mlt_profile profile = mlt_service_profile(MLT_PRODUCER_SERVICE(self)); mlt_events_block(mlt_factory_event_object(), mlt_factory_event_object()); if (service != NULL) clone = mlt_factory_producer(profile, service, resource); if (clone == NULL && resource != NULL) clone = mlt_factory_producer(profile, NULL, resource); if (clone != NULL) mlt_properties_inherit(MLT_PRODUCER_PROPERTIES(clone), properties); mlt_events_unblock(mlt_factory_event_object(), mlt_factory_event_object()); return clone; } /** Create clones. * * \private \memberof mlt_producer_s * \param self a producer * \param clones the number of copies to make * \see mlt_producer_optimise */ static void mlt_producer_set_clones(mlt_producer self, int clones) { mlt_producer parent = mlt_producer_cut_parent(self); mlt_properties properties = MLT_PRODUCER_PROPERTIES(parent); int existing = mlt_properties_get_int(properties, "_clones"); int i = 0; char key[25]; // If the number of existing clones is different, then create/remove as necessary if (existing != clones) { if (existing < clones) { for (i = existing; i < clones; i++) { mlt_producer clone = mlt_producer_clone(parent); sprintf(key, "_clone.%d", i); mlt_properties_set_data(properties, key, clone, 0, (mlt_destructor) mlt_producer_close, NULL); } } else { for (i = clones; i < existing; i++) { sprintf(key, "_clone.%d", i); mlt_properties_set_data(properties, key, NULL, 0, NULL, NULL); } } } // Ensure all properties on the parent are passed to the clones for (i = 0; i < clones; i++) { mlt_producer clone = NULL; sprintf(key, "_clone.%d", i); clone = mlt_properties_get_data(properties, key, NULL); if (clone != NULL) mlt_properties_pass(MLT_PRODUCER_PROPERTIES(clone), properties, ""); } // Update the number of clones on the properties mlt_properties_set_int(properties, "_clones", clones); } /** \brief private to mlt_producer_s, used by mlt_producer_optimise() */ typedef struct { int multitrack; int track; int position; int length; int offset; } track_info; /** \brief private to mlt_producer_s, used by mlt_producer_optimise() */ typedef struct { mlt_producer cut; int start; int end; } clip_references; static int intersect(clip_references *a, clip_references *b) { int diff = (a->start - b->start) + (a->end - b->end); return diff >= 0 && diff < (a->end - a->start + 1); } static int push(mlt_parser self, int multitrack, int track, int position) { mlt_properties properties = mlt_parser_properties(self); mlt_deque stack = mlt_properties_get_data(properties, "stack", NULL); track_info *info = malloc(sizeof(track_info)); info->multitrack = multitrack; info->track = track; info->position = position; info->length = 0; info->offset = 0; return mlt_deque_push_back(stack, info); } static track_info *pop(mlt_parser self) { mlt_properties properties = mlt_parser_properties(self); mlt_deque stack = mlt_properties_get_data(properties, "stack", NULL); return mlt_deque_pop_back(stack); } static track_info *peek(mlt_parser self) { mlt_properties properties = mlt_parser_properties(self); mlt_deque stack = mlt_properties_get_data(properties, "stack", NULL); return mlt_deque_peek_back(stack); } static int on_start_multitrack(mlt_parser self, mlt_multitrack object) { track_info *info = peek(self); return push(self, info->multitrack++, info->track, info->position); } static int on_start_track(mlt_parser self) { track_info *info = peek(self); info->position -= info->offset; info->length -= info->offset; return push(self, info->multitrack, info->track++, info->position); } static int on_start_producer(mlt_parser self, mlt_producer object) { mlt_properties properties = mlt_parser_properties(self); mlt_properties producers = mlt_properties_get_data(properties, "producers", NULL); mlt_producer parent = mlt_producer_cut_parent(object); if (mlt_service_identify((mlt_service) mlt_producer_cut_parent(object)) == mlt_service_producer_type && mlt_producer_is_cut(object)) { int ref_count = 0; clip_references *old_refs = NULL; clip_references *refs = NULL; char key[50]; int count = 0; track_info *info = peek(self); sprintf(key, "%p", parent); mlt_properties_get_data(producers, key, &count); mlt_properties_set_data(producers, key, parent, ++count, NULL, NULL); old_refs = mlt_properties_get_data(properties, key, &ref_count); refs = malloc((ref_count + 1) * sizeof(clip_references)); if (old_refs != NULL) memcpy(refs, old_refs, ref_count * sizeof(clip_references)); mlt_properties_set_int(MLT_PRODUCER_PROPERTIES(object), "_clone", -1); refs[ref_count].cut = object; refs[ref_count].start = info->position; refs[ref_count].end = info->position + mlt_producer_get_playtime(object) - 1; mlt_properties_set_data(properties, key, refs, ++ref_count, free, NULL); info->position += mlt_producer_get_playtime(object); info->length += mlt_producer_get_playtime(object); } return 0; } static int on_end_track(mlt_parser self) { track_info *track = pop(self); track_info *multi = peek(self); multi->length += track->length; multi->position += track->length; multi->offset = track->length; free(track); return 0; } static int on_end_multitrack(mlt_parser self, mlt_multitrack object) { track_info *multi = pop(self); track_info *track = peek(self); track->position += multi->length; track->length += multi->length; free(multi); return 0; } /** Optimise for overlapping cuts from the same clip. * * \todo learn more about this * \public \memberof mlt_producer_s * \param self a producer * \return true if there was an error */ int mlt_producer_optimise(mlt_producer self) { int error = 1; mlt_parser parser = mlt_parser_new(); if (parser != NULL) { int i = 0, j = 0, k = 0; mlt_properties properties = mlt_parser_properties(parser); mlt_properties producers = mlt_properties_new(); mlt_deque stack = mlt_deque_init(); mlt_properties_set_data(properties, "producers", producers, 0, (mlt_destructor) mlt_properties_close, NULL); mlt_properties_set_data(properties, "stack", stack, 0, (mlt_destructor) mlt_deque_close, NULL); parser->on_start_producer = on_start_producer; parser->on_start_track = on_start_track; parser->on_end_track = on_end_track; parser->on_start_multitrack = on_start_multitrack; parser->on_end_multitrack = on_end_multitrack; push(parser, 0, 0, 0); mlt_parser_start(parser, MLT_PRODUCER_SERVICE(self)); free(pop(parser)); for (k = 0; k < mlt_properties_count(producers); k++) { char *name = mlt_properties_get_name(producers, k); int count = 0; int clones = 0; int max_clones = 0; mlt_producer producer = mlt_properties_get_data_at(producers, k, &count); if (producer != NULL && count > 1) { clip_references *refs = mlt_properties_get_data(properties, name, &count); for (i = 0; i < count; i++) { clones = 0; for (j = i + 1; j < count; j++) { if (intersect(&refs[i], &refs[j])) { clones++; mlt_properties_set_int(MLT_PRODUCER_PROPERTIES(refs[j].cut), "_clone", clones); } } if (clones > max_clones) max_clones = clones; } for (i = 0; i < count; i++) { mlt_producer cut = refs[i].cut; if (mlt_properties_get_int(MLT_PRODUCER_PROPERTIES(cut), "_clone") == -1) mlt_properties_set_int(MLT_PRODUCER_PROPERTIES(cut), "_clone", 0); } mlt_producer_set_clones(producer, max_clones); } else if (producer != NULL) { clip_references *refs = mlt_properties_get_data(properties, name, &count); for (i = 0; i < count; i++) { mlt_producer cut = refs[i].cut; mlt_properties_set_int(MLT_PRODUCER_PROPERTIES(cut), "_clone", 0); } mlt_producer_set_clones(producer, 0); } } mlt_parser_close(parser); } return error; } /** Close the producer. * * Destroys the producer and deallocates its resources managed by its * properties list. This will call the close virtual function. Therefore, a * subclass that defines its own close function should set its virtual close * function to NULL prior to calling this to avoid circular calls. * * \public \memberof mlt_producer_s * \param self a producer */ void mlt_producer_close(mlt_producer self) { if (self != NULL && mlt_properties_dec_ref(MLT_PRODUCER_PROPERTIES(self)) <= 0) { self->parent.close = NULL; if (self->close != NULL) { self->close(self->close_object); } else { int destroy = mlt_producer_is_cut(self); #if _MLT_PRODUCER_CHECKS_ == 1 // Show debug info mlt_properties_debug(MLT_PRODUCER_PROPERTIES(self), "Producer closing", stderr); #endif #ifdef _MLT_PRODUCER_CHECKS_ // Show current stats - these should match when the app is closed mlt_log(MLT_PRODUCER_SERVICE(self), MLT_LOG_DEBUG, "Producers created %d, destroyed %d\n", producers_created, ++producers_destroyed); #endif mlt_service_close(&self->parent); if (destroy) free(self); } } } /* * Boost implementation of timegm() * (C) Copyright Howard Hinnant * (C) Copyright 2010-2011 Vicente J. Botet Escriba */ static inline int32_t is_leap(int32_t year) { if (year % 400 == 0) return 1; if (year % 100 == 0) return 0; if (year % 4 == 0) return 1; return 0; } static inline int32_t days_from_0(int32_t year) { year--; return 365 * year + (year / 400) - (year / 100) + (year / 4); } static inline int32_t days_from_1970(int32_t year) { const int days_from_0_to_1970 = days_from_0(1970); return days_from_0(year) - days_from_0_to_1970; } static inline int32_t days_from_1jan(int32_t year, int32_t month, int32_t day) { static const int32_t days[2][12] = {{0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334}, {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335}}; return days[is_leap(year)][month - 1] + day - 1; } static inline time_t internal_timegm(struct tm const *t) { int year = t->tm_year + 1900; int month = t->tm_mon; if (month > 11) { year += month / 12; month %= 12; } else if (month < 0) { int years_diff = (-month + 11) / 12; year -= years_diff; month += 12 * years_diff; } month++; int day = t->tm_mday; int day_of_year = days_from_1jan(year, month, day); int days_since_epoch = days_from_1970(year) + day_of_year; time_t seconds_in_day = 3600 * 24; time_t result = seconds_in_day * days_since_epoch + 3600 * t->tm_hour + 60 * t->tm_min + t->tm_sec; return result; } /* End of Boost implementation of timegm(). */ /** Get the creation time for the producer. * * The creation_time value is searched in the following order: * - A "creation_time" property in ISO 8601 format (yyyy-mm-ddThh:mm:ss) * - A "meta.attr.com.apple.quicktime.creationdate.markup" property in ISO 8601 format * - A "meta.attr.creation_time.markup" property in ISO 8601 format * - If the producer has a resource that is a file, the mtime of the file * * \public \memberof mlt_producer_s * \param self a producer * \return the creation time of the producer in seconds since the epoch */ int64_t mlt_producer_get_creation_time(mlt_producer self) { mlt_producer producer = mlt_producer_cut_parent(self); // Prefer creation_time producer property if present char *datestr = mlt_properties_get(MLT_PRODUCER_PROPERTIES(producer), "creation_time"); if (!datestr) { // Fall back to quicktime creationdate metadata (common for .mov files) // creationdate is preferred over creation_time metadata because // creation_time may be recalculated if the device re-encodes the file. datestr = mlt_properties_get(MLT_PRODUCER_PROPERTIES(producer), "meta.attr.com.apple.quicktime.creationdate.markup"); } if (!datestr) { // Fall back to creation_time metadata (common for most media handled by ffmpeg) datestr = mlt_properties_get(MLT_PRODUCER_PROPERTIES(producer), "meta.attr.creation_time.markup"); } if (datestr) { struct tm time_info = {0}; double seconds; char offset_indicator = 0; int hour_offset = 0; int min_offset = 0; int ret = sscanf(datestr, "%04d-%02d-%02dT%02d:%02d:%lf%c%02d%02d", &time_info.tm_year, &time_info.tm_mon, &time_info.tm_mday, &time_info.tm_hour, &time_info.tm_min, &seconds, &offset_indicator, &hour_offset, &min_offset); if (ret >= 6) { time_info.tm_sec = (int) seconds; time_info.tm_mon -= 1; time_info.tm_year -= 1900; time_info.tm_isdst = -1; int64_t milliseconds = (int64_t) internal_timegm(&time_info) * 1000; milliseconds += (seconds - (double) time_info.tm_sec) * 1000.0; // Apply time zone offset if present. if (ret == 9 && offset_indicator == '-') { milliseconds += ((hour_offset * 60) + min_offset) * 60000; } else if (ret == 9 && offset_indicator == '+') { milliseconds -= ((hour_offset * 60) + min_offset) * 60000; } return milliseconds; } } // Fall back to file modification time. char *resource = mlt_properties_get(MLT_PRODUCER_PROPERTIES(producer), "resource"); if (!resource) { resource = mlt_properties_get(MLT_PRODUCER_PROPERTIES(producer), "warp_resource"); } if (resource) { struct stat file_info; if (!stat(resource, &file_info)) { return (int64_t) file_info.st_mtime * 1000; } } return 0; } /** Set the creation time for the producer. * * A "creation_time" property in ISO 8601 format (yyyy-mm-ddThh:mm:ss) will be * applied to the producer. * * \public \memberof mlt_producer_s * \param self a producer * \param creation_time the creation time of the producer in seconds since the epoch */ void mlt_producer_set_creation_time(mlt_producer self, int64_t creation_time) { time_t time = creation_time / 1000; mlt_producer parent = mlt_producer_cut_parent(self); char *datestr = calloc(1, 20); strftime(datestr, 20, "%Y-%m-%dT%H:%M:%S", gmtime(&time)); mlt_properties_set(MLT_PRODUCER_PROPERTIES(parent), "creation_time", datestr); free(datestr); } /** Probe the producer to publish metadata properties. * * After this call the producer will publish meta.media properties * * \public \memberof mlt_producer_s * \param self a producer * \return true on error */ int mlt_producer_probe(mlt_producer self) { if (self) { int (*probe)(mlt_producer) = mlt_properties_get_data(MLT_PRODUCER_PROPERTIES(self), "mlt_producer_probe", NULL); if (probe) return probe(self); } return 0; } mlt-7.22.0/src/framework/mlt_producer.h000664 000000 000000 00000016113 14531534050 020014 0ustar00rootroot000000 000000 /** * \file mlt_producer.h * \brief abstraction for all producer services * \see mlt_producer_s * * Copyright (C) 2003-2014 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef MLT_PRODUCER_H #define MLT_PRODUCER_H #include "mlt_filter.h" #include "mlt_profile.h" #include "mlt_service.h" /** \brief Producer abstract service class * * A producer is a service that generates audio, video, and metadata. * Some day it may also generate text (subtitles). This is not to say * a producer "synthesizes," rather that is an origin of data within the * service network - that could be through synthesis or reading a stream. * * \extends mlt_service * \event \em producer-changed either service-changed was fired or the timing of the producer changed * \properties \em mlt_type the name of the service subclass, e.g. mlt_producer * \properties \em mlt_service the name of a producer subclass * \properties \em _position the current position of the play head, relative to the in point * \properties \em _frame the current position of the play head, relative to the beginning of the resource * \properties \em _speed the current speed factor, where 1.0 is normal * \properties \em aspect_ratio sample aspect ratio * \properties \em length the duration of the cut in frames * \properties \em eof the end-of-file behavior, one of: pause, continue, loop * \properties \em resource the file name, stream address, or the class name in angle brackets * \properties \em _cut set if this producer is a "cut" producer * \properties \em mlt_mix stores the data for a "mix" producer * \properties \em _cut_parent holds a reference to the cut's parent producer * \properties \em ignore_points Set this to temporarily disable the in and out points. * \properties \em use_clone holds a reference to a clone's producer, as created by mlt_producer_optimise * \properties \em _clone is the index of the clone in the list of clones stored on the clone's producer * \properties \em _clones is the number of clones of the producer, as created by mlt_producer_optimise * \properties \em _clone.{N} holds a reference to the N'th clone of the producer, as created by mlt_producer_optimise * \properties \em meta.* holds metadata - there is a loose taxonomy to be defined * \properties \em set.* holds properties to set on a frame produced * \envvar \em MLT_DEFAULT_PRODUCER_LENGTH - the default duration of the producer in frames, defaults to 15000. * Most producers will set the producer length to something appropriate * like the real duration of an audio or video clip. However, some other things * like still images and generators do not have an intrinsic length besides one * or infinity. Those producers tend to not override the default length and one * expect the app or user to set the length. The default value of 15000 was chosen * to provide something useful - not too long or short and convenient to simply * set an out point without necessarily nedding to extend the length. * \todo define the media metadata taxonomy */ struct mlt_producer_s { /** A producer is a service. */ struct mlt_service_s parent; /** Get a frame of data (virtual function). * * \param mlt_producer a producer * \param mlt_frame_ptr a frame pointer by reference * \param int an index * \return true if there was an error */ int (*get_frame)(mlt_producer, mlt_frame_ptr, int); /** Seek to a specified position (virtual function). * * \param mlt_producer a producer * \param position set the "play head" position of the producer * \return false */ int (*seek)(mlt_producer, mlt_position); /** Set the in and out points. * * \param mlt_producer a producer * \param mlt_position the relative starting time; a negative value is the same as 0 * \param mlt_position the relative ending time; a negative value is the same as length - 1 * \return false */ int (*set_in_and_out)(mlt_producer, mlt_position, mlt_position); /** the destructor virtual function */ mlt_destructor close; void *close_object; /**< the object supplied to the close virtual function */ void *local; /**< \private instance object */ void *child; /**< \private the object of a subclass */ }; /* * Public final methods */ #define MLT_PRODUCER_SERVICE(producer) (&(producer)->parent) #define MLT_PRODUCER_PROPERTIES(producer) MLT_SERVICE_PROPERTIES(MLT_PRODUCER_SERVICE(producer)) extern int mlt_producer_init(mlt_producer self, void *child); extern mlt_producer mlt_producer_new(mlt_profile); extern mlt_service mlt_producer_service(mlt_producer self); extern mlt_properties mlt_producer_properties(mlt_producer self); extern int mlt_producer_seek(mlt_producer self, mlt_position position); extern int mlt_producer_seek_time(mlt_producer self, const char *time); extern mlt_position mlt_producer_position(mlt_producer self); extern mlt_position mlt_producer_frame(mlt_producer self); char *mlt_producer_frame_time(mlt_producer self, mlt_time_format); extern int mlt_producer_set_speed(mlt_producer self, double speed); extern double mlt_producer_get_speed(mlt_producer self); extern double mlt_producer_get_fps(mlt_producer self); extern int mlt_producer_set_in_and_out(mlt_producer self, mlt_position in, mlt_position out); extern int mlt_producer_clear(mlt_producer self); extern mlt_position mlt_producer_get_in(mlt_producer self); extern mlt_position mlt_producer_get_out(mlt_producer self); extern mlt_position mlt_producer_get_playtime(mlt_producer self); extern mlt_position mlt_producer_get_length(mlt_producer self); extern char *mlt_producer_get_length_time(mlt_producer self, mlt_time_format); extern void mlt_producer_prepare_next(mlt_producer self); extern int mlt_producer_attach(mlt_producer self, mlt_filter filter); extern int mlt_producer_detach(mlt_producer self, mlt_filter filter); extern mlt_filter mlt_producer_filter(mlt_producer self, int index); extern mlt_producer mlt_producer_cut(mlt_producer self, int in, int out); extern int mlt_producer_is_cut(mlt_producer self); extern int mlt_producer_is_mix(mlt_producer self); extern int mlt_producer_is_blank(mlt_producer self); extern mlt_producer mlt_producer_cut_parent(mlt_producer self); extern int mlt_producer_optimise(mlt_producer self); extern void mlt_producer_close(mlt_producer self); int64_t mlt_producer_get_creation_time(mlt_producer self); void mlt_producer_set_creation_time(mlt_producer self, int64_t creation_time); extern int mlt_producer_probe(mlt_producer self); #endif mlt-7.22.0/src/framework/mlt_profile.c000664 000000 000000 00000040377 14531534050 017635 0ustar00rootroot000000 000000 /** * \file mlt_profile.c * \brief video output definition * \see mlt_profile_s * * Copyright (C) 2007-2022 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "mlt.h" #include #include #include #include /** the default subdirectory of the datadir for holding profiles */ #define PROFILES_DIR "/profiles/" /** Load a profile from the system folder. * * The environment variable MLT_PROFILES_PATH overrides the default \p PROFILES_DIR. * * \private \memberof mlt_profile_s * \param name the name of a profile settings file located in the standard location or * the full path name to a profile settings file * \return a profile or NULL on error */ static mlt_profile mlt_profile_select(const char *name) { char *filename = NULL; const char *prefix = getenv("MLT_PROFILES_PATH"); mlt_properties properties = mlt_properties_load(name); mlt_profile profile = NULL; // Try to load from file specification if (properties && mlt_properties_get_int(properties, "width")) { filename = calloc(1, strlen(name) + 1); } // Load from $datadir/mlt/profiles else if (!prefix && mlt_environment("MLT_DATA")) { prefix = mlt_environment("MLT_DATA"); filename = calloc(1, strlen(prefix) + strlen(PROFILES_DIR) + strlen(name) + 1); strcpy(filename, prefix); strcat(filename, PROFILES_DIR); } // Use environment variable instead else if (prefix) { filename = calloc(1, strlen(prefix) + strlen(name) + 2); strcpy(filename, prefix); if (filename[strlen(filename) - 1] != '/') filename[strlen(filename)] = '/'; } else { mlt_properties_close(properties); return profile; } // Finish loading strcat(filename, name); profile = mlt_profile_load_file(filename); // Cleanup mlt_properties_close(properties); free(filename); return profile; } /** Construct a profile. * * This will never return NULL as it uses the dv_pal settings as hard-coded fallback default. * * \public \memberof mlt_profile_s * \param name the name of a profile settings file located in the standard location or * the full path name to a profile settings file * \return a profile */ mlt_profile mlt_profile_init(const char *name) { mlt_profile profile = NULL; // Explicit profile by name gets priority over environment variables if (name) profile = mlt_profile_select(name); // Try to load by environment variable if (profile == NULL) { // MLT_PROFILE is preferred environment variable if (getenv("MLT_PROFILE")) profile = mlt_profile_select(getenv("MLT_PROFILE")); // MLT_NORMALISATION backwards compatibility else if (getenv("MLT_NORMALISATION") && strcmp(getenv("MLT_NORMALISATION"), "PAL")) profile = mlt_profile_select("dv_ntsc"); else profile = mlt_profile_select("dv_pal"); // If still not loaded (no profile files), default to PAL if (profile == NULL) { profile = calloc(1, sizeof(struct mlt_profile_s)); if (profile) { mlt_environment_set("MLT_PROFILE", "dv_pal"); profile->description = strdup("PAL 4:3 DV or DVD"); profile->frame_rate_num = 25; profile->frame_rate_den = 1; profile->width = 720; profile->height = 576; profile->progressive = 0; profile->sample_aspect_num = 16; profile->sample_aspect_den = 15; profile->display_aspect_num = 4; profile->display_aspect_den = 3; profile->colorspace = 601; } } } return profile; } static void set_mlt_normalization(const char *profile_name) { if (profile_name) { if (strstr(profile_name, "_ntsc") || strstr(profile_name, "_60") || strstr(profile_name, "_5994") || strstr(profile_name, "_2997") || strstr(profile_name, "_30")) { mlt_environment_set("MLT_NORMALISATION", "NTSC"); } else if (strstr(profile_name, "_pal") || strstr(profile_name, "_50") || strstr(profile_name, "_25")) { mlt_environment_set("MLT_NORMALISATION", "PAL"); } } } /** Load a profile from specific file. * * \public \memberof mlt_profile_s * \param file the full path name to a properties file * \return a profile or NULL on error */ mlt_profile mlt_profile_load_file(const char *file) { mlt_profile profile = NULL; // Load the profile as properties mlt_properties properties = mlt_properties_load(file); if (properties) { // Simple check if the profile is valid if (mlt_properties_get_int(properties, "width")) { profile = mlt_profile_load_properties(properties); // Set MLT_PROFILE to basename char *filename = strdup(file); mlt_environment_set("MLT_PROFILE", basename(filename)); set_mlt_normalization(basename(filename)); free(filename); } mlt_properties_close(properties); } // Set MLT_NORMALISATION to appease legacy modules char *profile_name = mlt_environment("MLT_PROFILE"); set_mlt_normalization(profile_name); return profile; } /** Load a profile from a properties object. * * \public \memberof mlt_profile_s * \param properties a properties list * \return a profile or NULL if out of memory */ mlt_profile mlt_profile_load_properties(mlt_properties properties) { mlt_profile profile = calloc(1, sizeof(struct mlt_profile_s)); if (profile) { if (mlt_properties_get(properties, "name")) mlt_environment_set("MLT_PROFILE", mlt_properties_get(properties, "name")); if (mlt_properties_get(properties, "description")) profile->description = strdup(mlt_properties_get(properties, "description")); profile->frame_rate_num = mlt_properties_get_int(properties, "frame_rate_num"); profile->frame_rate_den = mlt_properties_get_int(properties, "frame_rate_den"); profile->width = mlt_properties_get_int(properties, "width"); profile->height = mlt_properties_get_int(properties, "height"); profile->progressive = mlt_properties_get_int(properties, "progressive"); profile->sample_aspect_num = mlt_properties_get_int(properties, "sample_aspect_num"); profile->sample_aspect_den = mlt_properties_get_int(properties, "sample_aspect_den"); profile->display_aspect_num = mlt_properties_get_int(properties, "display_aspect_num"); profile->display_aspect_den = mlt_properties_get_int(properties, "display_aspect_den"); profile->colorspace = mlt_properties_get_int(properties, "colorspace"); } return profile; } /** Load an anonymous profile from string. * * \public \memberof mlt_profile_s * \param string a newline-delimited list of properties as name=value pairs * \return a profile or NULL if out of memory */ mlt_profile mlt_profile_load_string(const char *string) { mlt_properties properties = mlt_properties_new(); mlt_profile profile = NULL; if (properties) { const char *p = string; while (p) { if (strcmp(p, "") && p[0] != '#') mlt_properties_parse(properties, p); p = strchr(p, '\n'); if (p) p++; } profile = mlt_profile_load_properties(properties); mlt_properties_close(properties); } return profile; } /** Get the video frame rate as a floating point value. * * \public \memberof mlt_profile_s * @param profile a profile * @return the frame rate */ double mlt_profile_fps(mlt_profile profile) { if (profile) return (double) profile->frame_rate_num / profile->frame_rate_den; else return 0; } /** Get the sample aspect ratio as a floating point value. * * \public \memberof mlt_profile_s * \param profile a profile * \return the pixel aspect ratio */ double mlt_profile_sar(mlt_profile profile) { if (profile) return (double) profile->sample_aspect_num / profile->sample_aspect_den; else return 0; } /** Get the display aspect ratio as floating point value. * * \public \memberof mlt_profile_s * \param profile a profile * \return the image aspect ratio */ double mlt_profile_dar(mlt_profile profile) { if (profile) return (double) profile->display_aspect_num / profile->display_aspect_den; else return 0; } /** Free up the global profile resources. * * \public \memberof mlt_profile_s * \param profile a profile */ void mlt_profile_close(mlt_profile profile) { if (profile) { free(profile->description); profile->description = NULL; free(profile); profile = NULL; } } /** Make a copy of a profile. * * \public \memberof mlt_profile_s * \param profile the profile to clone * \return a copy of the profile */ mlt_profile mlt_profile_clone(mlt_profile profile) { mlt_profile clone = NULL; if (profile) { clone = calloc(1, sizeof(*profile)); if (clone) { memcpy(clone, profile, sizeof(*profile)); clone->description = strdup(profile->description); } } return clone; } /** Get the list of profiles. * * The caller MUST close the returned properties object! * Each entry in the list is keyed on its name, and its value is another * properties object that contains the attributes of the profile. * \public \memberof mlt_profile_s * \return a list of profiles */ mlt_properties mlt_profile_list() { char *filename = NULL; const char *prefix = getenv("MLT_PROFILES_PATH"); mlt_properties properties = mlt_properties_new(); mlt_properties dir = mlt_properties_new(); int sort = 1; const char *wildcard = NULL; int i; // Load from $datadir/mlt/profiles if no env var if (prefix == NULL) { prefix = mlt_environment("MLT_DATA"); if (prefix) { filename = calloc(1, strlen(prefix) + strlen(PROFILES_DIR) + 1); strcpy(filename, prefix); strcat(filename, PROFILES_DIR); } else { filename = calloc(1, strlen(PROFILES_DIR) + 1); strcpy(filename, PROFILES_DIR); } prefix = filename; } mlt_properties_dir_list(dir, prefix, wildcard, sort); for (i = 0; i < mlt_properties_count(dir); i++) { char *filename = mlt_properties_get_value(dir, i); char *profile_name = basename(filename); if (profile_name[0] != '.' && strcmp(profile_name, "Makefile") && profile_name[strlen(profile_name) - 1] != '~') { mlt_properties profile = mlt_properties_load(filename); if (profile) { mlt_properties_set_data(properties, profile_name, profile, 0, (mlt_destructor) mlt_properties_close, NULL); } } } mlt_properties_close(dir); free(filename); return properties; } /** Update the profile using the attributes of a producer. * * Use this to make an "auto-profile." Typically, you need to re-open the producer * after you use this because some producers (e.g. avformat) adjust their framerate * to that of the profile used when you created it. * \public \memberof mlt_profile_s * \param profile the profile to update * \param producer the producer to inspect */ void mlt_profile_from_producer(mlt_profile profile, mlt_producer producer) { mlt_producer_probe(producer); mlt_properties p = MLT_PRODUCER_PROPERTIES(producer); // mlt_properties_dump(p, stderr); if (mlt_properties_get_int(p, "meta.media.frame_rate_den") && mlt_properties_get_int(p, "meta.media.sample_aspect_den")) { profile->width = mlt_properties_get_int(p, "meta.media.width"); profile->height = mlt_properties_get_int(p, "meta.media.height"); profile->progressive = mlt_properties_get_int(p, "meta.media.progressive"); if (1000 > mlt_properties_get_double(p, "meta.media.frame_rate_num") / mlt_properties_get_double(p, "meta.media.frame_rate_den")) { profile->frame_rate_num = mlt_properties_get_int(p, "meta.media.frame_rate_num"); profile->frame_rate_den = mlt_properties_get_int(p, "meta.media.frame_rate_den"); } else { profile->frame_rate_num = 60; profile->frame_rate_den = 1; } // AVCHD is mis-reported as double frame rate. if (profile->progressive == 0 && (profile->frame_rate_num / profile->frame_rate_den == 50 || profile->frame_rate_num / profile->frame_rate_den == 59)) profile->frame_rate_num /= 2; profile->sample_aspect_num = mlt_properties_get_int(p, "meta.media.sample_aspect_num"); profile->sample_aspect_den = mlt_properties_get_int(p, "meta.media.sample_aspect_den"); profile->colorspace = mlt_properties_get_int(p, "meta.media.colorspace"); int n = profile->display_aspect_num = profile->sample_aspect_num * profile->width; int m = profile->display_aspect_den = profile->sample_aspect_den * profile->height; int gcd, remainder; while (n) { remainder = m % n; m = n; n = remainder; } gcd = m; profile->display_aspect_num /= gcd; profile->display_aspect_den /= gcd; free(profile->description); profile->description = strdup("automatic"); profile->is_explicit = 0; } } /** Get the lumas subdirectory to use for the aspect ratio. * * \public \memberof mlt_profile_s * \param profile the profile to update * \return the name of a subdirectory generated by the lumas module */ char *mlt_profile_lumas_dir(mlt_profile profile) { if (profile) { if (profile->display_aspect_num == profile->display_aspect_den) { mlt_environment_set("MLT_LUMAS_DIR", "square"); // [-inf, 0.8) => 9:16 } else if (mlt_profile_dar(profile) < 0.8) { mlt_environment_set("MLT_LUMAS_DIR", "9_16"); // 0.5625 // [0.8, 1.3) => 1:1 } else if (mlt_profile_dar(profile) < 1.3) { mlt_environment_set("MLT_LUMAS_DIR", "square"); // [1.3, 1.5) => 4:3 } else if (mlt_profile_dar(profile) < 1.5) { if (profile->frame_rate_num == 30000 && profile->frame_rate_den == 1001) mlt_environment_set("MLT_LUMAS_DIR", "NTSC"); // 1.5 else mlt_environment_set("MLT_LUMAS_DIR", "PAL"); // 1.25 // [1.5, inf] => 16:9 } else { mlt_environment_set("MLT_LUMAS_DIR", "16_9"); } } else { mlt_environment_set("MLT_LUMAS_DIR", "16_9"); } return mlt_environment("MLT_LUMAS_DIR"); } /** Get the width scale factor. * * \public \memberof mlt_profile_s * \param profile the profile to reference * \param width the number of pixels the consumer requested * \return the scale factor for the width */ double mlt_profile_scale_width(mlt_profile profile, int width) { return (profile && width && profile->width) ? (double) width / profile->width : 1.0; } /** Get the height scale factor. * * \public \memberof mlt_profile_s * \param profile the profile to reference * \param height the number of pixels the consumer requested * \return the scale factor for the height */ double mlt_profile_scale_height(mlt_profile profile, int height) { return (profile && profile->width) ? (double) height / profile->height : 1.0; } mlt-7.22.0/src/framework/mlt_profile.h000664 000000 000000 00000006166 14531534050 017640 0ustar00rootroot000000 000000 /** * \file mlt_profile.h * \brief video output definition * \see mlt_profile_s * * Copyright (C) 2007-2018 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef MLT_PROFILE_H #define MLT_PROFILE_H #include "mlt_types.h" /** \brief Profile class * * \envvar \em MLT_PROFILES_PATH overrides the default full path to the profile preset files, defaults to \p MLT_DATA/profiles * \envvar \em MLT_PROFILE the profile preset to use, defaults to "dv_pal" */ struct mlt_profile_s { char *description; /**< a brief description suitable as a label in UI menu */ int frame_rate_num; /**< the numerator of the video frame rate */ int frame_rate_den; /**< the denominator of the video frame rate */ int width; /**< the horizontal resolution of the video */ int height; /**< the vertical resolution of the video */ int progressive; /**< a flag to indicate if the video is progressive scan, interlace if not set */ int sample_aspect_num; /**< the numerator of the pixel aspect ratio */ int sample_aspect_den; /**< the denominator of the pixel aspect ratio */ int display_aspect_num; /**< the numerator of the image aspect ratio in case it can not be simply derived (e.g. ITU-R 601) */ int display_aspect_den; /**< the denominator of the image aspect ratio in case it can not be simply derived (e.g. ITU-R 601) */ int colorspace; /**< the Y'CbCr colorspace standard: =601 for ITU-R 601, =709 for ITU-R 709, or =240 for SMPTE240M */ int is_explicit; /**< used internally to indicate if the profile was requested explicitly or computed or defaulted */ }; extern mlt_profile mlt_profile_init(const char *name); extern mlt_profile mlt_profile_load_file(const char *file); extern mlt_profile mlt_profile_load_properties(mlt_properties properties); extern mlt_profile mlt_profile_load_string(const char *string); extern double mlt_profile_fps(mlt_profile profile); extern double mlt_profile_sar(mlt_profile profile); extern double mlt_profile_dar(mlt_profile profile); extern void mlt_profile_close(mlt_profile profile); extern mlt_profile mlt_profile_clone(mlt_profile profile); extern mlt_properties mlt_profile_list(); extern void mlt_profile_from_producer(mlt_profile profile, mlt_producer producer); extern char *mlt_profile_lumas_dir(mlt_profile profile); extern double mlt_profile_scale_width(mlt_profile profile, int width); extern double mlt_profile_scale_height(mlt_profile profile, int height); #endif mlt-7.22.0/src/framework/mlt_properties.c000664 000000 000000 00000253321 14531534050 020364 0ustar00rootroot000000 000000 /** * \file mlt_properties.c * \brief Properties class definition * \see mlt_properties_s * * Copyright (C) 2003-2022 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ // For strtod_l #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #include "mlt_properties.h" #include "mlt_deque.h" #include "mlt_factory.h" #include "mlt_log.h" #include "mlt_property.h" #include #include #include #include #include #include #include #include #include #include #include #include #define MAX_LOAD_LINE_SIZE 4096 /** \brief private implementation of the property list */ typedef struct { int hash[199]; char **name; mlt_property *value; int count; int size; mlt_properties mirror; int ref_count; pthread_mutex_t mutex; mlt_locale_t locale; mlt_properties *children_properties; char **children_names; int children_count; } property_list; /* Memory leak checks */ //#define _MLT_PROPERTY_CHECKS_ 2 #ifdef _MLT_PROPERTY_CHECKS_ static int properties_created = 0; static int properties_destroyed = 0; #endif /** Initialize a properties object that was already allocated. * * This does allocate its ::property_list, and it adds a reference count. * \public \memberof mlt_properties_s * \param self the properties structure to initialize * \param child an opaque pointer to a subclass object * \return true if failed */ int mlt_properties_init(mlt_properties self, void *child) { if (self != NULL) { #ifdef _MLT_PROPERTY_CHECKS_ // Increment number of properties created properties_created++; #endif // NULL all methods memset(self, 0, sizeof(struct mlt_properties_s)); // Assign the child of the object self->child = child; // Allocate the local structure self->local = calloc(1, sizeof(property_list)); // Increment the ref count ((property_list *) self->local)->ref_count = 1; pthread_mutex_init(&((property_list *) self->local)->mutex, NULL); ; } // Check that initialisation was successful return self != NULL && self->local == NULL; } /** Create a properties object. * * This allocates the properties structure and calls mlt_properties_init() on it. * Free the properties object with mlt_properties_close(). * \public \memberof mlt_properties_s * \return a new properties object */ mlt_properties mlt_properties_new() { // Construct a standalone properties object mlt_properties self = calloc(1, sizeof(struct mlt_properties_s)); // Initialise self mlt_properties_init(self, NULL); // Return the pointer return self; } /** Set the numeric locale used for string/double conversions. * * \public \memberof mlt_properties_s * \param self a properties list * \param locale the locale name * \return true if error */ int mlt_properties_set_lcnumeric(mlt_properties self, const char *locale) { int error = 0; #if !defined(_WIN32) if (self && locale) { property_list *list = self->local; #if defined(__GLIBC__) || defined(__APPLE__) if (list->locale) freelocale(list->locale); list->locale = newlocale(LC_NUMERIC_MASK, locale, NULL); #else free(list->locale); list->locale = strdup(locale); #endif } else error = 1; #endif // _WIN32 return error; } /** Get the numeric locale for this properties object. * * Do not free the result. * \public \memberof mlt_properties_s * \param self a properties list * \return the locale name if this properties has a specific locale it is using, NULL otherwise */ const char *mlt_properties_get_lcnumeric(mlt_properties self) { if (!self) return NULL; const char *result = NULL; #if !defined(_WIN32) property_list *list = self->local; if (list->locale) { #if defined(__APPLE__) result = querylocale(LC_NUMERIC_MASK, list->locale); #elif defined(__GLIBC__) result = list->locale->__names[LC_NUMERIC]; #else result = list->locale; #endif #if defined(_WIN32) if (result) { // Convert the string from ANSI code page to UTF-8. mlt_properties_set_string(self, "_lcnumeric_in", result); mlt_properties_to_utf8(self, "_lcnumeric_in", "_lcnumeric_out"); result = mlt_properties_get(self, "_lcnumeric_out"); } #endif } #endif // _WIN32 return result; } static int load_properties(mlt_properties self, const char *filename) { // Open the file FILE *file = mlt_fopen(filename, "r"); // Load contents of file if (file != NULL) { // Temp string char temp[MAX_LOAD_LINE_SIZE]; char last[MAX_LOAD_LINE_SIZE] = ""; // Read each string from the file while (fgets(temp, MAX_LOAD_LINE_SIZE, file)) { // Chomp the new line character from the string int x = strlen(temp) - 1; if (temp[x] == '\n' || temp[x] == '\r') temp[x] = '\0'; // Check if the line starts with a . if (temp[0] == '.') { char temp2[MAX_LOAD_LINE_SIZE]; strcpy(temp2, last); strncat(temp2, temp, sizeof(temp2) - strlen(temp2) - 1); temp2[sizeof(temp2) - 1] = '\0'; strcpy(temp, temp2); } else if (strchr(temp, '=')) { strcpy(last, temp); *(strchr(last, '=')) = '\0'; } // Parse and set the property if (strcmp(temp, "") && temp[0] != '#') mlt_properties_parse(self, temp); } // Close the file fclose(file); } return file ? 0 : errno; } /** Create a properties object by reading a .properties text file. * * Free the properties object with mlt_properties_close(). * \deprecated Please start using mlt_properties_parse_yaml(). * \public \memberof mlt_properties_s * \param filename the absolute file name * \return a new properties object */ mlt_properties mlt_properties_load(const char *filename) { // Construct a standalone properties object mlt_properties self = mlt_properties_new(); if (self != NULL) load_properties(self, filename); // Return the pointer return self; } /** Set properties from a preset. * * Presets are typically installed to $prefix/share/mlt/presets/{type}/{service}/[{profile}/]{name}. * For example, "/usr/share/mlt/presets/consumer/avformat/dv_ntsc_wide/DVD" * could be an encoding preset for a widescreen NTSC DVD Video. * Do not specify the type and service in the preset name parameter; these are * inferred automatically from the service to which you are applying the preset. * Using the example above and assuming you are calling this function on the * avformat consumer, the name passed to the function should simply be DVD. * Note that the profile portion of the path is optional, but a profile-specific * preset with the same name as a more generic one is given a higher priority. * \todo Look in a user-specific location - somewhere in the home directory. * * \public \memberof mlt_properties_s * \param self a properties list * \param name the name of a preset in a well-known location or the explicit path * \return true if error */ int mlt_properties_preset(mlt_properties self, const char *name) { struct stat stat_buff; // validate input if (!(self && name && strlen(name))) return 1; // See if name is an explicit file if (!stat(name, &stat_buff)) { return load_properties(self, name); } else { // Look for profile-specific preset before a generic one. const char *data = mlt_environment("MLT_PRESETS_PATH"); const char *type = mlt_properties_get(self, "mlt_type"); const char *service = mlt_properties_get(self, "mlt_service"); const char *profile = mlt_environment("MLT_PROFILE"); int error = 0; if (data && type && service) { char *path = malloc(5 + strlen(name) + strlen(data) + strlen(type) + strlen(service) + (profile ? strlen(profile) : 0)); sprintf(path, "%s/%s/%s/%s/%s", data, type, service, profile, name); if (load_properties(self, path)) { sprintf(path, "%s/%s/%s/%s", data, type, service, name); error = load_properties(self, path); } free(path); } else { error = 1; } return error; } } /** Generate a hash key. * * \private \memberof mlt_properties_s * \param name a string * \return an integer */ static inline int generate_hash(const char *name) { unsigned int hash = 5381; while (*name) hash = hash * 33 + (unsigned int) (*name++); return hash % 199; } /** Copy a serializable property to a properties list that is mirroring this one. * * Special case - when a container (such as loader) is protecting another * producer, we need to ensure that properties are passed through to the * real producer. * \private \memberof mlt_properties_s * \param self a properties list * \param name the name of the property to copy */ static inline void mlt_properties_do_mirror(mlt_properties self, const char *name) { if (!self) return; property_list *list = self->local; if (list->mirror != NULL) { char *value = mlt_properties_get(self, name); if (value != NULL) mlt_properties_set_string(list->mirror, name, value); } } /** Increment the reference count. * * \public \memberof mlt_properties_s * \param self a properties list * \return the new reference count */ int mlt_properties_inc_ref(mlt_properties self) { int result = 0; if (self != NULL) { property_list *list = self->local; pthread_mutex_lock(&list->mutex); result = ++list->ref_count; pthread_mutex_unlock(&list->mutex); } return result; } /** Decrement the reference count. * * \public \memberof mlt_properties_s * \param self a properties list * \return the new reference count */ int mlt_properties_dec_ref(mlt_properties self) { int result = 0; if (self != NULL) { property_list *list = self->local; pthread_mutex_lock(&list->mutex); result = --list->ref_count; pthread_mutex_unlock(&list->mutex); } return result; } /** Get the reference count. * * \public \memberof mlt_properties_s * \param self a properties list * \return the current reference count */ int mlt_properties_ref_count(mlt_properties self) { if (self != NULL) { property_list *list = self->local; return list->ref_count; } return 0; } /** Set a properties list to be a mirror copy of another. * * Note that this does not copy all existing properties. Rather, you must * call this before setting the properties that you wish to copy. * \public \memberof mlt_properties_s * \param that the properties which will receive copies of the properties as they are set. * \param self the properties to mirror */ void mlt_properties_mirror(mlt_properties self, mlt_properties that) { if (!self) return; property_list *list = self->local; list->mirror = that; } /** Copy all serializable properties to another properties list. * * \public \memberof mlt_properties_s * \param self The properties to copy to * \param that The properties to copy from * \return true if error */ int mlt_properties_inherit(mlt_properties self, mlt_properties that) { if (!self || !that) return 1; // Set "properties" first so preset overrides are reliable. char *value = mlt_properties_get(that, "properties"); if (value) mlt_properties_set_string(self, "properties", value); mlt_properties_lock(that); int count = mlt_properties_count(that); int i = 0; for (i = 0; i < count; i++) { char *name = mlt_properties_get_name(that, i); if (name && strcmp("properties", name)) { char *value = mlt_properties_get_value(that, i); if (value != NULL) { mlt_properties_set_string(self, name, value); } else { mlt_properties that_child_props = mlt_properties_get_properties_at(that, i); if (that_child_props != NULL) { mlt_properties child_props = mlt_properties_new(); mlt_properties_set_properties(self, name, child_props); mlt_properties_inherit(child_props, that_child_props); } } } } mlt_properties_unlock(that); return 0; } /** Copy all serializable properties that match a prefix to another properties object * * \public \memberof mlt_properties_s * \param self the properties to copy to * \param that The properties to copy from * \param prefix the property names to match (required) * \return true if error */ int mlt_properties_copy(mlt_properties self, mlt_properties that, const char *prefix) { if (!self || !that) return 1; int count = mlt_properties_count(that); int length = strlen(prefix); int i = 0; for (i = 0; i < count; i++) { char *name = mlt_properties_get_name(that, i); if (!strncmp(name, prefix, length)) { char *value = mlt_properties_get_value(that, i); if (value != NULL) mlt_properties_set_string(self, name, value); } } return 0; } /** Pass all serializable properties that match a prefix to another properties object * * \warning The prefix is stripped from the name when it is set on the \p self properties list! * For example a property named "foo.bar" will match prefix "foo.", but the property * will be named simply "bar" on the receiving properties object. * \public \memberof mlt_properties_s * \param self the properties to copy to * \param that The properties to copy from * \param prefix the property names to match (required) * \return true if error */ int mlt_properties_pass(mlt_properties self, mlt_properties that, const char *prefix) { if (!self || !that) return 1; int count = mlt_properties_count(that); int length = strlen(prefix); int i = 0; for (i = 0; i < count; i++) { char *name = mlt_properties_get_name(that, i); if (!strncmp(name, prefix, length)) { char *value = mlt_properties_get_value(that, i); if (value != NULL) mlt_properties_set_string(self, name + length, value); } } return 0; } /** Locate a property by name. * * \private \memberof mlt_properties_s * \param self a properties list * \param name the property to lookup by name * \return the property or NULL for failure */ static inline mlt_property mlt_properties_find(mlt_properties self, const char *name) { if (!self || !name) return NULL; property_list *list = self->local; mlt_property value = NULL; int key = generate_hash(name); mlt_properties_lock(self); int i = list->hash[key] - 1; if (i >= 0) { // Check if we're hashed if (list->count > 0 && list->name[i] && !strcmp(list->name[i], name)) value = list->value[i]; // Locate the item for (i = list->count - 1; value == NULL && i >= 0; i--) if (list->name[i] && !strcmp(list->name[i], name)) value = list->value[i]; } mlt_properties_unlock(self); return value; } /** Add a new property. * * \private \memberof mlt_properties_s * \param self a properties list * \param name the name of the new property * \return the new property */ static mlt_property mlt_properties_add(mlt_properties self, const char *name) { property_list *list = self->local; int key = generate_hash(name); mlt_property result; mlt_properties_lock(self); // Check that we have space and resize if necessary if (list->count == list->size) { list->size += 50; list->name = realloc(list->name, list->size * sizeof(const char *)); list->value = realloc(list->value, list->size * sizeof(mlt_property)); } // Assign name/value pair list->name[list->count] = strdup(name); list->value[list->count] = mlt_property_init(); // Assign to hash table if (list->hash[key] == 0) list->hash[key] = list->count + 1; // Return and increment count accordingly result = list->value[list->count++]; mlt_properties_unlock(self); return result; } /** Fetch a property by name and add one if not found. * * \private \memberof mlt_properties_s * \param self a properties list * \param name the property to lookup or add * \return the property */ static mlt_property mlt_properties_fetch(mlt_properties self, const char *name) { // Try to find an existing property first mlt_property property = mlt_properties_find(self, name); // If it wasn't found, create one if (property == NULL) property = mlt_properties_add(self, name); // Return the property return property; } static void fire_property_changed(mlt_properties self, const char *name) { mlt_events_fire(self, "property-changed", mlt_event_data_from_string(name)); } /** Copy a property to another properties list. * * \public \memberof mlt_properties_s * \author Zach * \param self the properties to copy to * \param that the properties to copy from * \param name the name of the property to copy */ void mlt_properties_pass_property(mlt_properties self, mlt_properties that, const char *name) { // Make sure the source property isn't null. mlt_property that_prop = mlt_properties_find(that, name); if (that_prop == NULL) return; mlt_property_pass(mlt_properties_fetch(self, name), that_prop); fire_property_changed(self, name); } /** Copy all properties specified in a comma-separated list to another properties list. * * White space is also a delimiter. * \public \memberof mlt_properties_s * \author Zach * \param self the properties to copy to * \param that the properties to copy from * \param list a delimited list of property names * \return true if error */ int mlt_properties_pass_list(mlt_properties self, mlt_properties that, const char *list) { if (!self || !that || !list) return 1; char *props = strdup(list); char *ptr = props; const char *delim = " ,\t\n"; // Any combination of spaces, commas, tabs, and newlines int count, done = 0; while (!done) { count = strcspn(ptr, delim); if (ptr[count] == '\0') done = 1; else ptr[count] = '\0'; // Make it a real string mlt_properties_pass_property(self, that, ptr); ptr += count + 1; if (!done) ptr += strspn(ptr, delim); } free(props); return 0; } static int is_valid_expression(mlt_properties self, const char *value) { int result = *value != '\0'; char id[255]; while (*value != '\0') { size_t length = strcspn(value, "+-*/"); // Get the identifier length = MIN(sizeof(id) - 1, length); strncpy(id, value, length); id[length] = '\0'; value += length; // Determine if the property exists if (!isdigit(id[0]) && !mlt_properties_get(self, id)) { result = 0; break; } // Get the next op if (value[0] != '\0') ++value; } return result; } /** Set a property to a string. * * The property name "properties" is reserved to load the preset in \p value. * When the value begins with '@' then it is interpreted as a very simple math * expression containing only the +, -, *, and / operators. * The event "property-changed" is fired after the property has been set. * * This makes a copy of the string value you supply. * \public \memberof mlt_properties_s * \param self a properties list * \param name the property to set * \param value the property's new value * \return true if error */ int mlt_properties_set(mlt_properties self, const char *name, const char *value) { int error = 1; if (!self || !name) return error; // Fetch the property to work with mlt_property property = mlt_properties_fetch(self, name); // Set it if not NULL if (property == NULL) { mlt_log(NULL, MLT_LOG_FATAL, "Whoops - %s not found (should never occur)\n", name); } else if (value == NULL) { error = mlt_property_set_string(property, value); mlt_properties_do_mirror(self, name); } else if (value[0] == '@' && is_valid_expression(self, &value[1])) { double total = 0; double current = 0; char id[255]; char op = '+'; value++; while (*value != '\0') { size_t length = strcspn(value, "+-*/"); // Get the identifier length = MIN(sizeof(id) - 1, length); strncpy(id, value, length); id[length] = '\0'; value += length; // Determine the value if (isdigit(id[0])) { #if defined(__GLIBC__) || defined(__APPLE__) || HAVE_STRTOD_L property_list *list = self->local; if (list->locale) current = strtod_l(id, NULL, list->locale); else #endif current = strtod(id, NULL); } else { current = mlt_properties_get_double(self, id); } // Apply the operation switch (op) { case '+': total += current; break; case '-': total -= current; break; case '*': total *= current; break; case '/': total = total / current; break; } // Get the next op op = *value != '\0' ? *value++ : ' '; } error = mlt_property_set_double(property, total); mlt_properties_do_mirror(self, name); } else { error = mlt_property_set_string(property, value); mlt_properties_do_mirror(self, name); if (!strcmp(name, "properties")) mlt_properties_preset(self, value); } fire_property_changed(self, name); return error; } /** Set or default a property to a string. * * This makes a copy of the string value you supply. * \public \memberof mlt_properties_s * \param self a properties list * \param name the property to set * \param value the string value to set or NULL to use the default * \param def the default string if value is NULL * \return true if error */ int mlt_properties_set_or_default(mlt_properties self, const char *name, const char *value, const char *def) { return mlt_properties_set(self, name, value == NULL ? def : value); } /** Set a property to a string. * * Unlike \mlt_properties_set this function does not attempt to interpret an expression. * The property name "properties" is reserved to load the preset in \p value. * The event "property-changed" is fired after the property has been set. * * This makes a copy of the string value you supply. * \public \memberof mlt_properties_s * \param self a properties list * \param name the property to set * \param value the property's new value * \return true if error */ int mlt_properties_set_string(mlt_properties self, const char *name, const char *value) { int error = 1; if (!self || !name) return error; // Fetch the property to work with mlt_property property = mlt_properties_fetch(self, name); // Set it if not NULL if (property == NULL) { mlt_log(NULL, MLT_LOG_FATAL, "Whoops - %s not found (should never occur)\n", name); } else if (value == NULL) { error = mlt_property_set_string(property, value); mlt_properties_do_mirror(self, name); } else { error = mlt_property_set_string(property, value); mlt_properties_do_mirror(self, name); if (!strcmp(name, "properties")) mlt_properties_preset(self, value); } fire_property_changed(self, name); return error; } /** Get a string value by name. * * Do not free the returned string. It's lifetime is controlled by the property * and this properties object. * \public \memberof mlt_properties_s * \param self a properties list * \param name the property to get * \return the property's string value or NULL if it does not exist */ char *mlt_properties_get(mlt_properties self, const char *name) { char *result = NULL; mlt_property value = mlt_properties_find(self, name); if (value) { property_list *list = self->local; result = mlt_property_get_string_l(value, list->locale); } return result; } /** Get a property name by index. * * Do not free the returned string. * \public \memberof mlt_properties_s * \param self a properties list * \param index the numeric index of the property * \return the name of the property or NULL if index is out of range */ char *mlt_properties_get_name(mlt_properties self, int index) { if (!self) return NULL; property_list *list = self->local; if (index >= 0 && index < list->count) return list->name[index]; return NULL; } /** Get a property's string value by index (with time format). * * Do not free the returned string. * \public \memberof mlt_properties_s * \param self a properties list * \param index the numeric index of the property * \param time_format the time format to use for animation * \return the property value as a string or NULL if the index is out of range */ char *mlt_properties_get_value_tf(mlt_properties self, int index, mlt_time_format time_format) { if (!self) return NULL; property_list *list = self->local; if (index >= 0 && index < list->count) return mlt_property_get_string_l_tf(list->value[index], list->locale, time_format); return NULL; } /** Get a property's string value by index. * * Do not free the returned string. * \public \memberof mlt_properties_s * \param self a properties list * \param index the numeric index of the property * \return the property value as a string or NULL if the index is out of range */ char *mlt_properties_get_value(mlt_properties self, int index) { return mlt_properties_get_value_tf(self, index, mlt_time_frames); } /** Get a data value by index. * * Do not free the returned pointer if you supplied a destructor function when you * set this property. * \public \memberof mlt_properties_s * \param self a properties list * \param index the numeric index of the property * \param[out] size the size of the binary data in bytes or NULL if the index is out of range */ void *mlt_properties_get_data_at(mlt_properties self, int index, int *size) { if (!self) return NULL; property_list *list = self->local; if (index >= 0 && index < list->count) return mlt_property_get_data(list->value[index], size); return NULL; } /** Return the number of items in the list. * * \public \memberof mlt_properties_s * \param self a properties list * \return the number of property objects or -1 if error */ int mlt_properties_count(mlt_properties self) { if (!self) return -1; property_list *list = self->local; return list->count; } /** Set a value by parsing a name=value string. * * \public \memberof mlt_properties_s * \param self a properties list * \param namevalue a string containing name and value delimited by '=' * \return true if there was an error */ int mlt_properties_parse(mlt_properties self, const char *namevalue) { if (!self) return 1; char *name = strdup(namevalue); char *value = NULL; int error = 0; char *ptr = strchr(name, '='); if (ptr) { *(ptr++) = '\0'; if (*ptr != '\"') { value = strdup(ptr); } else { ptr++; value = strdup(ptr); if (value != NULL && value[strlen(value) - 1] == '\"') value[strlen(value) - 1] = '\0'; } } else { value = strdup(""); } error = mlt_properties_set(self, name, value); free(name); free(value); return error; } /** Get an integer associated to the name. * * \public \memberof mlt_properties_s * \param self a properties list * \param name the property to get * \return The integer value, 0 if not found (which may also be a legitimate value) */ int mlt_properties_get_int(mlt_properties self, const char *name) { int result = 0; mlt_property value = mlt_properties_find(self, name); if (value) { mlt_profile profile = mlt_properties_get_data(self, "_profile", NULL); double fps = mlt_profile_fps(profile); property_list *list = self->local; result = mlt_property_get_int(value, fps, list->locale); } return result; } /** Set a property to an integer value. * * \public \memberof mlt_properties_s * \param self a properties list * \param name the property to set * \param value the integer * \return true if error */ int mlt_properties_set_int(mlt_properties self, const char *name, int value) { int error = 1; if (!self || !name) return error; // Fetch the property to work with mlt_property property = mlt_properties_fetch(self, name); // Set it if not NULL if (property != NULL) { error = mlt_property_set_int(property, value); mlt_properties_do_mirror(self, name); } fire_property_changed(self, name); return error; } /** Get a 64-bit integer associated to the name. * * \public \memberof mlt_properties_s * \param self a properties list * \param name the property to get * \return the integer value, 0 if not found (which may also be a legitimate value) */ int64_t mlt_properties_get_int64(mlt_properties self, const char *name) { mlt_property value = mlt_properties_find(self, name); return value == NULL ? 0 : mlt_property_get_int64(value); } /** Set a property to a 64-bit integer value. * * \public \memberof mlt_properties_s * \param self a properties list * \param name the property to set * \param value the integer * \return true if error */ int mlt_properties_set_int64(mlt_properties self, const char *name, int64_t value) { int error = 1; if (!self || !name) return error; // Fetch the property to work with mlt_property property = mlt_properties_fetch(self, name); // Set it if not NULL if (property != NULL) { error = mlt_property_set_int64(property, value); mlt_properties_do_mirror(self, name); } fire_property_changed(self, name); return error; } /** Get a floating point value associated to the name. * * \public \memberof mlt_properties_s * \param self a properties list * \param name the property to get * \return the floating point, 0 if not found (which may also be a legitimate value) */ double mlt_properties_get_double(mlt_properties self, const char *name) { double result = 0; mlt_property value = mlt_properties_find(self, name); if (value) { mlt_profile profile = mlt_properties_get_data(self, "_profile", NULL); double fps = mlt_profile_fps(profile); property_list *list = self->local; result = mlt_property_get_double(value, fps, list->locale); } return result; } /** Set a property to a floating point value. * * \public \memberof mlt_properties_s * \param self a properties list * \param name the property to set * \param value the floating point value * \return true if error */ int mlt_properties_set_double(mlt_properties self, const char *name, double value) { int error = 1; if (!self || !name) return error; // Fetch the property to work with mlt_property property = mlt_properties_fetch(self, name); // Set it if not NULL if (property != NULL) { error = mlt_property_set_double(property, value); mlt_properties_do_mirror(self, name); } fire_property_changed(self, name); return error; } /** Get a position value associated to the name. * * \public \memberof mlt_properties_s * \param self a properties list * \param name the property to get * \return the position, 0 if not found (which may also be a legitimate value) */ mlt_position mlt_properties_get_position(mlt_properties self, const char *name) { mlt_position result = 0; mlt_property value = mlt_properties_find(self, name); if (value) { mlt_profile profile = mlt_properties_get_data(self, "_profile", NULL); double fps = mlt_profile_fps(profile); property_list *list = self->local; result = mlt_property_get_position(value, fps, list->locale); } return result; } /** Set a property to a position value. * * \public \memberof mlt_properties_s * \param self a properties list * \param name the property to get * \param value the position * \return true if error */ int mlt_properties_set_position(mlt_properties self, const char *name, mlt_position value) { int error = 1; if (!self || !name) return error; // Fetch the property to work with mlt_property property = mlt_properties_fetch(self, name); // Set it if not NULL if (property != NULL) { error = mlt_property_set_position(property, value); mlt_properties_do_mirror(self, name); } fire_property_changed(self, name); return error; } /** Get a binary data value associated to the name. * * Do not free the returned pointer if you supplied a destructor function * when you set this property. * \public \memberof mlt_properties_s * \param self a properties list * \param name the property to get * \param[out] length The size of the binary data in bytes, if available (often it is not, you should know) */ void *mlt_properties_get_data(mlt_properties self, const char *name, int *length) { mlt_property value = mlt_properties_find(self, name); return value == NULL ? NULL : mlt_property_get_data(value, length); } /** Store binary data as a property. * * \public \memberof mlt_properties_s * \param self a properties list * \param name the property to set * \param value an opaque pointer to binary data * \param length the size of the binary data in bytes (optional) * \param destroy a function to deallocate the binary data when the property is closed (optional) * \param serialise a function that can serialize the binary data as text (optional) * \return true if error */ int mlt_properties_set_data(mlt_properties self, const char *name, void *value, int length, mlt_destructor destroy, mlt_serialiser serialise) { int error = 1; if (!self || !name) return error; // Fetch the property to work with mlt_property property = mlt_properties_fetch(self, name); // Set it if not NULL if (property != NULL) error = mlt_property_set_data(property, value, length, destroy, serialise); fire_property_changed(self, name); return error; } /** Rename a property. * * \public \memberof mlt_properties_s * \param self a properties list * \param source the property to rename * \param dest the new name * \return true if the name is already in use */ int mlt_properties_rename(mlt_properties self, const char *source, const char *dest) { mlt_property value = mlt_properties_find(self, dest); if (value == NULL) { property_list *list = self->local; int i = 0; // Locate the item mlt_properties_lock(self); for (i = 0; i < list->count; i++) { if (list->name[i] && !strcmp(list->name[i], source)) { free(list->name[i]); list->name[i] = strdup(dest); list->hash[generate_hash(dest)] = i + 1; break; } } mlt_properties_unlock(self); } return value != NULL; } /** Dump the properties to a file handle. * * \public \memberof mlt_properties_s * \param self a properties list * \param output a file handle */ void mlt_properties_dump(mlt_properties self, FILE *output) { if (!self || !output) return; property_list *list = self->local; int i = 0; for (i = 0; i < list->count; i++) if (mlt_properties_get(self, list->name[i]) != NULL) fprintf(output, "%s=%s\n", list->name[i], mlt_properties_get(self, list->name[i])); } /** Output the properties to a file handle. * * This version includes reference counts and does not put each property on a new line. * \public \memberof mlt_properties_s * \param self a properties pointer * \param title a string to preface the output * \param output a file handle */ void mlt_properties_debug(mlt_properties self, const char *title, FILE *output) { if (!self || !output) return; if (output == NULL) output = stderr; fprintf(output, "%s: ", title); if (self != NULL) { property_list *list = self->local; int i = 0; fprintf(output, "[ ref=%d", list->ref_count); for (i = 0; i < list->count; i++) if (mlt_properties_get(self, list->name[i]) != NULL) fprintf(output, ", %s=%s", list->name[i], mlt_properties_get(self, list->name[i])); else if (mlt_properties_get_data(self, list->name[i], NULL) != NULL) fprintf(output, ", %s=%p", list->name[i], mlt_properties_get_data(self, list->name[i], NULL)); else fprintf(output, ", %s=%p", list->name[i], mlt_properties_get_properties(self, list->name[i])); fprintf(output, " ]"); } fprintf(output, "\n"); } /** Save the properties to a file by name. * * This uses the dump format - one line per property. * \public \memberof mlt_properties_s * \param self a properties list * \param filename the name of a file to create or overwrite * \return true if there was an error */ int mlt_properties_save(mlt_properties self, const char *filename) { int error = 1; if (!self || !filename) return error; FILE *f = mlt_fopen(filename, "w"); if (f != NULL) { mlt_properties_dump(self, f); fclose(f); error = 0; } return error; } /* This is a very basic cross platform fnmatch replacement - it will fail in * many cases, but for the basic *.XXX and YYY*.XXX, it will work ok. */ /** Test whether a filename or pathname matches a shell-style pattern. * * \private \memberof mlt_properties_s * \param wild a string containing a wildcard pattern * \param file the name of a file to test against * \return true if the file name matches the wildcard pattern */ static int mlt_fnmatch(const char *wild, const char *file) { int f = 0; int w = 0; while (f < strlen(file) && w < strlen(wild)) { if (wild[w] == '*') { w++; if (w == strlen(wild)) f = strlen(file); while (f != strlen(file) && tolower(file[f]) != tolower(wild[w])) f++; } else if (wild[w] == '?' || tolower(file[f]) == tolower(wild[w])) { f++; w++; } else if (wild[0] == '*') { w = 0; } else { return 0; } } return strlen(file) == f && strlen(wild) == w; } /** Compare the string or serialized value of two properties. * * \private \memberof mlt_properties_s * \param self a property * \param that a property * \return < 0 if \p self less than \p that, 0 if equal, or > 0 if \p self is greater than \p that */ static int mlt_compare(const void *self, const void *that) { return strcmp(mlt_property_get_string(*(const mlt_property *) self), mlt_property_get_string(*(const mlt_property *) that)); } /** Get the contents of a directory. * * Obtains an optionally sorted list of the files found in a directory with a specific wild card. * Entries in the list have a numeric name (running from 0 to count - 1). Only values change * position if sort is enabled. Designed to be posix compatible (linux, os/x, mingw etc). * \public \memberof mlt_properties_s * \param self a properties list * \param dirname the name of the directory * \param pattern a wildcard pattern to filter the directory listing * \param sort Do you want to sort the directory listing? * \return the number of items in the directory listing */ int mlt_properties_dir_list(mlt_properties self, const char *dirname, const char *pattern, int sort) { DIR *dir = opendir(dirname); if (dir) { char key[20]; struct dirent *de = readdir(dir); char fullname[1024]; while (de != NULL) { sprintf(key, "%d", mlt_properties_count(self)); snprintf(fullname, 1024, "%s/%s", dirname, de->d_name); if (pattern == NULL) mlt_properties_set_string(self, key, fullname); else if (de->d_name[0] != '.' && mlt_fnmatch(pattern, de->d_name)) mlt_properties_set_string(self, key, fullname); de = readdir(dir); } closedir(dir); } if (sort && mlt_properties_count(self)) { property_list *list = self->local; mlt_properties_lock(self); qsort(list->value, mlt_properties_count(self), sizeof(mlt_property), mlt_compare); mlt_properties_unlock(self); } return mlt_properties_count(self); } /** Close a properties object. * * Deallocates the properties object and everything it contains. * \public \memberof mlt_properties_s * \param self a properties object */ void mlt_properties_close(mlt_properties self) { if (self != NULL && mlt_properties_dec_ref(self) <= 0) { if (self->close != NULL) { self->close(self->close_object); } else { property_list *list = self->local; int index = 0; #if _MLT_PROPERTY_CHECKS_ == 1 // Show debug info mlt_properties_debug(self, "Closing", stderr); #endif #ifdef _MLT_PROPERTY_CHECKS_ // Increment destroyed count properties_destroyed++; // Show current stats - these should match when the app is closed mlt_log(NULL, MLT_LOG_DEBUG, "Created %d, destroyed %d\n", properties_created, properties_destroyed); #endif // Clean up names and values for (index = list->count - 1; index >= 0; index--) { mlt_property_close(list->value[index]); free(list->name[index]); } #if defined(__GLIBC__) || defined(__APPLE__) // Cleanup locale if (list->locale) freelocale(list->locale); #else free(list->locale); #endif // Clear up the list pthread_mutex_destroy(&list->mutex); free(list->name); free(list->value); free(list); // Free self now if self has no child if (self->child == NULL) free(self); } } } /** Determine if the properties list is really just a sequence or ordered list. * * \public \memberof mlt_properties_s * \param properties a properties list * \return true if all of the property names are numeric (a sequence) */ int mlt_properties_is_sequence(mlt_properties properties) { int i; int n = mlt_properties_count(properties); for (i = 0; i < n; i++) if (!isdigit(mlt_properties_get_name(properties, i)[0])) return 0; return 1; } /** \brief YAML Tiny Parser context structure * * YAML is a nifty text format popular in the Ruby world as a cleaner, * less verbose alternative to XML. See this Wikipedia topic for an overview: * http://en.wikipedia.org/wiki/YAML * The YAML specification is at: * http://yaml.org/ * YAML::Tiny is a Perl module that specifies a subset of YAML that we are * using here (for the same reasons): * http://search.cpan.org/~adamk/YAML-Tiny-1.25/lib/YAML/Tiny.pm * \private */ struct yaml_parser_context { mlt_deque stack; unsigned int level; int index; mlt_deque index_stack; char block; char *block_name; unsigned int block_indent; }; typedef struct yaml_parser_context *yaml_parser; /** Remove spaces from the left side of a string. * * \param s the string to trim * \return the number of characters removed */ static unsigned int ltrim(char **s) { unsigned int i = 0; char *c = *s; int n = strlen(c); for (i = 0; i < n && *c == ' '; i++, c++) ; *s = c; return i; } /** Remove spaces from the right side of a string. * * \param s the string to trim * \return the number of characters removed */ static unsigned int rtrim(char *s) { int n = strlen(s); int i; for (i = n; i > 0 && s[i - 1] == ' '; --i) s[i - 1] = 0; return n - i; } /** Parse a line of YAML Tiny. * * Adds a property if needed. * \private \memberof yaml_parser_context * \param context a YAML Tiny Parser context * \param namevalue a line of YAML Tiny * \return true if there was an error */ static int parse_yaml(yaml_parser context, const char *namevalue) { char *name_ = strdup(namevalue); char *name = name_; char *value = NULL; int error = 0; char *ptr = strchr(name, ':'); unsigned int indent = ltrim(&name); mlt_properties properties = mlt_deque_peek_back(context->stack); // If name is quoted if (ptr && name[0] == '\"') { // Look for ending quote. ptr = strchr(ptr + 1, '\"'); if (ptr) // Locate delimiter after ending quote. ptr = strchr(ptr + 1, ':'); else // No ending quote! ptr = strchr(name, ':'); } // Ascending one more levels in the tree if (indent < context->level) { unsigned int i; unsigned int n = (context->level - indent) / 2; for (i = 0; i < n; i++) { mlt_deque_pop_back(context->stack); context->index = mlt_deque_pop_back_int(context->index_stack); } properties = mlt_deque_peek_back(context->stack); context->level = indent; } // Descending a level in the tree else if (indent > context->level && context->block == 0) { context->level = indent; } // If there is a colon that is not part of a block if (ptr && (indent == context->level)) { // Reset block processing if (context->block_name) { free(context->block_name); context->block_name = NULL; context->block = 0; } // Terminate the name and setup the value pointer *(ptr++) = 0; // Trim comment char *comment = strchr(ptr, '#'); if (comment) { const char *quote = strchr(ptr, '\"'); if (!quote || quote > comment) *comment = 0; } // Trim leading and trailing spaces from bare value ltrim(&ptr); rtrim(ptr); // No value means a child if (strcmp(ptr, "") == 0) { mlt_properties child = mlt_properties_new(); mlt_properties_set_lcnumeric(child, mlt_properties_get_lcnumeric(properties)); mlt_properties_set_data(properties, name, child, 0, (mlt_destructor) mlt_properties_close, NULL); mlt_deque_push_back(context->stack, child); mlt_deque_push_back_int(context->index_stack, context->index); context->index = 0; free(name_); return error; } // A dash indicates a sequence item if (name[0] == '-') { mlt_properties child = mlt_properties_new(); char key[20]; mlt_properties_set_lcnumeric(child, mlt_properties_get_lcnumeric(properties)); snprintf(key, sizeof(key), "%d", context->index++); mlt_properties_set_data(properties, key, child, 0, (mlt_destructor) mlt_properties_close, NULL); mlt_deque_push_back(context->stack, child); mlt_deque_push_back_int(context->index_stack, context->index); name++; context->level += ltrim(&name) + 1; properties = child; } // Value is quoted if (*ptr == '\"') { ptr++; value = strdup(ptr); if (value && value[strlen(value) - 1] == '\"') value[strlen(value) - 1] = 0; } // Value is folded or unfolded block else if (*ptr == '|' || *ptr == '>') { context->block = *ptr; context->block_name = strdup(name); context->block_indent = 0; value = strdup(""); } // Bare value else { value = strdup(ptr); } } // A list of scalars else if (name[0] == '-') { // Reset block processing if (context->block_name) { free(context->block_name); context->block_name = NULL; context->block = 0; } char key[20]; snprintf(key, sizeof(key), "%d", context->index++); ptr = name + 1; // Trim comment char *comment = strchr(ptr, '#'); if (comment) { const char *quote = strchr(ptr, '\"'); if (!quote || quote > comment) *comment = 0; } // Trim leading and trailing spaces from bare value ltrim(&ptr); rtrim(ptr); // Value is quoted if (*ptr == '\"') { ptr++; value = strdup(ptr); if (value && value[strlen(value) - 1] == '\"') value[strlen(value) - 1] = 0; } // Value is folded or unfolded block else if (*ptr == '|' || *ptr == '>') { context->block = *ptr; context->block_name = strdup(key); context->block_indent = 0; value = strdup(""); } // Bare value else { value = strdup(ptr); } free(name_); name = name_ = strdup(key); } // Non-folded block else if (context->block == '|') { if (context->block_indent == 0) context->block_indent = indent; if (indent > context->block_indent) name = &name_[context->block_indent]; rtrim(name); char *old_value = mlt_properties_get(properties, context->block_name); value = calloc(1, strlen(old_value) + strlen(name) + 2); strcpy(value, old_value); if (strcmp(old_value, "")) strcat(value, "\n"); strcat(value, name); name = context->block_name; } // Folded block else if (context->block == '>') { ltrim(&name); rtrim(name); char *old_value = mlt_properties_get(properties, context->block_name); // Blank line (prepended with spaces) is new line if (strcmp(name, "") == 0) { value = calloc(1, strlen(old_value) + 2); strcat(value, old_value); strcat(value, "\n"); } // Concatenate with space else { value = calloc(1, strlen(old_value) + strlen(name) + 2); strcat(value, old_value); if (strcmp(old_value, "") && old_value[strlen(old_value) - 1] != '\n') strcat(value, " "); strcat(value, name); } name = context->block_name; } else { value = strdup(""); } // Remove quotes around the name. if (name && name[0] == '"' && name[strlen(name) - 1] == '"') { name++; name[strlen(name) - 1] = '\0'; } error = mlt_properties_set_string(properties, name, value); if (!strcmp(name, "LC_NUMERIC")) mlt_properties_set_lcnumeric(properties, value); free(name_); free(value); return error; } /** Parse a YAML Tiny file by name. * * \public \memberof mlt_properties_s * \param filename the name of a text file containing YAML Tiny * \return a new properties list */ mlt_properties mlt_properties_parse_yaml(const char *filename) { // Construct a standalone properties object mlt_properties self = mlt_properties_new(); if (self) { // Open the file FILE *file = mlt_fopen(filename, "r"); // Load contents of file if (file) { // Temp string char temp[MAX_LOAD_LINE_SIZE]; char *ptemp = &temp[0]; // Default to LC_NUMERIC = C mlt_properties_set_lcnumeric(self, "C"); // Parser context yaml_parser context = calloc(1, sizeof(struct yaml_parser_context)); context->stack = mlt_deque_init(); context->index_stack = mlt_deque_init(); mlt_deque_push_back(context->stack, self); mlt_deque_push_back_int(context->index_stack, 0); // Read each string from the file while (fgets(temp, MAX_LOAD_LINE_SIZE, file)) { // Check for end-of-stream if (strncmp(ptemp, "...", 3) == 0) break; // Chomp the string temp[strlen(temp) - 1] = '\0'; // Skip blank lines, comment lines, and document separator if (strcmp(ptemp, "") && ptemp[0] != '#' && strncmp(ptemp, "---", 3) && strncmp(ptemp, "%YAML", 5) && strncmp(ptemp, "% YAML", 6)) parse_yaml(context, temp); } // Close the file fclose(file); mlt_deque_close(context->stack); mlt_deque_close(context->index_stack); free(context->block_name); free(context); } } // Return the pointer return self; } /* * YAML Tiny Serializer */ /** How many bytes to grow at a time */ #define STRBUF_GROWTH (1024) /** \brief Private to mlt_properties_s, a self-growing buffer for building strings * \private */ struct strbuf_s { size_t size; char *string; }; typedef struct strbuf_s *strbuf; /** Create a new string buffer * * \private \memberof strbuf_s * \return a new string buffer */ static strbuf strbuf_new() { strbuf buffer = calloc(1, sizeof(struct strbuf_s)); buffer->size = STRBUF_GROWTH; buffer->string = calloc(1, buffer->size); return buffer; } /** Destroy a string buffer * * \private \memberof strbuf_s * \param buffer the string buffer to close */ static void strbuf_close(strbuf buffer) { // We do not free buffer->string; strbuf user must save that pointer // and free it. free(buffer); } /** Format a string into a string buffer * * A variable number of arguments follows the format string - one for each * format specifier. * \private \memberof strbuf_s * \param buffer the string buffer to write into * \param format a string that contains text and formatting instructions * \return the formatted string */ static char *strbuf_printf(strbuf buffer, const char *format, ...) { while (buffer->string) { va_list ap; va_start(ap, format); size_t len = strlen(buffer->string); size_t remain = buffer->size - len - 1; int need = vsnprintf(buffer->string + len, remain, format, ap); va_end(ap); if (need > -1 && need < remain) break; buffer->string[len] = 0; buffer->size += need + STRBUF_GROWTH; buffer->string = realloc(buffer->string, buffer->size); } return buffer->string; } /** Indent a line of YAML Tiny. * * \private \memberof strbuf_s * \param output a string buffer * \param indent the number of spaces to indent */ static inline void indent_yaml(strbuf output, int indent) { int j; for (j = 0; j < indent; j++) strbuf_printf(output, " "); } static void strbuf_escape(strbuf output, const char *value, char c) { char *v = strdup(value); char *s = v; char *found = strchr(s, c); while (found) { *found = '\0'; strbuf_printf(output, "%s\\%c", s, c); s = found + 1; found = strchr(s, c); } strbuf_printf(output, "%s", s); free(v); } static inline int has_reserved_char(const char *string) { return strchr(string, ':') || strchr(string, '[') || strchr(string, '\'') || strchr(string, '#'); } /** Convert a line string into a YAML block literal. * * \private \memberof strbuf_s * \param output a string buffer * \param value the string to format as a block literal * \param indent the number of spaces to indent */ static void output_yaml_block_literal(strbuf output, const char *value, int indent) { char *v = strdup(value); char *sol = v; char *eol = strchr(sol, '\n'); while (eol) { indent_yaml(output, indent); *eol = '\0'; strbuf_printf(output, "%s\n", sol); sol = eol + 1; eol = strchr(sol, '\n'); } indent_yaml(output, indent); strbuf_printf(output, "%s\n", sol); free(v); } /** Recursively serialize a properties list into a string buffer as YAML Tiny. * * \private \memberof mlt_properties_s * \param self a properties list * \param output a string buffer to hold the serialized YAML Tiny * \param indent the number of spaces to indent (for recursion, initialize to 0) * \param is_parent_sequence Is this properties list really just a sequence (for recursion, initialize to 0)? */ static void serialise_yaml(mlt_properties self, strbuf output, int indent, int is_parent_sequence) { property_list *list = self->local; int i = 0; int is_sequence = mlt_properties_is_sequence(self); for (i = 0; i < list->count; i++) { // This implementation assumes that all data elements are property lists. // Unfortunately, we do not have run time type identification. mlt_properties child = mlt_property_get_data(list->value[i], NULL); const char *name = list->name[i]; const char *value = mlt_properties_get(self, name); if (is_sequence) { // Ignore hidden/non-serialisable items if (name[0] != '_') { // Indicate a sequence item indent_yaml(output, indent); strbuf_printf(output, "- "); // If the value can be represented as a string if (value && strcmp(value, "")) { // Determine if this is an unfolded block literal if (strchr(value, '\n')) { strbuf_printf(output, "|\n"); output_yaml_block_literal(output, value, indent + strlen(name) + strlen("|")); } else if (has_reserved_char(value)) { strbuf_printf(output, "\""); strbuf_escape(output, value, '"'); strbuf_printf(output, "\"\n", value); } else if (strchr(value, '"')) { strbuf_printf(output, "'%s'\n", value); } else { strbuf_printf(output, "%s\n", value); } } } // Recurse on child if (child && child->local) serialise_yaml(child, output, indent + 2, 1); } else { // Assume this is a normal map-oriented properties list // Ignore hidden/non-serialisable items // If the value can be represented as a string if (name[0] != '_' && value && strcmp(value, "")) { if (is_parent_sequence == 0) indent_yaml(output, indent); else is_parent_sequence = 0; // Output the name. if (has_reserved_char(name)) { strbuf_printf(output, "\""); strbuf_escape(output, name, '"'); strbuf_printf(output, "\": "); } else { strbuf_printf(output, "%s: ", name); } // Determine if this is an unfolded block literal if (strchr(value, '\n')) { strbuf_printf(output, "|\n"); output_yaml_block_literal(output, value, indent + strlen(name) + strlen(": ")); } else if (has_reserved_char(value)) { strbuf_printf(output, "\""); strbuf_escape(output, value, '"'); strbuf_printf(output, "\"\n"); } else if (strchr(value, '"')) { strbuf_printf(output, "'%s'\n", value); } else { strbuf_printf(output, "%s\n", value); } } // Output a child as a map item if (child && child->local) { indent_yaml(output, indent); if (has_reserved_char(name)) { strbuf_printf(output, "\""); strbuf_escape(output, name, '"'); strbuf_printf(output, "\":\n"); } else { strbuf_printf(output, "%s:\n", name); } // Recurse on child serialise_yaml(child, output, indent + 2, 0); } } } } /** Serialize a properties list as a string of YAML Tiny. * * The caller MUST free the returned string! * This operates on properties containing properties as a hierarchical data * structure. * \public \memberof mlt_properties_s * \param self a properties list * \return a string containing YAML Tiny that represents the properties list */ char *mlt_properties_serialise_yaml(mlt_properties self) { if (!self) return NULL; const char *lc_numeric = mlt_properties_get_lcnumeric(self); strbuf b = strbuf_new(); strbuf_printf(b, "---\n"); mlt_properties_set_lcnumeric(self, "C"); serialise_yaml(self, b, 0, 0); mlt_properties_set_lcnumeric(self, lc_numeric); strbuf_printf(b, "...\n"); char *ret = b->string; strbuf_close(b); return ret; } /** Protect a properties list against concurrent access. * * \public \memberof mlt_properties_s * \param self a properties list */ void mlt_properties_lock(mlt_properties self) { if (self) pthread_mutex_lock(&((property_list *) (self->local))->mutex); } /** End protecting a properties list against concurrent access. * * \public \memberof mlt_properties_s * \param self a properties list */ void mlt_properties_unlock(mlt_properties self) { if (self) pthread_mutex_unlock(&((property_list *) (self->local))->mutex); } /** Remove the value for a property. * * This initializes the value to zero and removes any string, data, or animation. * This is especially useful when you want to reset an animation. * \public \memberof mlt_properties_s * \param self a properties list * \param name the name of the property to clear */ void mlt_properties_clear(mlt_properties self, const char *name) { if (!self || !name) return; // Fetch the property to work with mlt_property property = mlt_properties_fetch(self, name); // Set it if not NULL if (property) mlt_property_clear(property); fire_property_changed(self, name); } /** Check if a property exists. * * This function is not a substitute for checking for NULL as a property could * be set to NULL. * * This function will return false immediately after a call to * mlt_properties_clear() or if the property has never been created. * * \public \memberof mlt_properties_s * \param self a properties list * \param name the name of the property to query * \return true if the property exists */ int mlt_properties_exists(mlt_properties self, const char *name) { return !mlt_property_is_clear(mlt_properties_find(self, name)); } /** Get a time string associated to the name. * * Do not free the returned string. It's lifetime is controlled by the property. * \public \memberof mlt_properties_s * \param self a properties list * \param name the property to get * \param format the time format that you want * \return the property's time value or NULL if \p name does not exist or there is no profile */ char *mlt_properties_get_time(mlt_properties self, const char *name, mlt_time_format format) { mlt_profile profile = mlt_properties_get_data(self, "_profile", NULL); if (profile) { double fps = mlt_profile_fps(profile); mlt_property value = mlt_properties_find(self, name); property_list *list = self->local; return value == NULL ? NULL : mlt_property_get_time(value, format, fps, list->locale); } return NULL; } /** Convert a frame count to a time string. * * Do not free the returned string. It's lifetime is controlled by the property. * \public \memberof mlt_properties_s * \param self a properties list * \param frames the frame count to convert * \param format the time format that you want * \return the time string or NULL if error, e.g. there is no profile */ char *mlt_properties_frames_to_time(mlt_properties self, mlt_position frames, mlt_time_format format) { const char *name = "_mlt_properties_time"; mlt_properties_set_position(self, name, frames); return mlt_properties_get_time(self, name, format); } /** Convert a time string to a frame count. * * \public \memberof mlt_properties_s * \param self a properties list * \param time the time string to convert * \return a frame count or a negative value if error, e.g. there is no profile */ mlt_position mlt_properties_time_to_frames(mlt_properties self, const char *time) { const char *name = "_mlt_properties_time"; mlt_properties_set_string(self, name, time); return mlt_properties_get_position(self, name); } /** Set a property to an integer value by color. * * \public \memberof mlt_properties_s * \param self a properties list * \param name the property to set * \param color the color * \return true if error */ int mlt_properties_set_color(mlt_properties self, const char *name, mlt_color color) { int error = 1; if (!self || !name) return error; // Fetch the property to work with mlt_property property = mlt_properties_fetch(self, name); // Set it if not NULL if (property != NULL) { error = mlt_property_set_color(property, color); mlt_properties_do_mirror(self, name); } fire_property_changed(self, name); return error; } /** Convert a numeric property to a tuple of color components. * * If the property's string is red, green, blue, white, or black, then it * is converted to the corresponding opaque color tuple. Otherwise, the property * is fetched as an integer and then converted. * \public \memberof mlt_properties_s * \param self a properties list * \param name the property to get * \return a color structure */ mlt_color mlt_properties_get_color(mlt_properties self, const char *name) { mlt_property value = mlt_properties_find(self, name); mlt_color result = {0xff, 0xff, 0xff, 0xff}; if (value) { mlt_profile profile = mlt_properties_get_data(self, "_profile", NULL); double fps = mlt_profile_fps(profile); property_list *list = self->local; result = mlt_property_get_color(value, fps, list->locale); } return result; } /** Set a property to an integer value by color at a frame position. * * \public \memberof mlt_properties_s * \param self a properties list * \param name the property to set * \param value the color * \param position the frame number * \param length the maximum number of frames when interpreting negative keyframe times, * <=0 if you don't care or need that * \param keyframe_type the interpolation method for this keyframe * \return true if error */ extern int mlt_properties_anim_set_color(mlt_properties self, const char *name, mlt_color value, int position, int length, mlt_keyframe_type keyframe_type) { int error = 1; if (!self || !name) return error; // Fetch the property to work with mlt_property property = mlt_properties_fetch(self, name); // Set it if not NULL if (property != NULL) { mlt_profile profile = mlt_properties_get_data(self, "_profile", NULL); double fps = mlt_profile_fps(profile); property_list *list = self->local; error = mlt_property_anim_set_color(property, value, fps, list->locale, position, length, keyframe_type); mlt_properties_do_mirror(self, name); } fire_property_changed(self, name); return error; } /** Get a color associated to the name at a frame position. * * \public \memberof mlt_properties_s * \param self a properties list * \param name the property to get * \param position the frame number * \param length the maximum number of frames when interpreting negative keyframe times, * <=0 if you don't care or need that * \return a color structure */ mlt_color mlt_properties_anim_get_color(mlt_properties self, const char *name, int position, int length) { mlt_profile profile = mlt_properties_get_data(self, "_profile", NULL); double fps = mlt_profile_fps(profile); property_list *list = self->local; mlt_property value = mlt_properties_find(self, name); mlt_color color = {0xff, 0xff, 0xff, 0xff}; return value == NULL ? color : mlt_property_anim_get_color(value, fps, list->locale, position, length); } /** Get a string value by name at a frame position. * * Do not free the returned string. It's lifetime is controlled by the property * and this properties object. * Enclose a string property value in double quotation marks to prevent * mlt_properties_anim_get() from interpreting the string as animation. The * double-quotes are removed when retrieved through mlt_properties_anim_get(). * The same rule applies to string keyframe values: to protect a keyframed-string * value containing a semicolon or equal sign, enclose it in double-quotes. * * \public \memberof mlt_properties_s * \param self a properties list * \param name the property to get * \param position the frame number * \param length the maximum number of frames when interpreting negative keyframe times, * <=0 if you don't care or need that * \return the property's string value or NULL if it does not exist */ char *mlt_properties_anim_get(mlt_properties self, const char *name, int position, int length) { mlt_profile profile = mlt_properties_get_data(self, "_profile", NULL); double fps = mlt_profile_fps(profile); mlt_property value = mlt_properties_find(self, name); property_list *list = self->local; return value == NULL ? NULL : mlt_property_anim_get_string(value, fps, list->locale, position, length); } /** Set a property to a string at a frame position. * * The event "property-changed" is fired after the property has been set. * * This makes a copy of the string value you supply. * \public \memberof mlt_properties_s * \param self a properties list * \param name the property to set * \param value the property's new value * \param position the frame number * \param length the maximum number of frames when interpreting negative keyframe times, * <=0 if you don't care or need that * \return true if error */ int mlt_properties_anim_set( mlt_properties self, const char *name, const char *value, int position, int length) { int error = 1; if (!self || !name) return error; // Fetch the property to work with mlt_property property = mlt_properties_fetch(self, name); // Set it if not NULL if (property) { mlt_profile profile = mlt_properties_get_data(self, "_profile", NULL); double fps = mlt_profile_fps(profile); property_list *list = self->local; error = mlt_property_anim_set_string(property, value, fps, list->locale, position, length); mlt_properties_do_mirror(self, name); } fire_property_changed(self, name); return error; } /** Get an integer associated to the name at a frame position. * * \public \memberof mlt_properties_s * \param self a properties list * \param name the property to get * \param position the frame number * \param length the maximum number of frames when interpreting negative keyframe times, * <=0 if you don't care or need that * \return the integer value, 0 if not found (which may also be a legitimate value) */ int mlt_properties_anim_get_int(mlt_properties self, const char *name, int position, int length) { mlt_profile profile = mlt_properties_get_data(self, "_profile", NULL); double fps = mlt_profile_fps(profile); property_list *list = self->local; mlt_property value = mlt_properties_find(self, name); return value == NULL ? 0 : mlt_property_anim_get_int(value, fps, list->locale, position, length); } /** Set a property to an integer value at a frame position. * * \public \memberof mlt_properties_s * \param self a properties list * \param name the property to set * \param value the integer * \param position the frame number * \param length the maximum number of frames when interpreting negative keyframe times, * <=0 if you don't care or need that * \param keyframe_type the interpolation method for this keyframe * \return true if error */ int mlt_properties_anim_set_int(mlt_properties self, const char *name, int value, int position, int length, mlt_keyframe_type keyframe_type) { int error = 1; if (!self || !name) return error; // Fetch the property to work with mlt_property property = mlt_properties_fetch(self, name); // Set it if not NULL if (property != NULL) { mlt_profile profile = mlt_properties_get_data(self, "_profile", NULL); double fps = mlt_profile_fps(profile); property_list *list = self->local; error = mlt_property_anim_set_int(property, value, fps, list->locale, position, length, keyframe_type); mlt_properties_do_mirror(self, name); } fire_property_changed(self, name); return error; } /** Get a real number associated to the name at a frame position. * * \public \memberof mlt_properties_s * \param self a properties list * \param name the property to get * \param position the frame number * \param length the maximum number of frames when interpreting negative keyframe times, * <=0 if you don't care or need that * \return the real number, 0 if not found (which may also be a legitimate value) */ double mlt_properties_anim_get_double(mlt_properties self, const char *name, int position, int length) { mlt_profile profile = mlt_properties_get_data(self, "_profile", NULL); double fps = mlt_profile_fps(profile); property_list *list = self->local; mlt_property value = mlt_properties_find(self, name); return value == NULL ? 0.0 : mlt_property_anim_get_double(value, fps, list->locale, position, length); } /** Set a property to a real number at a frame position. * * \public \memberof mlt_properties_s * \param self a properties list * \param name the property to set * \param value the real number * \param position the frame number * \param length the maximum number of frames when interpreting negative keyframe times, * <=0 if you don't care or need that * \param keyframe_type the interpolation method for this keyframe * \return true if error */ int mlt_properties_anim_set_double(mlt_properties self, const char *name, double value, int position, int length, mlt_keyframe_type keyframe_type) { int error = 1; if (!self || !name) return error; // Fetch the property to work with mlt_property property = mlt_properties_fetch(self, name); // Set it if not NULL if (property != NULL) { mlt_profile profile = mlt_properties_get_data(self, "_profile", NULL); double fps = mlt_profile_fps(profile); property_list *list = self->local; error = mlt_property_anim_set_double(property, value, fps, list->locale, position, length, keyframe_type); mlt_properties_do_mirror(self, name); } fire_property_changed(self, name); return error; } /** Get the animation associated to the name. * * \public \memberof mlt_properties_s * \param self a properties list * \param name the property to get * \return The animation object or NULL if the property has no animation */ mlt_animation mlt_properties_get_animation(mlt_properties self, const char *name) { mlt_property value = mlt_properties_find(self, name); return value == NULL ? NULL : mlt_property_get_animation(value); } /** Set a property to a rectangle value. * * \public \memberof mlt_properties_s * \param self a properties list * \param name the property to set * \param value the rectangle * \return true if error */ extern int mlt_properties_set_rect(mlt_properties self, const char *name, mlt_rect value) { int error = 1; if (!self || !name) return error; // Fetch the property to work with mlt_property property = mlt_properties_fetch(self, name); // Set it if not NULL if (property != NULL) { error = mlt_property_set_rect(property, value); mlt_properties_do_mirror(self, name); } fire_property_changed(self, name); return error; } /** Get a rectangle associated to the name. * * \public \memberof mlt_properties_s * \param self a properties list * \param name the property to get * \return the rectangle value, the rectangle fields will be DBL_MIN if not found */ extern mlt_rect mlt_properties_get_rect(mlt_properties self, const char *name) { property_list *list = self->local; mlt_property value = mlt_properties_find(self, name); mlt_rect rect = {DBL_MIN, DBL_MIN, DBL_MIN, DBL_MIN, DBL_MIN}; return value == NULL ? rect : mlt_property_get_rect(value, list->locale); } /** Set a property to a rectangle value at a frame position. * * \public \memberof mlt_properties_s * \param self a properties list * \param name the property to set * \param value the rectangle * \param position the frame number * \param length the maximum number of frames when interpreting negative keyframe times, * <=0 if you don't care or need that * \param keyframe_type the interpolation method for this keyframe * \return true if error */ extern int mlt_properties_anim_set_rect(mlt_properties self, const char *name, mlt_rect value, int position, int length, mlt_keyframe_type keyframe_type) { int error = 1; if (!self || !name) return error; // Fetch the property to work with mlt_property property = mlt_properties_fetch(self, name); // Set it if not NULL if (property != NULL) { mlt_profile profile = mlt_properties_get_data(self, "_profile", NULL); double fps = mlt_profile_fps(profile); property_list *list = self->local; error = mlt_property_anim_set_rect(property, value, fps, list->locale, position, length, keyframe_type); mlt_properties_do_mirror(self, name); } fire_property_changed(self, name); return error; } /** Get a rectangle associated to the name at a frame position. * * \public \memberof mlt_properties_s * \param self a properties list * \param name the property to get * \param position the frame number * \param length the maximum number of frames when interpreting negative keyframe times, * <=0 if you don't care or need that * \return the rectangle value, the rectangle fields will be DBL_MIN if not found */ extern mlt_rect mlt_properties_anim_get_rect(mlt_properties self, const char *name, int position, int length) { mlt_profile profile = mlt_properties_get_data(self, "_profile", NULL); double fps = mlt_profile_fps(profile); property_list *list = self->local; mlt_property value = mlt_properties_find(self, name); mlt_rect rect = {DBL_MIN, DBL_MIN, DBL_MIN, DBL_MIN, DBL_MIN}; return value == NULL ? rect : mlt_property_anim_get_rect(value, fps, list->locale, position, length); } #ifndef _WIN32 // See win32/win32.c for win32 implementation. /** Convert UTF-8 property to the locale-defined encoding. * * MLT uses UTF-8 for strings, but Windows cannot accept UTF-8 for a filename. * Windows uses code pages for the locale encoding. * \public \memberof mlt_properties_s * \param self a properties list * \param name_from the property to read whose value is a UTF-8 string * \param name_to the name of the new property that will contain converted string * \return true if error */ int mlt_properties_from_utf8(mlt_properties properties, const char *name_from, const char *name_to) { // On non-Windows platforms, assume UTF-8 will always work and does not need conversion. // This function just becomes a pass-through operation. // This was largely chosen to prevent adding a libiconv dependency to the framework per policy. // However, for file open operations on Windows, especially when processing XML, a text codec // dependency is hardly avoidable. return mlt_properties_set_string(properties, name_to, mlt_properties_get(properties, name_from)); } #endif /** Set a property to a nested properties object. * * \public \memberof mlt_properties_s * \param self a properties list * \param name the property to get * \param properties the properties list to nest into \p self with \p name * \return true if error */ int mlt_properties_set_properties(mlt_properties self, const char *name, mlt_properties properties) { int error = 1; if (!self || !name || !properties) return error; // Fetch the property to work with mlt_property property = mlt_properties_fetch(self, name); // Set it if not NULL if (property != NULL) { error = mlt_property_set_properties(property, properties); mlt_properties_do_mirror(self, name); } fire_property_changed(self, name); return error; } /** Get a nested properties object by name. * * \public \memberof mlt_properties_s * \param self a properties list * \param name the property to get * \return the nested properties list */ mlt_properties mlt_properties_get_properties(mlt_properties self, const char *name) { mlt_property value = mlt_properties_find(self, name); return value == NULL ? NULL : mlt_property_get_properties(value); } /** Get a nested properties object by index. * * \public \memberof mlt_properties_s * \param self a properties list * \param index the 0-based index value of the list item * \return the nested properties list */ mlt_properties mlt_properties_get_properties_at(mlt_properties self, int index) { if (!self) return NULL; property_list *list = self->local; if (index >= 0 && index < list->count) return mlt_property_get_properties(list->value[index]); return NULL; } /** Check if a property is animated. * * \public \memberof mlt_properties_s * \param self a properties list * \param name the property to get * \return true if the property is animated */ int mlt_properties_is_anim(mlt_properties self, const char *name) { mlt_property property = mlt_properties_find(self, name); if (!property) return 0; property_list *list = self->local; pthread_mutex_lock(&list->mutex); int result = mlt_property_is_anim(property); pthread_mutex_unlock(&list->mutex); return result; } mlt-7.22.0/src/framework/mlt_properties.h000664 000000 000000 00000022726 14531534050 020374 0ustar00rootroot000000 000000 /** * \file mlt_properties.h * \brief Properties class declaration * \see mlt_properties_s * * Copyright (C) 2003-2022 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef MLT_PROPERTIES_H #define MLT_PROPERTIES_H #include "mlt_events.h" #include "mlt_types.h" #include /** \brief Properties class * * Properties is a combination list/dictionary of name/::mlt_property pairs. * It is also a base class for many of the other MLT classes. * * \event \em property-changed a property's value changed; * the event data is a string for the name of the property */ struct mlt_properties_s { void *child; /**< \private the object of a subclass */ void *local; /**< \private instance object */ /** the destructor virtual function */ mlt_destructor close; void *close_object; /**< the object supplied to the close virtual function */ }; extern int mlt_properties_init(mlt_properties, void *child); extern mlt_properties mlt_properties_new(); extern int mlt_properties_set_lcnumeric(mlt_properties, const char *locale); extern const char *mlt_properties_get_lcnumeric(mlt_properties self); extern mlt_properties mlt_properties_load(const char *file); extern int mlt_properties_preset(mlt_properties self, const char *name); extern int mlt_properties_inc_ref(mlt_properties self); extern int mlt_properties_dec_ref(mlt_properties self); extern int mlt_properties_ref_count(mlt_properties self); extern void mlt_properties_mirror(mlt_properties self, mlt_properties that); extern int mlt_properties_inherit(mlt_properties self, mlt_properties that); extern int mlt_properties_copy(mlt_properties self, mlt_properties that, const char *prefix); extern int mlt_properties_pass(mlt_properties self, mlt_properties that, const char *prefix); extern void mlt_properties_pass_property(mlt_properties self, mlt_properties that, const char *name); extern int mlt_properties_pass_list(mlt_properties self, mlt_properties that, const char *list); extern int mlt_properties_set(mlt_properties self, const char *name, const char *value); extern int mlt_properties_set_or_default(mlt_properties self, const char *name, const char *value, const char *def); extern int mlt_properties_set_string(mlt_properties self, const char *name, const char *value); extern int mlt_properties_parse(mlt_properties self, const char *namevalue); extern char *mlt_properties_get(mlt_properties self, const char *name); extern char *mlt_properties_get_name(mlt_properties self, int index); extern char *mlt_properties_get_value_tf(mlt_properties self, int index, mlt_time_format); extern char *mlt_properties_get_value(mlt_properties self, int index); extern void *mlt_properties_get_data_at(mlt_properties self, int index, int *size); extern int mlt_properties_get_int(mlt_properties self, const char *name); extern int mlt_properties_set_int(mlt_properties self, const char *name, int value); extern int64_t mlt_properties_get_int64(mlt_properties self, const char *name); extern int mlt_properties_set_int64(mlt_properties self, const char *name, int64_t value); extern double mlt_properties_get_double(mlt_properties self, const char *name); extern int mlt_properties_set_double(mlt_properties self, const char *name, double value); extern mlt_position mlt_properties_get_position(mlt_properties self, const char *name); extern int mlt_properties_set_position(mlt_properties self, const char *name, mlt_position value); extern int mlt_properties_set_data( mlt_properties self, const char *name, void *value, int length, mlt_destructor, mlt_serialiser); extern void *mlt_properties_get_data(mlt_properties self, const char *name, int *length); extern int mlt_properties_rename(mlt_properties self, const char *source, const char *dest); extern int mlt_properties_count(mlt_properties self); extern void mlt_properties_dump(mlt_properties self, FILE *output); extern void mlt_properties_debug(mlt_properties self, const char *title, FILE *output); extern int mlt_properties_save(mlt_properties, const char *); extern int mlt_properties_dir_list(mlt_properties, const char *, const char *, int); extern void mlt_properties_close(mlt_properties self); extern int mlt_properties_is_sequence(mlt_properties self); extern mlt_properties mlt_properties_parse_yaml(const char *file); extern char *mlt_properties_serialise_yaml(mlt_properties self); extern void mlt_properties_lock(mlt_properties self); extern void mlt_properties_unlock(mlt_properties self); extern void mlt_properties_clear(mlt_properties self, const char *name); extern int mlt_properties_exists(mlt_properties self, const char *name); extern char *mlt_properties_get_time(mlt_properties, const char *name, mlt_time_format); extern char *mlt_properties_frames_to_time(mlt_properties, mlt_position, mlt_time_format); extern mlt_position mlt_properties_time_to_frames(mlt_properties, const char *time); extern int mlt_properties_set_color(mlt_properties, const char *name, mlt_color value); extern mlt_color mlt_properties_get_color(mlt_properties, const char *name); extern int mlt_properties_anim_set_color(mlt_properties self, const char *name, mlt_color value, int position, int length, mlt_keyframe_type keyframe_type); extern mlt_color mlt_properties_anim_get_color(mlt_properties self, const char *name, int position, int length); extern char *mlt_properties_anim_get(mlt_properties self, const char *name, int position, int length); extern int mlt_properties_anim_set( mlt_properties self, const char *name, const char *value, int position, int length); extern int mlt_properties_anim_get_int(mlt_properties self, const char *name, int position, int length); extern int mlt_properties_anim_set_int(mlt_properties self, const char *name, int value, int position, int length, mlt_keyframe_type keyframe_type); extern double mlt_properties_anim_get_double(mlt_properties self, const char *name, int position, int length); extern int mlt_properties_anim_set_double(mlt_properties self, const char *name, double value, int position, int length, mlt_keyframe_type keyframe_type); extern mlt_animation mlt_properties_get_animation(mlt_properties self, const char *name); extern int mlt_properties_is_anim(mlt_properties self, const char *name); extern int mlt_properties_set_rect(mlt_properties self, const char *name, mlt_rect value); extern mlt_rect mlt_properties_get_rect(mlt_properties self, const char *name); extern int mlt_properties_anim_set_rect(mlt_properties self, const char *name, mlt_rect value, int position, int length, mlt_keyframe_type keyframe_type); extern mlt_rect mlt_properties_anim_get_rect(mlt_properties self, const char *name, int position, int length); extern int mlt_properties_from_utf8(mlt_properties properties, const char *name_from, const char *name_to); extern int mlt_properties_to_utf8(mlt_properties properties, const char *name_from, const char *name_to); extern int mlt_properties_set_properties(mlt_properties self, const char *name, mlt_properties properties); extern mlt_properties mlt_properties_get_properties(mlt_properties self, const char *name); extern mlt_properties mlt_properties_get_properties_at(mlt_properties self, int index); #endif mlt-7.22.0/src/framework/mlt_property.c000664 000000 000000 00000216610 14531534050 020054 0ustar00rootroot000000 000000 /** * \file mlt_property.c * \brief Property class definition * \see mlt_property_s * * Copyright (C) 2003-2023 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ // For strtod_l #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #include "mlt_property.h" #include "mlt_animation.h" #include "mlt_properties.h" #include #include #include #include #include #include #include /** Bit pattern used internally to indicated representations available. */ typedef enum { mlt_prop_none = 0, //!< not set mlt_prop_int = 1, //!< set as an integer mlt_prop_string = 2, //!< set as string or already converted to string mlt_prop_position = 4, //!< set as a position mlt_prop_double = 8, //!< set as a floating point mlt_prop_data = 16, //!< set as opaque binary mlt_prop_int64 = 32, //!< set as a 64-bit integer mlt_prop_rect = 64, //!< set as a mlt_rect mlt_prop_color = 128 //!< set as a mlt_color } mlt_property_type; /** \brief Property class * * A property is like a variant or dynamic type. They are used for many things * in MLT, but in particular they are the parameter mechanism for the plugins. */ struct mlt_property_s { /// Stores a bit pattern of types available for this property mlt_property_type types; /// Atomic type handling int prop_int; mlt_position prop_position; double prop_double; int64_t prop_int64; /// String handling char *prop_string; /// Generic type handling void *data; int length; mlt_destructor destructor; mlt_serialiser serialiser; pthread_mutex_t mutex; mlt_animation animation; mlt_properties properties; }; /** Construct a property and initialize it * \public \memberof mlt_property_s */ mlt_property mlt_property_init() { mlt_property self = calloc(1, sizeof(*self)); if (self) { pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); pthread_mutex_init(&self->mutex, &attr); } return self; } /** Clear (0/null) a property. * * Frees up any associated resources in the process. * \private \memberof mlt_property_s * \param self a property */ static void clear_property(mlt_property self) { // Special case data handling if (self->types & mlt_prop_data && self->destructor != NULL) self->destructor(self->data); // Special case string handling if (self->prop_string) free(self->prop_string); mlt_animation_close(self->animation); mlt_properties_close(self->properties); // Wipe stuff self->types = 0; self->prop_int = 0; self->prop_position = 0; self->prop_double = 0; self->prop_int64 = 0; self->prop_string = NULL; self->data = NULL; self->length = 0; self->destructor = NULL; self->serialiser = NULL; self->animation = NULL; self->properties = NULL; } /** Clear (0/null) a property. * * Frees up any associated resources in the process. * \public \memberof mlt_property_s * \param self a property */ void mlt_property_clear(mlt_property self) { pthread_mutex_lock(&self->mutex); clear_property(self); pthread_mutex_unlock(&self->mutex); } /** Check if a property is cleared. * * \public \memberof mlt_property_s * \param self a property * \return true if a property is clear. false if it has been set. */ int mlt_property_is_clear(mlt_property self) { int result = 1; if (self) { pthread_mutex_lock(&self->mutex); result = self->types == 0 && self->animation == NULL && self->properties == NULL; pthread_mutex_unlock(&self->mutex); } return result; } /** Set the property to an integer value. * * \public \memberof mlt_property_s * \param self a property * \param value an integer * \return false */ int mlt_property_set_int(mlt_property self, int value) { pthread_mutex_lock(&self->mutex); clear_property(self); self->types = mlt_prop_int; self->prop_int = value; pthread_mutex_unlock(&self->mutex); return 0; } /** Set the property to a floating point value. * * \public \memberof mlt_property_s * \param self a property * \param value a double precision floating point value * \return false */ int mlt_property_set_double(mlt_property self, double value) { pthread_mutex_lock(&self->mutex); clear_property(self); self->types = mlt_prop_double; self->prop_double = value; pthread_mutex_unlock(&self->mutex); return 0; } /** Set the property to a position value. * * Position is a relative time value in frame units. * \public \memberof mlt_property_s * \param self a property * \param value a position value * \return false */ int mlt_property_set_position(mlt_property self, mlt_position value) { pthread_mutex_lock(&self->mutex); clear_property(self); self->types = mlt_prop_position; self->prop_position = value; pthread_mutex_unlock(&self->mutex); return 0; } /** Set the property to a string value. * * This makes a copy of the string you supply so you do not need to track * a new reference to it. * \public \memberof mlt_property_s * \param self a property * \param value the string to copy to the property * \return true if it failed */ int mlt_property_set_string(mlt_property self, const char *value) { pthread_mutex_lock(&self->mutex); if (value != self->prop_string) { clear_property(self); self->types = mlt_prop_string; if (value != NULL) self->prop_string = strdup(value); } else { self->types = mlt_prop_string; } pthread_mutex_unlock(&self->mutex); return self->prop_string == NULL; } /** Set the property to a 64-bit integer value. * * \public \memberof mlt_property_s * \param self a property * \param value a 64-bit integer * \return false */ int mlt_property_set_int64(mlt_property self, int64_t value) { pthread_mutex_lock(&self->mutex); clear_property(self); self->types = mlt_prop_int64; self->prop_int64 = value; pthread_mutex_unlock(&self->mutex); return 0; } /** Set a property to an opaque binary value. * * This does not make a copy of the data. You can use a Properties object * with its reference tracking and the destructor function to control * the lifetime of the data. Otherwise, pass NULL for the destructor * function and control the lifetime yourself. * \public \memberof mlt_property_s * \param self a property * \param value an opaque pointer * \param length the number of bytes pointed to by value (optional) * \param destructor a function to use to destroy this binary data (optional, assuming you manage the resource) * \param serialiser a function to use to convert this binary data to a string (optional) * \return false */ int mlt_property_set_data(mlt_property self, void *value, int length, mlt_destructor destructor, mlt_serialiser serialiser) { pthread_mutex_lock(&self->mutex); if (self->data == value) self->destructor = NULL; clear_property(self); self->types = mlt_prop_data; self->data = value; self->length = length; self->destructor = destructor; self->serialiser = serialiser; pthread_mutex_unlock(&self->mutex); return 0; } /** Parse a SMIL clock value. * * \private \memberof mlt_property_s * \param self a property * \param s the string to parse * \param fps frames per second * \param locale the locale to use for parsing a real number value * \return position in frames */ static int time_clock_to_frames(mlt_property self, const char *s, double fps, mlt_locale_t locale) { char *pos, *copy = strdup(s); int hours = 0, minutes = 0; double seconds; s = copy; pos = strrchr(s, ':'); #if !defined(__GLIBC__) && !defined(__APPLE__) && !defined(_WIN32) && !defined(HAVE_STRTOD_L) char *orig_localename = NULL; if (locale) { // Protect damaging the global locale from a temporary locale on another thread. pthread_mutex_lock(&self->mutex); // Get the current locale orig_localename = strdup(setlocale(LC_NUMERIC, NULL)); // Set the new locale setlocale(LC_NUMERIC, locale); } #endif if (pos) { #if defined(__GLIBC__) || defined(__APPLE__) || defined(HAVE_STRTOD_L) if (locale) seconds = strtod_l(pos + 1, NULL, locale); else #endif seconds = strtod(pos + 1, NULL); *pos = 0; pos = strrchr(s, ':'); if (pos) { minutes = atoi(pos + 1); *pos = 0; hours = atoi(s); } else { minutes = atoi(s); } } else { #if defined(__GLIBC__) || defined(__APPLE__) || defined(HAVE_STRTOD_L) if (locale) seconds = strtod_l(s, NULL, locale); else #endif seconds = strtod(s, NULL); } #if !defined(__GLIBC__) && !defined(__APPLE__) && !defined(_WIN32) && !defined(HAVE_STRTOD_L) if (locale) { // Restore the current locale setlocale(LC_NUMERIC, orig_localename); free(orig_localename); pthread_mutex_unlock(&self->mutex); } #endif free(copy); return floor(fps * hours * 3600) + floor(fps * minutes * 60) + lrint(fps * seconds); } /** Parse a SMPTE timecode string. * * \private \memberof mlt_property_s * \param self a property * \param s the string to parse * \param fps frames per second * \return position in frames */ static int time_code_to_frames(mlt_property self, const char *s, double fps) { char *pos, *copy = strdup(s); int hours = 0, minutes = 0, seconds = 0, frames; s = copy; pos = strrchr(s, ';'); if (!pos) pos = strrchr(s, ':'); if (pos) { frames = atoi(pos + 1); *pos = 0; pos = strrchr(s, ':'); if (pos) { seconds = atoi(pos + 1); *pos = 0; pos = strrchr(s, ':'); if (pos) { minutes = atoi(pos + 1); *pos = 0; hours = atoi(s); } else { minutes = atoi(s); } } else { seconds = atoi(s); } } else { frames = atoi(s); } free(copy); return floor(fps * hours * 3600) + floor(fps * minutes * 60) + ceil(fps * seconds) + frames; } /** Convert a string to an integer. * * The string must begin with '0x' to be interpreted as hexadecimal. * Otherwise, it is interpreted as base 10. * * If the string begins with '#' it is interpreted as a hexadecimal color value * in the form RRGGBB or AARRGGBB. Color values that begin with '0x' are * always in the form RRGGBBAA where the alpha components are not optional. * Applications and services should expect the binary color value in bytes to * be in the following order: RGBA. This means they will have to cast the int * to an unsigned int. This is especially important when they need to shift * right to obtain RGB without alpha in order to make it do a logical instead * of arithmetic shift. * * If the string contains a colon it is interpreted as a time value. If it also * contains a period or comma character, the string is parsed as a clock value: * HH:MM:SS. Otherwise, the time value is parsed as a SMPTE timecode: HH:MM:SS:FF. * \private \memberof mlt_property_s * \param self a property * \param fps frames per second, used when converting from time value * \param locale the locale to use when converting from time clock value * \return the resultant integer */ static int mlt_property_atoi(mlt_property self, double fps, mlt_locale_t locale) { const char *value = self->prop_string; // Parse a hex color value as #RRGGBB or #AARRGGBB. if (value[0] == '#') { unsigned int rgb = strtoul(value + 1, NULL, 16); unsigned int alpha = (strlen(value) > 7) ? (rgb >> 24) : 0xff; return (rgb << 8) | alpha; } // Do hex and decimal explicitly to avoid decimal value with leading zeros // interpreted as octal. else if (value[0] == '0' && value[1] == 'x') { return strtoul(value + 2, NULL, 16); } else if (fps > 0 && strchr(value, ':')) { if (strchr(value, '.') || strchr(value, ',')) return time_clock_to_frames(self, value, fps, locale); else return time_code_to_frames(self, value, fps); } else { return strtol(value, NULL, 10); } } /** Get the property as an integer. * * \public \memberof mlt_property_s * \param self a property * \param fps frames per second, used when converting from time value * \param locale the locale to use when converting from time clock value * \return an integer value */ int mlt_property_get_int(mlt_property self, double fps, mlt_locale_t locale) { pthread_mutex_lock(&self->mutex); int result = 0; if (self->types & mlt_prop_int || self->types & mlt_prop_color) result = self->prop_int; else if (self->types & mlt_prop_double) result = (int) self->prop_double; else if (self->types & mlt_prop_position) result = (int) self->prop_position; else if (self->types & mlt_prop_int64) result = (int) self->prop_int64; else if (self->types & mlt_prop_rect && self->data) result = (int) ((mlt_rect *) self->data)->x; else { if (self->animation && !mlt_animation_get_string(self->animation)) mlt_property_get_string(self); if ((self->types & mlt_prop_string) && self->prop_string) result = mlt_property_atoi(self, fps, locale); } pthread_mutex_unlock(&self->mutex); return result; } /** Convert a string to a floating point number. * * If the string contains a colon it is interpreted as a time value. If it also * contains a period or comma character, the string is parsed as a clock value: * HH:MM:SS. Otherwise, the time value is parsed as a SMPTE timecode: HH:MM:SS:FF. * If the numeric string ends with '%' then the value is divided by 100 to convert * it into a ratio. * \private \memberof mlt_property_s * \param self a property * \param fps frames per second, used when converting from time value * \param locale the locale to use when converting from time clock value * \return the resultant real number */ static double mlt_property_atof(mlt_property self, double fps, mlt_locale_t locale) { const char *value = self->prop_string; if (fps > 0 && strchr(value, ':')) { if (strchr(value, '.') || strchr(value, ',')) return time_clock_to_frames(self, value, fps, locale); else return time_code_to_frames(self, value, fps); } else { char *end = NULL; double result; #if defined(__GLIBC__) || defined(__APPLE__) || defined(HAVE_STRTOD_L) if (locale) result = strtod_l(value, &end, locale); else #elif !defined(_WIN32) char *orig_localename = NULL; if (locale) { // Protect damaging the global locale from a temporary locale on another thread. pthread_mutex_lock(&self->mutex); // Get the current locale orig_localename = strdup(setlocale(LC_NUMERIC, NULL)); // Set the new locale setlocale(LC_NUMERIC, locale); } #endif result = strtod(value, &end); if (end && end[0] == '%') result /= 100.0; #if !defined(__GLIBC__) && !defined(__APPLE__) && !defined(_WIN32) && !defined(HAVE_STRTOD_L) if (locale) { // Restore the current locale setlocale(LC_NUMERIC, orig_localename); free(orig_localename); pthread_mutex_unlock(&self->mutex); } #endif return result; } } /** Get the property as a floating point. * * \public \memberof mlt_property_s * \param self a property * \param fps frames per second, used when converting from time value * \param locale the locale to use for this conversion * \return a floating point value */ double mlt_property_get_double(mlt_property self, double fps, mlt_locale_t locale) { double result = 0.0; pthread_mutex_lock(&self->mutex); if (self->types & mlt_prop_double) result = self->prop_double; else if (self->types & mlt_prop_int || self->types & mlt_prop_color) result = (double) self->prop_int; else if (self->types & mlt_prop_position) result = (double) self->prop_position; else if (self->types & mlt_prop_int64) result = (double) self->prop_int64; else if (self->types & mlt_prop_rect && self->data) result = ((mlt_rect *) self->data)->x; else { if (self->animation && !mlt_animation_get_string(self->animation)) mlt_property_get_string(self); if ((self->types & mlt_prop_string) && self->prop_string) result = mlt_property_atof(self, fps, locale); } pthread_mutex_unlock(&self->mutex); return result; } /** Get the property as a position. * * A position is an offset time in terms of frame units. * \public \memberof mlt_property_s * \param self a property * \param fps frames per second, used when converting from time value * \param locale the locale to use when converting from time clock value * \return the position in frames */ mlt_position mlt_property_get_position(mlt_property self, double fps, mlt_locale_t locale) { mlt_position result = 0; pthread_mutex_lock(&self->mutex); if (self->types & mlt_prop_position) result = self->prop_position; else if (self->types & mlt_prop_int || self->types & mlt_prop_color) result = (mlt_position) self->prop_int; else if (self->types & mlt_prop_double) result = (mlt_position) self->prop_double; else if (self->types & mlt_prop_int64) result = (mlt_position) self->prop_int64; else if (self->types & mlt_prop_rect && self->data) result = (mlt_position) ((mlt_rect *) self->data)->x; else { if (self->animation && !mlt_animation_get_string(self->animation)) mlt_property_get_string(self); if ((self->types & mlt_prop_string) && self->prop_string) result = (mlt_position) mlt_property_atoi(self, fps, locale); } pthread_mutex_unlock(&self->mutex); return result; } /** Convert a string to a 64-bit integer. * * If the string begins with '0x' it is interpreted as a hexadecimal value. * \private \memberof mlt_property_s * \param value a string * \return a 64-bit integer */ static inline int64_t mlt_property_atoll(const char *value) { if (value == NULL) return 0; else if (value[0] == '0' && value[1] == 'x') return strtoll(value + 2, NULL, 16); else return strtoll(value, NULL, 10); } /** Get the property as a signed integer. * * \public \memberof mlt_property_s * \param self a property * \return a 64-bit integer */ int64_t mlt_property_get_int64(mlt_property self) { int64_t result = 0; pthread_mutex_lock(&self->mutex); if (self->types & mlt_prop_int64) result = self->prop_int64; else if (self->types & mlt_prop_int || self->types & mlt_prop_color) result = (int64_t) self->prop_int; else if (self->types & mlt_prop_double) result = (int64_t) self->prop_double; else if (self->types & mlt_prop_position) result = (int64_t) self->prop_position; else if (self->types & mlt_prop_rect && self->data) result = (int64_t) ((mlt_rect *) self->data)->x; else { if (self->animation && !mlt_animation_get_string(self->animation)) mlt_property_get_string(self); if ((self->types & mlt_prop_string) && self->prop_string) result = mlt_property_atoll(self->prop_string); } pthread_mutex_unlock(&self->mutex); return result; } /** Get the property as a string (with time format). * * The caller is not responsible for deallocating the returned string! * The string is deallocated when the Property is closed. * This tries its hardest to convert the property to string including using * a serialization function for binary data, if supplied. * \public \memberof mlt_property_s * \param self a property * \param time_format the time format to use for animation * \return a string representation of the property or NULL if failed */ char *mlt_property_get_string_tf(mlt_property self, mlt_time_format time_format) { // Construct a string if need be pthread_mutex_lock(&self->mutex); if (self->animation && self->serialiser) { free(self->prop_string); self->prop_string = self->serialiser(self->animation, time_format); } else if (!(self->types & mlt_prop_string)) { if (self->types & mlt_prop_int) { self->types |= mlt_prop_string; self->prop_string = malloc(32); sprintf(self->prop_string, "%d", self->prop_int); } else if (self->types & mlt_prop_color) { self->types |= mlt_prop_string; self->prop_string = malloc(10); uint32_t int_value = ((self->prop_int & 0xff) << 24) | ((self->prop_int >> 8) & 0xffffff); sprintf(self->prop_string, "#%08x", int_value); } else if (self->types & mlt_prop_double) { self->types |= mlt_prop_string; self->prop_string = malloc(32); sprintf(self->prop_string, "%g", self->prop_double); } else if (self->types & mlt_prop_position) { self->types |= mlt_prop_string; self->prop_string = malloc(32); sprintf(self->prop_string, "%d", (int) self->prop_position); } else if (self->types & mlt_prop_int64) { self->types |= mlt_prop_string; self->prop_string = malloc(32); sprintf(self->prop_string, "%" PRId64, self->prop_int64); } else if (self->types & mlt_prop_data && self->data && self->serialiser) { self->types |= mlt_prop_string; self->prop_string = self->serialiser(self->data, self->length); } } pthread_mutex_unlock(&self->mutex); // Return the string (may be NULL) return self->prop_string; } static mlt_time_format default_time_format() { const char *e = getenv("MLT_ANIMATION_TIME_FORMAT"); return e ? strtol(e, NULL, 10) : mlt_time_frames; } /** Get the property as a string. * * The caller is not responsible for deallocating the returned string! * The string is deallocated when the Property is closed. * This tries its hardest to convert the property to string including using * a serialization function for binary data, if supplied. * \public \memberof mlt_property_s * \param self a property * \return a string representation of the property or NULL if failed */ char *mlt_property_get_string(mlt_property self) { return mlt_property_get_string_tf(self, default_time_format()); } /** Get the property as a string (with locale and time format). * * The caller is not responsible for deallocating the returned string! * The string is deallocated when the Property is closed. * This tries its hardest to convert the property to string including using * a serialization function for binary data, if supplied. * \public \memberof mlt_property_s * \param self a property * \param locale the locale to use for this conversion * \param time_format the time format to use for animation * \return a string representation of the property or NULL if failed */ char *mlt_property_get_string_l_tf(mlt_property self, mlt_locale_t locale, mlt_time_format time_format) { // Optimization for no locale if (!locale) return mlt_property_get_string_tf(self, time_format); // Construct a string if need be pthread_mutex_lock(&self->mutex); if (self->animation && self->serialiser) { free(self->prop_string); self->prop_string = self->serialiser(self->animation, time_format); } else if (!(self->types & mlt_prop_string)) { #if !defined(_WIN32) // TODO: when glibc gets sprintf_l, start using it! For now, hack on setlocale. // Save the current locale #if defined(__APPLE__) const char *localename = querylocale(LC_NUMERIC_MASK, locale); #elif defined(__GLIBC__) const char *localename = locale->__names[LC_NUMERIC]; #else const char *localename = locale; #endif // Get the current locale char *orig_localename = strdup(setlocale(LC_NUMERIC, NULL)); // Set the new locale setlocale(LC_NUMERIC, localename); #endif // _WIN32 if (self->types & mlt_prop_int) { self->types |= mlt_prop_string; self->prop_string = malloc(32); sprintf(self->prop_string, "%d", self->prop_int); } else if (self->types & mlt_prop_color) { self->types |= mlt_prop_string; self->prop_string = malloc(10); uint32_t int_value = ((self->prop_int & 0xff) << 24) | ((self->prop_int >> 8) & 0xffffff); sprintf(self->prop_string, "#%08x", int_value); } else if (self->types & mlt_prop_double) { self->types |= mlt_prop_string; self->prop_string = malloc(32); sprintf(self->prop_string, "%g", self->prop_double); } else if (self->types & mlt_prop_position) { self->types |= mlt_prop_string; self->prop_string = malloc(32); sprintf(self->prop_string, "%d", (int) self->prop_position); } else if (self->types & mlt_prop_int64) { self->types |= mlt_prop_string; self->prop_string = malloc(32); sprintf(self->prop_string, "%" PRId64, self->prop_int64); } else if (self->types & mlt_prop_data && self->data && self->serialiser) { self->types |= mlt_prop_string; self->prop_string = self->serialiser(self->data, self->length); } #if !defined(_WIN32) // Restore the current locale setlocale(LC_NUMERIC, orig_localename); free(orig_localename); #endif } pthread_mutex_unlock(&self->mutex); // Return the string (may be NULL) return self->prop_string; } /** Get the property as a string (with locale). * * The caller is not responsible for deallocating the returned string! * The string is deallocated when the Property is closed. * This tries its hardest to convert the property to string including using * a serialization function for binary data, if supplied. * \public \memberof mlt_property_s * \param self a property * \param locale the locale to use for this conversion * \return a string representation of the property or NULL if failed */ char *mlt_property_get_string_l(mlt_property self, mlt_locale_t locale) { return mlt_property_get_string_l_tf(self, locale, default_time_format()); } /** Get the binary data from a property. * * This only works if you previously put binary data into the property. * This does not return a copy of the data; it returns a pointer to it. * If you supplied a destructor function when setting the binary data, * the destructor is used when the Property is closed to free the memory. * Therefore, only free the returned pointer if you did not supply a * destructor function. * \public \memberof mlt_property_s * \param self a property * \param[out] length the size of the binary object in bytes (optional) * \return an opaque data pointer or NULL if not available */ void *mlt_property_get_data(mlt_property self, int *length) { // Assign length if not NULL if (length != NULL) *length = self->length; // Return the data (note: there is no conversion here) pthread_mutex_lock(&self->mutex); void *result = self->data; pthread_mutex_unlock(&self->mutex); return result; } /** Destroy a property and free all related resources. * * \public \memberof mlt_property_s * \param self a property */ void mlt_property_close(mlt_property self) { clear_property(self); pthread_mutex_destroy(&self->mutex); free(self); } /** Copy a property. * * A Property holding binary data only copies the data if a serialiser * function was supplied when you set the Property. * \public \memberof mlt_property_s * \author Zach * \param self a property * \param that another property */ void mlt_property_pass(mlt_property self, mlt_property that) { pthread_mutex_lock(&self->mutex); clear_property(self); self->types = that->types; if (self->types & mlt_prop_int64) self->prop_int64 = that->prop_int64; else if (self->types & mlt_prop_int || self->types & mlt_prop_color) self->prop_int = that->prop_int; else if (self->types & mlt_prop_double) self->prop_double = that->prop_double; else if (self->types & mlt_prop_position) self->prop_position = that->prop_position; if (self->types & mlt_prop_string) { if (that->prop_string != NULL) self->prop_string = strdup(that->prop_string); } else if (that->types & mlt_prop_rect) { clear_property(self); self->types = mlt_prop_rect | mlt_prop_data; self->length = that->length; self->data = calloc(1, self->length); memcpy(self->data, that->data, self->length); self->destructor = free; self->serialiser = that->serialiser; } else if (that->animation && that->serialiser) { self->types = mlt_prop_string; self->prop_string = that->serialiser(that->animation, default_time_format()); } else if (that->types & mlt_prop_data && that->serialiser) { self->types = mlt_prop_string; self->prop_string = that->serialiser(that->data, that->length); } pthread_mutex_unlock(&self->mutex); } /** Convert frame count to a SMPTE timecode string. * * \private \memberof mlt_property_s * \param frames a frame count * \param fps frames per second * \param[out] s the string to write into - must have enough space to hold largest time string */ static void time_smpte_from_frames(int frames, double fps, char *s, int drop) { int hours, mins, secs; char frame_sep = ':'; int save_frames = frames; if (fps == 30000.0 / 1001.0) { fps = 30.0; if (drop) { int i; for (i = 1800; i <= frames; i += 1800) { if (i % 18000) frames += 2; } frame_sep = ';'; } } else if (fps == 60000.0 / 1001.0) { fps = 60.0; if (drop) { int i; for (i = 3600; i <= frames; i += 3600) { if (i % 36000) frames += 4; } frame_sep = ';'; } } hours = frames / (fps * 3600); frames -= floor(hours * 3600 * fps); mins = frames / (fps * 60); if (mins == 60) { // floating point error ++hours; frames = save_frames - floor(hours * 3600 * fps); mins = 0; } save_frames = frames; frames -= floor(mins * 60 * fps); secs = frames / fps; if (secs == 60) { // floating point error ++mins; frames = save_frames - floor(mins * 60 * fps); secs = 0; } frames -= ceil(secs * fps); sprintf(s, "%02d:%02d:%02d%c%0*d", hours, mins, secs, frame_sep, (fps > 999 ? 4 : fps > 99 ? 3 : 2), frames); } /** Convert frame count to a SMIL clock value string. * * \private \memberof mlt_property_s * \param frames a frame count * \param fps frames per second * \param[out] s the string to write into - must have enough space to hold largest time string */ static void time_clock_from_frames(int frames, double fps, char *s) { int hours, mins; double secs; int save_frames = frames; hours = frames / (fps * 3600); frames -= floor(hours * 3600 * fps); mins = frames / (fps * 60); if (mins == 60) { // floating point error ++hours; frames = save_frames - floor(hours * 3600 * fps); mins = 0; } save_frames = frames; frames -= floor(mins * 60 * fps); secs = frames / fps; if (secs >= 60.0) { // floating point error ++mins; frames = save_frames - floor(mins * 60 * fps); secs = frames / fps; } sprintf(s, "%02d:%02d:%06.3f", hours, mins, secs); } /** Get the property as a time string. * * The time value can be either a SMPTE timecode or SMIL clock value. * The caller is not responsible for deallocating the returned string! * The string is deallocated when the property is closed. * \public \memberof mlt_property_s * \param self a property * \param format the time format that you want * \param fps frames per second * \param locale the locale to use for this conversion * \return a string representation of the property or NULL if failed */ char *mlt_property_get_time(mlt_property self, mlt_time_format format, double fps, mlt_locale_t locale) { #if !defined(_WIN32) char *orig_localename = NULL; #endif int frames = 0; // Remove existing string if (self->prop_string) mlt_property_set_int(self, mlt_property_get_int(self, fps, locale)); // Optimization for mlt_time_frames if (format == mlt_time_frames) return mlt_property_get_string_l(self, locale); #if !defined(_WIN32) // Use the specified locale if (locale) { // TODO: when glibc gets sprintf_l, start using it! For now, hack on setlocale. // Save the current locale #if defined(__APPLE__) const char *localename = querylocale(LC_NUMERIC_MASK, locale); #elif defined(__GLIBC__) const char *localename = locale->__names[LC_NUMERIC]; #else // TODO: not yet sure what to do on other platforms const char *localename = locale; #endif // _WIN32 // Protect damaging the global locale from a temporary locale on another thread. pthread_mutex_lock(&self->mutex); // Get the current locale orig_localename = strdup(setlocale(LC_NUMERIC, NULL)); // Set the new locale setlocale(LC_NUMERIC, localename); } else #endif // _WIN32 { // Make sure we have a lock before accessing self->types pthread_mutex_lock(&self->mutex); } // Convert number to string if (self->types & mlt_prop_int) { frames = self->prop_int; } else if (self->types & mlt_prop_position) { frames = (int) self->prop_position; } else if (self->types & mlt_prop_double) { frames = self->prop_double; } else if (self->types & mlt_prop_int64) { frames = (int) self->prop_int64; } self->types |= mlt_prop_string; self->prop_string = malloc(32); if (format == mlt_time_clock) time_clock_from_frames(frames, fps, self->prop_string); else if (format == mlt_time_smpte_ndf) time_smpte_from_frames(frames, fps, self->prop_string, 0); else // Use smpte drop frame by default time_smpte_from_frames(frames, fps, self->prop_string, 1); #if !defined(_WIN32) // Restore the current locale if (locale) { setlocale(LC_NUMERIC, orig_localename); free(orig_localename); pthread_mutex_unlock(&self->mutex); } else #endif // _WIN32 { // Make sure we have a lock before accessing self->types pthread_mutex_unlock(&self->mutex); } // Return the string (may be NULL) return self->prop_string; } /** Determine if the property holds a numeric or numeric string value. * * \public \memberof mlt_property_s * \param self a property * \param locale the locale to use for string evaluation * \return true if it is numeric */ int mlt_property_is_numeric(mlt_property self, mlt_locale_t locale) { int result = (self->types & mlt_prop_int) || (self->types & mlt_prop_color) || (self->types & mlt_prop_int64) || (self->types & mlt_prop_double) || (self->types & mlt_prop_position) || (self->types & mlt_prop_rect); // If not already numeric but string is numeric. if ((!result && self->types & mlt_prop_string) && self->prop_string) { char *p = NULL; #if defined(__GLIBC__) || defined(__APPLE__) || defined(HAVE_STRTOD_L) if (locale) strtod_l(self->prop_string, &p, locale); else #elif !defined(_WIN32) char *orig_localename = NULL; if (locale) { // Protect damaging the global locale from a temporary locale on another thread. pthread_mutex_lock(&self->mutex); // Get the current locale orig_localename = strdup(setlocale(LC_NUMERIC, NULL)); // Set the new locale setlocale(LC_NUMERIC, locale); } #endif strtod(self->prop_string, &p); #if !defined(__GLIBC__) && !defined(__APPLE__) && !defined(_WIN32) && !defined(HAVE_STRTOD_L) if (locale) { // Restore the current locale setlocale(LC_NUMERIC, orig_localename); free(orig_localename); pthread_mutex_unlock(&self->mutex); } #endif result = (p != self->prop_string); } return result; } /** A linear interpolation function for animation. * * \deprecated * \private \memberof mlt_property_s */ static inline double linear_interpolate(double y1, double y2, double t) { return y1 + (y2 - y1) * t; } /** A smooth spline interpolation for animation. * * \deprecated * For non-closed curves, you need to also supply the tangent vector at the first and last control point. * This is commonly done: T(P[0]) = P[1] - P[0] and T(P[n]) = P[n] - P[n-1]. * \private \memberof mlt_property_s */ static inline double catmull_rom_interpolate(double y0, double y1, double y2, double y3, double t) { double t2 = t * t; double a0 = -0.5 * y0 + 1.5 * y1 - 1.5 * y2 + 0.5 * y3; double a1 = y0 - 2.5 * y1 + 2 * y2 - 0.5 * y3; double a2 = -0.5 * y0 + 0.5 * y2; double a3 = y1; return a0 * t * t2 + a1 * t2 + a2 * t + a3; } /** Interpolate a new property value given a set of other properties. * * \deprecated * \public \memberof mlt_property_s * \param self the property onto which to set the computed value * \param p an array of at least 1 value in p[1] if \p interp is discrete, * 2 values in p[1] and p[2] if \p interp is linear, or * 4 values in p[0] - p[3] if \p interp is smooth * \param progress a ratio in the range [0, 1] to indicate how far between p[1] and p[2] * \param fps the frame rate, which may be needed for converting a time string to frame units * \param locale the locale, which may be needed for converting a string to a real number * \param interp the interpolation method to use * \return true if there was an error */ int mlt_property_interpolate(mlt_property self, mlt_property p[], double progress, double fps, mlt_locale_t locale, mlt_keyframe_type interp) { int error = 0; int colorstring = 0; const char *value = self->prop_string; if (value && ((strlen(value) > 6 && value[0] == '#') || (strlen(value) > 7 && value[0] == '0' && value[1] == 'x'))) { colorstring = 1; } if (interp != mlt_keyframe_discrete && (self->types & mlt_prop_color || colorstring)) { mlt_color value = {0xff, 0xff, 0xff, 0xff}; if (interp == mlt_keyframe_linear) { mlt_color colors[2]; mlt_color zero = {0xff, 0xff, 0xff, 0xff}; colors[0] = p[1] ? mlt_property_get_color(p[1], fps, locale) : zero; if (p[2]) { colors[1] = mlt_property_get_color(p[2], fps, locale); value.r = linear_interpolate(colors[0].r, colors[1].r, progress); value.g = linear_interpolate(colors[0].g, colors[1].g, progress); value.b = linear_interpolate(colors[0].b, colors[1].b, progress); value.a = linear_interpolate(colors[0].a, colors[1].a, progress); } else { value = colors[0]; } } else if (interp == mlt_keyframe_smooth) { mlt_color colors[4]; mlt_color zero = {0xff, 0xff, 0xff, 0xff}; colors[1] = p[1] ? mlt_property_get_color(p[1], fps, locale) : zero; if (p[2]) { colors[0] = p[0] ? mlt_property_get_color(p[0], fps, locale) : zero; colors[2] = p[2] ? mlt_property_get_color(p[2], fps, locale) : zero; colors[3] = p[3] ? mlt_property_get_color(p[3], fps, locale) : zero; value.r = CLAMP(catmull_rom_interpolate(colors[0].r, colors[1].r, colors[2].r, colors[3].r, progress), 0, 255); value.g = CLAMP(catmull_rom_interpolate(colors[0].g, colors[1].g, colors[2].g, colors[3].g, progress), 0, 255); value.b = CLAMP(catmull_rom_interpolate(colors[0].b, colors[1].b, colors[2].b, colors[3].b, progress), 0, 255); value.a = CLAMP(catmull_rom_interpolate(colors[0].a, colors[1].a, colors[2].a, colors[3].a, progress), 0, 255); } else { value = colors[1]; } } error = mlt_property_set_color(self, value); } else if (interp != mlt_keyframe_discrete && mlt_property_is_numeric(p[1], locale) && mlt_property_is_numeric(p[2], locale)) { if (self->types & mlt_prop_rect) { mlt_rect value = {DBL_MIN, DBL_MIN, DBL_MIN, DBL_MIN, DBL_MIN}; if (interp == mlt_keyframe_linear) { mlt_rect points[2]; mlt_rect zero = {0, 0, 0, 0, 0}; points[0] = p[1] ? mlt_property_get_rect(p[1], locale) : zero; if (p[2]) { points[1] = mlt_property_get_rect(p[2], locale); value.x = linear_interpolate(points[0].x, points[1].x, progress); value.y = linear_interpolate(points[0].y, points[1].y, progress); value.w = linear_interpolate(points[0].w, points[1].w, progress); value.h = linear_interpolate(points[0].h, points[1].h, progress); value.o = linear_interpolate(points[0].o, points[1].o, progress); } else { value = points[0]; } } else if (interp == mlt_keyframe_smooth) { mlt_rect points[4]; mlt_rect zero = {0, 0, 0, 0, 0}; points[1] = p[1] ? mlt_property_get_rect(p[1], locale) : zero; if (p[2]) { points[0] = p[0] ? mlt_property_get_rect(p[0], locale) : zero; points[2] = p[2] ? mlt_property_get_rect(p[2], locale) : zero; points[3] = p[3] ? mlt_property_get_rect(p[3], locale) : zero; value.x = catmull_rom_interpolate(points[0].x, points[1].x, points[2].x, points[3].x, progress); value.y = catmull_rom_interpolate(points[0].y, points[1].y, points[2].y, points[3].y, progress); value.w = catmull_rom_interpolate(points[0].w, points[1].w, points[2].w, points[3].w, progress); value.h = catmull_rom_interpolate(points[0].h, points[1].h, points[2].h, points[3].h, progress); value.o = catmull_rom_interpolate(points[0].o, points[1].o, points[2].o, points[3].o, progress); } else { value = points[1]; } } error = mlt_property_set_rect(self, value); } else { double value = 0.0; if (interp == mlt_keyframe_linear) { double points[2]; points[0] = p[1] ? mlt_property_get_double(p[1], fps, locale) : 0; points[1] = p[2] ? mlt_property_get_double(p[2], fps, locale) : 0; value = p[2] ? linear_interpolate(points[0], points[1], progress) : points[0]; } else if (interp == mlt_keyframe_smooth) { double points[4]; points[0] = p[0] ? mlt_property_get_double(p[0], fps, locale) : 0; points[1] = p[1] ? mlt_property_get_double(p[1], fps, locale) : 0; points[2] = p[2] ? mlt_property_get_double(p[2], fps, locale) : 0; points[3] = p[3] ? mlt_property_get_double(p[3], fps, locale) : 0; value = p[2] ? catmull_rom_interpolate(points[0], points[1], points[2], points[3], progress) : points[1]; } error = mlt_property_set_double(self, value); } } else { mlt_property_pass(self, p[1]); } return error; } /** Create a new animation or refresh an existing one. * * \private \memberof mlt_property_s * \param self a property * \param fps the frame rate, which may be needed for converting a time string to frame units * \param locale the locale, which may be needed for converting a string to a real number * \param length the maximum number of frames when interpreting negative keyframe times, * <=0 if you don't care or need that */ static void refresh_animation(mlt_property self, double fps, mlt_locale_t locale, int length) { if (!self->animation) { self->animation = mlt_animation_new(); self->serialiser = (mlt_serialiser) mlt_animation_serialize_tf; mlt_animation_parse(self->animation, self->prop_string, length, fps, locale); } else if (!mlt_animation_get_string(self->animation)) { // The animation clears its string if it is modified. // Do not use a property string that is out of sync. self->types &= ~mlt_prop_string; free(self->prop_string); self->prop_string = NULL; } else if ((self->types & mlt_prop_string) && self->prop_string) { mlt_animation_refresh(self->animation, self->prop_string, length); } else if (length >= 0) { mlt_animation_set_length(self->animation, length); } } /** Get the real number at a frame position. * * \public \memberof mlt_property_s * \param self a property * \param fps the frame rate, which may be needed for converting a time string to frame units * \param locale the locale, which may be needed for converting a string to a real number * \param position the frame number * \param length the maximum number of frames when interpreting negative keyframe times, * <=0 if you don't care or need that * \return the real number */ double mlt_property_anim_get_double( mlt_property self, double fps, mlt_locale_t locale, int position, int length) { double result; pthread_mutex_lock(&self->mutex); if (mlt_property_is_anim(self)) { struct mlt_animation_item_s item; item.property = mlt_property_init(); refresh_animation(self, fps, locale, length); mlt_animation_get_item(self->animation, &item, position); pthread_mutex_unlock(&self->mutex); result = mlt_property_get_double(item.property, fps, locale); mlt_property_close(item.property); } else { pthread_mutex_unlock(&self->mutex); result = mlt_property_get_double(self, fps, locale); } return result; } /** Get the property as an integer number at a frame position. * * \public \memberof mlt_property_s * \param self a property * \param fps the frame rate, which may be needed for converting a time string to frame units * \param locale the locale, which may be needed for converting a string to a real number * \param position the frame number * \param length the maximum number of frames when interpreting negative keyframe times, * <=0 if you don't care or need that * \return an integer value */ int mlt_property_anim_get_int( mlt_property self, double fps, mlt_locale_t locale, int position, int length) { int result; pthread_mutex_lock(&self->mutex); if (mlt_property_is_anim(self)) { struct mlt_animation_item_s item; item.property = mlt_property_init(); refresh_animation(self, fps, locale, length); mlt_animation_get_item(self->animation, &item, position); pthread_mutex_unlock(&self->mutex); result = mlt_property_get_int(item.property, fps, locale); mlt_property_close(item.property); } else { pthread_mutex_unlock(&self->mutex); result = mlt_property_get_int(self, fps, locale); } return result; } /** Get the string at certain a frame position. * * \public \memberof mlt_property_s * \param self a property * \param fps the frame rate, which may be needed for converting a time string to frame units * \param locale the locale, which may be needed for converting a string to a real number * \param position the frame number * \param length the maximum number of frames when interpreting negative keyframe times, * <=0 if you don't care or need that * \return the string representation of the property or NULL if failed */ char *mlt_property_anim_get_string( mlt_property self, double fps, mlt_locale_t locale, int position, int length) { char *result; pthread_mutex_lock(&self->mutex); if (mlt_property_is_anim(self)) { struct mlt_animation_item_s item; item.property = mlt_property_init(); if (!self->animation) refresh_animation(self, fps, locale, length); mlt_animation_get_item(self->animation, &item, position); free(self->prop_string); pthread_mutex_unlock(&self->mutex); self->prop_string = mlt_property_get_string_l(item.property, locale); pthread_mutex_lock(&self->mutex); if (self->prop_string) self->prop_string = strdup(self->prop_string); self->types |= mlt_prop_string; result = self->prop_string; mlt_property_close(item.property); pthread_mutex_unlock(&self->mutex); } else { pthread_mutex_unlock(&self->mutex); result = mlt_property_get_string_l(self, locale); } return result; } /** Set a property animation keyframe to a real number. * * \public \memberof mlt_property_s * \param self a property * \param value a double precision floating point value * \param fps the frame rate, which may be needed for converting a time string to frame units * \param locale the locale, which may be needed for converting a string to a real number * \param position the frame number * \param length the maximum number of frames when interpreting negative keyframe times, * <=0 if you don't care or need that * \param keyframe_type the interpolation method for this keyframe * \return false if successful, true to indicate error */ int mlt_property_anim_set_double(mlt_property self, double value, double fps, mlt_locale_t locale, int position, int length, mlt_keyframe_type keyframe_type) { int result; struct mlt_animation_item_s item; item.property = mlt_property_init(); item.frame = position; item.keyframe_type = keyframe_type; mlt_property_set_double(item.property, value); pthread_mutex_lock(&self->mutex); refresh_animation(self, fps, locale, length); result = mlt_animation_insert(self->animation, &item); mlt_animation_interpolate(self->animation); pthread_mutex_unlock(&self->mutex); mlt_property_close(item.property); return result; } /** Set a property animation keyframe to an integer value. * * \public \memberof mlt_property_s * \param self a property * \param value an integer * \param fps the frame rate, which may be needed for converting a time string to frame units * \param locale the locale, which may be needed for converting a string to a real number * \param position the frame number * \param length the maximum number of frames when interpreting negative keyframe times, * <=0 if you don't care or need that * \param keyframe_type the interpolation method for this keyframe * \return false if successful, true to indicate error */ int mlt_property_anim_set_int(mlt_property self, int value, double fps, mlt_locale_t locale, int position, int length, mlt_keyframe_type keyframe_type) { int result; struct mlt_animation_item_s item; item.property = mlt_property_init(); item.frame = position; item.keyframe_type = keyframe_type; mlt_property_set_int(item.property, value); pthread_mutex_lock(&self->mutex); refresh_animation(self, fps, locale, length); result = mlt_animation_insert(self->animation, &item); mlt_animation_interpolate(self->animation); pthread_mutex_unlock(&self->mutex); mlt_property_close(item.property); return result; } /** Set a property animation keyframe to a string. * * Strings only support discrete animation. Do not use this to set a property's * animation string that contains a semicolon-delimited set of values; use * mlt_property_set() for that. * \public \memberof mlt_property_s * \param self a property * \param value a string * \param fps the frame rate, which may be needed for converting a time string to frame units * \param locale the locale, which may be needed for converting a string to a real number * \param position the frame number * \param length the maximum number of frames when interpreting negative keyframe times, * <=0 if you don't care or need that * \return false if successful, true to indicate error */ int mlt_property_anim_set_string( mlt_property self, const char *value, double fps, mlt_locale_t locale, int position, int length) { int result; struct mlt_animation_item_s item; item.property = mlt_property_init(); item.frame = position; item.keyframe_type = mlt_keyframe_discrete; mlt_property_set_string(item.property, value); pthread_mutex_lock(&self->mutex); refresh_animation(self, fps, locale, length); result = mlt_animation_insert(self->animation, &item); mlt_animation_interpolate(self->animation); pthread_mutex_unlock(&self->mutex); mlt_property_close(item.property); return result; } /** Get an object's animation object. * * You might need to call another mlt_property_anim_ function to actually construct * the animation, as this is a simple accessor function. * \public \memberof mlt_property_s * \param self a property * \return the animation object or NULL if there is no animation */ mlt_animation mlt_property_get_animation(mlt_property self) { pthread_mutex_lock(&self->mutex); mlt_animation result = self->animation; pthread_mutex_unlock(&self->mutex); return result; } /** Set the property to a color value. * * \public \memberof mlt_property_s * \param self a property * \param value an integer * \return false */ int mlt_property_set_color(mlt_property self, mlt_color value) { pthread_mutex_lock(&self->mutex); clear_property(self); self->types = mlt_prop_color; uint32_t int_value = (value.r << 24) | (value.g << 16) | (value.b << 8) | value.a; self->prop_int = int_value; pthread_mutex_unlock(&self->mutex); return 0; } /** Get the property as a color. * * \public \memberof mlt_property_s * \param self a property * \param fps frames per second, used when converting from time value * \param locale the locale to use for when converting from a string * \return a color value */ mlt_color mlt_property_get_color(mlt_property self, double fps, mlt_locale_t locale) { mlt_color result = {0xff, 0xff, 0xff, 0xff}; int color_int = mlt_property_get_int(self, fps, locale); if ((self->types & mlt_prop_string) && self->prop_string) { const char *color = mlt_property_get_string_l(self, locale); if (!strcmp(color, "red")) { result.r = 0xff; result.g = 0x00; result.b = 0x00; return result; } if (!strcmp(color, "green")) { result.r = 0x00; result.g = 0xff; result.b = 0x00; return result; } if (!strcmp(color, "blue")) { result.r = 0x00; result.g = 0x00; result.b = 0xff; return result; } if (!strcmp(color, "black")) { result.r = 0x00; result.g = 0x00; result.b = 0x00; return result; } if (!strcmp(color, "white")) { return result; } } result.r = (color_int >> 24) & 0xff; result.g = (color_int >> 16) & 0xff; result.b = (color_int >> 8) & 0xff; result.a = (color_int) &0xff; return result; } /** Set a property animation keyframe to a color. * * \public \memberof mlt_property_s * \param self a property * \param value a color * \param fps the frame rate, which may be needed for converting a time string to frame units * \param locale the locale, which may be needed for converting a string to a real number * \param position the frame number * \param length the maximum number of frames when interpreting negative keyframe times, * <=0 if you don't care or need that * \param keyframe_type the interpolation method for this keyframe * \return false if successful, true to indicate error */ int mlt_property_anim_set_color(mlt_property self, mlt_color value, double fps, mlt_locale_t locale, int position, int length, mlt_keyframe_type keyframe_type) { int result; struct mlt_animation_item_s item; item.property = mlt_property_init(); item.frame = position; item.keyframe_type = keyframe_type; mlt_property_set_color(item.property, value); pthread_mutex_lock(&self->mutex); refresh_animation(self, fps, locale, length); result = mlt_animation_insert(self->animation, &item); mlt_animation_interpolate(self->animation); pthread_mutex_unlock(&self->mutex); mlt_property_close(item.property); return result; } /** Get a color at a frame position. * * \public \memberof mlt_property_s * \param self a property * \param fps the frame rate, which may be needed for converting a time string to frame units * \param locale the locale, which may be needed for converting a string to a real number * \param position the frame number * \param length the maximum number of frames when interpreting negative keyframe times, * <=0 if you don't care or need that * \return the color */ mlt_color mlt_property_anim_get_color( mlt_property self, double fps, mlt_locale_t locale, int position, int length) { mlt_color result; pthread_mutex_lock(&self->mutex); if (mlt_property_is_anim(self)) { struct mlt_animation_item_s item; item.property = mlt_property_init(); item.property->types = mlt_prop_color; refresh_animation(self, fps, locale, length); mlt_animation_get_item(self->animation, &item, position); pthread_mutex_unlock(&self->mutex); result = mlt_property_get_color(item.property, fps, locale); mlt_property_close(item.property); } else { pthread_mutex_unlock(&self->mutex); result = mlt_property_get_color(self, fps, locale); } return result; } /** Convert a rectangle value into a string. * * The canonical form of a mlt_rect * is a space delimited "x y w h o" even though many kinds of field delimiters * may be used to convert a string to a rectangle. * \private \memberof mlt_property_s * \param rect the rectangle to convert * \param length not used * \return the string representation of a rectangle */ static char *serialise_mlt_rect(mlt_rect *rect, int length) { char *result = calloc(1, 100); if (rect->x != DBL_MIN) sprintf(result + strlen(result), "%g", rect->x); if (rect->y != DBL_MIN) sprintf(result + strlen(result), " %g", rect->y); if (rect->w != DBL_MIN) sprintf(result + strlen(result), " %g", rect->w); if (rect->h != DBL_MIN) sprintf(result + strlen(result), " %g", rect->h); if (rect->o != DBL_MIN) sprintf(result + strlen(result), " %g", rect->o); return result; } /** Set a property to a mlt_rect rectangle. * * \public \memberof mlt_property_s * \param self a property * \param value a rectangle * \return false */ int mlt_property_set_rect(mlt_property self, mlt_rect value) { pthread_mutex_lock(&self->mutex); clear_property(self); self->types = mlt_prop_rect | mlt_prop_data; self->length = sizeof(value); self->data = calloc(1, self->length); memcpy(self->data, &value, self->length); self->destructor = free; self->serialiser = (mlt_serialiser) serialise_mlt_rect; pthread_mutex_unlock(&self->mutex); return 0; } /** Get the property as a rectangle. * * You can use any non-numeric character(s) as a field delimiter. * If the number has a '%' immediately following it, the number is divided by * 100 to convert it into a real number. * \public \memberof mlt_property_s * \param self a property * \param locale the locale to use for when converting from a string * \return a rectangle value */ mlt_rect mlt_property_get_rect(mlt_property self, mlt_locale_t locale) { mlt_rect rect = {DBL_MIN, DBL_MIN, DBL_MIN, DBL_MIN, DBL_MIN}; if ((self->types & mlt_prop_rect) && self->data) rect = *((mlt_rect *) self->data); else if (self->types & mlt_prop_double) rect.x = self->prop_double; else if (self->types & mlt_prop_int || self->types & mlt_prop_color) rect.x = (double) self->prop_int; else if (self->types & mlt_prop_position) rect.x = (double) self->prop_position; else if (self->types & mlt_prop_int64) rect.x = (double) self->prop_int64; else if ((self->types & mlt_prop_string) && self->prop_string) { char *value = self->prop_string; char *p = NULL; int count = 0; #if !defined(__GLIBC__) && !defined(__APPLE__) && !defined(_WIN32) && !defined(HAVE_STRTOD_L) char *orig_localename = NULL; if (locale) { // Protect damaging the global locale from a temporary locale on another thread. pthread_mutex_lock(&self->mutex); // Get the current locale orig_localename = strdup(setlocale(LC_NUMERIC, NULL)); // Set the new locale setlocale(LC_NUMERIC, locale); } #endif while (*value) { double temp; #if defined(__GLIBC__) || defined(__APPLE__) || defined(HAVE_STRTOD_L) if (locale) temp = strtod_l(value, &p, locale); else #endif temp = strtod(value, &p); if (p != value) { if (p[0] == '%') { temp /= 100.0; p++; } // Chomp the delimiter. if (*p) p++; // Assign the value to appropriate field. switch (count) { case 0: rect.x = temp; break; case 1: rect.y = temp; break; case 2: rect.w = temp; break; case 3: rect.h = temp; break; case 4: rect.o = temp; break; } } else { p++; } value = p; count++; } #if !defined(__GLIBC__) && !defined(__APPLE__) && !defined(_WIN32) && !defined(HAVE_STRTOD_L) if (locale) { // Restore the current locale setlocale(LC_NUMERIC, orig_localename); free(orig_localename); pthread_mutex_unlock(&self->mutex); } #endif } return rect; } /** Set a property animation keyframe to a rectangle. * * \public \memberof mlt_property_s * \param self a property * \param value a rectangle * \param fps the frame rate, which may be needed for converting a time string to frame units * \param locale the locale, which may be needed for converting a string to a real number * \param position the frame number * \param length the maximum number of frames when interpreting negative keyframe times, * <=0 if you don't care or need that * \param keyframe_type the interpolation method for this keyframe * \return false if successful, true to indicate error */ int mlt_property_anim_set_rect(mlt_property self, mlt_rect value, double fps, mlt_locale_t locale, int position, int length, mlt_keyframe_type keyframe_type) { int result; struct mlt_animation_item_s item; item.property = mlt_property_init(); item.frame = position; item.keyframe_type = keyframe_type; mlt_property_set_rect(item.property, value); pthread_mutex_lock(&self->mutex); refresh_animation(self, fps, locale, length); result = mlt_animation_insert(self->animation, &item); mlt_animation_interpolate(self->animation); pthread_mutex_unlock(&self->mutex); mlt_property_close(item.property); return result; } /** Get a rectangle at a frame position. * * \public \memberof mlt_property_s * \param self a property * \param fps the frame rate, which may be needed for converting a time string to frame units * \param locale the locale, which may be needed for converting a string to a real number * \param position the frame number * \param length the maximum number of frames when interpreting negative keyframe times, * <=0 if you don't care or need that * \return the rectangle */ mlt_rect mlt_property_anim_get_rect( mlt_property self, double fps, mlt_locale_t locale, int position, int length) { mlt_rect result; pthread_mutex_lock(&self->mutex); if (mlt_property_is_anim(self)) { struct mlt_animation_item_s item; item.property = mlt_property_init(); item.property->types = mlt_prop_rect; refresh_animation(self, fps, locale, length); mlt_animation_get_item(self->animation, &item, position); pthread_mutex_unlock(&self->mutex); result = mlt_property_get_rect(item.property, locale); mlt_property_close(item.property); } else { pthread_mutex_unlock(&self->mutex); result = mlt_property_get_rect(self, locale); } return result; } /** Set a nested properties object. * * \public \memberof mlt_property_s * \param self a property * \param properties the properties list to nest into \p self with \p name * \return true if error */ int mlt_property_set_properties(mlt_property self, mlt_properties properties) { pthread_mutex_lock(&self->mutex); clear_property(self); self->properties = properties; mlt_properties_inc_ref(properties); pthread_mutex_unlock(&self->mutex); return 0; } /** Get a nested properties object. * * \public \memberof mlt_property_s * \param self a property * \return the nested properties list */ mlt_properties mlt_property_get_properties(mlt_property self) { mlt_properties properties = NULL; pthread_mutex_lock(&self->mutex); properties = self->properties; pthread_mutex_unlock(&self->mutex); return properties; } /** Check if a property is animated. * * This is not a thread-safe function because it is used internally by * mlt_property_s under a lock. However, external callers should protect it. * \public \memberof mlt_property_s * \param self a property * \return true if the property is animated */ int mlt_property_is_anim(mlt_property self) { return self->animation || (self->prop_string && strchr(self->prop_string, '=')); } /** Check if a property is a color. * \public \memberof mlt_property_s * \param self a property * \return true if the property is color */ extern int mlt_property_is_color(mlt_property self) { int result = 0; if (self) { pthread_mutex_lock(&self->mutex); if (self->types & mlt_prop_color) { result = 1; } else { const char *value = self->prop_string; if (value && ((strlen(value) > 6 && value[0] == '#') || (strlen(value) > 7 && value[0] == '0' && value[1] == 'x'))) { result = 1; } } pthread_mutex_unlock(&self->mutex); } return result; } /** Check if a property is a rect. * \public \memberof mlt_property_s * \param self a property * \return true if the property is a rect */ extern int mlt_property_is_rect(mlt_property self) { int result = 0; if (self) { result = self->types & mlt_prop_rect; } return result; } mlt-7.22.0/src/framework/mlt_property.h000664 000000 000000 00000014711 14531534050 020057 0ustar00rootroot000000 000000 /** * \file mlt_property.h * \brief Property class declaration * \see mlt_property_s * * Copyright (C) 2003-2023 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef MLT_PROPERTY_H #define MLT_PROPERTY_H #include "mlt_types.h" #if defined(__FreeBSD__) /* This header has existed since 1994 and defines __FreeBSD_version below. */ #include #endif #if (defined(__linux__) && !defined(__APPLE__)) #include typedef locale_t mlt_locale_t; #elif defined(__APPLE__) || (defined(__FreeBSD_version) && __FreeBSD_version >= 900506) #include typedef locale_t mlt_locale_t; #elif defined(__OpenBSD__) /* XXX matches __nop_locale glue in libc++ */ typedef void *mlt_locale_t; #else typedef char *mlt_locale_t; #endif extern mlt_property mlt_property_init(); extern void mlt_property_clear(mlt_property self); extern int mlt_property_is_clear(mlt_property self); extern int mlt_property_set_int(mlt_property self, int value); extern int mlt_property_set_double(mlt_property self, double value); extern int mlt_property_set_position(mlt_property self, mlt_position value); extern int mlt_property_set_int64(mlt_property self, int64_t value); extern int mlt_property_set_string(mlt_property self, const char *value); extern int mlt_property_set_data(mlt_property self, void *value, int length, mlt_destructor destructor, mlt_serialiser serialiser); extern int mlt_property_get_int(mlt_property self, double fps, mlt_locale_t); extern double mlt_property_get_double(mlt_property self, double fps, mlt_locale_t); extern mlt_position mlt_property_get_position(mlt_property self, double fps, mlt_locale_t); extern int64_t mlt_property_get_int64(mlt_property self); extern char *mlt_property_get_string_tf(mlt_property self, mlt_time_format); extern char *mlt_property_get_string(mlt_property self); extern char *mlt_property_get_string_l_tf(mlt_property self, mlt_locale_t, mlt_time_format); extern char *mlt_property_get_string_l(mlt_property self, mlt_locale_t); extern void *mlt_property_get_data(mlt_property self, int *length); extern void mlt_property_close(mlt_property self); extern void mlt_property_pass(mlt_property self, mlt_property that); extern char *mlt_property_get_time(mlt_property self, mlt_time_format, double fps, mlt_locale_t); extern int mlt_property_interpolate(mlt_property self, mlt_property points[], double progress, double fps, mlt_locale_t locale, mlt_keyframe_type interp); extern double mlt_property_anim_get_double( mlt_property self, double fps, mlt_locale_t locale, int position, int length); extern int mlt_property_anim_get_int( mlt_property self, double fps, mlt_locale_t locale, int position, int length); extern char *mlt_property_anim_get_string( mlt_property self, double fps, mlt_locale_t locale, int position, int length); extern int mlt_property_anim_set_double(mlt_property self, double value, double fps, mlt_locale_t locale, int position, int length, mlt_keyframe_type keyframe_type); extern int mlt_property_anim_set_int(mlt_property self, int value, double fps, mlt_locale_t locale, int position, int length, mlt_keyframe_type keyframe_type); extern int mlt_property_anim_set_string( mlt_property self, const char *value, double fps, mlt_locale_t locale, int position, int length); extern mlt_animation mlt_property_get_animation(mlt_property self); extern int mlt_property_is_anim(mlt_property self); extern int mlt_property_set_color(mlt_property self, mlt_color value); extern mlt_color mlt_property_get_color(mlt_property self, double fps, mlt_locale_t locale); extern int mlt_property_anim_set_color(mlt_property self, mlt_color value, double fps, mlt_locale_t locale, int position, int length, mlt_keyframe_type keyframe_type); extern mlt_color mlt_property_anim_get_color( mlt_property self, double fps, mlt_locale_t locale, int position, int length); extern int mlt_property_set_rect(mlt_property self, mlt_rect value); extern mlt_rect mlt_property_get_rect(mlt_property self, mlt_locale_t locale); extern int mlt_property_anim_set_rect(mlt_property self, mlt_rect value, double fps, mlt_locale_t locale, int position, int length, mlt_keyframe_type keyframe_type); extern mlt_rect mlt_property_anim_get_rect( mlt_property self, double fps, mlt_locale_t locale, int position, int length); extern int mlt_property_set_properties(mlt_property self, mlt_properties properties); extern mlt_properties mlt_property_get_properties(mlt_property self); extern int mlt_property_is_color(mlt_property self); extern int mlt_property_is_numeric(mlt_property self, mlt_locale_t locale); extern int mlt_property_is_rect(mlt_property self); #endif mlt-7.22.0/src/framework/mlt_repository.c000664 000000 000000 00000050721 14531534050 020406 0ustar00rootroot000000 000000 /** * \file mlt_repository.c * \brief provides a map between service and shared objects * \see mlt_repository_s * * Copyright (C) 2003-2023 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "mlt_repository.h" #include "mlt_factory.h" #include "mlt_log.h" #include "mlt_properties.h" #include "mlt_tokeniser.h" #include #include #include #include #include #include #include /** \brief Repository class * * The Repository is a collection of plugin modules and their services and service metadata. * * \extends mlt_properties_s * \properties \p language a cached list of user locales */ struct mlt_repository_s { struct mlt_properties_s parent; /// a list of object files mlt_properties consumers; /// a list of entry points for consumers mlt_properties filters; /// a list of entry points for filters mlt_properties links; /// a list of entry points for links mlt_properties producers; /// a list of entry points for producers mlt_properties transitions; /// a list of entry points for transitions }; /** Construct a new repository. * * \public \memberof mlt_repository_s * \param directory the full path of a directory from which to read modules * \return a new repository or NULL if failed */ mlt_repository mlt_repository_init(const char *directory) { // Safety check if (directory == NULL || strcmp(directory, "") == 0) return NULL; // Construct the repository mlt_repository self = calloc(1, sizeof(struct mlt_repository_s)); mlt_properties_init(&self->parent, self); self->consumers = mlt_properties_new(); self->filters = mlt_properties_new(); self->links = mlt_properties_new(); self->producers = mlt_properties_new(); self->transitions = mlt_properties_new(); // Get the directory list mlt_properties dir = mlt_properties_new(); int count = mlt_properties_dir_list(dir, directory, NULL, 0); int i; int plugin_count = 0; #ifdef _WIN32 char *syspath = getenv("PATH"); char *exedir = mlt_environment("MLT_APPDIR"); #ifdef NODEPLOY char *sep = "\\bin;"; #else char *sep = ";"; #endif char *newpath; newpath = calloc(1, 5 + strlen(exedir) + strlen(sep) + strlen(syspath) + 1); strcat(newpath, "PATH="); // len=5 strcat(newpath, exedir); strcat(newpath, sep); strcat(newpath, syspath); putenv(newpath); free(newpath); #endif mlt_tokeniser tokeniser = mlt_tokeniser_init(); int dl_length = mlt_tokeniser_parse_new(tokeniser, getenv("MLT_REPOSITORY_DENY"), ":"); // check if both qt5 and qt6 modules are available… int qt_module_count = 0; int glaxnimate_module_count = 0; for (i = 0; i < count; i++) { const char *object_name = mlt_properties_get_value(dir, i); qt_module_count += !!strstr(object_name, "libmltqt"); glaxnimate_module_count += !!strstr(object_name, "libmltglaxnimate"); } // …and not blocked for (int j = 0; j < dl_length; j++) { char *denyfile = mlt_tokeniser_get_string(tokeniser, j); qt_module_count -= !strncmp("libmltqt", denyfile, strlen("libmltqt")); glaxnimate_module_count -= !strncmp("libmltglaxnimate", denyfile, strlen("libmltglaxnimate")); } // Iterate over files for (i = 0; i < count; i++) { int flags = RTLD_NOW; const char *object_name = mlt_properties_get_value(dir, i); // check if the plugin was asked to be skipped through MLT_REPOSITORY_DENY int ignore = 0; for (int j = 0; j < dl_length; j++) { char *denyfile = calloc(1, strlen(directory) + strlen(mlt_tokeniser_get_string(tokeniser, j)) + 3); sprintf(denyfile, "%s/%s.", directory, mlt_tokeniser_get_string(tokeniser, j)); ignore += !strncmp(object_name, denyfile, strlen(denyfile)); free(denyfile); } // in case we have both qt modules, we block qt6 to avoid conflicts if ((qt_module_count == 2 && strstr(object_name, "libmltqt6")) || (glaxnimate_module_count == 2 && strstr(object_name, "libmltglaxnimate-qt6"))) { ignore = 1; } if (ignore) { mlt_log_info(NULL, "%s: skip plugin %s\n", __FUNCTION__, object_name); continue; } mlt_log_debug(NULL, "%s: processing plugin at %s\n", __FUNCTION__, object_name); // Open the shared object void *object = dlopen(object_name, flags); if (object != NULL) { // Get the registration function mlt_repository_callback symbol_ptr = dlsym(object, "mlt_register"); // Call the registration function if (symbol_ptr != NULL) { symbol_ptr(self); // Register the object file for closure mlt_properties_set_data(&self->parent, object_name, object, 0, (mlt_destructor) dlclose, NULL); ++plugin_count; } else { dlclose(object); } } else if (strstr(object_name, "libmlt")) { mlt_log_warning(NULL, "%s: failed to dlopen %s\n (%s)\n", __FUNCTION__, object_name, dlerror()); } } if (!plugin_count) mlt_log_error(NULL, "%s: no plugins found in \"%s\"\n", __FUNCTION__, directory); mlt_properties_close(dir); mlt_tokeniser_close(tokeniser); return self; } /** Create a properties list for a service holding a function pointer to its constructor function. * * \private \memberof mlt_repository_s * \param symbol a pointer to a function that can create the service. * \return a properties list */ static mlt_properties new_service(void *symbol) { mlt_properties properties = mlt_properties_new(); mlt_properties_set_data(properties, "symbol", symbol, 0, NULL, NULL); return properties; } /** Register a service with the repository. * * Typically, this is invoked by a module within its mlt_register(). * * \public \memberof mlt_repository_s * \param self a repository * \param service_type a service class * \param service the name of a service * \param symbol a pointer to a function to create the service */ void mlt_repository_register(mlt_repository self, mlt_service_type service_type, const char *service, mlt_register_callback symbol) { // Add the entry point to the corresponding service list switch (service_type) { case mlt_service_consumer_type: mlt_properties_set_data(self->consumers, service, new_service(symbol), 0, (mlt_destructor) mlt_properties_close, NULL); break; case mlt_service_filter_type: mlt_properties_set_data(self->filters, service, new_service(symbol), 0, (mlt_destructor) mlt_properties_close, NULL); break; case mlt_service_link_type: mlt_properties_set_data(self->links, service, new_service(symbol), 0, (mlt_destructor) mlt_properties_close, NULL); break; case mlt_service_producer_type: mlt_properties_set_data(self->producers, service, new_service(symbol), 0, (mlt_destructor) mlt_properties_close, NULL); break; case mlt_service_transition_type: mlt_properties_set_data(self->transitions, service, new_service(symbol), 0, (mlt_destructor) mlt_properties_close, NULL); break; default: mlt_log_error(NULL, "%s: Unable to register \"%s\"\n", __FUNCTION__, service); break; } } /** Get the repository properties for particular service class. * * \private \memberof mlt_repository_s * \param self a repository * \param type a service class * \param service the name of a service * \return a properties list or NULL if error */ static mlt_properties get_service_properties(mlt_repository self, mlt_service_type type, const char *service) { mlt_properties service_properties = NULL; // Get the entry point from the corresponding service list switch (type) { case mlt_service_consumer_type: service_properties = mlt_properties_get_data(self->consumers, service, NULL); break; case mlt_service_filter_type: service_properties = mlt_properties_get_data(self->filters, service, NULL); break; case mlt_service_link_type: service_properties = mlt_properties_get_data(self->links, service, NULL); break; case mlt_service_producer_type: service_properties = mlt_properties_get_data(self->producers, service, NULL); break; case mlt_service_transition_type: service_properties = mlt_properties_get_data(self->transitions, service, NULL); break; default: break; } return service_properties; } /** Construct a new instance of a service. * * \public \memberof mlt_repository_s * \param self a repository * \param profile a \p mlt_profile to give the service * \param type a service class * \param service the name of the service * \param input an optional argument to the service constructor */ void *mlt_repository_create(mlt_repository self, mlt_profile profile, mlt_service_type type, const char *service, const void *input) { mlt_properties properties = get_service_properties(self, type, service); if (properties != NULL) { mlt_register_callback symbol_ptr = mlt_properties_get_data(properties, "symbol", NULL); // Construct the service return (symbol_ptr != NULL) ? symbol_ptr(profile, type, service, input) : NULL; } return NULL; } /** Destroy a repository and free its resources. * * \public \memberof mlt_repository_s * \param self a repository */ void mlt_repository_close(mlt_repository self) { mlt_properties_close(self->consumers); mlt_properties_close(self->filters); mlt_properties_close(self->producers); mlt_properties_close(self->transitions); mlt_properties_close(&self->parent); free(self); } /** Get the list of registered consumers. * * \public \memberof mlt_repository_s * \param self a repository * \return a properties list containing all of the consumers */ mlt_properties mlt_repository_consumers(mlt_repository self) { return self->consumers; } /** Get the list of registered filters. * * \public \memberof mlt_repository_s * \param self a repository * \return a properties list of all of the filters */ mlt_properties mlt_repository_filters(mlt_repository self) { return self->filters; } /** Get the list of registered links. * * \public \memberof mlt_repository_s * \param self a repository * \return a properties list of all of the links */ mlt_properties mlt_repository_links(mlt_repository self) { return self->links; } /** Get the list of registered producers. * * \public \memberof mlt_repository_s * \param self a repository * \return a properties list of all of the producers */ mlt_properties mlt_repository_producers(mlt_repository self) { return self->producers; } /** Get the list of registered transitions. * * \public \memberof mlt_repository_s * \param self a repository * \return a properties list of all of the transitions */ mlt_properties mlt_repository_transitions(mlt_repository self) { return self->transitions; } /** Register the metadata for a service. * * IMPORTANT: mlt_repository will take responsibility for deallocating the metadata properties * that you supply! * * \public \memberof mlt_repository_s * \param self a repository * \param type a service class * \param service the name of a service * \param callback the pointer to a function that can supply metadata * \param callback_data an opaque user data pointer to be supplied on the callback */ void mlt_repository_register_metadata(mlt_repository self, mlt_service_type type, const char *service, mlt_metadata_callback callback, void *callback_data) { mlt_properties service_properties = get_service_properties(self, type, service); mlt_properties_set_data(service_properties, "metadata_cb", callback, 0, NULL, NULL); mlt_properties_set_data(service_properties, "metadata_cb_data", callback_data, 0, NULL, NULL); } /** Get the metadata about a service. * * Returns NULL if service or its metadata are unavailable. * * \public \memberof mlt_repository_s * \param self a repository * \param type a service class * \param service the name of a service * \return the service metadata as a structured properties list */ mlt_properties mlt_repository_metadata(mlt_repository self, mlt_service_type type, const char *service) { mlt_properties metadata = NULL; mlt_properties properties = get_service_properties(self, type, service); // If this is a valid service if (properties) { // Lookup cached metadata metadata = mlt_properties_get_data(properties, "metadata", NULL); if (!metadata) { // Not cached, so get the registered metadata callback function mlt_metadata_callback callback = mlt_properties_get_data(properties, "metadata_cb", NULL); // If a metadata callback function is registered if (callback) { // Fetch the callback data arg void *data = mlt_properties_get_data(properties, "metadata_cb_data", NULL); // Fetch the metadata through the callback metadata = callback(type, service, data); // Cache the metadata if (metadata) // Include dellocation and serialisation mlt_properties_set_data(properties, "metadata", metadata, 0, (mlt_destructor) mlt_properties_close, (mlt_serialiser) mlt_properties_serialise_yaml); } } } return metadata; } /** Try to determine the locale from some commonly used environment variables. * * \private \memberof mlt_repository_s * \return a string containing the locale id or NULL if unknown */ static char *getenv_locale() { char *s = getenv("LANGUAGE"); if (s && s[0]) return s; s = getenv("LC_ALL"); if (s && s[0]) return s; s = getenv("LC_MESSAGES"); if (s && s[0]) return s; s = getenv("LANG"); if (s && s[0]) return s; return NULL; } /** Return a list of user-preferred language codes taken from environment variables. * * A module should use this to locate a localized YAML Tiny file from which to build * its metadata strucutured properties. * * \public \memberof mlt_repository_s * \param self a repository * \return a properties list that is a list (not a map) of locales, defaults to "en" if not * overridden by environment variables, in order: LANGUAGE, LC_ALL, LC_MESSAGES, LANG */ mlt_properties mlt_repository_languages(mlt_repository self) { mlt_properties languages = mlt_properties_get_data(&self->parent, "languages", NULL); if (languages) return languages; languages = mlt_properties_new(); char *locale = getenv_locale(); if (locale) { locale = strdup(locale); mlt_tokeniser tokeniser = mlt_tokeniser_init(); int count = mlt_tokeniser_parse_new(tokeniser, locale, ":"); if (count) { int i; for (i = 0; i < count; i++) { char *locale = mlt_tokeniser_get_string(tokeniser, i); if (strcmp(locale, "C") == 0 || strcmp(locale, "POSIX") == 0) locale = "en"; else if (strlen(locale) > 2) locale[2] = 0; char string[21]; snprintf(string, sizeof(string), "%d", i); mlt_properties_set(languages, string, locale); } } else { mlt_properties_set(languages, "0", "en"); } free(locale); mlt_tokeniser_close(tokeniser); } else { mlt_properties_set(languages, "0", "en"); } mlt_properties_set_data(&self->parent, "languages", languages, 0, (mlt_destructor) mlt_properties_close, NULL); return languages; } static void list_presets(mlt_properties properties, const char *path, const char *dirname) { DIR *dir = opendir(dirname); if (dir) { struct dirent *de = readdir(dir); char fullname[PATH_MAX]; while (de != NULL) { if (de->d_name[0] != '.' && de->d_name[strlen(de->d_name) - 1] != '~') { struct stat info; snprintf(fullname, sizeof(fullname), "%s/%s", dirname, de->d_name); stat(fullname, &info); if (S_ISDIR(info.st_mode)) { // recurse into subdirectories char sub[PATH_MAX]; if (path) snprintf(sub, sizeof(sub), "%s/%s", path, de->d_name); else strncpy(sub, de->d_name, sizeof(sub)); list_presets(properties, sub, fullname); } else { // load the preset mlt_properties preset = mlt_properties_load(fullname); if (preset && mlt_properties_count(preset)) { snprintf(fullname, 1024, "%s/%s", path, de->d_name); mlt_properties_set_data(properties, fullname, preset, 0, (mlt_destructor) mlt_properties_close, NULL); } } } de = readdir(dir); } closedir(dir); } } /** Get the list of presets. * * \public \memberof mlt_repository_s * \return a properties list of all the presets */ mlt_properties mlt_repository_presets() { mlt_properties result = mlt_properties_new(); list_presets(result, NULL, mlt_environment("MLT_PRESETS_PATH")); return result; } mlt-7.22.0/src/framework/mlt_repository.h000664 000000 000000 00000010371 14531534050 020410 0ustar00rootroot000000 000000 /** * \file mlt_repository.h * \brief provides a map between service and shared objects * \see mlt_repository_s * * Copyright (C) 2003-2014 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef MLT_REPOSITORY_H #define MLT_REPOSITORY_H #include "mlt_profile.h" #include "mlt_types.h" /** This callback is the main entry point into a module, which must be exported * with the symbol "mlt_register". * * Inside the callback, the module registers the additional callbacks below. */ typedef void (*mlt_repository_callback)(mlt_repository); /** The callback function that modules implement to construct a service. */ typedef void *(*mlt_register_callback)(mlt_profile, mlt_service_type, const char * /* service name */, const void * /* arg */); /** The callback function that modules implement to supply metadata as a properties list. */ typedef mlt_properties (*mlt_metadata_callback)(mlt_service_type, const char * /* service name */, void * /* callback_data */); /** A convenience macro to create an entry point for service registration. */ #define MLT_REPOSITORY void mlt_register(mlt_repository repository) /** A convenience macro to a register service in a more declarative manner. */ #define MLT_REGISTER(type, service, symbol) \ (mlt_repository_register(repository, (type), (service), (mlt_register_callback) (symbol))) /** A convenience macro to a register metadata in a more declarative manner. */ #define MLT_REGISTER_METADATA(type, service, callback, data) \ (mlt_repository_register_metadata(repository, \ (type), \ (service), \ (mlt_metadata_callback) (callback), \ (data))) extern mlt_repository mlt_repository_init(const char *directory); extern void mlt_repository_register(mlt_repository self, mlt_service_type service_type, const char *service, mlt_register_callback); extern void *mlt_repository_create(mlt_repository self, mlt_profile profile, mlt_service_type type, const char *service, const void *arg); extern void mlt_repository_close(mlt_repository self); extern mlt_properties mlt_repository_consumers(mlt_repository self); extern mlt_properties mlt_repository_filters(mlt_repository self); extern mlt_properties mlt_repository_links(mlt_repository self); extern mlt_properties mlt_repository_producers(mlt_repository self); extern mlt_properties mlt_repository_transitions(mlt_repository self); extern void mlt_repository_register_metadata(mlt_repository self, mlt_service_type type, const char *service, mlt_metadata_callback, void *callback_data); extern mlt_properties mlt_repository_metadata(mlt_repository self, mlt_service_type type, const char *service); extern mlt_properties mlt_repository_languages(mlt_repository self); extern mlt_properties mlt_repository_presets(); #endif mlt-7.22.0/src/framework/mlt_service.c000664 000000 000000 00000074517 14531534050 017640 0ustar00rootroot000000 000000 /** * \file mlt_service.c * \brief interface definition for all service classes * \see mlt_service_s * * Copyright (C) 2003-2022 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "mlt_service.h" #include "mlt_cache.h" #include "mlt_factory.h" #include "mlt_filter.h" #include "mlt_frame.h" #include "mlt_log.h" #include "mlt_producer.h" #include #include #include #include /* IMPORTANT NOTES The base service implements a null frame producing service - as such, it is functional without extension and will produce test cards frames and PAL sized audio frames. PLEASE DO NOT CHANGE THIS BEHAVIOUR!!! OVERRIDE THE METHODS THAT CONTROL THIS IN EXTENDING CLASSES. */ /** \brief private service definition */ typedef struct { int size; int count; mlt_service *in; mlt_service out; int filter_count; int filter_size; mlt_filter *filters; pthread_mutex_t mutex; } mlt_service_base; /* Private methods */ static void mlt_service_disconnect(mlt_service self); static void mlt_service_connect(mlt_service self, mlt_service that); static int service_get_frame(mlt_service self, mlt_frame_ptr frame, int index); /** Initialize a service. * * \public \memberof mlt_service_s * \param self the service structure to initialize * \param child pointer to the child object for the subclass * \return true if there was an error */ int mlt_service_init(mlt_service self, void *child) { int error = 0; // Initialise everything to NULL memset(self, 0, sizeof(struct mlt_service_s)); // Assign the child self->child = child; // Generate local space self->local = calloc(1, sizeof(mlt_service_base)); // Associate the methods self->get_frame = service_get_frame; // Initialise the properties error = mlt_properties_init(&self->parent, self); if (error == 0) { self->parent.close = (mlt_destructor) mlt_service_close; self->parent.close_object = self; mlt_events_init(&self->parent); mlt_events_register(&self->parent, "service-changed"); mlt_events_register(&self->parent, "property-changed"); pthread_mutex_init(&((mlt_service_base *) self->local)->mutex, NULL); } return error; } /** Acquire a mutual exclusion lock on this service. * * \public \memberof mlt_service_s * \param self the service to lock */ void mlt_service_lock(mlt_service self) { if (self != NULL) pthread_mutex_lock(&((mlt_service_base *) self->local)->mutex); } /** Release a mutual exclusion lock on this service. * * \public \memberof mlt_service_s * \param self the service to unlock */ void mlt_service_unlock(mlt_service self) { if (self != NULL) pthread_mutex_unlock(&((mlt_service_base *) self->local)->mutex); } /** Identify the subclass of the service. * * \public \memberof mlt_service_s * \param self a service * \return the subclass */ mlt_service_type mlt_service_identify(mlt_service self) { mlt_service_type type = mlt_service_invalid_type; if (self != NULL) { mlt_properties properties = MLT_SERVICE_PROPERTIES(self); char *mlt_type = mlt_properties_get(properties, "mlt_type"); char *resource = mlt_properties_get(properties, "resource"); if (mlt_type == NULL) type = mlt_service_unknown_type; else if (resource != NULL && !strcmp(resource, "")) type = mlt_service_playlist_type; else if (resource != NULL && !strcmp(resource, "")) type = mlt_service_tractor_type; else if (resource != NULL && !strcmp(resource, "")) type = mlt_service_multitrack_type; else if (!strcmp(mlt_type, "mlt_producer")) type = mlt_service_producer_type; else if (!strcmp(mlt_type, "producer")) type = mlt_service_producer_type; else if (!strcmp(mlt_type, "filter")) type = mlt_service_filter_type; else if (!strcmp(mlt_type, "transition")) type = mlt_service_transition_type; else if (!strcmp(mlt_type, "chain")) type = mlt_service_chain_type; else if (!strcmp(mlt_type, "consumer")) type = mlt_service_consumer_type; else if (!strcmp(mlt_type, "link")) type = mlt_service_link_type; else type = mlt_service_unknown_type; } return type; } /** Connect a producer to the service. * * \public \memberof mlt_service_s * \param self a service * \param producer a producer * \param index which of potentially multiple producers to this service (0 based) * \return 0 for success, -1 for error, or 3 if \p producer is already connected to \p self */ int mlt_service_connect_producer(mlt_service self, mlt_service producer, int index) { int i = 0; // Get the service base mlt_service_base *base = self->local; // Special case 'track' index - only works for last filter(s) in a particular chain // but allows a filter to apply to the output frame regardless of which track it comes from if (index == -1) index = 0; // Check if the producer is already registered with this service for (i = 0; i < base->count; i++) if (base->in[i] == producer) return 3; // Allocate space if (index >= base->size) { int new_size = base->size + index + 10; base->in = realloc(base->in, new_size * sizeof(mlt_service)); if (base->in != NULL) { for (i = base->size; i < new_size; i++) base->in[i] = NULL; base->size = new_size; } } // If we have space, assign the input if (base->in != NULL && index >= 0 && index < base->size) { // Get the current service mlt_service current = (index < base->count) ? base->in[index] : NULL; // Increment the reference count on this producer if (producer != NULL) mlt_properties_inc_ref(MLT_SERVICE_PROPERTIES(producer)); // Now we disconnect the producer service from its consumer mlt_service_disconnect(producer); // Add the service to index specified base->in[index] = producer; // Determine the number of active tracks if (index >= base->count) base->count = index + 1; // Now we connect the producer to its connected consumer mlt_service_connect(producer, self); // Close the current service mlt_service_close(current); // Inform caller that all went well return 0; } else { return -1; } } /** Insert a producer connected to the service. * * mlt_service_connect_producer() appends or overwrites a producer at input * \p index whereas this function inserts pushing all of the inputs down by * 1 in the list of inputs. * * \public \memberof mlt_service_s * \param self a service * \param producer a producer * \param index which of potentially multiple producers to this service (0 based) * \return 0 for success, -1 for error, or 3 if \p producer is already connected to \p self */ int mlt_service_insert_producer(mlt_service self, mlt_service producer, int index) { // Get the service base mlt_service_base *base = self->local; if (index >= base->count) return mlt_service_connect_producer(self, producer, index); int i = 0; // Special case 'track' index - only works for last filter(s) in a particular chain // but allows a filter to apply to the output frame regardless of which track it comes from if (index == -1) index = 0; // Check if the producer is already registered with this service. for (i = 0; i < base->count; i++) if (base->in[i] == producer) return 3; // Allocate space if needed. if (base->count + 1 > base->size) { int new_size = base->size + 10; base->in = realloc(base->in, new_size * sizeof(mlt_service)); if (base->in != NULL) { memset(&base->in[base->size], 0, new_size - base->size); base->size = new_size; } } // If we have space, assign the input if (base->in && index >= 0 && index < base->size) { // Increment the reference count on this producer. if (producer != NULL) mlt_properties_inc_ref(MLT_SERVICE_PROPERTIES(producer)); // Disconnect the producer from its consumer. mlt_service_disconnect(producer); // Make room in the list for the producer. memmove(&base->in[index + 1], &base->in[index], (base->count - index) * sizeof(mlt_service)); // Add the service to index specified. base->in[index] = producer; // Increase the number of active tracks. base->count++; // Connect the producer to its connected consumer. mlt_service_connect(producer, self); // Inform caller that all went well return 0; } else { return -1; } } /** Remove the N-th producer. * * \public \memberof mlt_service_s * \param self a service * \param index which producer to remove (0-based) * \return true if there was an error */ int mlt_service_disconnect_producer(mlt_service self, int index) { mlt_service_base *base = self->local; if (base->in && index >= 0 && index < base->count) { mlt_service current = base->in[index]; if (current) { // Close the current producer. mlt_service_disconnect(current); mlt_service_close(current); base->in[index] = NULL; // Contract the list of producers. for (; index + 1 < base->count; index++) base->in[index] = base->in[index + 1]; base->count--; return 0; } } return -1; } /** Remove the all the attached producers * * \public \memberof mlt_service_s * \param self a service * \return the number of successfully disconnected producers */ int mlt_service_disconnect_all_producers(mlt_service self) { int disconnected = 0, i = 0; mlt_service_base *base = self->local; if (base->in) { for (i = 0; i < base->count; i++) { mlt_service current = base->in[i]; if (current) { mlt_service_close(current); disconnected++; } base->in[i] = NULL; } base->count = 0; } return disconnected; } /** Disconnect a service from its consumer. * * \private \memberof mlt_service_s * \param self a service */ static void mlt_service_disconnect(mlt_service self) { if (self != NULL) { // Get the service base mlt_service_base *base = self->local; // Disconnect base->out = NULL; } } /** Obtain the consumer a service is connected to. * * \public \memberof mlt_service_s * \param self a service * \return the consumer */ mlt_service mlt_service_consumer(mlt_service self) { if (self) { // Get the service base mlt_service_base *base = self->local; // Return the connected consumer return base->out; } else return self; } /** Obtain the producer a service is connected to. * * \public \memberof mlt_service_s * \param self a service * \return the last-most producer */ mlt_service mlt_service_producer(mlt_service self) { if (self) { // Get the service base mlt_service_base *base = self->local; // Return the connected producer return base->count > 0 ? base->in[base->count - 1] : NULL; } else return self; } /** Associate a service to a consumer. * * Overwrites connection to any existing consumer. * \private \memberof mlt_service_s * \param self a service * \param that a consumer */ static void mlt_service_connect(mlt_service self, mlt_service that) { if (self != NULL) { // Get the service base mlt_service_base *base = self->local; // There's a bit more required here... base->out = that; } } /** Get the first connected producer. * * \public \memberof mlt_service_s * \param self a service * \return the first producer */ mlt_service mlt_service_get_producer(mlt_service self) { mlt_service producer = NULL; // Get the service base mlt_service_base *base = self->local; if (base->in != NULL) producer = base->in[0]; return producer; } /** Default implementation of the get_frame virtual function. * * \private \memberof mlt_service_s * \param self a service * \param[out] frame a frame by reference * \param index as determined by the producer * \return false */ static int service_get_frame(mlt_service self, mlt_frame_ptr frame, int index) { mlt_service_base *base = self->local; if (index < base->count) { mlt_service producer = base->in[index]; if (producer != NULL) return mlt_service_get_frame(producer, frame, index); } *frame = mlt_frame_init(self); return 0; } /** Return the properties object. * * \public \memberof mlt_service_s * \param self a service * \return the properties */ mlt_properties mlt_service_properties(mlt_service self) { return self != NULL ? &self->parent : NULL; } /** Recursively apply attached filters. * * \public \memberof mlt_service_s * \param self a service * \param frame a frame * \param index used to track depth of recursion, top caller should supply 0 */ void mlt_service_apply_filters(mlt_service self, mlt_frame frame, int index) { if (!self) return; int i; mlt_properties frame_properties = MLT_FRAME_PROPERTIES(frame); mlt_properties service_properties = MLT_SERVICE_PROPERTIES(self); mlt_service_base *base = self->local; mlt_position position = mlt_frame_get_position(frame); mlt_position self_in = mlt_properties_get_position(service_properties, "in"); mlt_position self_out = mlt_properties_get_position(service_properties, "out"); if (index == 0 || mlt_properties_get_int(service_properties, "_filter_private") == 0) { // Process the frame with the attached filters for (i = 0; i < base->filter_count; i++) { if (base->filters[i] != NULL) { mlt_position in = mlt_filter_get_in(base->filters[i]); mlt_position out = mlt_filter_get_out(base->filters[i]); int disable = mlt_properties_get_int(MLT_FILTER_PROPERTIES(base->filters[i]), "disable"); if (!disable && ((in == 0 && out == 0) || (position >= in && (position <= out || out == 0)))) { mlt_properties_set_position(frame_properties, "in", in == 0 ? self_in : in); mlt_properties_set_position(frame_properties, "out", out == 0 ? self_out : out); mlt_filter_process(base->filters[i], frame); mlt_service_apply_filters(MLT_FILTER_SERVICE(base->filters[i]), frame, index + 1); } } } } } /** Obtain a frame. * * \public \memberof mlt_service_s * \param self a service * \param[out] frame a frame by reference * \param index as determined by the producer * \return true if there was an error */ int mlt_service_get_frame(mlt_service self, mlt_frame_ptr frame, int index) { int result = 0; // Lock the service mlt_service_lock(self); // Ensure that the frame is NULL *frame = NULL; // Only process if we have a valid service if (self != NULL && self->get_frame != NULL) { mlt_properties properties = MLT_SERVICE_PROPERTIES(self); mlt_position in = mlt_properties_get_position(properties, "in"); mlt_position out = mlt_properties_get_position(properties, "out"); mlt_position position = -1; if (mlt_service_identify(self) == mlt_service_producer_type || mlt_service_identify(self) == mlt_service_chain_type) { position = mlt_producer_position(MLT_PRODUCER(self)); } result = self->get_frame(self, frame, index); if (result == 0) { mlt_properties_inc_ref(properties); properties = MLT_FRAME_PROPERTIES(*frame); if (in >= 0 && out > 0) { mlt_properties_set_position(properties, "in", in); mlt_properties_set_position(properties, "out", out); } mlt_service_apply_filters(self, *frame, 1); mlt_deque_push_back(MLT_FRAME_SERVICE_STACK(*frame), self); if (position > -1 && mlt_properties_get_int(MLT_SERVICE_PROPERTIES(self), "_need_previous_next")) { // Save the new position from self->get_frame mlt_position new_position = mlt_producer_position(MLT_PRODUCER(self)); // Get the preceding frame, unfiltered mlt_frame previous_frame; mlt_producer_seek(MLT_PRODUCER(self), position - 1); result = self->get_frame(self, &previous_frame, index); if (!result) mlt_properties_set_data(properties, "previous frame", previous_frame, 0, (mlt_destructor) mlt_frame_close, NULL); // Get the following frame, unfiltered mlt_frame next_frame; mlt_producer_seek(MLT_PRODUCER(self), position + 1); result = self->get_frame(self, &next_frame, index); if (!result) { mlt_properties_set_data(properties, "next frame", next_frame, 0, (mlt_destructor) mlt_frame_close, NULL); } // Restore the new position mlt_producer_seek(MLT_PRODUCER(self), new_position); } } } // Make sure we return a frame if (*frame == NULL) *frame = mlt_frame_init(self); // Unlock the service mlt_service_unlock(self); return result; } /** The service-changed event handler. * * \private \memberof mlt_service_s * \param owner ignored * \param self the service on which the "service-changed" event is fired */ static void mlt_service_filter_changed(mlt_service owner, mlt_service self) { mlt_events_fire(MLT_SERVICE_PROPERTIES(self), "service-changed", mlt_event_data_none()); } /** The property-changed event handler. * * \private \memberof mlt_service_s * \param owner ignored * \param self the service on which the "property-changed" event is fired * \param name the name of the property that changed */ static void mlt_service_filter_property_changed(mlt_service owner, mlt_service self, mlt_event_data event_data) { mlt_events_fire(MLT_SERVICE_PROPERTIES(self), "property-changed", event_data); } /** Attach a filter. * * \public \memberof mlt_service_s * \param self a service * \param filter the filter to attach * \return true if there was an error */ int mlt_service_attach(mlt_service self, mlt_filter filter) { int error = self == NULL || filter == NULL; if (error == 0) { int i = 0; mlt_properties properties = MLT_SERVICE_PROPERTIES(self); mlt_service_base *base = self->local; for (i = 0; error == 0 && i < base->filter_count; i++) if (base->filters[i] == filter) error = 1; if (error == 0) { if (base->filter_count == base->filter_size) { base->filter_size += 10; base->filters = realloc(base->filters, base->filter_size * sizeof(mlt_filter)); } if (base->filters != NULL) { mlt_properties props = MLT_FILTER_PROPERTIES(filter); mlt_properties_inc_ref(MLT_FILTER_PROPERTIES(filter)); base->filters[base->filter_count++] = filter; mlt_properties_set_data(props, "service", self, 0, NULL, NULL); mlt_events_fire(properties, "service-changed", mlt_event_data_none()); mlt_events_fire(props, "service-changed", mlt_event_data_none()); mlt_service cp = mlt_properties_get_data(properties, "_cut_parent", NULL); if (cp) mlt_events_fire(MLT_SERVICE_PROPERTIES(cp), "service-changed", mlt_event_data_none()); mlt_events_listen(props, self, "service-changed", (mlt_listener) mlt_service_filter_changed); mlt_events_listen(props, self, "property-changed", (mlt_listener) mlt_service_filter_property_changed); } else { error = 2; } } } return error; } /** Detach a filter. * * \public \memberof mlt_service_s * \param self a service * \param filter the filter to detach * \return true if there was an error */ int mlt_service_detach(mlt_service self, mlt_filter filter) { int error = self == NULL || filter == NULL; if (error == 0) { int i = 0; mlt_service_base *base = self->local; mlt_properties properties = MLT_SERVICE_PROPERTIES(self); for (i = 0; i < base->filter_count; i++) if (base->filters[i] == filter) break; if (i < base->filter_count) { base->filters[i] = NULL; for (i++; i < base->filter_count; i++) base->filters[i - 1] = base->filters[i]; base->filter_count--; mlt_events_disconnect(MLT_FILTER_PROPERTIES(filter), self); mlt_filter_close(filter); mlt_events_fire(properties, "service-changed", mlt_event_data_none()); } } return error; } /** Get the number of filters attached. * * \public \memberof mlt_service_s * \param self a service * \return the number of attached filters or -1 if there was an error */ int mlt_service_filter_count(mlt_service self) { int result = -1; if (self) { mlt_service_base *base = self->local; result = base->filter_count; } return result; } /** Reorder the attached filters. * * \public \memberof mlt_service_s * \param self a service * \param from the current index value of the filter to move * \param to the new index value for the filter specified in \p from * \return true if there was an error */ int mlt_service_move_filter(mlt_service self, int from, int to) { int error = -1; if (self) { mlt_service_base *base = self->local; if (from < 0) from = 0; if (from >= base->filter_count) from = base->filter_count - 1; if (to < 0) to = 0; if (to >= base->filter_count) to = base->filter_count - 1; if (from != to && base->filter_count > 1) { mlt_filter filter = base->filters[from]; int i; if (from > to) { for (i = from; i > to; i--) base->filters[i] = base->filters[i - 1]; } else { for (i = from; i < to; i++) base->filters[i] = base->filters[i + 1]; } base->filters[to] = filter; mlt_events_fire(MLT_SERVICE_PROPERTIES(self), "service-changed", mlt_event_data_none()); error = 0; } } return error; } /** Retrieve an attached filter. * * \public \memberof mlt_service_s * \param self a service * \param index which one of potentially multiple filters * \return the filter or null if there was an error */ mlt_filter mlt_service_filter(mlt_service self, int index) { mlt_filter filter = NULL; if (self != NULL) { mlt_service_base *base = self->local; if (index >= 0 && index < base->filter_count) filter = base->filters[index]; } return filter; } /** Retrieve the profile. * * \public \memberof mlt_service_s * \param self a service * \return the profile */ mlt_profile mlt_service_profile(mlt_service self) { return self ? mlt_properties_get_data(MLT_SERVICE_PROPERTIES(self), "_profile", NULL) : NULL; } /** Set the profile for a service. * * \public \memberof mlt_service_s * \param self a service * \param profile the profile to set onto the service */ void mlt_service_set_profile(mlt_service self, mlt_profile profile) { mlt_properties_set_data(MLT_SERVICE_PROPERTIES(self), "_profile", profile, 0, NULL, NULL); } /** Destroy a service. * * \public \memberof mlt_service_s * \param self the service to destroy */ void mlt_service_close(mlt_service self) { if (self != NULL && mlt_properties_dec_ref(MLT_SERVICE_PROPERTIES(self)) <= 0) { if (self->close != NULL) { self->close(self->close_object); } else { mlt_service_base *base = self->local; int i = 0; int count = base->filter_count; mlt_events_block(MLT_SERVICE_PROPERTIES(self), self); while (count--) mlt_service_detach(self, base->filters[0]); free(base->filters); for (i = 0; i < base->count; i++) if (base->in[i] != NULL) mlt_service_close(base->in[i]); self->parent.close = NULL; free(base->in); pthread_mutex_destroy(&base->mutex); free(base); mlt_properties_close(&self->parent); } } } /** Release a service's cache items. * * \private \memberof mlt_service_s * \param self a service */ void mlt_service_cache_purge(mlt_service self) { mlt_properties caches = mlt_properties_get_data(mlt_global_properties(), "caches", NULL); if (caches) { int i = mlt_properties_count(caches); while (i--) { mlt_cache_purge(mlt_properties_get_data_at(caches, i, NULL), self); mlt_properties_set_data(mlt_global_properties(), mlt_properties_get_name(caches, i), NULL, 0, NULL, NULL); } } } /** Lookup the cache object for a service. * * \private \memberof mlt_service_s * \param self a service * \param name a name for the object * \return a cache */ static mlt_cache get_cache(mlt_service self, const char *name) { mlt_cache result = NULL; mlt_properties caches = mlt_properties_get_data(mlt_global_properties(), "caches", NULL); if (!caches) { caches = mlt_properties_new(); mlt_properties_set_data(mlt_global_properties(), "caches", caches, 0, (mlt_destructor) mlt_properties_close, NULL); } if (caches) { result = mlt_properties_get_data(caches, name, NULL); if (!result) { result = mlt_cache_init(); mlt_properties_set_data(caches, name, result, 0, (mlt_destructor) mlt_cache_close, NULL); } } return result; } /** Put an object into a service's cache. * * \public \memberof mlt_service_s * \param self a service * \param name a name for the object that is unique to the service class, but not to the instance * \param data an opaque pointer to the object to put into the cache * \param size the number of bytes pointed to by data * \param destructor a function that releases the data */ void mlt_service_cache_put( mlt_service self, const char *name, void *data, int size, mlt_destructor destructor) { mlt_log(self, MLT_LOG_DEBUG, "%s: name %s object %p data %p\n", __FUNCTION__, name, self, data); mlt_cache cache = get_cache(self, name); if (cache) mlt_cache_put(cache, self, data, size, destructor); } /** Get an object from a service's cache. * * \public \memberof mlt_service_s * \param self a service * \param name a name for the object that is unique to the service class, but not to the instance * \return a cache item or NULL if an object is not found * \see mlt_cache_item_data */ mlt_cache_item mlt_service_cache_get(mlt_service self, const char *name) { mlt_log(self, MLT_LOG_DEBUG, "%s: name %s object %p\n", __FUNCTION__, name, self); mlt_cache_item result = NULL; mlt_cache cache = get_cache(self, name); if (cache) result = mlt_cache_get(cache, self); return result; } /** Set the number of items to cache for the named cache. * * \public \memberof mlt_service_s * \param self a service * \param name a name for the object that is unique to the service class, but not to the instance * \param size the number of items to cache */ void mlt_service_cache_set_size(mlt_service self, const char *name, int size) { mlt_cache cache = get_cache(self, name); if (cache) mlt_cache_set_size(cache, size); } /** Get the current maximum size of the named cache. * * \public \memberof mlt_service_s * \param self a service * \param name a name for the object that is unique to the service class, but not to the instance * \return the current maximum number of items to cache or zero if there is an error */ int mlt_service_cache_get_size(mlt_service self, const char *name) { mlt_cache cache = get_cache(self, name); if (cache) return mlt_cache_get_size(cache); else return 0; } mlt-7.22.0/src/framework/mlt_service.h000664 000000 000000 00000012340 14531534050 017627 0ustar00rootroot000000 000000 /** * \file mlt_service.h * \brief interface declaration for all service classes * \see mlt_service_s * * Copyright (C) 2003-2015 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef MLT_SERVICE_H #define MLT_SERVICE_H #include "mlt_properties.h" #include "mlt_types.h" /** \brief Service abstract base class * * \extends mlt_properties * The service is the base class for all of the interesting classes and * plugins for MLT. A service can have multiple inputs connections to * other services called its "producers" but only a single output to another * service called its "consumer." A service that has both producer and * consumer connections is called a filter. Any service can have zero or more * filters "attached" to it. We call any collection of services and their * connections a "service network," which is similar to what DirectShow calls * a filter graph or what gstreamer calls an element pipeline. * * \event \em service-changed a filter was attached or detached or a transition was connected or disconnected * \event \em property-changed a property's value changed; the event data is a string for the name of the property * \properties \em mlt_type identifies the subclass * \properties \em _mlt_service_hidden a flag that indicates whether to hide the mlt_service * \properties \em mlt_service is the name of the implementation of the service * \properties \em resource is either the stream identifier or grandchild-class * \properties \em in when to start, what is started is service-specific * \properties \em out when to stop * \properties \em _filter_private Set this on a service to ensure that attached filters are handled privately. * See modules/core/filter_watermark.c for example. * \properties \em _profile stores the mlt_profile for a service * \properties \em _unique_id is a unique identifier * \properties \em _need_previous_next boolean that instructs producers to get * preceding and following frames inside of \p mlt_service_get_frame */ struct mlt_service_s { struct mlt_properties_s parent; /**< \private A service extends properties. */ /** Get a frame of data (virtual function). * * \param mlt_producer a producer * \param mlt_frame_ptr a frame pointer by reference * \param int an index * \return true if there was an error */ int (*get_frame)(mlt_service self, mlt_frame_ptr frame, int index); /** the destructor virtual function */ mlt_destructor close; void *close_object; /**< the object supplied to the close virtual function */ void *local; /**< \private instance object */ void *child; /**< \private the object of a subclass */ }; #define MLT_SERVICE_PROPERTIES(service) (&(service)->parent) extern int mlt_service_init(mlt_service self, void *child); extern void mlt_service_lock(mlt_service self); extern void mlt_service_unlock(mlt_service self); extern mlt_service_type mlt_service_identify(mlt_service self); extern int mlt_service_connect_producer(mlt_service self, mlt_service producer, int index); extern int mlt_service_insert_producer(mlt_service self, mlt_service producer, int index); extern int mlt_service_disconnect_producer(mlt_service self, int index); extern int mlt_service_disconnect_all_producers(mlt_service self); extern mlt_service mlt_service_get_producer(mlt_service self); extern int mlt_service_get_frame(mlt_service self, mlt_frame_ptr frame, int index); extern mlt_properties mlt_service_properties(mlt_service self); extern mlt_service mlt_service_consumer(mlt_service self); extern mlt_service mlt_service_producer(mlt_service self); extern int mlt_service_attach(mlt_service self, mlt_filter filter); extern int mlt_service_detach(mlt_service self, mlt_filter filter); extern void mlt_service_apply_filters(mlt_service self, mlt_frame frame, int index); extern int mlt_service_filter_count(mlt_service self); extern int mlt_service_move_filter(mlt_service self, int from, int to); extern mlt_filter mlt_service_filter(mlt_service self, int index); extern mlt_profile mlt_service_profile(mlt_service self); extern void mlt_service_set_profile(mlt_service self, mlt_profile profile); extern void mlt_service_close(mlt_service self); extern void mlt_service_cache_put( mlt_service self, const char *name, void *data, int size, mlt_destructor destructor); extern mlt_cache_item mlt_service_cache_get(mlt_service self, const char *name); extern void mlt_service_cache_set_size(mlt_service self, const char *name, int size); extern int mlt_service_cache_get_size(mlt_service self, const char *name); extern void mlt_service_cache_purge(mlt_service self); #endif mlt-7.22.0/src/framework/mlt_slices.c000664 000000 000000 00000027167 14531534050 017461 0ustar00rootroot000000 000000 /** * \file mlt_slices.c * \brief sliced threading processing helper * \see mlt_slices_s * * Copyright (C) 2016-2022 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "mlt_slices.h" #include "mlt_factory.h" #include "mlt_log.h" #include "mlt_properties.h" #include #include #include #include #ifdef _WIN32 #include #endif #define MAX_SLICES 32 #define ENV_SLICES "MLT_SLICES_COUNT" typedef enum { mlt_policy_normal, mlt_policy_rr, mlt_policy_fifo, mlt_policy_nb } mlt_schedule_policy; static pthread_mutex_t g_lock = PTHREAD_MUTEX_INITIALIZER; static mlt_slices globals[mlt_policy_nb] = {NULL, NULL, NULL}; struct mlt_slices_runtime_s { int jobs, done, curr; mlt_slices_proc proc; void *cookie; struct mlt_slices_runtime_s *next; }; struct mlt_slices_s { int f_exit; int count; int readys; int ref; pthread_mutex_t cond_mutex; pthread_cond_t cond_var_job; pthread_cond_t cond_var_ready; pthread_t threads[MAX_SLICES]; struct mlt_slices_runtime_s *head, *tail; const char *name; }; static void *mlt_slices_worker(void *p) { int id, idx; struct mlt_slices_runtime_s *r; mlt_slices ctx = (mlt_slices) p; mlt_log_debug(NULL, "%s:%d: ctx=[%p][%s] entering\n", __FUNCTION__, __LINE__, ctx, ctx->name); pthread_mutex_lock(&ctx->cond_mutex); id = ctx->readys; ctx->readys++; while (1) { mlt_log_debug(NULL, "%s:%d: ctx=[%p][%s] waiting\n", __FUNCTION__, __LINE__, ctx, ctx->name); /* wait for new jobs */ while (!ctx->f_exit && !(r = ctx->head)) pthread_cond_wait(&ctx->cond_var_job, &ctx->cond_mutex); if (ctx->f_exit) break; if (!r) continue; /* check if no new job */ if (r->curr == r->jobs) { ctx->head = ctx->head->next; if (!ctx->head) ctx->tail = NULL; mlt_log_debug(NULL, "%s:%d: new ctx->head=%p\n", __FUNCTION__, __LINE__, ctx->head); continue; }; /* new job id */ idx = r->curr; r->curr++; /* run job */ pthread_mutex_unlock(&ctx->cond_mutex); mlt_log_debug(NULL, "%s:%d: running job: id=%d, idx=%d/%d, pool=[%s]\n", __FUNCTION__, __LINE__, id, idx, r->jobs, ctx->name); r->proc(id, idx, r->jobs, r->cookie); pthread_mutex_lock(&ctx->cond_mutex); /* increase done jobs counter */ r->done++; /* notify we fininished last job */ if (r->done == r->jobs) { mlt_log_debug(NULL, "%s:%d: pthread_cond_signal( &ctx->cond_var_ready )\n", __FUNCTION__, __LINE__); pthread_cond_broadcast(&ctx->cond_var_ready); } } pthread_mutex_unlock(&ctx->cond_mutex); return NULL; } /** Initialize a sliced threading context * * \private \memberof mlt_slices_s * \param threads number of threads to use for job list, 0 for #cpus * \param policy scheduling policy of processing threads, -1 for normal * \param priority priority value that can be used with the scheduling algorithm, -1 for maximum * \return the context pointer */ static mlt_slices mlt_slices_init(int threads, int policy, int priority) { pthread_attr_t tattr; struct sched_param param; mlt_slices ctx = (mlt_slices) calloc(1, sizeof(struct mlt_slices_s)); char *env = getenv(ENV_SLICES); #ifdef _WIN32 #if _WIN32_WINNT >= 0x0601 int cpus = GetActiveProcessorCount(ALL_PROCESSOR_GROUPS); #else SYSTEM_INFO info; GetSystemInfo(&info); int cpus = info.dwNumberOfProcessors; #endif #else int cpus = sysconf(_SC_NPROCESSORS_ONLN); #endif int i, env_val = env ? atoi(env) : 0; /* check given threads count */ if (!env || !env_val) { if (threads < 0) threads = -threads * cpus; else if (!threads) threads = cpus; } else if (env_val < 0) { if (threads < 0) threads = env_val * threads * cpus; else if (!threads) threads = -env_val * cpus; else threads = -env_val * threads; } else // env_val > 0 { if (threads < 0) threads = env_val * threads; else if (!threads) threads = env_val; } if (threads > MAX_SLICES) threads = MAX_SLICES; ctx->count = threads; /* init attributes */ pthread_mutex_init(&ctx->cond_mutex, NULL); pthread_cond_init(&ctx->cond_var_job, NULL); pthread_cond_init(&ctx->cond_var_ready, NULL); pthread_attr_init(&tattr); if (policy < 0) policy = SCHED_OTHER; if (priority < 0) priority = sched_get_priority_max(policy); pthread_attr_setschedpolicy(&tattr, policy); param.sched_priority = priority; pthread_attr_setschedparam(&tattr, ¶m); /* run worker threads */ for (i = 0; i < ctx->count; i++) { pthread_create(&ctx->threads[i], &tattr, mlt_slices_worker, ctx); pthread_setschedparam(ctx->threads[i], policy, ¶m); } pthread_attr_destroy(&tattr); /* return context */ return ctx; } /** Destroy sliced threading context * * \private \memberof mlt_slices_s * \param ctx context pointer */ static void mlt_slices_close(mlt_slices ctx) { int j; pthread_mutex_lock(&g_lock); mlt_log_debug(NULL, "%s:%d: ctx=[%p][%s] closing\n", __FUNCTION__, __LINE__, ctx, ctx->name); /* check reference count */ if (ctx->ref) { ctx->ref--; mlt_log_debug(NULL, "%s:%d: ctx=[%p][%s] new ref=%d\n", __FUNCTION__, __LINE__, ctx, ctx->name, ctx->ref); pthread_mutex_unlock(&g_lock); return; } pthread_mutex_unlock(&g_lock); /* notify to exit */ ctx->f_exit = 1; pthread_mutex_lock(&ctx->cond_mutex); pthread_cond_broadcast(&ctx->cond_var_job); pthread_cond_broadcast(&ctx->cond_var_ready); pthread_mutex_unlock(&ctx->cond_mutex); /* wait for threads exit */ for (j = 0; j < ctx->count; j++) pthread_join(ctx->threads[j], NULL); /* destroy vars */ pthread_cond_destroy(&ctx->cond_var_ready); pthread_cond_destroy(&ctx->cond_var_job); pthread_mutex_destroy(&ctx->cond_mutex); /* free context */ free(ctx); } /** Run sliced execution * * \private \memberof mlt_slices_s * \param ctx context pointer * \param jobs number of jobs to process * \param proc a pointer to the function that will be called * \param cookie an opaque data pointer passed to \p proc */ static void mlt_slices_run(mlt_slices ctx, int jobs, mlt_slices_proc proc, void *cookie) { if (jobs == 1) { proc(0, 0, 1, cookie); return; } struct mlt_slices_runtime_s runtime, *r = &runtime; /* lock */ pthread_mutex_lock(&ctx->cond_mutex); /* check jobs count */ if (jobs < 0) jobs = (-jobs) * ctx->count; if (!jobs) jobs = ctx->count; /* setup runtime args */ r->jobs = jobs; r->done = 0; r->curr = 0; r->proc = proc; r->cookie = cookie; r->next = NULL; /* attach job */ if (ctx->tail) { ctx->tail->next = r; ctx->tail = r; } else { ctx->head = ctx->tail = r; } /* notify workers */ pthread_cond_broadcast(&ctx->cond_var_job); /* wait for end of task */ while (!ctx->f_exit && (r->done < r->jobs)) { pthread_cond_wait(&ctx->cond_var_ready, &ctx->cond_mutex); mlt_log_debug(NULL, "%s:%d: ctx=[%p][%s] signalled\n", __FUNCTION__, __LINE__, ctx, ctx->name); } pthread_mutex_unlock(&ctx->cond_mutex); } /** Get a global shared sliced threading context. * * There are separate contexts for each scheduling policy. * * \private \memberof mlt_slices_s * \param policy the thread scheduling policy needed * \return the context pointer */ static mlt_slices mlt_slices_get_global(mlt_schedule_policy policy) { pthread_mutex_lock(&g_lock); if (!globals[policy]) { int posix_policy; switch (policy) { case mlt_policy_rr: posix_policy = SCHED_RR; break; case mlt_policy_fifo: posix_policy = SCHED_FIFO; break; default: posix_policy = SCHED_OTHER; } globals[policy] = mlt_slices_init(0, posix_policy, -1); mlt_factory_register_for_clean_up(globals[policy], (mlt_destructor) mlt_slices_close); } pthread_mutex_unlock(&g_lock); return globals[policy]; } /** Get the number of slices for the normal scheduling policy. * * \public \memberof mlt_slices_s * \return the number of slices */ int mlt_slices_count_normal() { mlt_slices slices = mlt_slices_get_global(mlt_policy_normal); if (slices) return slices->count; else return 0; } /** Get the number of slices for the round robin scheduling policy. * * \public \memberof mlt_slices_s * \return the number of slices */ int mlt_slices_count_rr() { mlt_slices slices = mlt_slices_get_global(mlt_policy_rr); if (slices) return slices->count; else return 0; } /** Get the number of slices for the fifo scheduling policy. * * \public \memberof mlt_slices_s * \return the number of slices */ int mlt_slices_count_fifo() { mlt_slices slices = mlt_slices_get_global(mlt_policy_fifo); if (slices) return slices->count; else return 0; } void mlt_slices_run_normal(int jobs, mlt_slices_proc proc, void *cookie) { return mlt_slices_run(mlt_slices_get_global(mlt_policy_normal), jobs, proc, cookie); } void mlt_slices_run_rr(int jobs, mlt_slices_proc proc, void *cookie) { return mlt_slices_run(mlt_slices_get_global(mlt_policy_rr), jobs, proc, cookie); } void mlt_slices_run_fifo(int jobs, mlt_slices_proc proc, void *cookie) { return mlt_slices_run(mlt_slices_get_global(mlt_policy_fifo), jobs, proc, cookie); } /** Compute size of a slice. * * This a helper function for use in a mlt_slices_proc() to get the number of * pixels over which to operate. * * \public \memberof mlt_slices_s * \param jobs the number of slices * \param index the zero-based index of the current slice * \param input_size the size of a dimension, usually in pixel units, for example height * \param[out] start the optional starting unit for this slice * \return the size of the slice, typically in pixel units */ int mlt_slices_size_slice(int jobs, int index, int input_size, int *start) { int size = (input_size + jobs - 1) / jobs; int my_start = index * size; if (start) { *start = my_start; } return CLAMP(input_size - my_start, 0, size); } mlt-7.22.0/src/framework/mlt_slices.h000664 000000 000000 00000003110 14531534050 017444 0ustar00rootroot000000 000000 /** * \file mlt_slices.h * \brief sliced threading processing helper * \see mlt_slices_s * * Copyright (C) 2016-2022 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef MLT_SLICES_H #define MLT_SLICES_H #include "mlt_types.h" /** * \envvar \em MLT_SLICES_COUNT Set the number of slices to use, which * defaults to number of CPUs found. */ struct mlt_slices_s; typedef int (*mlt_slices_proc)(int id, int idx, int jobs, void *cookie); extern int mlt_slices_count_normal(); extern int mlt_slices_count_rr(); extern int mlt_slices_count_fifo(); extern void mlt_slices_run_normal(int jobs, mlt_slices_proc proc, void *cookie); extern void mlt_slices_run_rr(int jobs, mlt_slices_proc proc, void *cookie); extern void mlt_slices_run_fifo(int jobs, mlt_slices_proc proc, void *cookie); extern int mlt_slices_size_slice(int jobs, int index, int input_size, int *start); #endif mlt-7.22.0/src/framework/mlt_tokeniser.c000664 000000 000000 00000010331 14531534050 020163 0ustar00rootroot000000 000000 /** * \file mlt_tokeniser.c * \brief string tokeniser * \see mlt_tokeniser_s * * Copyright (C) 2002-2014 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /* System header files */ #include #include /* Application header files */ #include "mlt_tokeniser.h" /** Initialise a tokeniser. */ mlt_tokeniser mlt_tokeniser_init() { mlt_tokeniser tokeniser = calloc(1, sizeof(mlt_tokeniser_t)); tokeniser->input = NULL; tokeniser->tokens = NULL; tokeniser->count = 0; tokeniser->size = 0; return tokeniser; } /** Clear the tokeniser. */ static void mlt_tokeniser_clear(mlt_tokeniser tokeniser) { int index = 0; for (index = 0; index < tokeniser->count; index++) free(tokeniser->tokens[index]); tokeniser->count = 0; free(tokeniser->input); tokeniser->input = NULL; } /** Append a string to the tokeniser. */ static int mlt_tokeniser_append(mlt_tokeniser tokeniser, char *token) { int error = 0; if (tokeniser->count == tokeniser->size) { tokeniser->size += 20; tokeniser->tokens = realloc(tokeniser->tokens, tokeniser->size * sizeof(char *)); } if (tokeniser->tokens != NULL) { tokeniser->tokens[tokeniser->count++] = strdup(token); } else { tokeniser->count = 0; error = -1; } return error; } /** Parse a string by splitting on the delimiter provided. */ int mlt_tokeniser_parse_new(mlt_tokeniser tokeniser, char *string, const char *delimiter) { if (!string || !delimiter) return 0; int count = 0; int length = strlen(string); int delimiter_size = strlen(delimiter); int index = 0; char *token = strdup(string); int token_size = strlen(token); mlt_tokeniser_clear(tokeniser); tokeniser->input = strdup(string); strcpy(token, ""); for (index = 0; index < length;) { char *start = string + index; char *end = strstr(start, delimiter); if (end == NULL) { strcat(token, start); mlt_tokeniser_append(tokeniser, token); index = length; count++; } else if (start != end) { strncat(token, start, end - start); index += end - start; if (strchr(token, '\"') == NULL || token[strlen(token) - 1] == '\"') { mlt_tokeniser_append(tokeniser, token); strcpy(token, ""); count++; } else while (strncmp(string + index, delimiter, delimiter_size) == 0) { strncat(token, delimiter, token_size); token[token_size] = '\0'; index += delimiter_size; } } else { index += delimiter_size; } } /* Special case - malformed string condition */ if (!strcmp(token, "")) { count = 0 - (count - 1); mlt_tokeniser_append(tokeniser, token); } free(token); return count; } /** Get the original input. */ char *mlt_tokeniser_get_input(mlt_tokeniser tokeniser) { return tokeniser->input; } /** Get the number of tokens. */ int mlt_tokeniser_count(mlt_tokeniser tokeniser) { return tokeniser->count; } /** Get a token as a string. */ char *mlt_tokeniser_get_string(mlt_tokeniser tokeniser, int index) { if (index < tokeniser->count) return tokeniser->tokens[index]; else return NULL; } /** Close the tokeniser. */ void mlt_tokeniser_close(mlt_tokeniser tokeniser) { mlt_tokeniser_clear(tokeniser); free(tokeniser->tokens); free(tokeniser); } mlt-7.22.0/src/framework/mlt_tokeniser.h000664 000000 000000 00000002737 14531534050 020203 0ustar00rootroot000000 000000 /** * \file mlt_tokeniser.h * \brief string tokeniser * \see mlt_tokeniser_s * * Copyright (C) 2002-2014 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef MLT_TOKENISER_H #define MLT_TOKENISER_H /** \brief Tokeniser class * */ typedef struct { char *input; char **tokens; int count; int size; } * mlt_tokeniser, mlt_tokeniser_t; /* Remote parser API. */ extern mlt_tokeniser mlt_tokeniser_init(); extern int mlt_tokeniser_parse_new(mlt_tokeniser tokeniser, char *text, const char *delimiter); extern char *mlt_tokeniser_get_input(mlt_tokeniser tokeniser); extern int mlt_tokeniser_count(mlt_tokeniser tokeniser); extern char *mlt_tokeniser_get_string(mlt_tokeniser tokeniser, int index); extern void mlt_tokeniser_close(mlt_tokeniser tokeniser); #endif mlt-7.22.0/src/framework/mlt_tractor.c000664 000000 000000 00000061367 14531534050 017655 0ustar00rootroot000000 000000 /** * \file mlt_tractor.c * \brief tractor service class * \see mlt_tractor_s * * Copyright (C) 2003-2022 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "mlt_tractor.h" #include "mlt_field.h" #include "mlt_frame.h" #include "mlt_log.h" #include "mlt_multitrack.h" #include "mlt_transition.h" #include #include #include #include /* Forward references to static methods. */ static int producer_get_frame(mlt_producer parent, mlt_frame_ptr frame, int track); static void mlt_tractor_listener(mlt_multitrack tracks, mlt_tractor self); /** Construct a tractor without a field or multitrack. * * Sets the resource property to "", the mlt_type to "mlt_producer", * and mlt_service to "tractor". * * \public \memberof mlt_tractor_s * \return the new tractor */ mlt_tractor mlt_tractor_init() { mlt_tractor self = calloc(1, sizeof(struct mlt_tractor_s)); if (self != NULL) { mlt_producer producer = &self->parent; if (mlt_producer_init(producer, self) == 0) { mlt_properties properties = MLT_PRODUCER_PROPERTIES(producer); mlt_properties_set(properties, "resource", ""); mlt_properties_set(properties, "mlt_type", "mlt_producer"); mlt_properties_set(properties, "mlt_service", "tractor"); mlt_properties_set_int(properties, "in", 0); mlt_properties_set_int(properties, "out", -1); mlt_properties_set_int(properties, "length", 0); producer->get_frame = producer_get_frame; producer->close = (mlt_destructor) mlt_tractor_close; producer->close_object = self; } else { free(self); self = NULL; } } return self; } /** Construct a tractor as well as a field and multitrack. * * Sets the resource property to "", the mlt_type to "mlt_producer", * and mlt_service to "tractor". * * \public \memberof mlt_tractor_s * \return the new tractor */ mlt_tractor mlt_tractor_new() { mlt_tractor self = calloc(1, sizeof(struct mlt_tractor_s)); if (self != NULL) { mlt_producer producer = &self->parent; if (mlt_producer_init(producer, self) == 0) { mlt_multitrack multitrack = mlt_multitrack_init(); mlt_field field = mlt_field_new(multitrack, self); mlt_properties props = MLT_PRODUCER_PROPERTIES(producer); mlt_properties_set(props, "resource", ""); mlt_properties_set(props, "mlt_type", "mlt_producer"); mlt_properties_set(props, "mlt_service", "tractor"); mlt_properties_set_position(props, "in", 0); mlt_properties_set_position(props, "out", 0); mlt_properties_set_position(props, "length", 0); mlt_properties_set_data(props, "multitrack", multitrack, 0, (mlt_destructor) mlt_multitrack_close, NULL); mlt_properties_set_data(props, "field", field, 0, (mlt_destructor) mlt_field_close, NULL); mlt_events_listen(MLT_MULTITRACK_PROPERTIES(multitrack), self, "producer-changed", (mlt_listener) mlt_tractor_listener); producer->get_frame = producer_get_frame; producer->close = (mlt_destructor) mlt_tractor_close; producer->close_object = self; } else { free(self); self = NULL; } } return self; } /** Get the service object associated to the tractor. * * \public \memberof mlt_tractor_s * \param self a tractor * \return the parent service object * \see MLT_TRACTOR_SERVICE */ mlt_service mlt_tractor_service(mlt_tractor self) { return MLT_PRODUCER_SERVICE(&self->parent); } /** Get the producer object associated to the tractor. * * \public \memberof mlt_tractor_s * \param self a tractor * \return the parent producer object * \see MLT_TRACTOR_PRODUCER */ mlt_producer mlt_tractor_producer(mlt_tractor self) { return self != NULL ? &self->parent : NULL; } /** Get the properties object associated to the tractor. * * \public \memberof mlt_tractor_s * \param self a tractor * \return the tractor's property list * \see MLT_TRACTOR_PROPERTIES */ mlt_properties mlt_tractor_properties(mlt_tractor self) { return MLT_PRODUCER_PROPERTIES(&self->parent); } /** Get the field self tractor is harvesting. * * \public \memberof mlt_tractor_s * \param self a tractor * \return a field or NULL if there is no field for this tractor */ mlt_field mlt_tractor_field(mlt_tractor self) { return mlt_properties_get_data(MLT_TRACTOR_PROPERTIES(self), "field", NULL); } /** Get the multitrack a tractor is pulling. * * \public \memberof mlt_tractor_s * \param self a tractor * \return a multitrack or NULL if there is none */ mlt_multitrack mlt_tractor_multitrack(mlt_tractor self) { return mlt_properties_get_data(MLT_TRACTOR_PROPERTIES(self), "multitrack", NULL); } /** Ensure the tractors in/out points match the multitrack. * * \public \memberof mlt_tractor_s * \param self a tractor */ void mlt_tractor_refresh(mlt_tractor self) { mlt_multitrack multitrack = mlt_tractor_multitrack(self); mlt_properties multitrack_props = MLT_MULTITRACK_PROPERTIES(multitrack); mlt_properties properties = MLT_TRACTOR_PROPERTIES(self); mlt_events_block(multitrack_props, properties); mlt_events_block(properties, properties); mlt_multitrack_refresh(multitrack); mlt_properties_set_position(properties, "in", 0); mlt_properties_set_position(properties, "out", mlt_properties_get_position(multitrack_props, "out")); mlt_events_unblock(properties, properties); mlt_events_unblock(multitrack_props, properties); mlt_properties_set_position(properties, "length", mlt_properties_get_position(multitrack_props, "length")); } static void mlt_tractor_listener(mlt_multitrack tracks, mlt_tractor self) { mlt_tractor_refresh(self); } /** Connect the tractor. * * \public \memberof mlt_tractor_s * \param self a tractor * \param producer a producer * \return true on error */ int mlt_tractor_connect(mlt_tractor self, mlt_service producer) { int ret = mlt_service_connect_producer(MLT_TRACTOR_SERVICE(self), producer, 0); // This is the producer we're going to connect to if (ret == 0) self->producer = producer; return ret; } /** Set the producer for a specific track. * * \public \memberof mlt_tractor_s * \param self a tractor * \param producer a producer * \param index the 0-based track index * \return true on error */ int mlt_tractor_set_track(mlt_tractor self, mlt_producer producer, int index) { return mlt_multitrack_connect(mlt_tractor_multitrack(self), producer, index); } /** Insert a producer before a specific track. * * This also adjusts the track indices on mlt_transition_s and mlt_filter_s, * * \public \memberof mlt_tractor_s * \param self a tractor * \param producer a producer * \param index the 0-based track index * \return true on error */ int mlt_tractor_insert_track(mlt_tractor self, mlt_producer producer, int index) { int error = mlt_multitrack_insert(mlt_tractor_multitrack(self), producer, index); if (!error) { // Update the track indices of transitions and track filters. mlt_service service = mlt_service_producer(MLT_TRACTOR_SERVICE(self)); while (service) { mlt_service_type type = mlt_service_identify(service); mlt_properties properties = MLT_SERVICE_PROPERTIES(service); if (type == mlt_service_transition_type) { mlt_transition transition = MLT_TRANSITION(service); int a_track = mlt_transition_get_a_track(transition); int b_track = mlt_transition_get_b_track(transition); if (a_track >= index || b_track >= index) { a_track = a_track >= index ? a_track + 1 : a_track; b_track = b_track >= index ? b_track + 1 : b_track; mlt_transition_set_tracks(transition, a_track, b_track); } } else if (type == mlt_service_filter_type) { int current_track = mlt_properties_get_int(properties, "track"); if (current_track >= index) mlt_properties_set_int(properties, "track", current_track + 1); } service = mlt_service_producer(service); } } return error; } /** Remove a track by its index. * * \public \memberof mlt_tractor_s * \param self a tractor * \param index the 0-based track index * \return true on error */ int mlt_tractor_remove_track(mlt_tractor self, int index) { int error = mlt_multitrack_disconnect(mlt_tractor_multitrack(self), index); if (!error) { // Update the track indices of transitions and track filters. mlt_service service = mlt_service_producer(MLT_TRACTOR_SERVICE(self)); while (service) { mlt_service_type type = mlt_service_identify(service); mlt_properties properties = MLT_SERVICE_PROPERTIES(service); int track_max = MAX(mlt_multitrack_count(mlt_tractor_multitrack(self)) - 1, 0); if (type == mlt_service_transition_type) { mlt_transition transition = MLT_TRANSITION(service); int a_track = mlt_transition_get_a_track(transition); int b_track = mlt_transition_get_b_track(transition); if (a_track > index || b_track >= index) { a_track = CLAMP(a_track > index ? a_track - 1 : a_track, 0, track_max); b_track = CLAMP(b_track >= index ? b_track - 1 : b_track, 0, track_max); mlt_transition_set_tracks(transition, a_track, b_track); } } else if (type == mlt_service_filter_type) { int current_track = mlt_properties_get_int(properties, "track"); if (current_track >= index) mlt_properties_set_int(properties, "track", CLAMP(current_track - 1, 0, track_max)); } service = mlt_service_producer(service); } } return error; } /** Get the producer for a specific track. * * \public \memberof mlt_tractor_s * \param self a tractor * \param index the 0-based track index * \return the producer for track \p index */ mlt_producer mlt_tractor_get_track(mlt_tractor self, int index) { return mlt_multitrack_track(mlt_tractor_multitrack(self), index); } static int producer_get_image(mlt_frame self, uint8_t **buffer, mlt_image_format *format, int *width, int *height, int writable) { uint8_t *data = NULL; int size = 0; mlt_properties properties = MLT_FRAME_PROPERTIES(self); mlt_frame frame = mlt_frame_pop_service(self); mlt_properties frame_properties = MLT_FRAME_PROPERTIES(frame); mlt_properties_set_int(frame_properties, "resize_alpha", mlt_properties_get_int(properties, "resize_alpha")); mlt_properties_set_int(frame_properties, "distort", mlt_properties_get_int(properties, "distort")); mlt_properties_copy(frame_properties, properties, "consumer."); // WebVfx uses this to setup a consumer-stopping event handler. mlt_properties_set_data(frame_properties, "consumer", mlt_properties_get_data(properties, "consumer", NULL), 0, NULL, NULL); mlt_frame_get_image(frame, buffer, format, width, height, writable); mlt_frame_set_image(self, *buffer, 0, NULL); mlt_properties_set_int(properties, "width", *width); mlt_properties_set_int(properties, "height", *height); mlt_properties_set_int(properties, "format", *format); mlt_properties_set_double(properties, "aspect_ratio", mlt_frame_get_aspect_ratio(frame)); // Pass all required frame properties mlt_properties_pass_list( properties, frame_properties, "progressive,distort,colorspace,full_range,force_full_luma,top_field_first,color_trc"); mlt_properties_set_data(properties, "movit.convert.fence", mlt_properties_get_data(frame_properties, "movit.convert.fence", NULL), 0, NULL, NULL); mlt_properties_set_data(properties, "movit.convert.texture", mlt_properties_get_data(frame_properties, "movit.convert.texture", NULL), 0, NULL, NULL); mlt_properties_set_int(properties, "movit.convert.use_texture", mlt_properties_get_int(frame_properties, "movit.convert.use_texture")); int i; for (i = 0; i < mlt_properties_count(frame_properties); i++) { char *name = mlt_properties_get_name(frame_properties, i); if (name && !strncmp(name, "_movit ", 7)) { mlt_properties_set_data(properties, name, mlt_properties_get_data_at(frame_properties, i, NULL), 0, NULL, NULL); } } data = mlt_frame_get_alpha_size(frame, &size); if (data) { mlt_frame_set_alpha(self, data, size, NULL); } self->convert_image = frame->convert_image; self->convert_audio = frame->convert_audio; return 0; } static int producer_get_audio(mlt_frame self, void **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples) { mlt_properties properties = MLT_FRAME_PROPERTIES(self); mlt_frame frame = mlt_frame_pop_audio(self); mlt_properties frame_properties = MLT_FRAME_PROPERTIES(frame); mlt_properties_set(frame_properties, "consumer.channel_layout", mlt_properties_get(properties, "consumer.channel_layout")); mlt_properties_set(frame_properties, "producer_consumer_fps", mlt_properties_get(properties, "producer_consumer_fps")); mlt_frame_get_audio(frame, buffer, format, frequency, channels, samples); mlt_frame_set_audio(self, *buffer, *format, mlt_audio_format_size(*format, *samples, *channels), NULL); mlt_properties_set_int(properties, "audio_frequency", *frequency); mlt_properties_set_int(properties, "audio_channels", *channels); mlt_properties_set_int(properties, "audio_samples", *samples); return 0; } /** Get the next frame. * * \private \memberof mlt_tractor_s * \param parent the producer interface to the tractor * \param[out] frame a frame by reference * \param track the 0-based track index * \return true on error */ static int producer_get_frame(mlt_producer parent, mlt_frame_ptr frame, int track) { mlt_tractor self = parent->child; // We only respond to the first track requests if (track == 0 && self->producer != NULL) { int i = 0; int done = 0; mlt_frame temp = NULL; int count = 0; int image_count = 0; // Get the properties of the parent producer mlt_properties properties = MLT_PRODUCER_PROPERTIES(parent); // Try to obtain the multitrack associated to the tractor mlt_multitrack multitrack = mlt_properties_get_data(properties, "multitrack", NULL); // Or a specific producer mlt_producer producer = mlt_properties_get_data(properties, "producer", NULL); // If we don't have one, we're in trouble... if (multitrack != NULL) { // Used to garbage collect all frames char label[64]; // Get the id of the tractor char *id = mlt_properties_get(properties, "_unique_id"); if (!id) { mlt_properties_set_int64(properties, "_unique_id", (int64_t) properties); id = mlt_properties_get(properties, "_unique_id"); } // Will be used to store the frame properties object mlt_properties frame_properties = NULL; // We'll store audio and video frames to use here mlt_frame audio = NULL; mlt_frame video = NULL; mlt_frame first_video = NULL; // Temporary properties mlt_properties temp_properties = NULL; // Get the multitrack's producer mlt_producer target = MLT_MULTITRACK_PRODUCER(multitrack); mlt_producer_seek(target, mlt_producer_frame(parent)); mlt_producer_set_speed(target, mlt_producer_get_speed(parent)); // We will create one frame and attach everything to it *frame = mlt_frame_init(MLT_PRODUCER_SERVICE(parent)); // Get the properties of the frame frame_properties = MLT_FRAME_PROPERTIES(*frame); // Loop through each of the tracks we're harvesting for (i = 0; !done; i++) { // Get a frame from the producer mlt_service_get_frame(self->producer, &temp, i); // Get the temporary properties temp_properties = MLT_FRAME_PROPERTIES(temp); // Pass all unique meta properties from the producer's frame to the new frame mlt_properties_lock(temp_properties); mlt_properties_copy(frame_properties, temp_properties, "meta."); mlt_properties_unlock(temp_properties); // Copy the format conversion virtual functions if (!(*frame)->convert_image && temp->convert_image) (*frame)->convert_image = temp->convert_image; if (!(*frame)->convert_audio && temp->convert_audio) (*frame)->convert_audio = temp->convert_audio; // Check for last track done = mlt_properties_get_int(temp_properties, "last_track"); // Handle fx only tracks if (mlt_properties_get_int(temp_properties, "fx_cut")) { int hide = (video == NULL ? 1 : 0) | (audio == NULL ? 2 : 0); mlt_properties_set_int(temp_properties, "hide", hide); } // We store all frames with a destructor on the output frame snprintf(label, sizeof(label), "mlt_tractor %s_%d", id, count++); mlt_properties_set_data(frame_properties, label, temp, 0, (mlt_destructor) mlt_frame_close, NULL); // Pick up first video and audio frames if (!done && !mlt_frame_is_test_audio(temp) && !(mlt_properties_get_int(temp_properties, "hide") & 2)) { // Order of frame creation is starting to get problematic if (audio != NULL) { mlt_deque_push_front(MLT_FRAME_AUDIO_STACK(temp), producer_get_audio); mlt_deque_push_front(MLT_FRAME_AUDIO_STACK(temp), audio); } audio = temp; } if (!done && !mlt_frame_is_test_card(temp) && !(mlt_properties_get_int(temp_properties, "hide") & 1)) { if (video != NULL) { mlt_deque_push_front(MLT_FRAME_IMAGE_STACK(temp), producer_get_image); mlt_deque_push_front(MLT_FRAME_IMAGE_STACK(temp), video); } video = temp; if (first_video == NULL) first_video = temp; mlt_properties_set_int(MLT_FRAME_PROPERTIES(temp), "image_count", ++image_count); image_count = 1; } } // Now stack callbacks if (audio != NULL) { mlt_frame_push_audio(*frame, audio); mlt_frame_push_audio(*frame, producer_get_audio); } if (video != NULL) { mlt_properties video_properties = MLT_FRAME_PROPERTIES(first_video); mlt_frame_push_service(*frame, video); mlt_frame_push_service(*frame, producer_get_image); mlt_properties_set_int(frame_properties, "width", mlt_properties_get_int(video_properties, "width")); mlt_properties_set_int(frame_properties, "height", mlt_properties_get_int(video_properties, "height")); mlt_properties_set_int(frame_properties, "format", mlt_properties_get_int(video_properties, "format")); mlt_properties_pass_list(frame_properties, video_properties, "meta.media.width, meta.media.height"); mlt_properties_set_int(frame_properties, "progressive", mlt_properties_get_int(video_properties, "progressive")); mlt_properties_set_double(frame_properties, "aspect_ratio", mlt_properties_get_double(video_properties, "aspect_ratio")); mlt_properties_set_int(frame_properties, "image_count", image_count); mlt_properties_set_data(frame_properties, "_producer", mlt_frame_get_original_producer(first_video), 0, NULL, NULL); } mlt_frame_set_position(*frame, mlt_producer_frame(parent)); mlt_properties_set_int(MLT_FRAME_PROPERTIES(*frame), "test_audio", audio == NULL); mlt_properties_set_int(MLT_FRAME_PROPERTIES(*frame), "test_image", video == NULL); } else if (producer != NULL) { mlt_producer_seek(producer, mlt_producer_frame(parent)); mlt_producer_set_speed(producer, mlt_producer_get_speed(parent)); mlt_service_get_frame(self->producer, frame, track); } else { mlt_log(MLT_PRODUCER_SERVICE(parent), MLT_LOG_ERROR, "tractor without a multitrack!!\n"); mlt_service_get_frame(self->producer, frame, track); } // Prepare the next frame mlt_producer_prepare_next(parent); // Indicate our found status return 0; } else { // Generate a test card *frame = mlt_frame_init(MLT_PRODUCER_SERVICE(parent)); return 0; } } /** Close the tractor and free its resources. * * \public \memberof mlt_tractor_s * \param self a tractor */ void mlt_tractor_close(mlt_tractor self) { if (self != NULL && mlt_properties_dec_ref(MLT_TRACTOR_PROPERTIES(self)) <= 0) { self->parent.close = NULL; mlt_producer_close(&self->parent); free(self); } } mlt-7.22.0/src/framework/mlt_tractor.h000664 000000 000000 00000005040 14531534050 017644 0ustar00rootroot000000 000000 /** * \file mlt_tractor.h * \brief tractor service class * \see mlt_tractor_s * * Copyright (C) 2003-2015 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef MLT_TRACTOR_H #define MLT_TRACTOR_H #include "mlt_producer.h" /** \brief Tractor class * * The tractor is a convenience class that works with the field class * to manage a multitrack, track filters, and transitions. * * \extends mlt_producer_s * \properties \em multitrack holds a reference to the mulitrack object that a tractor manages * \properties \em field holds a reference to the field object that a tractor manages * \properties \em producer holds a reference to an encapsulated producer */ struct mlt_tractor_s { struct mlt_producer_s parent; mlt_service producer; }; #define MLT_TRACTOR_PRODUCER(tractor) (&(tractor)->parent) #define MLT_TRACTOR_SERVICE(tractor) MLT_PRODUCER_SERVICE(MLT_TRACTOR_PRODUCER(tractor)) #define MLT_TRACTOR_PROPERTIES(tractor) MLT_SERVICE_PROPERTIES(MLT_TRACTOR_SERVICE(tractor)) extern mlt_tractor mlt_tractor_init(); extern mlt_tractor mlt_tractor_new(); extern mlt_service mlt_tractor_service(mlt_tractor self); extern mlt_producer mlt_tractor_producer(mlt_tractor self); extern mlt_properties mlt_tractor_properties(mlt_tractor self); extern mlt_field mlt_tractor_field(mlt_tractor self); extern mlt_multitrack mlt_tractor_multitrack(mlt_tractor self); extern int mlt_tractor_connect(mlt_tractor self, mlt_service service); extern void mlt_tractor_refresh(mlt_tractor self); extern int mlt_tractor_set_track(mlt_tractor self, mlt_producer producer, int index); extern int mlt_tractor_insert_track(mlt_tractor self, mlt_producer producer, int index); extern int mlt_tractor_remove_track(mlt_tractor self, int index); extern mlt_producer mlt_tractor_get_track(mlt_tractor self, int index); extern void mlt_tractor_close(mlt_tractor self); #endif mlt-7.22.0/src/framework/mlt_transition.c000664 000000 000000 00000046205 14531534050 020363 0ustar00rootroot000000 000000 /** * \file mlt_transition.c * \brief abstraction for all transition services * \see mlt_transition_s * * Copyright (C) 2003-2022 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "mlt_transition.h" #include "mlt_frame.h" #include "mlt_log.h" #include "mlt_producer.h" #include #include #include /* Forward references */ static int transition_get_frame(mlt_service self, mlt_frame_ptr frame, int index); /** Initialize a new transition. * * \public \memberof mlt_transition_s * \param self a transition * \param child the object of a subclass * \return true on error */ int mlt_transition_init(mlt_transition self, void *child) { mlt_service service = &self->parent; memset(self, 0, sizeof(struct mlt_transition_s)); self->child = child; if (mlt_service_init(service, self) == 0) { mlt_properties properties = MLT_TRANSITION_PROPERTIES(self); service->get_frame = transition_get_frame; service->close = (mlt_destructor) mlt_transition_close; service->close_object = self; pthread_mutex_init(&self->mutex, NULL); mlt_properties_set(properties, "mlt_type", "transition"); mlt_properties_set_position(properties, "in", 0); mlt_properties_set_position(properties, "out", 0); mlt_properties_set_int(properties, "a_track", 0); mlt_properties_set_int(properties, "b_track", 1); return 0; } return 1; } /** Create and initialize a new transition. * * \public \memberof mlt_transition_s * \return a new transition */ mlt_transition mlt_transition_new() { mlt_transition self = calloc(1, sizeof(struct mlt_transition_s)); if (self != NULL) mlt_transition_init(self, NULL); return self; } /** Get the service class interface. * * \public \memberof mlt_transition_s * \param self a transition * \return the service class * \see MLT_TRANSITION_SERVICE */ mlt_service mlt_transition_service(mlt_transition self) { return self != NULL ? &self->parent : NULL; } /** Get the properties interface. * * \public \memberof mlt_transition_s * \param self a transition * \return the transition's properties * \see MLT_TRANSITION_PROPERTIES */ mlt_properties mlt_transition_properties(mlt_transition self) { return MLT_TRANSITION_PROPERTIES(self); } /** Connect a transition with a producer's a and b tracks. * * \public \memberof mlt_transition_s * \param self a transition * \param producer a producer * \param a_track the track index of the first input * \param b_track the track index of the second index * \return true on error */ int mlt_transition_connect(mlt_transition self, mlt_service producer, int a_track, int b_track) { int ret = mlt_service_connect_producer(&self->parent, producer, a_track); if (ret == 0) { mlt_properties properties = MLT_TRANSITION_PROPERTIES(self); self->producer = producer; mlt_properties_set_int(properties, "a_track", a_track); mlt_properties_set_int(properties, "b_track", b_track); } return ret; } /** Set the starting and ending time for when the transition is active. * * \public \memberof mlt_transition_s * \param self a transition * \param in the starting time * \param out the ending time */ void mlt_transition_set_in_and_out(mlt_transition self, mlt_position in, mlt_position out) { mlt_properties properties = MLT_TRANSITION_PROPERTIES(self); mlt_properties_set_position(properties, "in", in); mlt_properties_set_position(properties, "out", out); } /** Change the track indices of a transition. * * \public \memberof mlt_transition_s * \param a_track the track index of the first input * \param b_track the track index of the second index */ void mlt_transition_set_tracks(mlt_transition self, int a_track, int b_track) { mlt_properties_set_int(MLT_TRANSITION_PROPERTIES(self), "a_track", a_track); mlt_properties_set_int(MLT_TRANSITION_PROPERTIES(self), "b_track", b_track); pthread_mutex_lock(&self->mutex); free(self->frames); self->frames = NULL; pthread_mutex_unlock(&self->mutex); } /** Get the index of the a track. * * \public \memberof mlt_transition_s * \param self a transition * \return the 0-based index of the track of the first producer */ int mlt_transition_get_a_track(mlt_transition self) { return mlt_properties_get_int(MLT_TRANSITION_PROPERTIES(self), "a_track"); } /** Get the index of the b track. * * \public \memberof mlt_transition_s * \param self a transition * \return the 0-based index of the track of the second producer */ int mlt_transition_get_b_track(mlt_transition self) { return mlt_properties_get_int(MLT_TRANSITION_PROPERTIES(self), "b_track"); } /** Get the in point. * * \public \memberof mlt_transition_s * \param self a transition * \return the starting time */ mlt_position mlt_transition_get_in(mlt_transition self) { return mlt_properties_get_position(MLT_TRANSITION_PROPERTIES(self), "in"); } /** Get the out point. * * \public \memberof mlt_transition_s * \param self a transition * \return the ending time */ mlt_position mlt_transition_get_out(mlt_transition self) { return mlt_properties_get_position(MLT_TRANSITION_PROPERTIES(self), "out"); } /** Get the duration. * * \public \memberof mlt_transition_s * \param self a transition * \return the duration or zero if unlimited */ mlt_position mlt_transition_get_length(mlt_transition self) { mlt_properties properties = MLT_SERVICE_PROPERTIES(&self->parent); mlt_position in = mlt_properties_get_position(properties, "in"); mlt_position out = mlt_properties_get_position(properties, "out"); return (out > 0) ? (out - in + 1) : 0; } /** Get the position within the transition. * * The position is relative to the in point. * * \public \memberof mlt_transition_s * \param self a transition * \param frame a frame * \return the position */ mlt_position mlt_transition_get_position(mlt_transition self, mlt_frame frame) { mlt_position in = mlt_transition_get_in(self); mlt_position position = mlt_frame_get_position(frame); return position - in; } /** Get the percent complete. * * \public \memberof mlt_transition_s * \param self a transition * \param frame a frame * \return the progress in the range 0.0 to 1.0 */ double mlt_transition_get_progress(mlt_transition self, mlt_frame frame) { double progress = 0; mlt_position in = mlt_transition_get_in(self); mlt_position out = mlt_transition_get_out(self); if (out == 0) { // If always active, use the frame's producer mlt_producer producer = mlt_frame_get_original_producer(frame); if (producer) { in = mlt_producer_get_in(producer); out = mlt_producer_get_out(producer); } } if (out != 0) { if (in == out) { // Special case for one frame transition progress = 0.5; } else { mlt_position position = mlt_frame_get_position(frame); progress = (double) (position - in) / (double) (out - in + 1); } } return progress; } /** Get the second field incremental progress. * * \public \memberof mlt_transition_s * \param self a transition * \param frame a frame * \return the progress increment in the range 0.0 to 1.0 */ double mlt_transition_get_progress_delta(mlt_transition self, mlt_frame frame) { double progress = 0; mlt_position in = mlt_transition_get_in(self); mlt_position out = mlt_transition_get_out(self); if (out == 0) { // If always active, use the frame's producer mlt_producer producer = mlt_frame_get_original_producer(frame); if (producer) { in = mlt_producer_get_in(producer); out = mlt_producer_get_out(producer); } } if (out != 0) { mlt_position position = mlt_frame_get_position(frame); double length = out - in + 1; double x = (double) (position - in) / length; double y = (double) (position + 1 - in) / length; progress = (y - x) / 2.0; } return progress; } /** Process the frame. * * If we have no process method (unlikely), we simply return the a_frame unmolested. * * \public \memberof mlt_transition_s * \param self a transition * \param a_frame a frame from the first producer * \param b_frame a frame from the second producer * \return a frame */ mlt_frame mlt_transition_process(mlt_transition self, mlt_frame a_frame, mlt_frame b_frame) { if (self->process == NULL) return a_frame; else return self->process(self, a_frame, b_frame); } static int get_image_a(mlt_frame a_frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { mlt_transition self = mlt_frame_pop_service(a_frame); mlt_properties a_props = MLT_FRAME_PROPERTIES(a_frame); // All transitions get scaling const char *rescale = mlt_properties_get(a_props, "consumer.rescale"); if (!rescale || !strcmp(rescale, "none")) mlt_properties_set(a_props, "consumer.rescale", "nearest"); // Ensure sane aspect ratio if (mlt_frame_get_aspect_ratio(a_frame) == 0.0) mlt_frame_set_aspect_ratio(a_frame, mlt_profile_sar( mlt_service_profile(MLT_TRANSITION_SERVICE(self)))); return mlt_frame_get_image(a_frame, image, format, width, height, writable); } static int get_image_b(mlt_frame b_frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { mlt_transition self = mlt_frame_pop_service(b_frame); mlt_frame a_frame = mlt_frame_pop_frame(b_frame); mlt_properties a_props = MLT_FRAME_PROPERTIES(a_frame); mlt_properties b_props = MLT_FRAME_PROPERTIES(b_frame); // Set scaling from A frame if not already provided. if (!mlt_properties_get(b_props, "consumer.rescale")) { const char *rescale = mlt_properties_get(a_props, "consumer.rescale"); if (!rescale || !strcmp(rescale, "none")) rescale = "nearest"; mlt_properties_set(b_props, "consumer.rescale", rescale); } // Ensure sane aspect ratio if (mlt_frame_get_aspect_ratio(b_frame) == 0.0) mlt_frame_set_aspect_ratio(b_frame, mlt_profile_sar( mlt_service_profile(MLT_TRANSITION_SERVICE(self)))); mlt_properties_copy(b_props, a_props, "consumer."); return mlt_frame_get_image(b_frame, image, format, width, height, writable); } /** Get a frame from a transition. The logic is complex here. A transition is typically applied to frames on the a and b tracks specified in the connect method above and only if both contain valid info for the transition type (this is either audio or image). However, the fixed a_track may not always contain data of the correct type, eg:
	+---------+                               +-------+
	|c1       |                               |c5     | <-- A(0,1) <-- B(0,2) <-- get frame
	+---------+                     +---------+-+-----+        |          |
	                                |c4         |       <------+          |
	         +----------+-----------+-+---------+                         |
	         |c2        |c3           |                 <-----------------+
	         +----------+-------------+
During the overlap of c1 and c2, there is nothing for the A transition to do, so this results in a no operation, but B is triggered. During the overlap of c2 and c3, again, the A transition is inactive and because the B transition is pointing at track 0, it too would be inactive. This isn't an ideal situation - it's better if the B transition simply treats the frames from c3 as though they're the a track. For this to work, we cache all frames coming from all tracks between the a and b tracks. Before we process, we determine that the b frame contains something of the right type and then we determine which frame to use as the a frame (selecting a matching frame from a_track to b_track - 1). If both frames contain data of the correct type, we process the transition. This method is invoked for each track and we return the cached frames as needed. We clear the cache only when the requested frame is flagged as a 'last_track' frame. * \private \memberof mlt_transition_s * \param service a service * \param[out] frame a frame by reference * \param index 0-based track index * \return true on error */ static int transition_get_frame(mlt_service service, mlt_frame_ptr frame, int index) { int error = 0; mlt_transition self = service->child; mlt_properties properties = MLT_TRANSITION_PROPERTIES(self); int accepts_blanks = mlt_properties_get_int(properties, "accepts_blanks"); int a_track = mlt_properties_get_int(properties, "a_track"); int b_track = mlt_properties_get_int(properties, "b_track"); mlt_position in = mlt_properties_get_position(properties, "in"); mlt_position out = mlt_properties_get_position(properties, "out"); int always_active = mlt_properties_get_int(properties, "always_active"); int type = mlt_properties_get_int(properties, "_transition_type"); int reverse_order = 0; // Ensure that we have the correct order if (a_track > b_track) { reverse_order = 1; a_track = b_track; b_track = mlt_properties_get_int(properties, "a_track"); } a_track = a_track < 0 ? 0 : a_track; b_track = b_track < 0 ? 0 : b_track; // Only act on this operation once per multitrack iteration from the tractor pthread_mutex_lock(&self->mutex); if (!self->held) { int active = 0; int i = 0; int a_frame = a_track; int b_frame = b_track; mlt_position position; int (*invalid)(mlt_frame) = type == 1 ? mlt_frame_is_test_card : mlt_frame_is_test_audio; // Initialise temporary store if (self->frames == NULL) self->frames = calloc(b_track + 1, sizeof(mlt_frame)); // Get all frames between a and b for (i = a_track; i <= b_track; i++) mlt_service_get_frame(self->producer, &self->frames[i], i); // We're holding these frames until the last_track frame property is received self->held = 1; // When we need to locate the a_frame switch (type) { case 1: case 2: // Some transitions (esp. audio) may accept blank frames active = accepts_blanks; // If we're not active then... if (!active) { // Hunt for the a_frame while (a_frame <= b_frame && (invalid(self->frames[a_frame]) || (mlt_properties_get_int(MLT_FRAME_PROPERTIES(self->frames[a_frame]), "hide") & type))) { a_frame++; } // Determine if we're active now active = a_frame != b_frame && !invalid(self->frames[b_frame]); } break; default: mlt_log(service, MLT_LOG_ERROR, "invalid transition type\n"); break; } // Now handle the non-always active case if (active && !always_active && a_frame <= b_track) { // For non-always-active transitions, we need the current position of the a frame position = mlt_frame_get_position(self->frames[a_frame]); // If a is in range, we're active active = position >= in && (out == 0 || position <= out); } // Finally, process the a and b frames if (active && !mlt_properties_get_int(MLT_TRANSITION_PROPERTIES(self), "disable")) { int frame_nb = (!reverse_order && a_frame <= b_track) ? a_frame : b_frame; mlt_frame a_frame_ptr = self->frames[frame_nb]; frame_nb = (!reverse_order || a_frame > b_track) ? b_frame : a_frame; mlt_frame b_frame_ptr = self->frames[frame_nb]; if (a_frame_ptr && MLT_FRAME_PROPERTIES(a_frame_ptr)->local && b_frame_ptr && MLT_FRAME_PROPERTIES(b_frame_ptr)->local) { int a_hide = mlt_properties_get_int(MLT_FRAME_PROPERTIES(a_frame_ptr), "hide"); int b_hide = mlt_properties_get_int(MLT_FRAME_PROPERTIES(b_frame_ptr), "hide"); if (!(a_hide & type) && !(b_hide & type)) { // Add hooks for pre-processing frames mlt_frame_push_service(a_frame_ptr, self); mlt_frame_push_get_image(a_frame_ptr, get_image_a); mlt_frame_push_frame(b_frame_ptr, a_frame_ptr); mlt_frame_push_service(b_frame_ptr, self); mlt_frame_push_get_image(b_frame_ptr, get_image_b); // Process the transition *frame = mlt_transition_process(self, a_frame_ptr, b_frame_ptr); // We need to ensure that the tractor doesn't consider this frame for output if (*frame == a_frame_ptr) b_hide |= type; else a_hide |= type; mlt_properties_set_int(MLT_FRAME_PROPERTIES(a_frame_ptr), "hide", a_hide); mlt_properties_set_int(MLT_FRAME_PROPERTIES(b_frame_ptr), "hide", b_hide); } } } } // Obtain the frame from the cache or the producer we're attached to if (index >= a_track && index <= b_track && self->frames) *frame = self->frames[index]; else error = mlt_service_get_frame(self->producer, frame, index); // Determine if that was the last track if (!error && *frame) { self->held = !mlt_properties_get_int(MLT_FRAME_PROPERTIES(*frame), "last_track"); } pthread_mutex_unlock(&self->mutex); return error; } /** Close and destroy the transition. * * \public \memberof mlt_transition_s * \param self a transition */ void mlt_transition_close(mlt_transition self) { if (self != NULL && mlt_properties_dec_ref(MLT_TRANSITION_PROPERTIES(self)) <= 0) { self->parent.close = NULL; if (self->close != NULL) { self->close(self); } else { mlt_service_close(&self->parent); free(self->frames); pthread_mutex_destroy(&self->mutex); free(self); } } } mlt-7.22.0/src/framework/mlt_transition.h000664 000000 000000 00000007235 14531534050 020370 0ustar00rootroot000000 000000 /** * \file mlt_transition.h * \brief abstraction for all transition services * \see mlt_transition_s * * Copyright (C) 2003-2021 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef MLT_TRANSITION_H #define MLT_TRANSITION_H #include "mlt_service.h" #include /** \brief Transition abstract service class * * A transition may modify the output of a producer based on the output of a second producer. * * \extends mlt_service_s * \properties \em a_track the track index (0-based) of a multitrack of the first producer * \properties \em b_track the track index (0-based) of a multitrack of the second producer * \properties \em accepts_blanks a flag to indicate if the transition should accept blank frames * \properties \em always_active a flag to indicate that the in and out points do not apply * \properties \em _transition_type 1 for video, 2 for audio, 3 for both audio and video * \properties \em disable Set this to disable the transition while keeping it in the object model. */ struct mlt_transition_s { /** We're implementing service here */ struct mlt_service_s parent; /** public virtual */ void (*close)(mlt_transition); /** protected transition method */ mlt_frame (*process)(mlt_transition, mlt_frame, mlt_frame); /** Protected */ void *child; /** track and in/out points */ mlt_service producer; /** Private */ mlt_frame *frames; int held; pthread_mutex_t mutex; }; #define MLT_TRANSITION_SERVICE(transition) (&(transition)->parent) #define MLT_TRANSITION_PROPERTIES(transition) \ MLT_SERVICE_PROPERTIES(MLT_TRANSITION_SERVICE(transition)) extern int mlt_transition_init(mlt_transition self, void *child); extern mlt_transition mlt_transition_new(); extern mlt_service mlt_transition_service(mlt_transition self); extern mlt_properties mlt_transition_properties(mlt_transition self); extern int mlt_transition_connect(mlt_transition self, mlt_service producer, int a_track, int b_track); extern void mlt_transition_set_in_and_out(mlt_transition self, mlt_position in, mlt_position out); extern void mlt_transition_set_tracks(mlt_transition self, int a_track, int b_track); extern int mlt_transition_get_a_track(mlt_transition self); extern int mlt_transition_get_b_track(mlt_transition self); extern mlt_position mlt_transition_get_in(mlt_transition self); extern mlt_position mlt_transition_get_out(mlt_transition self); extern mlt_position mlt_transition_get_length(mlt_transition self); extern mlt_position mlt_transition_get_position(mlt_transition self, mlt_frame frame); extern double mlt_transition_get_progress(mlt_transition self, mlt_frame frame); extern double mlt_transition_get_progress_delta(mlt_transition self, mlt_frame frame); extern mlt_frame mlt_transition_process(mlt_transition self, mlt_frame a_frame, mlt_frame b_frame); extern void mlt_transition_close(mlt_transition self); #endif mlt-7.22.0/src/framework/mlt_types.c000664 000000 000000 00000003674 14531534050 017340 0ustar00rootroot000000 000000 /** * \file mlt_types.c * \brief Mlt types helper functions * * Copyright (C) 2023 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "mlt_types.h" #include #include const char *mlt_deinterlacer_name(mlt_deinterlacer method) { switch (method) { case mlt_deinterlacer_none: return "none"; case mlt_deinterlacer_onefield: return "onefield"; case mlt_deinterlacer_linearblend: return "linearblend"; case mlt_deinterlacer_bob: return "bob"; case mlt_deinterlacer_weave: return "weave"; case mlt_deinterlacer_greedy: return "greedy"; case mlt_deinterlacer_yadif_nospatial: return "yadif-nospatial"; case mlt_deinterlacer_yadif: return "yadif"; case mlt_deinterlacer_bwdif: return "bwdif"; case mlt_deinterlacer_estdif: return "estdif"; case mlt_deinterlacer_invalid: return "invalid"; } return "invalid"; } mlt_deinterlacer mlt_deinterlacer_id(const char *name) { mlt_deinterlacer m; for (m = mlt_deinterlacer_none; name && m < mlt_deinterlacer_invalid; m++) { if (!strcmp(mlt_deinterlacer_name(m), name)) return m; } return mlt_deinterlacer_invalid; } mlt-7.22.0/src/framework/mlt_types.h000664 000000 000000 00000032127 14531534050 017340 0ustar00rootroot000000 000000 /** * \file mlt_types.h * \brief Provides forward definitions of all public types * * Copyright (C) 2003-2023 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef MLT_TYPES_H #define MLT_TYPES_H #ifndef GCC_VERSION #define GCC_VERSION (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) #endif #ifdef __cplusplus extern "C" { #endif #include "mlt_pool.h" #include #include #include #ifndef PATH_MAX #define PATH_MAX 4096 #endif /** The set of supported image formats */ typedef enum { mlt_image_none = 0, /**< image not available */ mlt_image_rgb, /**< 8-bit RGB */ mlt_image_rgba, /**< 8-bit RGB with alpha channel */ mlt_image_yuv422, /**< 8-bit YUV 4:2:2 packed */ mlt_image_yuv420p, /**< 8-bit YUV 4:2:0 planar */ mlt_image_movit, /**< for movit module internal use only */ mlt_image_opengl_texture, /**< an OpenGL texture name */ mlt_image_yuv422p16, /**< planar YUV 4:2:2, 32bpp, (1 Cr & Cb sample per 2x1 Y samples), little-endian */ mlt_image_yuv420p10, /**< planar YUV 4:2:0, 15bpp, (1 Cr & Cb sample per 2x2 Y samples), little-endian */ mlt_image_yuv444p10, /**< planar YUV 4:4:4, 30bpp, (1 Cr & Cb sample per 1x1 Y samples), little-endian */ mlt_image_invalid } mlt_image_format; /** The set of supported audio formats */ typedef enum { mlt_audio_none = 0, /**< audio not available */ mlt_audio_s16 = 1, /**< signed 16-bit interleaved PCM */ mlt_audio_s32, /**< signed 32-bit non-interleaved PCM */ mlt_audio_float, /**< 32-bit non-interleaved floating point */ mlt_audio_s32le, /**< signed 32-bit interleaved PCM */ mlt_audio_f32le, /**< 32-bit interleaved floating point */ mlt_audio_u8 /**< unsigned 8-bit interleaved PCM */ } mlt_audio_format; typedef enum { mlt_channel_auto = 0, /**< MLT will determine the default configuration based on channel number */ mlt_channel_independent, /**< channels are not related */ mlt_channel_mono, mlt_channel_stereo, mlt_channel_2p1, mlt_channel_3p0, mlt_channel_3p0_back, mlt_channel_4p0, mlt_channel_quad_back, mlt_channel_quad_side, mlt_channel_3p1, mlt_channel_5p0_back, mlt_channel_5p0, mlt_channel_4p1, mlt_channel_5p1_back, mlt_channel_5p1, mlt_channel_6p0, mlt_channel_6p0_front, mlt_channel_hexagonal, mlt_channel_6p1, mlt_channel_6p1_back, mlt_channel_6p1_front, mlt_channel_7p0, mlt_channel_7p0_front, mlt_channel_7p1, mlt_channel_7p1_wide_side, mlt_channel_7p1_wide_back, } mlt_channel_layout; /** Colorspace definitions */ typedef enum { mlt_colorspace_rgb = 0, ///< order of coefficients is actually GBR, also IEC 61966-2-1 (sRGB) mlt_colorspace_bt709 = 1, ///< also ITU-R BT1361 / IEC 61966-2-4 xvYCC709 / SMPTE RP177 Annex B mlt_colorspace_unspecified = 2, mlt_colorspace_reserved = 3, mlt_colorspace_fcc = 4, ///< FCC Title 47 Code of Federal Regulations 73.682 (a)(20) mlt_colorspace_bt470bg = 5, ///< also ITU-R BT601-6 625 / ITU-R BT1358 625 / ITU-R BT1700 625 PAL & SECAM / IEC 61966-2-4 xvYCC601 mlt_colorspace_smpte170m = 6, ///< also ITU-R BT601-6 525 / ITU-R BT1358 525 / ITU-R BT1700 NTSC mlt_colorspace_smpte240m = 7, ///< functionally identical to above mlt_colorspace_ycgco = 8, ///< Used by Dirac / VC-2 and H.264 FRext, see ITU-T SG16 mlt_colorspace_bt2020_ncl = 9, ///< ITU-R BT2020 non-constant luminance system mlt_colorspace_bt2020_cl = 10, ///< ITU-R BT2020 constant luminance system mlt_colorspace_smpte2085 = 11, ///< SMPTE 2085, Y'D'zD'x } mlt_colorspace; typedef enum { mlt_deinterlacer_none, mlt_deinterlacer_onefield, mlt_deinterlacer_linearblend, mlt_deinterlacer_weave, mlt_deinterlacer_bob, mlt_deinterlacer_greedy, mlt_deinterlacer_yadif_nospatial, mlt_deinterlacer_yadif, mlt_deinterlacer_bwdif, mlt_deinterlacer_estdif, mlt_deinterlacer_invalid, } mlt_deinterlacer; /** The time string formats */ typedef enum { mlt_time_frames = 0, /**< frame count */ mlt_time_clock, /**< SMIL clock-value as [[hh:]mm:]ss[.fraction] */ mlt_time_smpte_df, /**< SMPTE timecode as [[[hh:]mm:]ss{:|;}]frames */ mlt_time_smpte_ndf /**< SMPTE NDF timecode as [[[hh:]mm:]ss:]frames */ } mlt_time_format; /** Interpolation methods for animation keyframes */ typedef enum { mlt_keyframe_discrete = 0, /**< non-interpolated; value changes instantaneously at the key frame */ mlt_keyframe_linear, /**< simple, constant pace from this key frame to the next */ mlt_keyframe_smooth, /**< deprecated use mlt_keyframe_smooth_loose */ mlt_keyframe_smooth_loose = mlt_keyframe_smooth, /**< Unity Catmull-Rom spline interpolation. May have cusps or overshoots*/ mlt_keyframe_smooth_natural, /**< Centripetal Catmull-Rom spline interpolation with natural slope at each keyframe. Will not have cusps or overshoots*/ mlt_keyframe_smooth_tight, /**< Centripetal Catmull-Rom spline interpolation with 0 slope at each keyframe. Will not have cusps or overshoots*/ mlt_keyframe_sinusoidal_in, mlt_keyframe_sinusoidal_out, mlt_keyframe_sinusoidal_in_out, mlt_keyframe_quadratic_in, mlt_keyframe_quadratic_out, mlt_keyframe_quadratic_in_out, mlt_keyframe_cubic_in, mlt_keyframe_cubic_out, mlt_keyframe_cubic_in_out, mlt_keyframe_quartic_in, mlt_keyframe_quartic_out, mlt_keyframe_quartic_in_out, mlt_keyframe_quintic_in, mlt_keyframe_quintic_out, mlt_keyframe_quintic_in_out, mlt_keyframe_exponential_in, mlt_keyframe_exponential_out, mlt_keyframe_exponential_in_out, mlt_keyframe_circular_in, mlt_keyframe_circular_out, mlt_keyframe_circular_in_out, mlt_keyframe_back_in, mlt_keyframe_back_out, mlt_keyframe_back_in_out, mlt_keyframe_elastic_in, mlt_keyframe_elastic_out, mlt_keyframe_elastic_in_out, mlt_keyframe_bounce_in, mlt_keyframe_bounce_out, mlt_keyframe_bounce_in_out, } mlt_keyframe_type; /** The relative time qualifiers */ typedef enum { mlt_whence_relative_start = 0, /**< relative to the beginning */ mlt_whence_relative_current, /**< relative to the current position */ mlt_whence_relative_end /**< relative to the end */ } mlt_whence; /** The recognized subclasses of mlt_service */ typedef enum { mlt_service_invalid_type = 0, /**< invalid service */ mlt_service_unknown_type, /**< unknown class */ mlt_service_producer_type, /**< Producer class */ mlt_service_tractor_type, /**< Tractor class */ mlt_service_playlist_type, /**< Playlist class */ mlt_service_multitrack_type, /**< Multitrack class */ mlt_service_filter_type, /**< Filter class */ mlt_service_transition_type, /**< Transition class */ mlt_service_consumer_type, /**< Consumer class */ mlt_service_field_type, /**< Field class */ mlt_service_link_type, /**< Link class */ mlt_service_chain_type /**< Chain class */ } mlt_service_type; /* I don't want to break anyone's applications without warning. -Zach */ #ifdef DOUBLE_MLT_POSITION #define MLT_POSITION_FMT "%f" #define MLT_POSITION_MOD(A, B) ((A) - (B) * ((int) ((A) / (B)))) typedef double mlt_position; #else #define MLT_POSITION_MOD(A, B) ((A) % (B)) #define MLT_POSITION_FMT "%d" typedef int32_t mlt_position; #endif /** A rectangle type with coordinates, size, and opacity */ typedef struct { double x; /**< X coordinate */ double y; /**< Y coordinate */ double w; /**< width */ double h; /**< height */ double o; /**< opacity / mix-level */ } mlt_rect; /** A tuple of color components */ typedef struct { uint8_t r; /**< red */ uint8_t g; /**< green */ uint8_t b; /**< blue */ uint8_t a; /**< alpha */ } mlt_color; typedef struct mlt_audio_s *mlt_audio; /**< pointer to Audio object */ typedef struct mlt_image_s *mlt_image; /**< pointer to Image object */ typedef struct mlt_frame_s *mlt_frame, **mlt_frame_ptr; /**< pointer to Frame object */ typedef struct mlt_property_s *mlt_property; /**< pointer to Property object */ typedef struct mlt_properties_s *mlt_properties; /**< pointer to Properties object */ typedef struct mlt_event_struct *mlt_event; /**< pointer to Event object */ typedef struct mlt_service_s *mlt_service; /**< pointer to Service object */ typedef struct mlt_producer_s *mlt_producer; /**< pointer to Producer object */ typedef struct mlt_playlist_s *mlt_playlist; /**< pointer to Playlist object */ typedef struct mlt_multitrack_s *mlt_multitrack; /**< pointer to Multitrack object */ typedef struct mlt_filter_s *mlt_filter; /**< pointer to Filter object */ typedef struct mlt_transition_s *mlt_transition; /**< pointer to Transition object */ typedef struct mlt_tractor_s *mlt_tractor; /**< pointer to Tractor object */ typedef struct mlt_field_s *mlt_field; /**< pointer to Field object */ typedef struct mlt_consumer_s *mlt_consumer; /**< pointer to Consumer object */ typedef struct mlt_parser_s *mlt_parser; /**< pointer to Properties object */ typedef struct mlt_deque_s *mlt_deque; /**< pointer to Deque object */ typedef struct mlt_geometry_s *mlt_geometry; /**< pointer to Geometry object */ typedef struct mlt_geometry_item_s *mlt_geometry_item; /**< pointer to Geometry Item object */ typedef struct mlt_profile_s *mlt_profile; /**< pointer to Profile object */ typedef struct mlt_repository_s *mlt_repository; /**< pointer to Repository object */ typedef struct mlt_cache_s *mlt_cache; /**< pointer to Cache object */ typedef struct mlt_cache_item_s *mlt_cache_item; /**< pointer to CacheItem object */ typedef struct mlt_animation_s *mlt_animation; /**< pointer to Property Animation object */ typedef struct mlt_slices_s *mlt_slices; /**< pointer to Sliced processing context object */ typedef struct mlt_link_s *mlt_link; /**< pointer to Link object */ typedef struct mlt_chain_s *mlt_chain; /**< pointer to Chain object */ typedef void (*mlt_destructor)(void *); /**< pointer to destructor function */ typedef char *(*mlt_serialiser)(void *, int length); /**< pointer to serialization function */ typedef void *(*mlt_thread_function_t)(void *); /**< generic thread function pointer */ #define MLT_SERVICE(x) ((mlt_service) (x)) /**< Cast to a Service pointer */ #define MLT_PRODUCER(x) ((mlt_producer) (x)) /**< Cast to a Producer pointer */ #define MLT_MULTITRACK(x) ((mlt_multitrack) (x)) /**< Cast to a Multitrack pointer */ #define MLT_PLAYLIST(x) ((mlt_playlist) (x)) /**< Cast to a Playlist pointer */ #define MLT_TRACTOR(x) ((mlt_tractor) (x)) /**< Cast to a Tractor pointer */ #define MLT_FILTER(x) ((mlt_filter) (x)) /**< Cast to a Filter pointer */ #define MLT_TRANSITION(x) ((mlt_transition) (x)) /**< Cast to a Transition pointer */ #define MLT_CONSUMER(x) ((mlt_consumer) (x)) /**< Cast to a Consumer pointer */ #define MLT_FRAME(x) ((mlt_frame) (x)) /**< Cast to a Frame pointer */ #define MLT_LINK(x) ((mlt_link) (x)) /**< Cast to a Link pointer */ #define MLT_CHAIN(x) ((mlt_chain) (x)) /**< Cast to a Chain pointer */ #ifndef MIN #define MIN(x, y) ((x) < (y) ? (x) : (y)) #endif #ifndef MAX #define MAX(x, y) ((x) > (y) ? (x) : (y)) #endif #ifndef CLAMP #define CLAMP(x, min, max) ((x) < (min) ? (min) : (x) > (max) ? (max) : (x)) #endif #ifdef _WIN32 #include /* Win32 compatibility function declarations */ #if !defined(__MINGW32__) extern int usleep(unsigned int useconds); #endif #ifndef WIN_PTHREADS_TIME_H extern int nanosleep(const struct timespec *rqtp, struct timespec *rmtp); #endif extern int setenv(const char *name, const char *value, int overwrite); extern char *getlocale(); extern FILE *win32_fopen(const char *filename_utf8, const char *mode_utf8); #include extern char *strptime(const char *buf, const char *fmt, struct tm *tm); #define mlt_fopen win32_fopen #define MLT_DIRLIST_DELIMITER ";" #else #define mlt_fopen fopen #define MLT_DIRLIST_DELIMITER ":" #endif /* ifdef _WIN32 */ extern const char *mlt_deinterlacer_name(mlt_deinterlacer method); extern mlt_deinterlacer mlt_deinterlacer_id(const char *name); #ifdef __cplusplus } #endif #endif mlt-7.22.0/src/framework/mlt_version.c000664 000000 000000 00000002271 14531534050 017651 0ustar00rootroot000000 000000 /** * \file mlt_version.c * \brief contains version information * * Copyright (C) 2014 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "mlt_version.h" int mlt_version_get_int() { return LIBMLT_VERSION_INT; } char *mlt_version_get_string() { return LIBMLT_VERSION; } int mlt_version_get_major() { return LIBMLT_VERSION_MAJOR; } int mlt_version_get_minor() { return LIBMLT_VERSION_MINOR; } int mlt_version_get_revision() { return LIBMLT_VERSION_REVISION; } mlt-7.22.0/src/framework/mlt_version.h000664 000000 000000 00000003005 14531534050 017652 0ustar00rootroot000000 000000 /** * \file mlt_version.h * \brief contains version information * * Copyright (C) 2010-2023 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef MLT_VERSION_H #define MLT_VERSION_H // Add quotes around any #define variables #define MLT_STRINGIZE2(s) #s #define MLT_STRINGIZE(s) MLT_STRINGIZE2(s) #define LIBMLT_VERSION_MAJOR 7 #define LIBMLT_VERSION_MINOR 22 #define LIBMLT_VERSION_REVISION 0 #define LIBMLT_VERSION_INT \ ((LIBMLT_VERSION_MAJOR << 16) + (LIBMLT_VERSION_MINOR << 8) + LIBMLT_VERSION_REVISION) #define LIBMLT_VERSION \ MLT_STRINGIZE(LIBMLT_VERSION_MAJOR.LIBMLT_VERSION_MINOR.LIBMLT_VERSION_REVISION) extern int mlt_version_get_int(); extern int mlt_version_get_major(); extern int mlt_version_get_minor(); extern int mlt_version_get_revision(); extern char *mlt_version_get_string(); #endif mlt-7.22.0/src/melt/000775 000000 000000 00000000000 14531534050 014106 5ustar00rootroot000000 000000 mlt-7.22.0/src/melt/CMakeLists.txt000664 000000 000000 00000002376 14531534050 016656 0ustar00rootroot000000 000000 add_executable(melt melt.c io.c io.h) target_compile_options(melt PRIVATE ${MLT_COMPILE_OPTIONS}) target_link_libraries(melt PRIVATE mlt Threads::Threads) target_compile_definitions(melt PRIVATE VERSION="${MLT_VERSION}") if(TARGET PkgConfig::sdl2 AND NOT ANDROID) target_link_libraries(melt PRIVATE PkgConfig::sdl2) target_compile_definitions(melt PRIVATE HAVE_SDL2) if(MINGW) target_link_libraries(melt PRIVATE mingw32) endif() endif() if(MINGW) target_link_options(melt PRIVATE -mconsole) endif() if(UNIX AND NOT APPLE) install(CODE "execute_process(COMMAND ${CMAKE_COMMAND} -E remove melt melt-${MLT_VERSION_MAJOR} \ WORKING_DIRECTORY \$ENV\{DESTDIR\}${CMAKE_INSTALL_FULL_BINDIR})" ) endif() install(TARGETS melt RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) if(UNIX AND NOT APPLE) install(CODE "execute_process(COMMAND ${CMAKE_COMMAND} -E rename melt melt-${MLT_VERSION_MAJOR} \ WORKING_DIRECTORY \$ENV\{DESTDIR\}${CMAKE_INSTALL_FULL_BINDIR})" ) install(CODE "execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink melt-${MLT_VERSION_MAJOR} melt \ WORKING_DIRECTORY \$ENV\{DESTDIR\}${CMAKE_INSTALL_FULL_BINDIR})" ) endif() mlt-7.22.0/src/melt/io.c000664 000000 000000 00000011650 14531534050 014664 0ustar00rootroot000000 000000 /* * io.c -- melt input/output * Copyright (C) 2002-2015 Meltytech, LLC * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifdef HAVE_CONFIG_H #include #endif /* System header files */ #include #include #include #include #ifndef _WIN32 #include #else // MinGW defines struct timespec in pthread.h #include // for nanosleep() #include #include #endif #include #include /* Application header files */ #include "io.h" char *chomp(char *input) { if (input != NULL) { int length = strlen(input); if (length && input[length - 1] == '\n') input[length - 1] = '\0'; if (length > 1 && input[length - 2] == '\r') input[length - 2] = '\0'; } return input; } char *trim(char *input) { if (input != NULL) { int length = strlen(input); int first = 0; while (first < length && isspace(input[first])) first++; memmove(input, input + first, length - first + 1); length = length - first; while (length > 0 && isspace(input[length - 1])) input[--length] = '\0'; } return input; } char *strip_quotes(char *input) { if (input != NULL) { char *ptr = strrchr(input, '\"'); if (ptr != NULL) *ptr = '\0'; if (input[0] == '\"') strcpy(input, input + 1); } return input; } int *get_int(int *output, int use) { int *value = NULL; char temp[132]; *output = use; if (trim(chomp(fgets(temp, 132, stdin))) != NULL) { if (strcmp(temp, "")) *output = atoi(temp); value = output; } return value; } /** This stores the previous settings */ #ifndef _WIN32 static struct termios oldtty; #else static DWORD oldtty; #endif static int mode = 0; /** This is called automatically on application exit to restore the previous tty settings. */ void term_exit(void) { if (mode == 1) { #ifndef _WIN32 tcsetattr(0, TCSANOW, &oldtty); #else HANDLE h = GetStdHandle(STD_INPUT_HANDLE); if (h) { SetConsoleMode(h, oldtty); } #endif mode = 0; } } /** Init terminal so that we can grab keys without blocking. */ void term_init() { #ifndef _WIN32 struct termios tty; tcgetattr(0, &tty); oldtty = tty; tty.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON); tty.c_oflag |= OPOST; tty.c_lflag &= ~(ECHO | ECHONL | ICANON | IEXTEN); tty.c_cflag &= ~(CSIZE | PARENB); tty.c_cflag |= CS8; tty.c_cc[VMIN] = 1; tty.c_cc[VTIME] = 0; tcsetattr(0, TCSANOW, &tty); #else HANDLE h = GetStdHandle(STD_INPUT_HANDLE); if (h) { DWORD tty; GetConsoleMode(h, &tty); oldtty = tty; SetConsoleMode(h, mode & ~(ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT)); } #endif mode = 1; atexit(term_exit); } /** Check for a keypress without blocking infinitely. Returns: ASCII value of keypress or -1 if no keypress detected. */ int term_read() { #ifndef _WIN32 int n = 1; unsigned char ch; struct timeval tv; fd_set rfds; FD_ZERO(&rfds); FD_SET(0, &rfds); tv.tv_sec = 0; tv.tv_usec = 40000; n = select(1, &rfds, NULL, NULL, &tv); if (n > 0) { n = read(0, &ch, 1); tcflush(0, TCIFLUSH); if (n == 1) return ch; return n; } #else HANDLE h = GetStdHandle(STD_INPUT_HANDLE); if (h && WaitForSingleObject(h, 0) == WAIT_OBJECT_0) { DWORD count; TCHAR c = 0; ReadConsole(h, &c, 1, &count, NULL); return (int) c; } else { struct timespec tm = {0, 40000000}; nanosleep(&tm, NULL); return 0; } #endif return -1; } char get_keypress() { char value = '\0'; int pressed = 0; fflush(stdout); term_init(); while ((pressed = term_read()) == -1) ; term_exit(); value = (char) pressed; return value; } void wait_for_any_key(char *message) { if (message == NULL) printf("Press any key to continue: "); else printf("%s", message); get_keypress(); printf("\n\n"); } void beep() { printf("%c", 7); fflush(stdout); } mlt-7.22.0/src/melt/io.h000664 000000 000000 00000002340 14531534050 014665 0ustar00rootroot000000 000000 /* * io.h -- melt input/output * Copyright (C) 2002-2014 Meltytech, LLC * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _DEMO_IO_H_ #define _DEMO_IO_H_ #ifdef __cplusplus extern "C" { #endif extern char *chomp(char *); extern char *trim(char *); extern char *strip_quotes(char *); extern char *get_string(char *, int, char *); extern int *get_int(int *, int); extern void term_init(); extern int term_read(); extern void term_exit(); extern char get_keypress(); extern void wait_for_any_key(char *); extern void beep(); #ifdef __cplusplus } #endif #endif mlt-7.22.0/src/melt/melt.c000664 000000 000000 00000126510 14531534050 015220 0ustar00rootroot000000 000000 /* * melt.c -- MLT command line utility * Copyright (C) 2002-2023 Meltytech, LLC * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #include #include #include #include #include #include #include #include #include #include #if (defined(__APPLE__) || defined(_WIN32) || defined(HAVE_SDL2)) && !defined(MELT_NOSDL) #include #endif #include "io.h" static mlt_producer melt = NULL; static void stop_handler(int signum) { if (melt) { mlt_properties properties = MLT_PRODUCER_PROPERTIES(melt); mlt_properties_set_int(properties, "done", 1); } } static void abnormal_exit_handler(int signum) { // The process is going down hard. Restore the terminal first. term_exit(); // Reset the default handler so the core gets dumped. signal(signum, SIG_DFL); raise(signum); } static void fire_jack_seek_event(mlt_properties jack, int position) { mlt_events_fire(jack, "jack-seek", mlt_event_data_from_int(position)); } static void transport_action(mlt_producer producer, char *value) { mlt_properties properties = MLT_PRODUCER_PROPERTIES(producer); mlt_multitrack multitrack = mlt_properties_get_data(properties, "multitrack", NULL); mlt_consumer consumer = mlt_properties_get_data(properties, "transport_consumer", NULL); mlt_properties jack = mlt_properties_get_data(MLT_CONSUMER_PROPERTIES(consumer), "jack_filter", NULL); mlt_position position = producer ? mlt_producer_position(producer) : 0; mlt_properties_set_int(properties, "stats_off", 1); if (strlen(value) == 1) { switch (value[0]) { case 'q': case 'Q': mlt_properties_set_int(properties, "done", 1); mlt_events_fire(jack, "jack-stop", mlt_event_data_none()); break; case '0': position = 0; mlt_producer_set_speed(producer, 1); mlt_producer_seek(producer, position); mlt_consumer_purge(consumer); fire_jack_seek_event(jack, position); break; case '1': mlt_producer_set_speed(producer, -10); break; case '2': mlt_producer_set_speed(producer, -5); break; case '3': mlt_producer_set_speed(producer, -2); break; case '4': mlt_producer_set_speed(producer, -1); break; case '5': mlt_producer_set_speed(producer, 0); mlt_consumer_purge(consumer); mlt_producer_seek(producer, mlt_consumer_position(consumer) + 1); mlt_events_fire(jack, "jack-stop", mlt_event_data_none()); break; case '6': case ' ': if (!jack || mlt_producer_get_speed(producer) != 0) mlt_producer_set_speed(producer, 1); mlt_consumer_purge(consumer); mlt_events_fire(jack, "jack-start", mlt_event_data_none()); break; case '7': mlt_producer_set_speed(producer, 2); break; case '8': mlt_producer_set_speed(producer, 5); break; case '9': mlt_producer_set_speed(producer, 10); break; case 'd': if (multitrack != NULL) { int i = 0; mlt_position last = -1; fprintf(stderr, "\n"); for (i = 0; 1; i++) { position = mlt_multitrack_clip(multitrack, mlt_whence_relative_start, i); if (position == last) break; last = position; fprintf(stderr, "%d: %d\n", i, (int) position); } } break; case 'g': if (multitrack != NULL) { position = mlt_multitrack_clip(multitrack, mlt_whence_relative_current, 0); mlt_producer_seek(producer, position); mlt_consumer_purge(consumer); fire_jack_seek_event(jack, position); } break; case 'H': if (producer != NULL) { position -= mlt_producer_get_fps(producer) * 60; mlt_consumer_purge(consumer); mlt_producer_seek(producer, position); fire_jack_seek_event(jack, position); } break; case 'h': if (producer != NULL) { position--; mlt_producer_set_speed(producer, 0); mlt_consumer_purge(consumer); mlt_producer_seek(producer, position); mlt_events_fire(jack, "jack-stop", mlt_event_data_none()); fire_jack_seek_event(jack, position); } break; case 'j': if (multitrack != NULL) { position = mlt_multitrack_clip(multitrack, mlt_whence_relative_current, 1); mlt_consumer_purge(consumer); mlt_producer_seek(producer, position); fire_jack_seek_event(jack, position); } break; case 'k': if (multitrack != NULL) { position = mlt_multitrack_clip(multitrack, mlt_whence_relative_current, -1); mlt_consumer_purge(consumer); mlt_producer_seek(producer, position); fire_jack_seek_event(jack, position); } break; case 'l': if (producer != NULL) { position++; mlt_consumer_purge(consumer); if (mlt_producer_get_speed(producer) != 0) { mlt_producer_set_speed(producer, 0); mlt_events_fire(jack, "jack-stop", mlt_event_data_none()); } else { mlt_producer_seek(producer, position); fire_jack_seek_event(jack, position); } } break; case 'L': if (producer != NULL) { position += mlt_producer_get_fps(producer) * 60; mlt_consumer_purge(consumer); mlt_producer_seek(producer, position); fire_jack_seek_event(jack, position); } break; } mlt_properties_set_int(MLT_CONSUMER_PROPERTIES(consumer), "refresh", 1); } mlt_properties_set_int(properties, "stats_off", 0); } static void on_jack_started(mlt_properties owner, mlt_consumer consumer, mlt_event_data event_data) { mlt_producer producer = mlt_properties_get_data(MLT_CONSUMER_PROPERTIES(consumer), "transport_producer", NULL); if (producer) { if (mlt_producer_get_speed(producer) != 0) { mlt_properties jack = mlt_properties_get_data(MLT_CONSUMER_PROPERTIES(consumer), "jack_filter", NULL); mlt_events_fire(jack, "jack-stop", mlt_event_data_none()); } else { mlt_position position = mlt_event_data_to_int(event_data); mlt_producer_set_speed(producer, 1); mlt_consumer_purge(consumer); mlt_producer_seek(producer, position); mlt_properties_set_int(MLT_CONSUMER_PROPERTIES(consumer), "refresh", 1); } } } static void on_jack_stopped(mlt_properties owner, mlt_consumer consumer, mlt_event_data event_data) { mlt_producer producer = mlt_properties_get_data(MLT_CONSUMER_PROPERTIES(consumer), "transport_producer", NULL); if (producer) { mlt_position position = mlt_event_data_to_int(event_data); mlt_producer_set_speed(producer, 0); mlt_consumer_purge(consumer); mlt_producer_seek(producer, position); mlt_properties_set_int(MLT_CONSUMER_PROPERTIES(consumer), "refresh", 1); } } static void setup_jack_transport(mlt_consumer consumer, mlt_profile profile) { mlt_properties properties = MLT_CONSUMER_PROPERTIES(consumer); mlt_filter jack = mlt_factory_filter(profile, "jackrack", NULL); mlt_properties jack_properties = MLT_FILTER_PROPERTIES(jack); mlt_service_attach(MLT_CONSUMER_SERVICE(consumer), jack); mlt_properties_set_int(properties, "audio_off", 1); mlt_properties_set_data(properties, "jack_filter", jack, 0, (mlt_destructor) mlt_filter_close, NULL); // mlt_properties_set( jack_properties, "out_1", "system:playback_1" ); // mlt_properties_set( jack_properties, "out_2", "system:playback_2" ); mlt_events_listen(jack_properties, consumer, "jack-started", (mlt_listener) on_jack_started); mlt_events_listen(jack_properties, consumer, "jack-stopped", (mlt_listener) on_jack_stopped); } static mlt_consumer create_consumer(mlt_profile profile, char *id) { char *myid = id ? strdup(id) : NULL; char *arg = myid ? strchr(myid, ':') : NULL; if (arg != NULL) *arg++ = '\0'; mlt_consumer consumer = mlt_factory_consumer(profile, myid, arg); if (consumer != NULL) { mlt_properties properties = MLT_CONSUMER_PROPERTIES(consumer); mlt_properties_set_data(properties, "transport_callback", transport_action, 0, NULL, NULL); } free(myid); return consumer; } static int load_consumer(mlt_consumer *consumer, mlt_profile profile, int argc, char **argv) { int i; int multi = 0; int qglsl = 0; for (i = 1; i < argc; i++) { // See if we need multi consumer. multi += !strcmp(argv[i], "-consumer"); // Seee if we need the qglsl variant of multi consumer. if (!strncmp(argv[i], "glsl.", 5) || !strncmp(argv[i], "movit.", 6)) qglsl = 1; #if SDL_MAJOR_VERSION == 2 if (!strcmp("sdl", argv[i]) || !strcmp("sdl_audio", argv[i]) || !strcmp("sdl_preview", argv[i]) || !strcmp("sdl_still", argv[i])) { fprintf(stderr, "Error: This program was linked against SDL2, which is incompatible with\nSDL1 " "consumers. Aborting.\n"); return EXIT_FAILURE; } #endif } // Disable qglsl if xgl is being used! for (i = 1; qglsl && i < argc; i++) if (!strcmp(argv[i], "xgl")) qglsl = 0; if (multi > 1 || qglsl) { // If there is more than one -consumer use the 'multi' consumer. int k = 0; char key[20]; if (*consumer) mlt_consumer_close(*consumer); *consumer = create_consumer(profile, (qglsl ? "qglsl" : "multi")); mlt_properties properties = MLT_CONSUMER_PROPERTIES(*consumer); for (i = 1; i < argc; i++) { if (!strcmp(argv[i], "-consumer") && argv[i + 1]) { // Create a properties object for each sub-consumer mlt_properties new_props = mlt_properties_new(); snprintf(key, sizeof(key), "%d", k++); mlt_properties_set_data(properties, key, new_props, 0, (mlt_destructor) mlt_properties_close, NULL); if (strchr(argv[i + 1], ':')) { char *temp = strdup(argv[++i]); char *service = temp; char *target = strchr(temp, ':'); *target++ = 0; mlt_properties_set(new_props, "mlt_service", service); mlt_properties_set(new_props, "target", target); } else { mlt_properties_set(new_props, "mlt_service", argv[++i]); } while (argv[i + 1] && strchr(argv[i + 1], '=')) mlt_properties_parse(new_props, argv[++i]); } } } else for (i = 1; i < argc; i++) { if (!strcmp(argv[i], "-consumer")) { if (*consumer) mlt_consumer_close(*consumer); *consumer = create_consumer(profile, argv[++i]); if (*consumer) { mlt_properties properties = MLT_CONSUMER_PROPERTIES(*consumer); while (argv[i + 1] != NULL && strchr(argv[i + 1], '=')) mlt_properties_parse(properties, argv[++i]); } } } return EXIT_SUCCESS; } #if defined(SDL_MAJOR_VERSION) static void event_handling(mlt_producer producer, mlt_consumer consumer) { SDL_Event event; while (SDL_PollEvent(&event)) { switch (event.type) { case SDL_QUIT: mlt_properties_set_int(MLT_PRODUCER_PROPERTIES(producer), "done", 1); break; case SDL_KEYDOWN: #if SDL_MAJOR_VERSION == 2 if (event.key.keysym.sym < 0x80 && event.key.keysym.sym > 0) { char keyboard[2] = {event.key.keysym.sym, 0}; if (event.key.keysym.mod & KMOD_SHIFT) keyboard[0] += 'A' - 'a'; transport_action(producer, keyboard); } break; case SDL_WINDOWEVENT: if (mlt_properties_get(MLT_CONSUMER_PROPERTIES(consumer), "mlt_service") && !strcmp("sdl2", mlt_properties_get(MLT_CONSUMER_PROPERTIES(consumer), "mlt_service"))) if (event.window.event == SDL_WINDOWEVENT_RESIZED || event.window.event == SDL_WINDOWEVENT_SIZE_CHANGED) { mlt_properties_set_int(MLT_CONSUMER_PROPERTIES(consumer), "window_width", event.window.data1); mlt_properties_set_int(MLT_CONSUMER_PROPERTIES(consumer), "window_height", event.window.data2); } break; #else if (event.key.keysym.unicode < 0x80 && event.key.keysym.unicode > 0) { char keyboard[2] = {event.key.keysym.unicode, 0}; transport_action(producer, keyboard); } break; #endif } } } #endif static void transport(mlt_producer producer, mlt_consumer consumer) { mlt_properties properties = MLT_PRODUCER_PROPERTIES(producer); int silent = mlt_properties_get_int(MLT_CONSUMER_PROPERTIES(consumer), "silent"); int progress = mlt_properties_get_int(MLT_CONSUMER_PROPERTIES(consumer), "progress"); int is_getc = mlt_properties_get_int(MLT_CONSUMER_PROPERTIES(consumer), "melt_getc"); struct timespec tm = {0, 40000000}; int total_length = mlt_producer_get_playtime(producer); int last_position = 0; if (mlt_properties_get_int(properties, "done") == 0 && !mlt_consumer_is_stopped(consumer)) { if (!silent && !progress) { if (!is_getc) term_init(); fprintf(stderr, "+-----+ +-----+ +-----+ +-----+ +-----+ +-----+ +-----+ +-----+ +-----+\n"); fprintf(stderr, "|1=-10| |2= -5| |3= -2| |4= -1| |5= 0| |6= 1| |7= 2| |8= 5| |9= 10|\n"); fprintf(stderr, "+-----+ +-----+ +-----+ +-----+ +-----+ +-----+ +-----+ +-----+ +-----+\n"); fprintf(stderr, "+---------------------------------------------------------------------+\n"); fprintf(stderr, "| H = back 1 minute, L = forward 1 minute |\n"); fprintf(stderr, "| h = previous frame, l = next frame |\n"); fprintf(stderr, "| g = start of clip, j = next clip, k = previous clip |\n"); fprintf(stderr, "| 0 = restart, q = quit, space = play |\n"); fprintf(stderr, "+---------------------------------------------------------------------+\n"); } while (mlt_properties_get_int(properties, "done") == 0 && !mlt_consumer_is_stopped(consumer)) { int value = (silent || progress || is_getc) ? -1 : term_read(); if (is_getc) { value = getc(stdin); value = (value == EOF) ? 'q' : value; } if (value != -1) { char string[2] = {value, 0}; transport_action(producer, string); } #if defined(SDL_MAJOR_VERSION) event_handling(producer, consumer); #endif if (!silent && mlt_properties_get_int(properties, "stats_off") == 0) { if (progress) { int current_position = mlt_producer_position(producer); if (current_position > last_position) { fprintf(stderr, "Current Frame: %10d, percentage: %10d%c", current_position, 100 * current_position / total_length, progress == 2 ? '\n' : '\r'); last_position = current_position; } } else { fprintf(stderr, "Current Position: %10d\r", (int) mlt_consumer_position(consumer)); } fflush(stderr); } if (silent || progress) nanosleep(&tm, NULL); } if (!silent) fprintf(stderr, "\n"); } } static void show_usage(char *program_name) { fprintf(stdout, "Usage: %s [options] [producer [name=value]* ]+\n" "Options:\n" " -attach filter[:arg] [name=value]* Attach a filter to the output\n" " -attach-cut filter[:arg] [name=value]* Attach a filter to a cut\n" " -attach-track filter[:arg] [name=value]* Attach a filter to a track\n" " -attach-clip filter[:arg] [name=value]* Attach a filter to a producer\n" " -audio-track | -hide-video Add an audio-only track\n" " -blank frames Add blank silence to a track\n" " -chain id[:arg] [name=value]* Add a producer as a chain\n" " -consumer id[:arg] [name=value]* Set the consumer (sink)\n" " -debug Set the logging level to debug\n" " -filter filter[:arg] [name=value]* Add a filter to the current track\n" " -getc Get keyboard input using getc\n" " -group [name=value]* Apply properties repeatedly\n" " -help Show this message\n" " -jack Enable JACK transport synchronization\n" " -join clips Join multiple clips into one cut\n" " -link id[:arg] [name=value]* Add a link to a chain\n" " -mix length Add a mix between the last two cuts\n" " -mixer transition Add a transition to the mix\n" " -null-track | -hide-track Add a hidden track\n" " -profile name Set the processing settings\n" " -progress Display progress along with position\n" " -query List all of the registered services\n" " -query \"consumers\" | \"consumer\"=id List consumers or show info about one\n" " -query \"filters\" | \"filter\"=id List filters or show info about one\n" " -query \"links\" | \"link\"=id List links or show info about one\n" " -query \"producers\" | \"producer\"=id List producers or show info about one\n" " -query \"transitions\" | \"transition\"=id List transitions, show info about one\n" " -query \"profiles\" | \"profile\"=id List profiles, show info about one\n" " -query \"presets\" | \"preset\"=id List presets, show info about one\n" " -query \"formats\" List audio/video formats\n" " -query \"audio_codecs\" List audio codecs\n" " -query \"video_codecs\" List video codecs\n" " -quiet Set the logging level to quiet\n" " -remove Remove the most recent cut\n" " -repeat times Repeat the last cut\n" " -repository path Set the directory of MLT modules\n" " -serialise [filename] Write the commands to a text file\n" " -setlocale Make numeric strings locale-sensitive\n" " -silent Do not display position/transport\n" " -split relative-frame Split the last cut into two cuts\n" " -swap Rearrange the last two cuts\n" " -track Add a track\n" " -transition id[:arg] [name=value]* Add a transition\n" " -verbose Set the logging level to verbose\n" " -timings Set the logging level to timings\n" " -version Show the version and copyright\n" " -video-track | -hide-audio Add a video-only track\n" "For more help: \n", basename(program_name)); } static void query_metadata(mlt_repository repo, mlt_service_type type, const char *typestr, char *id) { mlt_properties metadata = mlt_repository_metadata(repo, type, id); if (metadata) { char *s = mlt_properties_serialise_yaml(metadata); fprintf(stdout, "%s", s); free(s); } else { fprintf(stdout, "# No metadata for %s \"%s\"\n", typestr, id); } } static int is_service_hidden(mlt_repository repo, mlt_service_type type, const char *service_name) { mlt_properties metadata = NULL; mlt_properties tags = NULL; metadata = mlt_repository_metadata(repo, type, service_name); if (metadata) { tags = mlt_properties_get_data(metadata, "tags", NULL); if (tags) { int k; for (k = 0; k < mlt_properties_count(tags); k++) { const char *value = mlt_properties_get_value(tags, k); if (!strcmp("Hidden", value)) { return 1; } } } } return 0; } static void query_services(mlt_repository repo, mlt_service_type type) { mlt_properties services = NULL; const char *typestr = NULL; switch (type) { case mlt_service_consumer_type: services = mlt_repository_consumers(repo); typestr = "consumers"; break; case mlt_service_filter_type: services = mlt_repository_filters(repo); typestr = "filters"; break; case mlt_service_link_type: services = mlt_repository_links(repo); typestr = "links"; break; case mlt_service_producer_type: services = mlt_repository_producers(repo); typestr = "producers"; break; case mlt_service_transition_type: services = mlt_repository_transitions(repo); typestr = "transitions"; break; default: return; } fprintf(stdout, "---\n%s:\n", typestr); if (services) { int j; for (j = 0; j < mlt_properties_count(services); j++) { const char *service_name = mlt_properties_get_name(services, j); if (!is_service_hidden(repo, type, service_name)) fprintf(stdout, " - %s\n", service_name); } } fprintf(stdout, "...\n"); } static void query_profiles() { mlt_properties profiles = mlt_profile_list(); fprintf(stdout, "---\nprofiles:\n"); if (profiles) { int j; for (j = 0; j < mlt_properties_count(profiles); j++) fprintf(stdout, " - %s\n", mlt_properties_get_name(profiles, j)); } fprintf(stdout, "...\n"); mlt_properties_close(profiles); } static void query_profile(const char *id) { mlt_properties profiles = mlt_profile_list(); mlt_properties profile = mlt_properties_get_data(profiles, id, NULL); if (profile) { char *s = mlt_properties_serialise_yaml(profile); fprintf(stdout, "%s", s); free(s); } else { fprintf(stdout, "# No metadata for profile \"%s\"\n", id); } mlt_properties_close(profiles); } static void query_presets() { mlt_properties presets = mlt_repository_presets(); fprintf(stdout, "---\npresets:\n"); if (presets) { int j; for (j = 0; j < mlt_properties_count(presets); j++) fprintf(stdout, " - %s\n", mlt_properties_get_name(presets, j)); } fprintf(stdout, "...\n"); mlt_properties_close(presets); } static void query_preset(const char *id) { mlt_properties presets = mlt_repository_presets(); mlt_properties preset = mlt_properties_get_data(presets, id, NULL); if (preset) { char *s = mlt_properties_serialise_yaml(preset); fprintf(stdout, "%s", s); free(s); } else { fprintf(stdout, "# No metadata for preset \"%s\"\n", id); } mlt_properties_close(presets); } static void query_formats() { mlt_consumer consumer = mlt_factory_consumer(NULL, "avformat", NULL); if (consumer) { mlt_properties_set(MLT_CONSUMER_PROPERTIES(consumer), "f", "list"); mlt_consumer_start(consumer); mlt_consumer_close(consumer); } else { fprintf(stdout, "# No formats - failed to load avformat consumer\n"); } } static void query_acodecs() { mlt_consumer consumer = mlt_factory_consumer(NULL, "avformat", NULL); if (consumer) { mlt_properties_set(MLT_CONSUMER_PROPERTIES(consumer), "acodec", "list"); mlt_consumer_start(consumer); mlt_consumer_close(consumer); } else { fprintf(stdout, "# No audio codecs - failed to load avformat consumer\n"); } } static void query_vcodecs() { mlt_consumer consumer = mlt_factory_consumer(NULL, "avformat", NULL); if (consumer) { mlt_properties_set(MLT_CONSUMER_PROPERTIES(consumer), "vcodec", "list"); mlt_consumer_start(consumer); mlt_consumer_close(consumer); } else { fprintf(stdout, "# No video codecs - failed to load avformat consumer\n"); } } static void on_fatal_error(mlt_properties owner, mlt_consumer consumer) { mlt_properties_set_int(MLT_CONSUMER_PROPERTIES(consumer), "done", 1); mlt_properties_set_int(MLT_CONSUMER_PROPERTIES(consumer), "melt_error", 1); } static void set_preview_scale(mlt_profile *profile, mlt_profile *backup_profile, double scale) { *backup_profile = mlt_profile_clone(*profile); if (*backup_profile) { mlt_profile temp = *profile; *profile = *backup_profile; *backup_profile = temp; (*profile)->width *= scale; (*profile)->width -= (*profile)->width % 2; (*profile)->height *= scale; (*profile)->height -= (*profile)->height % 2; } } static mlt_repository setup_factory(const char *repo_path, int set_locale) { mlt_repository repo = mlt_factory_init(repo_path); if (repo && set_locale) { // Load the system locales const char *locale = ""; #if defined(_WIN32) if (getenv("LC_ALL")) locale = getenv("LC_ALL"); #endif setlocale(LC_ALL, locale); } return repo; } int main(int argc, char **argv) { int i; mlt_consumer consumer = NULL; FILE *store = NULL; char *name = NULL; mlt_profile profile = NULL; int is_progress = 0; int is_silent = 0; int is_abort = 0; int is_getc = 0; int error = 0; mlt_profile backup_profile; mlt_repository repo = NULL; const char *repo_path = NULL; int is_consumer_explicit = 0; int is_setlocale = 0; // Handle abnormal exit situations. signal(SIGSEGV, abnormal_exit_handler); signal(SIGILL, abnormal_exit_handler); signal(SIGABRT, abnormal_exit_handler); for (i = 1; i < argc; i++) { if (!strcmp(argv[i], "-setlocale")) { is_setlocale = 1; break; } } for (i = 1; i < argc; i++) { // Check for serialisation switch if (!strcmp(argv[i], "-serialise")) { name = argv[++i]; if (name != NULL && strstr(name, ".melt")) store = fopen(name, "w"); else { if (name == NULL || name[0] == '-') store = stdout; name = NULL; } } // Look for the profile option else if (!strcmp(argv[i], "-profile")) { // Construct the factory if (!repo) repo = setup_factory(repo_path, is_setlocale); const char *pname = argv[++i]; if (pname && pname[0] != '-') profile = mlt_profile_init(pname); } else if (!strcmp(argv[i], "-progress")) { is_progress = 1; } else if (!strcmp(argv[i], "-progress2")) { is_progress = 2; } // Look for the query option else if (!strcmp(argv[i], "-query")) { // Construct the factory if (!repo) repo = setup_factory(repo_path, is_setlocale); const char *pname = argv[++i]; if (pname && pname[0] != '-') { if (!strcmp(pname, "consumers") || !strcmp(pname, "consumer")) query_services(repo, mlt_service_consumer_type); else if (!strcmp(pname, "filters") || !strcmp(pname, "filter")) query_services(repo, mlt_service_filter_type); else if (!strcmp(pname, "links") || !strcmp(pname, "link")) query_services(repo, mlt_service_link_type); else if (!strcmp(pname, "producers") || !strcmp(pname, "producer")) query_services(repo, mlt_service_producer_type); else if (!strcmp(pname, "transitions") || !strcmp(pname, "transition")) query_services(repo, mlt_service_transition_type); else if (!strcmp(pname, "profiles") || !strcmp(pname, "profile")) query_profiles(); else if (!strcmp(pname, "presets") || !strcmp(pname, "preset")) query_presets(); else if (!strncmp(pname, "format", 6)) query_formats(); else if (!strncmp(pname, "acodec", 6) || !strcmp(pname, "audio_codecs")) query_acodecs(); else if (!strncmp(pname, "vcodec", 6) || !strcmp(pname, "video_codecs")) query_vcodecs(); else if (!strncmp(pname, "consumer=", 9)) query_metadata(repo, mlt_service_consumer_type, "consumer", strchr(pname, '=') + 1); else if (!strncmp(pname, "filter=", 7)) query_metadata(repo, mlt_service_filter_type, "filter", strchr(pname, '=') + 1); else if (!strncmp(pname, "link=", 5)) query_metadata(repo, mlt_service_link_type, "link", strchr(pname, '=') + 1); else if (!strncmp(pname, "producer=", 9)) query_metadata(repo, mlt_service_producer_type, "producer", strchr(pname, '=') + 1); else if (!strncmp(pname, "transition=", 11)) query_metadata(repo, mlt_service_transition_type, "transition", strchr(pname, '=') + 1); else if (!strncmp(pname, "profile=", 8)) query_profile(strchr(pname, '=') + 1); else if (!strncmp(pname, "preset=", 7)) query_preset(strchr(pname, '=') + 1); else goto query_all; } else { query_all: query_services(repo, mlt_service_consumer_type); query_services(repo, mlt_service_filter_type); query_services(repo, mlt_service_link_type); query_services(repo, mlt_service_producer_type); query_services(repo, mlt_service_transition_type); fprintf(stdout, "# You can query the metadata for a specific service using:\n" "# -query =\n" "# where is one of: consumer, filter, producer, or transition.\n"); } goto exit_factory; } else if (!strcmp(argv[i], "-silent")) { is_silent = 1; } else if (!strcmp(argv[i], "-quiet")) { is_silent = 1; mlt_log_set_level(MLT_LOG_QUIET); } else if (!strcmp(argv[i], "-verbose")) { mlt_log_set_level(MLT_LOG_VERBOSE); } else if (!strcmp(argv[i], "-timings")) { mlt_log_set_level(MLT_LOG_TIMINGS); } else if (!strcmp(argv[i], "-version") || !strcmp(argv[i], "--version")) { fprintf(stdout, "%s " VERSION "\n" "Copyright (C) 2002-2022 Meltytech, LLC\n" "\n" "This is free software; see the source for copying conditions. There is NO\n" "warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n", basename(argv[0])); goto exit_factory; } else if (!strcmp(argv[i], "-debug")) { mlt_log_set_level(MLT_LOG_DEBUG); } else if (!strcmp(argv[i], "-abort")) { is_abort = 1; } else if (!strcmp(argv[i], "-getc")) { is_getc = 1; } else if (!repo && !strcmp(argv[i], "-repository")) { if (i + 1 < argc && argv[i + 1][0] != '-') repo_path = argv[++i]; } else if (!strcmp(argv[i], "-consumer")) { is_consumer_explicit = 1; } } if (!is_silent && !isatty(STDIN_FILENO) && !is_progress) is_progress = 1; // Construct the factory if (!repo) repo = setup_factory(repo_path, is_setlocale); // Create profile if not set explicitly if (getenv("MLT_PROFILE")) profile = mlt_profile_init(NULL); if (profile == NULL) profile = mlt_profile_init(NULL); else profile->is_explicit = 1; // Look for the consumer option to load profile settings from consumer properties backup_profile = mlt_profile_clone(profile); if (load_consumer(&consumer, profile, argc, argv) != EXIT_SUCCESS) goto exit_factory; // If the consumer changed the profile, then it is explicit. if (backup_profile && !profile->is_explicit && (profile->width != backup_profile->width || profile->height != backup_profile->height || profile->sample_aspect_num != backup_profile->sample_aspect_num || profile->sample_aspect_den != backup_profile->sample_aspect_den || profile->frame_rate_den != backup_profile->frame_rate_den || profile->frame_rate_num != backup_profile->frame_rate_num || profile->colorspace != backup_profile->colorspace)) profile->is_explicit = 1; mlt_profile_close(backup_profile); backup_profile = NULL; // Get melt producer if (argc > 1) melt = mlt_factory_producer(profile, "melt", &argv[1]); if (melt) { // Generate an automatic profile if needed. if (!profile->is_explicit) { mlt_producer first_producer = mlt_properties_get_data(MLT_PRODUCER_PROPERTIES(melt), "first_producer", NULL); mlt_profile_from_producer(profile, first_producer); mlt_consumer melt_consumer = MLT_CONSUMER( mlt_service_consumer(MLT_PRODUCER_SERVICE(melt))); if (melt_consumer) mlt_consumer_connect(melt_consumer, NULL); mlt_producer_close(melt); melt = mlt_factory_producer(profile, "melt", &argv[1]); } double scale = mlt_properties_get_double(MLT_CONSUMER_PROPERTIES(consumer), "scale"); if (scale > 0.0) { set_preview_scale(&profile, &backup_profile, scale); } // Reload the consumer with the fully qualified profile. // The producer or auto-profile could have changed the profile. load_consumer(&consumer, profile, argc, argv); // See if producer has consumer already attached if (!store && !consumer) { consumer = MLT_CONSUMER(mlt_service_consumer(MLT_PRODUCER_SERVICE(melt))); if (consumer) { mlt_properties_inc_ref( MLT_CONSUMER_PROPERTIES(consumer)); // because we explicitly close it mlt_properties_set_data(MLT_CONSUMER_PROPERTIES(consumer), "transport_callback", transport_action, 0, NULL, NULL); } } // If we have no consumer, default to sdl if (store == NULL && consumer == NULL) consumer = create_consumer(profile, NULL); } // Set transport properties on consumer and produder if (consumer != NULL && melt != NULL) { mlt_properties_set_data(MLT_CONSUMER_PROPERTIES(consumer), "transport_producer", melt, 0, NULL, NULL); mlt_properties_set_data(MLT_PRODUCER_PROPERTIES(melt), "transport_consumer", consumer, 0, NULL, NULL); if (is_progress) mlt_properties_set_int(MLT_CONSUMER_PROPERTIES(consumer), "progress", is_progress); if (is_silent) mlt_properties_set_int(MLT_CONSUMER_PROPERTIES(consumer), "silent", is_silent); if (is_getc) mlt_properties_set_int(MLT_CONSUMER_PROPERTIES(consumer), "melt_getc", is_getc); } if (argc > 1 && melt != NULL && mlt_producer_get_length(melt) > 0) { // Parse the arguments for (i = 1; i < argc; i++) { if (!strcmp(argv[i], "-jack") && consumer) { setup_jack_transport(consumer, profile); } else if (!strcmp(argv[i], "-serialise")) { if (store != stdout) i++; } else { if (store != NULL) fprintf(store, "%s\n", argv[i]); i++; while (argv[i] != NULL && argv[i][0] != '-') { if (store != NULL) fprintf(store, "%s\n", argv[i]); i += 1; } i--; } } if (consumer != NULL && store == NULL) { // Get melt's properties mlt_properties melt_props = MLT_PRODUCER_PROPERTIES(melt); mlt_properties properties = MLT_CONSUMER_PROPERTIES(consumer); if (is_consumer_explicit) { // Apply group settings mlt_properties group = mlt_properties_get_data(melt_props, "group", 0); mlt_properties_inherit(properties, group); } int in = mlt_properties_get_int(properties, "in"); int out = mlt_properties_get_int(properties, "out"); if (in > 0 || out > 0) { if (out == 0) { out = mlt_producer_get_length(melt) - 1; } mlt_producer_set_in_and_out(melt, in, out); mlt_producer_seek(melt, 0); } // Connect consumer to melt mlt_consumer_connect(consumer, MLT_PRODUCER_SERVICE(melt)); // Start the consumer mlt_events_listen(properties, consumer, "consumer-fatal-error", (mlt_listener) on_fatal_error); if (mlt_consumer_start(consumer) == 0) { // Try to exit gracefully upon these signals signal(SIGINT, stop_handler); signal(SIGTERM, stop_handler); #ifndef _WIN32 signal(SIGHUP, stop_handler); signal(SIGPIPE, stop_handler); #endif // Transport functionality transport(melt, consumer); // Stop the consumer mlt_consumer_stop(consumer); } } else if (store != NULL && store != stdout && name != NULL) { fprintf(stderr, "Project saved as %s.\n", name); fclose(store); } } else { show_usage(argv[0]); } // Disconnect producer from consumer to prevent ref cycles from closing services if (consumer) { error = mlt_properties_get_int(MLT_CONSUMER_PROPERTIES(consumer), "melt_error"); mlt_consumer_connect(consumer, NULL); if (!is_abort) mlt_events_fire(MLT_CONSUMER_PROPERTIES(consumer), "consumer-cleanup", mlt_event_data_none()); } if (is_abort) return error; // Close the producer if (melt != NULL) mlt_producer_close(melt); // Close the consumer if (consumer != NULL) mlt_consumer_close(consumer); // Close the factory mlt_profile_close(profile); mlt_profile_close(backup_profile); exit_factory: // Workaround qmelt on OS X from crashing at exit. #if !defined(__MACH__) || !defined(QT_GUI_LIB) mlt_factory_close(); #endif return error; } mlt-7.22.0/src/mlt++/000775 000000 000000 00000000000 14531534050 014067 5ustar00rootroot000000 000000 mlt-7.22.0/src/mlt++/CMakeLists.txt000664 000000 000000 00000005251 14531534050 016632 0ustar00rootroot000000 000000 set(MLTPP_PUBLIC_HEADERS Mlt.h MltAnimation.h MltAudio.h MltChain.h MltConfig.h MltConsumer.h MltDeque.h MltEvent.h MltFactory.h MltField.h MltFilter.h MltFilteredConsumer.h MltFilteredProducer.h MltFrame.h MltImage.h MltLink.h MltMultitrack.h MltParser.h MltPlaylist.h MltProducer.h MltProfile.h MltProperties.h MltPushConsumer.h MltRepository.h MltService.h MltTokeniser.h MltTractor.h MltTransition.h ) add_library(mlt++ SHARED MltAnimation.cpp MltAudio.cpp MltChain.cpp MltConsumer.cpp MltDeque.cpp MltEvent.cpp MltFactory.cpp MltField.cpp MltFilter.cpp MltFilteredConsumer.cpp MltFilteredProducer.cpp MltFrame.cpp MltImage.cpp MltLink.cpp MltMultitrack.cpp MltParser.cpp MltPlaylist.cpp MltProducer.cpp MltProfile.cpp MltProperties.cpp MltPushConsumer.cpp MltRepository.cpp MltService.cpp MltTokeniser.cpp MltTractor.cpp MltTransition.cpp ) add_custom_target("Other_mlt++_Files" SOURCES mlt++.pc.in mlt++.vers ) add_library(Mlt${MLT_VERSION_MAJOR}::mlt++ ALIAS mlt++) target_sources(mlt++ PRIVATE ${MLTPP_PUBLIC_HEADERS}) target_compile_options(mlt++ PRIVATE ${MLT_COMPILE_OPTIONS}) target_link_libraries(mlt++ PUBLIC mlt) target_include_directories(mlt++ PUBLIC $ $ ) set_target_properties(mlt++ PROPERTIES VERSION ${MLT_VERSION} SOVERSION ${MLT_VERSION_MAJOR} OUTPUT_NAME mlt++-${MLT_VERSION_MAJOR} PUBLIC_HEADER "${MLTPP_PUBLIC_HEADERS}" ) if(WIN32) if(MINGW) install(FILES "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/libmlt++-${MLT_VERSION_MAJOR}.dll" DESTINATION ${CMAKE_INSTALL_LIBDIR} RENAME libmlt++.dll ) target_link_options(mlt++ PRIVATE -Wl,--output-def,libmlt++.def) install(FILES "${CMAKE_BINARY_DIR}/libmlt++.def" DESTINATION ${CMAKE_INSTALL_LIBDIR}) endif() target_compile_definitions(mlt++ PRIVATE MLTPP_EXPORTS) endif() if(NOT (WIN32 OR APPLE)) target_link_options(mlt++ PRIVATE -Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/mlt++.vers) set_target_properties(mlt++ PROPERTIES LINK_DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/mlt++.vers) endif() install(TARGETS mlt++ EXPORT MltTargets RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/mlt-${MLT_VERSION_MAJOR}/mlt++ ) configure_file(mlt++.pc.in mlt++-${MLT_VERSION_MAJOR}.pc @ONLY) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/mlt++-${MLT_VERSION_MAJOR}.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig COMPONENT Development ) mlt-7.22.0/src/mlt++/Mlt.h000664 000000 000000 00000003001 14531534050 014766 0ustar00rootroot000000 000000 /** * Mlt.h - Convenience header file for all mlt++ objects * Copyright (C) 2004-2015 Meltytech, LLC * Author: Charles Yates * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef MLTPP_H #define MLTPP_H #include "MltAnimation.h" #include "MltAudio.h" #include "MltChain.h" #include "MltConsumer.h" #include "MltDeque.h" #include "MltEvent.h" #include "MltFactory.h" #include "MltField.h" #include "MltFilter.h" #include "MltFilteredConsumer.h" #include "MltFrame.h" #include "MltImage.h" #include "MltMultitrack.h" #include "MltParser.h" #include "MltPlaylist.h" #include "MltProducer.h" #include "MltProfile.h" #include "MltProperties.h" #include "MltPushConsumer.h" #include "MltRepository.h" #include "MltService.h" #include "MltTokeniser.h" #include "MltTractor.h" #include "MltTransition.h" #endif mlt-7.22.0/src/mlt++/MltAnimation.cpp000664 000000 000000 00000014713 14531534050 017175 0ustar00rootroot000000 000000 /** * MltAnimation.cpp - MLT Wrapper * Copyright (C) 2015-2021 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "MltAnimation.h" #include using namespace Mlt; Animation::Animation() : instance(0) {} Animation::Animation(mlt_animation animation) : instance(animation) {} Animation::Animation(const Animation &animation) : instance(animation.instance) {} Animation::~Animation() { // Do not call mlt_animation_close() because mlt_animation is not reference- // counted, and typically a mlt_properties owns it. instance = 0; } bool Animation::is_valid() const { return instance != 0; } mlt_animation Animation::get_animation() const { return instance; } Animation &Animation::operator=(const Animation &animation) { if (this != &animation) { instance = animation.instance; } return *this; } int Animation::length() { return mlt_animation_get_length(instance); } int Animation::get_item(int position, bool &is_key, mlt_keyframe_type &type) { struct mlt_animation_item_s item; item.property = NULL; int error = mlt_animation_get_item(instance, &item, position); if (!error) { is_key = item.is_key; type = item.keyframe_type; } return error; } bool Animation::is_key(int position) { struct mlt_animation_item_s item; item.is_key = 0; item.property = NULL; mlt_animation_get_item(instance, &item, position); return item.is_key; } mlt_keyframe_type Animation::keyframe_type(int position) { struct mlt_animation_item_s item; item.property = NULL; int error = mlt_animation_get_item(instance, &item, position); if (!error) return item.keyframe_type; else return (mlt_keyframe_type) -1; } /** Get the keyfame at the position or the next following. * * If no keyframe exists at or after the position, the return value is invalid * * \deprecated Prefer bool Animation::next_key( int position, int& key ) * \param position the frame number at which to start looking for the next keyframe * \return the position of the next keyframe */ int Animation::next_key(int position) { struct mlt_animation_item_s item; item.property = NULL; int error = mlt_animation_next_key(instance, &item, position); if (!error) return item.frame; else return error; } /** Get the keyfame at the position or the next following. * * On error, key is not modified. * * \param position the frame number at which to start looking for the next keyframe * \param key the returned position of the next keyframe * \return true if there was an error */ bool Animation::next_key(int position, int &key) { struct mlt_animation_item_s item; item.property = NULL; bool error = mlt_animation_next_key(instance, &item, position); if (!error) { key = item.frame; } return error; } /** Get the keyfame at the position or the previous keyframe before. * * If no keyframe exists at or before the position, the return value is invalid * * \deprecated Prefer bool Animation::previous_key( int position, int& key ) * \param position the frame number at which to start looking for the previous keyframe * \return the position of the previous keyframe */ int Animation::previous_key(int position) { struct mlt_animation_item_s item; item.property = NULL; int error = mlt_animation_prev_key(instance, &item, position); if (!error) return item.frame; else return error; } /** Get the keyfame at the position or the previous before. * * On error, key is not modified. * * \param position the frame number at which to start looking for the previous keyframe * \param key the returned position of the previous keyframe * \return true if there was an error */ bool Animation::previous_key(int position, int &key) { struct mlt_animation_item_s item; item.property = NULL; bool error = mlt_animation_prev_key(instance, &item, position); if (!error) { key = item.frame; } return error; } int Animation::key_count() { return mlt_animation_key_count(instance); } int Animation::key_get(int index, int &frame, mlt_keyframe_type &type) { struct mlt_animation_item_s item; item.property = NULL; int error = mlt_animation_key_get(instance, &item, index); if (!error) { frame = item.frame; type = item.keyframe_type; } return error; } int Animation::key_get_frame(int index) { struct mlt_animation_item_s item; item.is_key = 0; item.property = NULL; int error = mlt_animation_key_get(instance, &item, index); if (!error) return item.frame; else return -1; } mlt_keyframe_type Animation::key_get_type(int index) { struct mlt_animation_item_s item; item.property = NULL; int error = mlt_animation_key_get(instance, &item, index); if (!error) return item.keyframe_type; else return (mlt_keyframe_type) -1; } int Animation::key_set_type(int index, mlt_keyframe_type type) { return mlt_animation_key_set_type(instance, index, type); } int Animation::key_set_frame(int index, int frame) { return mlt_animation_key_set_frame(instance, index, frame); } void Animation::shift_frames(int shift) { return mlt_animation_shift_frames(instance, shift); } void Animation::set_length(int length) { return mlt_animation_set_length(instance, length); } int Animation::remove(int position) { return mlt_animation_remove(instance, position); } void Animation::interpolate() { mlt_animation_interpolate(instance); } char *Animation::serialize_cut(int in, int out) { return mlt_animation_serialize_cut(instance, in, out); } char *Animation::serialize_cut(mlt_time_format format, int in, int out) { return mlt_animation_serialize_cut_tf(instance, in, out, format); } mlt-7.22.0/src/mlt++/MltAnimation.h000664 000000 000000 00000004110 14531534050 016630 0ustar00rootroot000000 000000 /** * MltAnimation.h - MLT Wrapper * Copyright (C) 2015-2021 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef MLTPP_ANIMATION_H #define MLTPP_ANIMATION_H #include "MltConfig.h" #include namespace Mlt { class MLTPP_DECLSPEC Animation { private: mlt_animation instance; public: Animation(); Animation(mlt_animation animation); Animation(const Animation &); ~Animation(); bool is_valid() const; mlt_animation get_animation() const; Animation &operator=(const Animation &); int length(); int get_item(int position, bool &is_key, mlt_keyframe_type &); bool is_key(int position); mlt_keyframe_type keyframe_type(int position); int next_key(int position); bool next_key(int position, int &key); int previous_key(int position); bool previous_key(int position, int &key); int key_count(); int key_get(int index, int &frame, mlt_keyframe_type &); int key_get_frame(int index); mlt_keyframe_type key_get_type(int index); int key_set_type(int index, mlt_keyframe_type type); int key_set_frame(int index, int frame); void shift_frames(int shift); void set_length(int length); int remove(int position); void interpolate(); char *serialize_cut(int in = -1, int out = -1); char *serialize_cut(mlt_time_format format, int in = -1, int out = -1); }; } // namespace Mlt #endif mlt-7.22.0/src/mlt++/MltAudio.cpp000664 000000 000000 00000003502 14531534050 016311 0ustar00rootroot000000 000000 /** * MltAudio.cpp - MLT Wrapper * Copyright (C) 2020 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "MltAudio.h" using namespace Mlt; Audio::Audio() { instance = mlt_audio_new(); } Audio::Audio(mlt_audio audio) : instance(audio) {} Audio::~Audio() { mlt_audio_close(instance); } void *Audio::data() { return instance->data; } void Audio::set_data(void *data) { instance->data = data; } int Audio::frequency() { return instance->frequency; } void Audio::set_frequency(int frequency) { instance->frequency = frequency; } mlt_audio_format Audio::format() { return instance->format; } void Audio::set_format(mlt_audio_format format) { instance->format = format; } int Audio::samples() { return instance->samples; } void Audio::set_samples(int samples) { instance->samples = samples; } int Audio::channels() { return instance->channels; } void Audio::set_channels(int channels) { instance->channels = channels; } mlt_channel_layout Audio::layout() { return instance->layout; } void Audio::set_layout(mlt_channel_layout layout) { instance->layout = layout; } mlt-7.22.0/src/mlt++/MltAudio.h000664 000000 000000 00000002701 14531534050 015756 0ustar00rootroot000000 000000 /** * MltAudio.h - MLT Wrapper * Copyright (C) 2020 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef MLTPP_AUDIO_H #define MLTPP_AUDIO_H #include "MltConfig.h" #include namespace Mlt { class MLTPP_DECLSPEC Audio { private: mlt_audio instance; public: Audio(); Audio(mlt_audio audio); virtual ~Audio(); void *data(); void set_data(void *data); int frequency(); void set_frequency(int frequency); mlt_audio_format format(); void set_format(mlt_audio_format format); int samples(); void set_samples(int samples); int channels(); void set_channels(int channels); mlt_channel_layout layout(); void set_layout(mlt_channel_layout layout); }; } // namespace Mlt #endif mlt-7.22.0/src/mlt++/MltChain.cpp000664 000000 000000 00000005763 14531534050 016305 0ustar00rootroot000000 000000 /** * MltChain.cpp - Chain wrapper * Copyright (C) 2020-2022 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "MltChain.h" using namespace Mlt; Chain::Chain() : instance(nullptr) {} Chain::Chain(Profile &profile, const char *id, const char *service) : instance(nullptr) { if (!id || !service) { service = id ? id : service; id = nullptr; } mlt_producer source = mlt_factory_producer(profile.get_profile(), id, service); if (source) { instance = mlt_chain_init(profile.get_profile()); mlt_chain_set_source(instance, source); if (id == NULL) mlt_chain_attach_normalizers(instance); mlt_producer_close(source); } } Chain::Chain(Profile &profile) : instance(mlt_chain_init(profile.get_profile())) {} Chain::Chain(mlt_chain chain) : instance(chain) { inc_ref(); } Chain::Chain(Chain &chain) : Mlt::Producer(chain) , instance(chain.get_chain()) { inc_ref(); } Chain::Chain(Chain *chain) : Mlt::Producer(chain) , instance(chain != NULL ? chain->get_chain() : NULL) { if (is_valid()) inc_ref(); } Chain::Chain(Service &chain) : instance(NULL) { if (chain.type() == mlt_service_chain_type) { instance = (mlt_chain) chain.get_service(); inc_ref(); } } Chain::~Chain() { mlt_chain_close(instance); instance = nullptr; } mlt_chain Chain::get_chain() { return instance; } mlt_producer Chain::get_producer() { return MLT_CHAIN_PRODUCER(instance); } void Chain::set_source(Mlt::Producer &source) { mlt_chain_set_source(instance, source.get_producer()); } Mlt::Producer Chain::get_source() { return Mlt::Producer(mlt_chain_get_source(instance)); } int Chain::attach(Mlt::Link &link) { return mlt_chain_attach(instance, link.get_link()); } int Chain::detach(Mlt::Link &link) { return mlt_chain_detach(instance, link.get_link()); } int Chain::link_count() const { return mlt_chain_link_count(instance); } bool Chain::move_link(int from, int to) { return (bool) mlt_chain_move_link(instance, from, to); } Mlt::Link *Chain::link(int index) { mlt_link result = mlt_chain_link(instance, index); return result == NULL ? NULL : new Link(result); } void Chain::attach_normalizers() { mlt_chain_attach_normalizers(instance); } mlt-7.22.0/src/mlt++/MltChain.h000664 000000 000000 00000003274 14531534050 015745 0ustar00rootroot000000 000000 /** * MltChain.h - Chain wrapper * Copyright (C) 2020-2022 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef MLTPP_CHAIN_H #define MLTPP_CHAIN_H #include "MltConfig.h" #include #include "MltLink.h" #include "MltProducer.h" #include "MltProfile.h" namespace Mlt { class MLTPP_DECLSPEC Chain : public Producer { private: mlt_chain instance; public: Chain(); Chain(Profile &profile, const char *id, const char *service = NULL); Chain(Mlt::Profile &profile); Chain(mlt_chain chain); Chain(Chain &chain); Chain(Chain *chain); Chain(Service &chain); virtual ~Chain(); virtual mlt_chain get_chain(); mlt_producer get_producer() override; void set_source(Mlt::Producer &source); Mlt::Producer get_source(); int attach(Mlt::Link &link); int detach(Mlt::Link &link); int link_count() const; bool move_link(int from, int to); Mlt::Link *link(int index); void attach_normalizers(); }; } // namespace Mlt #endif mlt-7.22.0/src/mlt++/MltConfig.h000664 000000 000000 00000002325 14531534050 016124 0ustar00rootroot000000 000000 /** * MltConfig.h - Convenience header file for all mlt++ objects * Copyright (C) 2004-2015 Meltytech, LLC * Author: Charles Yates * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef MLTPP_CONFIG_H #define MLTPP_CONFIG_H #if defined(_WIN32) #ifdef MLTPP_EXPORTS #define MLTPP_DECLSPEC __declspec(dllexport) #else #define MLTPP_DECLSPEC __declspec(dllimport) #endif #else #if __GNUC__ >= 4 #define MLTPP_DECLSPEC __attribute__((visibility("default"))) #else #define MLTPP_DECLSPEC #endif #endif #endif mlt-7.22.0/src/mlt++/MltConsumer.cpp000664 000000 000000 00000006113 14531534050 017044 0ustar00rootroot000000 000000 /** * MltConsumer.cpp - MLT Wrapper * Copyright (C) 2004-2019 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "MltConsumer.h" #include "MltEvent.h" #include "MltProfile.h" #include #include using namespace Mlt; Consumer::Consumer() : instance(NULL) { instance = mlt_factory_consumer(NULL, NULL, NULL); } Consumer::Consumer(Profile &profile) : instance(NULL) { instance = mlt_factory_consumer(profile.get_profile(), NULL, NULL); } Consumer::Consumer(Profile &profile, const char *id, const char *arg) : Consumer(profile.get_profile(), id, arg) {} Consumer::Consumer(mlt_profile profile, const char *id, const char *arg) : instance(NULL) { if (id == NULL || arg != NULL) { instance = mlt_factory_consumer(profile, id, arg); } else { if (strchr(id, ':')) { char *temp = strdup(id); char *arg = strchr(temp, ':') + 1; *(arg - 1) = '\0'; instance = mlt_factory_consumer(profile, temp, arg); free(temp); } else { instance = mlt_factory_consumer(profile, id, NULL); } } } Consumer::Consumer(Service &consumer) : instance(NULL) { if (consumer.type() == mlt_service_consumer_type) { instance = (mlt_consumer) consumer.get_service(); inc_ref(); } } Consumer::Consumer(Consumer &consumer) : Mlt::Service(consumer) , instance(consumer.get_consumer()) { inc_ref(); } Consumer::Consumer(mlt_consumer consumer) : instance(consumer) { inc_ref(); } Consumer::~Consumer() { mlt_consumer_close(instance); } mlt_consumer Consumer::get_consumer() { return instance; } mlt_service Consumer::get_service() { return mlt_consumer_service(get_consumer()); } int Consumer::connect(Service &service) { return connect_producer(service); } int Consumer::start() { return mlt_consumer_start(get_consumer()); } void Consumer::purge() { mlt_consumer_purge(get_consumer()); } int Consumer::stop() { return mlt_consumer_stop(get_consumer()); } bool Consumer::is_stopped() { return mlt_consumer_is_stopped(get_consumer()) != 0; } int Consumer::run() { int ret = start(); if (!is_stopped()) { Event *e = setup_wait_for("consumer-stopped"); wait_for(e); delete e; } return ret; } int Consumer::position() { return mlt_consumer_position(get_consumer()); } mlt-7.22.0/src/mlt++/MltConsumer.h000664 000000 000000 00000003315 14531534050 016512 0ustar00rootroot000000 000000 /** * MltConsumer.h - MLT Wrapper * Copyright (C) 2004-2015 Meltytech, LLC * Author: Charles Yates * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef MLTPP_CONSUMER_H #define MLTPP_CONSUMER_H #include "MltConfig.h" #include #include "MltService.h" namespace Mlt { class Service; class Profile; class MLTPP_DECLSPEC Consumer : public Service { private: mlt_consumer instance; public: Consumer(); Consumer(Profile &profile); Consumer(Profile &profile, const char *id, const char *service = NULL); Consumer(mlt_profile profile, const char *id, const char *service = NULL); Consumer(Service &consumer); Consumer(Consumer &consumer); Consumer(mlt_consumer consumer); virtual ~Consumer(); virtual mlt_consumer get_consumer(); mlt_service get_service() override; virtual int connect(Service &service); int run(); int start(); void purge(); int stop(); bool is_stopped(); int position(); }; } // namespace Mlt #endif mlt-7.22.0/src/mlt++/MltDeque.cpp000664 000000 000000 00000003074 14531534050 016317 0ustar00rootroot000000 000000 /** * MltDeque.cpp - MLT Wrapper * Copyright (C) 2004-2015 Meltytech, LLC * Author: Charles Yates * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "MltDeque.h" using namespace Mlt; Deque::Deque() { deque = mlt_deque_init(); } Deque::~Deque() { mlt_deque_close(deque); } int Deque::count() { return mlt_deque_count(deque); } int Deque::push_back(void *item) { return mlt_deque_push_back(deque, item); } void *Deque::pop_back() { return mlt_deque_pop_back(deque); } int Deque::push_front(void *item) { return mlt_deque_push_front(deque, item); } void *Deque::pop_front() { return mlt_deque_pop_front(deque); } void *Deque::peek_back() { return mlt_deque_peek_back(deque); } void *Deque::peek_front() { return mlt_deque_peek_front(deque); } void *Deque::peek(int index) { return mlt_deque_peek(deque, index); } mlt-7.22.0/src/mlt++/MltDeque.h000664 000000 000000 00000002437 14531534050 015766 0ustar00rootroot000000 000000 /** * MltDeque.h - MLT Wrapper * Copyright (C) 2004-2015 Meltytech, LLC * Author: Charles Yates * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef MLTPP_DEQUE_H #define MLTPP_DEQUE_H #include "MltConfig.h" #include namespace Mlt { class MLTPP_DECLSPEC Deque { private: mlt_deque deque; public: Deque(); ~Deque(); int count(); int push_back(void *item); void *pop_back(); int push_front(void *item); void *pop_front(); void *peek_back(); void *peek_front(); void *peek(int index); }; } // namespace Mlt #endif mlt-7.22.0/src/mlt++/MltEvent.cpp000664 000000 000000 00000004134 14531534050 016333 0ustar00rootroot000000 000000 /** * MltEvent.cpp - MLT Wrapper * Copyright (C) 2004-2021 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "MltEvent.h" #include "MltFrame.h" using namespace Mlt; Event::Event(mlt_event event) : instance(event) { mlt_event_inc_ref(instance); } Event::Event(Event &event) : instance(event.get_event()) { mlt_event_inc_ref(instance); } Event::~Event() { mlt_event_close(instance); } mlt_event Event::get_event() { return instance; } bool Event::is_valid() { return instance != NULL; } void Event::block() { mlt_event_block(get_event()); } void Event::unblock() { mlt_event_unblock(get_event()); } EventData::EventData(mlt_event_data data) : instance(data) {} EventData::EventData(EventData &data) : instance(data.get_event_data()) {} EventData::EventData(const EventData &data) : instance(data.get_event_data()) {} EventData &EventData::operator=(const EventData &data) { instance = data.get_event_data(); return *this; } mlt_event_data EventData::get_event_data() const { return instance; } int EventData::to_int() const { return mlt_event_data_to_int(instance); } const char *EventData::to_string() const { return mlt_event_data_to_string(instance); } Frame EventData::to_frame() const { return Frame(mlt_event_data_to_frame(instance)); } void *EventData::to_object() const { return mlt_event_data_to_object(instance); } mlt-7.22.0/src/mlt++/MltEvent.h000664 000000 000000 00000003072 14531534050 016000 0ustar00rootroot000000 000000 /** * MltEvent.h - MLT Wrapper * Copyright (C) 2004-2021 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef MLTPP_EVENT_H #define MLTPP_EVENT_H #include "MltConfig.h" #include namespace Mlt { class Frame; class MLTPP_DECLSPEC Event { private: mlt_event instance; public: Event(mlt_event); Event(Event &); ~Event(); mlt_event get_event(); bool is_valid(); void block(); void unblock(); }; class MLTPP_DECLSPEC EventData { private: mlt_event_data instance; public: EventData(mlt_event_data); EventData(EventData &); EventData(const EventData &); EventData &operator=(const EventData &); ~EventData(){}; mlt_event_data get_event_data() const; int to_int() const; const char *to_string() const; Frame to_frame() const; void *to_object() const; }; } // namespace Mlt #endif mlt-7.22.0/src/mlt++/MltFactory.cpp000664 000000 000000 00000003350 14531534050 016660 0ustar00rootroot000000 000000 /** * MltFactory.cpp - MLT Wrapper * Copyright (C) 2004-2017 Meltytech, LLC * Author: Charles Yates * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "MltFactory.h" #include "MltConsumer.h" #include "MltFilter.h" #include "MltProducer.h" #include "MltRepository.h" #include "MltTransition.h" using namespace Mlt; Repository *Factory::init(const char *directory) { return new Repository(mlt_factory_init(directory)); } Properties *Factory::event_object() { return new Properties(mlt_factory_event_object()); } Producer *Factory::producer(Profile &profile, char *id, char *arg) { return new Producer(profile, id, arg); } Filter *Factory::filter(Profile &profile, char *id, char *arg) { return new Filter(profile, id, arg); } Transition *Factory::transition(Profile &profile, char *id, char *arg) { return new Transition(profile, id, arg); } Consumer *Factory::consumer(Profile &profile, char *id, char *arg) { return new Consumer(profile, id, arg); } void Factory::close() { mlt_factory_close(); } mlt-7.22.0/src/mlt++/MltFactory.h000664 000000 000000 00000003164 14531534050 016330 0ustar00rootroot000000 000000 /** * MltFactory.h - MLT Wrapper * Copyright (C) 2004-2017 Meltytech, LLC * Author: Charles Yates * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef MLTPP_FACTORY_H #define MLTPP_FACTORY_H #include "MltConfig.h" #ifdef SWIG #define MLTPP_DECLSPEC #endif #include namespace Mlt { class Properties; class Producer; class Filter; class Transition; class Consumer; class Profile; class Repository; class MLTPP_DECLSPEC Factory { public: static Repository *init(const char *directory = NULL); static Properties *event_object(); static Producer *producer(Profile &profile, char *id, char *arg = NULL); static Filter *filter(Profile &profile, char *id, char *arg = NULL); static Transition *transition(Profile &profile, char *id, char *arg = NULL); static Consumer *consumer(Profile &profile, char *id, char *arg = NULL); static void close(); }; } // namespace Mlt #endif mlt-7.22.0/src/mlt++/MltField.cpp000664 000000 000000 00000003360 14531534050 016275 0ustar00rootroot000000 000000 /** * MltField.cpp - Field wrapper * Copyright (C) 2004-2015 Meltytech, LLC * Author: Charles Yates * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "MltField.h" #include "MltFilter.h" #include "MltTransition.h" using namespace Mlt; Field::Field(mlt_field field) : instance(field) { inc_ref(); } Field::Field(Field &field) : Mlt::Service(field) , instance(field.get_field()) { inc_ref(); } Field::~Field() { mlt_field_close(instance); } mlt_field Field::get_field() { return instance; } mlt_service Field::get_service() { return mlt_field_service(get_field()); } int Field::plant_filter(Filter &filter, int track) { return mlt_field_plant_filter(get_field(), filter.get_filter(), track); } int Field::plant_transition(Transition &transition, int a_track, int b_track) { return mlt_field_plant_transition(get_field(), transition.get_transition(), a_track, b_track); } void Field::disconnect_service(Service &service) { mlt_field_disconnect_service(get_field(), service.get_service()); } mlt-7.22.0/src/mlt++/MltField.h000664 000000 000000 00000002742 14531534050 015745 0ustar00rootroot000000 000000 /** * MltField.h - Field wrapper * Copyright (C) 2004-2015 Meltytech, LLC * Author: Charles Yates * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef MLTPP_FIELD_H #define MLTPP_FIELD_H #include "MltConfig.h" #include #include "MltService.h" namespace Mlt { class Service; class Filter; class Transition; class MLTPP_DECLSPEC Field : public Service { private: mlt_field instance; public: Field(mlt_field field); Field(Field &field); virtual ~Field(); mlt_field get_field(); mlt_service get_service() override; int plant_filter(Filter &filter, int track = 0); int plant_transition(Transition &transition, int a_track = 0, int b_track = 1); void disconnect_service(Service &service); }; } // namespace Mlt #endif mlt-7.22.0/src/mlt++/MltFilter.cpp000664 000000 000000 00000006721 14531534050 016503 0ustar00rootroot000000 000000 /** * MltFilter.cpp - MLT Wrapper * Copyright (C) 2004-2021 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "MltFilter.h" #include "MltProfile.h" #include #include using namespace Mlt; Filter::Filter() : Service() , instance(nullptr) {} Filter::Filter(Profile &profile, const char *id, const char *arg) : Filter(profile.get_profile(), id, arg) {} Filter::Filter(mlt_profile profile, const char *id, const char *arg) : instance(NULL) { if (arg != NULL) { instance = mlt_factory_filter(profile, id, arg); } else { if (strchr(id, ':')) { char *temp = strdup(id); char *arg = strchr(temp, ':') + 1; *(arg - 1) = '\0'; instance = mlt_factory_filter(profile, temp, arg); free(temp); } else { instance = mlt_factory_filter(profile, id, NULL); } } } Filter::Filter(Service &filter) : instance(NULL) { if (filter.type() == mlt_service_filter_type) { instance = (mlt_filter) filter.get_service(); inc_ref(); } } Filter::Filter(Filter &filter) : Mlt::Service(filter) , instance(filter.get_filter()) { inc_ref(); } Filter::Filter(const Filter &filter) : Filter(const_cast(filter)) {} Filter::Filter(mlt_filter filter) : instance(filter) { inc_ref(); } Filter::Filter(Filter *filter) : Filter(filter ? filter->get_filter() : nullptr) {} Filter::~Filter() { mlt_filter_close(instance); } Filter &Filter::operator=(const Filter &filter) { if (this != &filter) { mlt_filter_close(instance); instance = filter.instance; inc_ref(); } return *this; } mlt_filter Filter::get_filter() { return instance; } mlt_service Filter::get_service() { return mlt_filter_service(get_filter()); } int Filter::connect(Service &service, int index) { return mlt_filter_connect(get_filter(), service.get_service(), index); } void Filter::set_in_and_out(int in, int out) { mlt_filter_set_in_and_out(get_filter(), in, out); } int Filter::get_in() { return mlt_filter_get_in(get_filter()); } int Filter::get_out() { return mlt_filter_get_out(get_filter()); } int Filter::get_length() { return mlt_filter_get_length(get_filter()); } int Filter::get_length2(Frame &frame) { return mlt_filter_get_length2(get_filter(), frame.get_frame()); } int Filter::get_track() { return mlt_filter_get_track(get_filter()); } int Filter::get_position(Frame &frame) { return mlt_filter_get_position(get_filter(), frame.get_frame()); } double Filter::get_progress(Frame &frame) { return mlt_filter_get_progress(get_filter(), frame.get_frame()); } void Filter::process(Frame &frame) { mlt_filter_process(get_filter(), frame.get_frame()); } mlt-7.22.0/src/mlt++/MltFilter.h000664 000000 000000 00000003556 14531534050 016153 0ustar00rootroot000000 000000 /** * MltFilter.h - MLT Wrapper * Copyright (C) 2004-2021 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef MLTPP_FILTER_H #define MLTPP_FILTER_H #include "MltConfig.h" #include #include "MltService.h" namespace Mlt { class Service; class Profile; class Frame; class MLTPP_DECLSPEC Filter : public Service { private: mlt_filter instance; public: Filter(); Filter(Profile &profile, const char *id, const char *service = NULL); Filter(mlt_profile profile, const char *id, const char *service = NULL); Filter(Service &filter); Filter(Filter &filter); Filter(const Filter &filter); Filter(mlt_filter filter); Filter(Filter *filter); virtual ~Filter(); Filter &operator=(const Filter &filter); virtual mlt_filter get_filter(); mlt_service get_service() override; int connect(Service &service, int index = 0); void set_in_and_out(int in, int out); int get_in(); int get_out(); int get_length(); int get_length2(Frame &frame); int get_track(); int get_position(Frame &frame); double get_progress(Frame &frame); void process(Frame &frame); }; } // namespace Mlt #endif mlt-7.22.0/src/mlt++/MltFilteredConsumer.cpp000664 000000 000000 00000006075 14531534050 020532 0ustar00rootroot000000 000000 /** * MltFilteredConsumer.cpp - MLT Wrapper * Copyright (C) 2004-2015 Meltytech, LLC * Author: Charles Yates * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "MltFilteredConsumer.h" using namespace Mlt; FilteredConsumer::FilteredConsumer(Profile &profile, const char *id, const char *arg) : Consumer(profile, id, arg) { // Create a reference to the first service first = new Service(*this); } FilteredConsumer::FilteredConsumer(Consumer &consumer) : Consumer(consumer) { // Create a reference to the first service first = new Service(*this); } FilteredConsumer::~FilteredConsumer() { // Delete the reference to the first service delete first; } int FilteredConsumer::connect(Service &service) { // All producers must connect to the first service, hence the use of the virtual here return first->connect_producer(service); } int FilteredConsumer::attach(Filter &filter) { int error = 0; if (filter.is_valid()) { Service *producer = first->producer(); error = filter.connect(*producer); if (error == 0) { first->connect_producer(filter); delete first; first = new Service(filter); } delete producer; } else { error = 1; } return error; } int FilteredConsumer::last(Filter &filter) { int error = 0; if (filter.is_valid()) { Service *producer = this->producer(); error = filter.connect(*producer); if (error == 0) connect_producer(filter); delete producer; } else { error = 1; } return error; } int FilteredConsumer::detach(Filter &filter) { if (filter.is_valid()) { Service *it = new Service(*first); while (it->is_valid() && it->get_service() != filter.get_service()) { Service *consumer = it->consumer(); delete it; it = consumer; } if (it->get_service() == filter.get_service()) { Service *producer = it->producer(); Service *consumer = it->consumer(); consumer->connect_producer(*producer); Service dummy; it->connect_producer(dummy); if (first->get_service() == it->get_service()) { delete first; first = new Service(*consumer); } } delete it; } return 0; } mlt-7.22.0/src/mlt++/MltFilteredConsumer.h000664 000000 000000 00000003013 14531534050 020164 0ustar00rootroot000000 000000 /** * MltFilteredConsumer.h - MLT Wrapper * Copyright (C) 2004-2015 Meltytech, LLC * Author: Charles Yates * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef MLTPP_FILTERED_CONSUMER_H #define MLTPP_FILTERED_CONSUMER_H #include "MltConfig.h" #include "MltConsumer.h" #include "MltFilter.h" #include "MltService.h" namespace Mlt { class Consumer; class Service; class Filter; class Profile; class MLTPP_DECLSPEC FilteredConsumer : public Consumer { private: Service *first; public: FilteredConsumer(Profile &profile, const char *id, const char *arg = NULL); FilteredConsumer(Consumer &consumer); virtual ~FilteredConsumer(); int connect(Service &service) override; int attach(Filter &filter); int last(Filter &filter); int detach(Filter &filter); }; } // namespace Mlt #endif mlt-7.22.0/src/mlt++/MltFilteredProducer.cpp000664 000000 000000 00000004766 14531534050 020527 0ustar00rootroot000000 000000 /** * MltFilteredProducer.cpp - MLT Wrapper * Copyright (C) 2004-2015 Meltytech, LLC * Author: Charles Yates * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "MltFilteredProducer.h" #include "MltProfile.h" using namespace Mlt; FilteredProducer::FilteredProducer(Profile &profile, const char *id, const char *arg) : Producer(profile, id, arg) { // Create a reference to the last service last = new Service(*this); } FilteredProducer::~FilteredProducer() { // Delete the reference to the last service delete last; } int FilteredProducer::attach(Filter &filter) { int error = 0; if (filter.is_valid()) { Service *consumer = last->consumer(); filter.connect_producer(*last); if (consumer->is_valid()) consumer->connect_producer(filter); delete consumer; delete last; last = new Service(filter); } else { error = 1; } return error; } int FilteredProducer::detach(Filter &filter) { if (filter.is_valid()) { Service *it = new Service(*last); while (it->is_valid() && it->get_service() != filter.get_service()) { Service *producer = it->producer(); delete it; it = producer; } if (it->get_service() == filter.get_service()) { Service *producer = it->producer(); Service *consumer = it->consumer(); if (consumer->is_valid()) consumer->connect_producer(*producer); Profile p(get_profile()); Producer dummy(p, "colour"); dummy.connect_producer(*it); if (last->get_service() == it->get_service()) { delete last; last = new Service(*producer); } } delete it; } return 0; } mlt-7.22.0/src/mlt++/MltFilteredProducer.h000664 000000 000000 00000002626 14531534050 020165 0ustar00rootroot000000 000000 /** * MltFilteredProducer.h - MLT Wrapper * Copyright (C) 2004-2015 Meltytech, LLC * Author: Charles Yates * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef MLTPP_FILTERED_PRODUCER_H #define MLTPP_FILTERED_PRODUCER_H #include "MltConfig.h" #include "MltFilter.h" #include "MltProducer.h" #include "MltService.h" namespace Mlt { class Producer; class Service; class Filter; class Profile; class MLTPP_DECLSPEC FilteredProducer : public Producer { private: Service *last; public: FilteredProducer(Profile &profile, const char *id, const char *arg = NULL); virtual ~FilteredProducer(); int attach(Filter &filter); int detach(Filter &filter); }; } // namespace Mlt #endif mlt-7.22.0/src/mlt++/MltFrame.cpp000664 000000 000000 00000006573 14531534050 016315 0ustar00rootroot000000 000000 /** * MltFrame.cpp - MLT Wrapper * Copyright (C) 2004-2015 Meltytech, LLC * Author: Charles Yates * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "MltFrame.h" #include "MltProducer.h" using namespace Mlt; Frame::Frame() : Mlt::Properties((mlt_properties) NULL) , instance(NULL) {} Frame::Frame(mlt_frame frame) : Mlt::Properties((mlt_properties) NULL) , instance(frame) { inc_ref(); } Frame::Frame(Frame &frame) : Mlt::Properties((mlt_properties) NULL) , instance(frame.instance) { inc_ref(); } Frame::Frame(const Frame &frame) : Mlt::Properties((mlt_properties) NULL) , instance(frame.instance) { inc_ref(); } Frame::~Frame() { mlt_frame_close(instance); } Frame &Frame::operator=(const Frame &frame) { if (this != &frame) { mlt_frame_close(instance); instance = frame.instance; inc_ref(); } return *this; } mlt_frame Frame::get_frame() { return instance; } mlt_properties Frame::get_properties() { return mlt_frame_properties(get_frame()); } uint8_t *Frame::get_image(mlt_image_format &format, int &w, int &h, int writable) { uint8_t *image = NULL; if (get_double("consumer_aspect_ratio") == 0.0) set("consumer_aspect_ratio", 1.0); mlt_frame_get_image(get_frame(), &image, &format, &w, &h, writable); set("format", format); set("writable", writable); return image; } unsigned char *Frame::fetch_image(mlt_image_format f, int w, int h, int writable) { uint8_t *image = NULL; if (get_double("consumer_aspect_ratio") == 0.0) set("consumer_aspect_ratio", 1.0); mlt_frame_get_image(get_frame(), &image, &f, &w, &h, writable); set("format", f); set("writable", writable); return image; } void *Frame::get_audio(mlt_audio_format &format, int &frequency, int &channels, int &samples) { void *audio = NULL; mlt_frame_get_audio(get_frame(), &audio, &format, &frequency, &channels, &samples); return audio; } unsigned char *Frame::get_waveform(int w, int h) { return mlt_frame_get_waveform(get_frame(), w, h); } Producer *Frame::get_original_producer() { return new Producer(mlt_frame_get_original_producer(get_frame())); } mlt_properties Frame::get_unique_properties(Service &service) { return mlt_frame_unique_properties(get_frame(), service.get_service()); } int Frame::get_position() { return mlt_frame_get_position(get_frame()); } int Frame::set_image(uint8_t *image, int size, mlt_destructor destroy) { return mlt_frame_set_image(get_frame(), image, size, destroy); } int Frame::set_alpha(uint8_t *alpha, int size, mlt_destructor destroy) { return mlt_frame_set_alpha(get_frame(), alpha, size, destroy); } mlt-7.22.0/src/mlt++/MltFrame.h000664 000000 000000 00000003721 14531534050 015752 0ustar00rootroot000000 000000 /** * MltFilter.h - MLT Wrapper * Copyright (C) 2004-2015 Meltytech, LLC * Author: Charles Yates * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef MLTPP_FRAME_H #define MLTPP_FRAME_H #include "MltConfig.h" #include "MltProperties.h" #include namespace Mlt { class Properties; class Producer; class Service; class MLTPP_DECLSPEC Frame : public Properties { private: mlt_frame instance; public: Frame(); Frame(mlt_frame frame); Frame(Frame &frame); Frame(const Frame &frame); virtual ~Frame(); Frame &operator=(const Frame &frame); virtual mlt_frame get_frame(); mlt_properties get_properties() override; uint8_t *get_image(mlt_image_format &format, int &w, int &h, int writable = 0); unsigned char *fetch_image(mlt_image_format format, int w, int h, int writable = 0); void *get_audio(mlt_audio_format &format, int &frequency, int &channels, int &samples); unsigned char *get_waveform(int w, int h); Producer *get_original_producer(); int get_position(); mlt_properties get_unique_properties(Service &service); int set_image(uint8_t *image, int size, mlt_destructor destroy); int set_alpha(uint8_t *alpha, int size, mlt_destructor destroy); }; } // namespace Mlt #endif mlt-7.22.0/src/mlt++/MltImage.cpp000664 000000 000000 00000003644 14531534050 016301 0ustar00rootroot000000 000000 /** * MltImage.cpp - MLT Wrapper * Copyright (C) 2021 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "MltImage.h" using namespace Mlt; Image::Image() { instance = mlt_image_new(); } Image::Image(mlt_image image) : instance(image) {} Image::Image(int width, int height, mlt_image_format format) { instance = mlt_image_new(); alloc(width, height, format); } Image::~Image() { mlt_image_close(instance); } mlt_image_format Image::format() { return instance->format; } int Image::width() { return instance->width; } int Image::height() { return instance->height; } void Image::set_colorspace(int colorspace) { instance->colorspace = colorspace; } int Image::colorspace() { return instance->colorspace; } void Image::alloc(int width, int height, mlt_image_format format, bool alpha) { instance->width = width; instance->height = height; instance->format = format; mlt_image_alloc_data(instance); if (alpha) mlt_image_alloc_alpha(instance); } void Image::init_alpha() { mlt_image_alloc_alpha(instance); } uint8_t *Image::plane(int plane) { return instance->planes[plane]; } int Image::stride(int plane) { return instance->strides[plane]; } mlt-7.22.0/src/mlt++/MltImage.h000664 000000 000000 00000002650 14531534050 015742 0ustar00rootroot000000 000000 /** * MltImage.h - MLT Wrapper * Copyright (C) 2021 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef MLTPP_IMAGE_H #define MLTPP_IMAGE_H #include "MltConfig.h" #include namespace Mlt { class MLTPP_DECLSPEC Image { private: mlt_image instance; public: Image(); Image(mlt_image image); Image(int width, int height, mlt_image_format format); virtual ~Image(); mlt_image_format format(); int width(); int height(); void set_colorspace(int colorspace); int colorspace(); void alloc(int width, int height, mlt_image_format format, bool alpha = false); void init_alpha(); uint8_t *plane(int plane); int stride(int plane); }; } // namespace Mlt #endif mlt-7.22.0/src/mlt++/MltLink.cpp000664 000000 000000 00000004713 14531534050 016152 0ustar00rootroot000000 000000 /** * MltLink.cpp - MLT Wrapper * Copyright (C) 2020-2021 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "MltLink.h" #include "MltProfile.h" #include #include using namespace Mlt; Link::Link() : instance(nullptr) {} Link::Link(mlt_link link) : instance(link) { inc_ref(); } Link::Link(const char *id, const char *arg) : instance(NULL) { if (arg != NULL) { instance = mlt_factory_link(id, arg); } else { if (strchr(id, ':')) { char *temp = strdup(id); char *arg = strchr(temp, ':') + 1; *(arg - 1) = '\0'; instance = mlt_factory_link(temp, arg); free(temp); } else { instance = mlt_factory_link(id, NULL); } } } Link::Link(Link *link) : Link(link ? link->get_link() : nullptr) {} Link::Link(Service &link) : instance(nullptr) { if (link.type() == mlt_service_link_type) { instance = (mlt_link) link.get_service(); inc_ref(); } } Link::Link(Link &link) { if (link.type() == mlt_service_link_type) { instance = (mlt_link) link.get_service(); inc_ref(); } } Link::Link(const Link &link) : Link(const_cast(link)) {} Link &Link::operator=(const Link &link) { if (this != &link) { mlt_link_close(instance); instance = link.instance; inc_ref(); } return *this; } Link::~Link() { mlt_link_close(instance); } mlt_link Link::get_link() { return instance; } mlt_producer Link::get_producer() { return MLT_LINK_PRODUCER(instance); } int Link::connect_next(Mlt::Producer &next, Mlt::Profile &default_profile) { return mlt_link_connect_next(instance, next.get_producer(), default_profile.get_profile()); } mlt-7.22.0/src/mlt++/MltLink.h000664 000000 000000 00000002740 14531534050 015615 0ustar00rootroot000000 000000 /** * MltLink.h - MLT Wrapper * Copyright (C) 2020-2021 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef MLTPP_LINK_H #define MLTPP_LINK_H #include "MltConfig.h" #include "MltProducer.h" #include namespace Mlt { class Producer; class Profile; class MLTPP_DECLSPEC Link : public Producer { private: mlt_link instance; public: Link(); Link(mlt_link link); Link(const char *id, const char *service = NULL); Link(Link *link); Link(Service &link); Link(Link &link); Link(const Link &link); Link &operator=(const Link &link); virtual ~Link(); virtual mlt_link get_link(); mlt_producer get_producer() override; int connect_next(Mlt::Producer &next, Mlt::Profile &default_profile); }; } // namespace Mlt #endif mlt-7.22.0/src/mlt++/MltMultitrack.cpp000664 000000 000000 00000004620 14531534050 017371 0ustar00rootroot000000 000000 /** * MltMultitrack.h - Multitrack wrapper * Copyright (C) 2004-2015 Meltytech, LLC * Author: Charles Yates * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "MltMultitrack.h" #include "MltProducer.h" using namespace Mlt; Multitrack::Multitrack(mlt_multitrack multitrack) : instance(multitrack) { inc_ref(); } Multitrack::Multitrack(Service &multitrack) : instance(NULL) { if (multitrack.type() == mlt_service_multitrack_type) { instance = (mlt_multitrack) multitrack.get_service(); inc_ref(); } } Multitrack::Multitrack(Multitrack &multitrack) : Mlt::Producer(multitrack) , instance(multitrack.get_multitrack()) { inc_ref(); } Multitrack::~Multitrack() { mlt_multitrack_close(instance); } mlt_multitrack Multitrack::get_multitrack() { return instance; } mlt_producer Multitrack::get_producer() { return mlt_multitrack_producer(get_multitrack()); } int Multitrack::connect(Producer &producer, int index) { return mlt_multitrack_connect(get_multitrack(), producer.get_producer(), index); } int Multitrack::insert(Producer &producer, int index) { return mlt_multitrack_insert(get_multitrack(), producer.get_producer(), index); } int Multitrack::disconnect(int index) { return mlt_multitrack_disconnect(get_multitrack(), index); } int Multitrack::clip(mlt_whence whence, int index) { return mlt_multitrack_clip(get_multitrack(), whence, index); } int Multitrack::count() { return mlt_multitrack_count(get_multitrack()); } Producer *Multitrack::track(int index) { return new Producer(mlt_multitrack_track(get_multitrack(), index)); } void Multitrack::refresh() { return mlt_multitrack_refresh(get_multitrack()); } mlt-7.22.0/src/mlt++/MltMultitrack.h000664 000000 000000 00000003175 14531534050 017042 0ustar00rootroot000000 000000 /** * MltMultitrack.h - Multitrack wrapper * Copyright (C) 2004-2015 Meltytech, LLC * Author: Charles Yates * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef MLTPP_MULTITRACK_H #define MLTPP_MULTITRACK_H #include "MltConfig.h" #include #include "MltProducer.h" namespace Mlt { class Service; class Producer; class MLTPP_DECLSPEC Multitrack : public Producer { private: mlt_multitrack instance; public: Multitrack(mlt_multitrack multitrack); Multitrack(Service &multitrack); Multitrack(Multitrack &multitrack); virtual ~Multitrack(); mlt_multitrack get_multitrack(); mlt_producer get_producer() override; int connect(Producer &producer, int index); int insert(Producer &producer, int index); int disconnect(int index); int clip(mlt_whence whence, int index); int count(); Producer *track(int index); void refresh(); }; } // namespace Mlt #endif mlt-7.22.0/src/mlt++/MltParser.cpp000664 000000 000000 00000023643 14531534050 016514 0ustar00rootroot000000 000000 /** * MltParser.cpp - MLT Wrapper * Copyright (C) 2004-2015 Meltytech, LLC * Author: Charles Yates * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "Mlt.h" using namespace Mlt; static int on_invalid_cb(mlt_parser self, mlt_service object) { mlt_properties properties = mlt_parser_properties(self); Parser *parser = (Parser *) mlt_properties_get_data(properties, "_parser_object", NULL); Service service(object); return parser->on_invalid(&service); } static int on_unknown_cb(mlt_parser self, mlt_service object) { mlt_properties properties = mlt_parser_properties(self); Parser *parser = (Parser *) mlt_properties_get_data(properties, "_parser_object", NULL); Service service(object); return parser->on_unknown(&service); } static int on_start_producer_cb(mlt_parser self, mlt_producer object) { mlt_properties properties = mlt_parser_properties(self); Parser *parser = (Parser *) mlt_properties_get_data(properties, "_parser_object", NULL); Producer producer(object); return parser->on_start_producer(&producer); } static int on_end_producer_cb(mlt_parser self, mlt_producer object) { mlt_properties properties = mlt_parser_properties(self); Parser *parser = (Parser *) mlt_properties_get_data(properties, "_parser_object", NULL); Producer producer(object); return parser->on_end_producer(&producer); } static int on_start_playlist_cb(mlt_parser self, mlt_playlist object) { mlt_properties properties = mlt_parser_properties(self); Parser *parser = (Parser *) mlt_properties_get_data(properties, "_parser_object", NULL); Playlist playlist(object); return parser->on_start_playlist(&playlist); } static int on_end_playlist_cb(mlt_parser self, mlt_playlist object) { mlt_properties properties = mlt_parser_properties(self); Parser *parser = (Parser *) mlt_properties_get_data(properties, "_parser_object", NULL); Playlist playlist(object); return parser->on_end_playlist(&playlist); } static int on_start_tractor_cb(mlt_parser self, mlt_tractor object) { mlt_properties properties = mlt_parser_properties(self); Parser *parser = (Parser *) mlt_properties_get_data(properties, "_parser_object", NULL); Tractor tractor(object); return parser->on_start_tractor(&tractor); } static int on_end_tractor_cb(mlt_parser self, mlt_tractor object) { mlt_properties properties = mlt_parser_properties(self); Parser *parser = (Parser *) mlt_properties_get_data(properties, "_parser_object", NULL); Tractor tractor(object); return parser->on_end_tractor(&tractor); } static int on_start_multitrack_cb(mlt_parser self, mlt_multitrack object) { mlt_properties properties = mlt_parser_properties(self); Parser *parser = (Parser *) mlt_properties_get_data(properties, "_parser_object", NULL); Multitrack multitrack(object); return parser->on_start_multitrack(&multitrack); } static int on_end_multitrack_cb(mlt_parser self, mlt_multitrack object) { mlt_properties properties = mlt_parser_properties(self); Parser *parser = (Parser *) mlt_properties_get_data(properties, "_parser_object", NULL); Multitrack multitrack(object); return parser->on_end_multitrack(&multitrack); } static int on_start_track_cb(mlt_parser self) { mlt_properties properties = mlt_parser_properties(self); Parser *parser = (Parser *) mlt_properties_get_data(properties, "_parser_object", NULL); return parser->on_start_track(); } static int on_end_track_cb(mlt_parser self) { mlt_properties properties = mlt_parser_properties(self); Parser *parser = (Parser *) mlt_properties_get_data(properties, "_parser_object", NULL); return parser->on_end_track(); } static int on_start_filter_cb(mlt_parser self, mlt_filter object) { mlt_properties properties = mlt_parser_properties(self); Parser *parser = (Parser *) mlt_properties_get_data(properties, "_parser_object", NULL); Filter filter(object); return parser->on_start_filter(&filter); } static int on_end_filter_cb(mlt_parser self, mlt_filter object) { mlt_properties properties = mlt_parser_properties(self); Parser *parser = (Parser *) mlt_properties_get_data(properties, "_parser_object", NULL); Filter filter(object); return parser->on_end_filter(&filter); } static int on_start_transition_cb(mlt_parser self, mlt_transition object) { mlt_properties properties = mlt_parser_properties(self); Parser *parser = (Parser *) mlt_properties_get_data(properties, "_parser_object", NULL); Transition transition(object); return parser->on_start_transition(&transition); } static int on_end_transition_cb(mlt_parser self, mlt_transition object) { mlt_properties properties = mlt_parser_properties(self); Parser *parser = (Parser *) mlt_properties_get_data(properties, "_parser_object", NULL); Transition transition(object); return parser->on_end_transition(&transition); } static int on_start_chain_cb(mlt_parser self, mlt_chain object) { mlt_properties properties = mlt_parser_properties(self); Parser *parser = (Parser *) mlt_properties_get_data(properties, "_parser_object", NULL); Chain chain(object); return parser->on_start_chain(&chain); } static int on_end_chain_cb(mlt_parser self, mlt_chain object) { mlt_properties properties = mlt_parser_properties(self); Parser *parser = (Parser *) mlt_properties_get_data(properties, "_parser_object", NULL); Chain chain(object); return parser->on_end_chain(&chain); } static int on_start_link_cb(mlt_parser self, mlt_link object) { mlt_properties properties = mlt_parser_properties(self); Parser *parser = (Parser *) mlt_properties_get_data(properties, "_parser_object", NULL); Link link(object); return parser->on_start_link(&link); } static int on_end_link_cb(mlt_parser self, mlt_link object) { mlt_properties properties = mlt_parser_properties(self); Parser *parser = (Parser *) mlt_properties_get_data(properties, "_parser_object", NULL); Link link(object); return parser->on_end_link(&link); } Parser::Parser() : Properties(false) { parser = mlt_parser_new(); set("_parser_object", this, 0); parser->on_invalid = on_invalid_cb; parser->on_unknown = on_unknown_cb; parser->on_start_producer = on_start_producer_cb; parser->on_end_producer = on_end_producer_cb; parser->on_start_playlist = on_start_playlist_cb; parser->on_end_playlist = on_end_playlist_cb; parser->on_start_tractor = on_start_tractor_cb; parser->on_end_tractor = on_end_tractor_cb; parser->on_start_multitrack = on_start_multitrack_cb; parser->on_end_multitrack = on_end_multitrack_cb; parser->on_start_track = on_start_track_cb; parser->on_end_track = on_end_track_cb; parser->on_start_filter = on_start_filter_cb; parser->on_end_filter = on_end_filter_cb; parser->on_start_transition = on_start_transition_cb; parser->on_end_transition = on_end_transition_cb; parser->on_start_chain = on_start_chain_cb; parser->on_end_chain = on_end_chain_cb; parser->on_start_link = on_start_link_cb; parser->on_end_link = on_end_link_cb; } Parser::~Parser() { mlt_parser_close(parser); } mlt_properties Parser::get_properties() { return mlt_parser_properties(parser); } int Parser::start(Service &service) { return mlt_parser_start(parser, service.get_service()); } int Parser::on_invalid(Service *object) { object->debug("Invalid"); return 0; } int Parser::on_unknown(Service *object) { object->debug("Unknown"); return 0; } int Parser::on_start_producer(Producer *object) { object->debug("on_start_producer"); return 0; } int Parser::on_end_producer(Producer *object) { object->debug("on_end_producer"); return 0; } int Parser::on_start_playlist(Playlist *object) { object->debug("on_start_playlist"); return 0; } int Parser::on_end_playlist(Playlist *object) { object->debug("on_end_playlist"); return 0; } int Parser::on_start_tractor(Tractor *object) { object->debug("on_start_tractor"); return 0; } int Parser::on_end_tractor(Tractor *object) { object->debug("on_end_tractor"); return 0; } int Parser::on_start_multitrack(Multitrack *object) { object->debug("on_start_multitrack"); return 0; } int Parser::on_end_multitrack(Multitrack *object) { object->debug("on_end_multitrack"); return 0; } int Parser::on_start_track() { fprintf(stderr, "on_start_track\n"); return 0; } int Parser::on_end_track() { fprintf(stderr, "on_end_track\n"); return 0; } int Parser::on_start_filter(Filter *object) { object->debug("on_start_filter"); return 0; } int Parser::on_end_filter(Filter *object) { object->debug("on_end_filter"); return 0; } int Parser::on_start_transition(Transition *object) { object->debug("on_start_transition"); return 0; } int Parser::on_end_transition(Transition *object) { object->debug("on_end_transition"); return 0; } int Parser::on_start_chain(Chain *object) { object->debug("on_start_chain"); return 0; } int Parser::on_end_chain(Chain *object) { object->debug("on_end_chain"); return 0; } int Parser::on_start_link(Link *object) { object->debug("on_start_link"); return 0; } int Parser::on_end_link(Link *object) { object->debug("on_end_link"); return 0; } mlt-7.22.0/src/mlt++/MltParser.h000664 000000 000000 00000004455 14531534050 016161 0ustar00rootroot000000 000000 /** * MltParser.h - MLT Wrapper * Copyright (C) 2004-2015 Meltytech, LLC * Author: Charles Yates * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef MLTPP_PARSER_H #define MLTPP_PARSER_H #include "MltConfig.h" #include "MltProperties.h" #include namespace Mlt { class Properties; class Service; class Producer; class Playlist; class Tractor; class Multitrack; class Filter; class Transition; class MLTPP_DECLSPEC Parser : public Properties { private: mlt_parser parser; public: Parser(); ~Parser(); int start(Service &service); virtual mlt_properties get_properties() override; virtual int on_invalid(Service *object); virtual int on_unknown(Service *object); virtual int on_start_producer(Producer *object); virtual int on_end_producer(Producer *object); virtual int on_start_playlist(Playlist *object); virtual int on_end_playlist(Playlist *object); virtual int on_start_tractor(Tractor *object); virtual int on_end_tractor(Tractor *object); virtual int on_start_multitrack(Multitrack *object); virtual int on_end_multitrack(Multitrack *object); virtual int on_start_track(); virtual int on_end_track(); virtual int on_start_filter(Filter *object); virtual int on_end_filter(Filter *object); virtual int on_start_transition(Transition *object); virtual int on_end_transition(Transition *object); virtual int on_start_chain(Chain *object); virtual int on_end_chain(Chain *object); virtual int on_start_link(Link *object); virtual int on_end_link(Link *object); }; } // namespace Mlt #endif mlt-7.22.0/src/mlt++/MltPlaylist.cpp000664 000000 000000 00000020035 14531534050 017051 0ustar00rootroot000000 000000 /** * MltPlaylist.cpp - MLT Wrapper * Copyright (C) 2004-2015 Meltytech, LLC * Author: Charles Yates * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "MltPlaylist.h" #include "MltProfile.h" #include "MltTransition.h" #include #include using namespace Mlt; ClipInfo::ClipInfo() : clip(0) , producer(NULL) , cut(NULL) , start(0) , resource(NULL) , frame_in(0) , frame_out(0) , frame_count(0) , length(0) , fps(0) , repeat(0) {} ClipInfo::ClipInfo(mlt_playlist_clip_info *info) : clip(info->clip) , producer(new Producer(info->producer)) , cut(new Producer(info->cut)) , start(info->start) , resource(info->resource ? strdup(info->resource) : 0) , frame_in(info->frame_in) , frame_out(info->frame_out) , frame_count(info->frame_count) , length(info->length) , fps(info->fps) , repeat(info->repeat) {} ClipInfo::~ClipInfo() { delete producer; delete cut; free(resource); } void ClipInfo::update(mlt_playlist_clip_info *info) { delete producer; delete cut; free(resource); clip = info->clip; producer = new Producer(info->producer); cut = new Producer(info->cut); start = info->start; resource = info->resource ? strdup(info->resource) : 0; frame_in = info->frame_in; frame_out = info->frame_out; frame_count = info->frame_count; length = info->length; fps = info->fps; repeat = info->repeat; } Playlist::Playlist() : instance(NULL) { instance = mlt_playlist_new(nullptr); } Playlist::Playlist(Profile &profile) : instance(NULL) { instance = mlt_playlist_new(profile.get_profile()); } Playlist::Playlist(Service &producer) : instance(NULL) { if (producer.type() == mlt_service_playlist_type) { instance = (mlt_playlist) producer.get_service(); inc_ref(); } } Playlist::Playlist(Playlist &playlist) : Mlt::Producer(playlist) , instance(playlist.get_playlist()) { inc_ref(); } Playlist::Playlist(mlt_playlist playlist) : instance(playlist) { inc_ref(); } Playlist::~Playlist() { mlt_playlist_close(instance); } mlt_playlist Playlist::get_playlist() { return instance; } mlt_producer Playlist::get_producer() { return mlt_playlist_producer(get_playlist()); } int Playlist::count() { return mlt_playlist_count(get_playlist()); } int Playlist::clear() { return mlt_playlist_clear(get_playlist()); } int Playlist::append(Producer &producer, int in, int out) { return mlt_playlist_append_io(get_playlist(), producer.get_producer(), in, out); } int Playlist::blank(int out) { return mlt_playlist_blank(get_playlist(), out); } int Playlist::blank(const char *length) { return mlt_playlist_blank_time(get_playlist(), length); } int Playlist::clip(mlt_whence whence, int index) { return mlt_playlist_clip(get_playlist(), whence, index); } int Playlist::current_clip() { return mlt_playlist_current_clip(get_playlist()); } Producer *Playlist::current() { return new Producer(mlt_playlist_current(get_playlist())); } ClipInfo *Playlist::clip_info(int index, ClipInfo *info) { mlt_playlist_clip_info clip_info; if (mlt_playlist_get_clip_info(get_playlist(), &clip_info, index)) return NULL; if (info == NULL) return new ClipInfo(&clip_info); info->update(&clip_info); return info; } void Playlist::delete_clip_info(ClipInfo *info) { delete info; } int Playlist::insert(Producer &producer, int where, int in, int out) { return mlt_playlist_insert(get_playlist(), producer.get_producer(), where, in, out); } int Playlist::remove(int where) { return mlt_playlist_remove(get_playlist(), where); } int Playlist::move(int from, int to) { return mlt_playlist_move(get_playlist(), from, to); } int Playlist::reorder(const int *indices) { return mlt_playlist_reorder(get_playlist(), indices); } int Playlist::resize_clip(int clip, int in, int out) { return mlt_playlist_resize_clip(get_playlist(), clip, in, out); } int Playlist::split(int clip, int position) { return mlt_playlist_split(get_playlist(), clip, position); } int Playlist::split_at(int position, bool left) { return mlt_playlist_split_at(get_playlist(), position, left); } int Playlist::join(int clip, int count, int merge) { return mlt_playlist_join(get_playlist(), clip, count, merge); } int Playlist::mix(int clip, int length, Transition *transition) { return mlt_playlist_mix(get_playlist(), clip, length, transition == NULL ? NULL : transition->get_transition()); } int Playlist::mix_in(int clip, int length) { return mlt_playlist_mix_in(get_playlist(), clip, length); } int Playlist::mix_out(int clip, int length) { return mlt_playlist_mix_out(get_playlist(), clip, length); } int Playlist::mix_add(int clip, Transition *transition) { return mlt_playlist_mix_add(get_playlist(), clip, transition == NULL ? NULL : transition->get_transition()); } int Playlist::repeat(int clip, int count) { return mlt_playlist_repeat_clip(get_playlist(), clip, count); } Producer *Playlist::get_clip(int clip) { mlt_producer producer = mlt_playlist_get_clip(get_playlist(), clip); return producer != NULL ? new Producer(producer) : NULL; } Producer *Playlist::get_clip_at(int position) { mlt_producer producer = mlt_playlist_get_clip_at(get_playlist(), position); return producer != NULL ? new Producer(producer) : NULL; } int Playlist::get_clip_index_at(int position) { return mlt_playlist_get_clip_index_at(get_playlist(), position); } bool Playlist::is_mix(int clip) { return mlt_playlist_clip_is_mix(get_playlist(), clip) != 0; } bool Playlist::is_blank(int clip) { return mlt_playlist_is_blank(get_playlist(), clip) != 0; } bool Playlist::is_blank_at(int position) { return mlt_playlist_is_blank_at(get_playlist(), position) != 0; } Producer *Playlist::replace_with_blank(int clip) { mlt_producer producer = mlt_playlist_replace_with_blank(get_playlist(), clip); Producer *object = producer != NULL ? new Producer(producer) : NULL; mlt_producer_close(producer); return object; } void Playlist::consolidate_blanks(int keep_length) { return mlt_playlist_consolidate_blanks(get_playlist(), keep_length); } void Playlist::insert_blank(int clip, int out) { mlt_playlist_insert_blank(get_playlist(), clip, out); } void Playlist::pad_blanks(int position, int length, int find) { mlt_playlist_pad_blanks(get_playlist(), position, length, find); } int Playlist::insert_at(int position, Producer *producer, int mode) { return mlt_playlist_insert_at(get_playlist(), position, producer->get_producer(), mode); } int Playlist::insert_at(int position, Producer &producer, int mode) { return mlt_playlist_insert_at(get_playlist(), position, producer.get_producer(), mode); } int Playlist::clip_start(int clip) { return mlt_playlist_clip_start(get_playlist(), clip); } int Playlist::blanks_from(int clip, int bounded) { return mlt_playlist_blanks_from(get_playlist(), clip, bounded); } int Playlist::clip_length(int clip) { return mlt_playlist_clip_length(get_playlist(), clip); } int Playlist::remove_region(int position, int length) { return mlt_playlist_remove_region(get_playlist(), position, length); } mlt-7.22.0/src/mlt++/MltPlaylist.h000664 000000 000000 00000006654 14531534050 016531 0ustar00rootroot000000 000000 /** * MltPlaylist.h - MLT Wrapper * Copyright (C) 2004-2015 Meltytech, LLC * Author: Charles Yates * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef MLTPP_PLAYLIST_H #define MLTPP_PLAYLIST_H #include "MltConfig.h" #include #include "MltProducer.h" namespace Mlt { class Producer; class Service; class Playlist; class Transition; class Profile; class MLTPP_DECLSPEC ClipInfo { public: ClipInfo(); ClipInfo(mlt_playlist_clip_info *info); ~ClipInfo(); void update(mlt_playlist_clip_info *info); int clip; Producer *producer; Producer *cut; int start; char *resource; int frame_in; int frame_out; int frame_count; int length; float fps; int repeat; }; class MLTPP_DECLSPEC Playlist : public Producer { private: mlt_playlist instance; public: Playlist(); Playlist(Profile &profile); Playlist(Service &playlist); Playlist(Playlist &playlist); Playlist(mlt_playlist playlist); virtual ~Playlist(); virtual mlt_playlist get_playlist(); mlt_producer get_producer() override; int count(); int clear(); int append(Producer &producer, int in = -1, int out = -1); int blank(int out); int blank(const char *length); int clip(mlt_whence whence, int index); int current_clip(); Producer *current(); ClipInfo *clip_info(int index, ClipInfo *info = NULL); static void delete_clip_info(ClipInfo *info); int insert(Producer &producer, int where, int in = -1, int out = -1); int remove(int where); int move(int from, int to); int reorder(const int *indices); int resize_clip(int clip, int in, int out); int split(int clip, int position); int split_at(int position, bool left = true); int join(int clip, int count = 1, int merge = 1); int mix(int clip, int length, Transition *transition = NULL); int mix_in(int clip, int length); int mix_out(int clip, int length); int mix_add(int clip, Transition *transition); int repeat(int clip, int count); Producer *get_clip(int clip); Producer *get_clip_at(int position); int get_clip_index_at(int position); bool is_mix(int clip); bool is_blank(int clip); bool is_blank_at(int position); void consolidate_blanks(int keep_length = 0); Producer *replace_with_blank(int clip); void insert_blank(int clip, int out); void pad_blanks(int position, int length, int find = 0); int insert_at(int position, Producer *producer, int mode = 0); int insert_at(int position, Producer &producer, int mode = 0); int clip_start(int clip); int clip_length(int clip); int blanks_from(int clip, int bounded = 0); int remove_region(int position, int length); }; } // namespace Mlt #endif mlt-7.22.0/src/mlt++/MltProducer.cpp000664 000000 000000 00000014135 14531534050 017037 0ustar00rootroot000000 000000 /** * MltProducer.cpp - MLT Wrapper * Copyright (C) 2004-2019 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "MltProducer.h" #include "MltConsumer.h" #include "MltEvent.h" #include "MltFilter.h" #include "MltProfile.h" using namespace Mlt; Producer::Producer() : instance(NULL) , parent_(NULL) {} Producer::Producer(Profile &profile, const char *id, const char *service) : Producer(profile.get_profile(), id, service) {} Producer::Producer(mlt_profile profile, const char *id, const char *service) : instance(NULL) , parent_(NULL) { if (id != NULL && service != NULL) instance = mlt_factory_producer(profile, id, service); else instance = mlt_factory_producer(profile, NULL, id != NULL ? id : service); } Producer::Producer(Service &producer) : instance(NULL) , parent_(NULL) { mlt_service_type type = producer.type(); if (type == mlt_service_producer_type || type == mlt_service_playlist_type || type == mlt_service_tractor_type || type == mlt_service_multitrack_type || type == mlt_service_transition_type || type == mlt_service_chain_type || type == mlt_service_link_type) { instance = (mlt_producer) producer.get_service(); inc_ref(); } } Producer::Producer(mlt_producer producer) : instance(producer) , parent_(NULL) { inc_ref(); } Producer::Producer(Producer &producer) : Mlt::Service(producer) , instance(producer.get_producer()) , parent_(NULL) { inc_ref(); } Producer::Producer(const Producer &producer) : Producer(const_cast(producer)) {} Producer::Producer(Producer *producer) : instance(producer != NULL ? producer->get_producer() : NULL) , parent_(NULL) { if (is_valid()) inc_ref(); } Producer::~Producer() { delete parent_; mlt_producer_close(instance); instance = NULL; } Producer &Producer::operator=(const Producer &producer) { if (this != &producer) { delete parent_; parent_ = nullptr; mlt_producer_close(instance); instance = producer.instance; inc_ref(); } return *this; } mlt_producer Producer::get_producer() { return instance; } mlt_producer Producer::get_parent() { return get_producer() != NULL && mlt_producer_cut_parent(get_producer()) != NULL ? mlt_producer_cut_parent(get_producer()) : get_producer(); } Producer &Producer::parent() { if (is_cut() && parent_ == NULL) parent_ = new Producer(get_parent()); return parent_ == NULL ? *this : *parent_; } mlt_service Producer::get_service() { return mlt_producer_service(get_producer()); } int Producer::seek(int position) { return mlt_producer_seek(get_producer(), position); } int Producer::seek(const char *time) { return mlt_producer_seek_time(get_producer(), time); } int Producer::position() { return mlt_producer_position(get_producer()); } int Producer::frame() { return mlt_producer_frame(get_producer()); } char *Producer::frame_time(mlt_time_format format) { return mlt_producer_frame_time(get_producer(), format); } int Producer::set_speed(double speed) { return mlt_producer_set_speed(get_producer(), speed); } int Producer::pause() { int result = 0; if (get_speed() != 0) { Consumer consumer((mlt_consumer) mlt_service_consumer(get_service())); Event *event = consumer.setup_wait_for("consumer-sdl-paused"); result = mlt_producer_set_speed(get_producer(), 0); if (result == 0 && consumer.is_valid() && !consumer.is_stopped()) consumer.wait_for(event); delete event; } return result; } double Producer::get_speed() { return mlt_producer_get_speed(get_producer()); } double Producer::get_fps() { return mlt_producer_get_fps(get_producer()); } int Producer::set_in_and_out(int in, int out) { return mlt_producer_set_in_and_out(get_producer(), in, out); } int Producer::get_in() { return mlt_producer_get_in(get_producer()); } int Producer::get_out() { return mlt_producer_get_out(get_producer()); } int Producer::get_length() { return mlt_producer_get_length(get_producer()); } char *Producer::get_length_time(mlt_time_format format) { return mlt_producer_get_length_time(get_producer(), format); } int Producer::get_playtime() { return mlt_producer_get_playtime(get_producer()); } Producer *Producer::cut(int in, int out) { mlt_producer producer = mlt_producer_cut(get_producer(), in, out); Producer *result = new Producer(producer); mlt_producer_close(producer); return result; } bool Producer::is_cut() { return mlt_producer_is_cut(get_producer()) != 0; } bool Producer::is_blank() { return mlt_producer_is_blank(get_producer()) != 0; } bool Producer::same_clip(Producer &that) { return mlt_producer_cut_parent(get_producer()) == mlt_producer_cut_parent(that.get_producer()); } bool Producer::runs_into(Producer &that) { return same_clip(that) && get_out() == (that.get_in() - 1); } void Producer::optimise() { mlt_producer_optimise(get_producer()); } int Producer::clear() { return mlt_producer_clear(get_producer()); } int64_t Producer::get_creation_time() { return mlt_producer_get_creation_time(get_producer()); } void Producer::set_creation_time(int64_t creation_time) { mlt_producer_set_creation_time(get_producer(), creation_time); } bool Producer::probe() { return mlt_producer_probe(get_producer()); } mlt-7.22.0/src/mlt++/MltProducer.h000664 000000 000000 00000004651 14531534050 016506 0ustar00rootroot000000 000000 /** * MltProducer.h - MLT Wrapper * Copyright (C) 2004-2019 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef MLTPP_PRODUCER_H #define MLTPP_PRODUCER_H #include "MltConfig.h" #include #include "MltService.h" namespace Mlt { class Service; class Filter; class Profile; class Frame; class MLTPP_DECLSPEC Producer : public Service { private: mlt_producer instance; Producer *parent_; public: Producer(); Producer(Profile &profile, const char *id, const char *service = NULL); Producer(mlt_profile profile, const char *id, const char *service = NULL); Producer(Service &producer); Producer(mlt_producer producer); Producer(Producer &producer); Producer(const Producer &producer); Producer(Producer *producer); virtual ~Producer(); Producer &operator=(const Producer &producer); virtual mlt_producer get_producer(); Producer &parent(); mlt_producer get_parent(); mlt_service get_service() override; int seek(int position); int seek(const char *time); int position(); int frame(); char *frame_time(mlt_time_format = mlt_time_smpte_df); int set_speed(double speed); int pause(); double get_speed(); double get_fps(); int set_in_and_out(int in, int out); int get_in(); int get_out(); int get_length(); char *get_length_time(mlt_time_format = mlt_time_smpte_df); int get_playtime(); Producer *cut(int in = 0, int out = -1); bool is_cut(); bool is_blank(); bool same_clip(Producer &that); bool runs_into(Producer &that); void optimise(); int clear(); int64_t get_creation_time(); void set_creation_time(int64_t creation_time); bool probe(); }; } // namespace Mlt #endif mlt-7.22.0/src/mlt++/MltProfile.cpp000664 000000 000000 00000007554 14531534050 016663 0ustar00rootroot000000 000000 /** * MltProfile.cpp - MLT Wrapper * Copyright (C) 2008-2020 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "MltProfile.h" #include "MltProducer.h" #include "MltProperties.h" using namespace Mlt; Profile::Profile() : instance(NULL) { instance = mlt_profile_init(NULL); } Profile::Profile(const char *name) : instance(NULL) { instance = mlt_profile_init(name); } Profile::Profile(Properties &properties) : instance(NULL) { instance = mlt_profile_load_properties(properties.get_properties()); } Profile::Profile(mlt_profile profile) : instance(profile) {} Profile::~Profile() { if (instance) mlt_profile_close(instance); instance = NULL; } bool Profile::is_valid() const { return instance != NULL; } mlt_profile Profile::get_profile() const { return instance; } char *Profile::description() const { return instance->description; } int Profile::frame_rate_num() const { return instance->frame_rate_num; } int Profile::frame_rate_den() const { return instance->frame_rate_den; } double Profile::fps() const { return mlt_profile_fps(instance); } int Profile::width() const { return instance->width; } int Profile::height() const { return instance->height; } bool Profile::progressive() const { return instance->progressive; } int Profile::sample_aspect_num() const { return instance->sample_aspect_num; } int Profile::sample_aspect_den() const { return instance->sample_aspect_den; } double Profile::sar() const { return mlt_profile_sar(instance); } int Profile::display_aspect_num() const { return instance->display_aspect_num; } int Profile::display_aspect_den() const { return instance->display_aspect_den; } double Profile::dar() const { return mlt_profile_dar(instance); } int Profile::is_explicit() const { return instance->is_explicit; } int Profile::colorspace() const { return instance->colorspace; } Properties *Profile::list() { return new Properties(mlt_profile_list()); } void Profile::from_producer(Producer &producer) { mlt_profile_from_producer(instance, producer.get_producer()); } void Profile::set_width(int width) { instance->width = width; } void Profile::set_height(int height) { instance->height = height; } void Profile::set_sample_aspect(int numerator, int denominator) { instance->sample_aspect_num = numerator; instance->sample_aspect_den = denominator; } void Profile::set_display_aspect(int numerator, int denominator) { instance->display_aspect_num = numerator; instance->display_aspect_den = denominator; } void Profile::set_progressive(int progressive) { instance->progressive = progressive; } void Profile::set_colorspace(int colorspace) { instance->colorspace = colorspace; } void Profile::set_frame_rate(int numerator, int denominator) { instance->frame_rate_num = numerator; instance->frame_rate_den = denominator; } void Profile::set_explicit(int boolean) { instance->is_explicit = boolean; } double Profile::scale_width(int width) { return mlt_profile_scale_width(instance, width); } double Profile::scale_height(int height) { return mlt_profile_scale_height(instance, height); } mlt-7.22.0/src/mlt++/MltProfile.h000664 000000 000000 00000004343 14531534050 016321 0ustar00rootroot000000 000000 /** * MltProfile.h - MLT Wrapper * Copyright (C) 2008-2020 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef MLTPP_PROFILE_H #define MLTPP_PROFILE_H #include "MltConfig.h" #ifdef SWIG #define MLTPP_DECLSPEC #endif #include namespace Mlt { class Properties; class Producer; class MLTPP_DECLSPEC Profile { private: mlt_profile instance; public: Profile(); Profile(const char *name); Profile(Properties &properties); Profile(mlt_profile profile); ~Profile(); bool is_valid() const; mlt_profile get_profile() const; char *description() const; int frame_rate_num() const; int frame_rate_den() const; double fps() const; int width() const; int height() const; bool progressive() const; int sample_aspect_num() const; int sample_aspect_den() const; double sar() const; int display_aspect_num() const; int display_aspect_den() const; double dar() const; int is_explicit() const; int colorspace() const; static Properties *list(); void from_producer(Producer &producer); void set_width(int width); void set_height(int height); void set_sample_aspect(int numerator, int denominator); void set_display_aspect(int numerator, int denominator); void set_progressive(int progressive); void set_colorspace(int colorspace); void set_frame_rate(int numerator, int denominator); void set_explicit(int boolean); double scale_width(int width); double scale_height(int height); }; } // namespace Mlt #endif mlt-7.22.0/src/mlt++/MltProperties.cpp000664 000000 000000 00000030747 14531534050 017417 0ustar00rootroot000000 000000 /** * MltProperties.cpp - MLT Wrapper * Copyright (C) 2004-2022 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "MltProperties.h" #include "MltAnimation.h" #include "MltEvent.h" using namespace Mlt; Properties::Properties() : instance(NULL) { instance = mlt_properties_new(); } Properties::Properties(bool /*dummy*/) : instance(NULL) {} Properties::Properties(Properties &properties) : instance(properties.get_properties()) { inc_ref(); } Properties::Properties(const Properties &properties) : Properties(const_cast(properties)) {} Properties::Properties(mlt_properties properties) : instance(properties) { inc_ref(); } Properties::Properties(void *properties) : instance(mlt_properties(properties)) { inc_ref(); } Properties::Properties(const char *file) : instance(NULL) { instance = mlt_properties_load(file); } Properties::~Properties() { mlt_properties_close(instance); } Properties &Properties::operator=(const Properties &properties) { if (this != &properties) { mlt_properties_close(instance); instance = properties.instance; inc_ref(); } return *this; } mlt_properties Properties::get_properties() { return instance; } int Properties::inc_ref() { return mlt_properties_inc_ref(get_properties()); } int Properties::dec_ref() { return mlt_properties_dec_ref(get_properties()); } int Properties::ref_count() { return mlt_properties_ref_count(get_properties()); } void Properties::lock() { mlt_properties_lock(get_properties()); } void Properties::unlock() { mlt_properties_unlock(get_properties()); } void Properties::block(void *object) { mlt_events_block(get_properties(), object != NULL ? object : get_properties()); } void Properties::unblock(void *object) { mlt_events_unblock(get_properties(), object != NULL ? object : get_properties()); } int Properties::fire_event(const char *event) { return mlt_events_fire(get_properties(), event, mlt_event_data_none()); } bool Properties::is_valid() { return get_properties() != NULL; } int Properties::count() { return mlt_properties_count(get_properties()); } char *Properties::get(const char *name) { return mlt_properties_get(get_properties(), name); } int Properties::get_int(const char *name) { return mlt_properties_get_int(get_properties(), name); } int64_t Properties::get_int64(const char *name) { return mlt_properties_get_int64(get_properties(), name); } double Properties::get_double(const char *name) { return mlt_properties_get_double(get_properties(), name); } void *Properties::get_data(const char *name, int &size) { return mlt_properties_get_data(get_properties(), name, &size); } void *Properties::get_data(const char *name) { return mlt_properties_get_data(get_properties(), name, NULL); } int Properties::set(const char *name, const char *value) { return mlt_properties_set(get_properties(), name, value); } int Properties::set_string(const char *name, const char *value) { return mlt_properties_set_string(get_properties(), name, value); } int Properties::set(const char *name, int value) { return mlt_properties_set_int(get_properties(), name, value); } int Properties::set(const char *name, int64_t value) { return mlt_properties_set_int64(get_properties(), name, value); } int Properties::set(const char *name, double value) { return mlt_properties_set_double(get_properties(), name, value); } int Properties::set( const char *name, void *value, int size, mlt_destructor destructor, mlt_serialiser serialiser) { return mlt_properties_set_data(get_properties(), name, value, size, destructor, serialiser); } int Properties::copy(Properties &that, const char *prefix) { return mlt_properties_copy(get_properties(), that.get_properties(), prefix); } void Properties::pass_property(Properties &that, const char *name) { return mlt_properties_pass_property(get_properties(), that.get_properties(), name); } int Properties::pass_values(Properties &that, const char *prefix) { return mlt_properties_pass(get_properties(), that.get_properties(), prefix); } int Properties::pass_list(Properties &that, const char *list) { return mlt_properties_pass_list(get_properties(), that.get_properties(), list); } int Properties::parse(const char *namevalue) { return mlt_properties_parse(get_properties(), namevalue); } char *Properties::get_name(int index) { return mlt_properties_get_name(get_properties(), index); } char *Properties::get(int index) { return mlt_properties_get_value(get_properties(), index); } char *Properties::get(int index, mlt_time_format format) { return mlt_properties_get_value_tf(get_properties(), index, format); } void *Properties::get_data(int index, int &size) { return mlt_properties_get_data_at(get_properties(), index, &size); } void Properties::mirror(Properties &that) { mlt_properties_mirror(get_properties(), that.get_properties()); } int Properties::inherit(Properties &that) { return mlt_properties_inherit(get_properties(), that.get_properties()); } int Properties::rename(const char *source, const char *dest) { return mlt_properties_rename(get_properties(), source, dest); } void Properties::dump(FILE *output) { mlt_properties_dump(get_properties(), output); } void Properties::debug(const char *title, FILE *output) { mlt_properties_debug(get_properties(), title, output); } void Properties::load(const char *file) { mlt_properties properties = mlt_properties_load(file); if (properties != NULL) mlt_properties_pass(get_properties(), properties, ""); mlt_properties_close(properties); } int Properties::save(const char *file) { return mlt_properties_save(get_properties(), file); } Event *Properties::listen(const char *id, void *object, mlt_listener listener) { mlt_event event = mlt_events_listen(get_properties(), object, id, listener); return new Event(event); } Event *Properties::setup_wait_for(const char *id) { return new Event(mlt_events_setup_wait_for(get_properties(), id)); } void Properties::delete_event(Event *event) { delete event; } void Properties::wait_for(Event *event, bool destroy) { mlt_events_wait_for(get_properties(), event->get_event()); if (destroy) mlt_events_close_wait_for(get_properties(), event->get_event()); } void Properties::wait_for(const char *id) { Event *event = setup_wait_for(id); wait_for(event); delete event; } bool Properties::is_sequence() { return mlt_properties_is_sequence(get_properties()); } Properties *Properties::parse_yaml(const char *file) { return new Properties(mlt_properties_parse_yaml(file)); } char *Properties::serialise_yaml() { return mlt_properties_serialise_yaml(get_properties()); } int Properties::preset(const char *name) { return mlt_properties_preset(get_properties(), name); } int Properties::set_lcnumeric(const char *locale) { return mlt_properties_set_lcnumeric(get_properties(), locale); } const char *Properties::get_lcnumeric() { return mlt_properties_get_lcnumeric(get_properties()); } void Properties::clear(const char *name) { return mlt_properties_clear(get_properties(), name); } bool Properties::property_exists(const char *name) { return mlt_properties_exists(get_properties(), name); } char *Properties::get_time(const char *name, mlt_time_format format) { return mlt_properties_get_time(get_properties(), name, format); } char *Properties::frames_to_time(int frames, mlt_time_format format) { return mlt_properties_frames_to_time(get_properties(), frames, format); } int Properties::time_to_frames(const char *time) { return mlt_properties_time_to_frames(get_properties(), time); } mlt_color Properties::get_color(const char *name) { return mlt_properties_get_color(get_properties(), name); } int Properties::set(const char *name, mlt_color value) { return mlt_properties_set_color(get_properties(), name, value); } mlt_color Properties::anim_get_color(const char *name, int position, int length) { return mlt_properties_anim_get_color(get_properties(), name, position, length); } int Properties::anim_set( const char *name, mlt_color value, int position, int length, mlt_keyframe_type keyframe_type) { return mlt_properties_anim_set_color(get_properties(), name, value, position, length, keyframe_type); } char *Properties::anim_get(const char *name, int position, int length) { return mlt_properties_anim_get(get_properties(), name, position, length); } int Properties::anim_set(const char *name, const char *value, int position, int length) { return mlt_properties_anim_set(get_properties(), name, value, position, length); } int Properties::anim_get_int(const char *name, int position, int length) { return mlt_properties_anim_get_int(get_properties(), name, position, length); } int Properties::anim_set( const char *name, int value, int position, int length, mlt_keyframe_type keyframe_type) { return mlt_properties_anim_set_int(get_properties(), name, value, position, length, keyframe_type); } double Properties::anim_get_double(const char *name, int position, int length) { return mlt_properties_anim_get_double(get_properties(), name, position, length); } int Properties::anim_set( const char *name, double value, int position, int length, mlt_keyframe_type keyframe_type) { return mlt_properties_anim_set_double(get_properties(), name, value, position, length, keyframe_type); } int Properties::set(const char *name, mlt_rect value) { return mlt_properties_set_rect(get_properties(), name, value); } int Properties::set(const char *name, double x, double y, double w, double h, double opacity) { mlt_rect value = {x, y, w, h, opacity}; return mlt_properties_set_rect(get_properties(), name, value); } mlt_rect Properties::get_rect(const char *name) { return mlt_properties_get_rect(get_properties(), name); } int Properties::anim_set( const char *name, mlt_rect value, int position, int length, mlt_keyframe_type keyframe_type) { return mlt_properties_anim_set_rect(get_properties(), name, value, position, length, keyframe_type); } mlt_rect Properties::anim_get_rect(const char *name, int position, int length) { return mlt_properties_anim_get_rect(get_properties(), name, position, length); } mlt_animation Properties::get_animation(const char *name) { return mlt_properties_get_animation(get_properties(), name); } Animation *Properties::get_anim(const char *name) { return new Animation(mlt_properties_get_animation(get_properties(), name)); } bool Properties::is_anim(const char *name) { return mlt_properties_is_anim(get_properties(), name); } int Properties::set(const char *name, Properties &properties) { return mlt_properties_set_properties(get_properties(), name, properties.get_properties()); } Properties *Properties::get_props(const char *name) { return new Properties(mlt_properties_get_properties(get_properties(), name)); } Properties *Properties::get_props_at(int index) { return new Properties(mlt_properties_get_properties_at(get_properties(), index)); } mlt-7.22.0/src/mlt++/MltProperties.h000664 000000 000000 00000013001 14531534050 017044 0ustar00rootroot000000 000000 /** * MltProperties.h - MLT Wrapper * Copyright (C) 2004-2022 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef MLTPP_PROPERTIES_H #define MLTPP_PROPERTIES_H #include "MltConfig.h" #include #include namespace Mlt { class Event; class Animation; /** Abstract Properties class. */ class MLTPP_DECLSPEC Properties { private: mlt_properties instance; public: Properties(); Properties(bool dummy); Properties(Properties &properties); Properties(const Properties &properties); Properties(mlt_properties properties); Properties(void *properties); Properties(const char *file); virtual ~Properties(); Properties &operator=(const Properties &properties); virtual mlt_properties get_properties(); int inc_ref(); int dec_ref(); int ref_count(); void lock(); void unlock(); void block(void *object = NULL); void unblock(void *object = NULL); int fire_event(const char *event); bool is_valid(); int count(); char *get(const char *name); int get_int(const char *name); int64_t get_int64(const char *name); double get_double(const char *name); void *get_data(const char *name, int &size); void *get_data(const char *name); int set(const char *name, const char *value); int set_string(const char *name, const char *value); int set(const char *name, int value); int set(const char *name, int64_t value); int set(const char *name, double value); int set(const char *name, void *value, int size, mlt_destructor destroy = NULL, mlt_serialiser serial = NULL); int copy(Properties &that, const char *prefix); void pass_property(Properties &that, const char *name); int pass_values(Properties &that, const char *prefix); int pass_list(Properties &that, const char *list); int parse(const char *namevalue); char *get_name(int index); char *get(int index); char *get(int index, mlt_time_format); void *get_data(int index, int &size); void mirror(Properties &that); int inherit(Properties &that); int rename(const char *source, const char *dest); void dump(FILE *output = stderr); void debug(const char *title = "Object", FILE *output = stderr); void load(const char *file); int save(const char *file); Event *listen(const char *id, void *object, mlt_listener); static void delete_event(Event *); Event *setup_wait_for(const char *id); void wait_for(Event *, bool destroy = true); void wait_for(const char *id); bool is_sequence(); static Properties *parse_yaml(const char *file); char *serialise_yaml(); int preset(const char *name); int set_lcnumeric(const char *locale); const char *get_lcnumeric(); void clear(const char *name); bool property_exists(const char *name); char *get_time(const char *name, mlt_time_format = mlt_time_smpte_df); char *frames_to_time(int, mlt_time_format = mlt_time_smpte_df); int time_to_frames(const char *time); mlt_color get_color(const char *name); int set(const char *name, mlt_color value); mlt_color anim_get_color(const char *name, int position, int length = 0); int anim_set(const char *name, mlt_color value, int position, int length = 0, mlt_keyframe_type keyframe_type = mlt_keyframe_linear); char *anim_get(const char *name, int position, int length = 0); int anim_set(const char *name, const char *value, int position, int length = 0); int anim_get_int(const char *name, int position, int length = 0); int anim_set(const char *name, int value, int position, int length = 0, mlt_keyframe_type keyframe_type = mlt_keyframe_linear); double anim_get_double(const char *name, int position, int length = 0); int anim_set(const char *name, double value, int position, int length = 0, mlt_keyframe_type keyframe_type = mlt_keyframe_linear); int set(const char *name, mlt_rect value); int set(const char *name, double x, double y, double w, double h, double opacity = 1.0); mlt_rect get_rect(const char *name); int anim_set(const char *name, mlt_rect value, int position, int length = 0, mlt_keyframe_type keyframe_type = mlt_keyframe_linear); mlt_rect anim_get_rect(const char *name, int position, int length = 0); mlt_animation get_animation(const char *name); Animation *get_anim(const char *name); bool is_anim(const char *name); int set(const char *name, Properties &properties); Properties *get_props(const char *name); Properties *get_props_at(int index); }; } // namespace Mlt #endif mlt-7.22.0/src/mlt++/MltPushConsumer.cpp000664 000000 000000 00000010631 14531534050 017704 0ustar00rootroot000000 000000 /** * MltPushConsumer.cpp - MLT Wrapper * Copyright (C) 2004-2022 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "MltPushConsumer.h" #include "MltFilter.h" using namespace Mlt; namespace Mlt { class PushPrivate { public: PushPrivate() {} }; } // namespace Mlt static void filter_destructor(void *arg) { Filter *filter = (Filter *) arg; delete filter; } PushConsumer::PushConsumer(Profile &profile, const char *id, const char *service) : Consumer(profile, id, service) , m_private(new PushPrivate()) { if (is_valid()) { // Set up push mode (known as put mode in mlt) set("real_time", 0); set("put_mode", 1); set("terminate_on_pause", 0); set("buffer", 0); // We might need resize and rescale filters so we'll create them now // NB: Try to use the best rescaler available here Filter *resize = new Filter(profile, "resize"); Filter *rescale = new Filter(profile, "mcrescale"); if (!rescale->is_valid()) { delete rescale; rescale = new Filter(profile, "gtkrescale"); } if (!rescale->is_valid()) { delete rescale; rescale = new Filter(profile, "rescale"); } Filter *convert = new Filter(profile, "avcolour_space"); set("filter_convert", convert, 0, filter_destructor); set("filter_resize", resize, 0, filter_destructor); set("filter_rescale", rescale, 0, filter_destructor); } } PushConsumer::~PushConsumer() {} void PushConsumer::set_render(int width, int height, double aspect_ratio) { set("render_width", width); set("render_height", height); set("render_aspect_ratio", aspect_ratio); } int PushConsumer::connect(Service & /*service*/) { return -1; } int PushConsumer::push(Frame *frame) { frame->inc_ref(); // Here we have the option to process the frame at a render resolution (this will // typically be PAL or NTSC) prior to scaling according to the consumers profile // This is done to optimise quality, esp. with regard to compositing positions if (get_int("render_width")) { // Process the projects render resolution first mlt_image_format format = mlt_image_yuv422; int w = get_int("render_width"); int h = get_int("render_height"); frame->set("consumer_aspect_ratio", get_double("render_aspect_ratio")); frame->set("consumer.progressive", get_int("progressive") | get_int("deinterlace")); frame->set("consumer.deinterlacer", get("deinterlacer") ? get("deinterlacer") : get("deinterlace_method")); frame->set("consumer.rescale", get("rescale")); // Render the frame frame->get_image(format, w, h); // Now set up the post image scaling Filter *convert = (Filter *) get_data("filter_convert"); mlt_filter_process(convert->get_filter(), frame->get_frame()); Filter *rescale = (Filter *) get_data("filter_rescale"); mlt_filter_process(rescale->get_filter(), frame->get_frame()); Filter *resize = (Filter *) get_data("filter_resize"); mlt_filter_process(resize->get_filter(), frame->get_frame()); } return mlt_consumer_put_frame((mlt_consumer) get_service(), frame->get_frame()); } int PushConsumer::push(Frame &frame) { return push(&frame); } int PushConsumer::drain() { return 0; } // Convenience function - generates a frame with an image of a given size Frame *PushConsumer::construct(int size) { mlt_frame f = mlt_frame_init(get_service()); Frame *frame = new Frame(f); uint8_t *buffer = (uint8_t *) mlt_pool_alloc(size); frame->set("image", buffer, size, mlt_pool_release); mlt_frame_close(f); return frame; } mlt-7.22.0/src/mlt++/MltPushConsumer.h000664 000000 000000 00000002767 14531534050 017364 0ustar00rootroot000000 000000 /** * MltPushConsumer.h - MLT Wrapper * Copyright (C) 2004-2015 Meltytech, LLC * Author: Charles Yates * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef MLTPP_PUSH_CONSUMER_H #define MLTPP_PUSH_CONSUMER_H #include "MltConfig.h" #include "MltConsumer.h" namespace Mlt { class Frame; class Service; class PushPrivate; class Profile; class MLTPP_DECLSPEC PushConsumer : public Consumer { private: PushPrivate *m_private; public: PushConsumer(Profile &profile, const char *id, const char *service = NULL); virtual ~PushConsumer(); void set_render(int width, int height, double aspect_ratio); virtual int connect(Service &service) override; int push(Frame *frame); int push(Frame &frame); int drain(); Frame *construct(int); }; } // namespace Mlt #endif mlt-7.22.0/src/mlt++/MltRepository.cpp000664 000000 000000 00000005465 14531534050 017441 0ustar00rootroot000000 000000 /** * MltRepository.cpp - MLT Wrapper * Copyright (C) 2008-2017 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "MltRepository.h" #include "MltProfile.h" #include "MltProperties.h" using namespace Mlt; Repository::Repository(const char *directory) : instance(NULL) { instance = mlt_repository_init(directory); } Repository::Repository(mlt_repository repository) : instance(repository) {} Repository::~Repository() { instance = NULL; } void Repository::register_service(mlt_service_type service_type, const char *service, mlt_register_callback symbol) { mlt_repository_register(instance, service_type, service, symbol); } void *Repository::create(Profile &profile, mlt_service_type type, const char *service, void *arg) { return mlt_repository_create(instance, profile.get_profile(), type, service, arg); } Properties *Repository::consumers() const { return new Properties(mlt_repository_consumers(instance)); } Properties *Repository::filters() const { return new Properties(mlt_repository_filters(instance)); } Properties *Repository::links() const { return new Properties(mlt_repository_links(instance)); } Properties *Repository::producers() const { return new Properties(mlt_repository_producers(instance)); } Properties *Repository::transitions() const { return new Properties(mlt_repository_transitions(instance)); } void Repository::register_metadata(mlt_service_type type, const char *service, mlt_metadata_callback callback, void *callback_data) { mlt_repository_register_metadata(instance, type, service, callback, callback_data); } Properties *Repository::metadata(mlt_service_type type, const char *service) const { return new Properties(mlt_repository_metadata(instance, type, service)); } Properties *Repository::languages() const { return new Properties(mlt_repository_languages(instance)); } Properties *Repository::presets() { return new Properties(mlt_repository_presets()); } mlt-7.22.0/src/mlt++/MltRepository.h000664 000000 000000 00000003752 14531534050 017103 0ustar00rootroot000000 000000 /** * MltRepository.h - MLT Wrapper * Copyright (C) 2008-2017 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef MLTPP_REPOSITORY_H #define MLTPP_REPOSITORY_H #include "MltConfig.h" #ifdef SWIG #define MLTPP_DECLSPEC #endif #include namespace Mlt { class Profile; class Properties; class MLTPP_DECLSPEC Repository { private: mlt_repository instance; Repository() {} public: Repository(const char *directory); Repository(mlt_repository repository); ~Repository(); void register_service(mlt_service_type service_type, const char *service, mlt_register_callback symbol); void *create(Profile &profile, mlt_service_type type, const char *service, void *arg); Properties *consumers() const; Properties *filters() const; Properties *links() const; Properties *producers() const; Properties *transitions() const; void register_metadata(mlt_service_type type, const char *service, mlt_metadata_callback, void *callback_data); Properties *metadata(mlt_service_type type, const char *service) const; Properties *languages() const; static Properties *presets(); }; } // namespace Mlt #endif mlt-7.22.0/src/mlt++/MltService.cpp000664 000000 000000 00000007536 14531534050 016663 0ustar00rootroot000000 000000 /** * MltService.cpp - MLT Wrapper * Copyright (C) 2004-2019 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "MltService.h" #include "MltFilter.h" #include "MltProfile.h" #include using namespace Mlt; Service::Service() : Properties(false) , instance(NULL) {} Service::Service(Service &service) : Properties(false) , instance(service.get_service()) { inc_ref(); } Service::Service(const Service &service) : Service(const_cast(service)) {} Service::Service(mlt_service service) : Properties(false) , instance(service) { inc_ref(); } Service::Service(Service *service) : Service(service ? service->get_service() : nullptr) {} Service::~Service() { mlt_service_close(instance); } Service &Service::operator=(const Service &service) { if (this != &service) { mlt_service_close(instance); instance = service.instance; inc_ref(); } return *this; } mlt_service Service::get_service() { return instance; } mlt_properties Service::get_properties() { return mlt_service_properties(get_service()); } void Service::lock() { mlt_service_lock(get_service()); } void Service::unlock() { mlt_service_unlock(get_service()); } int Service::connect_producer(Service &producer, int index) { return mlt_service_connect_producer(get_service(), producer.get_service(), index); } int Mlt::Service::insert_producer(Mlt::Service &producer, int index) { return mlt_service_insert_producer(get_service(), producer.get_service(), index); } int Service::disconnect_producer(int index) { return mlt_service_disconnect_producer(get_service(), index); } int Service::disconnect_all_producers() { return mlt_service_disconnect_all_producers(get_service()); } Service *Service::producer() { return new Service(mlt_service_producer(get_service())); } Service *Service::consumer() { return new Service(mlt_service_consumer(get_service())); } Profile *Service::profile() { return new Profile(mlt_service_profile(get_service())); } mlt_profile Service::get_profile() { return mlt_service_profile(get_service()); } Frame *Service::get_frame(int index) { mlt_frame frame = NULL; mlt_service_get_frame(get_service(), &frame, index); Frame *result = new Frame(frame); mlt_frame_close(frame); return result; } mlt_service_type Service::type() { return mlt_service_identify(get_service()); } int Service::attach(Filter &filter) { return mlt_service_attach(get_service(), filter.get_filter()); } int Service::detach(Filter &filter) { return mlt_service_detach(get_service(), filter.get_filter()); } int Service::filter_count() { return mlt_service_filter_count(get_service()); } int Service::move_filter(int from, int to) { return mlt_service_move_filter(get_service(), from, to); } Filter *Service::filter(int index) { mlt_filter result = mlt_service_filter(get_service(), index); return result == NULL ? NULL : new Filter(result); } void Service::set_profile(mlt_profile profile) { mlt_service_set_profile(get_service(), profile); } void Service::set_profile(Profile &profile) { set_profile(profile.get_profile()); } mlt-7.22.0/src/mlt++/MltService.h000664 000000 000000 00000004132 14531534050 016315 0ustar00rootroot000000 000000 /** * MltService.h - MLT Wrapper * Copyright (C) 2004-2021 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef MLTPP_SERVICE_H #define MLTPP_SERVICE_H #include "MltConfig.h" #include #include "MltFrame.h" #include "MltProperties.h" namespace Mlt { class Properties; class Filter; class Frame; class Profile; class MLTPP_DECLSPEC Service : public Properties { private: mlt_service instance; public: Service(); Service(Service &service); Service(const Service &service); Service(mlt_service service); Service(Service *service); virtual ~Service(); Service &operator=(const Service &service); virtual mlt_service get_service(); void lock(); void unlock(); virtual mlt_properties get_properties() override; int connect_producer(Service &producer, int index = 0); int insert_producer(Service &producer, int index = 0); int disconnect_producer(int index = 0); int disconnect_all_producers(); Service *consumer(); Service *producer(); Profile *profile(); mlt_profile get_profile(); Frame *get_frame(int index = 0); mlt_service_type type(); int attach(Filter &filter); int detach(Filter &filter); int filter_count(); int move_filter(int from, int to); Filter *filter(int index); void set_profile(mlt_profile profile); void set_profile(Profile &profile); }; } // namespace Mlt #endif mlt-7.22.0/src/mlt++/MltTokeniser.cpp000664 000000 000000 00000003032 14531534050 017211 0ustar00rootroot000000 000000 /** * MltTokeniser.cpp - MLT Wrapper * Copyright (C) 2004-2015 Meltytech, LLC * Author: Charles Yates * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "MltTokeniser.h" #include using namespace Mlt; Tokeniser::Tokeniser(char *text, char *delimiter) { tokens = mlt_tokeniser_init(); if (text != NULL) mlt_tokeniser_parse_new(tokens, text, delimiter ? delimiter : " "); } Tokeniser::~Tokeniser() { mlt_tokeniser_close(tokens); } int Tokeniser::parse(char *text, char *delimiter) { return mlt_tokeniser_parse_new(tokens, text, delimiter ? delimiter : " "); } int Tokeniser::count() { return mlt_tokeniser_count(tokens); } char *Tokeniser::get(int index) { return mlt_tokeniser_get_string(tokens, index); } char *Tokeniser::input() { return mlt_tokeniser_get_input(tokens); } mlt-7.22.0/src/mlt++/MltTokeniser.h000664 000000 000000 00000002417 14531534050 016664 0ustar00rootroot000000 000000 /** * MltTokeniser.h - MLT Wrapper * Copyright (C) 2004-2015 Meltytech, LLC * Author: Charles Yates * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef MLTPP_TOKENISER_H #define MLTPP_TOKENISER_H #include "MltConfig.h" #include namespace Mlt { class MLTPP_DECLSPEC Tokeniser { private: mlt_tokeniser tokens; public: Tokeniser(char *text = NULL, char *delimiter = NULL); ~Tokeniser(); int parse(char *text, char *delimiter = NULL); int count(); char *get(int index); char *input(); }; } // namespace Mlt #endif mlt-7.22.0/src/mlt++/MltTractor.cpp000664 000000 000000 00000011357 14531534050 016675 0ustar00rootroot000000 000000 /** * MltTractor.cpp - Tractor wrapper * Copyright (C) 2004-2019 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "MltTractor.h" #include "MltField.h" #include "MltFilter.h" #include "MltMultitrack.h" #include "MltPlaylist.h" #include "MltProfile.h" #include "MltTransition.h" using namespace Mlt; Tractor::Tractor() : instance(mlt_tractor_new()) {} Tractor::Tractor(Profile &profile) : instance(mlt_tractor_new()) { set_profile(profile); } Tractor::Tractor(Service &tractor) : instance(NULL) { if (tractor.type() == mlt_service_tractor_type) { instance = (mlt_tractor) tractor.get_service(); inc_ref(); } } Tractor::Tractor(mlt_tractor tractor) : instance(tractor) { inc_ref(); } Tractor::Tractor(Tractor &tractor) : Mlt::Producer(tractor) , instance(tractor.get_tractor()) { inc_ref(); } Tractor::Tractor(Profile &profile, char *id, char *resource) : Tractor(profile.get_profile(), id, resource) {} Tractor::Tractor(mlt_profile profile, char *id, char *resource) : instance(NULL) { Producer producer(profile, id, resource); if (producer.is_valid() && producer.type() == mlt_service_tractor_type) { instance = (mlt_tractor) producer.get_producer(); inc_ref(); } else if (producer.is_valid()) { instance = mlt_tractor_new(); set_profile(profile); set_track(producer, 0); } } Tractor::~Tractor() { mlt_tractor_close(instance); } mlt_tractor Tractor::get_tractor() { return instance; } mlt_producer Tractor::get_producer() { return mlt_tractor_producer(get_tractor()); } Multitrack *Tractor::multitrack() { return new Multitrack(mlt_tractor_multitrack(get_tractor())); } Field *Tractor::field() { return new Field(mlt_tractor_field(get_tractor())); } void Tractor::refresh() { return mlt_tractor_refresh(get_tractor()); } int Tractor::set_track(Producer &producer, int index) { return mlt_tractor_set_track(get_tractor(), producer.get_producer(), index); } int Tractor::insert_track(Producer &producer, int index) { return mlt_tractor_insert_track(get_tractor(), producer.get_producer(), index); } int Tractor::remove_track(int index) { return mlt_tractor_remove_track(get_tractor(), index); } Producer *Tractor::track(int index) { mlt_producer producer = mlt_tractor_get_track(get_tractor(), index); return producer != NULL ? new Producer(producer) : NULL; } int Tractor::count() { return mlt_multitrack_count(mlt_tractor_multitrack(get_tractor())); } void Tractor::plant_transition(Transition &transition, int a_track, int b_track) { mlt_field_plant_transition(mlt_tractor_field(get_tractor()), transition.get_transition(), a_track, b_track); } void Tractor::plant_transition(Transition *transition, int a_track, int b_track) { if (transition != NULL) mlt_field_plant_transition(mlt_tractor_field(get_tractor()), transition->get_transition(), a_track, b_track); } void Tractor::plant_filter(Filter &filter, int track) { mlt_field_plant_filter(mlt_tractor_field(get_tractor()), filter.get_filter(), track); } void Tractor::plant_filter(Filter *filter, int track) { mlt_field_plant_filter(mlt_tractor_field(get_tractor()), filter->get_filter(), track); } bool Tractor::locate_cut(Producer *producer, int &track, int &cut) { bool found = false; for (track = 0; producer != NULL && !found && track < count(); track++) { Playlist playlist((mlt_playlist) mlt_tractor_get_track(get_tractor(), track)); for (cut = 0; !found && cut < playlist.count(); cut++) { Producer *clip = playlist.get_clip(cut); found = producer->get_producer() == clip->get_producer(); delete clip; } } track--; cut--; return found; } int Tractor::connect(Producer &producer) { return mlt_tractor_connect(get_tractor(), producer.get_service()); } mlt-7.22.0/src/mlt++/MltTractor.h000664 000000 000000 00000004302 14531534050 016332 0ustar00rootroot000000 000000 /** * MltTractor.h - Tractor wrapper * Copyright (C) 2004-2015 Meltytech, LLC * Author: Charles Yates * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef MLTPP_TRACTOR_H #define MLTPP_TRACTOR_H #include "MltConfig.h" #include #include "MltProducer.h" namespace Mlt { class Producer; class Field; class Multitrack; class Transition; class Filter; class Profile; class MLTPP_DECLSPEC Tractor : public Producer { private: mlt_tractor instance; public: Tractor(); Tractor(Profile &profile); Tractor(Service &tractor); Tractor(mlt_tractor tractor); Tractor(Tractor &tractor); Tractor(Profile &profile, char *id, char *arg = NULL); Tractor(mlt_profile profile, char *id, char *arg = NULL); virtual ~Tractor(); virtual mlt_tractor get_tractor(); mlt_producer get_producer() override; Multitrack *multitrack(); Field *field(); void refresh(); int set_track(Producer &producer, int index); int insert_track(Producer &producer, int index); int remove_track(int index); Producer *track(int index); int count(); void plant_transition(Transition &transition, int a_track = 0, int b_track = 1); void plant_transition(Transition *transition, int a_track = 0, int b_track = 1); void plant_filter(Filter &filter, int track = 0); void plant_filter(Filter *filter, int track = 0); bool locate_cut(Producer *producer, int &track, int &cut); int connect(Producer &producer); }; } // namespace Mlt #endif mlt-7.22.0/src/mlt++/MltTransition.cpp000664 000000 000000 00000010047 14531534050 017404 0ustar00rootroot000000 000000 /** * MltTransition.cpp - MLT Wrapper * Copyright (C) 2004-2019 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "MltTransition.h" #include "MltProducer.h" #include "MltProfile.h" #include #include using namespace Mlt; Transition::Transition() : Service() , instance(nullptr) {} Transition::Transition(Profile &profile, const char *id, const char *arg) : Transition(profile.get_profile(), id, arg) {} Transition::Transition(mlt_profile profile, const char *id, const char *arg) : instance(NULL) { if (arg != NULL) { instance = mlt_factory_transition(profile, id, arg); } else { if (strchr(id, ':')) { char *temp = strdup(id); char *arg = strchr(temp, ':') + 1; *(arg - 1) = '\0'; instance = mlt_factory_transition(profile, temp, arg); free(temp); } else { instance = mlt_factory_transition(profile, id, NULL); } } } Transition::Transition(Service &transition) : instance(NULL) { if (transition.type() == mlt_service_transition_type) { instance = (mlt_transition) transition.get_service(); inc_ref(); } } Transition::Transition(Transition &transition) : Mlt::Service(transition) , instance(transition.get_transition()) { inc_ref(); } Transition::Transition(const Transition &transition) : Transition(const_cast(transition)) {} Transition::Transition(mlt_transition transition) : instance(transition) { inc_ref(); } Transition::~Transition() { mlt_transition_close(instance); } Transition &Transition::operator=(const Transition &transition) { if (this != &transition) { mlt_transition_close(instance); instance = transition.instance; inc_ref(); } return *this; } mlt_transition Transition::get_transition() { return instance; } mlt_service Transition::get_service() { return mlt_transition_service(get_transition()); } void Transition::set_in_and_out(int in, int out) { mlt_transition_set_in_and_out(get_transition(), in, out); } void Transition::set_tracks(int a_track, int b_track) { mlt_transition_set_tracks(get_transition(), a_track, b_track); } int Transition::connect(Producer &producer, int a_track, int b_track) { return mlt_transition_connect(get_transition(), producer.get_service(), a_track, b_track); } int Transition::connect(Service &service, int a_track, int b_track) { return mlt_transition_connect(get_transition(), service.get_service(), a_track, b_track); } int Transition::get_a_track() { return mlt_transition_get_a_track(get_transition()); } int Transition::get_b_track() { return mlt_transition_get_b_track(get_transition()); } int Transition::get_in() { return mlt_transition_get_in(get_transition()); } int Transition::get_out() { return mlt_transition_get_out(get_transition()); } int Transition::get_length() { return mlt_transition_get_length(get_transition()); } int Transition::get_position(Frame &frame) { return mlt_transition_get_position(get_transition(), frame.get_frame()); } double Transition::get_progress(Frame &frame) { return mlt_transition_get_progress(get_transition(), frame.get_frame()); } double Transition::get_progress_delta(Frame &frame) { return mlt_transition_get_progress_delta(get_transition(), frame.get_frame()); } mlt-7.22.0/src/mlt++/MltTransition.h000664 000000 000000 00000004102 14531534050 017044 0ustar00rootroot000000 000000 /** * MltTransition.h - MLT Wrapper * Copyright (C) 2004-2019 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef MLTPP_TRANSITION_H #define MLTPP_TRANSITION_H #include "MltConfig.h" #include "MltService.h" #include namespace Mlt { class Service; class Profile; class Frame; class Chain; class Link; class MLTPP_DECLSPEC Transition : public Service { private: mlt_transition instance; public: Transition(); Transition(Profile &profile, const char *id, const char *arg = NULL); Transition(mlt_profile profile, const char *id, const char *arg = NULL); Transition(Service &transition); Transition(Transition &transition); Transition(const Transition &transition); Transition(mlt_transition transition); virtual ~Transition(); Transition &operator=(const Transition &transition); virtual mlt_transition get_transition(); mlt_service get_service() override; void set_in_and_out(int in, int out); void set_tracks(int a_track, int b_track); int connect(Producer &producer, int a_track, int b_track); int connect(Service &service, int a_track, int b_track); int get_a_track(); int get_b_track(); int get_in(); int get_out(); int get_length(); int get_position(Frame &frame); double get_progress(Frame &frame); double get_progress_delta(Frame &frame); }; } // namespace Mlt #endif mlt-7.22.0/src/mlt++/mlt++.pc.in000664 000000 000000 00000000632 14531534050 015743 0ustar00rootroot000000 000000 prefix=@CMAKE_INSTALL_PREFIX@ exec_prefix=@CMAKE_INSTALL_PREFIX@ libdir=@CMAKE_INSTALL_FULL_LIBDIR@ includedir=@CMAKE_INSTALL_FULL_INCLUDEDIR@ datadir=@CMAKE_INSTALL_FULL_DATADIR@ Name: mlt++ Description: C++ API for MLT multimedia framework Version: @MLT_VERSION@ Requires: mlt-framework-@MLT_VERSION_MAJOR@ Libs: -L${libdir} -lmlt++-@MLT_VERSION_MAJOR@ Cflags: -I${includedir}/mlt-@MLT_VERSION_MAJOR@/mlt++ mlt-7.22.0/src/mlt++/mlt++.vers000664 000000 000000 00000071566 14531534050 015731 0ustar00rootroot000000 000000 MLTPP_0.8.8 { global: extern "C++" { "Mlt::ClipInfo::~ClipInfo()"; "Mlt::ClipInfo::ClipInfo()"; "Mlt::ClipInfo::ClipInfo(mlt_playlist_clip_info*)"; "Mlt::ClipInfo::update(mlt_playlist_clip_info*)"; "typeinfo for Mlt::Consumer"; "typeinfo name for Mlt::Consumer"; "vtable for Mlt::Consumer"; "Mlt::Consumer::connect(Mlt::Service&)"; "Mlt::Consumer::~Consumer()"; "Mlt::Consumer::Consumer()"; "Mlt::Consumer::Consumer(Mlt::Consumer&)"; "Mlt::Consumer::Consumer(mlt_consumer_s*)"; "Mlt::Consumer::Consumer(Mlt::Profile&)"; "Mlt::Consumer::Consumer(Mlt::Profile&, char const*, char const*)"; "Mlt::Consumer::Consumer(Mlt::Service&)"; "Mlt::Consumer::get_consumer()"; "Mlt::Consumer::get_service()"; "Mlt::Consumer::is_stopped()"; "Mlt::Consumer::position()"; "Mlt::Consumer::purge()"; "Mlt::Consumer::run()"; "Mlt::Consumer::start()"; "Mlt::Consumer::stop()"; "Mlt::Deque::count()"; "Mlt::Deque::~Deque()"; "Mlt::Deque::Deque()"; "Mlt::Deque::peek_back()"; "Mlt::Deque::peek_front()"; "Mlt::Deque::pop_back()"; "Mlt::Deque::pop_front()"; "Mlt::Deque::push_back(void*)"; "Mlt::Deque::push_front(void*)"; "Mlt::Event::block()"; "Mlt::Event::~Event()"; "Mlt::Event::Event(Mlt::Event&)"; "Mlt::Event::Event(mlt_event_struct*)"; "Mlt::Event::get_event()"; "Mlt::Event::is_valid()"; "Mlt::Event::unblock()"; "Mlt::Factory::close()"; "Mlt::Factory::consumer(Mlt::Profile&, char*, char*)"; "Mlt::Factory::event_object()"; "Mlt::Factory::filter(Mlt::Profile&, char*, char*)"; "Mlt::Factory::init(char const*)"; "Mlt::Factory::producer(Mlt::Profile&, char*, char*)"; "Mlt::Factory::transition(Mlt::Profile&, char*, char*)"; "typeinfo for Mlt::Field"; "typeinfo name for Mlt::Field"; "vtable for Mlt::Field"; "Mlt::Field::disconnect_service(Mlt::Service&)"; "Mlt::Field::~Field()"; "Mlt::Field::Field(Mlt::Field&)"; "Mlt::Field::Field(mlt_field_s*)"; "Mlt::Field::get_field()"; "Mlt::Field::get_service()"; "Mlt::Field::plant_filter(Mlt::Filter&, int)"; "Mlt::Field::plant_transition(Mlt::Transition&, int, int)"; "typeinfo for Mlt::FilteredConsumer"; "typeinfo name for Mlt::FilteredConsumer"; "vtable for Mlt::FilteredConsumer"; "Mlt::FilteredConsumer::attach(Mlt::Filter&)"; "Mlt::FilteredConsumer::connect(Mlt::Service&)"; "Mlt::FilteredConsumer::detach(Mlt::Filter&)"; "Mlt::FilteredConsumer::~FilteredConsumer()"; "Mlt::FilteredConsumer::FilteredConsumer(Mlt::Consumer&)"; "Mlt::FilteredConsumer::FilteredConsumer(Mlt::Profile&, char const*, char const*)"; "Mlt::FilteredConsumer::last(Mlt::Filter&)"; "typeinfo for Mlt::FilteredProducer"; "typeinfo name for Mlt::FilteredProducer"; "vtable for Mlt::FilteredProducer"; "Mlt::FilteredProducer::attach(Mlt::Filter&)"; "Mlt::FilteredProducer::detach(Mlt::Filter&)"; "Mlt::FilteredProducer::~FilteredProducer()"; "Mlt::FilteredProducer::FilteredProducer(Mlt::Profile&, char const*, char const*)"; "typeinfo for Mlt::Filter"; "typeinfo name for Mlt::Filter"; "vtable for Mlt::Filter"; "Mlt::Filter::connect(Mlt::Service&, int)"; "Mlt::Filter::~Filter()"; "Mlt::Filter::Filter(Mlt::Filter&)"; "Mlt::Filter::Filter(mlt_filter_s*)"; "Mlt::Filter::Filter(Mlt::Profile&, char const*, char const*)"; "Mlt::Filter::Filter(Mlt::Service&)"; "Mlt::Filter::get_filter()"; "Mlt::Filter::get_in()"; "Mlt::Filter::get_length()"; "Mlt::Filter::get_length2(Mlt::Frame&)"; "Mlt::Filter::get_out()"; "Mlt::Filter::get_position(Mlt::Frame&)"; "Mlt::Filter::get_progress(Mlt::Frame&)"; "Mlt::Filter::get_service()"; "Mlt::Filter::get_track()"; "Mlt::Filter::set_in_and_out(int, int)"; "typeinfo for Mlt::Frame"; "typeinfo name for Mlt::Frame"; "vtable for Mlt::Frame"; "Mlt::Frame::fetch_image(mlt_image_format, int, int, int)"; "Mlt::Frame::~Frame()"; "Mlt::Frame::Frame(Mlt::Frame&)"; "Mlt::Frame::Frame(mlt_frame_s*)"; "Mlt::Frame::get_audio(mlt_audio_format&, int&, int&, int&)"; "Mlt::Frame::get_frame()"; "Mlt::Frame::get_image(mlt_image_format&, int&, int&, int)"; "Mlt::Frame::get_original_producer()"; "Mlt::Frame::get_position()"; "Mlt::Frame::get_properties()"; "Mlt::Frame::get_unique_properties(Mlt::Service&)"; "Mlt::Frame::get_waveform(int, int)"; "Mlt::Frame::set_alpha(unsigned char*, int, void (*)(void*))"; "Mlt::Frame::set_image(unsigned char*, int, void (*)(void*))"; "Mlt::Geometry::fetch(Mlt::GeometryItem*, float)"; "Mlt::Geometry::fetch(Mlt::GeometryItem&, float)"; "Mlt::Geometry::~Geometry()"; "Mlt::Geometry::Geometry(char*, int, int, int)"; "Mlt::Geometry::insert(Mlt::GeometryItem*)"; "Mlt::Geometry::insert(Mlt::GeometryItem&)"; "Mlt::Geometry::interpolate()"; "Mlt::Geometry::next_key(Mlt::GeometryItem*, int)"; "Mlt::Geometry::next_key(Mlt::GeometryItem&, int)"; "Mlt::Geometry::parse(char*, int, int, int)"; "Mlt::Geometry::prev_key(Mlt::GeometryItem*, int)"; "Mlt::Geometry::prev_key(Mlt::GeometryItem&, int)"; "Mlt::Geometry::remove(int)"; "Mlt::Geometry::serialise()"; "Mlt::Geometry::serialise(int, int)"; "typeinfo for Mlt::Multitrack"; "typeinfo name for Mlt::Multitrack"; "vtable for Mlt::Multitrack"; "Mlt::Multitrack::clip(mlt_whence, int)"; "Mlt::Multitrack::connect(Mlt::Producer&, int)"; "Mlt::Multitrack::count()"; "Mlt::Multitrack::get_multitrack()"; "Mlt::Multitrack::get_producer()"; "Mlt::Multitrack::~Multitrack()"; "Mlt::Multitrack::Multitrack(Mlt::Multitrack&)"; "Mlt::Multitrack::Multitrack(mlt_multitrack_s*)"; "Mlt::Multitrack::Multitrack(Mlt::Service&)"; "Mlt::Multitrack::refresh()"; "Mlt::Multitrack::track(int)"; "typeinfo for Mlt::Parser"; "typeinfo name for Mlt::Parser"; "vtable for Mlt::Parser"; "Mlt::Parser::get_properties()"; "Mlt::Parser::on_end_filter(Mlt::Filter*)"; "Mlt::Parser::on_end_multitrack(Mlt::Multitrack*)"; "Mlt::Parser::on_end_playlist(Mlt::Playlist*)"; "Mlt::Parser::on_end_producer(Mlt::Producer*)"; "Mlt::Parser::on_end_track()"; "Mlt::Parser::on_end_tractor(Mlt::Tractor*)"; "Mlt::Parser::on_end_transition(Mlt::Transition*)"; "Mlt::Parser::on_invalid(Mlt::Service*)"; "Mlt::Parser::on_start_filter(Mlt::Filter*)"; "Mlt::Parser::on_start_multitrack(Mlt::Multitrack*)"; "Mlt::Parser::on_start_playlist(Mlt::Playlist*)"; "Mlt::Parser::on_start_producer(Mlt::Producer*)"; "Mlt::Parser::on_start_track()"; "Mlt::Parser::on_start_tractor(Mlt::Tractor*)"; "Mlt::Parser::on_start_transition(Mlt::Transition*)"; "Mlt::Parser::on_unknown(Mlt::Service*)"; "Mlt::Parser::~Parser()"; "Mlt::Parser::Parser()"; "Mlt::Parser::start(Mlt::Service&)"; "typeinfo for Mlt::Playlist"; "typeinfo name for Mlt::Playlist"; "vtable for Mlt::Playlist"; "Mlt::Playlist::append(Mlt::Producer&, int, int)"; "Mlt::Playlist::blank(char const*)"; "Mlt::Playlist::blank(int)"; "Mlt::Playlist::blanks_from(int, int)"; "Mlt::Playlist::clear()"; "Mlt::Playlist::clip_info(int, Mlt::ClipInfo*)"; "Mlt::Playlist::clip_length(int)"; "Mlt::Playlist::clip(mlt_whence, int)"; "Mlt::Playlist::clip_start(int)"; "Mlt::Playlist::consolidate_blanks(int)"; "Mlt::Playlist::count()"; "Mlt::Playlist::current()"; "Mlt::Playlist::current_clip()"; "Mlt::Playlist::delete_clip_info(Mlt::ClipInfo*)"; "Mlt::Playlist::get_clip_at(int)"; "Mlt::Playlist::get_clip_index_at(int)"; "Mlt::Playlist::get_clip(int)"; "Mlt::Playlist::get_playlist()"; "Mlt::Playlist::get_producer()"; "Mlt::Playlist::insert_at(int, Mlt::Producer*, int)"; "Mlt::Playlist::insert_at(int, Mlt::Producer&, int)"; "Mlt::Playlist::insert_blank(int, int)"; "Mlt::Playlist::insert(Mlt::Producer&, int, int, int)"; "Mlt::Playlist::is_blank_at(int)"; "Mlt::Playlist::is_blank(int)"; "Mlt::Playlist::is_mix(int)"; "Mlt::Playlist::join(int, int, int)"; "Mlt::Playlist::mix_add(int, Mlt::Transition*)"; "Mlt::Playlist::mix(int, int, Mlt::Transition*)"; "Mlt::Playlist::move(int, int)"; "Mlt::Playlist::move_region(int, int, int)"; "Mlt::Playlist::pad_blanks(int, int, int)"; "Mlt::Playlist::~Playlist()"; "Mlt::Playlist::Playlist()"; "Mlt::Playlist::Playlist(Mlt::Playlist&)"; "Mlt::Playlist::Playlist(mlt_playlist_s*)"; "Mlt::Playlist::Playlist(Mlt::Profile&)"; "Mlt::Playlist::Playlist(Mlt::Service&)"; "Mlt::Playlist::remove(int)"; "Mlt::Playlist::remove_region(int, int)"; "Mlt::Playlist::repeat(int, int)"; "Mlt::Playlist::replace_with_blank(int)"; "Mlt::Playlist::resize_clip(int, int, int)"; "Mlt::Playlist::split_at(int, bool)"; "Mlt::Playlist::split(int, int)"; "typeinfo for Mlt::Producer"; "typeinfo name for Mlt::Producer"; "vtable for Mlt::Producer"; "Mlt::Producer::clear()"; "Mlt::Producer::cut(int, int)"; "Mlt::Producer::frame()"; "Mlt::Producer::frame_time(mlt_time_format)"; "Mlt::Producer::get_fps()"; "Mlt::Producer::get_in()"; "Mlt::Producer::get_length()"; "Mlt::Producer::get_length_time(mlt_time_format)"; "Mlt::Producer::get_out()"; "Mlt::Producer::get_parent()"; "Mlt::Producer::get_playtime()"; "Mlt::Producer::get_producer()"; "Mlt::Producer::get_service()"; "Mlt::Producer::get_speed()"; "Mlt::Producer::is_blank()"; "Mlt::Producer::is_cut()"; "Mlt::Producer::optimise()"; "Mlt::Producer::parent()"; "Mlt::Producer::pause()"; "Mlt::Producer::position()"; "Mlt::Producer::~Producer()"; "Mlt::Producer::Producer()"; "Mlt::Producer::Producer(Mlt::Producer*)"; "Mlt::Producer::Producer(Mlt::Producer&)"; "Mlt::Producer::Producer(mlt_producer_s*)"; "Mlt::Producer::Producer(Mlt::Profile&, char const*, char const*)"; "Mlt::Producer::Producer(Mlt::Service&)"; "Mlt::Producer::runs_into(Mlt::Producer&)"; "Mlt::Producer::same_clip(Mlt::Producer&)"; "Mlt::Producer::seek(char const*)"; "Mlt::Producer::seek(int)"; "Mlt::Producer::set_in_and_out(int, int)"; "Mlt::Producer::set_speed(double)"; "Mlt::Profile::colorspace() const"; "Mlt::Profile::dar() const"; "Mlt::Profile::description() const"; "Mlt::Profile::display_aspect_den() const"; "Mlt::Profile::display_aspect_num() const"; "Mlt::Profile::fps() const"; "Mlt::Profile::frame_rate_den() const"; "Mlt::Profile::frame_rate_num() const"; "Mlt::Profile::from_producer(Mlt::Producer&)"; "Mlt::Profile::get_profile() const"; "Mlt::Profile::height() const"; "Mlt::Profile::is_explicit() const"; "Mlt::Profile::list()"; "Mlt::Profile::~Profile()"; "Mlt::Profile::Profile()"; "Mlt::Profile::Profile(char const*)"; "Mlt::Profile::Profile(mlt_profile_s*)"; "Mlt::Profile::Profile(Mlt::Properties&)"; "Mlt::Profile::progressive() const"; "Mlt::Profile::sample_aspect_den() const"; "Mlt::Profile::sample_aspect_num() const"; "Mlt::Profile::sar() const"; "Mlt::Profile::set_colorspace(int)"; "Mlt::Profile::set_explicit(int)"; "Mlt::Profile::set_frame_rate(int, int)"; "Mlt::Profile::set_height(int)"; "Mlt::Profile::set_progressive(int)"; "Mlt::Profile::set_sample_aspect(int, int)"; "Mlt::Profile::set_width(int)"; "Mlt::Profile::width() const"; "typeinfo for Mlt::Properties"; "typeinfo name for Mlt::Properties"; "vtable for Mlt::Properties"; "Mlt::Properties::block(void*)"; "Mlt::Properties::count()"; "Mlt::Properties::debug(char const*, _IO_FILE*)"; "Mlt::Properties::dec_ref()"; "Mlt::Properties::delete_event(Mlt::Event*)"; "Mlt::Properties::dump(_IO_FILE*)"; "Mlt::Properties::fire_event(char const*)"; "Mlt::Properties::get(char const*)"; "Mlt::Properties::get_data(char const*)"; "Mlt::Properties::get_data(char const*, int&)"; "Mlt::Properties::get_data(int, int&)"; "Mlt::Properties::get_double(char const*)"; "Mlt::Properties::get(int)"; "Mlt::Properties::get_int64(char const*)"; "Mlt::Properties::get_int(char const*)"; "Mlt::Properties::get_lcnumeric()"; "Mlt::Properties::get_name(int)"; "Mlt::Properties::get_properties()"; "Mlt::Properties::get_time(char const*, mlt_time_format)"; "Mlt::Properties::inc_ref()"; "Mlt::Properties::inherit(Mlt::Properties&)"; "Mlt::Properties::is_sequence()"; "Mlt::Properties::is_valid()"; "Mlt::Properties::listen(char const*, void*, void (*)(void*, ...))"; "Mlt::Properties::load(char const*)"; "Mlt::Properties::lock()"; "Mlt::Properties::mirror(Mlt::Properties&)"; "Mlt::Properties::parse(char const*)"; "Mlt::Properties::parse_yaml(char const*)"; "Mlt::Properties::pass_list(Mlt::Properties&, char const*)"; "Mlt::Properties::pass_property(Mlt::Properties&, char const*)"; "Mlt::Properties::pass_values(Mlt::Properties&, char const*)"; "Mlt::Properties::preset(char const*)"; "Mlt::Properties::~Properties()"; "Mlt::Properties::Properties()"; "Mlt::Properties::Properties(bool)"; "Mlt::Properties::Properties(char const*)"; "Mlt::Properties::Properties(Mlt::Properties&)"; "Mlt::Properties::Properties(mlt_properties_s*)"; "Mlt::Properties::Properties(void*)"; "Mlt::Properties::ref_count()"; "Mlt::Properties::rename(char const*, char const*)"; "Mlt::Properties::save(char const*)"; "Mlt::Properties::serialise_yaml()"; "Mlt::Properties::set(char const*, char const*)"; "Mlt::Properties::set(char const*, double)"; "Mlt::Properties::set(char const*, int)"; "Mlt::Properties::set(char const*, long)"; "Mlt::Properties::set(char const*, long long)"; "Mlt::Properties::set(char const*, void*, int, void (*)(void*), char* (*)(void*, int))"; "Mlt::Properties::set_lcnumeric(char const*)"; "Mlt::Properties::setup_wait_for(char const*)"; "Mlt::Properties::unblock(void*)"; "Mlt::Properties::unlock()"; "Mlt::Properties::wait_for(char const*)"; "Mlt::Properties::wait_for(Mlt::Event*, bool)"; "typeinfo for Mlt::PushConsumer"; "typeinfo name for Mlt::PushConsumer"; "vtable for Mlt::PushConsumer"; "Mlt::PushConsumer::connect(Mlt::Service&)"; "Mlt::PushConsumer::construct(int)"; "Mlt::PushConsumer::drain()"; "Mlt::PushConsumer::~PushConsumer()"; "Mlt::PushConsumer::PushConsumer(Mlt::Profile&, char const*, char const*)"; "Mlt::PushConsumer::push(Mlt::Frame*)"; "Mlt::PushConsumer::push(Mlt::Frame&)"; "Mlt::PushConsumer::set_render(int, int, double)"; "Mlt::Repository::consumers() const"; "Mlt::Repository::create(Mlt::Profile&, mlt_service_type, char const*, void*)"; "Mlt::Repository::filters() const"; "Mlt::Repository::languages() const"; "Mlt::Repository::metadata(mlt_service_type, char const*) const"; "Mlt::Repository::presets()"; "Mlt::Repository::producers() const"; "Mlt::Repository::register_metadata(mlt_service_type, char const*, mlt_properties_s* (*)(mlt_service_type, char const*, void*), void*)"; "Mlt::Repository::register_service(mlt_service_type, char const*, void* (*)(mlt_profile_s*, mlt_service_type, char const*, void const*))"; "Mlt::Repository::~Repository()"; "Mlt::Repository::Repository(char const*)"; "Mlt::Repository::Repository(mlt_repository_s*)"; "Mlt::Repository::transitions() const"; "typeinfo for Mlt::Service"; "typeinfo name for Mlt::Service"; "vtable for Mlt::Service"; "Mlt::Service::attach(Mlt::Filter&)"; "Mlt::Service::connect_producer(Mlt::Service&, int)"; "Mlt::Service::consumer()"; "Mlt::Service::detach(Mlt::Filter&)"; "Mlt::Service::filter(int)"; "Mlt::Service::get_frame(int)"; "Mlt::Service::get_profile()"; "Mlt::Service::get_properties()"; "Mlt::Service::get_service()"; "Mlt::Service::lock()"; "Mlt::Service::producer()"; "Mlt::Service::profile()"; "Mlt::Service::~Service()"; "Mlt::Service::Service()"; "Mlt::Service::Service(Mlt::Service&)"; "Mlt::Service::Service(mlt_service_s*)"; "Mlt::Service::set_profile(Mlt::Profile&)"; "Mlt::Service::type()"; "Mlt::Service::unlock()"; "Mlt::Tokeniser::count()"; "Mlt::Tokeniser::get(int)"; "Mlt::Tokeniser::input()"; "Mlt::Tokeniser::parse(char*, char*)"; "Mlt::Tokeniser::~Tokeniser()"; "Mlt::Tokeniser::Tokeniser(char*, char*)"; "typeinfo for Mlt::Tractor"; "typeinfo name for Mlt::Tractor"; "vtable for Mlt::Tractor"; "Mlt::Tractor::connect(Mlt::Producer&)"; "Mlt::Tractor::count()"; "Mlt::Tractor::field()"; "Mlt::Tractor::get_producer()"; "Mlt::Tractor::get_tractor()"; "Mlt::Tractor::locate_cut(Mlt::Producer*, int&, int&)"; "Mlt::Tractor::multitrack()"; "Mlt::Tractor::plant_filter(Mlt::Filter*, int)"; "Mlt::Tractor::plant_filter(Mlt::Filter&, int)"; "Mlt::Tractor::plant_transition(Mlt::Transition*, int, int)"; "Mlt::Tractor::plant_transition(Mlt::Transition&, int, int)"; "Mlt::Tractor::refresh()"; "Mlt::Tractor::set_track(Mlt::Producer&, int)"; "Mlt::Tractor::track(int)"; "Mlt::Tractor::~Tractor()"; "Mlt::Tractor::Tractor()"; "Mlt::Tractor::Tractor(Mlt::Profile&, char*, char*)"; "Mlt::Tractor::Tractor(Mlt::Service&)"; "Mlt::Tractor::Tractor(Mlt::Tractor&)"; "Mlt::Tractor::Tractor(mlt_tractor_s*)"; "typeinfo for Mlt::Transition"; "typeinfo name for Mlt::Transition"; "vtable for Mlt::Transition"; "Mlt::Transition::connect(Mlt::Producer&, int, int)"; "Mlt::Transition::get_a_track()"; "Mlt::Transition::get_b_track()"; "Mlt::Transition::get_in()"; "Mlt::Transition::get_length()"; "Mlt::Transition::get_out()"; "Mlt::Transition::get_position(Mlt::Frame&)"; "Mlt::Transition::get_progress_delta(Mlt::Frame&)"; "Mlt::Transition::get_progress(Mlt::Frame&)"; "Mlt::Transition::get_service()"; "Mlt::Transition::get_transition()"; "Mlt::Transition::set_in_and_out(int, int)"; "Mlt::Transition::~Transition()"; "Mlt::Transition::Transition(Mlt::Profile&, char const*, char const*)"; "Mlt::Transition::Transition(Mlt::Service&)"; "Mlt::Transition::Transition(Mlt::Transition&)"; "Mlt::Transition::Transition(mlt_transition_s*)"; }; local: *; }; MLTPP_0.9.0 { global: extern "C++" { "Mlt::Deque::peek(int)"; "Mlt::Properties::anim_get(char const*, int, int)"; "Mlt::Properties::anim_get_double(char const*, int, int)"; "Mlt::Properties::anim_get_int(char const*, int, int)"; "Mlt::Properties::anim_get_rect(char const*, int, int)"; "Mlt::Properties::anim_set(char const*, char const*, int, int)"; "Mlt::Properties::anim_set(char const*, double, int, int, mlt_keyframe_type)"; "Mlt::Properties::anim_set(char const*, int, int, int, mlt_keyframe_type)"; "Mlt::Properties::anim_set(char const*, mlt_rect, int, int, mlt_keyframe_type)"; "Mlt::Properties::get_color(char const*)"; "Mlt::Properties::get_rect(char const*)"; "Mlt::Properties::set(char const*, double, double, double, double, double)"; "Mlt::Properties::set(char const*, mlt_color)"; "Mlt::Properties::set(char const*, mlt_rect)"; "Mlt::Service::filter_count()"; "Mlt::Service::move_filter(int, int)"; }; } MLTPP_0.8.8; MLTPP_0.9.2 { global: extern "C++" { "Mlt::Playlist::mix_in(int, int)"; "Mlt::Playlist::mix_out(int, int)"; "Mlt::Properties::frames_to_time(int, mlt_time_format)"; "Mlt::Properties::time_to_frames(char const*)"; }; } MLTPP_0.9.0; MLTPP_0.9.4 { global: extern "C++" { "Mlt::Profile::set_display_aspect(int, int)"; "Mlt::Tractor::Tractor(Mlt::Profile&)"; "Mlt::Frame::Frame()"; "Mlt::Frame::Frame(Mlt::Frame const&)"; "Mlt::Frame::operator=(Mlt::Frame const&)"; "Mlt::Filter::process(Mlt::Frame&)"; }; } MLTPP_0.9.2; MLTPP_0.9.8 { global: extern "C++" { "Mlt::Multitrack::disconnect(int)"; "Mlt::Service::disconnect_producer(int)"; "Mlt::Tractor::remove_track(int)"; "Mlt::Service::insert_producer(Mlt::Service&, int)"; "Mlt::Multitrack::insert(Mlt::Producer&, int)"; "Mlt::Tractor::insert_track(Mlt::Producer&, int)"; "Mlt::Transition::set_tracks(int, int)"; "Mlt::Properties::get_animation(char const*)"; "Mlt::Properties::get_anim(char const*)"; "Mlt::Animation::Animation()"; "Mlt::Animation::Animation(mlt_animation_s*)"; "Mlt::Animation::Animation(Mlt::Animation const&)"; "Mlt::Animation::~Animation()"; "Mlt::Animation::is_valid() const"; "Mlt::Animation::get_animation() const"; "Mlt::Animation::operator=(Mlt::Animation const&)"; "Mlt::Animation::length()"; "Mlt::Animation::is_key(int)"; "Mlt::Animation::keyframe_type(int)"; "Mlt::Animation::get_item(int, bool&, mlt_keyframe_type&)"; "Mlt::Animation::next_key(int)"; "Mlt::Animation::previous_key(int)"; "Mlt::Animation::key_count()"; "Mlt::Animation::key_get(int, int&, mlt_keyframe_type&)"; "Mlt::Animation::key_get_frame(int)"; "Mlt::Animation::key_get_type(int)"; "Mlt::Animation::set_length(int)"; "Mlt::Animation::remove(int)"; "Mlt::Animation::interpolate()"; "Mlt::Animation::serialize_cut(int, int)"; }; } MLTPP_0.9.4; MLTPP_6.4.0 { global: extern "C++" { "Mlt::Profile::is_valid() const"; }; } MLTPP_0.9.8; MLTPP_6.6.0 { global: extern "C++" { "Mlt::Service::disconnect_all_producers()"; }; } MLTPP_6.4.0; MLTPP_6.8.0 { global: extern "C++" { "Mlt::Animation::key_set_type(int, mlt_keyframe_type)"; "Mlt::Animation::key_set_frame(int, int)"; }; } MLTPP_6.6.0; MLTPP_6.10.0 { global: extern "C++" { "Mlt::Properties::get(int, mlt_time_format)"; "Mlt::Properties::clear(char const*)"; "Mlt::Animation::serialize_cut(mlt_time_format, int, int)"; }; } MLTPP_6.8.0; MLTPP_6.14.0 { global: extern "C++" { "Mlt::Producer::Producer(mlt_profile_s*, char const*, char const*)"; "Mlt::Consumer::Consumer(mlt_profile_s*, char const*, char const*)"; "Mlt::Transition::Transition(mlt_profile_s*, char const*, char const*)"; "Mlt::Filter::Filter(mlt_profile_s*, char const*, char const*)"; "Mlt::Tractor::Tractor(mlt_profile_s*, char*, char*)"; "Mlt::Service::set_profile(mlt_profile_s*)"; "Mlt::Playlist::reorder(int const*)"; "Mlt::Transition::connect(Mlt::Service&, int, int)"; "Mlt::Producer::set_creation_time(long)"; "Mlt::Producer::set_creation_time(long long)"; "Mlt::Producer::get_creation_time()"; }; } MLTPP_6.10.0; MLTPP_6.18.0 { global: extern "C++" { "Mlt::Filter::Filter()"; "Mlt::Filter::Filter(Mlt::Filter const&)"; "Mlt::Filter::operator=(Mlt::Filter const&)"; "Mlt::Producer::Producer(Mlt::Producer const&)"; "Mlt::Producer::operator=(Mlt::Producer const&)"; "Mlt::Properties::Properties(Mlt::Properties const&)"; "Mlt::Properties::operator=(Mlt::Properties const&)"; "Mlt::Service::Service(Mlt::Service const&)"; "Mlt::Service::operator=(Mlt::Service const&)"; "Mlt::Transition::Transition()"; "Mlt::Transition::Transition(Mlt::Transition const&)"; "Mlt::Transition::operator=(Mlt::Transition const&)"; }; } MLTPP_6.14.0; MLTPP_6.20.0 { global: extern "C++" { "Mlt::Profile::scale_width(int)"; "Mlt::Profile::scale_height(int)"; "Mlt::Properties::set_string(char const*, char const*)"; }; } MLTPP_6.18.0; MLTPP_6.22.0 { global: extern "C++" { "Mlt::Properties::property_exists(char const*)"; "Mlt::Audio::Audio()"; "Mlt::Audio::Audio(mlt_audio_s*)"; "Mlt::Audio::~Audio()"; "Mlt::Audio::data()"; "Mlt::Audio::set_data(void*)"; "Mlt::Audio::frequency()"; "Mlt::Audio::set_frequency(int)"; "Mlt::Audio::format()"; "Mlt::Audio::set_format(mlt_audio_format)"; "Mlt::Audio::samples()"; "Mlt::Audio::set_samples(int)"; "Mlt::Audio::channels()"; "Mlt::Audio::set_channels(int)"; "Mlt::Audio::layout()"; "Mlt::Audio::set_layout(mlt_channel_layout)"; }; } MLTPP_6.20.0; MLTPP_7.0.0 { global: extern "C++" { "typeinfo for Mlt::Audio"; "typeinfo name for Mlt::Audio"; "vtable for Mlt::Audio"; "typeinfo for Mlt::Image"; "typeinfo name for Mlt::Image"; "vtable for Mlt::Image"; "typeinfo for Mlt::Link"; "typeinfo name for Mlt::Link"; "vtable for Mlt::Link"; "typeinfo for Mlt::Chain"; "typeinfo name for Mlt::Chain"; "vtable for Mlt::Chain"; "Mlt::Link::Link()"; "Mlt::Link::Link(mlt_link_s*)"; "Mlt::Link::Link(char const*, char const*)"; "Mlt::Link::~Link()"; "Mlt::Link::get_link()"; "Mlt::Link::get_producer()"; "Mlt::Link::connect_next(Mlt::Producer&, Mlt::Profile&)"; "Mlt::Chain::Chain()"; "Mlt::Chain::Chain(Mlt::Profile&, char const*, char const*)"; "Mlt::Chain::Chain(Mlt::Profile&)"; "Mlt::Chain::Chain(mlt_chain)"; "Mlt::Chain::Chain(Mlt::Chain&)"; "Mlt::Chain::Chain(Mlt::Chain*)"; "Mlt::Chain::Chain(Mlt::Service&)"; "Mlt::Chain::~Chain()"; "Mlt::Chain::get_chain()"; "Mlt::Chain::get_producer()"; "Mlt::Chain::set_source(Mlt::Producer&)"; "Mlt::Chain::get_source()"; "Mlt::Chain::attach(Mlt::Link&)"; "Mlt::Chain::detach(Mlt::Link&)"; "Mlt::Chain::link_count() const"; "Mlt::Chain::move_link(int, int)"; "Mlt::Chain::link(int)"; "Mlt::Repository::links() const"; "Mlt::Parser::on_start_chain(Mlt::Chain*)"; "Mlt::Parser::on_end_chain(Mlt::Chain*)"; "Mlt::Parser::on_start_link(Mlt::Link*)"; "Mlt::Parser::on_end_link(Mlt::Link*)"; "Mlt::Animation::shift_frames(int)"; "Mlt::Image::Image()"; "Mlt::Image::Image(mlt_image_s*)"; "Mlt::Image::Image(int, int, mlt_image_format)"; "Mlt::Image::~Image()"; "Mlt::Image::format()"; "Mlt::Image::width()"; "Mlt::Image::height()"; "Mlt::Image::set_colorspace(int)"; "Mlt::Image::colorspace()"; "Mlt::Image::alloc(int, int, mlt_image_format, bool)"; "Mlt::Image::init_alpha()"; "Mlt::Image::plane(int)"; "Mlt::Image::stride(int)"; "Mlt::Properties::listen(char const*, void*, void (*)(mlt_properties_s*, void*, mlt_event_data))"; "typeinfo name for Mlt::EventData"; "vtable for Mlt::EventData"; "Mlt::EventData::EventData(mlt_event_data)"; "Mlt::EventData::EventData(Mlt::EventData&)"; "Mlt::EventData::EventData(Mlt::EventData const&)"; "Mlt::EventData::operator=(Mlt::EventData const&)"; "Mlt::EventData::get_event_data() const"; "Mlt::EventData::to_int() const"; "Mlt::EventData::to_string() const"; "Mlt::EventData::to_frame() const"; "Mlt::EventData::to_object() const"; }; } MLTPP_6.22.0; MLTPP_7.1.0 { global: extern "C++" { "Mlt::Properties::set(char const*, Mlt::Properties&)"; "Mlt::Properties::get_props(char const*)"; "Mlt::Properties::get_props_at(int)"; }; } MLTPP_7.0.0; MLT_7.4.0 { global: extern "C++" { "Mlt::Properties::is_anim(char const*)"; "Mlt::Filter::Filter(Mlt::Filter*)"; "Mlt::Link::Link(Mlt::Link*)"; "Mlt::Link::Link(Mlt::Service&)"; "Mlt::Link::Link(Mlt::Link&)"; "Mlt::Link::Link(Mlt::Link const&)"; "Mlt::Link::operator=(Mlt::Link const&)"; "Mlt::Service::Service(Mlt::Service*)"; }; } MLTPP_7.1.0; MLT_7.6.0 { global: extern "C++" { "Mlt::Animation::next_key(int, int&)"; "Mlt::Animation::previous_key(int, int&)"; "Mlt::Properties::copy(Mlt::Properties&, char const*)"; }; } MLT_7.4.0; MLT_7.12.0 { global: extern "C++" { "Mlt::Properties::anim_get_color(char const*, int, int)"; "Mlt::Properties::anim_set(char const*, mlt_color, int, int, mlt_keyframe_type)"; }; } MLT_7.6.0; MLT_7.14.0 { global: extern "C++" { "Mlt::Producer::probe()"; "Mlt::Chain::attach_normalizers()"; }; } MLT_7.12.0; mlt-7.22.0/src/modules/000775 000000 000000 00000000000 14531534050 014615 5ustar00rootroot000000 000000 mlt-7.22.0/src/modules/CMakeLists.txt000664 000000 000000 00000002463 14531534050 017362 0ustar00rootroot000000 000000 add_subdirectory(core) if(MOD_AVFORMAT) add_subdirectory(avformat) endif() if(MOD_DECKLINK) add_subdirectory(decklink) endif() if(MOD_FREI0R) add_subdirectory(frei0r) endif() if(MOD_GDK) add_subdirectory(gdk) endif() if(MOD_GLAXNIMATE OR MOD_GLAXNIMATE_QT6) add_subdirectory(glaxnimate) endif() if(MOD_JACKRACK) add_subdirectory(jackrack) endif() if(MOD_KDENLIVE) add_subdirectory(kdenlive) endif() if(MOD_NDI) add_subdirectory(ndi) endif() if(MOD_NORMALIZE) add_subdirectory(normalize) endif() if(MOD_OLDFILM) add_subdirectory(oldfilm) endif() if(MOD_OPENCV) add_subdirectory(opencv) endif() if(MOD_MOVIT) add_subdirectory(movit) endif() if(MOD_PLUS) add_subdirectory(plus) endif() if(MOD_PLUSGPL) add_subdirectory(plusgpl) endif() if(MOD_QT OR MOD_QT6) add_subdirectory(qt) endif() if(MOD_RESAMPLE) add_subdirectory(resample) endif() if(MOD_RTAUDIO) add_subdirectory(rtaudio) endif() if(MOD_RUBBERBAND) add_subdirectory(rubberband) endif() if(MOD_SDL1) add_subdirectory(sdl) endif() if(MOD_SDL2) add_subdirectory(sdl2) endif() if(MOD_SOX) add_subdirectory(sox) endif() if(MOD_VIDSTAB) add_subdirectory(vid.stab) endif() if(MOD_VORBIS) add_subdirectory(vorbis) endif() if(MOD_XINE) add_subdirectory(xine) endif() if(MOD_XML) add_subdirectory(xml) endif() mlt-7.22.0/src/modules/avformat/000775 000000 000000 00000000000 14531534050 016434 5ustar00rootroot000000 000000 mlt-7.22.0/src/modules/avformat/CMakeLists.txt000664 000000 000000 00000004136 14531534050 021200 0ustar00rootroot000000 000000 add_library(mltavformat MODULE common.c common.h factory.c filter_avcolour_space.c filter_avdeinterlace.c filter_swscale.c ) file(GLOB YML "*.yml") add_custom_target(Other_avformat_Files SOURCES ${YML} blacklist.txt yuv_only.txt ) target_compile_options(mltavformat PRIVATE ${MLT_COMPILE_OPTIONS}) target_link_libraries(mltavformat PRIVATE mlt m Threads::Threads PkgConfig::libavformat PkgConfig::libswscale PkgConfig::libavutil ) target_compile_definitions(mltavformat PRIVATE FILTERS) if(WIN32) target_compile_definitions(mltavformat PRIVATE AVDATADIR="share/ffmpeg/") endif() if(TARGET PkgConfig::libavcodec) target_sources(mltavformat PRIVATE producer_avformat.c consumer_avformat.c) target_link_libraries(mltavformat PRIVATE PkgConfig::libavcodec) target_compile_definitions(mltavformat PRIVATE CODECS) endif() if(TARGET PkgConfig::libavfilter) target_sources(mltavformat PRIVATE filter_avfilter.c link_avdeinterlace.c link_avfilter.c) target_link_libraries(mltavformat PRIVATE PkgConfig::libavfilter) target_compile_definitions(mltavformat PRIVATE AVFILTER) endif() if(TARGET PkgConfig::libavdevice) target_link_libraries(mltavformat PRIVATE PkgConfig::libavdevice) target_compile_definitions(mltavformat PRIVATE AVDEVICE) endif() if(TARGET PkgConfig::libswresample) target_sources(mltavformat PRIVATE common_swr.c common_swr.h filter_swresample.c link_swresample.c) target_link_libraries(mltavformat PRIVATE PkgConfig::libswresample) target_compile_definitions(mltavformat PRIVATE SWRESAMPLE) endif() if(CPU_MMX) target_compile_definitions(mltavformat PRIVATE USE_MMX) endif() set_target_properties(mltavformat PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${MLT_MODULE_OUTPUT_DIRECTORY}") install(TARGETS mltavformat LIBRARY DESTINATION ${MLT_INSTALL_MODULE_DIR}) install(FILES consumer_avformat.yml filter_avcolour_space.yml filter_avdeinterlace.yml filter_swresample.yml filter_swscale.yml link_avdeinterlace.yml link_swresample.yml producer_avformat.yml resolution_scale.yml blacklist.txt yuv_only.txt DESTINATION ${MLT_INSTALL_DATA_DIR}/avformat ) mlt-7.22.0/src/modules/avformat/blacklist.txt000664 000000 000000 00000001522 14531534050 021145 0ustar00rootroot000000 000000 acopy acrossfade afifo aformat amerge amix amultiply anull apad aperms aresample areverse asendcmd asetnsamples asetpts asetrate asettb astreamselect atempo atrim channelsplit codecview copy cover_rect dejudder detelecine displace extractplanes fifo format fps framerate framestep frei0r hysteresis join interlace ladspa maskedclamp maskedmerge mergeplanes mestimate midequalizer minterpolate mix mpdecimate noformat null overlay palettegen paletteuse pan perms pixdesctest premultiply psnr pullup qp readeia608 readvitc repeatfields remap repeatfields replaygain resample reverse scale scale2ref sendcmd separatefields setdar setfield setparams setpts setsar settb showpalette sidechaincompress sidechaingate silenceremove ssim streamselect surround telecine thumbnail tile tinterlace trim unpremultiply vidstabdetect vidstabtransform vfrdet xstack mlt-7.22.0/src/modules/avformat/common.c000664 000000 000000 00000035374 14531534050 020104 0ustar00rootroot000000 000000 /* * common.h * Copyright (C) 2018-2023 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "common.h" #include #include #include int mlt_get_sws_flags( int srcwidth, int srcheight, int srcformat, int dstwidth, int dstheight, int dstformat) { // Use default flags unless there is a reason to use something different. int flags = SWS_BICUBIC | SWS_FULL_CHR_H_INP | SWS_FULL_CHR_H_INT | SWS_ACCURATE_RND; if (srcwidth != dstwidth || srcheight != dstheight) { // Any resolution change should use default flags return flags; } const AVPixFmtDescriptor *srcDesc = av_pix_fmt_desc_get(srcformat); const AVPixFmtDescriptor *dstDesc = av_pix_fmt_desc_get(dstformat); if (!srcDesc || !dstDesc) { return flags; } if ((srcDesc->flags & AV_PIX_FMT_FLAG_RGB) != 0 && (dstDesc->flags & AV_PIX_FMT_FLAG_RGB) == 0) { // RGB -> YUV flags = SWS_BICUBIC; flags |= SWS_ACCURATE_RND; // Can improve precision by one bit flags |= SWS_FULL_CHR_H_INT; // Avoids luma reduction. Causes chroma bleeding when used with SWS_BICUBIC } else if ((srcDesc->flags & AV_PIX_FMT_FLAG_RGB) == 0 && (dstDesc->flags & AV_PIX_FMT_FLAG_RGB) != 0) { // YUV -> RGB // Going from lower sampling to full sampling - so pick the closest sample without interpolation flags = SWS_POINT; flags |= SWS_ACCURATE_RND; // Can improve precision by one bit flags |= SWS_FULL_CHR_H_INT; // Avoids luma reduction. Does not cause chroma bleeding when used with SWS_POINT } else if ((srcDesc->flags & AV_PIX_FMT_FLAG_RGB) == 0 && (dstDesc->flags & AV_PIX_FMT_FLAG_RGB) == 0) { // YUV -> YUV if (srcDesc->log2_chroma_w == dstDesc->log2_chroma_w && srcDesc->log2_chroma_h == dstDesc->log2_chroma_h) { // No chroma subsampling conversion. No interpolation required flags = SWS_POINT; flags |= SWS_ACCURATE_RND; // Can improve precision by one bit } else { // Chroma will be interpolated. Bilinear is suitable. flags = SWS_BILINEAR | SWS_ACCURATE_RND; } } return flags; } int mlt_to_av_sample_format(mlt_audio_format format) { switch (format) { case mlt_audio_none: return AV_SAMPLE_FMT_NONE; case mlt_audio_s16: return AV_SAMPLE_FMT_S16; case mlt_audio_s32: return AV_SAMPLE_FMT_S32P; case mlt_audio_float: return AV_SAMPLE_FMT_FLTP; case mlt_audio_s32le: return AV_SAMPLE_FMT_S32; case mlt_audio_f32le: return AV_SAMPLE_FMT_FLT; case mlt_audio_u8: return AV_SAMPLE_FMT_U8; } mlt_log_error(NULL, "[avformat] Unknown audio format: %d\n", format); return AV_SAMPLE_FMT_NONE; } int64_t mlt_to_av_channel_layout(mlt_channel_layout layout) { switch (layout) { case mlt_channel_auto: case mlt_channel_independent: mlt_log_error(NULL, "[avformat] No matching channel layout: %s\n", mlt_audio_channel_layout_name(layout)); return 0; case mlt_channel_mono: return AV_CH_LAYOUT_MONO; case mlt_channel_stereo: return AV_CH_LAYOUT_STEREO; case mlt_channel_2p1: return AV_CH_LAYOUT_2POINT1; case mlt_channel_3p0: return AV_CH_LAYOUT_SURROUND; case mlt_channel_3p0_back: return AV_CH_LAYOUT_2_1; case mlt_channel_3p1: return AV_CH_LAYOUT_3POINT1; case mlt_channel_4p0: return AV_CH_LAYOUT_4POINT0; case mlt_channel_quad_back: return AV_CH_LAYOUT_QUAD; case mlt_channel_quad_side: return AV_CH_LAYOUT_2_2; case mlt_channel_5p0: return AV_CH_LAYOUT_5POINT0; case mlt_channel_5p0_back: return AV_CH_LAYOUT_5POINT0_BACK; case mlt_channel_4p1: return AV_CH_LAYOUT_4POINT1; case mlt_channel_5p1: return AV_CH_LAYOUT_5POINT1; case mlt_channel_5p1_back: return AV_CH_LAYOUT_5POINT1_BACK; case mlt_channel_6p0: return AV_CH_LAYOUT_6POINT0; case mlt_channel_6p0_front: return AV_CH_LAYOUT_6POINT0_FRONT; case mlt_channel_hexagonal: return AV_CH_LAYOUT_HEXAGONAL; case mlt_channel_6p1: return AV_CH_LAYOUT_6POINT1; case mlt_channel_6p1_back: return AV_CH_LAYOUT_6POINT1_BACK; case mlt_channel_6p1_front: return AV_CH_LAYOUT_6POINT1_FRONT; case mlt_channel_7p0: return AV_CH_LAYOUT_7POINT0; case mlt_channel_7p0_front: return AV_CH_LAYOUT_7POINT0_FRONT; case mlt_channel_7p1: return AV_CH_LAYOUT_7POINT1; case mlt_channel_7p1_wide_side: return AV_CH_LAYOUT_7POINT1_WIDE; case mlt_channel_7p1_wide_back: return AV_CH_LAYOUT_7POINT1_WIDE_BACK; } mlt_log_error(NULL, "[avformat] Unknown channel configuration: %d\n", layout); return 0; } mlt_channel_layout av_channel_layout_to_mlt(int64_t layout) { switch (layout) { case 0: return mlt_channel_independent; case AV_CH_LAYOUT_MONO: return mlt_channel_mono; case AV_CH_LAYOUT_STEREO: return mlt_channel_stereo; case AV_CH_LAYOUT_STEREO_DOWNMIX: return mlt_channel_stereo; case AV_CH_LAYOUT_2POINT1: return mlt_channel_2p1; case AV_CH_LAYOUT_SURROUND: return mlt_channel_3p0; case AV_CH_LAYOUT_2_1: return mlt_channel_3p0_back; case AV_CH_LAYOUT_3POINT1: return mlt_channel_3p1; case AV_CH_LAYOUT_4POINT0: return mlt_channel_4p0; case AV_CH_LAYOUT_QUAD: return mlt_channel_quad_back; case AV_CH_LAYOUT_2_2: return mlt_channel_quad_side; case AV_CH_LAYOUT_5POINT0: return mlt_channel_5p0; case AV_CH_LAYOUT_5POINT0_BACK: return mlt_channel_5p0_back; case AV_CH_LAYOUT_4POINT1: return mlt_channel_4p1; case AV_CH_LAYOUT_5POINT1: return mlt_channel_5p1; case AV_CH_LAYOUT_5POINT1_BACK: return mlt_channel_5p1_back; case AV_CH_LAYOUT_6POINT0: return mlt_channel_6p0; case AV_CH_LAYOUT_6POINT0_FRONT: return mlt_channel_6p0_front; case AV_CH_LAYOUT_HEXAGONAL: return mlt_channel_hexagonal; case AV_CH_LAYOUT_6POINT1: return mlt_channel_6p1; case AV_CH_LAYOUT_6POINT1_BACK: return mlt_channel_6p1_back; case AV_CH_LAYOUT_6POINT1_FRONT: return mlt_channel_6p1_front; case AV_CH_LAYOUT_7POINT0: return mlt_channel_7p0; case AV_CH_LAYOUT_7POINT0_FRONT: return mlt_channel_7p0_front; case AV_CH_LAYOUT_7POINT1: return mlt_channel_7p1; case AV_CH_LAYOUT_7POINT1_WIDE: return mlt_channel_7p1_wide_side; case AV_CH_LAYOUT_7POINT1_WIDE_BACK: return mlt_channel_7p1_wide_back; } mlt_log_error(NULL, "[avformat] Unknown channel layout: %lu\n", (unsigned long) layout); return mlt_channel_independent; } mlt_channel_layout mlt_get_channel_layout_or_default(const char *name, int channels) { mlt_channel_layout layout = mlt_audio_channel_layout_id(name); if (layout == mlt_channel_auto || (layout != mlt_channel_independent && mlt_audio_channel_layout_channels(layout) != channels)) { layout = mlt_audio_channel_layout_default(channels); } return layout; } int mlt_set_luma_transfer(struct SwsContext *context, int src_colorspace, int dst_colorspace, int src_full_range, int dst_full_range) { const int *src_coefficients = sws_getCoefficients(SWS_CS_DEFAULT); const int *dst_coefficients = sws_getCoefficients(SWS_CS_DEFAULT); int brightness = 0; int contrast = 1 << 16; int saturation = 1 << 16; int src_range = src_full_range ? 1 : 0; int dst_range = dst_full_range ? 1 : 0; switch (src_colorspace) { case 170: case 470: case 601: case 624: src_coefficients = sws_getCoefficients(SWS_CS_ITU601); break; case 240: src_coefficients = sws_getCoefficients(SWS_CS_SMPTE240M); break; case 709: src_coefficients = sws_getCoefficients(SWS_CS_ITU709); break; default: break; } switch (dst_colorspace) { case 170: case 470: case 601: case 624: dst_coefficients = sws_getCoefficients(SWS_CS_ITU601); break; case 240: dst_coefficients = sws_getCoefficients(SWS_CS_SMPTE240M); break; case 709: dst_coefficients = sws_getCoefficients(SWS_CS_ITU709); break; default: break; } return sws_setColorspaceDetails(context, src_coefficients, src_range, dst_coefficients, dst_range, brightness, contrast, saturation); } int mlt_to_av_image_format(mlt_image_format format) { switch (format) { case mlt_image_none: return AV_PIX_FMT_NONE; case mlt_image_rgb: return AV_PIX_FMT_RGB24; case mlt_image_rgba: return AV_PIX_FMT_RGBA; case mlt_image_yuv422: return AV_PIX_FMT_YUYV422; case mlt_image_yuv420p: return AV_PIX_FMT_YUV420P; case mlt_image_yuv420p10: return AV_PIX_FMT_YUV420P10LE; case mlt_image_yuv444p10: return AV_PIX_FMT_YUV444P10LE; case mlt_image_yuv422p16: return AV_PIX_FMT_YUV422P16LE; case mlt_image_movit: case mlt_image_opengl_texture: case mlt_image_invalid: mlt_log_error(NULL, "[filter_avfilter] Unexpected image format: %s\n", mlt_image_format_name(format)); return AV_PIX_FMT_NONE; } mlt_log_error(NULL, "[filter_avfilter] Unknown image format: %d\n", format); return AV_PIX_FMT_NONE; } mlt_image_format mlt_get_supported_image_format(mlt_image_format format) { switch (format) { case mlt_image_invalid: case mlt_image_none: case mlt_image_movit: case mlt_image_opengl_texture: case mlt_image_rgba: return mlt_image_rgba; case mlt_image_rgb: return mlt_image_rgb; case mlt_image_yuv420p: return mlt_image_yuv420p; case mlt_image_yuv422: return mlt_image_yuv422; case mlt_image_yuv420p10: return mlt_image_yuv420p10; case mlt_image_yuv444p10: return mlt_image_yuv444p10; case mlt_image_yuv422p16: return mlt_image_yuv422p16; } mlt_log_error(NULL, "[filter_avfilter] Unknown image format requested: %d\n", format); return mlt_image_rgba; } void mlt_image_to_avframe(mlt_image image, mlt_frame mltframe, AVFrame *avframe) { mlt_properties frame_properties = MLT_FRAME_PROPERTIES(mltframe); avframe->width = image->width; avframe->height = image->height; avframe->format = mlt_to_av_image_format(image->format); avframe->sample_aspect_ratio = av_d2q(mlt_frame_get_aspect_ratio(mltframe), 1024); ; avframe->pts = mlt_frame_get_position(mltframe); avframe->interlaced_frame = !mlt_properties_get_int(frame_properties, "progressive"); avframe->top_field_first = mlt_properties_get_int(frame_properties, "top_field_first"); avframe->color_primaries = mlt_properties_get_int(frame_properties, "color_primaries"); avframe->color_trc = mlt_properties_get_int(frame_properties, "color_trc"); avframe->color_range = mlt_properties_get_int(frame_properties, "full_range") ? AVCOL_RANGE_JPEG : AVCOL_RANGE_MPEG; switch (mlt_properties_get_int(frame_properties, "colorspace")) { case 240: avframe->colorspace = AVCOL_SPC_SMPTE240M; break; case 601: avframe->colorspace = AVCOL_SPC_BT470BG; break; case 709: avframe->colorspace = AVCOL_SPC_BT709; break; case 2020: avframe->colorspace = AVCOL_SPC_BT2020_NCL; break; case 2021: avframe->colorspace = AVCOL_SPC_BT2020_CL; break; } int ret = av_frame_get_buffer(avframe, 1); if (ret < 0) { mlt_log_error(NULL, "Cannot get frame buffer\n"); } // Set up the input frame if (image->format == mlt_image_yuv420p) { int i = 0; int p = 0; int widths[3] = {image->width, image->width / 2, image->width / 2}; int heights[3] = {image->height, image->height / 2, image->height / 2}; uint8_t *src = image->data; for (p = 0; p < 3; p++) { uint8_t *dst = avframe->data[p]; for (i = 0; i < heights[p]; i++) { memcpy(dst, src, widths[p]); src += widths[p]; dst += avframe->linesize[p]; } } } else { int i; uint8_t *src = image->data; uint8_t *dst = avframe->data[0]; int stride = mlt_image_format_size(image->format, image->width, 1, NULL); for (i = 0; i < image->height; i++) { memcpy(dst, src, stride); src += stride; dst += avframe->linesize[0]; } } } void avframe_to_mlt_image(AVFrame *avframe, mlt_image image) { if (image->format == mlt_image_yuv420p) { int i = 0; int p = 0; int widths[3] = {image->width, image->width / 2, image->width / 2}; int heights[3] = {image->height, image->height / 2, image->height / 2}; uint8_t *dst = image->data; for (p = 0; p < 3; p++) { uint8_t *src = avframe->data[p]; for (i = 0; i < heights[p]; i++) { memcpy(dst, src, widths[p]); dst += widths[p]; src += avframe->linesize[p]; } } } else { int i; uint8_t *dst = image->data; uint8_t *src = avframe->data[0]; int stride = mlt_image_format_size(image->format, image->width, 1, NULL); for (i = 0; i < image->height; i++) { memcpy(dst, src, stride); dst += stride; src += avframe->linesize[0]; } } } mlt-7.22.0/src/modules/avformat/common.h000664 000000 000000 00000003600 14531534050 020074 0ustar00rootroot000000 000000 /* * common.h * Copyright (C) 2018 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef COMMON_H #define COMMON_H #include #include #include #define MLT_AVFILTER_SWS_FLAGS "bicubic+accurate_rnd+full_chroma_int+full_chroma_inp" int mlt_to_av_sample_format(mlt_audio_format format); int64_t mlt_to_av_channel_layout(mlt_channel_layout layout); mlt_channel_layout av_channel_layout_to_mlt(int64_t layout); mlt_channel_layout mlt_get_channel_layout_or_default(const char *name, int channels); int mlt_set_luma_transfer(struct SwsContext *context, int src_colorspace, int dst_colorspace, int src_full_range, int dst_full_range); int mlt_get_sws_flags( int srcwidth, int srcheight, int srcformat, int dstwidth, int dstheight, int dstformat); int mlt_to_av_image_format(mlt_image_format format); mlt_image_format mlt_get_supported_image_format(mlt_image_format format); void mlt_image_to_avframe(mlt_image image, mlt_frame mltframe, AVFrame *avframe); void avframe_to_mlt_image(AVFrame *avframe, mlt_image image); #endif // COMMON_H mlt-7.22.0/src/modules/avformat/common_swr.c000664 000000 000000 00000010617 14531534050 020770 0ustar00rootroot000000 000000 /* * common.h * Copyright (C) 2022-2023 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "common_swr.h" #include "common.h" #include #include #include int mlt_configure_swr_context(mlt_service service, mlt_swr_private_data *pdata) { int error = 0; mlt_log_info(service, "%d(%s) %s %dHz -> %d(%s) %s %dHz\n", pdata->in_channels, mlt_audio_channel_layout_name(pdata->in_layout), mlt_audio_format_name(pdata->in_format), pdata->in_frequency, pdata->out_channels, mlt_audio_channel_layout_name(pdata->out_layout), mlt_audio_format_name(pdata->out_format), pdata->out_frequency); mlt_free_swr_context(pdata); pdata->ctx = swr_alloc(); if (!pdata->ctx) { mlt_log_error(service, "Cannot allocate context\n"); return 1; } // Configure format, frequency and channels. av_opt_set_int(pdata->ctx, "osf", mlt_to_av_sample_format(pdata->out_format), 0); av_opt_set_int(pdata->ctx, "osr", pdata->out_frequency, 0); av_opt_set_int(pdata->ctx, "och", pdata->out_channels, 0); av_opt_set_int(pdata->ctx, "isf", mlt_to_av_sample_format(pdata->in_format), 0); av_opt_set_int(pdata->ctx, "isr", pdata->in_frequency, 0); av_opt_set_int(pdata->ctx, "ich", pdata->in_channels, 0); if (pdata->in_layout != mlt_channel_independent && pdata->out_layout != mlt_channel_independent) { // Use standard channel layout and matrix for known channel configurations. av_opt_set_int(pdata->ctx, "ocl", mlt_to_av_channel_layout(pdata->out_layout), 0); av_opt_set_int(pdata->ctx, "icl", mlt_to_av_channel_layout(pdata->in_layout), 0); } else { // Use a custom channel layout and matrix for independent channels. // This matrix will simply map input channels to output channels in order. // If input channels > output channels, channels will be dropped. // If input channels < output channels, silent channels will be added. int64_t custom_in_layout = 0; int64_t custom_out_layout = 0; double *matrix = av_calloc(pdata->in_channels * pdata->out_channels, sizeof(double)); int stride = pdata->in_channels; int i = 0; for (i = 0; i < pdata->in_channels; i++) { custom_in_layout = (custom_in_layout << 1) | 0x01; } for (i = 0; i < pdata->out_channels; i++) { custom_out_layout = (custom_out_layout << 1) | 0x01; if (i < pdata->in_channels) { double *matrix_row = matrix + (stride * i); matrix_row[i] = 1.0; } } av_opt_set_int(pdata->ctx, "ocl", custom_out_layout, 0); av_opt_set_int(pdata->ctx, "icl", custom_in_layout, 0); error = swr_set_matrix(pdata->ctx, matrix, stride); av_free(matrix); if (error != 0) { swr_free(&pdata->ctx); mlt_log_error(service, "Unable to create custom matrix\n"); return error; } } error = swr_init(pdata->ctx); if (error != 0) { swr_free(&pdata->ctx); mlt_log_error(service, "Cannot initialize context\n"); return error; } // Allocate the channel buffer pointers pdata->in_buffers = av_calloc(pdata->in_channels, sizeof(uint8_t *)); pdata->out_buffers = av_calloc(pdata->out_channels, sizeof(uint8_t *)); return error; } void mlt_free_swr_context(mlt_swr_private_data *pdata) { if (pdata) { swr_free(&pdata->ctx); av_freep(&pdata->in_buffers); av_freep(&pdata->out_buffers); } } mlt-7.22.0/src/modules/avformat/common_swr.h000664 000000 000000 00000002575 14531534050 021001 0ustar00rootroot000000 000000 /* * common.h * Copyright (C) 2022 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef COMMON_SWR_H #define COMMON_SWR_H #include #include typedef struct { SwrContext *ctx; uint8_t **in_buffers; uint8_t **out_buffers; mlt_audio_format in_format; mlt_audio_format out_format; int in_frequency; int out_frequency; int in_channels; int out_channels; mlt_channel_layout in_layout; mlt_channel_layout out_layout; } mlt_swr_private_data; int mlt_configure_swr_context(mlt_service service, mlt_swr_private_data *pdata); void mlt_free_swr_context(mlt_swr_private_data *pdata); #endif // COMMON_H mlt-7.22.0/src/modules/avformat/consumer_avformat.c000664 000000 000000 00000274662 14531534050 022353 0ustar00rootroot000000 000000 /* * consumer_avformat.c -- an encoder based on avformat * Copyright (C) 2003-2023 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "common.h" // mlt Header files #include #include #include #include #include // System header files #include #include #include #include #include #include #include #include // avformat header files #include #include #include #include #include #include #include #include #include #include #include #ifdef AVFILTER #include #include #include #endif #define MAX_AUDIO_STREAMS (8) #define AUDIO_ENCODE_BUFFER_SIZE (48000 * 2 * MAX_AUDIO_STREAMS) #define AUDIO_BUFFER_SIZE (1024 * 42) #define VIDEO_BUFFER_SIZE (8192 * 8192) #define IMAGE_ALIGN (4) // // This structure should be extended and made globally available in mlt // typedef struct { uint8_t *buffer; int size; int used; double time; int frequency; int channels; } * sample_fifo, sample_fifo_s; sample_fifo sample_fifo_init(int frequency, int channels) { sample_fifo fifo = calloc(1, sizeof(sample_fifo_s)); fifo->frequency = frequency; fifo->channels = channels; return fifo; } // count is the number of samples multiplied by the number of bytes per sample void sample_fifo_append(sample_fifo fifo, uint8_t *samples, int count) { if ((fifo->size - fifo->used) < count) { fifo->size += count * 5; fifo->buffer = realloc(fifo->buffer, fifo->size); } memcpy(&fifo->buffer[fifo->used], samples, count); fifo->used += count; } int sample_fifo_used(sample_fifo fifo) { return fifo->used; } int sample_fifo_fetch(sample_fifo fifo, uint8_t *samples, int count) { if (count > fifo->used) count = fifo->used; memcpy(samples, fifo->buffer, count); fifo->used -= count; memmove(fifo->buffer, &fifo->buffer[count], fifo->used); fifo->time += (double) count / fifo->channels / fifo->frequency; return count; } void sample_fifo_close(sample_fifo fifo) { free(fifo->buffer); free(fifo); } #if defined(AVFILTER) static AVFilterGraph *vfilter_graph; static int setup_hwupload_filter(mlt_properties properties, AVStream *stream, AVCodecContext *codec_context) { AVFilterContext *vfilter_in; AVFilterContext *vfilter_out; AVFilterContext *vfilter_hwupload; vfilter_graph = avfilter_graph_alloc(); mlt_properties_set_data(properties, "vfilter_graph", &vfilter_graph, 0, (mlt_destructor) avfilter_graph_free, NULL); // From ffplay.c:configure_video_filters(). char buffersrc_args[256]; snprintf(buffersrc_args, sizeof(buffersrc_args), "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d:frame_rate=%d/%d", codec_context->width, codec_context->height, AV_PIX_FMT_NV12, stream->time_base.num, stream->time_base.den, codec_context->sample_aspect_ratio.num, codec_context->sample_aspect_ratio.den, codec_context->time_base.den, codec_context->time_base.num); int result = avfilter_graph_create_filter(&vfilter_in, avfilter_get_by_name("buffer"), "mlt_buffer", buffersrc_args, NULL, vfilter_graph); if (result >= 0) { result = avfilter_graph_create_filter(&vfilter_out, avfilter_get_by_name("buffersink"), "mlt_buffersink", NULL, NULL, vfilter_graph); if (result >= 0) { enum AVPixelFormat pix_fmts[] = {codec_context->pix_fmt, AV_PIX_FMT_NONE}; result = av_opt_set_int_list(vfilter_out, "pix_fmts", pix_fmts, AV_PIX_FMT_NONE, AV_OPT_SEARCH_CHILDREN); result = avfilter_graph_create_filter(&vfilter_hwupload, avfilter_get_by_name("hwupload"), "mlt_hwupload", "", NULL, vfilter_graph); if (result >= 0) { vfilter_hwupload->hw_device_ctx = av_buffer_ref(codec_context->hw_device_ctx); result = avfilter_link(vfilter_in, 0, vfilter_hwupload, 0); if (result >= 0) { result = avfilter_link(vfilter_hwupload, 0, vfilter_out, 0); if (result >= 0) { result = avfilter_graph_config(vfilter_graph, NULL); if (result >= 0) codec_context->hw_frames_ctx = av_buffer_ref( av_buffersink_get_hw_frames_ctx(vfilter_out)); } } } mlt_properties_set_data(properties, "vfilter_in", vfilter_in, 0, NULL, NULL); mlt_properties_set_data(properties, "vfilter_out", vfilter_out, 0, NULL, NULL); } } return result; } static AVBufferRef *hw_device_ctx; static int init_vaapi(mlt_properties properties, AVCodecContext *codec_context) { int err = 0; const char *vaapi_device = mlt_properties_get(properties, "vaapi_device"); AVDictionary *opts = NULL; av_dict_set(&opts, "connection_type", mlt_properties_get(properties, "connection_type"), 0); av_dict_set(&opts, "driver", mlt_properties_get(properties, "driver"), 0); av_dict_set(&opts, "kernel_driver", mlt_properties_get(properties, "kernel_driver"), 0); if ((err = av_hwdevice_ctx_create(&hw_device_ctx, AV_HWDEVICE_TYPE_VAAPI, vaapi_device, opts, 0)) < 0) { mlt_log_warning(NULL, "Failed to create VAAPI device.\n"); } else { codec_context->hw_device_ctx = av_buffer_ref(hw_device_ctx); mlt_properties_set_data(properties, "hw_device_ctx", &hw_device_ctx, 0, (mlt_destructor) av_buffer_unref, NULL); } av_dict_free(&opts); return err; } #endif // Forward references. static void property_changed(mlt_properties owner, mlt_consumer self, mlt_event_data); static int consumer_start(mlt_consumer consumer); static int consumer_stop(mlt_consumer consumer); static int consumer_is_stopped(mlt_consumer consumer); static void *consumer_thread(void *arg); static void consumer_close(mlt_consumer consumer); /** Initialise the consumer. */ mlt_consumer consumer_avformat_init(mlt_profile profile, char *arg) { // Allocate the consumer mlt_consumer consumer = mlt_consumer_new(profile); // If memory allocated and initialises without error if (consumer != NULL) { // Get properties from the consumer mlt_properties properties = MLT_CONSUMER_PROPERTIES(consumer); // Assign close callback consumer->close = consumer_close; // Interpret the argument if (arg != NULL) mlt_properties_set(properties, "target", arg); // sample and frame queue mlt_properties_set_data(properties, "frame_queue", mlt_deque_init(), 0, (mlt_destructor) mlt_deque_close, NULL); // Audio options not fully handled by AVOptions #define QSCALE_NONE (-99999) mlt_properties_set_int(properties, "aq", QSCALE_NONE); // Video options not fully handled by AVOptions mlt_properties_set_int(properties, "dc", 8); // Muxer options not fully handled by AVOptions mlt_properties_set_double(properties, "muxdelay", 0.7); mlt_properties_set_double(properties, "muxpreload", 0.5); // Ensure termination at end of the stream mlt_properties_set_int(properties, "terminate_on_pause", 1); // Default to separate processing threads for producer and consumer with no frame dropping! mlt_properties_set_int(properties, "real_time", -1); mlt_properties_set_int(properties, "prefill", 1); // Set up start/stop/terminated callbacks consumer->start = consumer_start; consumer->stop = consumer_stop; consumer->is_stopped = consumer_is_stopped; mlt_events_register(properties, "consumer-fatal-error"); mlt_event event = mlt_events_listen(properties, consumer, "property-changed", (mlt_listener) property_changed); mlt_properties_set_data(properties, "property-changed event", event, 0, NULL, NULL); } // Return consumer return consumer; } static void recompute_aspect_ratio(mlt_properties properties) { double ar = mlt_properties_get_double(properties, "aspect"); if (ar > 0.0) { AVRational rational = av_d2q(ar, 255); int width = mlt_properties_get_int(properties, "width"); int height = mlt_properties_get_int(properties, "height"); // Update the profile and properties as well since this is an alias // for mlt properties that correspond to profile settings mlt_properties_set_int(properties, "display_aspect_num", rational.num); mlt_properties_set_int(properties, "display_aspect_den", rational.den); // Now compute the sample aspect ratio rational = av_d2q(ar * height / FFMAX(width, 1), 255); // Update the profile and properties as well since this is an alias // for mlt properties that correspond to profile settings mlt_properties_set_int(properties, "sample_aspect_num", rational.num); mlt_properties_set_int(properties, "sample_aspect_den", rational.den); } } static void color_trc_from_colorspace(mlt_properties properties) { // Default color transfer characteristic from colorspace. switch (mlt_properties_get_int(properties, "colorspace")) { case 709: mlt_properties_set_int(properties, "color_trc", AVCOL_TRC_BT709); break; case 470: mlt_properties_set_int(properties, "color_trc", AVCOL_TRC_GAMMA28); break; case 240: mlt_properties_set_int(properties, "color_trc", AVCOL_TRC_SMPTE240M); break; case 0: // sRGB mlt_properties_set_int(properties, "color_trc", AVCOL_TRC_IEC61966_2_1); break; case 601: case 170: mlt_properties_set_int(properties, "color_trc", AVCOL_TRC_SMPTE170M); break; default: break; } } static void color_primaries_from_colorspace(mlt_properties properties) { // Default color transfer characteristic from colorspace. switch (mlt_properties_get_int(properties, "colorspace")) { case 0: // sRGB case 709: mlt_properties_set_int(properties, "color_primaries", AVCOL_PRI_BT709); break; case 470: mlt_properties_set_int(properties, "color_primaries", AVCOL_PRI_BT470M); break; case 240: mlt_properties_set_int(properties, "color_primaries", AVCOL_PRI_SMPTE240M); break; case 601: mlt_properties_set_int(properties, "color_primaries", mlt_properties_get_int(properties, "height") == 576 ? AVCOL_PRI_BT470BG : AVCOL_PRI_SMPTE170M); break; case 170: mlt_properties_set_int(properties, "color_primaries", AVCOL_PRI_SMPTE170M); break; default: break; } } static void property_changed(mlt_properties owner, mlt_consumer self, mlt_event_data event_data) { mlt_properties properties = MLT_CONSUMER_PROPERTIES(self); const char *name = mlt_event_data_to_string(event_data); if (name && !strcmp(name, "s")) { // Obtain the size property char *size = mlt_properties_get(properties, "s"); int width = mlt_properties_get_int(properties, "width"); int height = mlt_properties_get_int(properties, "height"); int tw, th; if (sscanf(size, "%dx%d", &tw, &th) == 2 && tw > 0 && th > 0) { width = tw; height = th; } else { mlt_log_warning(MLT_CONSUMER_SERVICE(self), "Invalid size property %s - ignoring.\n", size); } // Now ensure we honour the multiple of two requested by libavformat width = (width / 2) * 2; height = (height / 2) * 2; mlt_properties_set_int(properties, "width", width); mlt_properties_set_int(properties, "height", height); recompute_aspect_ratio(properties); } // "-aspect" on ffmpeg command line is display aspect ratio else if (!strcmp(name, "aspect") || !strcmp(name, "width") || !strcmp(name, "height")) { recompute_aspect_ratio(properties); } // Handle the ffmpeg command line "-r" property for frame rate else if (!strcmp(name, "r")) { double frame_rate = mlt_properties_get_double(properties, "r"); AVRational rational = av_d2q(frame_rate, 255); mlt_properties_set_int(properties, "frame_rate_num", rational.num); mlt_properties_set_int(properties, "frame_rate_den", rational.den); } // Apply AVOptions that are synonyms for standard mlt_consumer options else if (!strcmp(name, "ac")) { mlt_properties_set_int(properties, "channels", mlt_properties_get_int(properties, "ac")); } else if (!strcmp(name, "ar")) { mlt_properties_set_int(properties, "frequency", mlt_properties_get_int(properties, "ar")); } } /** Start the consumer. */ static int consumer_start(mlt_consumer consumer) { // Get the properties mlt_properties properties = MLT_CONSUMER_PROPERTIES(consumer); int error = 0; // Report information about available muxers and codecs as YAML Tiny char *s = mlt_properties_get(properties, "f"); if (s && strcmp(s, "list") == 0) { mlt_properties doc = mlt_properties_new(); mlt_properties formats = mlt_properties_new(); char key[20]; const AVOutputFormat *format = NULL; void *iterator = NULL; mlt_properties_set_data(properties, "f", formats, 0, (mlt_destructor) mlt_properties_close, NULL); mlt_properties_set_data(doc, "formats", formats, 0, NULL, NULL); while ((format = av_muxer_iterate(&iterator))) { snprintf(key, sizeof(key), "%d", mlt_properties_count(formats)); mlt_properties_set(formats, key, format->name); } s = mlt_properties_serialise_yaml(doc); fprintf(stdout, "%s", s); free(s); mlt_properties_close(doc); error = 1; } s = mlt_properties_get(properties, "acodec"); if (s && strcmp(s, "list") == 0) { mlt_properties doc = mlt_properties_new(); mlt_properties codecs = mlt_properties_new(); char key[20]; const AVCodec *codec = NULL; mlt_properties_set_data(properties, "acodec", codecs, 0, (mlt_destructor) mlt_properties_close, NULL); mlt_properties_set_data(doc, "audio_codecs", codecs, 0, NULL, NULL); void *iterator = NULL; while ((codec = av_codec_iterate(&iterator))) if (av_codec_is_encoder(codec) && codec->type == AVMEDIA_TYPE_AUDIO) { snprintf(key, sizeof(key), "%d", mlt_properties_count(codecs)); mlt_properties_set(codecs, key, codec->name); } s = mlt_properties_serialise_yaml(doc); fprintf(stdout, "%s", s); free(s); mlt_properties_close(doc); error = 1; } s = mlt_properties_get(properties, "vcodec"); if (s && strcmp(s, "list") == 0) { mlt_properties doc = mlt_properties_new(); mlt_properties codecs = mlt_properties_new(); char key[20]; const AVCodec *codec = NULL; void *iterator = NULL; mlt_properties_set_data(properties, "vcodec", codecs, 0, (mlt_destructor) mlt_properties_close, NULL); mlt_properties_set_data(doc, "video_codecs", codecs, 0, NULL, NULL); while ((codec = av_codec_iterate(&iterator))) if (av_codec_is_encoder(codec) && codec->type == AVMEDIA_TYPE_VIDEO) { snprintf(key, sizeof(key), "%d", mlt_properties_count(codecs)); mlt_properties_set(codecs, key, codec->name); } s = mlt_properties_serialise_yaml(doc); fprintf(stdout, "%s", s); free(s); mlt_properties_close(doc); error = 1; } // Check that we're not already running if (!error && !mlt_properties_get_int(properties, "running")) { // Allocate a thread pthread_t *thread = calloc(1, sizeof(pthread_t)); mlt_event_block(mlt_properties_get_data(properties, "property-changed event", NULL)); // Because Movit only reads this on the first frame, // we must do this after properties have been set but before first frame request. if (!mlt_properties_get(properties, "color_trc")) color_trc_from_colorspace(properties); if (!mlt_properties_get(properties, "color_primaries")) color_primaries_from_colorspace(properties); // Assign the thread to properties mlt_properties_set_data(properties, "thread", thread, sizeof(pthread_t), free, NULL); // Create the thread pthread_create(thread, NULL, consumer_thread, consumer); // Set the running state mlt_properties_set_int(properties, "running", 1); } return error; } /** Stop the consumer. */ static int consumer_stop(mlt_consumer consumer) { // Get the properties mlt_properties properties = MLT_CONSUMER_PROPERTIES(consumer); pthread_t *thread = mlt_properties_get_data(properties, "thread", NULL); // Check that we're running if (thread) { // Stop the thread mlt_properties_set_int(properties, "running", 0); // Wait for termination pthread_join(*thread, NULL); mlt_properties_set_data(properties, "thread", NULL, 0, NULL, NULL); mlt_event_unblock(mlt_properties_get_data(properties, "property-changed event", NULL)); } return 0; } /** Determine if the consumer is stopped. */ static int consumer_is_stopped(mlt_consumer consumer) { // Get the properties mlt_properties properties = MLT_CONSUMER_PROPERTIES(consumer); return !mlt_properties_get_int(properties, "running"); } /** Process properties as AVOptions and apply to AV context obj */ static void apply_properties(void *obj, mlt_properties properties, int flags) { int i; int count = mlt_properties_count(properties); for (i = 0; i < count; i++) { const char *opt_name = mlt_properties_get_name(properties, i); int search_flags = AV_OPT_SEARCH_CHILDREN; const AVOption *opt = av_opt_find(obj, opt_name, NULL, flags, search_flags); // If option not found, see if it was prefixed with a or v (-vb) if (!opt && ((opt_name[0] == 'v' && (flags & AV_OPT_FLAG_VIDEO_PARAM)) || (opt_name[0] == 'a' && (flags & AV_OPT_FLAG_AUDIO_PARAM)))) opt = av_opt_find(obj, ++opt_name, NULL, flags, search_flags); // Apply option if found if (opt && strcmp(opt_name, "channel_layout")) av_opt_set(obj, opt_name, mlt_properties_get_value(properties, i), search_flags); } } static enum AVPixelFormat pick_pix_fmt(mlt_image_format img_fmt) { switch (img_fmt) { case mlt_image_rgb: return AV_PIX_FMT_RGB24; case mlt_image_rgba: return AV_PIX_FMT_RGBA; case mlt_image_yuv420p: return AV_PIX_FMT_YUV420P; case mlt_image_yuv422p16: return AV_PIX_FMT_YUV422P16LE; case mlt_image_yuv420p10: return AV_PIX_FMT_YUV420P10LE; case mlt_image_yuv444p10: return AV_PIX_FMT_YUV444P10LE; default: return AV_PIX_FMT_YUYV422; } } static int get_mlt_audio_format(int av_sample_fmt) { switch (av_sample_fmt) { case AV_SAMPLE_FMT_U8: return mlt_audio_u8; case AV_SAMPLE_FMT_S32: return mlt_audio_s32le; case AV_SAMPLE_FMT_FLT: return mlt_audio_f32le; case AV_SAMPLE_FMT_U8P: return mlt_audio_u8; case AV_SAMPLE_FMT_S32P: return mlt_audio_s32le; case AV_SAMPLE_FMT_FLTP: return mlt_audio_f32le; default: return mlt_audio_s16; } } static int pick_sample_fmt(mlt_properties properties, const AVCodec *codec) { int sample_fmt = AV_SAMPLE_FMT_S16; const char *format = mlt_properties_get(properties, "mlt_audio_format"); const int *p = codec->sample_fmts; const char *sample_fmt_str = mlt_properties_get(properties, "sample_fmt"); if (sample_fmt_str) sample_fmt = av_get_sample_fmt(sample_fmt_str); // get default av_sample_fmt from mlt_audio_format if (format && (!sample_fmt_str || sample_fmt == AV_SAMPLE_FMT_NONE)) { if (!strcmp(format, "s32le")) sample_fmt = AV_SAMPLE_FMT_S32; else if (!strcmp(format, "f32le")) sample_fmt = AV_SAMPLE_FMT_FLT; else if (!strcmp(format, "u8")) sample_fmt = AV_SAMPLE_FMT_U8; else if (!strcmp(format, "s32")) sample_fmt = AV_SAMPLE_FMT_S32P; else if (!strcmp(format, "float")) sample_fmt = AV_SAMPLE_FMT_FLTP; } // check if codec supports our mlt_audio_format for (; *p != -1; p++) { if (*p == sample_fmt) return sample_fmt; } // no match - pick first one we support for (p = codec->sample_fmts; *p != -1; p++) { switch (*p) { case AV_SAMPLE_FMT_U8: case AV_SAMPLE_FMT_S16: case AV_SAMPLE_FMT_S32: case AV_SAMPLE_FMT_FLT: case AV_SAMPLE_FMT_U8P: case AV_SAMPLE_FMT_S16P: case AV_SAMPLE_FMT_S32P: case AV_SAMPLE_FMT_FLTP: return *p; default: break; } } mlt_log_error(properties, "audio codec sample_fmt not compatible"); return AV_SAMPLE_FMT_NONE; } static uint8_t *interleaved_to_planar(int samples, int channels, uint8_t *audio, int bytes_per_sample) { uint8_t *buffer = mlt_pool_alloc(AUDIO_ENCODE_BUFFER_SIZE); uint8_t *p = buffer; int c; memset(buffer, 0, AUDIO_ENCODE_BUFFER_SIZE); for (c = 0; c < channels; c++) { uint8_t *q = audio + c * bytes_per_sample; int i = samples + 1; while (--i) { memcpy(p, q, bytes_per_sample); p += bytes_per_sample; q += channels * bytes_per_sample; } } return buffer; } /** Add an audio output stream */ static AVStream *add_audio_stream(mlt_consumer consumer, AVFormatContext *oc, const AVCodec *codec, AVCodecContext **codec_context, int channels, int64_t channel_layout) { // Get the properties mlt_properties properties = MLT_CONSUMER_PROPERTIES(consumer); // Create a new stream AVStream *st = avformat_new_stream(oc, codec); // If created, then initialise from properties if (st != NULL) { AVCodecContext *c = *codec_context = avcodec_alloc_context3(codec); if (!c) { mlt_log_fatal(MLT_CONSUMER_SERVICE(consumer), "Failed to allocate the audio encoder context\n"); return NULL; } c->codec_id = codec->id; c->codec_type = AVMEDIA_TYPE_AUDIO; c->sample_fmt = pick_sample_fmt(properties, codec); c->channel_layout = channel_layout; // disabled until some audio codecs are multi-threaded #if 0 // Setup multi-threading int thread_count = mlt_properties_get_int( properties, "threads" ); if ( thread_count == 0 && getenv( "MLT_AVFORMAT_THREADS" ) ) thread_count = atoi( getenv( "MLT_AVFORMAT_THREADS" ) ); if ( thread_count >= 0 ) c->thread_count = thread_count; #endif if (oc->oformat->flags & AVFMT_GLOBALHEADER) c->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; // Allow the user to override the audio fourcc if (mlt_properties_get(properties, "atag")) { char *tail = NULL; char *arg = mlt_properties_get(properties, "atag"); int tag = strtol(arg, &tail, 0); if (!tail || *tail) tag = arg[0] + (arg[1] << 8) + (arg[2] << 16) + (arg[3] << 24); c->codec_tag = tag; } // Process properties as AVOptions char *apre = mlt_properties_get(properties, "apre"); if (apre) { mlt_properties p = mlt_properties_load(apre); apply_properties(c, p, AV_OPT_FLAG_AUDIO_PARAM | AV_OPT_FLAG_ENCODING_PARAM); mlt_properties_close(p); } apply_properties(c, properties, AV_OPT_FLAG_AUDIO_PARAM | AV_OPT_FLAG_ENCODING_PARAM); int audio_qscale = mlt_properties_get_int(properties, "aq"); if (audio_qscale > QSCALE_NONE) { c->flags |= AV_CODEC_FLAG_QSCALE; c->global_quality = FF_QP2LAMBDA * audio_qscale; } // Set parameters controlled by MLT c->sample_rate = mlt_properties_get_int(properties, "frequency"); st->time_base = (AVRational){1, c->sample_rate}; c->channels = channels; if (mlt_properties_get(properties, "alang") != NULL) av_dict_set(&oc->metadata, "language", mlt_properties_get(properties, "alang"), 0); } else { mlt_log_error(MLT_CONSUMER_SERVICE(consumer), "Could not allocate a stream for audio\n"); } return st; } static int open_audio(mlt_properties properties, AVFormatContext *oc, AVStream *st, const AVCodec *codec, AVCodecContext *c) { // We will return the audio input size from here int audio_input_frame_size = 0; // Process properties as AVOptions on the AVCodec if (codec && codec->priv_class) { char *apre = mlt_properties_get(properties, "apre"); if (apre) { mlt_properties p = mlt_properties_load(apre); apply_properties(c->priv_data, p, AV_OPT_FLAG_AUDIO_PARAM | AV_OPT_FLAG_ENCODING_PARAM); mlt_properties_close(p); } apply_properties(c->priv_data, properties, AV_OPT_FLAG_AUDIO_PARAM | AV_OPT_FLAG_ENCODING_PARAM); } // Continue if codec found and we can open it if (codec && avcodec_open2(c, codec, NULL) >= 0) { #if LIBAVFORMAT_VERSION_INT < ((58 << 16) + (76 << 8) + 100) if (avcodec_copy_context(st->codec, c) < 0) { mlt_log_warning(NULL, "Failed to copy encoder parameters to output audio stream\n"); } #endif if (avcodec_parameters_from_context(st->codecpar, c) < 0) { mlt_log_warning(NULL, "Failed to copy encoder parameters to output audio stream\n"); return 0; } // ugly hack for PCM codecs (will be removed ASAP with new PCM // support to compute the input frame size in samples if (c->frame_size <= 1) audio_input_frame_size = 1; else audio_input_frame_size = c->frame_size; // Some formats want stream headers to be separate (hmm) if (!strcmp(oc->oformat->name, "mp4") || !strcmp(oc->oformat->name, "mov") || !strcmp(oc->oformat->name, "3gp")) c->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; } else { mlt_log_warning(NULL, "%s: Unable to encode audio - disabling audio output.\n", __FILE__); audio_input_frame_size = 0; } return audio_input_frame_size; } /** Add a video output stream */ static AVStream *add_video_stream(mlt_consumer consumer, AVFormatContext *oc, const AVCodec *codec, AVCodecContext **codec_context) { // Get the properties mlt_properties properties = MLT_CONSUMER_PROPERTIES(consumer); // Create a new stream AVStream *st = avformat_new_stream(oc, codec); if (st != NULL) { char *pix_fmt = mlt_properties_get(properties, "pix_fmt"); AVCodecContext *c = *codec_context = avcodec_alloc_context3(codec); if (!c) { mlt_log_fatal(MLT_CONSUMER_SERVICE(consumer), "Failed to allocate the video encoder context\n"); return NULL; } c->codec_id = codec->id; c->codec_type = AVMEDIA_TYPE_VIDEO; // Setup multi-threading int thread_count = mlt_properties_get_int(properties, "threads"); if (thread_count == 0 && getenv("MLT_AVFORMAT_THREADS")) thread_count = atoi(getenv("MLT_AVFORMAT_THREADS")); if (thread_count >= 0) c->thread_count = thread_count; // Process properties as AVOptions char *vpre = mlt_properties_get(properties, "vpre"); if (vpre) { mlt_properties p = mlt_properties_load(vpre); #ifdef AVDATADIR if (mlt_properties_count(p) < 1) { AVCodec *codec = avcodec_find_encoder(c->codec_id); if (codec) { char *path = malloc(strlen(AVDATADIR) + strlen(codec->name) + strlen(vpre) + strlen(".ffpreset") + 2); strcpy(path, AVDATADIR); strcat(path, codec->name); strcat(path, "-"); strcat(path, vpre); strcat(path, ".ffpreset"); mlt_properties_close(p); p = mlt_properties_load(path); if (mlt_properties_count(p) > 0) mlt_properties_debug(p, path, stderr); free(path); } } else { mlt_properties_debug(p, vpre, stderr); } #endif apply_properties(c, p, AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_ENCODING_PARAM); mlt_properties_close(p); } int colorspace = mlt_properties_get_int(properties, "colorspace"); mlt_properties_set(properties, "colorspace", NULL); apply_properties(c, properties, AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_ENCODING_PARAM); mlt_properties_set_int(properties, "colorspace", colorspace); // Set options controlled by MLT c->width = mlt_properties_get_int(properties, "width"); c->height = mlt_properties_get_int(properties, "height"); c->time_base.num = mlt_properties_get_int(properties, "frame_rate_den"); c->time_base.den = mlt_properties_get_int(properties, "frame_rate_num"); st->time_base = c->time_base; st->avg_frame_rate = av_inv_q(c->time_base); c->framerate = av_inv_q(c->time_base); // Default to the codec's first pix_fmt if possible. c->pix_fmt = pix_fmt ? av_get_pix_fmt(pix_fmt) : codec ? (codec->pix_fmts ? codec->pix_fmts[0] : AV_PIX_FMT_YUV422P) : AV_PIX_FMT_YUV420P; #if defined(AVFILTER) if (AV_PIX_FMT_VAAPI == c->pix_fmt) { int result = init_vaapi(properties, c); if (result >= 0) { int result = setup_hwupload_filter(properties, st, c); if (result < 0) mlt_log_error(MLT_CONSUMER_SERVICE(consumer), "Failed to setup hwfilter: %d\n", result); } else { mlt_log_error(MLT_CONSUMER_SERVICE(consumer), "Failed to initialize VA-API: %d\n", result); } } #endif switch (colorspace) { case 170: c->colorspace = AVCOL_SPC_SMPTE170M; break; case 240: c->colorspace = AVCOL_SPC_SMPTE240M; break; case 470: c->colorspace = AVCOL_SPC_BT470BG; break; case 601: c->colorspace = (576 % c->height) ? AVCOL_SPC_SMPTE170M : AVCOL_SPC_BT470BG; break; case 709: c->colorspace = AVCOL_SPC_BT709; break; } if (mlt_properties_get(properties, "aspect")) { // "-aspect" on ffmpeg command line is display aspect ratio double ar = mlt_properties_get_double(properties, "aspect"); c->sample_aspect_ratio = av_d2q(ar * c->height / c->width, 255); } else { c->sample_aspect_ratio.num = mlt_properties_get_int(properties, "sample_aspect_num"); c->sample_aspect_ratio.den = mlt_properties_get_int(properties, "sample_aspect_den"); } st->sample_aspect_ratio = c->sample_aspect_ratio; if (mlt_properties_get_double(properties, "qscale") > 0) { c->flags |= AV_CODEC_FLAG_QSCALE; c->global_quality = FF_QP2LAMBDA * mlt_properties_get_double(properties, "qscale"); } // Allow the user to override the video fourcc if (mlt_properties_get(properties, "vtag")) { char *tail = NULL; const char *arg = mlt_properties_get(properties, "vtag"); int tag = strtol(arg, &tail, 0); if (!tail || *tail) tag = arg[0] + (arg[1] << 8) + (arg[2] << 16) + (arg[3] << 24); c->codec_tag = tag; } // Some formats want stream headers to be separate if (oc->oformat->flags & AVFMT_GLOBALHEADER) c->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; // Translate these standard mlt consumer properties to ffmpeg if (mlt_properties_get_int(properties, "progressive") == 0 && mlt_properties_get_int(properties, "deinterlace") == 0) { if (!mlt_properties_get(properties, "ildct") || mlt_properties_get_int(properties, "ildct")) c->flags |= AV_CODEC_FLAG_INTERLACED_DCT; if (!mlt_properties_get(properties, "ilme") || mlt_properties_get_int(properties, "ilme")) c->flags |= AV_CODEC_FLAG_INTERLACED_ME; } // parse the ratecontrol override string int i; char *rc_override = mlt_properties_get(properties, "rc_override"); for (i = 0; rc_override; i++) { int start, end, q; int e = sscanf(rc_override, "%d,%d,%d", &start, &end, &q); if (e != 3) mlt_log_warning(MLT_CONSUMER_SERVICE(consumer), "Error parsing rc_override\n"); c->rc_override = av_realloc(c->rc_override, sizeof(RcOverride) * (i + 1)); c->rc_override[i].start_frame = start; c->rc_override[i].end_frame = end; if (q > 0) { c->rc_override[i].qscale = q; c->rc_override[i].quality_factor = 1.0; } else { c->rc_override[i].qscale = 0; c->rc_override[i].quality_factor = -q / 100.0; } rc_override = strchr(rc_override, '/'); if (rc_override) rc_override++; } c->rc_override_count = i; if (!c->rc_initial_buffer_occupancy) c->rc_initial_buffer_occupancy = c->rc_buffer_size * 3 / 4; c->intra_dc_precision = mlt_properties_get_int(properties, "dc") - 8; // Setup dual-pass i = mlt_properties_get_int(properties, "pass"); if (i == 1) c->flags |= AV_CODEC_FLAG_PASS1; else if (i == 2) c->flags |= AV_CODEC_FLAG_PASS2; #ifdef AV_CODEC_ID_H265 if (codec->id != AV_CODEC_ID_H265) #endif if (codec->id != AV_CODEC_ID_H264 && (c->flags & (AV_CODEC_FLAG_PASS1 | AV_CODEC_FLAG_PASS2))) { FILE *f; int size; char *logbuffer; char *filename; if (mlt_properties_get(properties, "passlogfile")) { filename = mlt_properties_get(properties, "passlogfile"); } else { char logfilename[1024]; snprintf(logfilename, sizeof(logfilename), "%s_2pass.log", mlt_properties_get(properties, "target")); mlt_properties_set(properties, "_logfilename", logfilename); filename = mlt_properties_get(properties, "_logfilename"); } if (c->flags & AV_CODEC_FLAG_PASS1) { f = mlt_fopen(filename, "w"); if (!f) perror(filename); else mlt_properties_set_data(properties, "_logfile", f, 0, (mlt_destructor) fclose, NULL); } else { /* read the log file */ f = mlt_fopen(filename, "r"); if (!f) { perror(filename); } else { fseek(f, 0, SEEK_END); size = ftell(f); fseek(f, 0, SEEK_SET); logbuffer = av_malloc(size + 1); if (!logbuffer) mlt_log_fatal(MLT_CONSUMER_SERVICE(consumer), "Could not allocate log buffer\n"); else { if (size >= 0) { size = fread(logbuffer, 1, size, f); logbuffer[size] = '\0'; c->stats_in = logbuffer; } } fclose(f); } } } } else { mlt_log_error(MLT_CONSUMER_SERVICE(consumer), "Could not allocate a stream for video\n"); } return st; } static AVFrame *alloc_picture(int pix_fmt, int width, int height) { // Allocate a frame AVFrame *picture = av_frame_alloc(); if (picture) { int size = av_image_alloc(picture->data, picture->linesize, width, height, pix_fmt, IMAGE_ALIGN); if (size > 0) { picture->format = pix_fmt; picture->width = width; picture->height = height; } else { av_free(picture); picture = NULL; } } else { // Something failed - clean up what we can av_free(picture); picture = NULL; } return picture; } static int open_video(mlt_properties properties, AVFormatContext *oc, AVStream *st, const AVCodec *codec, AVCodecContext *video_enc) { // Process properties as AVOptions on the AVCodec if (codec && codec->priv_class) { char *vpre = mlt_properties_get(properties, "vpre"); if (vpre) { mlt_properties p = mlt_properties_load(vpre); apply_properties(video_enc->priv_data, p, AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_ENCODING_PARAM); mlt_properties_close(p); } apply_properties(video_enc->priv_data, properties, AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_ENCODING_PARAM); } if (codec && codec->pix_fmts) { const enum AVPixelFormat *p = codec->pix_fmts; for (; *p != -1; p++) { if (*p == video_enc->pix_fmt) break; } if (*p == -1) video_enc->pix_fmt = codec->pix_fmts[0]; } const AVPixFmtDescriptor *srcDesc = av_pix_fmt_desc_get(video_enc->pix_fmt); if (srcDesc->flags & AV_PIX_FMT_FLAG_RGB) { video_enc->colorspace = AVCOL_SPC_RGB; } int result = codec && avcodec_open2(video_enc, codec, NULL) >= 0; if (result >= 0) { #if LIBAVFORMAT_VERSION_INT < ((58 << 16) + (76 << 8) + 100) result = avcodec_copy_context(st->codec, video_enc); if (!result) { mlt_log_warning(NULL, "Failed to copy encoder parameters to output video stream\n"); } #endif result = avcodec_parameters_from_context(st->codecpar, video_enc) >= 0; if (!result) { mlt_log_warning(NULL, "Failed to copy encoder parameters to output video stream\n"); } } else { mlt_log_warning(NULL, "%s: Unable to encode video - disabling video output.\n", __FILE__); } return result; } static inline long time_difference(struct timeval *time1) { struct timeval time2; gettimeofday(&time2, NULL); return time2.tv_sec * 1000000 + time2.tv_usec - time1->tv_sec * 1000000 - time1->tv_usec; } typedef struct { uint8_t *data; size_t size; } buffer_t; static int mlt_write(void *h, uint8_t *buf, int size) { mlt_properties properties = (mlt_properties) h; buffer_t buffer = {buf, size}; mlt_events_fire(properties, "avformat-write", mlt_event_data_from_object(&buffer)); return 0; } typedef struct encode_ctx_desc { mlt_consumer consumer; int audio_outbuf_size; int audio_input_frame_size; uint8_t audio_outbuf[AUDIO_BUFFER_SIZE], audio_buf_1[AUDIO_ENCODE_BUFFER_SIZE], audio_buf_2[AUDIO_ENCODE_BUFFER_SIZE]; int channels; int total_channels; int frequency; int sample_bytes; sample_fifo fifo; AVFormatContext *oc; AVStream *video_st; AVCodecContext *vcodec_ctx; AVStream *audio_st[MAX_AUDIO_STREAMS]; AVCodecContext *acodec_ctx[MAX_AUDIO_STREAMS]; int64_t sample_count[MAX_AUDIO_STREAMS]; // Used to store and override codec ids int video_codec_id; int audio_codec_id; int error_count; int frame_count; double audio_pts; double video_pts; int terminate_on_pause; int terminated; mlt_properties properties; mlt_properties frame_meta_properties; AVFrame *audio_avframe; } encode_ctx_t; static int encode_audio(encode_ctx_t *ctx) { char key[27]; int i, j = 0, samples = ctx->audio_input_frame_size; int frame_length = ctx->audio_input_frame_size * ctx->channels * ctx->sample_bytes; // Get samples count to fetch from fifo if (sample_fifo_used(ctx->fifo) < frame_length) { samples = sample_fifo_used(ctx->fifo) / (ctx->channels * ctx->sample_bytes); } else if (ctx->audio_input_frame_size == 1) { // PCM consumes as much as possible. samples = FFMIN(sample_fifo_used(ctx->fifo), AUDIO_ENCODE_BUFFER_SIZE) / frame_length; } // Get the audio samples if (samples > 0) { sample_fifo_fetch(ctx->fifo, ctx->audio_buf_1, samples * ctx->sample_bytes * ctx->channels); } else if (ctx->audio_codec_id == AV_CODEC_ID_VORBIS && ctx->terminated) { // This prevents an infinite loop when some versions of vorbis do not // increment pts when encoding silence. ctx->audio_pts = ctx->video_pts; return 1; } else { memset(ctx->audio_buf_1, 0, AUDIO_ENCODE_BUFFER_SIZE); } // For each output stream for (i = 0; i < MAX_AUDIO_STREAMS && ctx->audio_st[i] && j < ctx->total_channels; i++) { AVStream *stream = ctx->audio_st[i]; AVCodecContext *codec = ctx->acodec_ctx[i]; AVPacket pkt; av_init_packet(&pkt); pkt.data = ctx->audio_outbuf; pkt.size = ctx->audio_outbuf_size; // Optimized for single track and no channel remap if (!ctx->audio_st[1] && !mlt_properties_count(ctx->frame_meta_properties)) { void *p = ctx->audio_buf_1; if (codec->sample_fmt == AV_SAMPLE_FMT_FLTP) p = interleaved_to_planar(samples, ctx->channels, p, sizeof(float)); else if (codec->sample_fmt == AV_SAMPLE_FMT_S16P) p = interleaved_to_planar(samples, ctx->channels, p, sizeof(int16_t)); else if (codec->sample_fmt == AV_SAMPLE_FMT_S32P) p = interleaved_to_planar(samples, ctx->channels, p, sizeof(int32_t)); else if (codec->sample_fmt == AV_SAMPLE_FMT_U8P) p = interleaved_to_planar(samples, ctx->channels, p, sizeof(uint8_t)); ctx->audio_avframe->nb_samples = FFMAX(samples, ctx->audio_input_frame_size); ctx->audio_avframe->pts = ctx->sample_count[i]; ctx->sample_count[i] += ctx->audio_avframe->nb_samples; avcodec_fill_audio_frame(ctx->audio_avframe, codec->channels, codec->sample_fmt, (const uint8_t *) p, AUDIO_ENCODE_BUFFER_SIZE, 0); int ret = avcodec_send_frame(codec, samples ? ctx->audio_avframe : NULL); if (ret < 0) { pkt.size = ret; } else { ret = avcodec_receive_packet(codec, &pkt); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) pkt.size = 0; else if (ret < 0) pkt.size = ret; } if (p != ctx->audio_buf_1) mlt_pool_release(p); } else { // Extract the audio channels according to channel mapping int dest_offset = 0; // channel offset into interleaved dest buffer // Get the number of channels for this stream sprintf(key, "channels.%d", i); int current_channels = mlt_properties_get_int(ctx->properties, key); // Clear the destination audio buffer. memset(ctx->audio_buf_2, 0, AUDIO_ENCODE_BUFFER_SIZE); // For each output channel while (dest_offset < current_channels && j < ctx->total_channels) { int map_start = -1, map_channels = 0; int source_offset = 0; int k; // Look for a mapping that starts at j for (k = 0; k < (MAX_AUDIO_STREAMS * 2) && map_start != j; k++) { sprintf(key, "%d.channels", k); map_channels = mlt_properties_get_int(ctx->frame_meta_properties, key); sprintf(key, "%d.start", k); if (mlt_properties_get(ctx->frame_meta_properties, key)) map_start = mlt_properties_get_int(ctx->frame_meta_properties, key); if (map_start != j) source_offset += map_channels; } // If no mapping if (map_start != j) { map_channels = current_channels; source_offset = j; } // Copy samples if source offset valid if (source_offset < ctx->channels) { // Interleave the audio buffer with the # channels for this stream/mapping. for (k = 0; k < map_channels; k++, j++, source_offset++, dest_offset++) { void *src = ctx->audio_buf_1 + source_offset * ctx->sample_bytes; void *dest = ctx->audio_buf_2 + dest_offset * ctx->sample_bytes; int s = samples + 1; while (--s) { memcpy(dest, src, ctx->sample_bytes); dest += current_channels * ctx->sample_bytes; src += ctx->channels * ctx->sample_bytes; } } } // Otherwise silence else { j += current_channels; dest_offset += current_channels; } } ctx->audio_avframe->nb_samples = FFMAX(samples, ctx->audio_input_frame_size); ctx->audio_avframe->pts = ctx->sample_count[i]; ctx->sample_count[i] += ctx->audio_avframe->nb_samples; avcodec_fill_audio_frame(ctx->audio_avframe, codec->channels, codec->sample_fmt, (const uint8_t *) ctx->audio_buf_2, AUDIO_ENCODE_BUFFER_SIZE, 0); int ret = avcodec_send_frame(codec, samples ? ctx->audio_avframe : NULL); if (ret < 0) { pkt.size = ret; } else { receive_audio_packet: ret = avcodec_receive_packet(codec, &pkt); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) pkt.size = 0; else if (ret < 0) pkt.size = ret; } } if (pkt.size > 0) { // Write the compressed frame in the media file av_packet_rescale_ts(&pkt, codec->time_base, stream->time_base); pkt.stream_index = stream->index; if (av_interleaved_write_frame(ctx->oc, &pkt)) { mlt_log_fatal(MLT_CONSUMER_SERVICE(ctx->consumer), "error writing audio frame\n"); mlt_events_fire(ctx->properties, "consumer-fatal-error", mlt_event_data_none()); return -1; } ctx->error_count = 0; mlt_log_debug(MLT_CONSUMER_SERVICE(ctx->consumer), "audio stream %d pkt pts %" PRId64 " frame_size %d\n", stream->index, pkt.pts, codec->frame_size); goto receive_audio_packet; } else if (pkt.size < 0) { mlt_log_warning(MLT_CONSUMER_SERVICE(ctx->consumer), "error with audio encode: %d (frame %d)\n", pkt.size, ctx->frame_count); if (++ctx->error_count > 2) return -1; } else if (!samples) // flushing { pkt.stream_index = stream->index; av_packet_rescale_ts(&pkt, codec->time_base, stream->time_base); av_interleaved_write_frame(ctx->oc, &pkt); } if (i == 0) { ctx->audio_pts = (double) ctx->sample_count[0] * av_q2d(codec->time_base); } } return 0; } /** The main thread - the argument is simply the consumer. */ static void *consumer_thread(void *arg) { int i; // Encoding content encode_ctx_t *enc_ctx = mlt_pool_alloc(sizeof(encode_ctx_t)); memset(enc_ctx, 0, sizeof(encode_ctx_t)); // Map the argument to the object mlt_consumer consumer = enc_ctx->consumer = arg; // Get the properties mlt_properties properties = enc_ctx->properties = MLT_CONSUMER_PROPERTIES(consumer); // Get the terminate on pause property enc_ctx->terminate_on_pause = mlt_properties_get_int(enc_ctx->properties, "terminate_on_pause"); // Determine if feed is slow (for realtime stuff) int real_time_output = mlt_properties_get_int(properties, "real_time"); // Time structures struct timeval ante; // Get the frame rate double fps = mlt_properties_get_double(properties, "fps"); // Get width and height int width = mlt_properties_get_int(properties, "width"); int height = mlt_properties_get_int(properties, "height"); int img_width = width; int img_height = height; // Get default audio properties enc_ctx->total_channels = enc_ctx->channels = mlt_properties_get_int(properties, "channels"); enc_ctx->frequency = mlt_properties_get_int(properties, "frequency"); void *pcm = NULL; int samples = 0; // AVFormat audio buffer and frame size enc_ctx->audio_outbuf_size = AUDIO_BUFFER_SIZE; // AVFormat video buffer and frame count int video_outbuf_size = VIDEO_BUFFER_SIZE; uint8_t *video_outbuf = av_malloc(video_outbuf_size); // Used for the frame properties mlt_frame frame = NULL; mlt_properties frame_properties = NULL; // Get the queues mlt_deque queue = mlt_properties_get_data(properties, "frame_queue", NULL); enc_ctx->fifo = mlt_properties_get_data(properties, "sample_fifo", NULL); // For receiving images from an mlt_frame uint8_t *image; mlt_image_format img_fmt = mlt_image_yuv422; // Need two av pictures for converting AVFrame *converted_avframe = NULL; AVFrame *avframe = NULL; // For receiving audio samples back from the fifo int count = 0; // Frames dispatched long int frames = 0; long int total_time = 0; // Determine the format const AVOutputFormat *fmt = NULL; const char *filename = mlt_properties_get(properties, "target"); char *format = mlt_properties_get(properties, "f"); char *vcodec = mlt_properties_get(properties, "vcodec"); char *acodec = mlt_properties_get(properties, "acodec"); const AVCodec *audio_codec = NULL; const AVCodec *video_codec = NULL; // Misc char key[27]; enc_ctx->frame_meta_properties = mlt_properties_new(); int header_written = 0; int dst_colorspace = mlt_properties_get_int(properties, "colorspace"); const char *color_range = mlt_properties_get(properties, "color_range"); int dst_full_range = color_range && (!strcmp("pc", color_range) || !strcmp("jpeg", color_range)); // Check for user selected format first if (format != NULL) fmt = av_guess_format(format, NULL, NULL); // Otherwise check on the filename if (fmt == NULL && filename != NULL) fmt = av_guess_format(NULL, filename, NULL); // Otherwise default to mpeg if (fmt == NULL) fmt = av_guess_format("mpeg", NULL, NULL); // We need a filename - default to stdout? if (filename == NULL || !strcmp(filename, "")) filename = "pipe:"; avformat_alloc_output_context2(&enc_ctx->oc, fmt, format, filename); // Get the codec ids selected enc_ctx->audio_codec_id = fmt->audio_codec; enc_ctx->video_codec_id = fmt->video_codec; // Check for audio codec overrides if ((acodec && strcmp(acodec, "none") == 0) || mlt_properties_get_int(properties, "an")) enc_ctx->audio_codec_id = AV_CODEC_ID_NONE; else if (acodec) { audio_codec = avcodec_find_encoder_by_name(acodec); if (audio_codec) { enc_ctx->audio_codec_id = audio_codec->id; if (enc_ctx->audio_codec_id == AV_CODEC_ID_AC3 && avcodec_find_encoder_by_name("ac3_fixed")) { mlt_properties_set(enc_ctx->properties, "_acodec", "ac3_fixed"); acodec = mlt_properties_get(enc_ctx->properties, "_acodec"); audio_codec = avcodec_find_encoder_by_name(acodec); } else if (!strcmp(acodec, "aac") || !strcmp(acodec, "vorbis")) { mlt_properties_set(enc_ctx->properties, "astrict", "experimental"); } } else { enc_ctx->audio_codec_id = AV_CODEC_ID_NONE; mlt_log_warning(MLT_CONSUMER_SERVICE(consumer), "audio codec %s unrecognised - ignoring\n", acodec); } } else { audio_codec = avcodec_find_encoder(enc_ctx->audio_codec_id); } // Check for video codec overrides if ((vcodec && strcmp(vcodec, "none") == 0) || mlt_properties_get_int(properties, "vn")) enc_ctx->video_codec_id = AV_CODEC_ID_NONE; else if (vcodec) { video_codec = avcodec_find_encoder_by_name(vcodec); if (video_codec) { enc_ctx->video_codec_id = video_codec->id; } else { enc_ctx->video_codec_id = AV_CODEC_ID_NONE; mlt_log_warning(MLT_CONSUMER_SERVICE(consumer), "video codec %s unrecognised - ignoring\n", vcodec); } } else { video_codec = avcodec_find_encoder(enc_ctx->video_codec_id); } // Write metadata for (i = 0; i < mlt_properties_count(properties); i++) { char *name = mlt_properties_get_name(properties, i); if (name && !strncmp(name, "meta.attr.", 10)) { char *key = strdup(name + 10); char *markup = strrchr(key, '.'); if (markup && !strcmp(markup, ".markup")) { markup[0] = '\0'; if (!strstr(key, ".stream.")) av_dict_set(&enc_ctx->oc->metadata, key, mlt_properties_get_value(properties, i), 0); } free(key); } } // Add audio and video streams if (enc_ctx->video_codec_id != AV_CODEC_ID_NONE) { if ((enc_ctx->video_st = add_video_stream(consumer, enc_ctx->oc, video_codec, &enc_ctx->vcodec_ctx))) { const char *img_fmt_name = mlt_properties_get(properties, "mlt_image_format"); if (img_fmt_name) { // Set the mlt_image_format from explicit property. mlt_image_format f = mlt_image_format_id(img_fmt_name); if (mlt_image_invalid != f) img_fmt = f; } else { // Set the mlt_image_format from the selected pix_fmt. const char *pix_fmt_name = av_get_pix_fmt_name(enc_ctx->vcodec_ctx->pix_fmt); if (!strcmp(pix_fmt_name, "rgba") || !strcmp(pix_fmt_name, "argb") || !strcmp(pix_fmt_name, "bgra")) { mlt_properties_set(properties, "mlt_image_format", "rgba"); img_fmt = mlt_image_rgba; } else if (strstr(pix_fmt_name, "rgb") || strstr(pix_fmt_name, "bgr")) { mlt_properties_set(properties, "mlt_image_format", "rgb"); img_fmt = mlt_image_rgb; } } } } if (enc_ctx->audio_codec_id != AV_CODEC_ID_NONE) { int is_multi = 0; enc_ctx->total_channels = 0; // multitrack audio for (i = 0; i < MAX_AUDIO_STREAMS; i++) { sprintf(key, "channels.%d", i); int j = mlt_properties_get_int(properties, key); if (j) { is_multi = 1; enc_ctx->total_channels += j; enc_ctx->audio_st[i] = add_audio_stream(consumer, enc_ctx->oc, audio_codec, &enc_ctx->acodec_ctx[i], j, av_get_default_channel_layout(j)); } } // single track if (!is_multi) { mlt_channel_layout layout = mlt_audio_channel_layout_id( mlt_properties_get(properties, "channel_layout")); if (layout == mlt_channel_auto || layout == mlt_channel_independent || mlt_audio_channel_layout_channels(layout) != enc_ctx->channels) { layout = mlt_audio_channel_layout_default(enc_ctx->channels); } enc_ctx->audio_st[0] = add_audio_stream(consumer, enc_ctx->oc, audio_codec, &enc_ctx->acodec_ctx[0], enc_ctx->channels, mlt_to_av_channel_layout(layout)); enc_ctx->total_channels = enc_ctx->channels; } } mlt_properties_set_int(properties, "channels", enc_ctx->total_channels); // Audio format is determined when adding the audio stream mlt_audio_format aud_fmt = mlt_audio_none; if (enc_ctx->audio_st[0]) aud_fmt = get_mlt_audio_format(enc_ctx->acodec_ctx[0]->sample_fmt); enc_ctx->sample_bytes = mlt_audio_format_size(aud_fmt, 1, 1); enc_ctx->sample_bytes = enc_ctx->sample_bytes ? enc_ctx->sample_bytes : 1; // prevent divide by zero // Set the parameters (even though we have none...) { if (mlt_properties_get(properties, "muxpreload") && !mlt_properties_get(properties, "preload")) mlt_properties_set_double(properties, "preload", mlt_properties_get_double(properties, "muxpreload")); enc_ctx->oc->max_delay = (int) (mlt_properties_get_double(properties, "muxdelay") * AV_TIME_BASE); // Process properties as AVOptions char *fpre = mlt_properties_get(properties, "fpre"); if (fpre) { mlt_properties p = mlt_properties_load(fpre); apply_properties(enc_ctx->oc, p, AV_OPT_FLAG_ENCODING_PARAM); if (enc_ctx->oc->oformat && enc_ctx->oc->oformat->priv_class && enc_ctx->oc->priv_data) apply_properties(enc_ctx->oc->priv_data, p, AV_OPT_FLAG_ENCODING_PARAM); mlt_properties_close(p); } apply_properties(enc_ctx->oc, properties, AV_OPT_FLAG_ENCODING_PARAM); if (enc_ctx->oc->oformat && enc_ctx->oc->oformat->priv_class && enc_ctx->oc->priv_data) apply_properties(enc_ctx->oc->priv_data, properties, AV_OPT_FLAG_ENCODING_PARAM); if (enc_ctx->video_st && !open_video(properties, enc_ctx->oc, enc_ctx->video_st, video_codec, enc_ctx->vcodec_ctx)) enc_ctx->video_st = NULL; for (i = 0; i < MAX_AUDIO_STREAMS && enc_ctx->audio_st[i]; i++) { enc_ctx->audio_input_frame_size = open_audio(properties, enc_ctx->oc, enc_ctx->audio_st[i], audio_codec, enc_ctx->acodec_ctx[i]); if (!enc_ctx->audio_input_frame_size) { // Remove the audio stream from the output context unsigned int j; for (j = 0; j < enc_ctx->oc->nb_streams; j++) { if (enc_ctx->oc->streams[j] == enc_ctx->audio_st[i]) av_freep(&enc_ctx->oc->streams[j]); } --enc_ctx->oc->nb_streams; enc_ctx->audio_st[i] = NULL; } } // Setup custom I/O if redirecting if (mlt_properties_get_int(properties, "redirect")) { int buffer_size = 32768; unsigned char *buffer = av_malloc(buffer_size); AVIOContext *io = avio_alloc_context(buffer, buffer_size, 1, properties, NULL, mlt_write, NULL); if (buffer && io) { enc_ctx->oc->pb = io; enc_ctx->oc->flags |= AVFMT_FLAG_CUSTOM_IO; mlt_properties_set_data(properties, "avio_buffer", buffer, buffer_size, av_free, NULL); mlt_properties_set_data(properties, "avio_context", io, 0, av_free, NULL); mlt_events_register(properties, "avformat-write"); } else { av_free(buffer); mlt_log_error(MLT_CONSUMER_SERVICE(consumer), "failed to setup output redirection\n"); } } // Open the output file, if needed else if (!(fmt->flags & AVFMT_NOFILE)) { if (avio_open(&enc_ctx->oc->pb, filename, AVIO_FLAG_WRITE) < 0) { mlt_log_error(MLT_CONSUMER_SERVICE(consumer), "Could not open '%s'\n", filename); mlt_events_fire(properties, "consumer-fatal-error", mlt_event_data_none()); goto on_fatal_error; } } } // Last check - need at least one stream if (!enc_ctx->audio_st[0] && !enc_ctx->video_st) { mlt_events_fire(properties, "consumer-fatal-error", mlt_event_data_none()); goto on_fatal_error; } // Allocate picture enum AVPixelFormat pix_fmt = AV_PIX_FMT_YUV420P; if (enc_ctx->video_st) { #if defined(AVFILTER) pix_fmt = enc_ctx->vcodec_ctx->pix_fmt == AV_PIX_FMT_VAAPI ? AV_PIX_FMT_NV12 : enc_ctx->vcodec_ctx->pix_fmt; #else pix_fmt = enc_ctx->vcodec_ctx->pix_fmt; #endif converted_avframe = alloc_picture(pix_fmt, width, height); if (!converted_avframe) { mlt_log_error(MLT_CONSUMER_SERVICE(consumer), "failed to allocate video AVFrame\n"); mlt_events_fire(properties, "consumer-fatal-error", mlt_event_data_none()); goto on_fatal_error; } } // Allocate audio AVFrame if (enc_ctx->audio_st[0]) { enc_ctx->audio_avframe = av_frame_alloc(); if (enc_ctx->audio_avframe) { AVCodecContext *c = enc_ctx->acodec_ctx[0]; enc_ctx->audio_avframe->format = c->sample_fmt; enc_ctx->audio_avframe->nb_samples = enc_ctx->audio_input_frame_size; enc_ctx->audio_avframe->channel_layout = c->channel_layout; enc_ctx->audio_avframe->channels = c->channels; } else { mlt_log_error(MLT_CONSUMER_SERVICE(consumer), "failed to allocate audio AVFrame\n"); mlt_events_fire(properties, "consumer-fatal-error", mlt_event_data_none()); goto on_fatal_error; } } // Get the starting time (can ignore the times above) gettimeofday(&ante, NULL); // Loop while running while (mlt_properties_get_int(properties, "running") && (!enc_ctx->terminated || (enc_ctx->video_st && mlt_deque_count(queue)))) { if (!frame) frame = mlt_consumer_rt_frame(consumer); // Check that we have a frame to work with if (frame != NULL) { // Default audio args frame_properties = MLT_FRAME_PROPERTIES(frame); // Write the stream header. if (!header_written) { // set timecode from first frame if not been set from metadata if (!mlt_properties_get(properties, "timecode")) { char *vitc = mlt_properties_get(frame_properties, "meta.attr.vitc.markup"); if (vitc && vitc[0]) { mlt_log_debug(MLT_CONSUMER_SERVICE(consumer), "timecode=[%s]\n", vitc); av_dict_set(&enc_ctx->oc->metadata, "timecode", vitc, 0); if (enc_ctx->video_st) av_dict_set(&enc_ctx->video_st->metadata, "timecode", vitc, 0); }; }; if (avformat_write_header(enc_ctx->oc, NULL) < 0) { mlt_log_error(MLT_CONSUMER_SERVICE(consumer), "Could not write header '%s'\n", filename); mlt_events_fire(properties, "consumer-fatal-error", mlt_event_data_none()); goto on_fatal_error; } header_written = 1; } // Increment frames dispatched frames++; // Check for the terminated condition enc_ctx->terminated = enc_ctx->terminate_on_pause && mlt_properties_get_double(frame_properties, "_speed") == 0.0; // Get audio and append to the fifo if (!enc_ctx->terminated && enc_ctx->audio_st[0]) { samples = mlt_audio_calculate_frame_samples(fps, enc_ctx->frequency, count++); enc_ctx->channels = enc_ctx->total_channels; mlt_frame_get_audio(frame, &pcm, &aud_fmt, &enc_ctx->frequency, &enc_ctx->channels, &samples); // Save the audio channel remap properties for later mlt_properties_pass(enc_ctx->frame_meta_properties, frame_properties, "meta.map.audio."); // Create the fifo if we don't have one if (enc_ctx->fifo == NULL) { enc_ctx->fifo = sample_fifo_init(enc_ctx->frequency, enc_ctx->channels); mlt_properties_set_data(properties, "sample_fifo", enc_ctx->fifo, 0, (mlt_destructor) sample_fifo_close, NULL); } if (pcm) { // Silence if not normal forward speed if (mlt_properties_get_double(frame_properties, "_speed") != 1.0) memset(pcm, 0, samples * enc_ctx->channels * enc_ctx->sample_bytes); // Append the samples sample_fifo_append(enc_ctx->fifo, pcm, samples * enc_ctx->channels * enc_ctx->sample_bytes); total_time += (samples * 1000000) / enc_ctx->frequency; } if (!enc_ctx->video_st) { mlt_events_fire(properties, "consumer-frame-show", mlt_event_data_from_frame(frame)); } } // Encode the image if (!enc_ctx->terminated && enc_ctx->video_st) mlt_deque_push_back(queue, frame); else mlt_frame_close(frame); frame = NULL; } // While we have stuff to process, process... while (1) { // Write interleaved audio and video frames if (!enc_ctx->video_st || (enc_ctx->video_st && enc_ctx->audio_st[0] && enc_ctx->audio_pts < enc_ctx->video_pts)) { // Write audio int fifo_frames = sample_fifo_used(enc_ctx->fifo) / (enc_ctx->audio_input_frame_size * enc_ctx->channels * enc_ctx->sample_bytes); if ((enc_ctx->video_st && enc_ctx->terminated) || fifo_frames) { int r = encode_audio(enc_ctx); if (r > 0) break; else if (r < 0) goto on_fatal_error; } else { break; } } else if (enc_ctx->video_st) { // Write video if (mlt_deque_count(queue)) { int ret = 0; AVCodecContext *c = enc_ctx->vcodec_ctx; frame = mlt_deque_pop_front(queue); frame_properties = MLT_FRAME_PROPERTIES(frame); if (mlt_properties_get_int(frame_properties, "rendered")) { AVFrame video_avframe; int is_interlaced_chroma_correction = 0; mlt_frame_get_image(frame, &image, &img_fmt, &img_width, &img_height, 0); // Interlaced 420 correction if (!mlt_properties_get_int(frame_properties, "progressive") && pix_fmt == AV_PIX_FMT_YUV420P // dst && img_fmt == mlt_image_yuv422 // src. It looks like rgb and 444 go as 422 too. && height % 4 == 0 // because reducing twice && width == converted_avframe->linesize[1] * 2) // if != things become too complicated { width *= 2; // substitute resolution, to appear each half-frame side-by-side height /= 2; for (int i = 0; i < 3; ++i) converted_avframe->linesize[i] *= 2; is_interlaced_chroma_correction = 1; mlt_log_debug(MLT_CONSUMER_SERVICE(consumer), "interlaced chroma correction is activated\n"); } mlt_image_format_planes(img_fmt, width, height, image, video_avframe.data, video_avframe.linesize); // Do the colour space conversion int srcfmt = pick_pix_fmt(img_fmt); int flags = mlt_get_sws_flags(width, height, srcfmt, width, height, pix_fmt); struct SwsContext *context = sws_getContext( width, height, srcfmt, width, height, pix_fmt, flags, NULL, NULL, NULL); int src_colorspace = mlt_properties_get_int(frame_properties, "colorspace"); int src_full_range = mlt_properties_get_int(frame_properties, "full_range"); mlt_set_luma_transfer(context, src_colorspace, dst_colorspace, src_full_range, dst_full_range); sws_scale(context, (const uint8_t *const *) video_avframe.data, video_avframe.linesize, 0, height, converted_avframe->data, converted_avframe->linesize); sws_freeContext(context); if (is_interlaced_chroma_correction) // restoring everything back { width /= 2; height *= 2; for (int i = 0; i < 3; ++i) converted_avframe->linesize[i] /= 2; } mlt_events_fire(properties, "consumer-frame-show", mlt_event_data_from_frame(frame)); // Apply the alpha if applicable if (!mlt_properties_get(properties, "mlt_image_format") || strcmp(mlt_properties_get(properties, "mlt_image_format"), "rgba")) if (c->pix_fmt == AV_PIX_FMT_RGBA || c->pix_fmt == AV_PIX_FMT_ARGB || c->pix_fmt == AV_PIX_FMT_BGRA) { uint8_t *p; uint8_t *alpha = mlt_frame_get_alpha(frame); if (alpha) { register int n; for (i = 0; i < height; i++) { n = (width + 7) / 8; p = converted_avframe->data[0] + i * converted_avframe->linesize[0] + 3; switch (width % 8) { case 0: do { *p = *alpha++; p += 4; case 7: *p = *alpha++; p += 4; case 6: *p = *alpha++; p += 4; case 5: *p = *alpha++; p += 4; case 4: *p = *alpha++; p += 4; case 3: *p = *alpha++; p += 4; case 2: *p = *alpha++; p += 4; case 1: *p = *alpha++; p += 4; } while (--n); } } } else { for (i = 0; i < height; i++) { int n = width; uint8_t *p = converted_avframe->data[0] + i * converted_avframe->linesize[0] + 3; while (n) { *p = 255; p += 4; n--; } } } } #if defined(AVFILTER) if (AV_PIX_FMT_VAAPI == c->pix_fmt) { AVFilterContext *vfilter_in = mlt_properties_get_data(properties, "vfilter_in", NULL); AVFilterContext *vfilter_out = mlt_properties_get_data(properties, "vfilter_out", NULL); if (vfilter_in && vfilter_out) { if (!avframe) avframe = av_frame_alloc(); ret = av_buffersrc_add_frame(vfilter_in, converted_avframe); ret = av_buffersink_get_frame(vfilter_out, avframe); if (ret < 0) { mlt_log_warning(MLT_CONSUMER_SERVICE(consumer), "error with hwupload: %d (frame %d)\n", ret, enc_ctx->frame_count); if (++enc_ctx->error_count > 2) goto on_fatal_error; ret = 0; } } } else { avframe = converted_avframe; } #else avframe = converted_avframe; #endif } #ifdef AVFMT_RAWPICTURE if (enc_ctx->oc->oformat->flags & AVFMT_RAWPICTURE) { // raw video case. The API will change slightly in the near future for that AVPacket pkt; av_init_packet(&pkt); // Set frame interlace hints if (mlt_properties_get_int(frame_properties, "progressive")) c->field_order = AV_FIELD_PROGRESSIVE; else c->field_order = (mlt_properties_get_int(frame_properties, "top_field_first")) ? AV_FIELD_TB : AV_FIELD_BT; pkt.flags |= AV_PKT_FLAG_KEY; pkt.stream_index = enc_ctx->video_st->index; pkt.data = (uint8_t *) avframe; pkt.size = sizeof(AVPicture); ret = av_write_frame(enc_ctx->oc, &pkt); } else #endif { AVPacket pkt; av_init_packet(&pkt); if (c->codec->id == AV_CODEC_ID_RAWVIDEO) { pkt.data = NULL; pkt.size = 0; } else { pkt.data = video_outbuf; pkt.size = video_outbuf_size; } // Set the quality avframe->quality = c->global_quality; avframe->pts = enc_ctx->frame_count; // Set frame interlace hints avframe->interlaced_frame = !mlt_properties_get_int(frame_properties, "progressive"); avframe->top_field_first = mlt_properties_get_int(frame_properties, "top_field_first"); if (mlt_properties_get_int(frame_properties, "progressive")) c->field_order = AV_FIELD_PROGRESSIVE; else if (c->codec_id == AV_CODEC_ID_MJPEG) c->field_order = (mlt_properties_get_int(frame_properties, "top_field_first")) ? AV_FIELD_TT : AV_FIELD_BB; else c->field_order = (mlt_properties_get_int(frame_properties, "top_field_first")) ? AV_FIELD_TB : AV_FIELD_BT; // Encode the image ret = avcodec_send_frame(c, avframe); if (ret < 0) { pkt.size = ret; } else { receive_video_packet: ret = avcodec_receive_packet(c, &pkt); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) pkt.size = ret = 0; else if (ret < 0) pkt.size = ret; } // If zero size, it means the image was buffered if (pkt.size > 0) { av_packet_rescale_ts(&pkt, c->time_base, enc_ctx->video_st->time_base); pkt.stream_index = enc_ctx->video_st->index; // write the compressed frame in the media file ret = av_interleaved_write_frame(enc_ctx->oc, &pkt); mlt_log_debug(MLT_CONSUMER_SERVICE(consumer), " frame_size %d\n", c->frame_size); // Dual pass logging if (mlt_properties_get_data(properties, "_logfile", NULL) && c->stats_out) fprintf(mlt_properties_get_data(properties, "_logfile", NULL), "%s", c->stats_out); enc_ctx->error_count = 0; if (!ret) goto receive_video_packet; } else if (pkt.size < 0) { mlt_log_warning(MLT_CONSUMER_SERVICE(consumer), "error with video encode: %d (frame %d)\n", pkt.size, enc_ctx->frame_count); if (++enc_ctx->error_count > 2) goto on_fatal_error; ret = 0; } } enc_ctx->frame_count++; enc_ctx->video_pts = (double) enc_ctx->frame_count * av_q2d(enc_ctx->vcodec_ctx->time_base); if (ret) { mlt_log_fatal(MLT_CONSUMER_SERVICE(consumer), "error writing video frame: %d\n", ret); mlt_events_fire(properties, "consumer-fatal-error", mlt_event_data_none()); goto on_fatal_error; } mlt_frame_close(frame); frame = NULL; #if defined(AVFILTER) if (AV_PIX_FMT_VAAPI == c->pix_fmt) av_frame_unref(avframe); #endif } else { break; } } if (enc_ctx->audio_st[0]) mlt_log_debug(MLT_CONSUMER_SERVICE(consumer), "audio pts %f ", enc_ctx->audio_pts); if (enc_ctx->video_st) mlt_log_debug(MLT_CONSUMER_SERVICE(consumer), "video pts %f ", enc_ctx->video_pts); mlt_log_debug(MLT_CONSUMER_SERVICE(consumer), "\n"); } if (real_time_output == 1 && frames % 2 == 0) { long passed = time_difference(&ante); if (enc_ctx->fifo != NULL) { long pending = (((long) sample_fifo_used(enc_ctx->fifo) / enc_ctx->sample_bytes * 1000) / enc_ctx->frequency) * 1000; passed -= pending; } if (passed < total_time) { long total = (total_time - passed); struct timespec t = {total / 1000000, (total % 1000000) * 1000}; nanosleep(&t, NULL); } } } // Flush the encoder buffers if (real_time_output <= 0) { // Flush audio fifo // TODO: flush all audio streams if (enc_ctx->fifo && enc_ctx->audio_st[0]) for (;;) { int sz = sample_fifo_used(enc_ctx->fifo); int ret = encode_audio(enc_ctx); mlt_log_debug(MLT_CONSUMER_SERVICE(consumer), "flushing audio: sz=%d, ret=%d\n", sz, ret); if (!sz || ret < 0) break; } // Flush video #ifdef AVFMT_RAWPICTURE if (enc_ctx->video_st && !(enc_ctx->oc->oformat->flags & AVFMT_RAWPICTURE)) for (;;) #else if (enc_ctx->video_st) for (;;) #endif { AVCodecContext *c = enc_ctx->vcodec_ctx; AVPacket pkt; av_init_packet(&pkt); if (c->codec->id == AV_CODEC_ID_RAWVIDEO) { pkt.data = NULL; pkt.size = 0; } else { pkt.data = video_outbuf; pkt.size = video_outbuf_size; } // Encode the image int ret; while ((ret = avcodec_receive_packet(c, &pkt)) == AVERROR(EAGAIN)) { ret = avcodec_send_frame(c, NULL); if (ret < 0) { mlt_log_warning(MLT_CONSUMER_SERVICE(consumer), "error with video encode: %d\n", ret); break; } } mlt_log_debug(MLT_CONSUMER_SERVICE(consumer), "flushing video size %d\n", pkt.size); if (pkt.size < 0) break; // Dual pass logging if (mlt_properties_get_data(properties, "_logfile", NULL) && c->stats_out) fprintf(mlt_properties_get_data(properties, "_logfile", NULL), "%s", c->stats_out); if (!pkt.size) break; av_packet_rescale_ts(&pkt, c->time_base, enc_ctx->video_st->time_base); pkt.stream_index = enc_ctx->video_st->index; // write the compressed frame in the media file if (av_interleaved_write_frame(enc_ctx->oc, &pkt) != 0) { mlt_log_fatal(MLT_CONSUMER_SERVICE(consumer), "error writing flushed video frame\n"); mlt_events_fire(properties, "consumer-fatal-error", mlt_event_data_none()); goto on_fatal_error; } } } on_fatal_error: if (frame) mlt_frame_close(frame); // Write the trailer, if any if (frames) av_write_trailer(enc_ctx->oc); // Clean up input and output frames if (converted_avframe) av_free(converted_avframe->data[0]); av_free(converted_avframe); #if defined(AVFILTER) if (enc_ctx->video_st && enc_ctx->vcodec_ctx && AV_PIX_FMT_VAAPI == enc_ctx->vcodec_ctx->pix_fmt) av_frame_free(&avframe); #endif av_free(video_outbuf); av_free(enc_ctx->audio_avframe); // close each codec avcodec_free_context(&enc_ctx->vcodec_ctx); for (i = 0; i < MAX_AUDIO_STREAMS; i++) avcodec_free_context(&enc_ctx->acodec_ctx[i]); // Free the streams for (unsigned int i = 0; i < enc_ctx->oc->nb_streams; i++) av_freep(&enc_ctx->oc->streams[i]); // Close the output file if (!(fmt->flags & AVFMT_NOFILE) && !mlt_properties_get_int(properties, "redirect")) { if (enc_ctx->oc->pb) avio_close(enc_ctx->oc->pb); } // Free the stream av_free(enc_ctx->oc); // Just in case we terminated on pause mlt_consumer_stopped(consumer); mlt_properties_close(enc_ctx->frame_meta_properties); if (mlt_properties_get_int(properties, "pass") > 1) { // Remove the dual pass log file if (mlt_properties_get(properties, "_logfilename")) remove(mlt_properties_get(properties, "_logfilename")); // Remove the x264 dual pass logs char *cwd = getcwd(NULL, 0); const char *file = "x264_2pass.log"; char *full = malloc(strlen(cwd) + strlen(file) + 2); sprintf(full, "%s/%s", cwd, file); remove(full); free(full); file = "x264_2pass.log.temp"; full = malloc(strlen(cwd) + strlen(file) + 2); sprintf(full, "%s/%s", cwd, file); remove(full); free(full); file = "x264_2pass.log.mbtree"; full = malloc(strlen(cwd) + strlen(file) + 2); sprintf(full, "%s/%s", cwd, file); remove(full); free(full); free(cwd); remove("x264_2pass.log.temp"); // Recent versions of libavcodec/x264 support passlogfile and need cleanup if specified. if (!mlt_properties_get(properties, "_logfilename") && mlt_properties_get(properties, "passlogfile")) { mlt_properties_get(properties, "passlogfile"); file = mlt_properties_get(properties, "passlogfile"); remove(file); full = malloc(strlen(file) + strlen(".mbtree") + 1); sprintf(full, "%s.mbtree", file); remove(full); free(full); } } while ((frame = mlt_deque_pop_back(queue))) mlt_frame_close(frame); mlt_pool_release(enc_ctx); return NULL; } /** Close the consumer. */ static void consumer_close(mlt_consumer consumer) { // Stop the consumer mlt_consumer_stop(consumer); // Close the parent mlt_consumer_close(consumer); // Free the memory free(consumer); } mlt-7.22.0/src/modules/avformat/consumer_avformat.yml000664 000000 000000 00000026416 14531534050 022722 0ustar00rootroot000000 000000 schema_version: 0.3 type: consumer identifier: avformat title: FFmpeg Output version: 3 copyright: Copyright (C) 2003-2019 Meltytech, LLC license: LGPL language: en url: http://www.ffmpeg.org/ creator: Charles Yates contributor: - Dan Dennedy tags: - Audio - Video description: Write or stream audio and/or video using FFmpeg. notes: > The avformat consumer uses the FFmpeg libraries to encode to a file or network stream. You can get a lot of information about how to encode with FFmpeg all over the web including FFmpeg's web site. With melt, you simply need to add "-consumer avformat:output.file" to the command line followed by the encoding parameters by translating ffmpeg's '-option value' syntax to melt's 'option=value' syntax. Not all ffmpeg options are supported. Some are very specific to avconv/ffmpeg, the command line utility, and not an "AVOption" used in the libraries. In some cases, there are ffmpeg options that are not AVOptions but which closely resemble an existing MLT property. In that case, MLT supports the ffmpeg option name. For example, ffmpeg's "-ac" is equivalent to the MLT "channels" property. Therefore, the avformat consumer also supports the "ac" property. Complete details are below. Please note that the exact options depend on the version of libavformat and libavcodec on your system. The following is based on FFmpeg v4.0. parameters: - identifier: target argument: yes title: File/URL type: string description: > This is not the same thing as the ffmpeg -target option! If this is not supplied then it will output to stdout. widget: filesave - identifier: mlt_profile title: MLT Profile type: string description: > Choose a MLT basic video settings preset. This overrides a profile that may have been set elsewhere. - identifier: redirect title: Redirect I/O description: > This option allows other services to encapsulate the avformat consumer and do something different (not already available in a protocol) with its output by listening to the avformat-write event. type: integer minimum: 0 maximum: 1 default: 0 widget: checkbox # These override the MLT profile - identifier: width title: Width type: integer minimum: 0 unit: pixels - identifier: height title: Height type: integer minimum: 0 unit: pixels - identifier: display_aspect_num title: Display aspect ratio numerator type: integer minimum: 0 - identifier: display_aspect_den title: Display aspect ratio denominator type: integer minimum: 0 - identifier: display_ratio title: Display aspect ratio readonly: yes - identifier: sample_aspect_num title: Sample aspect ratio numerator type: integer minimum: 0 - identifier: sample_aspect_den title: Sample aspect ratio denominator type: integer minimum: 1 - identifier: progressive title: Progressive type: integer minimum: 0 maximum: 1 widget: checkbox - identifier: colorspace title: Colorspace type: integer description: Set the video colorspace (Y'CbCr only). values: - 240 # SMPTE 240M - 601 # ITU-R BT.601 - 709 # ITU-R BT.709 - identifier: frame_rate_num title: Frame rate numerator type: integer minimum: 0 unit: frames/second - identifier: frame_rate_den title: Frame rate denominator type: integer minimum: 1 unit: frames/second - identifier: fps title: Frame rate readonly: yes unit: frames/second # These are common to all consumers. - identifier: deinterlacer title: Deinterlacer type: string default: yadif values: - greedy - linearblend - onefield - yadif - yadif-nospatial - identifier: rescale title: Image scaler type: string description: Set the pixel interpolation mode. values: - nearest - bilinear - bicubic - bicublin - gauss - sinc - lanczos - spline - identifier: frequency title: Audio sample rate type: integer minimum: 0 maximum: 256000 default: 48000 unit: Hz - identifier: channels title: Audio channels type: integer minimum: 1 maximum: 16 default: 2 - identifier: channels.0 title: Channels on track 1 type: integer description: Used to map a bundle of channels to multi-track audio. minimum: 0 maximum: 16 default: 0 - identifier: channels.1 title: Channels on track 2 type: integer description: Used to map a bundle of channels to multi-track audio. minimum: 0 maximum: 16 default: 0 - identifier: channels.2 title: Channels on track 3 type: integer description: Used to map a bundle of channels to multi-track audio. minimum: 0 maximum: 16 default: 0 - identifier: channels.3 title: Channels on track 4 type: integer description: Used to map a bundle of channels to multi-track audio. minimum: 0 maximum: 16 default: 0 - identifier: channels.4 title: Channels on track 5 type: integer description: Used to map a bundle of channels to multi-track audio. minimum: 0 maximum: 16 default: 0 - identifier: channels.5 title: Channels on track 6 type: integer description: Used to map a bundle of channels to multi-track audio. minimum: 0 maximum: 16 default: 0 - identifier: channels.6 title: Channels on track 7 type: integer description: Used to map a bundle of channels to multi-track audio. minimum: 0 maximum: 16 default: 0 - identifier: channels.7 title: Channels on track 8 type: integer description: Used to map a bundle of channels to multi-track audio. minimum: 0 maximum: 16 default: 0 # These are common to all consumers and affect runtime behavior - identifier: terminate_on_pause title: File output type: integer description: Disable this for streaming. minimum: 0 maximum: 1 default: 1 widget: checkbox - identifier: real_time title: Drop frames type: integer description: > Set the number of processing threads and enable frame-dropping (positive) or disable frame-dropping (negative). default: -1 widget: spinner unit: threads - identifier: prefill title: Pre-roll type: integer description: Set the number of frames to buffer before starting actual output. minimum: 1 default: 1 unit: frames - identifier: buffer title: Buffer type: integer description: > Set the maximum number of frames to buffer - process ahead of the output position. minimum: 1 default: 25 unit: frames # These are ffmpeg-compatible aliases to MLT properties - identifier: s title: Size type: string description: > This is a ffmpeg-compatible equivalent to the MLT profile and width and height parameters. format: WxH unit: pixels - identifier: aspect title: Aspect ratio type: string description: > This is a ffmpeg-compatible equivalent to the MLT profile and other aspect ratio parameters. format: numerator:denominator - identifier: deinterlace title: Deinterlace type: integer description: > This is a ffmpeg-compatible equivalent to the MLT profile and progressive parameter. minimum: 0 maximum: 1 - identifier: r title: Frame rate type: float description: > This is a ffmpeg-compatible equivalent to the MLT profile and frame rate parameters. minimum: 5.0 - identifier: ac title: Audio channels type: integer description: > This is a ffmpeg-compatible equivalent to the channels parameter. minimum: 1 maximum: 16 default: 2 - identifier: ar title: Audio sample rate type: integer description: > This is a ffmpeg-compatible equivalent to the frequency parameter. minimum: 0 maximum: 256000 default: 48000 unit: Hz # These are other non-AVOption parameters specific to FFmpeg. - identifier: threads title: Encoding threads type: integer minimum: 0 maximum: 16 default: 1 widget: spinner unit: threads - identifier: aq title: Audio quality type: integer description: The meaning depends upon the codec. - identifier: dc title: Intra DC precision type: integer default: 8 - identifier: muxdelay title: Muxer delay type: float description: Set the maximum demux-decode delay. default: 0.7 unit: seconds - identifier: muxpreload title: Muxer preload type: float description: Set the initial demux-decode delay. default: 0.5 unit: seconds - identifier: f title: Format type: string description: Use "list" to see the list of formats. default: mpeg - identifier: acodec title: Audio codec description: Use "list" to see the list of audio codecs. default: mp2 - identifier: vcodec title: Video codec description: Use "list" to see the list of video codecs. default: mpeg2video - identifier: atag title: Audio FourCC type: string - identifier: apre title: Audio codec preset type: string - identifier: vpre title: Video codec preset type: string - identifier: fpre title: Format preset type: string - identifier: alang title: Audio language type: string description: Set the 3-character ISO 639 language code of the current audio stream. - identifier: pix_fmt title: Pixel format type: string description: > See 'ffmpeg -pix_fmts' to see a list of values. Normally, this is not required, but some codecs support multiple pixel formats, especially chroma bit-depth. - identifier: sample_fmt title: Audio sample format type: string description: > See 'ffmpeg -sample_fmts' to see a list of values. Normally, this is not required, but some codecs support multiple sample formats, especially bit-depth and planar vs. interleaved. This is evaluated at a lower priority than mlt_audio_format. - identifier: qscale title: Video quantizer type: float description: Set a fixed video quantizer scale for constant quality VBR output. - identifier: vtag title: Video FourCC type: string - identifier: rc_override title: Rate control type: string format: start_frame,end_frame,qscale/... description: This is an override for specific intervals. - identifier: pass title: Pass type: integer description: Select the pass number for two-pass encoding. minimum: 1 maximum: 2 - identifier: passlogfile title: Two-pass log file type: string - identifier: vb title: Video bitrate type: string unit: bits/second description: > Normally this is an integer, but you can append a K suffix for convenience. minimum: 0 - identifier: ab title: Audio bitrate type: string unit: bits/second description: > Normally this is an integer, but you can append a K suffix for convenience. - identifier: an title: Disable audio type: integer minimum: 0 maximum: 1 widget: checkbox - identifier: vn title: Disable video type: integer minimum: 0 maximum: 1 widget: checkbox mlt-7.22.0/src/modules/avformat/factory.c000664 000000 000000 00000054254 14531534050 020261 0ustar00rootroot000000 000000 /* * factory.c -- the factory method interfaces * Copyright (C) 2003-2023 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include extern mlt_consumer consumer_avformat_init(mlt_profile profile, char *file); extern mlt_filter filter_avcolour_space_init(void *arg); extern mlt_filter filter_avdeinterlace_init(void *arg); extern mlt_filter filter_swresample_init(mlt_profile profile, char *arg); extern mlt_filter filter_swscale_init(mlt_profile profile, char *arg); extern mlt_producer producer_avformat_init(mlt_profile profile, const char *service, char *file); extern mlt_filter filter_avfilter_init(mlt_profile, mlt_service_type, const char *, char *); extern mlt_link link_avdeinterlace_init(mlt_profile, mlt_service_type, const char *, char *); extern mlt_link link_avfilter_init(mlt_profile, mlt_service_type, const char *, char *); extern mlt_link link_swresample_init(mlt_profile profile, mlt_service_type, const char *, char *); // ffmpeg Header files #include #include #ifdef AVDEVICE #include #endif #ifdef AVFILTER #include #endif #include // A static flag used to determine if avformat has been initialised static int avformat_initialised = 0; static void avformat_init() { // Initialise avformat if necessary if (avformat_initialised == 0) { avformat_initialised = 1; #ifdef AVDEVICE avdevice_register_all(); #endif avformat_network_init(); av_log_set_level(mlt_log_get_level()); if (getenv("MLT_AVFORMAT_PRODUCER_CACHE")) { int n = atoi(getenv("MLT_AVFORMAT_PRODUCER_CACHE")); mlt_service_cache_set_size(NULL, "producer_avformat", n); } } } static void *create_service(mlt_profile profile, mlt_service_type type, const char *id, void *arg) { avformat_init(); #ifdef CODECS if (!strncmp(id, "avformat", 8)) { if (type == mlt_service_producer_type) return producer_avformat_init(profile, id, arg); else if (type == mlt_service_consumer_type) return consumer_avformat_init(profile, arg); } #endif #ifdef FILTERS if (!strcmp(id, "avcolor_space")) return filter_avcolour_space_init(arg); if (!strcmp(id, "avcolour_space")) return filter_avcolour_space_init(arg); if (!strcmp(id, "avdeinterlace")) { if (type == mlt_service_filter_type) return filter_avdeinterlace_init(arg); else if (type == mlt_service_link_type) return link_avdeinterlace_init(profile, type, id, arg); } if (!strcmp(id, "swscale")) return filter_swscale_init(profile, arg); #endif #ifdef SWRESAMPLE if (!strcmp(id, "swresample")) { if (type == mlt_service_filter_type) return filter_swresample_init(profile, arg); else if (type == mlt_service_link_type) return link_swresample_init(profile, type, id, arg); } #endif return NULL; } static void add_parameters(mlt_properties params, const void *object, int req_flags, const char *unit, const char *subclass, const char *id_prefix) { const AVOption *opt = NULL; // For each AVOption on the AVClass object while ((opt = av_opt_next(object, opt))) { // If matches flags and not a binary option (not supported by Mlt) if (!(opt->flags & req_flags) || (opt->type == AV_OPT_TYPE_BINARY)) continue; // Ignore constants (keyword values) if (!unit && opt->type == AV_OPT_TYPE_CONST) continue; // When processing a groups of options (unit)... // ...ignore non-constants else if (unit && opt->type != AV_OPT_TYPE_CONST) continue; // ...ignore constants not in this group else if (unit && opt->type == AV_OPT_TYPE_CONST && strcmp(unit, opt->unit)) continue; // ..add constants to the 'values' sequence else if (unit && opt->type == AV_OPT_TYPE_CONST) { char key[20]; snprintf(key, 20, "%d", mlt_properties_count(params)); mlt_properties_set(params, key, opt->name); continue; } // Create a map for this option. mlt_properties p = mlt_properties_new(); char key[20]; snprintf(key, 20, "%d", mlt_properties_count(params)); // Add the map to the 'parameters' sequence. mlt_properties_set_data(params, key, p, 0, (mlt_destructor) mlt_properties_close, NULL); // Add the parameter metadata for this AVOption. if (id_prefix) { char id[200]; snprintf(id, sizeof(id), "%s%s", id_prefix, opt->name); mlt_properties_set(p, "identifier", id); } else { mlt_properties_set(p, "identifier", opt->name); } if (opt->help) { if (subclass) { char *s = malloc(strlen(opt->help) + strlen(subclass) + 4); strcpy(s, opt->help); strcat(s, " ("); strcat(s, subclass); strcat(s, ")"); mlt_properties_set(p, "description", s); free(s); } else mlt_properties_set(p, "description", opt->help); } switch (opt->type) { case AV_OPT_TYPE_FLAGS: mlt_properties_set(p, "type", "string"); mlt_properties_set(p, "format", "flags"); break; case AV_OPT_TYPE_INT: if (!opt->unit) { mlt_properties_set(p, "type", "integer"); if (opt->min != INT_MIN) mlt_properties_set_int(p, "minimum", (int) opt->min); if (opt->max != INT_MAX) mlt_properties_set_int(p, "maximum", (int) opt->max); mlt_properties_set_int(p, "default", (int) opt->default_val.i64); } else { mlt_properties_set(p, "type", "string"); mlt_properties_set(p, "format", "integer or keyword"); } break; case AV_OPT_TYPE_INT64: mlt_properties_set(p, "type", "integer"); mlt_properties_set(p, "format", "64-bit"); if (opt->min != INT64_MIN) mlt_properties_set_int64(p, "minimum", (int64_t) opt->min); if (opt->max != INT64_MAX) mlt_properties_set_int64(p, "maximum", (int64_t) opt->max); mlt_properties_set_int64(p, "default", (int64_t) opt->default_val.i64); break; case AV_OPT_TYPE_FLOAT: mlt_properties_set(p, "type", "float"); if (opt->min != FLT_MIN && opt->min != -340282346638528859811704183484516925440.0) mlt_properties_set_double(p, "minimum", opt->min); if (opt->max != FLT_MAX) mlt_properties_set_double(p, "maximum", opt->max); mlt_properties_set_double(p, "default", opt->default_val.dbl); break; case AV_OPT_TYPE_DOUBLE: mlt_properties_set(p, "type", "float"); mlt_properties_set(p, "format", "double"); if (opt->min != DBL_MIN) mlt_properties_set_double(p, "minimum", opt->min); if (opt->max != DBL_MAX) mlt_properties_set_double(p, "maximum", opt->max); mlt_properties_set_double(p, "default", opt->default_val.dbl); break; case AV_OPT_TYPE_STRING: mlt_properties_set(p, "type", "string"); if (opt->default_val.str) { size_t len = strlen(opt->default_val.str) + 3; char *quoted = malloc(len); snprintf(quoted, len, "'%s'", opt->default_val.str); mlt_properties_set(p, "default", quoted); free(quoted); } break; case AV_OPT_TYPE_RATIONAL: mlt_properties_set(p, "type", "string"); mlt_properties_set(p, "format", "numerator/denominator"); break; case AV_OPT_TYPE_CONST: mlt_properties_set(p, "type", "integer"); mlt_properties_set(p, "format", "constant"); break; case AV_OPT_TYPE_COLOR: mlt_properties_set(p, "type", "color"); if (opt->default_val.str) { size_t len = strlen(opt->default_val.str) + 3; char *quoted = malloc(len); snprintf(quoted, len, "'%s'", opt->default_val.str); mlt_properties_set(p, "default", quoted); free(quoted); } default: mlt_properties_set(p, "type", "string"); break; } // If the option belongs to a group (unit) and is not a constant (keyword value) if (opt->unit && opt->type != AV_OPT_TYPE_CONST) { // Create a 'values' sequence. mlt_properties values = mlt_properties_new(); // Recurse to add constants in this group to the 'values' sequence. add_parameters(values, object, req_flags, opt->unit, NULL, NULL); if (mlt_properties_count(values)) mlt_properties_set_data(p, "values", values, 0, (mlt_destructor) mlt_properties_close, NULL); else mlt_properties_close(values); } } } static mlt_properties avformat_metadata(mlt_service_type type, const char *id, void *data) { char file[PATH_MAX]; const char *service_type = NULL; mlt_properties result = NULL; // Convert the service type to a string. switch (type) { case mlt_service_consumer_type: service_type = "consumer"; break; case mlt_service_filter_type: service_type = "filter"; break; case mlt_service_producer_type: service_type = "producer"; break; case mlt_service_transition_type: service_type = "transition"; break; default: return NULL; } if (type == mlt_service_producer_type && !strcmp(id, "avformat-novalidate")) { id = "avformat"; } // Load the yaml file snprintf(file, PATH_MAX, "%s/avformat/%s_%s.yml", mlt_environment("MLT_DATA"), service_type, id); result = mlt_properties_parse_yaml(file); if (result && (type == mlt_service_consumer_type || type == mlt_service_producer_type)) { // Annotate the yaml properties with AVOptions. mlt_properties params = (mlt_properties) mlt_properties_get_data(result, "parameters", NULL); AVFormatContext *avformat = avformat_alloc_context(); AVCodecContext *avcodec = avcodec_alloc_context3(NULL); int flags = (type == mlt_service_consumer_type) ? AV_OPT_FLAG_ENCODING_PARAM : AV_OPT_FLAG_DECODING_PARAM; add_parameters(params, avformat, flags, NULL, NULL, NULL); avformat_init(); if (type == mlt_service_producer_type) { const AVInputFormat *f = NULL; void *iterator = NULL; while ((f = av_demuxer_iterate(&iterator))) if (f->priv_class) add_parameters(params, &f->priv_class, flags, NULL, f->name, NULL); } else { const AVOutputFormat *f = NULL; void *iterator = NULL; while ((f = av_muxer_iterate(&iterator))) if (f->priv_class) add_parameters(params, &f->priv_class, flags, NULL, f->name, NULL); } add_parameters(params, avcodec, flags, NULL, NULL, NULL); const AVCodec *c = NULL; void *iterator = NULL; while ((c = av_codec_iterate(&iterator))) if (c->priv_class) add_parameters(params, &c->priv_class, flags, NULL, c->name, NULL); av_free(avformat); av_free(avcodec); } return result; } #ifdef AVFILTER static mlt_properties avfilter_metadata(mlt_service_type type, const char *id, void *name) { AVFilter *f = (AVFilter *) avfilter_get_by_name(name); if (!f) return NULL; mlt_properties metadata = mlt_properties_new(); mlt_properties_set_double(metadata, "schema_version", 0.3); mlt_properties_set(metadata, "title", f->name); mlt_properties_set(metadata, "version", LIBAVFILTER_IDENT); mlt_properties_set(metadata, "identifier", id); mlt_properties_set(metadata, "description", f->description); mlt_properties_set( metadata, "notes", "Many parameters support animated values (keyframes) but only the numeric ones. Many " "numeric properties have type string because they accept an expression (see FFmpeg " "documentation) even though they evaluate to a numeric value."); mlt_properties_set(metadata, "creator", "libavfilter maintainers"); if (type == mlt_service_filter_type) { mlt_properties_set(metadata, "type", "filter"); } else { mlt_properties_set(metadata, "type", "link"); } mlt_properties tags = mlt_properties_new(); mlt_properties_set_data(metadata, "tags", tags, 0, (mlt_destructor) mlt_properties_close, NULL); if (avfilter_pad_get_type(f->inputs, 0) == AVMEDIA_TYPE_VIDEO) { mlt_properties_set(tags, "0", "Video"); } if (avfilter_pad_get_type(f->inputs, 0) == AVMEDIA_TYPE_AUDIO) { mlt_properties_set(tags, "0", "Audio"); } if (f->priv_class) { mlt_properties params = mlt_properties_new(); mlt_properties_set_data(metadata, "parameters", params, 0, (mlt_destructor) mlt_properties_close, NULL); add_parameters(params, &f->priv_class, AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_AUDIO_PARAM | AV_OPT_FLAG_FILTERING_PARAM, NULL, NULL, "av."); // Add the parameters common to all avfilters. if (f->flags & AVFILTER_FLAG_SLICE_THREADS) { mlt_properties p = mlt_properties_new(); char key[20]; snprintf(key, 20, "%d", mlt_properties_count(params)); mlt_properties_set_data(params, key, p, 0, (mlt_destructor) mlt_properties_close, NULL); mlt_properties_set(p, "identifier", "av.threads"); mlt_properties_set(p, "description", "Maximum number of threads"); mlt_properties_set(p, "type", "integer"); mlt_properties_set_int(p, "minimum", 0); mlt_properties_set_int(p, "default", 0); } { mlt_properties p = mlt_properties_new(); char key[20]; int i = 0; snprintf(key, 20, "%d", mlt_properties_count(params)); mlt_properties_set_data(params, key, p, 0, (mlt_destructor) mlt_properties_close, NULL); mlt_properties_set(p, "identifier", "position"); mlt_properties_set(p, "description", "The MLT position value to set on avfilter frames"); mlt_properties_set(p, "type", "string"); mlt_properties_set(p, "default", "frame"); mlt_properties values = mlt_properties_new(); mlt_properties_set_data(p, "values", values, 0, (mlt_destructor) mlt_properties_close, NULL); snprintf(key, 20, "%d", i++); mlt_properties_set(values, key, "frame"); snprintf(key, 20, "%d", i++); mlt_properties_set(values, key, "filter"); snprintf(key, 20, "%d", i++); mlt_properties_set(values, key, "source"); snprintf(key, 20, "%d", i++); mlt_properties_set(values, key, "producer"); } } return metadata; } #endif static mlt_properties metadata(mlt_service_type type, const char *id, void *data) { char file[PATH_MAX]; snprintf(file, PATH_MAX, "%s/avformat/%s", mlt_environment("MLT_DATA"), (char *) data); return mlt_properties_parse_yaml(file); } MLT_REPOSITORY { #ifdef CODECS MLT_REGISTER(mlt_service_consumer_type, "avformat", create_service); MLT_REGISTER(mlt_service_producer_type, "avformat", create_service); MLT_REGISTER(mlt_service_producer_type, "avformat-novalidate", create_service); MLT_REGISTER_METADATA(mlt_service_consumer_type, "avformat", avformat_metadata, NULL); MLT_REGISTER_METADATA(mlt_service_producer_type, "avformat", avformat_metadata, NULL); MLT_REGISTER_METADATA(mlt_service_producer_type, "avformat-novalidate", metadata, "producer_avformat-novalidate.yml"); #endif #ifdef FILTERS MLT_REGISTER(mlt_service_filter_type, "avcolour_space", create_service); MLT_REGISTER(mlt_service_filter_type, "avcolor_space", create_service); MLT_REGISTER(mlt_service_filter_type, "avdeinterlace", create_service); MLT_REGISTER(mlt_service_filter_type, "swscale", create_service); MLT_REGISTER(mlt_service_link_type, "avcolour_space", mlt_link_filter_init); MLT_REGISTER(mlt_service_link_type, "avcolor_space", mlt_link_filter_init); MLT_REGISTER(mlt_service_link_type, "avdeinterlace", create_service); MLT_REGISTER(mlt_service_link_type, "swscale", mlt_link_filter_init); MLT_REGISTER_METADATA(mlt_service_filter_type, "avcolour_space", metadata, "filter_avcolour_space.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "avcolor_space", metadata, "filter_avcolour_space.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "avdeinterlace", metadata, "filter_avdeinterlace.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "swscale", metadata, "filter_swscale.yml"); MLT_REGISTER_METADATA(mlt_service_link_type, "avcolour_space", mlt_link_filter_metadata, NULL); MLT_REGISTER_METADATA(mlt_service_link_type, "avcolor_space", mlt_link_filter_metadata, NULL); MLT_REGISTER_METADATA(mlt_service_link_type, "avdeinterlace", metadata, "link_avdeinterlace.yml"); MLT_REGISTER_METADATA(mlt_service_link_type, "swscale", mlt_link_filter_metadata, NULL); #ifdef AVFILTER char dirname[PATH_MAX]; snprintf(dirname, PATH_MAX, "%s/avformat/blacklist.txt", mlt_environment("MLT_DATA")); mlt_properties blacklist = mlt_properties_load(dirname); snprintf(dirname, PATH_MAX, "%s/avformat/yuv_only.txt", mlt_environment("MLT_DATA")); mlt_properties_set_data(mlt_global_properties(), "avfilter.yuv_only", mlt_properties_load(dirname), 0, (mlt_destructor) mlt_properties_close, NULL); // Load a list of parameters impacted by consumer scale into global properties. snprintf(dirname, PATH_MAX, "%s/avformat/resolution_scale.yml", mlt_environment("MLT_DATA")); mlt_properties_set_data(mlt_global_properties(), "avfilter.resolution_scale", mlt_properties_parse_yaml(dirname), 0, (mlt_destructor) mlt_properties_close, NULL); const AVFilter *f = NULL; void *iterator = NULL; while ((f = (AVFilter *) av_filter_iterate(&iterator))) { // Support filters that have one input and one output of the same type. #if LIBAVFILTER_VERSION_INT < ((8 << 16) + (3 << 8) + 101) if (avfilter_pad_count(f->inputs) == 1 && avfilter_pad_count(f->outputs) == 1 && #else if (avfilter_filter_pad_count(f, 0) == 1 && avfilter_filter_pad_count(f, 1) == 1 && #endif avfilter_pad_get_type(f->inputs, 0) == avfilter_pad_get_type(f->outputs, 0) && !mlt_properties_get(blacklist, f->name)) { char service_name[1024] = "avfilter."; strncat(service_name, f->name, sizeof(service_name) - strlen(service_name) - 1); MLT_REGISTER(mlt_service_filter_type, service_name, filter_avfilter_init); MLT_REGISTER_METADATA(mlt_service_filter_type, service_name, avfilter_metadata, (void *) f->name); MLT_REGISTER(mlt_service_link_type, service_name, link_avfilter_init); MLT_REGISTER_METADATA(mlt_service_link_type, service_name, avfilter_metadata, (void *) f->name); } } mlt_properties_close(blacklist); #endif // AVFILTER #endif #ifdef SWRESAMPLE MLT_REGISTER(mlt_service_filter_type, "swresample", create_service); MLT_REGISTER_METADATA(mlt_service_filter_type, "swresample", metadata, "filter_swresample.yml"); MLT_REGISTER(mlt_service_link_type, "swresample", create_service); MLT_REGISTER_METADATA(mlt_service_link_type, "swresample", metadata, "link_swresample.yml"); #endif } mlt-7.22.0/src/modules/avformat/filter_avcolour_space.c000664 000000 000000 00000030144 14531534050 023154 0ustar00rootroot000000 000000 /* * filter_avcolour_space.c -- Colour space filter * Copyright (C) 2004-2023 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "common.h" #include #include #include #include #include // ffmpeg Header files #include #include #include #include #include #if 0 // This test might come in handy elsewhere someday. static int is_big_endian( ) { union { int i; char c[ 4 ]; } big_endian_test; big_endian_test.i = 1; return big_endian_test.c[ 0 ] != 1; } #endif #define IMAGE_ALIGN (1) static int convert_mlt_to_av_cs(mlt_image_format format) { int value = 0; switch (format) { case mlt_image_rgb: value = AV_PIX_FMT_RGB24; break; case mlt_image_rgba: value = AV_PIX_FMT_RGBA; break; case mlt_image_yuv422: value = AV_PIX_FMT_YUYV422; break; case mlt_image_yuv420p: value = AV_PIX_FMT_YUV420P; break; case mlt_image_yuv422p16: value = AV_PIX_FMT_YUV422P16LE; break; case mlt_image_yuv420p10: value = AV_PIX_FMT_YUV420P10LE; break; case mlt_image_yuv444p10: value = AV_PIX_FMT_YUV444P10LE; break; default: mlt_log_error(NULL, "[filter avcolor_space] Invalid format %s\n", mlt_image_format_name(format)); break; } return value; } // returns set_lumage_transfer result static int av_convert_image(uint8_t *out, uint8_t *in, int out_fmt, int in_fmt, int out_width, int out_height, int in_width, int in_height, int src_colorspace, int dst_colorspace, int src_full_range, int dst_full_range) { uint8_t *in_data[4]; int in_stride[4]; uint8_t *out_data[4]; int out_stride[4]; int flags = mlt_get_sws_flags(in_width, in_height, in_fmt, out_width, out_height, out_fmt); int error = -1; if (in_fmt == AV_PIX_FMT_YUV422P16LE) mlt_image_format_planes(in_fmt, in_width, in_height, in, in_data, in_stride); else av_image_fill_arrays(in_data, in_stride, in, in_fmt, in_width, in_height, IMAGE_ALIGN); if (out_fmt == AV_PIX_FMT_YUV422P16LE) mlt_image_format_planes(out_fmt, out_width, out_height, out, out_data, out_stride); else av_image_fill_arrays(out_data, out_stride, out, out_fmt, out_width, out_height, IMAGE_ALIGN); struct SwsContext *context = sws_getContext( in_width, in_height, in_fmt, out_width, out_height, out_fmt, flags, NULL, NULL, NULL); if (context) { // libswscale wants the RGB colorspace to be SWS_CS_DEFAULT, which is = SWS_CS_ITU601. if (out_fmt == AV_PIX_FMT_RGB24 || out_fmt == AV_PIX_FMT_RGBA) dst_colorspace = 601; error = mlt_set_luma_transfer(context, src_colorspace, dst_colorspace, src_full_range, dst_full_range); sws_scale(context, (const uint8_t *const *) in_data, in_stride, 0, in_height, out_data, out_stride); sws_freeContext(context); } return error; } /** Do it :-). */ static int convert_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, mlt_image_format output_format) { mlt_properties properties = MLT_FRAME_PROPERTIES(frame); int error = 0; int out_width = mlt_properties_get_int(properties, "convert_image_width"); int out_height = mlt_properties_get_int(properties, "convert_image_height"); mlt_properties_clear(properties, "convert_image_width"); mlt_properties_clear(properties, "convert_image_height"); if (*format != output_format || out_width) { mlt_profile profile = mlt_service_profile( MLT_PRODUCER_SERVICE(mlt_frame_get_original_producer(frame))); int profile_colorspace = profile ? profile->colorspace : 601; int colorspace = mlt_properties_get_int(properties, "colorspace"); int width = mlt_properties_get_int(properties, "width"); int height = mlt_properties_get_int(properties, "height"); int src_full_range = mlt_properties_get_int(properties, "full_range"); const char *dst_color_range = mlt_properties_get(properties, "consumer.color_range"); int dst_full_range = dst_color_range && (!strcmp("pc", dst_color_range) || !strcmp("jpeg", dst_color_range)); if (out_width <= 0) out_width = width; if (out_height <= 0) out_height = height; mlt_log_debug( NULL, "[filter avcolor_space] %s @ %dx%d -> %s @ %dx%d space %d->%d full %d->%d (%d)\n", mlt_image_format_name(*format), width, height, mlt_image_format_name(output_format), out_width, out_height, colorspace, profile_colorspace, src_full_range, dst_full_range, mlt_frame_get_position(frame)); int in_fmt = convert_mlt_to_av_cs(*format); int out_fmt = convert_mlt_to_av_cs(output_format); int size = FFMAX(av_image_get_buffer_size(out_fmt, out_width, out_height, IMAGE_ALIGN), mlt_image_format_size(output_format, out_width, out_height, NULL)); uint8_t *output = mlt_pool_alloc(size); if (out_width == width && out_height == height) { if (*format == mlt_image_rgba) { register int len = width * height; uint8_t *alpha = mlt_pool_alloc(len); if (alpha) { // Extract the alpha mask from the RGBA image using Duff's Device register uint8_t *s = *image + 3; // start on the alpha component register uint8_t *d = alpha; register int n = (len + 7) / 8; switch (len % 8) { case 0: do { *d++ = *s; s += 4; case 7: *d++ = *s; s += 4; case 6: *d++ = *s; s += 4; case 5: *d++ = *s; s += 4; case 4: *d++ = *s; s += 4; case 3: *d++ = *s; s += 4; case 2: *d++ = *s; s += 4; case 1: *d++ = *s; s += 4; } while (--n > 0); } mlt_frame_set_alpha(frame, alpha, len, mlt_pool_release); } } } else { // Scaling mlt_properties_clear(properties, "alpha"); } // Update the output if (!av_convert_image(output, *image, out_fmt, in_fmt, out_width, out_height, width, height, colorspace, profile_colorspace, src_full_range, dst_full_range)) { // The new colorspace is only valid if destination is YUV. if (output_format == mlt_image_yuv422 || output_format == mlt_image_yuv420p || output_format == mlt_image_yuv422p16 || output_format == mlt_image_yuv420p10 || output_format == mlt_image_yuv444p10) mlt_properties_set_int(properties, "colorspace", profile_colorspace); mlt_properties_set_int(properties, "full_range", dst_full_range); } *image = output; *format = output_format; mlt_frame_set_image(frame, output, size, mlt_pool_release); if (out_width == width && out_height == height) if (output_format == mlt_image_rgba) { register int len = width * height; int alpha_size = 0; uint8_t *alpha = mlt_frame_get_alpha_size(frame, &alpha_size); if (alpha && alpha_size >= len) { // Merge the alpha mask from into the RGBA image using Duff's Device register uint8_t *s = alpha; register uint8_t *d = *image + 3; // start on the alpha component register int n = (len + 7) / 8; switch (len % 8) { case 0: do { *d = *s++; d += 4; case 7: *d = *s++; d += 4; case 6: *d = *s++; d += 4; case 5: *d = *s++; d += 4; case 4: *d = *s++; d += 4; case 3: *d = *s++; d += 4; case 2: *d = *s++; d += 4; case 1: *d = *s++; d += 4; } while (--n > 0); } } } mlt_properties_set_int(properties, "format", output_format); mlt_properties_set_int(properties, "width", out_width); mlt_properties_set_int(properties, "height", out_height); } return error; } /** Filter processing. */ static mlt_frame filter_process(mlt_filter filter, mlt_frame frame) { // Set a default colorspace on the frame if not yet set by the producer. // The producer may still change it during get_image. // This way we do not have to modify each producer to set a valid colorspace. mlt_properties properties = MLT_FRAME_PROPERTIES(frame); if (mlt_properties_get_int(properties, "colorspace") <= 0) mlt_properties_set_int(properties, "colorspace", mlt_service_profile(MLT_FILTER_SERVICE(filter))->colorspace); if (!frame->convert_image) frame->convert_image = convert_image; return frame; } /** Constructor for the filter. */ mlt_filter filter_avcolour_space_init(void *arg) { mlt_filter filter = mlt_filter_new(); if (filter != NULL) filter->process = filter_process; return filter; } mlt-7.22.0/src/modules/avformat/filter_avcolour_space.yml000664 000000 000000 00000000662 14531534050 023535 0ustar00rootroot000000 000000 schema_version: 7.0 type: filter identifier: avcolor_space title: FFmpeg Image Converter version: 1 copyright: Meltytech, LLC license: LGPLv2.1 language: en url: http://www.ffmpeg.org/ tags: - Video - Hidden description: Converts the colorspace and pixel format. notes: > This is not intended to be created directly. Rather, the loader producer loads it if it is available to set the convert_image function pointer on frames. mlt-7.22.0/src/modules/avformat/filter_avdeinterlace.c000664 000000 000000 00000026215 14531534050 022761 0ustar00rootroot000000 000000 /* * filter_avdeinterlace.c -- deinterlace filter * Copyright (C) 2003-2017 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include // ffmpeg Header files #include #include #ifdef USE_MMX #include "mmx.h" #else #define MAX_NEG_CROP 1024 static uint8_t ff_cropTbl[256 + 2 * MAX_NEG_CROP] = { 0, }; #endif #ifdef USE_MMX #define DEINT_INPLACE_LINE_LUM \ movd_m2r(lum_m4[0], mm0); \ movd_m2r(lum_m3[0], mm1); \ movd_m2r(lum_m2[0], mm2); \ movd_m2r(lum_m1[0], mm3); \ movd_m2r(lum[0], mm4); \ punpcklbw_r2r(mm7, mm0); \ movd_r2m(mm2, lum_m4[0]); \ punpcklbw_r2r(mm7, mm1); \ punpcklbw_r2r(mm7, mm2); \ punpcklbw_r2r(mm7, mm3); \ punpcklbw_r2r(mm7, mm4); \ paddw_r2r(mm3, mm1); \ psllw_i2r(1, mm2); \ paddw_r2r(mm4, mm0); \ psllw_i2r(2, mm1); \ paddw_r2r(mm6, mm2); \ paddw_r2r(mm2, mm1); \ psubusw_r2r(mm0, mm1); \ psrlw_i2r(3, mm1); \ packuswb_r2r(mm7, mm1); \ movd_r2m(mm1, lum_m2[0]); #define DEINT_LINE_LUM \ movd_m2r(lum_m4[0], mm0); \ movd_m2r(lum_m3[0], mm1); \ movd_m2r(lum_m2[0], mm2); \ movd_m2r(lum_m1[0], mm3); \ movd_m2r(lum[0], mm4); \ punpcklbw_r2r(mm7, mm0); \ punpcklbw_r2r(mm7, mm1); \ punpcklbw_r2r(mm7, mm2); \ punpcklbw_r2r(mm7, mm3); \ punpcklbw_r2r(mm7, mm4); \ paddw_r2r(mm3, mm1); \ psllw_i2r(1, mm2); \ paddw_r2r(mm4, mm0); \ psllw_i2r(2, mm1); \ paddw_r2r(mm6, mm2); \ paddw_r2r(mm2, mm1); \ psubusw_r2r(mm0, mm1); \ psrlw_i2r(3, mm1); \ packuswb_r2r(mm7, mm1); \ movd_r2m(mm1, dst[0]); #endif /* filter parameters: [-1 4 2 4 -1] // 8 */ static inline void deinterlace_line(uint8_t *dst, const uint8_t *lum_m4, const uint8_t *lum_m3, const uint8_t *lum_m2, const uint8_t *lum_m1, const uint8_t *lum, int size) { #ifndef USE_MMX uint8_t *cm = ff_cropTbl + MAX_NEG_CROP; int sum; for (; size > 0; size--) { sum = -lum_m4[0]; sum += lum_m3[0] << 2; sum += lum_m2[0] << 1; sum += lum_m1[0] << 2; sum += -lum[0]; dst[0] = cm[(sum + 4) >> 3]; lum_m4++; lum_m3++; lum_m2++; lum_m1++; lum++; dst++; } #else { mmx_t rounder; rounder.uw[0] = 4; rounder.uw[1] = 4; rounder.uw[2] = 4; rounder.uw[3] = 4; pxor_r2r(mm7, mm7); movq_m2r(rounder, mm6); } for (; size > 3; size -= 4) { DEINT_LINE_LUM lum_m4 += 4; lum_m3 += 4; lum_m2 += 4; lum_m1 += 4; lum += 4; dst += 4; } #endif } static inline void deinterlace_line_inplace( uint8_t *lum_m4, uint8_t *lum_m3, uint8_t *lum_m2, uint8_t *lum_m1, uint8_t *lum, int size) { #ifndef USE_MMX uint8_t *cm = ff_cropTbl + MAX_NEG_CROP; int sum; for (; size > 0; size--) { sum = -lum_m4[0]; sum += lum_m3[0] << 2; sum += lum_m2[0] << 1; lum_m4[0] = lum_m2[0]; sum += lum_m1[0] << 2; sum += -lum[0]; lum_m2[0] = cm[(sum + 4) >> 3]; lum_m4++; lum_m3++; lum_m2++; lum_m1++; lum++; } #else { mmx_t rounder; rounder.uw[0] = 4; rounder.uw[1] = 4; rounder.uw[2] = 4; rounder.uw[3] = 4; pxor_r2r(mm7, mm7); movq_m2r(rounder, mm6); } for (; size > 3; size -= 4) { DEINT_INPLACE_LINE_LUM lum_m4 += 4; lum_m3 += 4; lum_m2 += 4; lum_m1 += 4; lum += 4; } #endif } /* deinterlacing : 2 temporal taps, 3 spatial taps linear filter. The top field is copied as is, but the bottom field is deinterlaced against the top field. */ static inline void deinterlace_bottom_field( uint8_t *dst, int dst_wrap, const uint8_t *src1, int src_wrap, int width, int height) { const uint8_t *src_m2, *src_m1, *src_0, *src_p1, *src_p2; int y; src_m2 = src1; src_m1 = src1; src_0 = &src_m1[src_wrap]; src_p1 = &src_0[src_wrap]; src_p2 = &src_p1[src_wrap]; for (y = 0; y < (height - 2); y += 2) { memcpy(dst, src_m1, width); dst += dst_wrap; deinterlace_line(dst, src_m2, src_m1, src_0, src_p1, src_p2, width); src_m2 = src_0; src_m1 = src_p1; src_0 = src_p2; src_p1 += 2 * src_wrap; src_p2 += 2 * src_wrap; dst += dst_wrap; } memcpy(dst, src_m1, width); dst += dst_wrap; /* do last line */ deinterlace_line(dst, src_m2, src_m1, src_0, src_0, src_0, width); } static inline void deinterlace_bottom_field_inplace(uint8_t *src1, int src_wrap, int width, int height) { uint8_t *src_m1, *src_0, *src_p1, *src_p2; int y; uint8_t *buf; buf = (uint8_t *) av_malloc(width); src_m1 = src1; memcpy(buf, src_m1, width); src_0 = &src_m1[src_wrap]; src_p1 = &src_0[src_wrap]; src_p2 = &src_p1[src_wrap]; for (y = 0; y < (height - 2); y += 2) { deinterlace_line_inplace(buf, src_m1, src_0, src_p1, src_p2, width); src_m1 = src_p1; src_0 = src_p2; src_p1 += 2 * src_wrap; src_p2 += 2 * src_wrap; } /* do last line */ deinterlace_line_inplace(buf, src_m1, src_0, src_0, src_0, width); av_free(buf); } /* deinterlace - if not supported return -1 */ static int mlt_avpicture_deinterlace(uint8_t *dst_data[4], int dst_stride[4], uint8_t *src_data[4], int src_stride[4], int pix_fmt, int width, int height) { int i; if (pix_fmt != AV_PIX_FMT_YUV420P && pix_fmt != AV_PIX_FMT_YUV422P && pix_fmt != AV_PIX_FMT_YUYV422 && pix_fmt != AV_PIX_FMT_YUV444P && pix_fmt != AV_PIX_FMT_YUV411P) return -1; if ((width & 3) != 0 || (height & 3) != 0) return -1; if (pix_fmt != AV_PIX_FMT_YUYV422) { for (i = 0; i < 3; i++) { if (i == 1) { switch (pix_fmt) { case AV_PIX_FMT_YUV420P: width >>= 1; height >>= 1; break; case AV_PIX_FMT_YUV422P: width >>= 1; break; case AV_PIX_FMT_YUV411P: width >>= 2; break; default: break; } } if (src_data[0] == dst_data[0]) { deinterlace_bottom_field_inplace(dst_data[i], dst_stride[i], width, height); } else { deinterlace_bottom_field(dst_data[i], dst_stride[i], src_data[i], src_stride[i], width, height); } } } else { if (src_data[0] == dst_data[0]) { deinterlace_bottom_field_inplace(dst_data[0], dst_stride[0], width << 1, height); } else { deinterlace_bottom_field(dst_data[0], dst_stride[0], src_data[0], src_stride[0], width << 1, height); } } #ifdef USE_MMX emms(); #endif return 0; } /** Do it :-). */ static int filter_get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { int error = 0; int deinterlace = mlt_properties_get_int(MLT_FRAME_PROPERTIES(frame), "consumer.progressive"); // Determine if we need a writable version or not if (deinterlace && !writable) writable = !mlt_properties_get_int(MLT_FRAME_PROPERTIES(frame), "progressive"); // Get the input image *format = mlt_image_yuv422; error = mlt_frame_get_image(frame, image, format, width, height, 1); // Check that we want progressive and we aren't already progressive if (deinterlace && *format == mlt_image_yuv422 && *image != NULL && !mlt_properties_get_int(MLT_FRAME_PROPERTIES(frame), "progressive")) { // Create a picture uint8_t *image_data[4]; int strides[4]; // Fill the picture av_image_fill_arrays(image_data, strides, *image, AV_PIX_FMT_YUYV422, *width, *height, 1); mlt_log_timings_begin(); mlt_avpicture_deinterlace(image_data, strides, image_data, strides, AV_PIX_FMT_YUYV422, *width, *height); mlt_log_timings_end(NULL, "mlt_avpicture_deinterlace"); // Make sure that others know the frame is deinterlaced mlt_properties_set_int(MLT_FRAME_PROPERTIES(frame), "progressive", 1); } return error; } /** Deinterlace filter processing - this should be lazy evaluation here... */ static mlt_frame deinterlace_process(mlt_filter filter, mlt_frame frame) { // Push the get_image method on to the stack mlt_frame_push_get_image(frame, filter_get_image); return frame; } /** Constructor for the filter. */ mlt_filter filter_avdeinterlace_init(void *arg) { #ifndef USE_MMX if (ff_cropTbl[MAX_NEG_CROP + 1] == 0) { int i; for (i = 0; i < 256; i++) ff_cropTbl[i + MAX_NEG_CROP] = i; for (i = 0; i < MAX_NEG_CROP; i++) { ff_cropTbl[i] = 0; ff_cropTbl[i + MAX_NEG_CROP + 256] = 255; } } #endif mlt_filter filter = mlt_filter_new(); if (filter != NULL) filter->process = deinterlace_process; return filter; } mlt-7.22.0/src/modules/avformat/filter_avdeinterlace.yml000664 000000 000000 00000000736 14531534050 023340 0ustar00rootroot000000 000000 schema_version: 7.0 type: filter identifier: avdeinterlace title: Legacy FFmpeg Deinterlacer (*DEPRECATED*) version: 1 copyright: Meltytech, LLC license: LGPLv2.1 language: en url: http://www.ffmpeg.org/ tags: - Video - Hidden description: Deinterlace interlaced video. notes: > This is not intended to be created directly. Rather, the loader producer loads it if it is available to set deinterlace interlaced input when the consumer or profile is set to progressive. mlt-7.22.0/src/modules/avformat/filter_avfilter.c000664 000000 000000 00000112123 14531534050 021761 0ustar00rootroot000000 000000 /* * filter_avfilter.c -- provide various filters based on libavfilter * Copyright (C) 2016-2022 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #if !defined(_XOPEN_SOURCE) || _XOPEN_SOURCE < 700 #undef _XOPEN_SOURCE #define _XOPEN_SOURCE 700 #endif #include "common.h" #include #include #include #include #include #include #include #include #include #include #include #define PARAM_PREFIX "av." #define PARAM_PREFIX_LEN (sizeof(PARAM_PREFIX) - 1) typedef struct { AVFilter *avfilter; AVFilterContext *avbuffsink_ctx; AVFilterContext *avbuffsrc_ctx; AVFilterContext *avfilter_ctx; AVFilterContext *scale_ctx; AVFilterContext *pad_ctx; AVFilterGraph *avfilter_graph; AVFrame *avinframe; AVFrame *avoutframe; int format; int width; int height; int reset; } private_data; #if LIBAVUTIL_VERSION_INT >= ((56 << 16) + (35 << 8) + 101) static int animatable_avoption(const AVOption *opt) { return opt && (opt->flags & AV_OPT_FLAG_RUNTIME_PARAM) && opt->type != AV_OPT_TYPE_COLOR; } #endif static void property_changed(mlt_service owner, mlt_filter filter, mlt_event_data event_data) { const char *name = mlt_event_data_to_string(event_data); if (name && strncmp(PARAM_PREFIX, name, PARAM_PREFIX_LEN) == 0) { private_data *pdata = (private_data *) filter->child; if (pdata->avfilter_ctx) { mlt_service_lock(MLT_FILTER_SERVICE(filter)); const AVOption *opt = av_opt_find(pdata->avfilter_ctx->priv, name + PARAM_PREFIX_LEN, 0, 0, 0); #if LIBAVUTIL_VERSION_INT >= ((56 << 16) + (35 << 8) + 101) pdata->reset = opt && !(animatable_avoption(opt) && mlt_properties_is_anim(MLT_FILTER_PROPERTIES(filter), name)); #else pdata->reset = opt && !mlt_properties_is_anim(MLT_FILTER_PROPERTIES(filter), name); #endif mlt_service_unlock(MLT_FILTER_SERVICE(filter)); } } } static void set_avfilter_options(mlt_filter filter, double scale) { private_data *pdata = (private_data *) filter->child; mlt_properties filter_properties = MLT_FILTER_PROPERTIES(filter); int i; int count = mlt_properties_count(filter_properties); mlt_properties scale_map = mlt_properties_get_data(filter_properties, "_resolution_scale", NULL); for (i = 0; i < count; i++) { const char *param_name = mlt_properties_get_name(filter_properties, i); if (param_name && strncmp(PARAM_PREFIX, param_name, PARAM_PREFIX_LEN) == 0) { const AVOption *opt = av_opt_find(pdata->avfilter_ctx->priv, param_name + PARAM_PREFIX_LEN, 0, 0, 0); const char *value = mlt_properties_get_value(filter_properties, i); #if LIBAVUTIL_VERSION_INT >= ((56 << 16) + (35 << 8) + 101) if (opt && !(animatable_avoption(opt) && mlt_properties_is_anim(filter_properties, param_name))) #else if (opt && !mlt_properties_is_anim(filter_properties, param_name)) #endif { if (scale != 1.0) { double scale2 = mlt_properties_get_double(scale_map, opt->name); if (scale2 != 0.0) { double x = mlt_properties_get_double(filter_properties, param_name); x *= scale * scale2; mlt_properties_set_double(filter_properties, "_avfilter_temp", x); value = mlt_properties_get(filter_properties, "_avfilter_temp"); } } av_opt_set(pdata->avfilter_ctx->priv, opt->name, value, 0); } } } } static void send_avformat_commands(mlt_filter filter, mlt_frame frame, private_data *pdata, double scale) { #if LIBAVUTIL_VERSION_INT >= ((56 << 16) + (35 << 8) + 101) mlt_properties prop = MLT_FILTER_PROPERTIES(filter); mlt_position position = mlt_filter_get_position(filter, frame); int length = mlt_filter_get_length2(filter, frame); mlt_properties scale_map = mlt_properties_get_data(prop, "_resolution_scale", NULL); int count = mlt_properties_count(prop); int i; for (i = 0; i < count; i++) { char *name = mlt_properties_get_name(prop, i); if (!strncmp(name, PARAM_PREFIX, PARAM_PREFIX_LEN)) { const AVOption *opt = av_opt_find(pdata->avfilter_ctx->priv, name + PARAM_PREFIX_LEN, 0, 0, 0); if (animatable_avoption(opt) && mlt_properties_is_anim(prop, name)) { double x = mlt_properties_anim_get_double(prop, name, position, length); if (scale != 1.0) { double scale2 = mlt_properties_get_double(scale_map, opt->name); if (scale2 != 0.0) { x *= scale * scale2; } } mlt_properties_set_double(prop, "_avfilter_temp", x); char *new_val = mlt_properties_get(prop, "_avfilter_temp"); char *cur_val = NULL; av_opt_get(pdata->avfilter_ctx->priv, name + PARAM_PREFIX_LEN, AV_OPT_SEARCH_CHILDREN, (uint8_t **) &cur_val); if (new_val && cur_val && strcmp(new_val, cur_val)) { avfilter_graph_send_command(pdata->avfilter_graph, pdata->avfilter->name, name + PARAM_PREFIX_LEN, new_val, NULL, 0, 0); } av_free(cur_val); } } } #endif } static void init_audio_filtergraph(mlt_filter filter, mlt_audio_format format, int frequency, int channels) { private_data *pdata = (private_data *) filter->child; const AVFilter *abuffersrc = avfilter_get_by_name("abuffer"); const AVFilter *abuffersink = avfilter_get_by_name("abuffersink"); int sample_fmts[] = {-1, -1}; int sample_rates[] = {-1, -1}; int channel_counts[] = {-1, -1}; int64_t channel_layouts[] = {-1, -1}; char channel_layout_str[64]; int ret; pdata->format = format; // Set up formats sample_fmts[0] = mlt_to_av_sample_format(format); sample_rates[0] = frequency; channel_counts[0] = channels; channel_layouts[0] = av_get_default_channel_layout(channels); av_get_channel_layout_string(channel_layout_str, sizeof(channel_layout_str), 0, channel_layouts[0]); // Destroy the current filter graph avfilter_graph_free(&pdata->avfilter_graph); // Create the new filter graph pdata->avfilter_graph = avfilter_graph_alloc(); if (!pdata->avfilter_graph) { mlt_log_error(filter, "Cannot create filter graph\n"); goto fail; } // Set thread count if supported. if (pdata->avfilter->flags & AVFILTER_FLAG_SLICE_THREADS) { av_opt_set_int(pdata->avfilter_graph, "threads", FFMAX(0, mlt_properties_get_int(MLT_FILTER_PROPERTIES(filter), "av.threads")), 0); } // Initialize the buffer source filter context pdata->avbuffsrc_ctx = avfilter_graph_alloc_filter(pdata->avfilter_graph, abuffersrc, "in"); if (!pdata->avbuffsrc_ctx) { mlt_log_error(filter, "Cannot create audio buffer source\n"); goto fail; } ret = av_opt_set_int(pdata->avbuffsrc_ctx, "sample_rate", sample_rates[0], AV_OPT_SEARCH_CHILDREN); if (ret < 0) { mlt_log_error(filter, "Cannot set src sample rate %d\n", sample_rates[0]); goto fail; } ret = av_opt_set_int(pdata->avbuffsrc_ctx, "sample_fmt", sample_fmts[0], AV_OPT_SEARCH_CHILDREN); if (ret < 0) { mlt_log_error(filter, "Cannot set src sample format %d\n", sample_fmts[0]); goto fail; } ret = av_opt_set_int(pdata->avbuffsrc_ctx, "channels", channel_counts[0], AV_OPT_SEARCH_CHILDREN); if (ret < 0) { mlt_log_error(filter, "Cannot set src channels %d\n", channel_counts[0]); goto fail; } ret = av_opt_set(pdata->avbuffsrc_ctx, "channel_layout", channel_layout_str, AV_OPT_SEARCH_CHILDREN); if (ret < 0) { mlt_log_error(filter, "Cannot set src channel layout %s\n", channel_layout_str); goto fail; } ret = avfilter_init_str(pdata->avbuffsrc_ctx, NULL); if (ret < 0) { mlt_log_error(filter, "Cannot init buffer source\n"); goto fail; } // Initialize the buffer sink filter context pdata->avbuffsink_ctx = avfilter_graph_alloc_filter(pdata->avfilter_graph, abuffersink, "out"); if (!pdata->avbuffsink_ctx) { mlt_log_error(filter, "Cannot create audio buffer sink\n"); goto fail; } ret = av_opt_set_int_list(pdata->avbuffsink_ctx, "sample_fmts", sample_fmts, -1, AV_OPT_SEARCH_CHILDREN); if (ret < 0) { mlt_log_error(filter, "Cannot set sink sample formats\n"); goto fail; } ret = av_opt_set_int_list(pdata->avbuffsink_ctx, "sample_rates", sample_rates, -1, AV_OPT_SEARCH_CHILDREN); if (ret < 0) { mlt_log_error(filter, "Cannot set sink sample rates\n"); goto fail; } ret = av_opt_set_int_list(pdata->avbuffsink_ctx, "channel_counts", channel_counts, -1, AV_OPT_SEARCH_CHILDREN); if (ret < 0) { mlt_log_error(filter, "Cannot set sink channel counts\n"); goto fail; } ret = av_opt_set_int_list(pdata->avbuffsink_ctx, "channel_layouts", channel_layouts, -1, AV_OPT_SEARCH_CHILDREN); if (ret < 0) { mlt_log_error(filter, "Cannot set sink channel_layouts\n"); goto fail; } ret = avfilter_init_str(pdata->avbuffsink_ctx, NULL); if (ret < 0) { mlt_log_error(filter, "Cannot init buffer sink\n"); goto fail; } // Initialize the filter context pdata->avfilter_ctx = avfilter_graph_alloc_filter(pdata->avfilter_graph, pdata->avfilter, pdata->avfilter->name); if (!pdata->avfilter_ctx) { mlt_log_error(filter, "Cannot create audio filter\n"); goto fail; } set_avfilter_options(filter, 1.0); ret = avfilter_init_str(pdata->avfilter_ctx, NULL); if (ret < 0) { mlt_log_error(filter, "Cannot init filter\n"); goto fail; } // Connect the filters ret = avfilter_link(pdata->avbuffsrc_ctx, 0, pdata->avfilter_ctx, 0); if (ret < 0) { mlt_log_error(filter, "Cannot link src to filter\n"); goto fail; } ret = avfilter_link(pdata->avfilter_ctx, 0, pdata->avbuffsink_ctx, 0); if (ret < 0) { mlt_log_error(filter, "Cannot link filter to sink\n"); goto fail; } // Configure the graph. ret = avfilter_graph_config(pdata->avfilter_graph, NULL); if (ret < 0) { mlt_log_error(filter, "Cannot configure the filter graph\n"); goto fail; } return; fail: avfilter_graph_free(&pdata->avfilter_graph); } static void init_image_filtergraph( mlt_filter filter, mlt_image_format format, int width, int height, double resolution_scale) { private_data *pdata = (private_data *) filter->child; mlt_profile profile = mlt_service_profile(MLT_FILTER_SERVICE(filter)); const AVFilter *buffersrc = avfilter_get_by_name("buffer"); const AVFilter *buffersink = avfilter_get_by_name("buffersink"); const AVFilter *scale = avfilter_get_by_name("scale"); const AVFilter *pad = avfilter_get_by_name("pad"); mlt_properties p = mlt_properties_new(); enum AVPixelFormat pixel_fmts[] = {-1, -1}; AVRational sar = (AVRational){profile->sample_aspect_num, profile->sample_aspect_den}; AVRational timebase = (AVRational){profile->frame_rate_den, profile->frame_rate_num}; AVRational framerate = (AVRational){profile->frame_rate_num, profile->frame_rate_den}; int ret; pdata->format = format; pdata->width = width; pdata->height = height; // Set up formats pixel_fmts[0] = mlt_to_av_image_format(format); // Destroy the current filter graph avfilter_graph_free(&pdata->avfilter_graph); // Create the new filter graph pdata->avfilter_graph = avfilter_graph_alloc(); if (!pdata->avfilter_graph) { mlt_log_error(filter, "Cannot create filter graph\n"); goto fail; } pdata->avfilter_graph->scale_sws_opts = av_strdup("flags=" MLT_AVFILTER_SWS_FLAGS); // Set thread count if supported. if (pdata->avfilter->flags & AVFILTER_FLAG_SLICE_THREADS) { av_opt_set_int(pdata->avfilter_graph, "threads", FFMAX(0, mlt_properties_get_int(MLT_FILTER_PROPERTIES(filter), "av.threads")), 0); } // Initialize the buffer source filter context pdata->avbuffsrc_ctx = avfilter_graph_alloc_filter(pdata->avfilter_graph, buffersrc, "in"); if (!pdata->avbuffsrc_ctx) { mlt_log_error(filter, "Cannot create image buffer source\n"); goto fail; } ret = av_opt_set_int(pdata->avbuffsrc_ctx, "width", width, AV_OPT_SEARCH_CHILDREN); if (ret < 0) { mlt_log_error(filter, "Cannot set src width %d\n", width); goto fail; } ret = av_opt_set_int(pdata->avbuffsrc_ctx, "height", height, AV_OPT_SEARCH_CHILDREN); if (ret < 0) { mlt_log_error(filter, "Cannot set src height %d\n", height); goto fail; } ret = av_opt_set_pixel_fmt(pdata->avbuffsrc_ctx, "pix_fmt", pixel_fmts[0], AV_OPT_SEARCH_CHILDREN); if (ret < 0) { mlt_log_error(filter, "Cannot set src pixel format %d\n", pixel_fmts[0]); goto fail; } ret = av_opt_set_q(pdata->avbuffsrc_ctx, "sar", sar, AV_OPT_SEARCH_CHILDREN); if (ret < 0) { mlt_log_error(filter, "Cannot set src sar %d/%d\n", sar.num, sar.den); goto fail; } ret = av_opt_set_q(pdata->avbuffsrc_ctx, "time_base", timebase, AV_OPT_SEARCH_CHILDREN); if (ret < 0) { mlt_log_error(filter, "Cannot set src time_base %d/%d\n", timebase.num, timebase.den); goto fail; } ret = av_opt_set_q(pdata->avbuffsrc_ctx, "frame_rate", framerate, AV_OPT_SEARCH_CHILDREN); if (ret < 0) { mlt_log_error(filter, "Cannot set src frame_rate %d/%d\n", framerate.num, framerate.den); goto fail; } ret = avfilter_init_str(pdata->avbuffsrc_ctx, NULL); if (ret < 0) { mlt_log_error(filter, "Cannot init buffer source\n"); goto fail; } // Initialize the buffer sink filter context pdata->avbuffsink_ctx = avfilter_graph_alloc_filter(pdata->avfilter_graph, buffersink, "out"); if (!pdata->avbuffsink_ctx) { mlt_log_error(filter, "Cannot create image buffer sink\n"); goto fail; } ret = av_opt_set_int_list(pdata->avbuffsink_ctx, "pix_fmts", pixel_fmts, -1, AV_OPT_SEARCH_CHILDREN); if (ret < 0) { mlt_log_error(filter, "Cannot set sink pixel formats\n"); goto fail; } ret = avfilter_init_str(pdata->avbuffsink_ctx, NULL); if (ret < 0) { mlt_log_error(filter, "Cannot init buffer sink\n"); goto fail; } // Initialize the filter context pdata->avfilter_ctx = avfilter_graph_alloc_filter(pdata->avfilter_graph, pdata->avfilter, pdata->avfilter->name); if (!pdata->avfilter_ctx) { mlt_log_error(filter, "Cannot create video filter\n"); goto fail; } set_avfilter_options(filter, resolution_scale); if (!strcmp("lut3d", pdata->avfilter->name)) { #if defined(__GLIBC__) || defined(__APPLE__) || (__FreeBSD__) // LUT data files use period for the decimal point regardless of LC_NUMERIC. mlt_locale_t posix_locale = newlocale(LC_NUMERIC_MASK, "POSIX", NULL); // Get the current locale and switch to POSIX local. mlt_locale_t orig_locale = uselocale(posix_locale); // Initialize the filter. ret = avfilter_init_str(pdata->avfilter_ctx, NULL); // Restore the original locale. uselocale(orig_locale); freelocale(posix_locale); #else // Get the current locale and switch to POSIX local. char *orig_localename = strdup(setlocale(LC_NUMERIC, NULL)); setlocale(LC_NUMERIC, "C"); // Initialize the filter. ret = avfilter_init_str(pdata->avfilter_ctx, NULL); // Restore the original locale. setlocale(LC_NUMERIC, orig_localename); free(orig_localename); #endif } else { ret = avfilter_init_str(pdata->avfilter_ctx, NULL); } if (ret < 0) { mlt_log_error(filter, "Cannot init scale filter: %s\n", av_err2str(ret)); goto fail; } // scale=w=1280:h=720:force_original_aspect_ratio=decrease, pad=w=1280:h=720:x=(ow-iw)/2:y=(oh-ih)/2 // Initialize the scale filter context pdata->scale_ctx = avfilter_graph_alloc_filter(pdata->avfilter_graph, scale, "scale"); if (!pdata->scale_ctx) { mlt_log_error(filter, "Cannot create scale filer\n"); goto fail; } mlt_properties_set_int(p, "w", width); mlt_properties_set_int(p, "h", height); const AVOption *opt = av_opt_find(pdata->scale_ctx->priv, "w", 0, 0, 0); if (opt) { ret = av_opt_set(pdata->scale_ctx->priv, opt->name, mlt_properties_get(p, "w"), 0); if (ret < 0) { mlt_log_error(filter, "Cannot set scale width\n"); goto fail; } } opt = av_opt_find(pdata->scale_ctx->priv, "h", 0, 0, 0); if (opt) { ret = av_opt_set(pdata->scale_ctx->priv, opt->name, mlt_properties_get(p, "h"), 0); if (ret < 0) { mlt_log_error(filter, "Cannot set scale height\n"); goto fail; } } ret = av_opt_set_int(pdata->scale_ctx, "force_original_aspect_ratio", 1, AV_OPT_SEARCH_CHILDREN); if (ret < 0) { mlt_log_error(filter, "Cannot set scale force_original_aspect_ratio\n"); goto fail; } opt = av_opt_find(pdata->scale_ctx->priv, "flags", 0, 0, 0); if (opt) { ret = av_opt_set(pdata->scale_ctx->priv, opt->name, MLT_AVFILTER_SWS_FLAGS, 0); if (ret < 0) { mlt_log_error(filter, "Cannot set scale flags\n"); goto fail; } } ret = avfilter_init_str(pdata->scale_ctx, NULL); if (ret < 0) { mlt_log_error(filter, "Cannot init scale filter\n"); goto fail; } // Initialize the padding filter context pdata->pad_ctx = avfilter_graph_alloc_filter(pdata->avfilter_graph, pad, "pad"); if (!pdata->pad_ctx) { mlt_log_error(filter, "Cannot create pad filter\n"); goto fail; } opt = av_opt_find(pdata->pad_ctx->priv, "w", 0, 0, 0); if (opt) { ret = av_opt_set(pdata->pad_ctx->priv, opt->name, mlt_properties_get(p, "w"), 0); if (ret < 0) { mlt_log_error(filter, "Cannot set pad width\n"); goto fail; } } opt = av_opt_find(pdata->pad_ctx->priv, "h", 0, 0, 0); if (opt) { ret = av_opt_set(pdata->pad_ctx->priv, opt->name, mlt_properties_get(p, "h"), 0); if (ret < 0) { mlt_log_error(filter, "Cannot pad scale height\n"); goto fail; } } opt = av_opt_find(pdata->pad_ctx->priv, "x", 0, 0, 0); if (opt) { ret = av_opt_set(pdata->pad_ctx->priv, opt->name, "(ow-iw)/2", 0); if (ret < 0) { mlt_log_error(filter, "Cannot set pad x\n"); goto fail; } } opt = av_opt_find(pdata->pad_ctx->priv, "y", 0, 0, 0); if (opt) { ret = av_opt_set(pdata->pad_ctx->priv, opt->name, "(oh-ih)/2", 0); if (ret < 0) { mlt_log_error(filter, "Cannot set pad y\n"); goto fail; } } ret = avfilter_init_str(pdata->pad_ctx, NULL); if (ret < 0) { mlt_log_error(filter, "Cannot init pad filter\n"); goto fail; } // Connect the filters ret = avfilter_link(pdata->avbuffsrc_ctx, 0, pdata->avfilter_ctx, 0); if (ret < 0) { mlt_log_error(filter, "Cannot link src to filter\n"); goto fail; } ret = avfilter_link(pdata->avfilter_ctx, 0, pdata->scale_ctx, 0); if (ret < 0) { mlt_log_error(filter, "Cannot link filter to scale\n"); goto fail; } ret = avfilter_link(pdata->scale_ctx, 0, pdata->pad_ctx, 0); if (ret < 0) { mlt_log_error(filter, "Cannot link scale to pad\n"); goto fail; } ret = avfilter_link(pdata->pad_ctx, 0, pdata->avbuffsink_ctx, 0); if (ret < 0) { mlt_log_error(filter, "Cannot link pad to sink\n"); goto fail; } // Configure the graph. ret = avfilter_graph_config(pdata->avfilter_graph, NULL); if (ret < 0) { mlt_log_error(filter, "Cannot configure the filter graph\n"); goto fail; } return; fail: mlt_properties_close(p); avfilter_graph_free(&pdata->avfilter_graph); } static mlt_position get_position(mlt_filter filter, mlt_frame frame) { mlt_position position = mlt_frame_get_position(frame); const char *pos_type = mlt_properties_get(MLT_FILTER_PROPERTIES(filter), "position"); if (pos_type) { if (!strcmp("filter", pos_type)) { position = mlt_filter_get_position(filter, frame); } else if (!strcmp("source", pos_type)) { position = mlt_frame_original_position(frame); } else if (!strcmp("producer", pos_type)) { mlt_producer producer = mlt_properties_get_data(MLT_FILTER_PROPERTIES(filter), "service", NULL); if (producer) position = mlt_producer_position(producer); } } else { private_data *pdata = (private_data *) filter->child; if (!strcmp("subtitles", pdata->avfilter->name)) position = mlt_frame_original_position(frame); } return position; } static int filter_get_audio(mlt_frame frame, void **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples) { mlt_filter filter = mlt_frame_pop_audio(frame); private_data *pdata = (private_data *) filter->child; double fps = mlt_profile_fps(mlt_service_profile(MLT_FILTER_SERVICE(filter))); int64_t samplepos = mlt_audio_calculate_samples_to_position(fps, *frequency, get_position(filter, frame)); int bufsize = 0; int ret; // Get the producer's audio mlt_frame_get_audio(frame, buffer, format, frequency, channels, samples); bufsize = mlt_audio_format_size(*format, *samples, *channels); mlt_service_lock(MLT_FILTER_SERVICE(filter)); if (pdata->reset || pdata->format != *format) { init_audio_filtergraph(filter, *format, *frequency, *channels); pdata->reset = 0; } if (pdata->avfilter_graph) { // Set up the input frame mlt_channel_layout layout = mlt_get_channel_layout_or_default(mlt_properties_get(MLT_FRAME_PROPERTIES(frame), "channel_layout"), *channels); pdata->avinframe->sample_rate = *frequency; pdata->avinframe->format = mlt_to_av_sample_format(*format); pdata->avinframe->channel_layout = mlt_to_av_channel_layout(layout); pdata->avinframe->channels = *channels; pdata->avinframe->nb_samples = *samples; pdata->avinframe->pts = samplepos; ret = av_frame_get_buffer(pdata->avinframe, 1); if (ret < 0) { mlt_log_error(filter, "Cannot get in frame buffer\n"); } if (av_sample_fmt_is_planar(pdata->avinframe->format)) { int i = 0; int stride = bufsize / *channels; for (i = 0; i < *channels; i++) { memcpy(pdata->avinframe->extended_data[i], (uint8_t *) *buffer + stride * i, stride); } } else { memcpy(pdata->avinframe->extended_data[0], (uint8_t *) *buffer, bufsize); } send_avformat_commands(filter, frame, pdata, 1.0); // Run the frame through the filter graph ret = av_buffersrc_add_frame(pdata->avbuffsrc_ctx, pdata->avinframe); if (ret < 0) { mlt_log_error(filter, "Cannot add frame to buffer source\n"); } ret = av_buffersink_get_frame(pdata->avbuffsink_ctx, pdata->avoutframe); if (ret < 0) { mlt_log_error(filter, "Cannot get frame from buffer sink\n"); } // Sanity check the output frame if (*channels != pdata->avoutframe->channels || *samples != pdata->avoutframe->nb_samples || *frequency != pdata->avoutframe->sample_rate) { mlt_log_error(filter, "Unexpected return format\n"); goto exit; } // Copy the filter output into the original buffer if (av_sample_fmt_is_planar(pdata->avoutframe->format)) { int stride = bufsize / *channels; int i = 0; for (i = 0; i < *channels; i++) { memcpy((uint8_t *) *buffer + stride * i, pdata->avoutframe->extended_data[i], stride); } } else { memcpy((uint8_t *) *buffer, pdata->avoutframe->extended_data[0], bufsize); } } exit: av_frame_unref(pdata->avinframe); av_frame_unref(pdata->avoutframe); mlt_service_unlock(MLT_FILTER_SERVICE(filter)); return 0; } static int filter_get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { mlt_filter filter = mlt_frame_pop_service(frame); private_data *pdata = (private_data *) filter->child; int64_t pos = get_position(filter, frame); mlt_profile profile = mlt_service_profile(MLT_FILTER_SERVICE(filter)); mlt_properties frame_properties = MLT_FRAME_PROPERTIES(frame); int ret; mlt_log_debug(MLT_FILTER_SERVICE(filter), "position %" PRId64 "\n", pos); if (mlt_properties_get_int(MLT_FILTER_PROPERTIES(filter), "_yuv_only")) { *format = mlt_image_yuv422; } else { *format = mlt_get_supported_image_format(*format); } mlt_frame_get_image(frame, image, format, width, height, 0); mlt_service_lock(MLT_FILTER_SERVICE(filter)); double scale = mlt_profile_scale_width(profile, *width); if (pdata->reset || pdata->format != *format || pdata->width != *width || pdata->height != *height) { init_image_filtergraph(filter, *format, *width, *height, scale); pdata->reset = 0; } if (pdata->avfilter_graph) { pdata->avinframe->width = *width; pdata->avinframe->height = *height; pdata->avinframe->format = mlt_to_av_image_format(*format); pdata->avinframe->sample_aspect_ratio = (AVRational){profile->sample_aspect_num, profile->sample_aspect_den}; pdata->avinframe->pts = pos; pdata->avinframe->interlaced_frame = !mlt_properties_get_int(frame_properties, "progressive"); pdata->avinframe->top_field_first = mlt_properties_get_int(frame_properties, "top_field_first"); pdata->avinframe->color_primaries = mlt_properties_get_int(frame_properties, "color_primaries"); pdata->avinframe->color_trc = mlt_properties_get_int(frame_properties, "color_trc"); pdata->avinframe->color_range = mlt_properties_get_int(frame_properties, "full_range") ? AVCOL_RANGE_JPEG : AVCOL_RANGE_MPEG; switch (mlt_properties_get_int(frame_properties, "colorspace")) { case 240: pdata->avinframe->colorspace = AVCOL_SPC_SMPTE240M; break; case 601: pdata->avinframe->colorspace = AVCOL_SPC_BT470BG; break; case 709: pdata->avinframe->colorspace = AVCOL_SPC_BT709; break; case 2020: pdata->avinframe->colorspace = AVCOL_SPC_BT2020_NCL; break; case 2021: pdata->avinframe->colorspace = AVCOL_SPC_BT2020_CL; break; } ret = av_frame_get_buffer(pdata->avinframe, 1); if (ret < 0) { mlt_log_error(filter, "Cannot get in frame buffer\n"); } // Set up the input frame if (*format == mlt_image_yuv420p) { int i = 0; int p = 0; int widths[3] = {*width, *width / 2, *width / 2}; int heights[3] = {*height, *height / 2, *height / 2}; uint8_t *src = *image; for (p = 0; p < 3; p++) { uint8_t *dst = pdata->avinframe->data[p]; for (i = 0; i < heights[p]; i++) { memcpy(dst, src, widths[p]); src += widths[p]; dst += pdata->avinframe->linesize[p]; } } } else { int i; uint8_t *src = *image; uint8_t *dst = pdata->avinframe->data[0]; int stride = mlt_image_format_size(*format, *width, 1, NULL); for (i = 0; i < *height; i++) { memcpy(dst, src, stride); src += stride; dst += pdata->avinframe->linesize[0]; } } send_avformat_commands(filter, frame, pdata, scale); // Run the frame through the filter graph ret = av_buffersrc_add_frame(pdata->avbuffsrc_ctx, pdata->avinframe); if (ret < 0) { mlt_log_error(filter, "Cannot add frame to buffer source\n"); } ret = av_buffersink_get_frame(pdata->avbuffsink_ctx, pdata->avoutframe); if (ret < 0) { mlt_log_error(filter, "Cannot get frame from buffer sink\n"); } // Sanity check the output frame if (*width != pdata->avoutframe->width || *height != pdata->avoutframe->height) { mlt_log_error(filter, "Unexpected return format\n"); goto exit; } // Copy the filter output into the original buffer if (*format == mlt_image_yuv420p) { int i = 0; int p = 0; int widths[3] = {*width, *width / 2, *width / 2}; int heights[3] = {*height, *height / 2, *height / 2}; uint8_t *dst = *image; for (p = 0; p < 3; p++) { uint8_t *src = pdata->avoutframe->data[p]; for (i = 0; i < heights[p]; i++) { memcpy(dst, src, widths[p]); dst += widths[p]; src += pdata->avoutframe->linesize[p]; } } } else { int i; uint8_t *dst = *image; uint8_t *src = pdata->avoutframe->data[0]; int stride = mlt_image_format_size(*format, *width, 1, NULL); for (i = 0; i < *height; i++) { memcpy(dst, src, stride); dst += stride; src += pdata->avoutframe->linesize[0]; } } } exit: av_frame_unref(pdata->avinframe); av_frame_unref(pdata->avoutframe); mlt_service_unlock(MLT_FILTER_SERVICE(filter)); return 0; } /** Filter processing. */ static mlt_frame filter_process(mlt_filter filter, mlt_frame frame) { private_data *pdata = (private_data *) filter->child; if (avfilter_pad_get_type(pdata->avfilter->inputs, 0) == AVMEDIA_TYPE_VIDEO) { mlt_frame_push_service(frame, filter); mlt_frame_push_get_image(frame, filter_get_image); } else if (avfilter_pad_get_type(pdata->avfilter->inputs, 0) == AVMEDIA_TYPE_AUDIO) { mlt_frame_push_audio(frame, filter); mlt_frame_push_audio(frame, filter_get_audio); } return frame; } /** Destructor for the filter. */ static void filter_close(mlt_filter filter) { private_data *pdata = (private_data *) filter->child; if (pdata) { avfilter_graph_free(&pdata->avfilter_graph); av_frame_free(&pdata->avinframe); av_frame_free(&pdata->avoutframe); free(pdata); } filter->child = NULL; filter->close = NULL; filter->parent.close = NULL; mlt_service_close(&filter->parent); } /** Constructor for the filter. */ mlt_filter filter_avfilter_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = mlt_filter_new(); private_data *pdata = (private_data *) calloc(1, sizeof(private_data)); if (pdata && id) { id += 9; // Move past "avfilter." pdata->avfilter = (AVFilter *) avfilter_get_by_name(id); } if (filter && pdata && pdata->avfilter) { pdata->avbuffsink_ctx = NULL; pdata->avbuffsrc_ctx = NULL; pdata->avfilter_ctx = NULL; pdata->avfilter_graph = NULL; pdata->avinframe = av_frame_alloc(); pdata->avoutframe = av_frame_alloc(); pdata->format = -1; pdata->width = -1; pdata->height = -1; pdata->reset = 1; filter->close = filter_close; filter->process = filter_process; filter->child = pdata; mlt_events_listen(MLT_FILTER_PROPERTIES(filter), filter, "property-changed", (mlt_listener) property_changed); mlt_properties param_name_map = mlt_properties_get_data(mlt_global_properties(), "avfilter.resolution_scale", NULL); if (param_name_map) { // Lookup my plugin in the map param_name_map = mlt_properties_get_data(param_name_map, id, NULL); mlt_properties_set_data(MLT_FILTER_PROPERTIES(filter), "_resolution_scale", param_name_map, 0, NULL, NULL); } mlt_properties yuv_only = mlt_properties_get_data(mlt_global_properties(), "avfilter.yuv_only", NULL); if (yuv_only) { if (mlt_properties_get(yuv_only, id)) { mlt_properties_set_int(MLT_FILTER_PROPERTIES(filter), "_yuv_only", 1); } } } else { mlt_filter_close(filter); free(pdata); } return filter; } mlt-7.22.0/src/modules/avformat/filter_swresample.c000664 000000 000000 00000016506 14531534050 022337 0ustar00rootroot000000 000000 /* * filter_swresample.c -- convert from one format/ configuration to another * Copyright (C) 2018-2022 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "common.h" #include "common_swr.h" #include #include #include #include #include static int filter_get_audio(mlt_frame frame, void **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples) { int requested_samples = *samples; mlt_filter filter = mlt_frame_pop_audio(frame); mlt_swr_private_data *pdata = (mlt_swr_private_data *) filter->child; mlt_properties frame_properties = MLT_FRAME_PROPERTIES(frame); struct mlt_audio_s in; struct mlt_audio_s out; mlt_audio_set_values(&in, *buffer, *frequency, *format, *samples, *channels); mlt_audio_set_values(&out, NULL, *frequency, *format, *samples, *channels); // Get the producer's audio int error = mlt_frame_get_audio(frame, &in.data, &in.format, &in.frequency, &in.channels, &in.samples); if (error || in.format == mlt_audio_none || out.format == mlt_audio_none || in.frequency <= 0 || out.frequency <= 0 || in.channels <= 0 || out.channels <= 0) { // Error situation. Do not attempt to convert. mlt_audio_get_values(&in, buffer, frequency, format, samples, channels); mlt_log_error(MLT_FILTER_SERVICE(filter), "Invalid Parameters: %dS - %dHz %dC %s -> %dHz %dC %s\n", in.samples, in.frequency, in.channels, mlt_audio_format_name(in.format), out.frequency, out.channels, mlt_audio_format_name(out.format)); return error; } if (in.samples == 0) { // Noting to convert. return error; } // Determine the input/output channel layout. in.layout = mlt_get_channel_layout_or_default(mlt_properties_get(frame_properties, "channel_layout"), in.channels); out.layout = mlt_get_channel_layout_or_default(mlt_properties_get(frame_properties, "consumer.channel_layout"), out.channels); if (in.format == out.format && in.frequency == out.frequency && in.channels == out.channels && in.layout == out.layout) { // No change necessary mlt_audio_get_values(&in, buffer, frequency, format, samples, channels); return error; } mlt_service_lock(MLT_FILTER_SERVICE(filter)); // Detect configuration change if (!pdata->ctx || pdata->in_format != in.format || pdata->out_format != out.format || pdata->in_frequency != in.frequency || pdata->out_frequency != out.frequency || pdata->in_channels != in.channels || pdata->out_channels != out.channels || pdata->in_layout != in.layout || pdata->out_layout != out.layout) { // Save the configuration pdata->in_format = in.format; pdata->out_format = out.format; pdata->in_frequency = in.frequency; pdata->out_frequency = out.frequency; pdata->in_channels = in.channels; pdata->out_channels = out.channels; pdata->in_layout = in.layout; pdata->out_layout = out.layout; // Reconfigure the context error = mlt_configure_swr_context(MLT_FILTER_SERVICE(filter), pdata); } if (!error) { out.samples = requested_samples; mlt_audio_alloc_data(&out); mlt_audio_get_planes(&in, pdata->in_buffers); mlt_audio_get_planes(&out, pdata->out_buffers); int received_samples = swr_convert(pdata->ctx, pdata->out_buffers, out.samples, (const uint8_t **) pdata->in_buffers, in.samples); if (received_samples >= 0) { if (received_samples == 0) { mlt_log_info(MLT_FILTER_SERVICE(filter), "Precharge required - return silence\n"); mlt_audio_silence(&out, out.samples, 0); } else if (received_samples < requested_samples) { // Duplicate samples to return the exact number requested. mlt_audio_copy(&out, &out, received_samples, 0, requested_samples - received_samples); } else if (received_samples > requested_samples) { // Discard samples to return the exact number requested. mlt_audio_shrink(&out, requested_samples); } mlt_frame_set_audio(frame, out.data, out.format, 0, out.release_data); mlt_audio_get_values(&out, buffer, frequency, format, samples, channels); mlt_properties_set(frame_properties, "channel_layout", mlt_audio_channel_layout_name(out.layout)); } else { mlt_log_error(MLT_FILTER_SERVICE(filter), "swr_convert() failed. Alloc: %d\tIn: %d\tOut: %d\n", out.samples, in.samples, received_samples); out.release_data(out.data); error = 1; } } mlt_service_unlock(MLT_FILTER_SERVICE(filter)); return error; } static mlt_frame filter_process(mlt_filter filter, mlt_frame frame) { mlt_frame_push_audio(frame, filter); mlt_frame_push_audio(frame, filter_get_audio); return frame; } static void filter_close(mlt_filter filter) { mlt_swr_private_data *pdata = (mlt_swr_private_data *) filter->child; if (pdata) { mlt_free_swr_context(pdata); free(pdata); } filter->child = NULL; filter->close = NULL; filter->parent.close = NULL; mlt_service_close(&filter->parent); } mlt_filter filter_swresample_init(mlt_profile profile, char *arg) { mlt_filter filter = mlt_filter_new(); mlt_swr_private_data *pdata = (mlt_swr_private_data *) calloc(1, sizeof(mlt_swr_private_data)); if (filter && pdata) { memset(pdata, 0, sizeof(*pdata)); filter->close = filter_close; filter->process = filter_process; filter->child = pdata; } else { mlt_filter_close(filter); free(pdata); } return filter; } mlt-7.22.0/src/modules/avformat/filter_swresample.yml000664 000000 000000 00000000702 14531534050 022705 0ustar00rootroot000000 000000 schema_version: 7.0 type: filter identifier: swresample title: FFmpeg Audio Resampler version: 1 copyright: Meltytech, LLC license: LGPLv2.1 language: en url: http://www.ffmpeg.org/ tags: - Audio - Hidden description: Converts the audio sample rate. notes: > This is not intended to be created directly. Rather, the loader producer loads it if it is available to set the audio resampler to normalize inputs to the consumer audio frequency. mlt-7.22.0/src/modules/avformat/filter_swscale.c000664 000000 000000 00000031001 14531534050 021601 0ustar00rootroot000000 000000 /* * filter_swscale.c -- image scaling filter * Copyright (C) 2008-2022 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include // ffmpeg Header files #include #include #include #include #include #include #include #define MAX_THREADS (6) static inline int convert_mlt_to_av_cs(mlt_image_format format) { int value = 0; switch (format) { case mlt_image_rgb: value = AV_PIX_FMT_RGB24; break; case mlt_image_rgba: value = AV_PIX_FMT_RGBA; break; case mlt_image_yuv422: value = AV_PIX_FMT_YUYV422; break; case mlt_image_yuv420p: value = AV_PIX_FMT_YUV420P; break; default: mlt_log_error(NULL, "[filter swscale] Invalid format %s\n", mlt_image_format_name(format)); break; } return value; } static int filter_scale(mlt_frame frame, uint8_t **image, mlt_image_format *format, int iwidth, int iheight, int owidth, int oheight) { int result = 0; // Get the properties mlt_properties properties = MLT_FRAME_PROPERTIES(frame); // Get the requested interpolation method char *interps = mlt_properties_get(properties, "consumer.rescale"); // Convert to the SwScale flag int interp = SWS_BILINEAR; if (strcmp(interps, "nearest") == 0 || strcmp(interps, "neighbor") == 0) interp = SWS_POINT; else if (strcmp(interps, "tiles") == 0 || strcmp(interps, "fast_bilinear") == 0) interp = SWS_FAST_BILINEAR; else if (strcmp(interps, "bilinear") == 0) interp = SWS_BILINEAR; else if (strcmp(interps, "bicubic") == 0) interp = SWS_BICUBIC; else if (strcmp(interps, "bicublin") == 0) interp = SWS_BICUBLIN; else if (strcmp(interps, "gauss") == 0) interp = SWS_GAUSS; else if (strcmp(interps, "sinc") == 0) interp = SWS_SINC; else if (strcmp(interps, "hyper") == 0 || strcmp(interps, "lanczos") == 0) interp = SWS_LANCZOS; else if (strcmp(interps, "spline") == 0) interp = SWS_SPLINE; // Set swscale flags to get good quality interp |= SWS_FULL_CHR_H_INP | SWS_FULL_CHR_H_INT | SWS_ACCURATE_RND; // Convert the pixel formats int avformat = convert_mlt_to_av_cs(*format); // Determine the output image size. int out_size = mlt_image_format_size(*format, owidth, oheight, NULL); uint8_t *outbuf = mlt_pool_alloc(out_size); // Create the context struct SwsContext *context = sws_alloc_context(); if (outbuf && context) { AVFrame *avinframe = av_frame_alloc(); AVFrame *avoutframe = av_frame_alloc(); av_opt_set_int(context, "srcw", iwidth, 0); av_opt_set_int(context, "srch", iheight, 0); av_opt_set_int(context, "src_format", avformat, 0); av_opt_set_int(context, "dstw", owidth, 0); av_opt_set_int(context, "dsth", oheight, 0); av_opt_set_int(context, "dst_format", avformat, 0); av_opt_set_int(context, "sws_flags", interp, 0); #if LIBSWSCALE_VERSION_MAJOR >= 6 av_opt_set_int(context, "threads", MIN(mlt_slices_count_normal(), MAX_THREADS), 0); #endif result = sws_init_context(context, NULL, NULL); if (result < 0) { mlt_log_error(NULL, "[filter swscale] Initializing swscale failed with %d (%s)\n", result, av_err2str(result)); result = 1; goto exit; } // Setup the input image avinframe->width = iwidth; avinframe->height = iheight; avinframe->format = avformat; avinframe->sample_aspect_ratio = av_d2q(mlt_frame_get_aspect_ratio(frame), 1024); avinframe->interlaced_frame = !mlt_properties_get_int(properties, "progressive"); avinframe->top_field_first = mlt_properties_get_int(properties, "top_field_first"); av_image_fill_arrays(avinframe->data, avinframe->linesize, *image, avinframe->format, iwidth, iheight, 1); // Setup the output image av_frame_copy_props(avoutframe, avinframe); avoutframe->width = owidth; avoutframe->height = oheight; avoutframe->format = avformat; result = av_frame_get_buffer(avoutframe, 0); if (result < 0) { mlt_log_error(NULL, "[filter swscale] Cannot allocate output frame buffer\n"); result = 1; goto exit; } // Perform the scaling #if LIBSWSCALE_VERSION_MAJOR >= 6 result = sws_scale_frame(context, avoutframe, avinframe); #else result = sws_scale(context, (const uint8_t **) avinframe->data, avinframe->linesize, 0, iheight, avoutframe->data, avoutframe->linesize); #endif if (result < 0) { mlt_log_error(NULL, "[filter swscale] sws_scale_frame failed with %d (%s)\n", result, av_err2str(result)); result = 1; goto exit; } sws_freeContext(context); context = NULL; // Sanity check the output frame if (owidth != avoutframe->width || oheight != avoutframe->height) { mlt_log_error(NULL, "[filter swscale] Unexpected output size\n"); result = 1; goto exit; } // Copy the filter output into the output buffer if (*format == mlt_image_yuv420p) { int i = 0; int p = 0; int widths[3] = {owidth, owidth / 2, owidth / 2}; int heights[3] = {oheight, oheight / 2, oheight / 2}; uint8_t *dst = outbuf; for (p = 0; p < 3; p++) { uint8_t *src = avoutframe->data[p]; for (i = 0; i < heights[p]; i++) { memcpy(dst, src, widths[p]); dst += widths[p]; src += avoutframe->linesize[p]; } } } else { int i; uint8_t *dst = outbuf; uint8_t *src = avoutframe->data[0]; int stride = mlt_image_format_size(*format, owidth, 1, NULL); for (i = 0; i < oheight; i++) { memcpy(dst, src, stride); dst += stride; src += avoutframe->linesize[0]; } } // Now update the MLT frame mlt_frame_set_image(frame, outbuf, out_size, mlt_pool_release); // Return the output image *image = outbuf; // Scale the alpha channel only if exists and not correct size int alpha_size = 0; uint8_t *alpha = mlt_frame_get_alpha_size(frame, &alpha_size); if (alpha && alpha_size > 0 && alpha_size != (owidth * oheight)) { // Create the context and output image outbuf = mlt_pool_alloc(owidth * oheight); context = sws_alloc_context(); if (outbuf && context) { av_frame_unref(avinframe); av_frame_unref(avoutframe); avformat = AV_PIX_FMT_GRAY8; av_opt_set_int(context, "srcw", iwidth, 0); av_opt_set_int(context, "srch", iheight, 0); av_opt_set_int(context, "src_format", avformat, 0); av_opt_set_int(context, "dstw", owidth, 0); av_opt_set_int(context, "dsth", oheight, 0); av_opt_set_int(context, "dst_format", avformat, 0); av_opt_set_int(context, "sws_flags", interp, 0); #if LIBSWSCALE_VERSION_MAJOR >= 6 av_opt_set_int(context, "threads", MIN(mlt_slices_count_normal(), MAX_THREADS), 0); #endif result = sws_init_context(context, NULL, NULL); if (result < 0) { mlt_log_error( NULL, "[filter swscale] Initializing swscale alpha failed with %d (%s)\n", result, av_err2str(result)); result = 1; goto exit; } // Setup the input image avinframe->width = iwidth; avinframe->height = iheight; avinframe->format = avformat; avinframe->data[0] = alpha; avinframe->linesize[0] = iwidth; // Setup the output image avoutframe->width = owidth; avoutframe->height = oheight; avoutframe->format = avformat; result = av_frame_get_buffer(avoutframe, 0); if (result < 0) { mlt_log_error(NULL, "[filter swscale] Cannot allocate alpha frame buffer\n"); result = 1; goto exit; } // Perform the scaling #if LIBSWSCALE_VERSION_MAJOR >= 6 result = sws_scale_frame(context, avoutframe, avinframe); #else result = sws_scale(context, (const uint8_t **) avinframe->data, avinframe->linesize, 0, iheight, avoutframe->data, avoutframe->linesize); #endif if (result < 0) { mlt_log_error( NULL, "[filter swscale] sws_scale_frame alpha failed with %d (%s) %d %d\n", result, av_err2str(result), avoutframe->width, avoutframe->height); result = 1; goto exit; } sws_freeContext(context); context = NULL; // Sanity check the output frame if (owidth != avoutframe->width || oheight != avoutframe->height) { mlt_log_error(NULL, "[filter swscale] Unexpected output alpha size\n"); result = 1; goto exit; } int i; uint8_t *dst = outbuf; uint8_t *src = avoutframe->data[0]; for (i = 0; i < oheight; i++) { memcpy(dst, src, owidth); dst += owidth; src += avoutframe->linesize[0]; } // Set it back on the frame mlt_frame_set_alpha(frame, outbuf, owidth * oheight, mlt_pool_release); } } exit: av_frame_free(&avinframe); av_frame_free(&avoutframe); sws_freeContext(context); } return result; } /** Constructor for the filter. */ mlt_filter filter_swscale_init(mlt_profile profile, void *arg) { // Create a new scaler mlt_filter filter = mlt_factory_filter(profile, "rescale", NULL); // If successful, then initialise it if (filter != NULL) { // Get the properties mlt_properties properties = MLT_FILTER_PROPERTIES(filter); // Set the inerpolation mlt_properties_set(properties, "interpolation", "bilinear"); // Set the method mlt_properties_set_data(properties, "method", filter_scale, 0, NULL, NULL); } return filter; } mlt-7.22.0/src/modules/avformat/filter_swscale.yml000664 000000 000000 00000000666 14531534050 022175 0ustar00rootroot000000 000000 schema_version: 7.0 type: filter identifier: swscale title: FFmpeg Image Scaler version: 1 copyright: Meltytech, LLC license: LGPLv2.1 language: en url: http://www.ffmpeg.org/ tags: - Video - Hidden description: Change the resolution of an image. notes: > This is not intended to be created directly. Rather, the rescale filter loads it if it is available to normalize video and image inputs to the consumer/profile resolution. mlt-7.22.0/src/modules/avformat/link_avdeinterlace.c000664 000000 000000 00000054331 14531534050 022431 0ustar00rootroot000000 000000 /* * link_avdeinterlace.c * Copyright (C) 2023 Meltytech, LLC * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "common.h" #include #include #include #include #include #include #include #include // Private Types #define FUTURE_FRAMES 1 typedef struct { mlt_position expected_frame; mlt_position continuity_frame; mlt_deinterlacer method; int informat; int outformat; int width; int height; } private_data; typedef struct { AVFilterContext *avbuffsink_ctx; AVFilterContext *avbuffsrc_ctx; AVFilterGraph *avfilter_graph; AVFrame *avinframe; AVFrame *avoutframe; } filter_data; static void destroy_filter_data(filter_data *fdata) { if (fdata) { avfilter_graph_free(&fdata->avfilter_graph); av_frame_free(&fdata->avinframe); av_frame_free(&fdata->avoutframe); free(fdata); } } static mlt_image_format validate_format(mlt_image_format format) { mlt_image_format ret_format = mlt_image_yuv422; switch (format) { case mlt_image_rgb: case mlt_image_rgba: case mlt_image_yuv422: case mlt_image_yuv420p: case mlt_image_yuv422p16: case mlt_image_yuv420p10: case mlt_image_yuv444p10: ret_format = format; break; case mlt_image_none: case mlt_image_movit: case mlt_image_opengl_texture: case mlt_image_invalid: ret_format = mlt_image_yuv422; break; } return ret_format; } static void init_image_filtergraph(mlt_link self, AVRational sar) { private_data *pdata = (private_data *) self->child; filter_data *fdata = (filter_data *) calloc(1, sizeof(filter_data)); mlt_profile profile = mlt_service_profile(MLT_LINK_SERVICE(self)); const AVFilter *buffersrc = avfilter_get_by_name("buffer"); const AVFilter *buffersink = avfilter_get_by_name("buffersink"); enum AVPixelFormat in_pixel_fmts[] = {-1, -1}; enum AVPixelFormat out_pixel_fmts[] = {-1, -1}; AVRational timebase = (AVRational){profile->frame_rate_den, profile->frame_rate_num}; AVRational framerate = (AVRational){profile->frame_rate_num, profile->frame_rate_den}; AVFilterContext *prev_ctx = NULL; AVFilterContext *avfilter_ctx = NULL; int ret; fdata->avinframe = av_frame_alloc(); fdata->avoutframe = av_frame_alloc(); // Set up formats in_pixel_fmts[0] = mlt_to_av_image_format(pdata->informat); out_pixel_fmts[0] = mlt_to_av_image_format(pdata->outformat); // Create the new filter graph fdata->avfilter_graph = avfilter_graph_alloc(); if (!fdata->avfilter_graph) { mlt_log_error(self, "Cannot create filter graph\n"); goto fail; } fdata->avfilter_graph->scale_sws_opts = av_strdup("flags=" MLT_AVFILTER_SWS_FLAGS); // Initialize the buffer source filter context fdata->avbuffsrc_ctx = avfilter_graph_alloc_filter(fdata->avfilter_graph, buffersrc, "in"); if (!fdata->avbuffsrc_ctx) { mlt_log_error(self, "Cannot create image buffer source\n"); goto fail; } ret = av_opt_set_int(fdata->avbuffsrc_ctx, "width", pdata->width, AV_OPT_SEARCH_CHILDREN); if (ret < 0) { mlt_log_error(self, "Cannot set src width %d\n", pdata->width); goto fail; } ret = av_opt_set_int(fdata->avbuffsrc_ctx, "height", pdata->height, AV_OPT_SEARCH_CHILDREN); if (ret < 0) { mlt_log_error(self, "Cannot set src height %d\n", pdata->height); goto fail; } ret = av_opt_set_pixel_fmt(fdata->avbuffsrc_ctx, "pix_fmt", in_pixel_fmts[0], AV_OPT_SEARCH_CHILDREN); if (ret < 0) { mlt_log_error(self, "Cannot set src pixel format %d\n", in_pixel_fmts[0]); goto fail; } ret = av_opt_set_q(fdata->avbuffsrc_ctx, "sar", sar, AV_OPT_SEARCH_CHILDREN); if (ret < 0) { mlt_log_error(self, "Cannot set src sar %d/%d\n", sar.num, sar.den); goto fail; } ret = av_opt_set_q(fdata->avbuffsrc_ctx, "time_base", timebase, AV_OPT_SEARCH_CHILDREN); if (ret < 0) { mlt_log_error(self, "Cannot set src time_base %d/%d\n", timebase.num, timebase.den); goto fail; } ret = av_opt_set_q(fdata->avbuffsrc_ctx, "frame_rate", framerate, AV_OPT_SEARCH_CHILDREN); if (ret < 0) { mlt_log_error(self, "Cannot set src frame_rate %d/%d\n", framerate.num, framerate.den); goto fail; } ret = avfilter_init_str(fdata->avbuffsrc_ctx, NULL); if (ret < 0) { mlt_log_error(self, "Cannot init buffer source\n"); goto fail; } prev_ctx = fdata->avbuffsrc_ctx; // Initialize the deinterlace filter context if (pdata->method <= mlt_deinterlacer_onefield) { const AVFilter *deint = (AVFilter *) avfilter_get_by_name("field"); avfilter_ctx = avfilter_graph_alloc_filter(fdata->avfilter_graph, deint, deint->name); if (!avfilter_ctx) { mlt_log_error(self, "Cannot create field filter\n"); goto fail; } ret = avfilter_init_str(avfilter_ctx, NULL); if (ret < 0) { mlt_log_error(self, "Cannot init filter: %s\n", av_err2str(ret)); goto fail; } ret = avfilter_link(prev_ctx, 0, avfilter_ctx, 0); if (ret < 0) { mlt_log_error(self, "Cannot link field filter\n"); goto fail; } prev_ctx = avfilter_ctx; const AVFilter *scale = (AVFilter *) avfilter_get_by_name("scale"); avfilter_ctx = avfilter_graph_alloc_filter(fdata->avfilter_graph, scale, scale->name); if (!avfilter_ctx) { mlt_log_error(self, "Cannot create scale filter\n"); goto fail; } ret = avfilter_init_str(avfilter_ctx, "w=iw:h=2*ih"); if (ret < 0) { mlt_log_error(self, "Cannot init scale filter: %s\n", av_err2str(ret)); goto fail; } ret = avfilter_link(prev_ctx, 0, avfilter_ctx, 0); if (ret < 0) { mlt_log_error(self, "Cannot link scale filter\n"); goto fail; } prev_ctx = avfilter_ctx; } else if (pdata->method <= mlt_deinterlacer_linearblend) { const AVFilter *deint = (AVFilter *) avfilter_get_by_name("pp"); avfilter_ctx = avfilter_graph_alloc_filter(fdata->avfilter_graph, deint, deint->name); if (!avfilter_ctx) { mlt_log_error(self, "Cannot create video filter\n"); goto fail; } ret = avfilter_init_str(avfilter_ctx, "subfilters=lb"); if (ret < 0) { mlt_log_error(self, "Cannot init filter: %s\n", av_err2str(ret)); goto fail; } ret = avfilter_link(prev_ctx, 0, avfilter_ctx, 0); if (ret < 0) { mlt_log_error(self, "Cannot link deinterlace filter\n"); goto fail; } prev_ctx = avfilter_ctx; } else if (pdata->method <= mlt_deinterlacer_yadif_nospatial) { const AVFilter *deint = (AVFilter *) avfilter_get_by_name("yadif"); avfilter_ctx = avfilter_graph_alloc_filter(fdata->avfilter_graph, deint, deint->name); if (!avfilter_ctx) { mlt_log_error(self, "Cannot create yadif filter\n"); goto fail; } ret = avfilter_init_str(avfilter_ctx, "mode=send_frame_nospatial:parity=auto:deint=all"); if (ret < 0) { mlt_log_error(self, "Cannot init yadif filter: %s\n", av_err2str(ret)); goto fail; } ret = avfilter_link(prev_ctx, 0, avfilter_ctx, 0); if (ret < 0) { mlt_log_error(self, "Cannot link yadif filter\n"); goto fail; } prev_ctx = avfilter_ctx; } else if (pdata->method <= mlt_deinterlacer_yadif) { const AVFilter *deint = (AVFilter *) avfilter_get_by_name("yadif"); avfilter_ctx = avfilter_graph_alloc_filter(fdata->avfilter_graph, deint, deint->name); if (!avfilter_ctx) { mlt_log_error(self, "Cannot create yadif filter\n"); goto fail; } ret = avfilter_init_str(avfilter_ctx, "mode=send_frame:parity=auto:deint=all"); if (ret < 0) { mlt_log_error(self, "Cannot init yadif filter: %s\n", av_err2str(ret)); goto fail; } ret = avfilter_link(prev_ctx, 0, avfilter_ctx, 0); if (ret < 0) { mlt_log_error(self, "Cannot link yadif filter\n"); goto fail; } prev_ctx = avfilter_ctx; } else if (pdata->method <= mlt_deinterlacer_bwdif) { const AVFilter *deint = (AVFilter *) avfilter_get_by_name("bwdif"); avfilter_ctx = avfilter_graph_alloc_filter(fdata->avfilter_graph, deint, deint->name); if (!avfilter_ctx) { mlt_log_error(self, "Cannot create bwdif filter\n"); goto fail; } ret = avfilter_init_str(avfilter_ctx, "mode=send_frame:parity=auto:deint=all"); if (ret < 0) { mlt_log_error(self, "Cannot init bwdif filter: %s\n", av_err2str(ret)); goto fail; } ret = avfilter_link(prev_ctx, 0, avfilter_ctx, 0); if (ret < 0) { mlt_log_error(self, "Cannot link bwdif filter\n"); goto fail; } prev_ctx = avfilter_ctx; } else { const AVFilter *deint = (AVFilter *) avfilter_get_by_name("estdif"); avfilter_ctx = avfilter_graph_alloc_filter(fdata->avfilter_graph, deint, deint->name); if (!avfilter_ctx) { mlt_log_error(self, "Cannot create estdif filter\n"); goto fail; } ret = avfilter_init_str(avfilter_ctx, "mode=frame:parity=auto:deint=all"); if (ret < 0) { mlt_log_error(self, "Cannot init estdif filter: %s\n", av_err2str(ret)); goto fail; } ret = avfilter_link(prev_ctx, 0, avfilter_ctx, 0); if (ret < 0) { mlt_log_error(self, "Cannot link estdif filter\n"); goto fail; } prev_ctx = avfilter_ctx; } // Initialize the buffer sink filter context fdata->avbuffsink_ctx = avfilter_graph_alloc_filter(fdata->avfilter_graph, buffersink, "out"); if (!fdata->avbuffsink_ctx) { mlt_log_error(self, "Cannot create image buffer sink\n"); goto fail; } ret = av_opt_set_int_list(fdata->avbuffsink_ctx, "pix_fmts", out_pixel_fmts, -1, AV_OPT_SEARCH_CHILDREN); if (ret < 0) { mlt_log_error(self, "Cannot set sink pixel formats %d\n", out_pixel_fmts[0]); goto fail; } ret = avfilter_init_str(fdata->avbuffsink_ctx, NULL); if (ret < 0) { mlt_log_error(self, "Cannot init buffer sink\n"); goto fail; } ret = avfilter_link(prev_ctx, 0, fdata->avbuffsink_ctx, 0); if (ret < 0) { mlt_log_error(self, "Cannot link filter to sink\n"); goto fail; } // Configure the graph. ret = avfilter_graph_config(fdata->avfilter_graph, NULL); if (ret < 0) { mlt_log_error(self, "Cannot configure the filter graph\n"); goto fail; } mlt_service_cache_put(MLT_LINK_SERVICE(self), "link_avdeinterlace", fdata, 0, (mlt_destructor) destroy_filter_data); return; fail: destroy_filter_data(fdata); } static void link_configure(mlt_link self, mlt_profile chain_profile) { // Operate at the same frame rate as the next link mlt_service_set_profile(MLT_LINK_SERVICE(self), mlt_service_profile(MLT_PRODUCER_SERVICE(self->next))); } static int link_get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { mlt_link self = (mlt_link) mlt_frame_pop_service(frame); private_data *pdata = (private_data *) self->child; mlt_properties frame_properties = MLT_FRAME_PROPERTIES(frame); mlt_deinterlacer method = mlt_deinterlacer_id( mlt_properties_get(frame_properties, "consumer.deinterlacer")); if (!mlt_properties_get_int(frame_properties, "consumer.progressive") || method == mlt_deinterlacer_none || mlt_frame_is_test_card(frame)) { mlt_log_debug(MLT_LINK_SERVICE(self), "Do not deinterlace - progressive=%d\tmethod=%s\ttest card=%d\n", mlt_properties_get_int(frame_properties, "consumer.progressive"), mlt_deinterlacer_name(pdata->method), mlt_frame_is_test_card(frame)); return mlt_frame_get_image(frame, image, format, width, height, writable); } // At this point, we know we need to deinterlace. mlt_properties unique_properties = mlt_frame_get_unique_properties(frame, MLT_LINK_SERVICE(self)); struct mlt_image_s srcimg = {0}; struct mlt_image_s dstimg = {0}; int error = 0; int ret = 0; // Operate on the native image format/size; srcimg.width = mlt_properties_get_int(unique_properties, "width"); srcimg.height = mlt_properties_get_int(unique_properties, "height"); srcimg.format = mlt_properties_get_int(unique_properties, "format"); // Sanitize the input if (srcimg.width <= 1 || srcimg.height <= 1) { mlt_profile profile = mlt_service_profile(MLT_LINK_SERVICE(self)); srcimg.width = profile->width; srcimg.height = profile->height; } srcimg.format = validate_format(srcimg.format); dstimg.format = validate_format(*format); mlt_service_lock(MLT_LINK_SERVICE(self)); if (pdata->method != method || pdata->expected_frame != mlt_frame_get_position(frame) || pdata->informat != srcimg.format || pdata->width != srcimg.width || pdata->height != srcimg.height || pdata->outformat != dstimg.format) { mlt_log_debug(MLT_LINK_SERVICE(self), "Init: %s->%s\t%d->%d\n", mlt_deinterlacer_name(pdata->method), mlt_deinterlacer_name(method), pdata->expected_frame, mlt_frame_get_position(frame)); pdata->method = method; pdata->continuity_frame = mlt_frame_get_position(frame); pdata->expected_frame = mlt_frame_get_position(frame); pdata->informat = srcimg.format; pdata->width = srcimg.width; pdata->height = srcimg.height; pdata->outformat = dstimg.format; init_image_filtergraph(self, av_d2q(mlt_frame_get_aspect_ratio(frame), 1024)); } filter_data *fdata = NULL; mlt_cache_item cache_item = mlt_service_cache_get(MLT_LINK_SERVICE(self), "link_avdeinterlace"); if (!cache_item) { mlt_log_error(MLT_LINK_SERVICE(self), "Cache miss\n"); init_image_filtergraph(self, av_d2q(mlt_frame_get_aspect_ratio(frame), 1024)); cache_item = mlt_service_cache_get(MLT_LINK_SERVICE(self), "link_avdeinterlace"); } fdata = mlt_cache_item_data(cache_item, NULL); pdata->expected_frame++; if (fdata && fdata->avfilter_graph) { while (1) { mlt_frame src_frame = NULL; if (pdata->continuity_frame == mlt_frame_get_position(frame)) { src_frame = frame; pdata->continuity_frame++; } else { if (!unique_properties) { error = 1; break; } char key[19]; int frame_delta = mlt_frame_get_position(frame) - mlt_frame_original_position(frame); sprintf(key, "%d", pdata->continuity_frame - frame_delta); src_frame = (mlt_frame) mlt_properties_get_data(unique_properties, key, NULL); if (!src_frame) { mlt_log_error(MLT_LINK_SERVICE(self), "Frame not found: %s\n", key); error = 1; break; } pdata->continuity_frame++; } error = mlt_frame_get_image(src_frame, (uint8_t **) &srcimg.data, &srcimg.format, &srcimg.width, &srcimg.height, 0); if (error || srcimg.format != pdata->informat || srcimg.width != pdata->width || srcimg.height != pdata->height) { mlt_log_error(MLT_LINK_SERVICE(self), "Failed to get image\t%d\t%d=%d\t%d=%d\t%d=%d\n", error, srcimg.format, pdata->informat, srcimg.width, pdata->width, srcimg.height, pdata->height); break; } mlt_image_to_avframe(&srcimg, src_frame, fdata->avinframe); // Run the frame through the filter graph ret = av_buffersrc_add_frame(fdata->avbuffsrc_ctx, fdata->avinframe); av_frame_unref(fdata->avinframe); if (ret < 0) { mlt_log_error(self, "Cannot add frame to buffer source\n"); error = 1; break; } ret = av_buffersink_get_frame(fdata->avbuffsink_ctx, fdata->avoutframe); if (ret >= 0) break; else if (ret == AVERROR(EAGAIN)) continue; else if (ret < 0) { mlt_log_error(self, "Cannot get frame from buffer sink\n"); error = 1; break; } srcimg.data = NULL; } if (!error) { // Allocate the output image mlt_image_set_values(&dstimg, NULL, pdata->outformat, fdata->avoutframe->width, fdata->avoutframe->height); mlt_image_alloc_data(&dstimg); avframe_to_mlt_image(fdata->avoutframe, &dstimg); } av_frame_unref(fdata->avoutframe); } mlt_service_unlock(MLT_LINK_SERVICE(self)); mlt_image_get_values(&dstimg, (void **) image, format, width, height); mlt_frame_set_image(frame, dstimg.data, 0, dstimg.release_data); mlt_properties_set_int(frame_properties, "progressive", 1); mlt_cache_item_close(cache_item); return 0; } static int link_get_frame(mlt_link self, mlt_frame_ptr frame, int index) { int error = 0; mlt_position frame_pos = mlt_producer_position(MLT_LINK_PRODUCER(self)); mlt_producer_seek(self->next, frame_pos); error = mlt_service_get_frame(MLT_PRODUCER_SERVICE(self->next), frame, index); mlt_producer original_producer = mlt_frame_get_original_producer(*frame); mlt_producer_probe(original_producer); if (mlt_properties_get_int(MLT_PRODUCER_PROPERTIES(original_producer), "meta.media.progressive") || mlt_properties_get_int(MLT_PRODUCER_PROPERTIES(original_producer), "progressive")) { return error; } mlt_properties unique_properties = mlt_frame_unique_properties(*frame, MLT_LINK_SERVICE(self)); mlt_properties_pass_list(unique_properties, MLT_PRODUCER_PROPERTIES(original_producer), "width height format"); // Pass future frames int i = 0; for (i = 0; i < FUTURE_FRAMES; i++) { mlt_position future_pos = frame_pos + i + 1; mlt_frame future_frame = NULL; mlt_producer_seek(self->next, future_pos); error = mlt_service_get_frame(MLT_PRODUCER_SERVICE(self->next), &future_frame, index); if (error) { mlt_log_error(MLT_LINK_SERVICE(self), "Error getting frame: %d\n", (int) future_pos); } char key[19]; sprintf(key, "%d", (int) future_pos); mlt_properties_set_data(unique_properties, key, future_frame, 0, (mlt_destructor) mlt_frame_close, NULL); } mlt_frame_push_service(*frame, self); mlt_frame_push_get_image(*frame, link_get_image); mlt_producer_prepare_next(MLT_LINK_PRODUCER(self)); return error; } static void link_close(mlt_link self) { if (self) { mlt_service_cache_purge(MLT_LINK_SERVICE(self)); free(self->child); self->close = NULL; self->child = NULL; mlt_link_close(self); free(self); } } mlt_link link_avdeinterlace_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_link self = mlt_link_init(); private_data *pdata = (private_data *) calloc(1, sizeof(private_data)); if (self && pdata) { pdata->continuity_frame = -1; pdata->expected_frame = -1; pdata->method = mlt_deinterlacer_linearblend; self->child = pdata; // Callback registration self->configure = link_configure; self->get_frame = link_get_frame; self->close = link_close; } else { free(pdata); mlt_link_close(self); self = NULL; } return self; } mlt-7.22.0/src/modules/avformat/link_avdeinterlace.yml000664 000000 000000 00000000613 14531534050 023002 0ustar00rootroot000000 000000 schema_version: 7.0 type: link identifier: avdeinterlace title: FFmpeg Deinterlacer version: 1 copyright: Meltytech, LLC license: LGPLv2.1 language: en url: http://www.ffmpeg.org/ tags: - Video - Hidden description: > Deinterlace interlaced video. This link can be added to a chain to normalize video from the producer to provide deinterlaced images if requested by the consumer. mlt-7.22.0/src/modules/avformat/link_avfilter.c000664 000000 000000 00000124511 14531534050 021435 0ustar00rootroot000000 000000 /* * link_avfilter.c -- provide various links based on libavfilter * Copyright (C) 2023 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #if !defined(_XOPEN_SOURCE) || _XOPEN_SOURCE < 700 #undef _XOPEN_SOURCE #define _XOPEN_SOURCE 700 #endif #include "common.h" #include #include #include #include #include #include #include #include #include #include #include #define PARAM_PREFIX "av." #define PARAM_PREFIX_LEN (sizeof(PARAM_PREFIX) - 1) typedef struct { AVFilter *avfilter; AVFilterContext *avbuffsink_ctx; AVFilterContext *avbuffsrc_ctx; AVFilterContext *avfilter_ctx; AVFilterContext *scale_ctx; AVFilterContext *pad_ctx; AVFilterGraph *avfilter_graph; AVFrame *avinframe; AVFrame *avoutframe; int format; int width; int height; int channels; int frequency; int reset; mlt_position expected_frame; mlt_position continuity_frame; } private_data; #if LIBAVUTIL_VERSION_INT >= ((56 << 16) + (35 << 8) + 101) static int animatable_avoption(const AVOption *opt) { return opt && (opt->flags & AV_OPT_FLAG_RUNTIME_PARAM) && opt->type != AV_OPT_TYPE_COLOR; } #endif static void property_changed(mlt_service owner, mlt_link self, mlt_event_data event_data) { const char *name = mlt_event_data_to_string(event_data); if (name && strncmp(PARAM_PREFIX, name, PARAM_PREFIX_LEN) == 0) { private_data *pdata = (private_data *) self->child; if (pdata->avfilter_ctx) { mlt_service_lock(MLT_LINK_SERVICE(self)); const AVOption *opt = av_opt_find(pdata->avfilter_ctx->priv, name + PARAM_PREFIX_LEN, 0, 0, 0); #if LIBAVUTIL_VERSION_INT >= ((56 << 16) + (35 << 8) + 101) pdata->reset = opt && !(animatable_avoption(opt) && mlt_properties_is_anim(MLT_LINK_PROPERTIES(self), name)); #else pdata->reset = opt && !mlt_properties_is_anim(MLT_LINK_PROPERTIES(self), name); #endif mlt_service_unlock(MLT_LINK_SERVICE(self)); } } } static int future_frames_needed(mlt_link self) { if (!self || !self->child) { return 0; } private_data *pdata = (private_data *) self->child; int future_frames = 0; if (strcmp(pdata->avfilter->name, "adeclick") == 0) { int windowms = mlt_properties_get_int(MLT_LINK_PROPERTIES(self), "av.window"); if (windowms <= 0) { // Default value is 100 windowms = 100; } double fps = mlt_profile_fps(mlt_service_profile(MLT_LINK_SERVICE(self))); // Provide future frames for 1.5x window duration (determined empirically) future_frames = ceil(1.5 * fps * windowms / 1000); } return future_frames; } static void set_avfilter_options(mlt_link self, double scale) { private_data *pdata = (private_data *) self->child; mlt_properties link_properties = MLT_LINK_PROPERTIES(self); int i; int count = mlt_properties_count(link_properties); mlt_properties scale_map = mlt_properties_get_data(link_properties, "_resolution_scale", NULL); for (i = 0; i < count; i++) { const char *param_name = mlt_properties_get_name(link_properties, i); if (param_name && strncmp(PARAM_PREFIX, param_name, PARAM_PREFIX_LEN) == 0) { const AVOption *opt = av_opt_find(pdata->avfilter_ctx->priv, param_name + PARAM_PREFIX_LEN, 0, 0, 0); const char *value = mlt_properties_get_value(link_properties, i); #if LIBAVUTIL_VERSION_INT >= ((56 << 16) + (35 << 8) + 101) if (opt && !(animatable_avoption(opt) && mlt_properties_is_anim(link_properties, param_name))) #else if (opt && !mlt_properties_is_anim(link_properties, param_name)) #endif { if (scale != 1.0) { double scale2 = mlt_properties_get_double(scale_map, opt->name); if (scale2 != 0.0) { double x = mlt_properties_get_double(link_properties, param_name); x *= scale * scale2; mlt_properties_set_double(link_properties, "_avfilter_temp", x); value = mlt_properties_get(link_properties, "_avfilter_temp"); } } av_opt_set(pdata->avfilter_ctx->priv, opt->name, value, 0); } } } } static void send_avformat_commands(mlt_link self, mlt_frame frame, private_data *pdata, double scale) { #if LIBAVUTIL_VERSION_INT >= ((56 << 16) + (35 << 8) + 101) mlt_properties prop = MLT_LINK_PROPERTIES(self); mlt_position position = mlt_producer_position(MLT_LINK_PRODUCER(self)) - mlt_producer_get_in(MLT_LINK_PRODUCER(self)); mlt_position length = mlt_producer_get_length(MLT_LINK_PRODUCER(self)); mlt_properties scale_map = mlt_properties_get_data(prop, "_resolution_scale", NULL); int count = mlt_properties_count(prop); int i; for (i = 0; i < count; i++) { char *name = mlt_properties_get_name(prop, i); if (!strncmp(name, PARAM_PREFIX, PARAM_PREFIX_LEN)) { const AVOption *opt = av_opt_find(pdata->avfilter_ctx->priv, name + PARAM_PREFIX_LEN, 0, 0, 0); if (animatable_avoption(opt) && mlt_properties_is_anim(prop, name)) { double x = mlt_properties_anim_get_double(prop, name, position, length); if (scale != 1.0) { double scale2 = mlt_properties_get_double(scale_map, opt->name); if (scale2 != 0.0) { x *= scale * scale2; } } mlt_properties_set_double(prop, "_avfilter_temp", x); char *new_val = mlt_properties_get(prop, "_avfilter_temp"); char *cur_val = NULL; av_opt_get(pdata->avfilter_ctx->priv, name + PARAM_PREFIX_LEN, AV_OPT_SEARCH_CHILDREN, (uint8_t **) &cur_val); if (new_val && cur_val && strcmp(new_val, cur_val)) { avfilter_graph_send_command(pdata->avfilter_graph, pdata->avfilter->name, name + PARAM_PREFIX_LEN, new_val, NULL, 0, 0); } av_free(cur_val); } } } #endif } static void init_audio_filtergraph(mlt_link self, mlt_audio_format format, int frequency, int channels) { private_data *pdata = (private_data *) self->child; const AVFilter *abuffersrc = avfilter_get_by_name("abuffer"); const AVFilter *abuffersink = avfilter_get_by_name("abuffersink"); int sample_fmts[] = {-1, -1}; int sample_rates[] = {-1, -1}; int channel_counts[] = {-1, -1}; int64_t channel_layouts[] = {-1, -1}; char channel_layout_str[64]; int ret; pdata->format = format; // Set up formats sample_fmts[0] = mlt_to_av_sample_format(format); sample_rates[0] = frequency; channel_counts[0] = channels; channel_layouts[0] = av_get_default_channel_layout(channels); av_get_channel_layout_string(channel_layout_str, sizeof(channel_layout_str), 0, channel_layouts[0]); // Destroy the current filter graph avfilter_graph_free(&pdata->avfilter_graph); // Create the new filter graph pdata->avfilter_graph = avfilter_graph_alloc(); if (!pdata->avfilter_graph) { mlt_log_error(self, "Cannot create filter graph\n"); goto fail; } // Set thread count if supported. if (pdata->avfilter->flags & AVFILTER_FLAG_SLICE_THREADS) { av_opt_set_int(pdata->avfilter_graph, "threads", FFMAX(0, mlt_properties_get_int(MLT_LINK_PROPERTIES(self), "av.threads")), 0); } // Initialize the buffer source filter context pdata->avbuffsrc_ctx = avfilter_graph_alloc_filter(pdata->avfilter_graph, abuffersrc, "in"); if (!pdata->avbuffsrc_ctx) { mlt_log_error(self, "Cannot create audio buffer source\n"); goto fail; } ret = av_opt_set_int(pdata->avbuffsrc_ctx, "sample_rate", sample_rates[0], AV_OPT_SEARCH_CHILDREN); if (ret < 0) { mlt_log_error(self, "Cannot set src sample rate %d\n", sample_rates[0]); goto fail; } ret = av_opt_set_int(pdata->avbuffsrc_ctx, "sample_fmt", sample_fmts[0], AV_OPT_SEARCH_CHILDREN); if (ret < 0) { mlt_log_error(self, "Cannot set src sample format %d\n", sample_fmts[0]); goto fail; } ret = av_opt_set_int(pdata->avbuffsrc_ctx, "channels", channel_counts[0], AV_OPT_SEARCH_CHILDREN); if (ret < 0) { mlt_log_error(self, "Cannot set src channels %d\n", channel_counts[0]); goto fail; } ret = av_opt_set(pdata->avbuffsrc_ctx, "channel_layout", channel_layout_str, AV_OPT_SEARCH_CHILDREN); if (ret < 0) { mlt_log_error(self, "Cannot set src channel layout %s\n", channel_layout_str); goto fail; } ret = avfilter_init_str(pdata->avbuffsrc_ctx, NULL); if (ret < 0) { mlt_log_error(self, "Cannot init buffer source\n"); goto fail; } // Initialize the buffer sink filter context pdata->avbuffsink_ctx = avfilter_graph_alloc_filter(pdata->avfilter_graph, abuffersink, "out"); if (!pdata->avbuffsink_ctx) { mlt_log_error(self, "Cannot create audio buffer sink\n"); goto fail; } ret = av_opt_set_int_list(pdata->avbuffsink_ctx, "sample_fmts", sample_fmts, -1, AV_OPT_SEARCH_CHILDREN); if (ret < 0) { mlt_log_error(self, "Cannot set sink sample formats\n"); goto fail; } ret = av_opt_set_int_list(pdata->avbuffsink_ctx, "sample_rates", sample_rates, -1, AV_OPT_SEARCH_CHILDREN); if (ret < 0) { mlt_log_error(self, "Cannot set sink sample rates\n"); goto fail; } ret = av_opt_set_int_list(pdata->avbuffsink_ctx, "channel_counts", channel_counts, -1, AV_OPT_SEARCH_CHILDREN); if (ret < 0) { mlt_log_error(self, "Cannot set sink channel counts\n"); goto fail; } ret = av_opt_set_int_list(pdata->avbuffsink_ctx, "channel_layouts", channel_layouts, -1, AV_OPT_SEARCH_CHILDREN); if (ret < 0) { mlt_log_error(self, "Cannot set sink channel_layouts\n"); goto fail; } ret = avfilter_init_str(pdata->avbuffsink_ctx, NULL); if (ret < 0) { mlt_log_error(self, "Cannot init buffer sink\n"); goto fail; } // Initialize the filter context pdata->avfilter_ctx = avfilter_graph_alloc_filter(pdata->avfilter_graph, pdata->avfilter, pdata->avfilter->name); if (!pdata->avfilter_ctx) { mlt_log_error(self, "Cannot create audio filter\n"); goto fail; } set_avfilter_options(self, 1.0); ret = avfilter_init_str(pdata->avfilter_ctx, NULL); if (ret < 0) { mlt_log_error(self, "Cannot init filter\n"); goto fail; } // Connect the filters ret = avfilter_link(pdata->avbuffsrc_ctx, 0, pdata->avfilter_ctx, 0); if (ret < 0) { mlt_log_error(self, "Cannot link src to filter\n"); goto fail; } ret = avfilter_link(pdata->avfilter_ctx, 0, pdata->avbuffsink_ctx, 0); if (ret < 0) { mlt_log_error(self, "Cannot link filter to sink\n"); goto fail; } // Configure the graph. ret = avfilter_graph_config(pdata->avfilter_graph, NULL); if (ret < 0) { mlt_log_error(self, "Cannot configure the filter graph\n"); goto fail; } return; fail: avfilter_graph_free(&pdata->avfilter_graph); } static void init_image_filtergraph( mlt_link self, mlt_image_format format, int width, int height, double resolution_scale) { private_data *pdata = (private_data *) self->child; mlt_profile profile = mlt_service_profile(MLT_LINK_SERVICE(self)); const AVFilter *buffersrc = avfilter_get_by_name("buffer"); const AVFilter *buffersink = avfilter_get_by_name("buffersink"); const AVFilter *scale = avfilter_get_by_name("scale"); const AVFilter *pad = avfilter_get_by_name("pad"); mlt_properties p = mlt_properties_new(); enum AVPixelFormat pixel_fmts[] = {-1, -1}; AVRational sar = (AVRational){profile->sample_aspect_num, profile->sample_aspect_den}; AVRational timebase = (AVRational){profile->frame_rate_den, profile->frame_rate_num}; AVRational framerate = (AVRational){profile->frame_rate_num, profile->frame_rate_den}; int ret; pdata->format = format; pdata->width = width; pdata->height = height; // Set up formats pixel_fmts[0] = mlt_to_av_image_format(format); // Destroy the current filter graph avfilter_graph_free(&pdata->avfilter_graph); // Create the new filter graph pdata->avfilter_graph = avfilter_graph_alloc(); if (!pdata->avfilter_graph) { mlt_log_error(self, "Cannot create filter graph\n"); goto fail; } pdata->avfilter_graph->scale_sws_opts = av_strdup("flags=" MLT_AVFILTER_SWS_FLAGS); // Set thread count if supported. if (pdata->avfilter->flags & AVFILTER_FLAG_SLICE_THREADS) { av_opt_set_int(pdata->avfilter_graph, "threads", FFMAX(0, mlt_properties_get_int(MLT_LINK_PROPERTIES(self), "av.threads")), 0); } // Initialize the buffer source filter context pdata->avbuffsrc_ctx = avfilter_graph_alloc_filter(pdata->avfilter_graph, buffersrc, "in"); if (!pdata->avbuffsrc_ctx) { mlt_log_error(self, "Cannot create image buffer source\n"); goto fail; } ret = av_opt_set_int(pdata->avbuffsrc_ctx, "width", width, AV_OPT_SEARCH_CHILDREN); if (ret < 0) { mlt_log_error(self, "Cannot set src width %d\n", width); goto fail; } ret = av_opt_set_int(pdata->avbuffsrc_ctx, "height", height, AV_OPT_SEARCH_CHILDREN); if (ret < 0) { mlt_log_error(self, "Cannot set src height %d\n", height); goto fail; } ret = av_opt_set_pixel_fmt(pdata->avbuffsrc_ctx, "pix_fmt", pixel_fmts[0], AV_OPT_SEARCH_CHILDREN); if (ret < 0) { mlt_log_error(self, "Cannot set src pixel format %d\n", pixel_fmts[0]); goto fail; } ret = av_opt_set_q(pdata->avbuffsrc_ctx, "sar", sar, AV_OPT_SEARCH_CHILDREN); if (ret < 0) { mlt_log_error(self, "Cannot set src sar %d/%d\n", sar.num, sar.den); goto fail; } ret = av_opt_set_q(pdata->avbuffsrc_ctx, "time_base", timebase, AV_OPT_SEARCH_CHILDREN); if (ret < 0) { mlt_log_error(self, "Cannot set src time_base %d/%d\n", timebase.num, timebase.den); goto fail; } ret = av_opt_set_q(pdata->avbuffsrc_ctx, "frame_rate", framerate, AV_OPT_SEARCH_CHILDREN); if (ret < 0) { mlt_log_error(self, "Cannot set src frame_rate %d/%d\n", framerate.num, framerate.den); goto fail; } ret = avfilter_init_str(pdata->avbuffsrc_ctx, NULL); if (ret < 0) { mlt_log_error(self, "Cannot init buffer source\n"); goto fail; } // Initialize the buffer sink filter context pdata->avbuffsink_ctx = avfilter_graph_alloc_filter(pdata->avfilter_graph, buffersink, "out"); if (!pdata->avbuffsink_ctx) { mlt_log_error(self, "Cannot create image buffer sink\n"); goto fail; } ret = av_opt_set_int_list(pdata->avbuffsink_ctx, "pix_fmts", pixel_fmts, -1, AV_OPT_SEARCH_CHILDREN); if (ret < 0) { mlt_log_error(self, "Cannot set sink pixel formats\n"); goto fail; } ret = avfilter_init_str(pdata->avbuffsink_ctx, NULL); if (ret < 0) { mlt_log_error(self, "Cannot init buffer sink\n"); goto fail; } // Initialize the filter context pdata->avfilter_ctx = avfilter_graph_alloc_filter(pdata->avfilter_graph, pdata->avfilter, pdata->avfilter->name); if (!pdata->avfilter_ctx) { mlt_log_error(self, "Cannot create video filter\n"); goto fail; } set_avfilter_options(self, resolution_scale); if (!strcmp("lut3d", pdata->avfilter->name)) { #if defined(__GLIBC__) || defined(__APPLE__) || (__FreeBSD__) // LUT data files use period for the decimal point regardless of LC_NUMERIC. mlt_locale_t posix_locale = newlocale(LC_NUMERIC_MASK, "POSIX", NULL); // Get the current locale and switch to POSIX local. mlt_locale_t orig_locale = uselocale(posix_locale); // Initialize the filter. ret = avfilter_init_str(pdata->avfilter_ctx, NULL); // Restore the original locale. uselocale(orig_locale); freelocale(posix_locale); #else // Get the current locale and switch to POSIX local. char *orig_localename = strdup(setlocale(LC_NUMERIC, NULL)); setlocale(LC_NUMERIC, "C"); // Initialize the filter. ret = avfilter_init_str(pdata->avfilter_ctx, NULL); // Restore the original locale. setlocale(LC_NUMERIC, orig_localename); free(orig_localename); #endif } else { ret = avfilter_init_str(pdata->avfilter_ctx, NULL); } if (ret < 0) { mlt_log_error(self, "Cannot init scale filter: %s\n", av_err2str(ret)); goto fail; } // Initialize the scale filter context pdata->scale_ctx = avfilter_graph_alloc_filter(pdata->avfilter_graph, scale, "scale"); if (!pdata->scale_ctx) { mlt_log_error(self, "Cannot create scale filer\n"); goto fail; } mlt_properties_set_int(p, "w", width); mlt_properties_set_int(p, "h", height); const AVOption *opt = av_opt_find(pdata->scale_ctx->priv, "w", 0, 0, 0); if (opt) { ret = av_opt_set(pdata->scale_ctx->priv, opt->name, mlt_properties_get(p, "w"), 0); if (ret < 0) { mlt_log_error(self, "Cannot set scale width\n"); goto fail; } } opt = av_opt_find(pdata->scale_ctx->priv, "h", 0, 0, 0); if (opt) { ret = av_opt_set(pdata->scale_ctx->priv, opt->name, mlt_properties_get(p, "h"), 0); if (ret < 0) { mlt_log_error(self, "Cannot set scale height\n"); goto fail; } } ret = av_opt_set_int(pdata->scale_ctx, "force_original_aspect_ratio", 1, AV_OPT_SEARCH_CHILDREN); if (ret < 0) { mlt_log_error(self, "Cannot set scale force_original_aspect_ratio\n"); goto fail; } opt = av_opt_find(pdata->scale_ctx->priv, "flags", 0, 0, 0); if (opt) { ret = av_opt_set(pdata->scale_ctx->priv, opt->name, MLT_AVFILTER_SWS_FLAGS, 0); if (ret < 0) { mlt_log_error(self, "Cannot set scale flags\n"); goto fail; } } ret = avfilter_init_str(pdata->scale_ctx, NULL); if (ret < 0) { mlt_log_error(self, "Cannot init scale filter\n"); goto fail; } // Initialize the padding filter context pdata->pad_ctx = avfilter_graph_alloc_filter(pdata->avfilter_graph, pad, "pad"); if (!pdata->pad_ctx) { mlt_log_error(self, "Cannot create pad filter\n"); goto fail; } opt = av_opt_find(pdata->pad_ctx->priv, "w", 0, 0, 0); if (opt) { ret = av_opt_set(pdata->pad_ctx->priv, opt->name, mlt_properties_get(p, "w"), 0); if (ret < 0) { mlt_log_error(self, "Cannot set pad width\n"); goto fail; } } opt = av_opt_find(pdata->pad_ctx->priv, "h", 0, 0, 0); if (opt) { ret = av_opt_set(pdata->pad_ctx->priv, opt->name, mlt_properties_get(p, "h"), 0); if (ret < 0) { mlt_log_error(self, "Cannot pad scale height\n"); goto fail; } } opt = av_opt_find(pdata->pad_ctx->priv, "x", 0, 0, 0); if (opt) { ret = av_opt_set(pdata->pad_ctx->priv, opt->name, "(ow-iw)/2", 0); if (ret < 0) { mlt_log_error(self, "Cannot set pad x\n"); goto fail; } } opt = av_opt_find(pdata->pad_ctx->priv, "y", 0, 0, 0); if (opt) { ret = av_opt_set(pdata->pad_ctx->priv, opt->name, "(oh-ih)/2", 0); if (ret < 0) { mlt_log_error(self, "Cannot set pad y\n"); goto fail; } } ret = avfilter_init_str(pdata->pad_ctx, NULL); if (ret < 0) { mlt_log_error(self, "Cannot init pad filter\n"); goto fail; } // Connect the filters ret = avfilter_link(pdata->avbuffsrc_ctx, 0, pdata->avfilter_ctx, 0); if (ret < 0) { mlt_log_error(self, "Cannot link src to filter\n"); goto fail; } ret = avfilter_link(pdata->avfilter_ctx, 0, pdata->scale_ctx, 0); if (ret < 0) { mlt_log_error(self, "Cannot link filter to scale\n"); goto fail; } ret = avfilter_link(pdata->scale_ctx, 0, pdata->pad_ctx, 0); if (ret < 0) { mlt_log_error(self, "Cannot link scale to pad\n"); goto fail; } ret = avfilter_link(pdata->pad_ctx, 0, pdata->avbuffsink_ctx, 0); if (ret < 0) { mlt_log_error(self, "Cannot link pad to sink\n"); goto fail; } // Configure the graph. ret = avfilter_graph_config(pdata->avfilter_graph, NULL); if (ret < 0) { mlt_log_error(self, "Cannot configure the filter graph\n"); goto fail; } return; fail: mlt_properties_close(p); avfilter_graph_free(&pdata->avfilter_graph); } static mlt_position get_position(mlt_link self, mlt_frame frame) { mlt_position position = mlt_frame_get_position(frame); const char *pos_type = mlt_properties_get(MLT_LINK_PROPERTIES(self), "position"); if (pos_type) { if (!strcmp("link", pos_type)) { position = mlt_producer_position(MLT_LINK_PRODUCER(self)); } else if (!strcmp("source", pos_type)) { position = mlt_frame_original_position(frame); } } else { private_data *pdata = (private_data *) self->child; if (!strcmp("subtitles", pdata->avfilter->name)) position = mlt_frame_original_position(frame); } return position; } static void link_configure(mlt_link self, mlt_profile chain_profile) { // Operate at the same frame rate as the next link mlt_service_set_profile(MLT_LINK_SERVICE(self), mlt_service_profile(MLT_PRODUCER_SERVICE(self->next))); } static int link_get_audio(mlt_frame frame, void **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples) { int error = 0; mlt_link self = mlt_frame_pop_audio(frame); private_data *pdata = (private_data *) self->child; double fps = mlt_profile_fps(mlt_service_profile(MLT_LINK_SERVICE(self))); int ret; mlt_service_lock(MLT_LINK_SERVICE(self)); if (pdata->reset || pdata->format != *format || pdata->channels != *channels || pdata->frequency != *frequency || pdata->expected_frame != mlt_frame_get_position(frame)) { mlt_log_error(MLT_LINK_SERVICE(self), "Init: %s\t%dc\t%dHz\n", mlt_audio_format_name(*format), *channels, *frequency); init_audio_filtergraph(self, *format, *frequency, *channels); pdata->reset = 0; pdata->format = *format; pdata->channels = *channels; pdata->frequency = *frequency; pdata->continuity_frame = mlt_frame_get_position(frame); pdata->expected_frame = mlt_frame_get_position(frame); } pdata->expected_frame++; while (pdata->avfilter_graph) { // Choose the frame to use (maybe a future frame) mlt_frame src_frame = NULL; if (pdata->continuity_frame == mlt_frame_get_position(frame)) { src_frame = frame; pdata->continuity_frame++; } else { mlt_properties unique_properties = mlt_frame_get_unique_properties(frame, MLT_LINK_SERVICE(self)); if (!unique_properties) { mlt_log_error(MLT_LINK_SERVICE(self), "Missing future frames\n"); error = 1; break; } char key[19]; int frame_delta = mlt_frame_get_position(frame) - mlt_frame_original_position(frame); sprintf(key, "%d", pdata->continuity_frame - frame_delta); src_frame = (mlt_frame) mlt_properties_get_data(unique_properties, key, NULL); if (!src_frame) { mlt_log_error(MLT_LINK_SERVICE(self), "Frame not found: %s\n", key); error = 1; break; } pdata->continuity_frame++; } // Get the producer's audio struct mlt_audio_s in; mlt_audio_set_values(&in, NULL, *frequency, *format, 0, *channels); in.samples = mlt_audio_calculate_frame_samples(mlt_producer_get_fps(MLT_LINK_PRODUCER(self)), in.frequency, mlt_frame_get_position(src_frame)); error = mlt_frame_get_audio(src_frame, &in.data, &in.format, &in.frequency, &in.channels, &in.samples); if (error || in.format != *format || in.frequency != *frequency || in.channels != *channels) { // Error situation. Do not attempt to process. mlt_log_error(MLT_LINK_SERVICE(self), "Invalid Return: E: %d %dS - %dHz %dC %s\n", error, in.samples, in.frequency, in.channels, mlt_audio_format_name(in.format)); error = 1; break; } // Set up the input frame mlt_channel_layout layout = mlt_get_channel_layout_or_default(mlt_properties_get(MLT_FRAME_PROPERTIES(src_frame), "channel_layout"), in.channels); int64_t samplepos = mlt_audio_calculate_samples_to_position(fps, *frequency, get_position(self, src_frame)); int inbufsize = mlt_audio_format_size(in.format, in.samples, in.channels); pdata->avinframe->sample_rate = in.frequency; pdata->avinframe->format = mlt_to_av_sample_format(in.format); pdata->avinframe->channel_layout = mlt_to_av_channel_layout(layout); pdata->avinframe->channels = in.channels; pdata->avinframe->nb_samples = in.samples; pdata->avinframe->pts = samplepos; ret = av_frame_get_buffer(pdata->avinframe, 1); if (ret < 0) { mlt_log_error(self, "Cannot get in frame buffer\n"); } if (av_sample_fmt_is_planar(pdata->avinframe->format)) { int i = 0; int stride = inbufsize / *channels; for (i = 0; i < *channels; i++) { memcpy(pdata->avinframe->extended_data[i], (uint8_t *) in.data + stride * i, stride); } } else { memcpy(pdata->avinframe->extended_data[0], (uint8_t *) in.data, inbufsize); } send_avformat_commands(self, frame, pdata, 1.0); // Run the frame through the filter graph ret = av_buffersrc_add_frame(pdata->avbuffsrc_ctx, pdata->avinframe); if (ret < 0) { mlt_log_error(self, "Cannot add frame to buffer source\n"); } ret = av_buffersink_get_samples(pdata->avbuffsink_ctx, pdata->avoutframe, *samples); if (ret == AVERROR(EAGAIN)) { mlt_log_debug(self, "Need more samples from next future frame\n"); continue; } else if (ret < 0) { mlt_log_error(self, "Cannot get frame from buffer sink\n"); error = 1; break; } // Sanity check the output frame if (*channels != pdata->avoutframe->channels || *samples != pdata->avoutframe->nb_samples || *frequency != pdata->avoutframe->sample_rate) { mlt_log_error(self, "Unexpected return format c %d->%d\tf %d->%d\tf %d->%d\n", *channels, pdata->avoutframe->channels, *samples, pdata->avoutframe->nb_samples, *frequency, pdata->avoutframe->sample_rate); error = 1; break; } // Copy the filter output into the frame int bufsize = mlt_audio_format_size(*format, *samples, *channels); *buffer = mlt_pool_alloc(bufsize); if (av_sample_fmt_is_planar(pdata->avoutframe->format)) { int stride = bufsize / *channels; int i = 0; for (i = 0; i < *channels; i++) { memcpy((uint8_t *) *buffer + stride * i, pdata->avoutframe->extended_data[i], stride); } } else { memcpy((uint8_t *) *buffer, pdata->avoutframe->extended_data[0], bufsize); } mlt_frame_set_audio(frame, *buffer, *format, bufsize, mlt_pool_release); break; } av_frame_unref(pdata->avinframe); av_frame_unref(pdata->avoutframe); if (error) { // Return unprocessed audio if an error occurs error = mlt_frame_get_audio(frame, buffer, format, frequency, channels, samples); } else { // Clear the audio stack in case get_audio was not called on this frame. while (mlt_frame_pop_audio(frame)) { }; } mlt_service_unlock(MLT_LINK_SERVICE(self)); return error; } static int link_get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { mlt_link self = mlt_frame_pop_service(frame); private_data *pdata = (private_data *) self->child; int64_t pos = get_position(self, frame); mlt_profile profile = mlt_service_profile(MLT_LINK_SERVICE(self)); mlt_properties frame_properties = MLT_FRAME_PROPERTIES(frame); int ret; mlt_log_debug(MLT_LINK_SERVICE(self), "position %" PRId64 "\n", pos); if (mlt_properties_get_int(MLT_LINK_PROPERTIES(self), "_yuv_only")) { *format = mlt_image_yuv422; } else { *format = mlt_get_supported_image_format(*format); } mlt_frame_get_image(frame, image, format, width, height, 0); mlt_service_lock(MLT_LINK_SERVICE(self)); double scale = mlt_profile_scale_width(profile, *width); if (pdata->reset || pdata->format != *format || pdata->width != *width || pdata->height != *height) { init_image_filtergraph(self, *format, *width, *height, scale); pdata->reset = 0; } if (pdata->avfilter_graph) { pdata->avinframe->width = *width; pdata->avinframe->height = *height; pdata->avinframe->format = mlt_to_av_image_format(*format); pdata->avinframe->sample_aspect_ratio = (AVRational){profile->sample_aspect_num, profile->sample_aspect_den}; pdata->avinframe->pts = pos; pdata->avinframe->interlaced_frame = !mlt_properties_get_int(frame_properties, "progressive"); pdata->avinframe->top_field_first = mlt_properties_get_int(frame_properties, "top_field_first"); pdata->avinframe->color_primaries = mlt_properties_get_int(frame_properties, "color_primaries"); pdata->avinframe->color_trc = mlt_properties_get_int(frame_properties, "color_trc"); pdata->avinframe->color_range = mlt_properties_get_int(frame_properties, "full_range") ? AVCOL_RANGE_JPEG : AVCOL_RANGE_MPEG; switch (mlt_properties_get_int(frame_properties, "colorspace")) { case 240: pdata->avinframe->colorspace = AVCOL_SPC_SMPTE240M; break; case 601: pdata->avinframe->colorspace = AVCOL_SPC_BT470BG; break; case 709: pdata->avinframe->colorspace = AVCOL_SPC_BT709; break; case 2020: pdata->avinframe->colorspace = AVCOL_SPC_BT2020_NCL; break; case 2021: pdata->avinframe->colorspace = AVCOL_SPC_BT2020_CL; break; } ret = av_frame_get_buffer(pdata->avinframe, 1); if (ret < 0) { mlt_log_error(self, "Cannot get in frame buffer\n"); } // Set up the input frame if (*format == mlt_image_yuv420p) { int i = 0; int p = 0; int widths[3] = {*width, *width / 2, *width / 2}; int heights[3] = {*height, *height / 2, *height / 2}; uint8_t *src = *image; for (p = 0; p < 3; p++) { uint8_t *dst = pdata->avinframe->data[p]; for (i = 0; i < heights[p]; i++) { memcpy(dst, src, widths[p]); src += widths[p]; dst += pdata->avinframe->linesize[p]; } } } else { int i; uint8_t *src = *image; uint8_t *dst = pdata->avinframe->data[0]; int stride = mlt_image_format_size(*format, *width, 1, NULL); for (i = 0; i < *height; i++) { memcpy(dst, src, stride); src += stride; dst += pdata->avinframe->linesize[0]; } } send_avformat_commands(self, frame, pdata, scale); // Run the frame through the filter graph ret = av_buffersrc_add_frame(pdata->avbuffsrc_ctx, pdata->avinframe); if (ret < 0) { mlt_log_error(self, "Cannot add frame to buffer source\n"); } ret = av_buffersink_get_frame(pdata->avbuffsink_ctx, pdata->avoutframe); if (ret < 0) { mlt_log_error(self, "Cannot get frame from buffer sink\n"); } // Sanity check the output frame if (*width != pdata->avoutframe->width || *height != pdata->avoutframe->height) { mlt_log_error(self, "Unexpected return format\n"); goto exit; } // Copy the filter output into the original buffer if (*format == mlt_image_yuv420p) { int i = 0; int p = 0; int widths[3] = {*width, *width / 2, *width / 2}; int heights[3] = {*height, *height / 2, *height / 2}; uint8_t *dst = *image; for (p = 0; p < 3; p++) { uint8_t *src = pdata->avoutframe->data[p]; for (i = 0; i < heights[p]; i++) { memcpy(dst, src, widths[p]); dst += widths[p]; src += pdata->avoutframe->linesize[p]; } } } else { int i; uint8_t *dst = *image; uint8_t *src = pdata->avoutframe->data[0]; int stride = mlt_image_format_size(*format, *width, 1, NULL); for (i = 0; i < *height; i++) { memcpy(dst, src, stride); dst += stride; src += pdata->avoutframe->linesize[0]; } } } exit: av_frame_unref(pdata->avinframe); av_frame_unref(pdata->avoutframe); mlt_service_unlock(MLT_LINK_SERVICE(self)); return 0; } static int link_get_frame(mlt_link self, mlt_frame_ptr frame, int index) { int error = 0; mlt_position frame_pos = mlt_producer_position(MLT_LINK_PRODUCER(self)); mlt_producer_seek(self->next, frame_pos); error = mlt_service_get_frame(MLT_PRODUCER_SERVICE(self->next), frame, index); mlt_properties unique_properties = mlt_frame_unique_properties(*frame, MLT_LINK_SERVICE(self)); // Pass future frames int future_frames = future_frames_needed(self); int i = 0; for (i = 0; i < future_frames; i++) { mlt_position future_pos = frame_pos + i + 1; mlt_frame future_frame = NULL; mlt_producer_seek(self->next, future_pos); error = mlt_service_get_frame(MLT_PRODUCER_SERVICE(self->next), &future_frame, index); if (error) { mlt_log_error(MLT_LINK_SERVICE(self), "Error getting frame: %d\n", (int) future_pos); } char key[19]; sprintf(key, "%d", (int) future_pos); mlt_properties_set_data(unique_properties, key, future_frame, 0, (mlt_destructor) mlt_frame_close, NULL); } private_data *pdata = (private_data *) self->child; if (avfilter_pad_get_type(pdata->avfilter->inputs, 0) == AVMEDIA_TYPE_VIDEO) { mlt_frame_push_service(*frame, self); mlt_frame_push_get_image(*frame, link_get_image); } else if (avfilter_pad_get_type(pdata->avfilter->inputs, 0) == AVMEDIA_TYPE_AUDIO) { mlt_frame_push_audio(*frame, self); mlt_frame_push_audio(*frame, link_get_audio); } mlt_producer_prepare_next(MLT_LINK_PRODUCER(self)); return error; } static void link_close(mlt_link self) { if (self) { private_data *pdata = (private_data *) self->child; if (pdata) { avfilter_graph_free(&pdata->avfilter_graph); av_frame_free(&pdata->avinframe); av_frame_free(&pdata->avoutframe); free(pdata); } self->close = NULL; mlt_link_close(self); free(self); } } mlt_link link_avfilter_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_link self = mlt_link_init(); private_data *pdata = (private_data *) calloc(1, sizeof(private_data)); if (pdata && id) { id += 9; // Move past "avfilter." pdata->avfilter = (AVFilter *) avfilter_get_by_name(id); } if (self && pdata && pdata->avfilter) { pdata->avbuffsink_ctx = NULL; pdata->avbuffsrc_ctx = NULL; pdata->avfilter_ctx = NULL; pdata->avfilter_graph = NULL; pdata->avinframe = av_frame_alloc(); pdata->avoutframe = av_frame_alloc(); pdata->format = -1; pdata->width = -1; pdata->height = -1; pdata->reset = 1; self->child = pdata; // Callback registration self->configure = link_configure; self->get_frame = link_get_frame; self->close = link_close; mlt_events_listen(MLT_LINK_PROPERTIES(self), self, "property-changed", (mlt_listener) property_changed); mlt_properties param_name_map = mlt_properties_get_data(mlt_global_properties(), "avfilter.resolution_scale", NULL); if (param_name_map) { // Lookup my plugin in the map param_name_map = mlt_properties_get_data(param_name_map, id, NULL); mlt_properties_set_data(MLT_LINK_PROPERTIES(self), "_resolution_scale", param_name_map, 0, NULL, NULL); } mlt_properties yuv_only = mlt_properties_get_data(mlt_global_properties(), "avfilter.yuv_only", NULL); if (yuv_only) { if (mlt_properties_get(yuv_only, id)) { mlt_properties_set_int(MLT_LINK_PROPERTIES(self), "_yuv_only", 1); } } } else { free(pdata); mlt_link_close(self); self = NULL; } return self; } mlt-7.22.0/src/modules/avformat/link_swresample.c000664 000000 000000 00000034376 14531534050 022014 0ustar00rootroot000000 000000 /* * link_swresample.c * Copyright (C) 2022 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "common.h" #include "common_swr.h" #include #include #include #include #include #include #include // Private Types #define FUTURE_FRAMES 1 typedef struct { mlt_position expected_frame; mlt_position continuity_frame; } private_data; static void destroy_swr_data(mlt_swr_private_data *swr) { if (swr) { mlt_free_swr_context(swr); free(swr); } } static void link_configure(mlt_link self, mlt_profile chain_profile) { // Operate at the same frame rate as the next link mlt_service_set_profile(MLT_LINK_SERVICE(self), mlt_service_profile(MLT_PRODUCER_SERVICE(self->next))); } static int link_get_audio(mlt_frame frame, void **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples) { int requested_frequency = *frequency <= 0 ? 48000 : *frequency; int requested_samples = *samples; mlt_link self = (mlt_link) mlt_frame_pop_audio(frame); private_data *pdata = (private_data *) self->child; // Validate the request *channels = *channels <= 0 ? 2 : *channels; int src_frequency = mlt_properties_get_int(MLT_FRAME_PROPERTIES(frame), "audio_frequency"); src_frequency = src_frequency <= 0 ? *frequency : src_frequency; int src_samples = mlt_audio_calculate_frame_samples(mlt_producer_get_fps( MLT_LINK_PRODUCER(self)), src_frequency, mlt_frame_get_position(frame)); int src_channels = mlt_properties_get_int(MLT_FRAME_PROPERTIES(frame), "audio_channels"); src_channels = src_channels <= 0 ? *channels : src_channels; mlt_properties frame_properties = MLT_FRAME_PROPERTIES(frame); struct mlt_audio_s in; struct mlt_audio_s out; mlt_audio_set_values(&in, *buffer, src_frequency, mlt_audio_none, src_samples, src_channels); mlt_audio_set_values(&out, NULL, requested_frequency, *format, requested_samples, *channels); // Get the producer's audio int error = mlt_frame_get_audio(frame, &in.data, &in.format, &in.frequency, &in.channels, &in.samples); if (error || in.format == mlt_audio_none || out.format == mlt_audio_none || in.frequency <= 0 || out.frequency <= 0 || in.channels <= 0 || out.channels <= 0) { // Error situation. Do not attempt to convert. mlt_audio_get_values(&in, buffer, frequency, format, samples, channels); mlt_log_error(MLT_LINK_SERVICE(self), "Invalid Parameters: %dS - %dHz %dC %s -> %dHz %dC %s\n", in.samples, in.frequency, in.channels, mlt_audio_format_name(in.format), out.frequency, out.channels, mlt_audio_format_name(out.format)); return error; } if (in.samples == 0) { // Noting to convert. return error; } // Determine the input/output channel layout. in.layout = mlt_get_channel_layout_or_default(mlt_properties_get(frame_properties, "channel_layout"), in.channels); out.layout = mlt_get_channel_layout_or_default(mlt_properties_get(frame_properties, "consumer.channel_layout"), out.channels); if (in.format == out.format && in.frequency == out.frequency && in.channels == out.channels && in.layout == out.layout) { // No change necessary mlt_audio_get_values(&in, buffer, frequency, format, samples, channels); return error; } mlt_service_lock(MLT_LINK_SERVICE(self)); mlt_swr_private_data *swr = NULL; int cache_miss = 0; mlt_cache_item cache_item = mlt_service_cache_get(MLT_LINK_SERVICE(self), "link_swresample"); swr = mlt_cache_item_data(cache_item, NULL); if (!cache_item) { cache_miss = 1; } // Detect configuration change if (cache_miss || swr->in_format != in.format || swr->out_format != out.format || swr->in_frequency != in.frequency || swr->out_frequency != out.frequency || swr->in_channels != in.channels || swr->out_channels != out.channels || swr->in_layout != in.layout || swr->out_layout != out.layout || pdata->expected_frame != mlt_frame_get_position(frame)) { mlt_cache_item_close(cache_item); swr = calloc(1, sizeof(mlt_swr_private_data)); // Save the configuration swr->in_format = in.format; swr->out_format = out.format; swr->in_frequency = in.frequency; swr->out_frequency = out.frequency; swr->in_channels = in.channels; swr->out_channels = out.channels; swr->in_layout = in.layout; swr->out_layout = out.layout; // Reconfigure the context error = mlt_configure_swr_context(MLT_LINK_SERVICE(self), swr); mlt_service_cache_put(MLT_LINK_SERVICE(self), "link_swresample", swr, 0, (mlt_destructor) destroy_swr_data); cache_item = mlt_service_cache_get(MLT_LINK_SERVICE(self), "link_swresample"); swr = mlt_cache_item_data(cache_item, NULL); pdata->continuity_frame = mlt_frame_get_position(frame); pdata->expected_frame = mlt_frame_get_position(frame); } if (swr && !error) { int total_received_samples = 0; out.samples = requested_samples; mlt_audio_alloc_data(&out); if (pdata->continuity_frame == mlt_frame_get_position(frame)) { // This is the nominal case when sample rate is not changing mlt_audio_get_planes(&in, swr->in_buffers); mlt_audio_get_planes(&out, swr->out_buffers); total_received_samples = swr_convert(swr->ctx, swr->out_buffers, out.samples, (const uint8_t **) swr->in_buffers, in.samples); if (total_received_samples < 0) { mlt_log_error(MLT_LINK_SERVICE(self), "swr_convert() failed. Needed: %d\tIn: %d\tOut: %d\n", out.samples, in.samples, total_received_samples); error = 1; } pdata->continuity_frame++; } while (total_received_samples < requested_samples && !error) { // The input frame is insufficient to fill the output frame. // This happens when sample rate conversion is occurring. // Request data from future frames. mlt_properties unique_properties = mlt_frame_get_unique_properties(frame, MLT_LINK_SERVICE(self)); if (!unique_properties) { error = 1; break; } char key[19]; int frame_delta = mlt_frame_get_position(frame) - mlt_frame_original_position(frame); sprintf(key, "%d", pdata->continuity_frame - frame_delta); mlt_frame src_frame = (mlt_frame) mlt_properties_get_data(unique_properties, key, NULL); if (!src_frame) { mlt_log_error(MLT_LINK_SERVICE(self), "Frame not found: %s\n", key); break; } // Get the audio from the in frame in.samples = mlt_audio_calculate_frame_samples(mlt_producer_get_fps( MLT_LINK_PRODUCER(self)), in.frequency, pdata->continuity_frame); in.format = mlt_audio_none; error = mlt_frame_get_audio(src_frame, &in.data, &in.format, &in.frequency, &in.channels, &in.samples); if (error) { break; } // Set up the SWR buffer for the audio from the in frame mlt_audio_get_planes(&in, swr->in_buffers); // Set up the SWR buffer for the audio from the out frame, // shifting according to what has already been received. int plane_count = mlt_audio_plane_count(&out); int plane_size = mlt_audio_plane_size(&out); int out_step_size = plane_size / out.samples; int p = 0; for (p = 0; p < plane_count; p++) { uint8_t *pAudio = (uint8_t *) out.data + (out_step_size * total_received_samples); swr->out_buffers[p] = pAudio + (p * plane_size); } int samples_needed = requested_samples - total_received_samples; int received_samples = swr_convert(swr->ctx, swr->out_buffers, samples_needed, (const uint8_t **) swr->in_buffers, in.samples); if (received_samples < 0) { mlt_log_error(MLT_LINK_SERVICE(self), "swr_convert() failed. Needed: %d\tIn: %d\tOut: %d\n", samples_needed, in.samples, received_samples); error = 1; } else { total_received_samples += received_samples; } pdata->continuity_frame++; } if (total_received_samples == 0) { mlt_log_info(MLT_LINK_SERVICE(self), "Failed to get any samples - return silence\n"); mlt_audio_silence(&out, out.samples, 0); } else if (total_received_samples < out.samples) { // Duplicate samples to return the exact number requested. mlt_audio_copy(&out, &out, total_received_samples, 0, out.samples - total_received_samples); } mlt_frame_set_audio(frame, out.data, out.format, 0, out.release_data); mlt_audio_get_values(&out, buffer, frequency, format, samples, channels); mlt_properties_set(frame_properties, "channel_layout", mlt_audio_channel_layout_name(out.layout)); pdata->expected_frame = mlt_frame_get_position(frame) + 1; } mlt_cache_item_close(cache_item); mlt_service_unlock(MLT_LINK_SERVICE(self)); return error; } static int link_get_frame(mlt_link self, mlt_frame_ptr frame, int index) { int error = 0; mlt_position frame_pos = mlt_producer_position(MLT_LINK_PRODUCER(self)); mlt_producer_seek(self->next, frame_pos); error = mlt_service_get_frame(MLT_PRODUCER_SERVICE(self->next), frame, index); if (error) { return error; } mlt_properties unique_properties = mlt_frame_unique_properties(*frame, MLT_LINK_SERVICE(self)); // Pass future frames int i = 0; for (i = 0; i < FUTURE_FRAMES; i++) { mlt_position future_pos = frame_pos + i + 1; mlt_frame future_frame = NULL; mlt_producer_seek(self->next, future_pos); error = mlt_service_get_frame(MLT_PRODUCER_SERVICE(self->next), &future_frame, index); if (error) { mlt_log_error(MLT_LINK_SERVICE(self), "Error getting frame: %d\n", (int) future_pos); } char key[19]; sprintf(key, "%d", (int) future_pos); mlt_properties_set_data(unique_properties, key, future_frame, 0, (mlt_destructor) mlt_frame_close, NULL); } mlt_frame_push_audio(*frame, (void *) self); mlt_frame_push_audio(*frame, link_get_audio); mlt_producer_prepare_next(MLT_LINK_PRODUCER(self)); return error; } static void link_close(mlt_link self) { if (self) { mlt_service_cache_purge(MLT_LINK_SERVICE(self)); free(self->child); self->close = NULL; self->child = NULL; mlt_link_close(self); free(self); } } mlt_link link_swresample_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_link self = mlt_link_init(); private_data *pdata = (private_data *) calloc(1, sizeof(private_data)); if (self && pdata) { pdata->continuity_frame = -1; pdata->expected_frame = -1; self->child = pdata; // Callback registration self->configure = link_configure; self->get_frame = link_get_frame; self->close = link_close; } else { if (pdata) { free(pdata); } if (self) { mlt_link_close(self); self = NULL; } } return self; } mlt-7.22.0/src/modules/avformat/link_swresample.yml000664 000000 000000 00000000655 14531534050 022364 0ustar00rootroot000000 000000 schema_version: 7.0 type: link identifier: swresample title: FFmpeg Audio Resampler version: 1 copyright: Meltytech, LLC license: LGPLv2.1 language: en url: http://www.ffmpeg.org/ tags: - Audio - Hidden description: > Change the audio sampling rate, channel layout and format. This link can be added to a chain to normalize audio from the producer to provide the rate, channels and format requested by the consumer. mlt-7.22.0/src/modules/avformat/mmx.h000664 000000 000000 00000022534 14531534050 017414 0ustar00rootroot000000 000000 /* * mmx.h * Copyright (C) 1997-2001 H. Dietz and R. Fisher */ #ifndef AVCODEC_I386MMX_H #define AVCODEC_I386MMX_H /* * The type of an value that fits in an MMX register (note that long * long constant values MUST be suffixed by LL and unsigned long long * values by ULL, lest they be truncated by the compiler) */ typedef union { long long q; /* Quadword (64-bit) value */ unsigned long long uq; /* Unsigned Quadword */ int d[2]; /* 2 Doubleword (32-bit) values */ unsigned int ud[2]; /* 2 Unsigned Doubleword */ short w[4]; /* 4 Word (16-bit) values */ unsigned short uw[4]; /* 4 Unsigned Word */ char b[8]; /* 8 Byte (8-bit) values */ unsigned char ub[8]; /* 8 Unsigned Byte */ float s[2]; /* Single-precision (32-bit) value */ } mmx_t; /* On an 8-byte (64-bit) boundary */ #define mmx_i2r(op,imm,reg) \ __asm__ __volatile__ (#op " %0, %%" #reg \ : /* nothing */ \ : "i" (imm) ) #define mmx_m2r(op,mem,reg) \ __asm__ __volatile__ (#op " %0, %%" #reg \ : /* nothing */ \ : "m" (mem)) #define mmx_r2m(op,reg,mem) \ __asm__ __volatile__ (#op " %%" #reg ", %0" \ : "=m" (mem) \ : /* nothing */ ) #define mmx_r2r(op,regs,regd) \ __asm__ __volatile__ (#op " %" #regs ", %" #regd) #define emms() __asm__ __volatile__ ("emms") #define movd_m2r(var,reg) mmx_m2r (movd, var, reg) #define movd_r2m(reg,var) mmx_r2m (movd, reg, var) #define movd_r2r(regs,regd) mmx_r2r (movd, regs, regd) #define movq_m2r(var,reg) mmx_m2r (movq, var, reg) #define movq_r2m(reg,var) mmx_r2m (movq, reg, var) #define movq_r2r(regs,regd) mmx_r2r (movq, regs, regd) #define packssdw_m2r(var,reg) mmx_m2r (packssdw, var, reg) #define packssdw_r2r(regs,regd) mmx_r2r (packssdw, regs, regd) #define packsswb_m2r(var,reg) mmx_m2r (packsswb, var, reg) #define packsswb_r2r(regs,regd) mmx_r2r (packsswb, regs, regd) #define packuswb_m2r(var,reg) mmx_m2r (packuswb, var, reg) #define packuswb_r2r(regs,regd) mmx_r2r (packuswb, regs, regd) #define paddb_m2r(var,reg) mmx_m2r (paddb, var, reg) #define paddb_r2r(regs,regd) mmx_r2r (paddb, regs, regd) #define paddd_m2r(var,reg) mmx_m2r (paddd, var, reg) #define paddd_r2r(regs,regd) mmx_r2r (paddd, regs, regd) #define paddw_m2r(var,reg) mmx_m2r (paddw, var, reg) #define paddw_r2r(regs,regd) mmx_r2r (paddw, regs, regd) #define paddsb_m2r(var,reg) mmx_m2r (paddsb, var, reg) #define paddsb_r2r(regs,regd) mmx_r2r (paddsb, regs, regd) #define paddsw_m2r(var,reg) mmx_m2r (paddsw, var, reg) #define paddsw_r2r(regs,regd) mmx_r2r (paddsw, regs, regd) #define paddusb_m2r(var,reg) mmx_m2r (paddusb, var, reg) #define paddusb_r2r(regs,regd) mmx_r2r (paddusb, regs, regd) #define paddusw_m2r(var,reg) mmx_m2r (paddusw, var, reg) #define paddusw_r2r(regs,regd) mmx_r2r (paddusw, regs, regd) #define pand_m2r(var,reg) mmx_m2r (pand, var, reg) #define pand_r2r(regs,regd) mmx_r2r (pand, regs, regd) #define pandn_m2r(var,reg) mmx_m2r (pandn, var, reg) #define pandn_r2r(regs,regd) mmx_r2r (pandn, regs, regd) #define pcmpeqb_m2r(var,reg) mmx_m2r (pcmpeqb, var, reg) #define pcmpeqb_r2r(regs,regd) mmx_r2r (pcmpeqb, regs, regd) #define pcmpeqd_m2r(var,reg) mmx_m2r (pcmpeqd, var, reg) #define pcmpeqd_r2r(regs,regd) mmx_r2r (pcmpeqd, regs, regd) #define pcmpeqw_m2r(var,reg) mmx_m2r (pcmpeqw, var, reg) #define pcmpeqw_r2r(regs,regd) mmx_r2r (pcmpeqw, regs, regd) #define pcmpgtb_m2r(var,reg) mmx_m2r (pcmpgtb, var, reg) #define pcmpgtb_r2r(regs,regd) mmx_r2r (pcmpgtb, regs, regd) #define pcmpgtd_m2r(var,reg) mmx_m2r (pcmpgtd, var, reg) #define pcmpgtd_r2r(regs,regd) mmx_r2r (pcmpgtd, regs, regd) #define pcmpgtw_m2r(var,reg) mmx_m2r (pcmpgtw, var, reg) #define pcmpgtw_r2r(regs,regd) mmx_r2r (pcmpgtw, regs, regd) #define pmaddwd_m2r(var,reg) mmx_m2r (pmaddwd, var, reg) #define pmaddwd_r2r(regs,regd) mmx_r2r (pmaddwd, regs, regd) #define pmulhw_m2r(var,reg) mmx_m2r (pmulhw, var, reg) #define pmulhw_r2r(regs,regd) mmx_r2r (pmulhw, regs, regd) #define pmullw_m2r(var,reg) mmx_m2r (pmullw, var, reg) #define pmullw_r2r(regs,regd) mmx_r2r (pmullw, regs, regd) #define por_m2r(var,reg) mmx_m2r (por, var, reg) #define por_r2r(regs,regd) mmx_r2r (por, regs, regd) #define pslld_i2r(imm,reg) mmx_i2r (pslld, imm, reg) #define pslld_m2r(var,reg) mmx_m2r (pslld, var, reg) #define pslld_r2r(regs,regd) mmx_r2r (pslld, regs, regd) #define psllq_i2r(imm,reg) mmx_i2r (psllq, imm, reg) #define psllq_m2r(var,reg) mmx_m2r (psllq, var, reg) #define psllq_r2r(regs,regd) mmx_r2r (psllq, regs, regd) #define psllw_i2r(imm,reg) mmx_i2r (psllw, imm, reg) #define psllw_m2r(var,reg) mmx_m2r (psllw, var, reg) #define psllw_r2r(regs,regd) mmx_r2r (psllw, regs, regd) #define psrad_i2r(imm,reg) mmx_i2r (psrad, imm, reg) #define psrad_m2r(var,reg) mmx_m2r (psrad, var, reg) #define psrad_r2r(regs,regd) mmx_r2r (psrad, regs, regd) #define psraw_i2r(imm,reg) mmx_i2r (psraw, imm, reg) #define psraw_m2r(var,reg) mmx_m2r (psraw, var, reg) #define psraw_r2r(regs,regd) mmx_r2r (psraw, regs, regd) #define psrld_i2r(imm,reg) mmx_i2r (psrld, imm, reg) #define psrld_m2r(var,reg) mmx_m2r (psrld, var, reg) #define psrld_r2r(regs,regd) mmx_r2r (psrld, regs, regd) #define psrlq_i2r(imm,reg) mmx_i2r (psrlq, imm, reg) #define psrlq_m2r(var,reg) mmx_m2r (psrlq, var, reg) #define psrlq_r2r(regs,regd) mmx_r2r (psrlq, regs, regd) #define psrlw_i2r(imm,reg) mmx_i2r (psrlw, imm, reg) #define psrlw_m2r(var,reg) mmx_m2r (psrlw, var, reg) #define psrlw_r2r(regs,regd) mmx_r2r (psrlw, regs, regd) #define psubb_m2r(var,reg) mmx_m2r (psubb, var, reg) #define psubb_r2r(regs,regd) mmx_r2r (psubb, regs, regd) #define psubd_m2r(var,reg) mmx_m2r (psubd, var, reg) #define psubd_r2r(regs,regd) mmx_r2r (psubd, regs, regd) #define psubw_m2r(var,reg) mmx_m2r (psubw, var, reg) #define psubw_r2r(regs,regd) mmx_r2r (psubw, regs, regd) #define psubsb_m2r(var,reg) mmx_m2r (psubsb, var, reg) #define psubsb_r2r(regs,regd) mmx_r2r (psubsb, regs, regd) #define psubsw_m2r(var,reg) mmx_m2r (psubsw, var, reg) #define psubsw_r2r(regs,regd) mmx_r2r (psubsw, regs, regd) #define psubusb_m2r(var,reg) mmx_m2r (psubusb, var, reg) #define psubusb_r2r(regs,regd) mmx_r2r (psubusb, regs, regd) #define psubusw_m2r(var,reg) mmx_m2r (psubusw, var, reg) #define psubusw_r2r(regs,regd) mmx_r2r (psubusw, regs, regd) #define punpckhbw_m2r(var,reg) mmx_m2r (punpckhbw, var, reg) #define punpckhbw_r2r(regs,regd) mmx_r2r (punpckhbw, regs, regd) #define punpckhdq_m2r(var,reg) mmx_m2r (punpckhdq, var, reg) #define punpckhdq_r2r(regs,regd) mmx_r2r (punpckhdq, regs, regd) #define punpckhwd_m2r(var,reg) mmx_m2r (punpckhwd, var, reg) #define punpckhwd_r2r(regs,regd) mmx_r2r (punpckhwd, regs, regd) #define punpcklbw_m2r(var,reg) mmx_m2r (punpcklbw, var, reg) #define punpcklbw_r2r(regs,regd) mmx_r2r (punpcklbw, regs, regd) #define punpckldq_m2r(var,reg) mmx_m2r (punpckldq, var, reg) #define punpckldq_r2r(regs,regd) mmx_r2r (punpckldq, regs, regd) #define punpcklwd_m2r(var,reg) mmx_m2r (punpcklwd, var, reg) #define punpcklwd_r2r(regs,regd) mmx_r2r (punpcklwd, regs, regd) #define pxor_m2r(var,reg) mmx_m2r (pxor, var, reg) #define pxor_r2r(regs,regd) mmx_r2r (pxor, regs, regd) /* 3DNOW extensions */ #define pavgusb_m2r(var,reg) mmx_m2r (pavgusb, var, reg) #define pavgusb_r2r(regs,regd) mmx_r2r (pavgusb, regs, regd) /* AMD MMX extensions - also available in intel SSE */ #define mmx_m2ri(op,mem,reg,imm) \ __asm__ __volatile__ (#op " %1, %0, %%" #reg \ : /* nothing */ \ : "X" (mem), "X" (imm)) #define mmx_r2ri(op,regs,regd,imm) \ __asm__ __volatile__ (#op " %0, %%" #regs ", %%" #regd \ : /* nothing */ \ : "X" (imm) ) #define mmx_fetch(mem,hint) \ __asm__ __volatile__ ("prefetch" #hint " %0" \ : /* nothing */ \ : "X" (mem)) #define maskmovq(regs,maskreg) mmx_r2ri (maskmovq, regs, maskreg) #define movntq_r2m(mmreg,var) mmx_r2m (movntq, mmreg, var) #define pavgb_m2r(var,reg) mmx_m2r (pavgb, var, reg) #define pavgb_r2r(regs,regd) mmx_r2r (pavgb, regs, regd) #define pavgw_m2r(var,reg) mmx_m2r (pavgw, var, reg) #define pavgw_r2r(regs,regd) mmx_r2r (pavgw, regs, regd) #define pextrw_r2r(mmreg,reg,imm) mmx_r2ri (pextrw, mmreg, reg, imm) #define pinsrw_r2r(reg,mmreg,imm) mmx_r2ri (pinsrw, reg, mmreg, imm) #define pmaxsw_m2r(var,reg) mmx_m2r (pmaxsw, var, reg) #define pmaxsw_r2r(regs,regd) mmx_r2r (pmaxsw, regs, regd) #define pmaxub_m2r(var,reg) mmx_m2r (pmaxub, var, reg) #define pmaxub_r2r(regs,regd) mmx_r2r (pmaxub, regs, regd) #define pminsw_m2r(var,reg) mmx_m2r (pminsw, var, reg) #define pminsw_r2r(regs,regd) mmx_r2r (pminsw, regs, regd) #define pminub_m2r(var,reg) mmx_m2r (pminub, var, reg) #define pminub_r2r(regs,regd) mmx_r2r (pminub, regs, regd) #define pmovmskb(mmreg,reg) \ __asm__ __volatile__ ("movmskps %" #mmreg ", %" #reg) #define pmulhuw_m2r(var,reg) mmx_m2r (pmulhuw, var, reg) #define pmulhuw_r2r(regs,regd) mmx_r2r (pmulhuw, regs, regd) #define prefetcht0(mem) mmx_fetch (mem, t0) #define prefetcht1(mem) mmx_fetch (mem, t1) #define prefetcht2(mem) mmx_fetch (mem, t2) #define prefetchnta(mem) mmx_fetch (mem, nta) #define psadbw_m2r(var,reg) mmx_m2r (psadbw, var, reg) #define psadbw_r2r(regs,regd) mmx_r2r (psadbw, regs, regd) #define pshufw_m2r(var,reg,imm) mmx_m2ri(pshufw, var, reg, imm) #define pshufw_r2r(regs,regd,imm) mmx_r2ri(pshufw, regs, regd, imm) #define sfence() __asm__ __volatile__ ("sfence\n\t") #endif /* AVCODEC_I386MMX_H */ mlt-7.22.0/src/modules/avformat/producer_avformat-novalidate.yml000664 000000 000000 00000001371 14531534050 025027 0ustar00rootroot000000 000000 schema_version: 7.0 type: producer identifier: avformat-novalidate title: Non-validating FFmpeg Reader version: 2 copyright: Meltytech, LLC license: LGPLv2.1 language: en url: http://www.ffmpeg.org/ tags: - Audio - Video description: Read an audio and/or video file using FFmpeg. notes: > This is basically the same as the avformat producer, but it does not validate that FFmpeg can open and read the resource. This is primarily useful in a composition (e.g. XML) that was constructed after it was validated. Since validation also determines the length property, you should set that yourself on this producer after having learned it from the normal avformat producer. See the documentation for the normal avformat producer for more information. mlt-7.22.0/src/modules/avformat/producer_avformat.c000664 000000 000000 00000501343 14531534050 022330 0ustar00rootroot000000 000000 /* * producer_avformat.c -- avformat producer * Copyright (C) 2003-2023 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #if !defined(_POSIX_C_SOURCE) || _POSIX_C_SOURCE < 200809L #undef _POSIX_C_SOURCE #define _POSIX_C_SOURCE 200809L #endif #if !defined(_XOPEN_SOURCE) || _XOPEN_SOURCE < 500 #undef _XOPEN_SOURCE #define _XOPEN_SOURCE 500 #endif #include "common.h" // MLT Header files #include #include #include #include #include #include #include #include // ffmpeg Header files #include #include #include #include #include #include #include #include #include #include #define USE_HWACCEL 1 #if USE_HWACCEL #include #endif #ifdef AVFILTER #include #include #include #endif // System header files #include #include #include #include #include #include #include #define POSITION_INITIAL (-2) #define POSITION_INVALID (-1) #define MAX_AUDIO_STREAMS (32) #define MAX_AUDIO_FRAME_SIZE (192000) // 1 second of 48khz 32bit audio #define IMAGE_ALIGN (1) #define VFR_THRESHOLD \ (3) // The minimum number of video frames with differing durations to be considered VFR. struct producer_avformat_s { mlt_producer parent; AVFormatContext *dummy_context; AVFormatContext *audio_format; AVFormatContext *video_format; AVCodecContext *audio_codec[MAX_AUDIO_STREAMS]; AVCodecContext *video_codec; AVFrame *video_frame; AVFrame *audio_frame; AVPacket pkt; mlt_position audio_expected; mlt_position video_expected; int audio_index; int video_index; int64_t first_pts; atomic_int_fast64_t last_position; int video_seekable; int seekable; /// This one is used for both audio and file level seekability. atomic_int_fast64_t current_position; mlt_position nonseek_position; atomic_int top_field_first; int progressive; uint8_t *audio_buffer[MAX_AUDIO_STREAMS]; int audio_buffer_size[MAX_AUDIO_STREAMS]; uint8_t *decode_buffer[MAX_AUDIO_STREAMS]; int audio_used[MAX_AUDIO_STREAMS]; int audio_streams; int audio_max_stream; int total_channels; int max_channel; int max_frequency; unsigned int invalid_pts_counter; unsigned int invalid_dts_counter; mlt_cache image_cache; mlt_cache audio_cache; int yuv_colorspace, color_primaries, color_trc; int full_range; pthread_mutex_t video_mutex; pthread_mutex_t audio_mutex; mlt_deque apackets; mlt_deque vpackets; pthread_mutex_t packets_mutex; pthread_mutex_t open_mutex; pthread_mutex_t close_mutex; int is_mutex_init; pthread_t packets_thread; pthread_cond_t packets_cond; int packets_thread_ret; // latest non-zero non-EGAIN return on av_read_frame() in packets_thread int packets_thread_stop; // non-zero when packets_thread is to stop int is_thread_init; AVRational video_time_base; mlt_frame last_good_frame; // for video error concealment int last_good_position; // for video error concealment #ifdef AVFILTER AVFilterGraph *vfilter_graph; AVFilterContext *vfilter_in; AVFilterContext *vfilter_out; #endif int autorotate; double rotation; int is_audio_synchronizing; int video_send_result; int reset_image_cache; #if USE_HWACCEL struct { int pix_fmt; int device_type; char device[128]; AVBufferRef *device_ctx; } hwaccel; #endif }; typedef struct producer_avformat_s *producer_avformat; // Forward references. static int list_components(char *file); static int producer_open( producer_avformat self, mlt_profile profile, const char *URL, int take_lock, int test_open); static int producer_get_frame(mlt_producer producer, mlt_frame_ptr frame, int index); static int producer_probe(mlt_producer producer); static void producer_avformat_close(producer_avformat); static void producer_close(mlt_producer parent); static void producer_set_up_video(producer_avformat self, mlt_frame frame); static void producer_set_up_audio(producer_avformat self, mlt_frame frame); static void apply_properties(void *obj, mlt_properties properties, int flags); static int video_codec_init(producer_avformat self, int index, mlt_properties properties); static void get_audio_streams_info(producer_avformat self); static mlt_audio_format pick_audio_format(int sample_fmt); static int pick_av_pixel_format(int *pix_fmt, int full_range); static void property_changed(mlt_service owner, producer_avformat self, char *name); static int absolute_stream_index(AVFormatContext *context, enum AVMediaType media_type, int relative) { if (context) { int n = -1; for (int i = 0; i < context->nb_streams; i++) { AVCodecParameters *codec_params = context->streams[i]->codecpar; if (codec_params->codec_type == media_type && ++n == relative) { return i; } } } return -1; } static int relative_stream_index(AVFormatContext *context, enum AVMediaType media_type, int absolute) { if (context) { int n = -1; for (int i = 0; i < context->nb_streams; i++) { AVCodecParameters *codec_params = context->streams[i]->codecpar; if (codec_params->codec_type == media_type) { ++n; if (absolute == i) return n; } } } return -1; } /** Constructor for libavformat. */ mlt_producer producer_avformat_init(mlt_profile profile, const char *service, char *file) { if (list_components(file)) return NULL; mlt_producer producer = NULL; // Check that we have a non-NULL argument if (file) { // Construct the producer producer_avformat self = calloc(1, sizeof(struct producer_avformat_s)); producer = calloc(1, sizeof(struct mlt_producer_s)); // Initialise it if (mlt_producer_init(producer, self) == 0) { self->parent = producer; // Get the properties mlt_properties properties = MLT_PRODUCER_PROPERTIES(producer); // Set the resource property (required for all producers) mlt_properties_set(properties, "resource", file); // Register transport implementation with the producer producer->close = (mlt_destructor) producer_close; // Register our get_frame implementation producer->get_frame = producer_get_frame; // Register our probe implementation mlt_properties_set_data(properties, "mlt_producer_probe", producer_probe, 0, NULL, NULL); // Force the duration to be computed unless explicitly provided. mlt_properties_set_position(properties, "length", 0); mlt_properties_set_position(properties, "out", 0); if (strcmp(service, "avformat-novalidate")) { // Open the file if (producer_open(self, profile, mlt_properties_get(properties, "resource"), 1, 1) != 0) { // Clean up producer_avformat_close(self); mlt_producer_close(producer); producer = NULL; } else if (self->seekable) { // Close the file to release resources for large playlists - reopen later as needed if (self->audio_format) avformat_close_input(&self->audio_format); if (self->video_format) avformat_close_input(&self->video_format); } } if (producer) { // Default the user-selectable indices from the auto-detected indices mlt_properties_set_int(properties, "audio_index", self->audio_index); mlt_properties_set_int(properties, "video_index", self->video_index); mlt_service_cache_put(MLT_PRODUCER_SERVICE(producer), "producer_avformat", self, 0, (mlt_destructor) producer_avformat_close); mlt_properties_set_int(properties, "mute_on_pause", 0); mlt_events_listen(properties, self, "property-changed", (mlt_listener) property_changed); } } } return producer; } int list_components(char *file) { int skip = 0; // Report information about available demuxers and codecs as YAML Tiny if (file && strstr(file, "f-list")) { fprintf(stderr, "---\nformats:\n"); void *state = NULL; const AVInputFormat *format = NULL; while ((format = av_demuxer_iterate(&state))) { fprintf(stderr, " - %s\n", format->name); } fprintf(stderr, "...\n"); skip = 1; } if (file && strstr(file, "acodec-list")) { fprintf(stderr, "---\naudio_codecs:\n"); void *state = NULL; const AVCodec *codec = NULL; while ((codec = av_codec_iterate(&state))) { if (av_codec_is_decoder(codec) && codec->type == AVMEDIA_TYPE_AUDIO) fprintf(stderr, " - %s\n", codec->name); } fprintf(stderr, "...\n"); skip = 1; } if (file && strstr(file, "vcodec-list")) { fprintf(stderr, "---\nvideo_codecs:\n"); void *state = NULL; const AVCodec *codec = NULL; while ((codec = av_codec_iterate(&state))) { if (av_codec_is_decoder(codec) && codec->type == AVMEDIA_TYPE_VIDEO) fprintf(stderr, " - %s\n", codec->name); } fprintf(stderr, "...\n"); skip = 1; } return skip; } static int first_video_index(producer_avformat self) { AVFormatContext *context = self->video_format ? self->video_format : self->audio_format; int result = -1; // not found if (context) { unsigned int i; for (i = 0; i < context->nb_streams; i++) { if (context->streams[i]->codecpar && context->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) break; } if (i < context->nb_streams) { result = i; } } return result; } #include static const char *get_projection(AVStream *st) { const AVSphericalMapping *spherical = (const AVSphericalMapping *) av_stream_get_side_data(st, AV_PKT_DATA_SPHERICAL, NULL); if (spherical) return av_spherical_projection_name(spherical->projection); return NULL; } #include static double get_rotation(mlt_properties properties, AVStream *st) { AVDictionaryEntry *rotate_tag = av_dict_get(st->metadata, "rotate", NULL, 0); int has_rotate_metadata = rotate_tag && *rotate_tag->value && strcmp(rotate_tag->value, "0"); uint8_t *displaymatrix = av_stream_get_side_data(st, AV_PKT_DATA_DISPLAYMATRIX, NULL); double theta = mlt_properties_get_double(properties, "rotate"); int has_mlt_rotate = !!mlt_properties_get(properties, "rotate"); if (has_rotate_metadata && !has_mlt_rotate) { char *tail; theta = strtod(rotate_tag->value, &tail); if (*tail) { // invalid theta = 0; has_rotate_metadata = 0; } } if (displaymatrix && !has_rotate_metadata && !has_mlt_rotate) { theta = -av_display_rotation_get((int32_t *) displaymatrix); } theta -= 360 * floor(theta / 360 + 0.9 / 360); return theta; } static char *filter_restricted(const char *in) { if (!in) return NULL; size_t n = strlen(in); // https://github.com/bminor/glibc/commit/9bcd12d223a8990254b65e2dada54faa5d2742f3 char *out = calloc(n + MB_CUR_MAX, 1); char *p = out; mbstate_t mbs; memset(&mbs, 0, sizeof(mbs)); while (*in) { wchar_t w; size_t c = mbrtowc(&w, in, n, &mbs); if (c <= 0 || c > n) break; n -= c; in += c; if (w == 0x9 || w == 0xA || w == 0xD || (w >= 0x20 && w <= 0xD7FF) || (w >= 0xE000 && w <= 0xFFFD) || (w >= 0x10000 && w <= 0x10FFFF)) { mbstate_t ps; memset(&ps, 0, sizeof(ps)); c = wcrtomb(p, w, &ps); if (c > 0) p += c; } } return out; } /** Find the default streams. */ static mlt_properties find_default_streams(producer_avformat self) { unsigned int i; char key[200]; AVDictionaryEntry *tag = NULL; AVFormatContext *context = self->video_format; mlt_properties meta_media = MLT_PRODUCER_PROPERTIES(self->parent); // Default to the first audio and video streams found self->audio_index = -1; int first_video_index = self->video_index = -1; mlt_properties_set_int(meta_media, "meta.media.nb_streams", context->nb_streams); // Allow for multiple audio and video streams in the file and select first of each (if available) for (i = 0; i < context->nb_streams; i++) { // Get the codec context AVStream *stream = context->streams[i]; if (!stream) continue; AVCodecParameters *codec_params = stream->codecpar; const AVCodec *codec = avcodec_find_decoder(codec_params->codec_id); if (!codec) continue; snprintf(key, sizeof(key), "meta.media.%u.stream.type", i); // Determine the type and obtain the first index of each type switch (codec_params->codec_type) { case AVMEDIA_TYPE_VIDEO: // Save the first video stream if (first_video_index < 0) first_video_index = i; // Only set the video stream if not album art if (self->video_index < 0 && !(context->streams[i]->disposition & AV_DISPOSITION_ATTACHED_PIC)) { self->video_index = i; } mlt_properties_set(meta_media, key, "video"); snprintf(key, sizeof(key), "meta.media.%u.stream.frame_rate", i); double avg_frame_rate = av_q2d(context->streams[i]->avg_frame_rate); double ffmpeg_fps = isfinite(avg_frame_rate) ? avg_frame_rate : av_q2d(context->streams[i]->r_frame_rate); mlt_properties_set_double(meta_media, key, ffmpeg_fps); const char *projection = get_projection(context->streams[i]); if (projection) { snprintf(key, sizeof(key), "meta.media.%u.stream.projection", i); mlt_properties_set_string(meta_media, key, projection); } snprintf(key, sizeof(key), "meta.media.%u.stream.sample_aspect_ratio", i); mlt_properties_set_double(meta_media, key, av_q2d(context->streams[i]->sample_aspect_ratio)); snprintf(key, sizeof(key), "meta.media.%u.codec.width", i); mlt_properties_set_int(meta_media, key, codec_params->width); snprintf(key, sizeof(key), "meta.media.%u.codec.height", i); mlt_properties_set_int(meta_media, key, codec_params->height); snprintf(key, sizeof(key), "meta.media.%u.codec.rotate", i); mlt_properties_set_int(meta_media, key, get_rotation(NULL, context->streams[i])); // snprintf( key, sizeof(key), "meta.media.%u.codec.frame_rate", i ); // AVRational frame_rate = { codec_context->time_base.den, codec_context->time_base.num * codec_context->ticks_per_frame }; // mlt_properties_set_double( meta_media, key, av_q2d( frame_rate ) ); snprintf(key, sizeof(key), "meta.media.%u.codec.pix_fmt", i); mlt_properties_set(meta_media, key, av_get_pix_fmt_name(codec_params->format)); snprintf(key, sizeof(key), "meta.media.%u.codec.sample_aspect_ratio", i); mlt_properties_set_double(meta_media, key, av_q2d(codec_params->sample_aspect_ratio)); snprintf(key, sizeof(key), "meta.media.%u.codec.colorspace", i); switch (codec_params->color_space) { case AVCOL_SPC_SMPTE240M: mlt_properties_set_int(meta_media, key, 240); break; case AVCOL_SPC_BT470BG: case AVCOL_SPC_SMPTE170M: mlt_properties_set_int(meta_media, key, 601); break; case AVCOL_SPC_BT709: mlt_properties_set_int(meta_media, key, 709); break; case AVCOL_SPC_UNSPECIFIED: case AVCOL_SPC_RESERVED: // This is a heuristic Charles Poynton suggests in "Digital Video and HDTV" mlt_properties_set_int(meta_media, key, codec_params->width * codec_params->height > 750000 ? 709 : 601); break; default: mlt_properties_set_int(meta_media, key, codec_params->color_space); break; } if (codec_params->color_trc && codec_params->color_trc != AVCOL_TRC_UNSPECIFIED) { snprintf(key, sizeof(key), "meta.media.%u.codec.color_trc", i); mlt_properties_set_double(meta_media, key, codec_params->color_trc); } break; case AVMEDIA_TYPE_AUDIO: if (!codec_params->channels) break; // Use first audio stream if (self->audio_index < 0 && pick_audio_format(codec_params->format) != mlt_audio_none) self->audio_index = i; mlt_properties_set(meta_media, key, "audio"); snprintf(key, sizeof(key), "meta.media.%u.codec.sample_fmt", i); mlt_properties_set(meta_media, key, av_get_sample_fmt_name(codec_params->format)); snprintf(key, sizeof(key), "meta.media.%u.codec.sample_rate", i); mlt_properties_set_int(meta_media, key, codec_params->sample_rate); snprintf(key, sizeof(key), "meta.media.%u.codec.channels", i); mlt_properties_set_int(meta_media, key, codec_params->channels); break; default: break; } // snprintf( key, sizeof(key), "meta.media.%u.stream.time_base", i ); // mlt_properties_set_double( meta_media, key, av_q2d( context->streams[ i ]->time_base ) ); snprintf(key, sizeof(key), "meta.media.%u.codec.name", i); mlt_properties_set(meta_media, key, codec->name); snprintf(key, sizeof(key), "meta.media.%u.codec.long_name", i); mlt_properties_set(meta_media, key, codec->long_name); snprintf(key, sizeof(key), "meta.media.%u.codec.bit_rate", i); mlt_properties_set_int64(meta_media, key, codec_params->bit_rate); // snprintf( key, sizeof(key), "meta.media.%u.codec.time_base", i ); // mlt_properties_set_double( meta_media, key, av_q2d( codec_context->time_base ) ); // snprintf( key, sizeof(key), "meta.media.%u.codec.profile", i ); // mlt_properties_set_int( meta_media, key, codec_context->profile ); // snprintf( key, sizeof(key), "meta.media.%u.codec.level", i ); // mlt_properties_set_int( meta_media, key, codec_context->level ); // Read Metadata while ((tag = av_dict_get(stream->metadata, "", tag, AV_DICT_IGNORE_SUFFIX))) { if (tag->value && strcmp(tag->value, "") && strcmp(tag->value, "und")) { snprintf(key, sizeof(key), "meta.attr.%u.stream.%s.markup", i, tag->key); char *value = filter_restricted(tag->value); mlt_properties_set(meta_media, key, value); free(value); } } } // Use the album art if that is all we have if (self->video_index < 0 && first_video_index >= 0) self->video_index = first_video_index; while ((tag = av_dict_get(context->metadata, "", tag, AV_DICT_IGNORE_SUFFIX))) { if (tag->value && strcmp(tag->value, "") && strcmp(tag->value, "und")) { snprintf(key, sizeof(key), "meta.attr.%s.markup", tag->key); char *value = filter_restricted(tag->value); mlt_properties_set(meta_media, key, value); free(value); } } return meta_media; } static void get_aspect_ratio(mlt_properties properties, AVStream *stream, AVCodecParameters *codec_params) { AVRational sar = stream->sample_aspect_ratio; if (sar.num <= 0 || sar.den <= 0) sar = codec_params->sample_aspect_ratio; if (sar.num <= 0 || sar.den <= 0) sar.num = sar.den = 1; mlt_properties_set_int(properties, "meta.media.sample_aspect_num", sar.num); mlt_properties_set_int(properties, "meta.media.sample_aspect_den", sar.den); mlt_properties_set_double(properties, "aspect_ratio", av_q2d(sar)); } static char *parse_url(mlt_profile profile, const char *URL, const AVInputFormat **format, AVDictionary **params) { (void) profile; // unused if (!URL) return NULL; char *protocol = strdup(URL); char *url = strchr(protocol, ':'); // Truncate protocol string if (url && (url - protocol) > 1 && avio_check(URL, 0) < 0) { // if defined and not a drive letter url[0] = '\0'; ++url; mlt_log_debug(NULL, "%s: protocol=%s resource=%s\n", __FUNCTION__, protocol, url); // Lookup the format *format = av_find_input_format(protocol); } else { url = protocol; } // Eat the format designator char *result = url; // support for legacy width and height parameters char *width = NULL; char *height = NULL; // Parse out params char *query = strchr(url, '?'); if (*format) { // Query string delimiter is '?' url = (query && query > url && query[-1] != '\\') ? query : NULL; } else { // Ignore unescaped question marks while (query && query > url && query[-1] != '\\') { query = strchr(query + 1, '?'); } // Query string delimiter is '\?' url = (query && query > url && query[-1] == '\\') ? query : NULL; if (url) url[-1] = '\0'; // null the backslash } while (url) { url[0] = '\0'; char *name = strdup(++url); char *value = strchr(name, '='); if (!value) // Also accept : as delimiter for backwards compatibility. value = strchr(name, ':'); if (value) { value[0] = '\0'; value++; char *t = strchr(value, '&'); if (t) t[0] = 0; // translate old parameters to new av_dict names if (!strcmp(name, "frame_rate")) av_dict_set(params, "framerate", value, 0); else if (!strcmp(name, "pix_fmt")) av_dict_set(params, "pixel_format", value, 0); else if (!strcmp(name, "width")) width = strdup(value); else if (!strcmp(name, "height")) height = strdup(value); else // generic demux/device option support av_dict_set(params, name, value, 0); } free(name); url = strchr(url, '&'); } // continued support for legacy width and height parameters if (width && height) { char *s = malloc(strlen(width) + strlen(height) + 2); strcpy(s, width); strcat(s, "x"); strcat(s, height); av_dict_set(params, "video_size", s, 0); free(s); } free(width); free(height); result = strdup(result); free(protocol); mlt_log_debug(NULL, "[producer avformat] %s filename = %s\n", __FUNCTION__, result); return result; } static enum AVPixelFormat pick_pix_fmt(enum AVPixelFormat pix_fmt) { switch (pix_fmt) { case AV_PIX_FMT_ARGB: case AV_PIX_FMT_RGBA: case AV_PIX_FMT_ABGR: case AV_PIX_FMT_BGRA: return AV_PIX_FMT_RGBA; case AV_PIX_FMT_BAYER_RGGB16LE: return AV_PIX_FMT_RGB24; #if USE_HWACCEL case AV_PIX_FMT_VAAPI: case AV_PIX_FMT_CUDA: case AV_PIX_FMT_VIDEOTOOLBOX: case AV_PIX_FMT_DXVA2_VLD: case AV_PIX_FMT_D3D11: return AV_PIX_FMT_YUV420P; #endif default: return AV_PIX_FMT_YUV422P; } } static mlt_image_format pick_image_format(enum AVPixelFormat pix_fmt, int full_range, mlt_image_format current_format) { if (current_format == mlt_image_none || current_format == mlt_image_movit || pix_fmt == AV_PIX_FMT_ARGB || pix_fmt == AV_PIX_FMT_RGBA || pix_fmt == AV_PIX_FMT_ABGR || pix_fmt == AV_PIX_FMT_BGRA) { switch (pix_fmt) { case AV_PIX_FMT_ARGB: case AV_PIX_FMT_RGBA: case AV_PIX_FMT_ABGR: case AV_PIX_FMT_BGRA: return mlt_image_rgba; case AV_PIX_FMT_YUV420P: case AV_PIX_FMT_YUVJ420P: case AV_PIX_FMT_YUVA420P: return mlt_image_yuv420p; case AV_PIX_FMT_RGB24: case AV_PIX_FMT_BGR24: case AV_PIX_FMT_GRAY8: case AV_PIX_FMT_MONOWHITE: case AV_PIX_FMT_MONOBLACK: case AV_PIX_FMT_RGB8: case AV_PIX_FMT_BGR8: case AV_PIX_FMT_BAYER_RGGB16LE: return mlt_image_rgb; case AV_PIX_FMT_YUV420P10LE: return mlt_image_yuv420p10; case AV_PIX_FMT_YUV422P10LE: case AV_PIX_FMT_YUV444P10LE: return mlt_image_yuv444p10; case AV_PIX_FMT_YUV422P16LE: return mlt_image_yuv422p16; default: current_format = mlt_image_yuv422; } } if (pix_fmt == AV_PIX_FMT_BAYER_RGGB16LE || (pix_fmt == AV_PIX_FMT_YUV420P10LE && full_range)) { return mlt_image_rgb; } else if (pix_fmt == AV_PIX_FMT_YUVA444P10LE || pix_fmt == AV_PIX_FMT_GBRAP10LE || pix_fmt == AV_PIX_FMT_GBRAP12LE) { return mlt_image_rgba; } return current_format; } static int get_basic_info(producer_avformat self, mlt_profile profile, const char *filename) { int error = 0; // Get the properties mlt_properties properties = MLT_PRODUCER_PROPERTIES(self->parent); AVFormatContext *format = self->video_format; // Get the duration if (mlt_properties_get_position(properties, "length") <= 0 || mlt_properties_get_position(properties, "out") <= 0) { if (format->duration != AV_NOPTS_VALUE) { // This isn't going to be accurate for all formats // We will treat everything with the producer fps. mlt_position frames = (mlt_position) lrint(format->duration * mlt_profile_fps(profile) / AV_TIME_BASE); if (mlt_properties_get_position(properties, "out") <= 0) mlt_properties_set_position(properties, "out", frames - 1); if (mlt_properties_get_position(properties, "length") <= 0) mlt_properties_set_position(properties, "length", frames); } else if (format->nb_streams > 0 && format->streams[0]->codecpar && format->streams[0]->codecpar->codec_id == AV_CODEC_ID_WEBP) { char *e = getenv("MLT_DEFAULT_PRODUCER_LENGTH"); int p = e ? atoi(e) : 15000; mlt_properties_set_int(properties, "out", MAX(0, p - 1)); mlt_properties_set_int(properties, "length", p); } else { // Set live sources to run forever if (mlt_properties_get_position(properties, "length") <= 0) mlt_properties_set_position(properties, "length", INT_MAX); if (mlt_properties_get_position(properties, "out") <= 0) mlt_properties_set_position(properties, "out", INT_MAX - 1); mlt_properties_set(properties, "eof", "loop"); } } // Check if we're seekable // avdevices are typically AVFMT_NOFILE and not seekable self->seekable = !format->iformat || !(format->iformat->flags & AVFMT_NOFILE); if (format->pb) { // protocols can indicate if they support seeking self->seekable = format->pb->seekable; } if (self->seekable) { // Do a more rigorous test of seekable on a disposable context if (format->nb_streams > 0 && format->streams[0]->codecpar && format->streams[0]->codecpar->codec_id != AV_CODEC_ID_WEBP) self->seekable = av_seek_frame(format, -1, format->start_time, AVSEEK_FLAG_BACKWARD) >= 0; mlt_properties_set_int(properties, "seekable", self->seekable); self->dummy_context = format; self->video_format = NULL; avformat_open_input(&self->video_format, filename, NULL, NULL); avformat_find_stream_info(self->video_format, NULL); format = self->video_format; } self->video_seekable = self->seekable; // Fetch the width, height and aspect ratio if (self->video_index != -1) { AVCodecParameters *codec_params = format->streams[self->video_index]->codecpar; mlt_properties_set_int(properties, "width", codec_params->width); mlt_properties_set_int(properties, "height", codec_params->height); get_aspect_ratio(properties, format->streams[self->video_index], codec_params); #ifdef AVFILTER int pix_fmt = self->vfilter_out ? av_buffersink_get_format(self->vfilter_out) : codec_params->format; #else int pix_fmt = codec_params->format; #endif pick_av_pixel_format(&pix_fmt, self->full_range); if (pix_fmt != AV_PIX_FMT_NONE) { // Verify that we can convert this to one of our image formats. struct SwsContext *context = sws_getContext(codec_params->width, codec_params->height, pix_fmt, codec_params->width, codec_params->height, pick_pix_fmt(pix_fmt), SWS_BILINEAR, NULL, NULL, NULL); if (context) { sws_freeContext(context); mlt_image_format format = pick_image_format(pix_fmt, self->full_range, mlt_image_yuv422); mlt_properties_set_int(properties, "format", format); } else error = 1; } else { self->video_index = -1; } } return error; } #ifdef AVFILTER static int setup_video_filters(producer_avformat self) { mlt_properties properties = MLT_PRODUCER_PROPERTIES(self->parent); AVFormatContext *format = self->video_format; AVStream *stream = format->streams[self->video_index]; AVCodecParameters *codec_params = stream->codecpar; self->vfilter_graph = avfilter_graph_alloc(); // From ffplay.c:configure_video_filters(). char buffersrc_args[256]; snprintf(buffersrc_args, sizeof(buffersrc_args), "video_size=%dx%d:pix_fmt=%d:time_base=%d/%d:pixel_aspect=%d/%d:frame_rate=%d/%d", codec_params->width, codec_params->height, codec_params->format, stream->time_base.num, stream->time_base.den, mlt_properties_get_int(properties, "meta.media.sample_aspect_num"), FFMAX(mlt_properties_get_int(properties, "meta.media.sample_aspect_den"), 1), stream->avg_frame_rate.num, FFMAX(stream->avg_frame_rate.den, 1)); int result = avfilter_graph_create_filter(&self->vfilter_in, avfilter_get_by_name("buffer"), "mlt_buffer", buffersrc_args, NULL, self->vfilter_graph); if (result >= 0) { result = avfilter_graph_create_filter(&self->vfilter_out, avfilter_get_by_name("buffersink"), "mlt_buffersink", NULL, NULL, self->vfilter_graph); } return result; } static int insert_filter(AVFilterGraph *graph, AVFilterContext **last_filter, const char *name, const char *args) { AVFilterContext *filt_ctx; int result = avfilter_graph_create_filter(&filt_ctx, avfilter_get_by_name(name), name, args, NULL, graph); if (result >= 0) { result = avfilter_link(filt_ctx, 0, *last_filter, 0); if (result >= 0) *last_filter = filt_ctx; } return result; } static int setup_filters(producer_avformat self) { int error = 0; mlt_properties properties = MLT_PRODUCER_PROPERTIES(self->parent); const char *filtergraph = mlt_properties_get(properties, "filtergraph"); double theta = 0.0; if (self->video_index != -1 && self->autorotate) { theta = get_rotation(properties, self->video_format->streams[self->video_index]); if (self->vfilter_graph && theta != self->rotation) { // The rotation has changed. Force the filter graph to be rebuilt avfilter_graph_free(&self->vfilter_graph); self->vfilter_out = NULL; self->rotation = theta; } } if (!self->vfilter_graph && (self->autorotate || filtergraph) && self->video_index != -1) { AVFilterContext *last_filter = NULL; if (self->autorotate) { if (fabs(theta - 90) < 1.0) { error = (setup_video_filters(self) < 0); last_filter = self->vfilter_out; if (!error) error = (insert_filter(self->vfilter_graph, &last_filter, "transpose", "clock") < 0); } else if (fabs(theta - 180) < 1.0) { error = (setup_video_filters(self) < 0); last_filter = self->vfilter_out; if (!error) error = (insert_filter(self->vfilter_graph, &last_filter, "hflip", NULL) < 0); if (!error) error = (insert_filter(self->vfilter_graph, &last_filter, "vflip", NULL) < 0); } else if (fabs(theta - 270) < 1.0) { error = (setup_video_filters(self) < 0); last_filter = self->vfilter_out; if (!error) error = (insert_filter(self->vfilter_graph, &last_filter, "transpose", "cclock") < 0); } } if (filtergraph && !error) { if (!self->vfilter_graph) { error = (setup_video_filters(self) < 0); last_filter = self->vfilter_out; } AVFilterInOut *outputs = avfilter_inout_alloc(); AVFilterInOut *inputs = avfilter_inout_alloc(); outputs->name = av_strdup("in"); outputs->filter_ctx = self->vfilter_in; outputs->pad_idx = 0; outputs->next = NULL; inputs->name = av_strdup("out"); inputs->filter_ctx = last_filter; inputs->pad_idx = 0; inputs->next = NULL; if (!error) error = (avfilter_graph_parse(self->vfilter_graph, filtergraph, inputs, outputs, NULL) < 0); } if (self->vfilter_graph) { if (!error && !filtergraph) error = (avfilter_link(self->vfilter_in, 0, last_filter, 0) < 0); if (!error) error = (avfilter_graph_config(self->vfilter_graph, NULL) < 0); } } if (error && self->vfilter_graph) { avfilter_graph_free(&self->vfilter_graph); } return error; } #endif static void set_up_discard(producer_avformat self, int audio_index, int video_index) { // The open_mutex must be locked when this function is called if (self->audio_format) { for (int x = 0; x < self->audio_format->nb_streams; x++) { if (audio_index == INT_MAX || x == audio_index || (self->audio_format == self->video_format && x == video_index)) self->audio_format->streams[x]->discard = AVDISCARD_DEFAULT; else self->audio_format->streams[x]->discard = AVDISCARD_ALL; } } if (self->video_format && self->video_format != self->audio_format) { for (int x = 0; x < self->video_format->nb_streams; x++) { if (x == video_index) self->video_format->streams[x]->discard = AVDISCARD_DEFAULT; else self->video_format->streams[x]->discard = AVDISCARD_ALL; } } } /** Open the file. */ static int producer_open( producer_avformat self, mlt_profile profile, const char *URL, int take_lock, int test_open) { // Return an error code (0 == no error) int error = 0; mlt_properties properties = MLT_PRODUCER_PROPERTIES(self->parent); if (!self->is_mutex_init) { pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); pthread_mutex_init(&self->audio_mutex, &attr); pthread_mutex_init(&self->video_mutex, &attr); pthread_mutex_init(&self->packets_mutex, &attr); pthread_mutex_init(&self->open_mutex, &attr); pthread_mutex_init(&self->close_mutex, &attr); self->is_mutex_init = 1; } // Lock the service if (take_lock) { pthread_mutex_lock(&self->audio_mutex); pthread_mutex_lock(&self->video_mutex); } mlt_events_block(properties, self->parent); // Parse URL const AVInputFormat *format = NULL; AVDictionary *params = NULL; char *filename = parse_url(profile, URL, &format, ¶ms); // Now attempt to open the file or device with filename error = avformat_open_input(&self->video_format, filename, format, ¶ms) < 0; if (error) // If the URL is a network stream URL, then we probably need to open with full URL error = avformat_open_input(&self->video_format, URL, format, ¶ms) < 0; // Set MLT properties onto video AVFormatContext if (!error && self->video_format) { apply_properties(self->video_format, properties, AV_OPT_FLAG_DECODING_PARAM); if (self->video_format->iformat && self->video_format->iformat->priv_class && self->video_format->priv_data) apply_properties(self->video_format->priv_data, properties, AV_OPT_FLAG_DECODING_PARAM); } // If successful, then try to get additional info if (!error && self->video_format) { // Get the stream info error = avformat_find_stream_info(self->video_format, NULL) < 0; // Continue if no error if (!error && self->video_format) { // Find default audio and video streams find_default_streams(self); error = get_basic_info(self, profile, filename); // Initialize position info self->first_pts = AV_NOPTS_VALUE; self->last_position = POSITION_INITIAL; #if USE_HWACCEL AVDictionaryEntry *hwaccel = av_dict_get(params, "hwaccel", NULL, 0); AVDictionaryEntry *hwaccel_device = av_dict_get(params, "hwaccel_device", NULL, 0); if (hwaccel && hwaccel->value) { // Leaving `device=NULL` will cause query string parameter `hwaccel_device` to be ignored char *device = NULL; if (!strcmp(hwaccel->value, "vaapi")) { self->hwaccel.pix_fmt = AV_PIX_FMT_VAAPI; self->hwaccel.device_type = AV_HWDEVICE_TYPE_VAAPI; device = "/dev/dri/renderD128"; } else if (!strcmp(hwaccel->value, "cuda")) { self->hwaccel.pix_fmt = AV_PIX_FMT_CUDA; self->hwaccel.device_type = AV_HWDEVICE_TYPE_CUDA; device = "0"; } else if (!strcmp(hwaccel->value, "videotoolbox")) { self->hwaccel.pix_fmt = AV_PIX_FMT_VIDEOTOOLBOX; self->hwaccel.device_type = AV_HWDEVICE_TYPE_VIDEOTOOLBOX; } else if (!strcmp(hwaccel->value, "d3d11va")) { self->hwaccel.pix_fmt = AV_PIX_FMT_D3D11; self->hwaccel.device_type = AV_HWDEVICE_TYPE_D3D11VA; device = "0"; } else if (!strcmp(hwaccel->value, "dxva2")) { self->hwaccel.pix_fmt = AV_PIX_FMT_DXVA2_VLD; self->hwaccel.device_type = AV_HWDEVICE_TYPE_DXVA2; device = "0"; } else { // TODO: init other hardware types } if (device) { if (hwaccel_device && hwaccel_device->value) device = hwaccel_device->value; memcpy(self->hwaccel.device, device, strlen(device)); } } #endif if (!self->audio_format) { // We're going to cheat here - for seekable A/V files, we will have separate contexts // to support independent seeking of audio from video. // TODO: Is this really necessary? if (self->audio_index != -1 && self->video_index != -1) { if (self->seekable) { // And open again for our audio context avformat_open_input(&self->audio_format, filename, NULL, NULL); apply_properties(self->audio_format, properties, AV_OPT_FLAG_DECODING_PARAM); if (self->audio_format->iformat && self->audio_format->iformat->priv_class && self->audio_format->priv_data) apply_properties(self->audio_format->priv_data, properties, AV_OPT_FLAG_DECODING_PARAM); avformat_find_stream_info(self->audio_format, NULL); } else { self->audio_format = self->video_format; } } else if (self->audio_index != -1) { // We only have an audio context self->audio_format = self->video_format; self->video_format = NULL; } else if (self->video_index == -1) { // Something has gone wrong error = -1; } if (self->audio_format && !self->audio_streams) get_audio_streams_info(self); #ifdef AVFILTER if (!test_open) { self->autorotate = !mlt_properties_get(properties, "autorotate") || mlt_properties_get_int(properties, "autorotate"); error = setup_filters(self); } #endif } } } av_dict_free(¶ms); free(filename); if (!error) { self->apackets = mlt_deque_init(); self->vpackets = mlt_deque_init(); } if (self->dummy_context) { pthread_mutex_lock(&self->open_mutex); avformat_close_input(&self->dummy_context); self->dummy_context = NULL; pthread_mutex_unlock(&self->open_mutex); } // Unlock the service if (take_lock) { pthread_mutex_unlock(&self->audio_mutex); pthread_mutex_unlock(&self->video_mutex); } mlt_events_unblock(properties, self->parent); return error; } static void prepare_reopen(producer_avformat self) { mlt_service_lock(MLT_PRODUCER_SERVICE(self->parent)); pthread_mutex_lock(&self->audio_mutex); pthread_mutex_lock(&self->open_mutex); int i; for (i = 0; i < MAX_AUDIO_STREAMS; i++) { mlt_pool_release(self->audio_buffer[i]); self->audio_buffer[i] = NULL; av_free(self->decode_buffer[i]); self->decode_buffer[i] = NULL; avcodec_free_context(&self->audio_codec[i]); } avcodec_free_context(&self->video_codec); av_frame_unref(self->video_frame); #if USE_HWACCEL av_buffer_unref(&self->hwaccel.device_ctx); self->hwaccel.device_ctx = NULL; #endif if (self->seekable && self->audio_format) avformat_close_input(&self->audio_format); if (self->video_format) avformat_close_input(&self->video_format); self->audio_format = NULL; self->video_format = NULL; #ifdef AVFILTER avfilter_graph_free(&self->vfilter_graph); #endif pthread_mutex_unlock(&self->open_mutex); // Cleanup the packet queues AVPacket *pkt; if (self->apackets) { while ((pkt = mlt_deque_pop_back(self->apackets))) { av_packet_unref(pkt); free(pkt); } mlt_deque_close(self->apackets); self->apackets = NULL; } if (self->vpackets) { while ((pkt = mlt_deque_pop_back(self->vpackets))) { av_packet_unref(pkt); free(pkt); } mlt_deque_close(self->vpackets); self->vpackets = NULL; } pthread_mutex_unlock(&self->audio_mutex); mlt_service_unlock(MLT_PRODUCER_SERVICE(self->parent)); } static int64_t best_pts(producer_avformat self, int64_t pts, int64_t dts) { self->invalid_pts_counter += pts == AV_NOPTS_VALUE; self->invalid_dts_counter += dts == AV_NOPTS_VALUE; if ((self->invalid_pts_counter <= self->invalid_dts_counter || dts == AV_NOPTS_VALUE) && pts != AV_NOPTS_VALUE) return pts; else return dts; } static void find_first_pts(producer_avformat self, int video_index) { // find initial PTS AVFormatContext *context = self->video_format ? self->video_format : self->audio_format; int ret = 0; int pkt_countdown = 500; // check max 500 packets for first video keyframe PTS int vfr_countdown = 20; // check max 20 video frames for VFR int vfr_counter = 0; // counts the number of frame duration changes AVPacket pkt; int64_t prev_pkt_duration = AV_NOPTS_VALUE; av_init_packet(&pkt); while (ret >= 0 && pkt_countdown-- > 0 && (self->first_pts == AV_NOPTS_VALUE || (vfr_counter < VFR_THRESHOLD && vfr_countdown > 0))) { ret = av_read_frame(context, &pkt); if (ret >= 0 && pkt.stream_index == video_index) { // Variable frame rate check if (pkt.duration != AV_NOPTS_VALUE && pkt.duration != prev_pkt_duration) { mlt_log_verbose(MLT_PRODUCER_SERVICE(self->parent), "checking VFR: pkt.duration %" PRId64 "\n", pkt.duration); if (prev_pkt_duration != AV_NOPTS_VALUE) ++vfr_counter; } prev_pkt_duration = pkt.duration; vfr_countdown--; // Finding PTS of first video key frame if ((pkt.flags & AV_PKT_FLAG_KEY) && self->first_pts == AV_NOPTS_VALUE) { mlt_log_debug(MLT_PRODUCER_SERVICE(self->parent), "first_pts %" PRId64 " dts %" PRId64 " pts_dts_delta %d\n", pkt.pts, pkt.dts, (int) (pkt.pts - pkt.dts)); if (pkt.dts != AV_NOPTS_VALUE && pkt.dts < 0) // Decoding Time Stamps with negative values are reported by ffmpeg code for // (at least) MP4 files containing h.264 video using b-frames. // For reasons not understood yet, the first PTS computed then is that of the // third frame, causing MLT to display the third frame as if it was the first. // This if-clause is meant to catch and work around this issue - if there is // a valid, but negative DTS value, we just guess that the first valid // Presentation Time Stamp is == 0. self->first_pts = 0; else self->first_pts = best_pts(self, pkt.pts, pkt.dts); } } av_packet_unref(&pkt); } if (vfr_counter >= VFR_THRESHOLD) mlt_properties_set_int(MLT_PRODUCER_PROPERTIES(self->parent), "meta.media.variable_frame_rate", 1); av_seek_frame(context, -1, 0, AVSEEK_FLAG_BACKWARD); } static int seek_video(producer_avformat self, mlt_position position, int64_t req_position, int preseek) { mlt_producer producer = self->parent; mlt_properties properties = MLT_PRODUCER_PROPERTIES(producer); int paused = 0; int seek_threshold = mlt_properties_get_int(properties, "seek_threshold"); if (seek_threshold <= 0) seek_threshold = 64; pthread_mutex_lock(&self->packets_mutex); if (self->video_seekable && (position != self->video_expected || self->last_position < 0)) { // Fetch the video format context AVFormatContext *context = self->video_format; // We may want to use the source fps if available double source_fps = mlt_properties_get_double(properties, "meta.media.frame_rate_num") / mlt_properties_get_double(properties, "meta.media.frame_rate_den"); if (self->first_pts == AV_NOPTS_VALUE && self->last_position == POSITION_INITIAL) find_first_pts(self, self->video_index); if (self->video_frame && position + 1 == self->video_expected) { // We're paused - use last image paused = 1; } else if (position < self->video_expected || position - self->video_expected >= seek_threshold || self->last_position < 0) { // Calculate the timestamp for the requested frame int64_t timestamp = req_position / (av_q2d(self->video_time_base) * source_fps); if (req_position <= 0) timestamp = 0; else if (self->first_pts != AV_NOPTS_VALUE) timestamp += self->first_pts; else if (context->start_time != AV_NOPTS_VALUE) timestamp += context->start_time; if (preseek && av_q2d(self->video_time_base) != 0) timestamp -= 2 / av_q2d(self->video_time_base); if (timestamp < 0) timestamp = 0; mlt_log_debug(MLT_PRODUCER_SERVICE(producer), "seeking timestamp %" PRId64 " position " MLT_POSITION_FMT " expected " MLT_POSITION_FMT " last_pos %" PRId64 "\n", timestamp, position, self->video_expected, self->last_position); // Seek to the timestamp self->video_codec->skip_loop_filter = AVDISCARD_NONREF; av_seek_frame(context, self->video_index, timestamp, AVSEEK_FLAG_BACKWARD); // flush any pictures still in decode buffer avcodec_flush_buffers(self->video_codec); self->video_send_result = 0; // let packets_worker know we handled EOF if (self->packets_thread_ret == AVERROR_EOF) { self->packets_thread_ret = 0; } // empty vpackets while (mlt_deque_count(self->vpackets) > 0) { AVPacket *tmp = (AVPacket *) mlt_deque_pop_front(self->vpackets); av_packet_free(&tmp); } pthread_cond_signal(&self->packets_cond); // Remove the cached info relating to the previous position self->current_position = POSITION_INVALID; self->last_position = POSITION_INVALID; av_frame_unref(self->video_frame); } } pthread_mutex_unlock(&self->packets_mutex); return paused; } /** Convert a frame position to a time code. */ static double producer_time_of_frame(mlt_producer producer, mlt_position position) { return (double) position / mlt_producer_get_fps(producer); } // Collect information about all audio streams static void get_audio_streams_info(producer_avformat self) { // Fetch the audio format context AVFormatContext *context = self->audio_format; unsigned int i; for (i = 0; i < context->nb_streams; i++) { if (context->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) { AVCodecParameters *codec_params = context->streams[i]->codecpar; const AVCodec *codec = avcodec_find_decoder(codec_params->codec_id); // Setup the codec context AVCodecContext *codec_context = avcodec_alloc_context3(codec); if (!codec_context) { mlt_log_info(MLT_PRODUCER_SERVICE(self->parent), "Failed to allocate the decoder context for stream #%d\n", i); continue; } int ret = avcodec_parameters_to_context(codec_context, codec_params); if (ret < 0) { mlt_log_info( MLT_PRODUCER_SERVICE(self->parent), "Failed to copy decoder parameters to input decoder context for stream #%d\n", i); continue; } // If we don't have a codec and we can't initialise it, we can't do much more... pthread_mutex_lock(&self->open_mutex); if (codec && avcodec_open2(codec_context, codec, NULL) >= 0) { self->audio_streams++; self->audio_max_stream = i; self->total_channels += codec_params->channels; if (codec_params->channels > self->max_channel) self->max_channel = codec_params->channels; if (codec_params->sample_rate > self->max_frequency) self->max_frequency = codec_params->sample_rate; avcodec_close(codec_context); } pthread_mutex_unlock(&self->open_mutex); } } mlt_log_verbose(NULL, "[producer avformat] audio: total_streams %d max_stream %d total_channels %d " "max_channels %d\n", self->audio_streams, self->audio_max_stream, self->total_channels, self->max_channel); } static mlt_audio_format pick_audio_format(int sample_fmt) { switch (sample_fmt) { // interleaved case AV_SAMPLE_FMT_U8: return mlt_audio_u8; case AV_SAMPLE_FMT_S16: return mlt_audio_s16; case AV_SAMPLE_FMT_S32: return mlt_audio_s32le; case AV_SAMPLE_FMT_FLT: return mlt_audio_f32le; // planar - this producer converts planar to interleaved case AV_SAMPLE_FMT_U8P: return mlt_audio_u8; case AV_SAMPLE_FMT_S16P: return mlt_audio_s16; case AV_SAMPLE_FMT_S32P: return mlt_audio_s32le; case AV_SAMPLE_FMT_FLTP: return mlt_audio_f32le; default: return mlt_audio_none; } } /** * Handle deprecated pixel format (JPEG range in YUV420P for example). * * Replace pix_fmt with the official pixel format to use. * @return 0 if no pix_fmt replacement, 1 otherwise */ static int pick_av_pixel_format(int *pix_fmt, int full_range) { switch (*pix_fmt) { case AV_PIX_FMT_YUV420P: case AV_PIX_FMT_YUVJ420P: *pix_fmt = full_range ? AV_PIX_FMT_YUVJ420P : AV_PIX_FMT_YUV420P; return 1; case AV_PIX_FMT_YUV411P: case AV_PIX_FMT_YUVJ411P: *pix_fmt = full_range ? AV_PIX_FMT_YUVJ411P : AV_PIX_FMT_YUV411P; return 1; case AV_PIX_FMT_YUV422P: case AV_PIX_FMT_YUVJ422P: *pix_fmt = full_range ? AV_PIX_FMT_YUVJ422P : AV_PIX_FMT_YUV422P; return 1; case AV_PIX_FMT_YUV444P: case AV_PIX_FMT_YUVJ444P: *pix_fmt = full_range ? AV_PIX_FMT_YUVJ444P : AV_PIX_FMT_YUV444P; return 1; case AV_PIX_FMT_YUV440P: case AV_PIX_FMT_YUVJ440P: *pix_fmt = full_range ? AV_PIX_FMT_YUVJ440P : AV_PIX_FMT_YUV440P; return 1; } return 0; } static void property_changed(mlt_service owner, producer_avformat self, char *name) { if (self && name && self->parent) { mlt_properties properties = MLT_PRODUCER_PROPERTIES(self->parent); if (!strcmp("color_range", name)) { if (self->video_codec && !av_opt_set(self->video_codec, name, mlt_properties_get(properties, name), AV_OPT_SEARCH_CHILDREN)) { if (self->full_range != (self->video_codec->color_range == AVCOL_RANGE_JPEG)) { self->full_range = self->video_codec->color_range == AVCOL_RANGE_JPEG; self->reset_image_cache = 1; } } } else if (!strcmp("force_full_range", name) || !strcmp("set.force_full_luma", name)) { if (self->full_range != mlt_properties_get_int(properties, name)) { self->full_range = mlt_properties_get_int(properties, name); self->reset_image_cache = 1; } } else if (!strcmp("force_progressive", name) || !strcmp("force_tff", name)) { self->reset_image_cache = 1; } else if (!strcmp("autorotate", name)) { self->autorotate = mlt_properties_get_int(properties, name); if (self->video_index != -1) { mlt_service_lock(MLT_PRODUCER_SERVICE(self->parent)); avfilter_graph_free(&self->vfilter_graph); self->vfilter_out = NULL; self->rotation = 0.0; setup_filters(self); self->reset_image_cache = 1; mlt_service_unlock(MLT_PRODUCER_SERVICE(self->parent)); } } else if (!strcmp("video_index", name) || !strcmp("vstream", name)) { if (mlt_properties_get_int(properties, "_probe_complete")) { mlt_properties_set_int(properties, "_probe_complete", 0); } } } } struct sliced_pix_fmt_conv_t { int width, height, slice_w; AVFrame *frame; uint8_t *out_data[4]; int out_stride[4]; enum AVPixelFormat src_format, dst_format; const AVPixFmtDescriptor *src_desc, *dst_desc; int flags, src_colorspace, dst_colorspace, src_full_range, dst_full_range; }; static int sliced_h_pix_fmt_conv_proc(int id, int idx, int jobs, void *cookie) { uint8_t *out[4]; const uint8_t *in[4]; int in_stride[4], out_stride[4]; int src_v_chr_pos = -513, dst_v_chr_pos = -513, ret, i, slice_x, slice_w, h, mul, field, slices, interlaced = 0; struct SwsContext *sws; struct sliced_pix_fmt_conv_t *ctx = (struct sliced_pix_fmt_conv_t *) cookie; interlaced = ctx->frame->interlaced_frame; field = (interlaced) ? (idx & 1) : 0; idx = (interlaced) ? (idx / 2) : idx; slices = (interlaced) ? (jobs / 2) : jobs; mul = (interlaced) ? 2 : 1; h = ctx->height >> !!interlaced; slice_w = ctx->slice_w; slice_x = slice_w * idx; slice_w = FFMIN(slice_w, ctx->width - slice_x); if (AV_PIX_FMT_YUV420P == ctx->src_format) src_v_chr_pos = (!interlaced) ? 128 : (!field) ? 64 : 192; if (AV_PIX_FMT_YUV420P == ctx->dst_format) dst_v_chr_pos = (!interlaced) ? 128 : (!field) ? 64 : 192; mlt_log_debug(NULL, "%s:%d: [id=%d, idx=%d, jobs=%d], interlaced=%d, field=%d, slices=%d, mul=%d, " "h=%d, slice_w=%d, slice_x=%d ctx->src_desc=[log2_chroma_h=%d, " "log2_chroma_w=%d], src_v_chr_pos=%d, dst_v_chr_pos=%d\n", __FUNCTION__, __LINE__, id, idx, jobs, interlaced, field, slices, mul, h, slice_w, slice_x, ctx->src_desc->log2_chroma_h, ctx->src_desc->log2_chroma_w, src_v_chr_pos, dst_v_chr_pos); if (slice_w <= 0) return 0; sws = sws_alloc_context(); av_opt_set_int(sws, "srcw", slice_w, 0); av_opt_set_int(sws, "srch", h, 0); av_opt_set_int(sws, "src_format", ctx->src_format, 0); av_opt_set_int(sws, "dstw", slice_w, 0); av_opt_set_int(sws, "dsth", h, 0); av_opt_set_int(sws, "dst_format", ctx->dst_format, 0); av_opt_set_int(sws, "sws_flags", ctx->flags, 0); av_opt_set_int(sws, "src_h_chr_pos", -513, 0); av_opt_set_int(sws, "src_v_chr_pos", src_v_chr_pos, 0); av_opt_set_int(sws, "dst_h_chr_pos", -513, 0); av_opt_set_int(sws, "dst_v_chr_pos", dst_v_chr_pos, 0); if ((ret = sws_init_context(sws, NULL, NULL)) < 0) { mlt_log_error(NULL, "%s:%d: sws_init_context failed, ret=%d\n", __FUNCTION__, __LINE__, ret); sws_freeContext(sws); return 0; } mlt_set_luma_transfer(sws, ctx->src_colorspace, ctx->dst_colorspace, ctx->src_full_range, ctx->dst_full_range); #define PIX_DESC_BPP(DESC) (DESC.step) for (i = 0; i < 4; i++) { int in_offset = (AV_PIX_FMT_FLAG_PLANAR & ctx->src_desc->flags) ? ((1 == i || 2 == i) ? (slice_x >> ctx->src_desc->log2_chroma_w) : slice_x) : ((0 == i) ? slice_x : 0); int out_offset = (AV_PIX_FMT_FLAG_PLANAR & ctx->dst_desc->flags) ? ((1 == i || 2 == i) ? (slice_x >> ctx->dst_desc->log2_chroma_w) : slice_x) : ((0 == i) ? slice_x : 0); in_offset *= PIX_DESC_BPP(ctx->src_desc->comp[i]); out_offset *= PIX_DESC_BPP(ctx->dst_desc->comp[i]); in_stride[i] = ctx->frame->linesize[i] * mul; out_stride[i] = ctx->out_stride[i] * mul; in[i] = ctx->frame->data[i] + ctx->frame->linesize[i] * field + in_offset; out[i] = ctx->out_data[i] + ctx->out_stride[i] * field + out_offset; } sws_scale(sws, in, in_stride, 0, h, out, out_stride); sws_freeContext(sws); return 0; } static int convert_image_yuvp(producer_avformat self, mlt_profile profile, AVFrame *frame, uint8_t *buffer, mlt_image_format format, int width, int height, int src_pix_fmt, int dst_pix_fmt, int dst_full_range) { int result = self->yuv_colorspace; int flags = mlt_get_sws_flags(width, height, src_pix_fmt, width, height, dst_pix_fmt); struct SwsContext *context = sws_getContext( width, height, src_pix_fmt, width, height, dst_pix_fmt, flags, NULL, NULL, NULL); uint8_t *out_data[4]; int out_stride[4]; mlt_image_format_planes(format, width, height, buffer, out_data, out_stride); if (!mlt_set_luma_transfer(context, self->yuv_colorspace, profile->colorspace, self->full_range, dst_full_range)) result = profile->colorspace; sws_scale(context, (const uint8_t *const *) frame->data, frame->linesize, 0, height, out_data, out_stride); sws_freeContext(context); return result; } static void convert_image_rgb(producer_avformat self, mlt_profile profile, AVFrame *frame, uint8_t *buffer, mlt_image_format format, int width, int height, int src_pix_fmt, int dst_pix_fmt, int dst_full_range) { int flags = mlt_get_sws_flags(width, height, src_pix_fmt, width, height, dst_pix_fmt); uint8_t *out_data[4]; int out_stride[4]; if (src_pix_fmt == AV_PIX_FMT_YUV420P && frame->interlaced_frame) { // Perform field-aware conversion for 4:2:0 int field_height = height / 2; const uint8_t *in_data[4]; int in_stride[4]; struct SwsContext *context = sws_getContext(width, field_height, src_pix_fmt, width, field_height, dst_pix_fmt, flags, NULL, NULL, NULL); // libswscale wants the RGB colorspace to be SWS_CS_DEFAULT, which is = SWS_CS_ITU601. mlt_set_luma_transfer(context, self->yuv_colorspace, 601, self->full_range, 1); av_image_fill_arrays(out_data, out_stride, buffer, dst_pix_fmt, width, height, IMAGE_ALIGN); // Copy the input frame arrays for (int i = 0; i < 4; i++) { in_data[i] = frame->data[i]; in_stride[i] = frame->linesize[i]; } // Modify the strides to skip every other line for (int i = 0; i < 4; i++) { in_stride[i] *= 2; out_stride[i] *= 2; } // Convert the first field sws_scale(context, in_data, in_stride, 0, field_height, out_data, out_stride); // Offset the data to point at the second field for (int i = 0; i < 4; i++) { in_data[i] += in_stride[i] / 2; out_data[i] += out_stride[i] / 2; } // Convert the second field sws_scale(context, in_data, in_stride, 0, field_height, out_data, out_stride); sws_freeContext(context); } else { struct SwsContext *context = sws_getContext( width, height, src_pix_fmt, width, height, dst_pix_fmt, flags, NULL, NULL, NULL); av_image_fill_arrays(out_data, out_stride, buffer, dst_pix_fmt, width, height, IMAGE_ALIGN); // libswscale wants the RGB colorspace to be SWS_CS_DEFAULT, which is = SWS_CS_ITU601. mlt_set_luma_transfer(context, self->yuv_colorspace, 601, self->full_range, 1); sws_scale(context, (const uint8_t *const *) frame->data, frame->linesize, 0, height, out_data, out_stride); sws_freeContext(context); } } // returns resulting YUV colorspace static int convert_image(producer_avformat self, AVFrame *frame, uint8_t *buffer, int pix_fmt, mlt_image_format *format, int width, int height, uint8_t **alpha, int dst_full_range) { mlt_profile profile = mlt_service_profile(MLT_PRODUCER_SERVICE(self->parent)); int result = self->yuv_colorspace; mlt_log_timings_begin(); mlt_log_debug(MLT_PRODUCER_SERVICE(self->parent), "%s @ %dx%d space %d->%d\n", mlt_image_format_name(*format), width, height, self->yuv_colorspace, profile->colorspace); // extract alpha from planar formats if ((pix_fmt == AV_PIX_FMT_YUVA420P || pix_fmt == AV_PIX_FMT_YUVA444P) && *format != mlt_image_rgba && frame->data[3] && frame->linesize[3]) { int i; uint8_t *src, *dst; dst = *alpha = mlt_pool_alloc(width * height); src = frame->data[3]; for (i = 0; i < height; dst += width, src += frame->linesize[3], i++) memcpy(dst, src, FFMIN(width, frame->linesize[3])); } // Figure out source and destination pixel format int src_pix_fmt = pix_fmt; pick_av_pixel_format(&src_pix_fmt, self->full_range); int dst_pix_fmt = AV_PIX_FMT_NONE; switch (*format) { case mlt_image_yuv420p: dst_pix_fmt = AV_PIX_FMT_YUV420P; break; case mlt_image_yuv420p10: dst_pix_fmt = AV_PIX_FMT_YUV420P10LE; break; case mlt_image_yuv444p10: dst_pix_fmt = AV_PIX_FMT_YUV444P10LE; break; case mlt_image_yuv422p16: dst_pix_fmt = AV_PIX_FMT_YUV422P16LE; break; case mlt_image_rgb: dst_pix_fmt = AV_PIX_FMT_RGB24; break; case mlt_image_rgba: dst_pix_fmt = AV_PIX_FMT_RGBA; break; case mlt_image_none: case mlt_image_yuv422: case mlt_image_movit: case mlt_image_opengl_texture: case mlt_image_invalid: break; } // Convert if (mlt_image_rgb == *format || mlt_image_rgba == *format) { convert_image_rgb(self, profile, frame, buffer, *format, width, height, src_pix_fmt, dst_pix_fmt, dst_full_range); } else if (dst_pix_fmt != AV_PIX_FMT_NONE) { result = convert_image_yuvp(self, profile, frame, buffer, *format, width, height, src_pix_fmt, dst_pix_fmt, dst_full_range); } else { int i, c; struct sliced_pix_fmt_conv_t ctx = { .width = width, .height = height, .frame = frame, .dst_format = AV_PIX_FMT_YUYV422, .src_colorspace = self->yuv_colorspace, .dst_colorspace = profile->colorspace, .src_full_range = self->full_range, .dst_full_range = dst_full_range, }; ctx.src_format = (self->full_range && src_pix_fmt == AV_PIX_FMT_YUV422P) ? AV_PIX_FMT_YUVJ422P : src_pix_fmt; ctx.src_desc = av_pix_fmt_desc_get(ctx.src_format); ctx.dst_desc = av_pix_fmt_desc_get(ctx.dst_format); ctx.flags = mlt_get_sws_flags(width, height, ctx.src_format, width, height, ctx.dst_format); av_image_fill_arrays(ctx.out_data, ctx.out_stride, buffer, ctx.dst_format, width, height, IMAGE_ALIGN); int sliced = !getenv("MLT_AVFORMAT_SLICED_PIXFMT_DISABLE") && src_pix_fmt != ctx.dst_format; if (sliced) { ctx.slice_w = (width < 1000) ? (256 >> frame->interlaced_frame) : (512 >> frame->interlaced_frame); } else { ctx.slice_w = width; } c = (width + ctx.slice_w - 1) / ctx.slice_w; int last_slice_w = width - ctx.slice_w * (c - 1); if (sliced && (last_slice_w % 8) == 0 && !(ctx.src_format == AV_PIX_FMT_YUV422P && last_slice_w % 16)) { c *= frame->interlaced_frame ? 2 : 1; mlt_slices_run_normal(c, sliced_h_pix_fmt_conv_proc, &ctx); } else { c = frame->interlaced_frame ? 2 : 1; ctx.slice_w = width; for (i = 0; i < c; i++) sliced_h_pix_fmt_conv_proc(i, i, c, &ctx); } result = profile->colorspace; } mlt_log_timings_end(NULL, __FUNCTION__); return result; } static void set_image_size(producer_avformat self, int *width, int *height) { #ifdef AVFILTER if (self->vfilter_out) { *width = av_buffersink_get_w(self->vfilter_out); *height = av_buffersink_get_h(self->vfilter_out); } else { #endif double dar = mlt_profile_dar(mlt_service_profile(MLT_PRODUCER_SERVICE(self->parent))); *width = self->video_codec->width; // Workaround 1088 encodings missing cropping info. if (self->video_codec->height == 1088 && dar == 16.0 / 9.0) *height = 1080; else *height = self->video_codec->height; #ifdef AVFILTER } #endif } /** Allocate the image buffer and set it on the frame. */ static int allocate_buffer(mlt_frame frame, AVCodecParameters *codec_params, uint8_t **buffer, mlt_image_format format, int width, int height) { int size = 0; if (codec_params->width == 0 || codec_params->height == 0) return size; size = mlt_image_format_size(format, width, height, NULL); *buffer = mlt_pool_alloc(size); if (*buffer) mlt_frame_set_image(frame, *buffer, size, mlt_pool_release); else size = 0; return size; } static int ignore_send_packet_result(int result) { return result >= 0 || result == AVERROR(EAGAIN) || result == AVERROR_EOF || result == AVERROR_INVALIDDATA || result == AVERROR(EINVAL); } static int is_album_art(producer_avformat self) { return self->video_index >= 0 && (self->video_format->streams[self->video_index]->disposition & AV_DISPOSITION_ATTACHED_PIC); } static void *packets_worker(void *param) { producer_avformat self = param; AVPacket *pkt = av_packet_alloc(); if (!pkt) { mlt_log_fatal(MLT_PRODUCER_SERVICE(self->parent), "av_packet_alloc failed\n"); exit(EXIT_FAILURE); } pthread_mutex_lock(&self->packets_mutex); for (;;) { check_stop: if (self->packets_thread_stop) { av_packet_free(&pkt); pthread_mutex_unlock(&self->packets_mutex); return NULL; } if (mlt_deque_count(self->vpackets) >= 1 || self->packets_thread_ret < 0) { pthread_cond_wait(&self->packets_cond, &self->packets_mutex); goto check_stop; } int ret = av_read_frame(self->video_format, pkt); // don't bother signalling on EAGAIN if (ret != AVERROR(EAGAIN)) { self->packets_thread_ret = ret; if (ret == 0) { if (pkt->stream_index == self->video_index) { mlt_deque_push_back(self->vpackets, av_packet_clone(pkt)); } else if (!self->video_seekable && pkt->stream_index == self->audio_index && !is_album_art(self)) { mlt_deque_push_back(self->apackets, av_packet_clone(pkt)); } av_packet_unref(pkt); } else if (ret != AVERROR_EOF) { mlt_log_verbose(MLT_PRODUCER_SERVICE(self->parent), "av_read_frame returned error %d inside packets_worker\n", ret); } pthread_cond_signal(&self->packets_cond); } } } static void init_cache(mlt_properties properties, mlt_cache *cache) { // if cache size supplied by environment variable int cache_supplied = getenv("MLT_AVFORMAT_CACHE") != NULL; int cache_size = cache_supplied ? atoi(getenv("MLT_AVFORMAT_CACHE")) : 0; // cache size supplied via property if (mlt_properties_get(properties, "cache")) { cache_supplied = 1; cache_size = mlt_properties_get_int(properties, "cache"); } if (mlt_properties_get_int(properties, "noimagecache")) { cache_supplied = 1; cache_size = 0; } // create cache if not disabled if (!cache_supplied || cache_size > 0) *cache = mlt_cache_init(); // set cache size if supplied if (*cache && cache_supplied) mlt_cache_set_size(*cache, cache_size); } /** Get an image from a frame. */ static int producer_get_image(mlt_frame frame, uint8_t **buffer, mlt_image_format *format, int *width, int *height, int writable) { // Get the producer (void) writable; // unused producer_avformat self = mlt_frame_pop_service(frame); mlt_producer producer = self->parent; // Get the properties from the frame mlt_properties frame_properties = MLT_FRAME_PROPERTIES(frame); // Obtain the frame number of this frame mlt_position position = mlt_frame_original_position(frame); // Get the producer properties mlt_properties properties = MLT_PRODUCER_PROPERTIES(producer); uint8_t *alpha = NULL; int got_picture = 0; int image_size = 0; const char *dst_color_range = mlt_properties_get(frame_properties, "consumer.color_range"); int dst_full_range = dst_color_range && (!strcmp("pc", dst_color_range) || !strcmp("jpeg", dst_color_range)); mlt_service_lock(MLT_PRODUCER_SERVICE(producer)); pthread_mutex_lock(&self->video_mutex); mlt_log_timings_begin(); #ifdef AVFILTER if (self->autorotate && self->video_index != -1 && get_rotation(properties, self->video_format->streams[self->video_index]) != self->rotation) { // Rotation has changed. Clear any cached frames. self->reset_image_cache = 1; } #endif if (self->reset_image_cache) { self->reset_image_cache = 0; mlt_cache_close(self->image_cache); self->image_cache = NULL; av_frame_free(&self->video_frame); } // Fetch the video format context AVFormatContext *context = self->video_format; AVCodecParameters *codec_params = NULL; if (!context) goto exit_get_image; // Get the video stream AVStream *stream = context->streams[self->video_index]; codec_params = stream->codecpar; // Always use the image cache for album art. if (is_album_art(self)) position = 0; // Get the image cache if (!self->image_cache) { init_cache(properties, &self->image_cache); } if (self->image_cache) { mlt_frame original = mlt_cache_get_frame(self->image_cache, position); if (original && (*format == mlt_image_none || *format == mlt_properties_get_int(MLT_FRAME_PROPERTIES(original), "format"))) { mlt_properties orig_props = MLT_FRAME_PROPERTIES(original); int size = 0; *buffer = mlt_frame_get_alpha_size(original, &size); if (*buffer) mlt_frame_set_alpha(frame, *buffer, size, NULL); *buffer = mlt_properties_get_data(orig_props, "image", &size); mlt_frame_set_image(frame, *buffer, size, NULL); mlt_properties_set_data(frame_properties, "avformat.image_cache", original, 0, (mlt_destructor) mlt_frame_close, NULL); *format = mlt_properties_get_int(orig_props, "format"); set_image_size(self, width, height); mlt_properties_pass_property(frame_properties, orig_props, "colorspace"); mlt_properties_set_int(frame_properties, "full_range", dst_full_range); got_picture = 1; goto exit_get_image; } else { mlt_frame_close(original); } } // Cache miss // We may want to use the source fps if available double source_fps = mlt_properties_get_double(properties, "meta.media.frame_rate_num") / mlt_properties_get_double(properties, "meta.media.frame_rate_den"); // This is the physical frame position in the source int64_t req_position = (int64_t) (position / mlt_producer_get_fps(producer) * source_fps + 0.5); // Determines if we have to decode all frames in a sequence - when there temporal compression is used. const AVCodecDescriptor *descriptor = avcodec_descriptor_get(codec_params->codec_id); int must_decode = descriptor && !(descriptor->props & AV_CODEC_PROP_INTRA_ONLY); double delay = mlt_properties_get_double(properties, "video_delay"); // Seek if necessary double speed = mlt_producer_get_speed(producer); int preseek = must_decode && self->video_codec->has_b_frames && speed >= 0.0 && speed <= 1.0; int paused = seek_video(self, position, req_position, preseek); // Seek might have reopened the file context = self->video_format; stream = context->streams[self->video_index]; codec_params = stream->codecpar; // Only change the requested image format for special cases #ifdef AVFILTER *format = pick_image_format(self->vfilter_out ? av_buffersink_get_format(self->vfilter_out) : codec_params->format, self->full_range, *format); #else *format = pick_image_format(codec_params->format, self->full_range, *format); #endif // Duplicate the last image if necessary if (self->video_frame && self->video_frame->linesize[0] && (self->pkt.stream_index == self->video_index) && (paused || self->current_position >= req_position)) { // Duplicate it set_image_size(self, width, height); if ((image_size = allocate_buffer(frame, codec_params, buffer, *format, *width, *height))) { int yuv_colorspace; #if USE_HWACCEL yuv_colorspace = convert_image(self, self->video_frame, *buffer, self->video_frame->format, format, *width, *height, &alpha, dst_full_range); #else yuv_colorspace = convert_image(self, self->video_frame, *buffer, codec_params->format, format, *width, *height, &alpha, dst_full_range); #endif mlt_properties_set_int(frame_properties, "colorspace", yuv_colorspace); mlt_properties_set_int(frame_properties, "full_range", dst_full_range); got_picture = 1; } } else { int64_t int_position = 0; int decode_errors = 0; // Construct an AVFrame for YUV422 conversion if (!self->video_frame) self->video_frame = av_frame_alloc(); else av_frame_unref(self->video_frame); if (!self->is_thread_init) { pthread_cond_init(&self->packets_cond, NULL); pthread_create(&self->packets_thread, NULL, packets_worker, self); self->is_thread_init = 1; } while (!got_picture && ignore_send_packet_result(self->video_send_result)) { if (self->video_send_result != AVERROR(EAGAIN)) { // Read a packet if (self->pkt.stream_index == self->video_index) av_packet_unref(&self->pkt); av_init_packet(&self->pkt); pthread_mutex_lock(&self->packets_mutex); while (mlt_deque_count(self->vpackets) == 0 && self->packets_thread_ret == 0) { pthread_cond_wait(&self->packets_cond, &self->packets_mutex); } if (self->packets_thread_ret == 0) { AVPacket *tmp = (AVPacket *) mlt_deque_pop_front(self->vpackets); av_packet_ref(&self->pkt, tmp); av_packet_free(&tmp); pthread_cond_signal(&self->packets_cond); } else { if (self->packets_thread_ret == AVERROR_EOF) { self->pkt.stream_index = self->video_index; } // notify packets_worker that we've seen the error self->packets_thread_ret = 0; pthread_cond_signal(&self->packets_cond); if (!self->video_seekable && mlt_properties_get_int(properties, "reconnect")) { // Try to reconnect to live sources by closing context and codecs, // and letting next call to get_frame() reopen. mlt_service_unlock(MLT_PRODUCER_SERVICE(producer)); prepare_reopen(self); mlt_service_lock(MLT_PRODUCER_SERVICE(producer)); pthread_mutex_unlock(&self->packets_mutex); goto exit_get_image; } if (!self->video_seekable && mlt_properties_get_int(properties, "exit_on_disconnect")) { mlt_log_fatal(MLT_PRODUCER_SERVICE(producer), "Exiting with error due to disconnected source.\n"); exit(EXIT_FAILURE); } // Send null packets to drain decoder. self->pkt.size = 0; self->pkt.data = NULL; } pthread_mutex_unlock(&self->packets_mutex); } // We only deal with video from the selected video_index if (self->pkt.stream_index == self->video_index) { int64_t pts = best_pts(self, self->pkt.pts, self->pkt.dts); if (pts != AV_NOPTS_VALUE) { if (!self->video_seekable && self->first_pts == AV_NOPTS_VALUE) self->first_pts = pts; if (self->first_pts != AV_NOPTS_VALUE) pts -= self->first_pts; else if (context->start_time != AV_NOPTS_VALUE) pts -= context->start_time; int_position = (int64_t) ((av_q2d(self->video_time_base) * pts + delay) * source_fps + 0.5); if (int_position == self->last_position) int_position = self->last_position + 1; } mlt_log_debug(MLT_PRODUCER_SERVICE(producer), "V pkt.pts %" PRId64 " pkt.dts %" PRId64 " req_pos %" PRId64 " cur_pos %" PRId64 " pkt_pos %" PRId64 "\n", self->pkt.pts, self->pkt.dts, req_position, self->current_position, int_position); // Make a dumb assumption on streams that contain wild timestamps if (llabs(req_position - int_position) > 999) { mlt_log_verbose(MLT_PRODUCER_SERVICE(producer), " WILD TIMESTAMP: " "pkt.pts=[%" PRId64 "], pkt.dts=[%" PRId64 "], req_position=[%" PRId64 "], " "current_position=[%" PRId64 "], int_position=[%" PRId64 "], pts=[%" PRId64 "] \n", self->pkt.pts, self->pkt.dts, req_position, self->current_position, int_position, pts); int_position = req_position; } self->last_position = int_position; // Decode the image if (must_decode || int_position >= req_position || !self->pkt.data) { self->video_codec->reordered_opaque = int_position; if (int_position >= req_position) self->video_codec->skip_loop_filter = AVDISCARD_NONE; self->video_send_result = avcodec_send_packet(self->video_codec, &self->pkt); mlt_log_debug(MLT_PRODUCER_SERVICE(producer), "decoded video packet with size %d => %d\n", self->pkt.size, self->video_send_result); // Note: decode may fail at the beginning of MPEGfile (B-frames referencing before first I-frame), so allow a few errors. if (!ignore_send_packet_result(self->video_send_result)) { mlt_log_warning(MLT_PRODUCER_SERVICE(producer), "video avcodec_send_packet failed with %d\n", self->video_send_result); } else { int error = avcodec_receive_frame(self->video_codec, self->video_frame); if (error < 0) { if (error != AVERROR(EAGAIN) && ++decode_errors > 10) { mlt_log_warning(MLT_PRODUCER_SERVICE(producer), "video decoding error %d\n", error); self->last_good_position = POSITION_INVALID; } } else { #if USE_HWACCEL if (self->hwaccel.device_ctx && self->video_frame->format == self->hwaccel.pix_fmt) { AVFrame *sw_video_frame = av_frame_alloc(); int transfer_data_result = av_hwframe_transfer_data(sw_video_frame, self->video_frame, 0); if (transfer_data_result < 0) { mlt_log_error(MLT_PRODUCER_SERVICE(producer), "av_hwframe_transfer_data() failed %d\n", transfer_data_result); av_frame_free(&sw_video_frame); goto exit_get_image; } av_frame_copy_props(sw_video_frame, self->video_frame); sw_video_frame->width = self->video_frame->width; sw_video_frame->height = self->video_frame->height; av_frame_unref(self->video_frame); av_frame_move_ref(self->video_frame, sw_video_frame); av_frame_free(&sw_video_frame); } #endif got_picture = 1; decode_errors = 0; } } } if (got_picture) { // Get position of reordered frame int_position = self->video_frame->reordered_opaque; pts = best_pts(self, self->video_frame->pts, self->video_frame->pkt_dts); if (pts != AV_NOPTS_VALUE) { // Some streams are not marking their key frames even though // there are I frames, and find_first_pts() fails as a result. // Try to set first_pts here after getting pict_type. if (self->first_pts == AV_NOPTS_VALUE && (self->video_frame->key_frame || self->video_frame->pict_type == AV_PICTURE_TYPE_I)) self->first_pts = pts; if (self->first_pts != AV_NOPTS_VALUE) pts -= self->first_pts; else if (context->start_time != AV_NOPTS_VALUE) pts -= context->start_time; int_position = (int64_t) ((av_q2d(self->video_time_base) * pts + delay) * source_fps + 0.5); } if (int_position < req_position) got_picture = 0; else if (int_position >= req_position) self->video_codec->skip_loop_filter = AVDISCARD_NONE; } else if (!self->pkt.data) // draining decoder with null packets { self->video_send_result = -1; } mlt_log_debug(MLT_PRODUCER_SERVICE(producer), " got_pic %d key %d send_result %d pkt_pos %" PRId64 "\n", got_picture, self->pkt.flags & AV_PKT_FLAG_KEY, self->video_send_result, int_position); } // Now handle the picture if we have one if (got_picture) { // Detect and correct scan type if (mlt_properties_get(properties, "force_progressive")) { self->progressive = !!mlt_properties_get_int(properties, "force_progressive"); } else if (self->video_frame && codec_params) { self->progressive = !self->video_frame->interlaced_frame && (codec_params->field_order == AV_FIELD_PROGRESSIVE || codec_params->field_order == AV_FIELD_UNKNOWN); } else { self->progressive = 0; } self->video_frame->interlaced_frame = !self->progressive; // Detect and correct field order if (mlt_properties_get(properties, "force_tff")) { self->top_field_first = !!mlt_properties_get_int(properties, "force_tff"); } else { self->top_field_first = self->video_frame->top_field_first || codec_params->field_order == AV_FIELD_TT || codec_params->field_order == AV_FIELD_TB; } self->video_frame->top_field_first = self->top_field_first; #ifdef AVFILTER if ((self->autorotate || mlt_properties_get(properties, "filtergraph")) && !setup_filters(self) && self->vfilter_graph) { int ret = av_buffersrc_add_frame(self->vfilter_in, self->video_frame); if (ret < 0) { got_picture = 0; break; } while (ret >= 0) { ret = av_buffersink_get_frame_flags(self->vfilter_out, self->video_frame, 0); if (ret < 0) { ret = 0; break; } } } #endif set_image_size(self, width, height); if ((image_size = allocate_buffer(frame, codec_params, buffer, *format, *width, *height))) { int yuv_colorspace; #if USE_HWACCEL // not sure why this is really needed, but doesn't seem to work otherwise yuv_colorspace = convert_image(self, self->video_frame, *buffer, self->video_frame->format, format, *width, *height, &alpha, dst_full_range); #else yuv_colorspace = convert_image(self, self->video_frame, *buffer, codec_params->format, format, *width, *height, &alpha, dst_full_range); #endif mlt_properties_set_int(frame_properties, "colorspace", yuv_colorspace); mlt_properties_set_int(frame_properties, "full_range", dst_full_range); self->current_position = int_position; } else { got_picture = 0; } } // Free packet data if not video and not live audio packet if (self->pkt.stream_index != self->video_index && !(!self->video_seekable && self->pkt.stream_index == self->audio_index)) av_packet_unref(&self->pkt); } } // set alpha if (alpha) mlt_frame_set_alpha(frame, alpha, (*width) * (*height), mlt_pool_release); if (image_size > 0) { mlt_properties_set_int(frame_properties, "format", *format); // Cache the image for rapid repeated access. if (self->image_cache) { if (is_album_art(self)) { mlt_position original_pos = mlt_frame_original_position(frame); mlt_properties_set_position(frame_properties, "original_position", 0); mlt_cache_put_frame_image(self->image_cache, frame); mlt_properties_set_position(frame_properties, "original_position", original_pos); } else { mlt_cache_put_frame_image(self->image_cache, frame); } } // Clone frame for error concealment. if (self->current_position >= self->last_good_position) { self->last_good_position = self->current_position; if (self->last_good_frame) mlt_frame_close(self->last_good_frame); self->last_good_frame = mlt_frame_clone(frame, 1); } } else if (self->last_good_frame) { // Use last known good frame if there was a decoding failure. mlt_frame original = mlt_frame_clone(self->last_good_frame, 1); mlt_properties orig_props = MLT_FRAME_PROPERTIES(original); int size = 0; *buffer = mlt_frame_get_alpha_size(original, &size); if (*buffer) mlt_frame_set_alpha(frame, *buffer, size, NULL); *buffer = mlt_properties_get_data(orig_props, "image", &size); mlt_frame_set_image(frame, *buffer, size, NULL); mlt_properties_set_data(frame_properties, "avformat.conceal_error", original, 0, (mlt_destructor) mlt_frame_close, NULL); *format = mlt_properties_get_int(orig_props, "format"); set_image_size(self, width, height); got_picture = 1; } // Regardless of speed, we expect to get the next frame (cos we ain't too bright) self->video_expected = position + 1; exit_get_image: pthread_mutex_unlock(&self->video_mutex); mlt_properties_set_int(frame_properties, "progressive", self->progressive); mlt_properties_set_int(frame_properties, "top_field_first", self->top_field_first); // Set immutable properties of the selected track's (or overridden) source attributes. mlt_properties_set_int(properties, "meta.media.top_field_first", self->top_field_first); mlt_properties_set_int(properties, "meta.media.progressive", self->progressive); mlt_properties_set_int(properties, "_probe_complete", 1); mlt_service_unlock(MLT_PRODUCER_SERVICE(producer)); mlt_log_timings_end(NULL, __FUNCTION__); return !got_picture; } /** Process properties as AVOptions and apply to AV context obj */ static void apply_properties(void *obj, mlt_properties properties, int flags) { int i; int count = mlt_properties_count(properties); for (i = 0; i < count; i++) { const char *opt_name = mlt_properties_get_name(properties, i); int search_flags = AV_OPT_SEARCH_CHILDREN; const AVOption *opt = av_opt_find(obj, opt_name, NULL, flags, search_flags); if (opt_name && mlt_properties_get(properties, opt_name) && strcmp(opt_name, "seekable")) { if (opt) av_opt_set(obj, opt_name, mlt_properties_get(properties, opt_name), search_flags); } } } /** Initialize the video codec context. */ static int video_codec_init(producer_avformat self, int index, mlt_properties properties) { // Initialise the codec if necessary if (!self->video_codec) { // Get the video stream AVStream *stream = self->video_format->streams[index]; // Get codec context AVCodecParameters *codec_params = stream->codecpar; // Find the codec const AVCodec *codec = avcodec_find_decoder(codec_params->codec_id); if (mlt_properties_get(properties, "vcodec")) { if (!(codec = avcodec_find_decoder_by_name(mlt_properties_get(properties, "vcodec")))) codec = avcodec_find_decoder(codec_params->codec_id); } else if (codec_params->codec_id == AV_CODEC_ID_VP9) { if (!(codec = avcodec_find_decoder_by_name("libvpx-vp9"))) codec = avcodec_find_decoder(codec_params->codec_id); } else if (codec_params->codec_id == AV_CODEC_ID_VP8) { if (!(codec = avcodec_find_decoder_by_name("libvpx"))) codec = avcodec_find_decoder(codec_params->codec_id); } // Setup the codec context AVCodecContext *codec_context = avcodec_alloc_context3(codec); if (!codec_context) { mlt_log_error(MLT_PRODUCER_SERVICE(self->parent), "Failed to allocate the decoder context for video stream #%d\n", index); self->video_index = -1; return 0; } int ret = avcodec_parameters_to_context(codec_context, codec_params); if (ret < 0) { mlt_log_error( MLT_PRODUCER_SERVICE(self->parent), "Failed to copy decoder parameters to input decoder context for video stream #%d\n", index); self->video_index = -1; return 0; } // Initialise multi-threading int thread_count = mlt_properties_get_int(properties, "threads"); if (thread_count == 0 && getenv("MLT_AVFORMAT_THREADS")) thread_count = atoi(getenv("MLT_AVFORMAT_THREADS")); if (thread_count >= 0) codec_context->thread_count = thread_count; #if USE_HWACCEL if (self->hwaccel.device_type == AV_HWDEVICE_TYPE_NONE || self->hwaccel.pix_fmt == AV_PIX_FMT_NONE) { mlt_log_debug(MLT_PRODUCER_SERVICE(self->parent), "missing hwaccel parameters. skipping hardware initialization\n"); goto skip_hwaccel; } int found_hw_pix_fmt = 0, i; for (i = 0;; i++) { const AVCodecHWConfig *config = avcodec_get_hw_config(codec, i); if (!config) break; if (config->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX && config->device_type == self->hwaccel.device_type && config->pix_fmt == self->hwaccel.pix_fmt) { found_hw_pix_fmt = 1; break; } } if (found_hw_pix_fmt) { av_buffer_unref(&self->hwaccel.device_ctx); int ret = av_hwdevice_ctx_create(&self->hwaccel.device_ctx, self->hwaccel.device_type, self->hwaccel.device, NULL, 0); if (ret >= 0) { codec_context->hw_device_ctx = av_buffer_ref(self->hwaccel.device_ctx); mlt_log_info(MLT_PRODUCER_SERVICE(self->parent), "av_hwdevice_ctx_create() success %d\n", codec_context->pix_fmt); } else { mlt_log_warning(MLT_PRODUCER_SERVICE(self->parent), "av_hwdevice_ctx_create() failed %d\n", ret); } } else { mlt_log_warning(MLT_PRODUCER_SERVICE(self->parent), "failed to find hw_pix_fmt\n"); } skip_hwaccel: #endif // If we don't have a codec and we can't initialise it, we can't do much more... pthread_mutex_lock(&self->open_mutex); if (codec && avcodec_open2(codec_context, codec, NULL) >= 0) { // Switch to the native vp8/vp9 decoder if not yuva420p if (codec_params->format != AV_PIX_FMT_YUVA420P && !mlt_properties_get(properties, "vcodec") && (!strcmp(codec->name, "libvpx") || !strcmp(codec->name, "libvpx-vp9"))) { codec = avcodec_find_decoder(codec_params->codec_id); if (codec && avcodec_open2(codec_context, codec, NULL) < 0) { self->video_index = -1; pthread_mutex_unlock(&self->open_mutex); return 0; } } // Now store the codec with its destructor self->video_codec = codec_context; } else { // Remember that we can't use this later self->video_index = -1; pthread_mutex_unlock(&self->open_mutex); return 0; } pthread_mutex_unlock(&self->open_mutex); // Process properties as AVOptions apply_properties(codec_context, properties, AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_DECODING_PARAM); if (codec && codec->priv_class && codec_context->priv_data) apply_properties(codec_context->priv_data, properties, AV_OPT_FLAG_VIDEO_PARAM | AV_OPT_FLAG_DECODING_PARAM); // Reset some image properties mlt_properties_set_int(properties, "width", codec_params->width); mlt_properties_set_int(properties, "height", codec_params->height); get_aspect_ratio(properties, stream, codec_params); // Start with the muxer frame rate. AVRational frame_rate = stream->avg_frame_rate; double fps = av_q2d(frame_rate); // Verify and sanitize the muxer frame rate. if (isnan(fps) || isinf(fps) || fps == 0) { frame_rate = stream->r_frame_rate; fps = av_q2d(frame_rate); } // With my samples when r_frame_rate != 1000 but avg_frame_rate is valid, // avg_frame_rate gives some approximate value that does not well match the media. // Also, on my sample where r_frame_rate = 1000, using avg_frame_rate directly // results in some very choppy output, but some value slightly different works // great. if (av_q2d(stream->r_frame_rate) >= 1000 && av_q2d(stream->avg_frame_rate) > 0) { frame_rate = av_d2q(av_q2d(stream->avg_frame_rate), 1024); fps = av_q2d(frame_rate); } // XXX frame rates less than 1 fps are not considered sane if (isnan(fps) || isinf(fps) || fps < 1.0) { // Get the frame rate from the codec. frame_rate.num = self->video_codec->time_base.den; frame_rate.den = self->video_codec->time_base.num * self->video_codec->ticks_per_frame; fps = av_q2d(frame_rate); } if (isnan(fps) || isinf(fps) || fps < 1.0) { // Use the profile frame rate if all else fails. mlt_profile profile = mlt_service_profile(MLT_PRODUCER_SERVICE(self->parent)); frame_rate.num = profile->frame_rate_num; frame_rate.den = profile->frame_rate_den; } // Normalize broadcast frame rates for Matroska if (self->video_format->iformat->name && strstr(self->video_format->iformat->name, "matroska")) { switch (lrint(100000.0 * frame_rate.num / frame_rate.den)) { case 2997003: frame_rate.num = 30000; frame_rate.den = 1001; break; case 5994006: frame_rate.num = 60000; frame_rate.den = 1001; break; case 2397602: frame_rate.num = 24000; frame_rate.den = 1001; break; case 4795204: frame_rate.num = 48000; frame_rate.den = 1001; break; default: break; } } self->video_time_base = stream->time_base; if (mlt_properties_get(properties, "force_fps")) { AVRational force_fps = av_d2q(mlt_properties_get_double(properties, "force_fps"), 1024); self->video_time_base = av_mul_q(stream->time_base, av_div_q(frame_rate, force_fps)); frame_rate = force_fps; } mlt_properties_set_int(properties, "meta.media.frame_rate_num", frame_rate.num); mlt_properties_set_int(properties, "meta.media.frame_rate_den", frame_rate.den); // Cover art is a single image at 90000 fps, which is not seekable. if (stream->disposition & AV_DISPOSITION_ATTACHED_PIC) self->video_seekable = 0; // Set the YUV colorspace from override or detect self->yuv_colorspace = mlt_properties_get_int(properties, "force_colorspace"); if (!self->yuv_colorspace) { switch (self->video_codec->colorspace) { case AVCOL_SPC_SMPTE240M: self->yuv_colorspace = 240; break; case AVCOL_SPC_BT470BG: case AVCOL_SPC_SMPTE170M: self->yuv_colorspace = 601; break; case AVCOL_SPC_BT709: self->yuv_colorspace = 709; break; default: // This is a heuristic Charles Poynton suggests in "Digital Video and HDTV" self->yuv_colorspace = self->video_codec->width * self->video_codec->height > 750000 ? 709 : 601; break; } } // Let apps get chosen colorspace mlt_properties_set_int(properties, "meta.media.colorspace", self->yuv_colorspace); // Get the color transfer characteristic (gamma). self->color_trc = mlt_properties_get_int(properties, "force_color_trc"); if (!self->color_trc) self->color_trc = self->video_codec->color_trc; mlt_properties_set_int(properties, "meta.media.color_trc", self->color_trc); // Get the RGB color primaries. switch (self->video_codec->color_primaries) { case AVCOL_PRI_BT470BG: self->color_primaries = 601625; break; case AVCOL_PRI_SMPTE170M: case AVCOL_PRI_SMPTE240M: self->color_primaries = 601525; break; case AVCOL_PRI_BT709: case AVCOL_PRI_UNSPECIFIED: default: self->color_primaries = 709; break; } mlt_properties_set_int(properties, "meta.media.has_b_frames", self->video_codec->has_b_frames); self->full_range = codec_context->color_range == AVCOL_RANGE_JPEG; if (mlt_properties_get(properties, "force_full_range")) { self->full_range = mlt_properties_get_int(properties, "force_full_range"); } else if (mlt_properties_get(properties, "set.force_full_luma")) { // deprecated self->full_range = mlt_properties_get_int(properties, "set.force_full_luma"); } } return self->video_index > -1; } static int pick_video_stream(producer_avformat self) { mlt_properties properties = MLT_PRODUCER_PROPERTIES(self->parent); int absolute_index; if (self->video_format && mlt_properties_get(properties, "vstream")) { // Get the relative stream index absolute_index = absolute_stream_index(self->video_format, AVMEDIA_TYPE_VIDEO, mlt_properties_get_int(properties, "vstream")); } else { // Failover to the absolute index absolute_index = mlt_properties_get_int(properties, "video_index"); if (self->video_format) { // Compute the relative stream index mlt_properties_set_int(properties, "vstream", relative_stream_index(self->video_format, AVMEDIA_TYPE_VIDEO, absolute_index)); } } if (mlt_properties_get_int(properties, "video_index") != absolute_index) { // Update the absolute index mlt_properties_set_int(properties, "video_index", absolute_index); self->video_index = absolute_index; } return absolute_index; } /** Set up video handling. */ static void producer_set_up_video(producer_avformat self, mlt_frame frame) { // Get the producer mlt_producer producer = self->parent; // Get the properties mlt_properties properties = MLT_PRODUCER_PROPERTIES(producer); // Fetch the video format context AVFormatContext *context = self->video_format; // Get the video stream index int index = mlt_properties_get(properties, "vstream") ? mlt_properties_get_int(properties, "vstream") : mlt_properties_get_int(properties, "video_index"); int unlock_needed = 0; // Reopen the file if necessary if (!context && index > -1) { unlock_needed = 1; pthread_mutex_lock(&self->video_mutex); producer_open(self, mlt_service_profile(MLT_PRODUCER_SERVICE(producer)), mlt_properties_get(properties, "resource"), 0, 0); context = self->video_format; } index = pick_video_stream(self); // Exception handling for video_index if (context && index >= (int) context->nb_streams) { // Get the last video stream for (index = context->nb_streams - 1; index >= 0 && context->streams[index]->codecpar->codec_type != AVMEDIA_TYPE_VIDEO; index--) ; mlt_properties_set_int(properties, "video_index", index); mlt_properties_set_int(properties, "vstream", relative_stream_index(context, AVMEDIA_TYPE_VIDEO, index)); } if (context && index > -1 && context->streams[index]->codecpar->codec_type != AVMEDIA_TYPE_VIDEO) { // Invalidate the video stream index = -1; mlt_properties_set_int(properties, "video_index", index); mlt_properties_set_int(properties, "vstream", relative_stream_index(context, AVMEDIA_TYPE_VIDEO, index)); } // Update the video properties if the index changed if (context && index > -1 && index != self->video_index) { // Reset the video properties if the index changed self->video_index = index; mlt_properties_set_int(properties, "_probe_complete", 0); pthread_mutex_lock(&self->open_mutex); avcodec_free_context(&self->video_codec); set_up_discard(self, self->audio_index, index); pthread_mutex_unlock(&self->open_mutex); } // Get the frame properties mlt_properties frame_properties = MLT_FRAME_PROPERTIES(frame); // Get the codec if (context && index > -1 && video_codec_init(self, index, properties)) { // Set the frame properties double force_aspect_ratio = mlt_properties_get_double(properties, "force_aspect_ratio"); double aspect_ratio = (force_aspect_ratio > 0.0) ? force_aspect_ratio : mlt_properties_get_double(properties, "aspect_ratio"); // Set the width and height double dar = mlt_profile_dar(mlt_service_profile(MLT_PRODUCER_SERVICE(producer))); double theta = self->autorotate ? get_rotation(properties, self->video_format->streams[index]) : 0.0; if (fabs(theta - 90.0) < 1.0 || fabs(theta - 270.0) < 1.0) { // Workaround 1088 encodings missing cropping info. if (self->video_codec->height == 1088 && dar == 16.0 / 9.0) { mlt_properties_set_int(frame_properties, "width", 1080); mlt_properties_set_int(properties, "meta.media.width", 1080); mlt_properties_set_int(properties, "width", 1080); } else { mlt_properties_set_int(frame_properties, "width", self->video_codec->height); mlt_properties_set_int(properties, "meta.media.width", self->video_codec->height); mlt_properties_set_int(properties, "width", self->video_codec->height); } mlt_properties_set_int(frame_properties, "height", self->video_codec->width); mlt_properties_set_int(properties, "meta.media.height", self->video_codec->width); mlt_properties_set_int(properties, "height", self->video_codec->width); aspect_ratio = (force_aspect_ratio > 0.0) ? force_aspect_ratio : 1.0 / aspect_ratio; mlt_properties_set_double(frame_properties, "aspect_ratio", 1.0 / aspect_ratio); } else { mlt_properties_set_int(frame_properties, "width", self->video_codec->width); mlt_properties_set_int(properties, "meta.media.width", self->video_codec->width); mlt_properties_set_int(properties, "width", self->video_codec->width); // Workaround 1088 encodings missing cropping info. if (self->video_codec->height == 1088 && dar == 16.0 / 9.0) { mlt_properties_set_int(frame_properties, "height", 1080); mlt_properties_set_int(properties, "meta.media.height", 1080); mlt_properties_set_int(properties, "height", 1080); } else { mlt_properties_set_int(frame_properties, "height", self->video_codec->height); mlt_properties_set_int(properties, "meta.media.height", self->video_codec->height); mlt_properties_set_int(properties, "height", self->video_codec->height); } mlt_properties_set_double(frame_properties, "aspect_ratio", aspect_ratio); } mlt_properties_set_int(frame_properties, "colorspace", self->yuv_colorspace); mlt_properties_set_int(frame_properties, "color_trc", self->color_trc); mlt_properties_set_int(frame_properties, "color_primaries", self->color_primaries); // full_range is the current state of frame mlt_properties_set_int(frame_properties, "full_range", self->full_range); // "full_luma" is deprecated but keep this for backwards compatibility for kdenlive mlt_properties_set_int(frame_properties, "full_luma", self->full_range); // meta.media.color_range is the range of this producer per video_index regardless of override mlt_properties_set(properties, "meta.media.color_range", self->full_range ? "full" : "mpeg"); // Add our image operation mlt_frame_push_service(frame, self); mlt_frame_push_get_image(frame, producer_get_image); } else { // If something failed, use test card image mlt_properties_set_int(frame_properties, "test_image", 1); } if (unlock_needed) pthread_mutex_unlock(&self->video_mutex); } static int seek_audio(producer_avformat self, mlt_position position, double timecode) { int paused = 0; pthread_mutex_lock(&self->packets_mutex); // Seek if necessary if (self->seekable && (position != self->audio_expected || self->last_position < 0)) { if (self->last_position == POSITION_INITIAL) { int video_index = self->video_index; if (video_index == -1) video_index = first_video_index(self); if (self->first_pts == AV_NOPTS_VALUE && video_index >= 0) find_first_pts(self, video_index); } if (position + 1 == self->audio_expected && mlt_properties_get_int(MLT_PRODUCER_PROPERTIES(self->parent), "mute_on_pause")) { // We're paused - silence required paused = 1; } else if (position < self->audio_expected || position - self->audio_expected >= 12) { AVFormatContext *context = self->audio_format; int64_t timestamp = llrint(timecode * AV_TIME_BASE); if (context->start_time != AV_NOPTS_VALUE) timestamp += context->start_time; if (timestamp < 0) timestamp = 0; // Set to the real timecode if (av_seek_frame(context, -1, timestamp, AVSEEK_FLAG_BACKWARD) != 0) paused = 1; // Clear the usage in the audio buffer int i = MAX_AUDIO_STREAMS + 1; while (--i) self->audio_used[i - 1] = 0; } } pthread_mutex_unlock(&self->packets_mutex); return paused; } static int sample_bytes(AVCodecContext *context) { return av_get_bytes_per_sample(context->sample_fmt); } static void planar_to_interleaved( uint8_t *dest, AVFrame *src, int samples, int channels, int bytes_per_sample) { int s, c; for (s = 0; s < samples; s++) { for (c = 0; c < channels; c++) { if (c < AV_NUM_DATA_POINTERS) memcpy(dest, &src->data[c][s * bytes_per_sample], bytes_per_sample); dest += bytes_per_sample; } } } static int decode_audio(producer_avformat self, int *ignore, const AVPacket *pkt, int samples, double timecode, double fps) { // Fetch the audio_format AVFormatContext *context = self->audio_format; // Get the current stream index int index = pkt->stream_index; // Get codec context AVCodecContext *codec_context = self->audio_codec[index]; // Obtain the audio buffers uint8_t *audio_buffer = self->audio_buffer[index]; int channels = codec_context->channels; int audio_used = self->audio_used[index]; int audio_used_at_start = audio_used; int ret = 0; int discarded = 1; int sizeof_sample = sample_bytes(codec_context); // Decode the audio if (!self->audio_frame) self->audio_frame = av_frame_alloc(); else av_frame_unref(self->audio_frame); int error = avcodec_send_packet(codec_context, pkt); mlt_log_debug(MLT_PRODUCER_SERVICE(self->parent), "decoded audio packet with size %d => %d\n", pkt->size, error); if (!ignore_send_packet_result(error)) { mlt_log_warning(MLT_PRODUCER_SERVICE(self->parent), "audio avcodec_send_packet failed with %d\n", error); } else while (!error) { error = avcodec_receive_frame(codec_context, self->audio_frame); if (error) { if (error != AVERROR(EAGAIN)) { mlt_log_warning(MLT_PRODUCER_SERVICE(self->parent), "audio decoding error %d\n", error); } } else { // Figure out how many samples will be needed after resampling int convert_samples = self->audio_frame->nb_samples; channels = codec_context->channels; ret += convert_samples * channels * sizeof_sample; // Resize audio buffer to prevent overflow if ((audio_used + convert_samples) * channels * sizeof_sample > self->audio_buffer_size[index]) { self->audio_buffer_size[index] = (audio_used + convert_samples * 2) * channels * sizeof_sample; audio_buffer = self->audio_buffer[index] = mlt_pool_realloc(audio_buffer, self->audio_buffer_size[index]); } uint8_t *dest = &audio_buffer[audio_used * channels * sizeof_sample]; switch (codec_context->sample_fmt) { case AV_SAMPLE_FMT_U8P: case AV_SAMPLE_FMT_S16P: case AV_SAMPLE_FMT_S32P: case AV_SAMPLE_FMT_FLTP: planar_to_interleaved(dest, self->audio_frame, convert_samples, channels, sizeof_sample); break; default: { int data_size = av_samples_get_buffer_size(NULL, channels, self->audio_frame->nb_samples, codec_context->sample_fmt, 1); // Straight copy to audio buffer memcpy(dest, self->audio_frame->data[0], data_size); } } audio_used += convert_samples; discarded = 0; } } // Handle ignore if (*ignore > 0 && audio_used) { int n = FFMIN(audio_used, *ignore); *ignore -= n; audio_used -= n; audio_used_at_start -= n; memmove(audio_buffer, &audio_buffer[n * channels * sizeof_sample], audio_used * channels * sizeof_sample); } // If we're behind, ignore this packet // Skip this on non-seekable, audio-only inputs. if (!discarded && pkt->pts >= 0 && (self->seekable || self->video_format) && *ignore == 0 && audio_used > samples / 2) { double timebase = av_q2d(context->streams[index]->time_base); int64_t pts_offset = lrint((double) audio_used_at_start / timebase / (double) codec_context->sample_rate); int64_t pts = pkt->pts - pts_offset; if (self->first_pts != AV_NOPTS_VALUE && self->video_index != -1) pts -= av_rescale_q(self->first_pts, context->streams[self->video_index]->time_base, context->streams[index]->time_base); else if (self->first_pts != AV_NOPTS_VALUE) pts -= self->first_pts; else if (context->start_time != AV_NOPTS_VALUE && self->video_index != -1) pts -= av_rescale_q(context->start_time, AV_TIME_BASE_Q, context->streams[index]->time_base); int64_t int_position = llrint(timebase * pts * fps); int64_t req_position = llrint(timecode * fps); int64_t req_pts = llrint(timecode / timebase); mlt_log_debug(MLT_PRODUCER_SERVICE(self->parent), "A pkt.pts %" PRId64 " pkt->dts %" PRId64 " req_pos %" PRId64 " cur_pos %" PRId64 " pkt_pos %" PRId64 "\n", pkt->pts, pkt->dts, req_position, self->current_position, int_position); if (self->seekable || int_position > 0) { int64_t ahead_threshold = 2; if (codec_context->codec_id == AV_CODEC_ID_WMAPRO) { // WMAPro needs more tolerance for sync detection ahead_threshold = 4; } if (req_pts > pts) { // We are behind, so skip some *ignore = lrint(timebase * (req_pts - pts) * codec_context->sample_rate); } else if (self->audio_index != INT_MAX && int_position > req_position + ahead_threshold && !self->is_audio_synchronizing) { // We are ahead, so seek backwards some more. // Supply -1 as the position to defeat the checks needed by for the other // call to seek_audio() at the beginning of producer_get_audio(). Otherwise, // more often than not, req_position will equal audio_expected. seek_audio(self, -1, timecode - 1.0); self->is_audio_synchronizing = 1; } } // Cancel the find_first_pts() in seek_audio() if (self->video_index == -1 && self->last_position == POSITION_INITIAL) self->last_position = int_position; } self->audio_used[index] = audio_used; return ret; } /** Get the audio from a frame. */ static int producer_get_audio(mlt_frame frame, void **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples) { // Get the producer producer_avformat self = mlt_frame_pop_audio(frame); pthread_mutex_lock(&self->audio_mutex); // Obtain the frame number of this frame mlt_position position = mlt_frame_original_position(frame); int paused = position + 1 == self->audio_expected && mlt_properties_get_int(MLT_PRODUCER_PROPERTIES(self->parent), "mute_on_pause"); if (!paused) { // Check the audio cache if not paused if (!self->audio_cache) { init_cache(MLT_PRODUCER_PROPERTIES(self->parent), &self->audio_cache); } else { mlt_frame original = mlt_cache_get_frame(self->audio_cache, position); if (original) { mlt_frame_get_audio(original, buffer, format, frequency, channels, samples); mlt_properties_set_data(MLT_FRAME_PROPERTIES(frame), "avformat.audio_cache", original, 0, (mlt_destructor) mlt_frame_close, NULL); mlt_properties_pass_property(MLT_FRAME_PROPERTIES(frame), MLT_FRAME_PROPERTIES(original), "channel_layout"); goto done_get_audio; } } } // Calculate the real time code double real_timecode = producer_time_of_frame(self->parent, position); // Get the producer fps double fps = mlt_producer_get_fps(self->parent); if (mlt_properties_get(MLT_FRAME_PROPERTIES(frame), "producer_consumer_fps")) fps = mlt_properties_get_double(MLT_FRAME_PROPERTIES(frame), "producer_consumer_fps"); // Number of frames to ignore (for ffwd) int ignore[MAX_AUDIO_STREAMS] = {0}; // Flag for paused (silence) double timecode = self->audio_expected > 0 ? real_timecode : FFMAX(real_timecode - 0.25, 0.0); paused = seek_audio(self, position, timecode); // Initialize ignore for all streams from the seek return value int i = MAX_AUDIO_STREAMS; while (i--) ignore[i] = ignore[0]; // Fetch the audio_format AVFormatContext *context = self->audio_format; if (!context) goto exit_get_audio; int sizeof_sample = sizeof(int16_t); // Determine the tracks to use int index = self->audio_index; int index_max = self->audio_index + 1; if (self->audio_index == INT_MAX) { index = 0; index_max = FFMIN(MAX_AUDIO_STREAMS, context->nb_streams); *channels = self->total_channels; *samples = mlt_audio_calculate_frame_samples(fps, self->max_frequency, position); *frequency = self->max_frequency; } // Initialize the buffers for (; index < index_max && index < MAX_AUDIO_STREAMS; index++) { // Get codec context AVCodecContext *codec_context = self->audio_codec[index]; if (codec_context && !self->audio_buffer[index]) { if (self->audio_index != INT_MAX && !mlt_properties_get(MLT_PRODUCER_PROPERTIES(self->parent), "request_channel_layout")) codec_context->request_channel_layout = av_get_default_channel_layout(*channels); sizeof_sample = sample_bytes(codec_context); // Check for audio buffer and create if necessary self->audio_buffer_size[index] = MAX_AUDIO_FRAME_SIZE * sizeof_sample; self->audio_buffer[index] = mlt_pool_alloc(self->audio_buffer_size[index]); // Check for decoder buffer and create if necessary self->decode_buffer[index] = av_malloc(self->audio_buffer_size[index]); } } // Get the audio if required if (!paused && *frequency > 0) { int ret = 0; int got_audio = 0; AVPacket pkt; mlt_channel_layout layout = mlt_channel_auto; av_init_packet(&pkt); // Caller requested number samples based on requested sample rate. if (self->audio_index != INT_MAX) *samples = mlt_audio_calculate_frame_samples(fps, self->audio_codec[self->audio_index] ->sample_rate, position); while (ret >= 0 && !got_audio) { // Check if the buffer already contains the samples required if (self->audio_index != INT_MAX && self->audio_used[self->audio_index] >= *samples && ignore[self->audio_index] == 0) { got_audio = 1; break; } else if (self->audio_index == INT_MAX) { // Check if there is enough audio for all streams got_audio = 1; for (index = 0; got_audio && index < index_max; index++) if ((self->audio_codec[index] && self->audio_used[index] < *samples) || ignore[index]) got_audio = 0; if (got_audio) break; } // Read a packet pthread_mutex_lock(&self->packets_mutex); if (mlt_deque_count(self->apackets)) { AVPacket *tmp = (AVPacket *) mlt_deque_pop_front(self->apackets); av_packet_ref(&pkt, tmp); av_packet_free(&tmp); } else { ret = av_read_frame(context, &pkt); if (ret >= 0 && !self->seekable && pkt.stream_index == self->video_index) { mlt_deque_push_back(self->vpackets, av_packet_clone(&pkt)); } else if (ret == AVERROR(EAGAIN)) { ret = 0; pthread_mutex_unlock(&self->packets_mutex); continue; } else if (ret < 0) { mlt_producer producer = self->parent; mlt_properties properties = MLT_PRODUCER_PROPERTIES(producer); if (ret != AVERROR_EOF) mlt_log_verbose(MLT_PRODUCER_SERVICE(producer), "av_read_frame returned error %d inside get_audio\n", ret); if (!self->seekable && mlt_properties_get_int(properties, "reconnect")) { // Try to reconnect to live sources by closing context and codecs, // and letting next call to get_frame() reopen. prepare_reopen(self); pthread_mutex_unlock(&self->packets_mutex); goto exit_get_audio; } if (!self->seekable && mlt_properties_get_int(properties, "exit_on_disconnect")) { mlt_log_fatal(MLT_PRODUCER_SERVICE(producer), "Exiting with error due to disconnected source.\n"); exit(EXIT_FAILURE); } } } pthread_mutex_unlock(&self->packets_mutex); // We only deal with audio from the selected audio index index = pkt.stream_index; if (index < MAX_AUDIO_STREAMS && ret >= 0 && pkt.data && pkt.size > 0 && (index == self->audio_index || (self->audio_index == INT_MAX && context->streams[index]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO))) { ret = decode_audio(self, &ignore[index], &pkt, *samples, real_timecode, fps); } if (self->seekable || index != self->video_index) av_packet_unref(&pkt); } self->is_audio_synchronizing = 0; // Set some additional return values *format = mlt_audio_s16; if (self->audio_index != INT_MAX) { index = self->audio_index; *channels = self->audio_codec[index]->channels; *frequency = self->audio_codec[index]->sample_rate; *format = pick_audio_format(self->audio_codec[index]->sample_fmt); sizeof_sample = sample_bytes(self->audio_codec[index]); if (self->audio_codec[index]->channel_layout == 0) layout = av_channel_layout_to_mlt( av_get_default_channel_layout(self->audio_codec[index]->channels)); else layout = av_channel_layout_to_mlt(self->audio_codec[index]->channel_layout); } else if (self->audio_index == INT_MAX) { layout = mlt_channel_independent; for (index = 0; index < index_max; index++) if (self->audio_codec[index]) { // XXX: This only works if all audio tracks have the same sample format. *format = pick_audio_format(self->audio_codec[index]->sample_fmt); sizeof_sample = sample_bytes(self->audio_codec[index]); break; } } mlt_properties_set(MLT_FRAME_PROPERTIES(frame), "channel_layout", mlt_audio_channel_layout_name(layout)); // Allocate and set the frame's audio buffer int size = mlt_audio_format_size(*format, *samples, *channels); *buffer = mlt_pool_alloc(size); mlt_frame_set_audio(frame, *buffer, *format, size, mlt_pool_release); // Interleave tracks if audio_index=all if (self->audio_index == INT_MAX) { uint8_t *dest = *buffer; int i; for (i = 0; i < *samples; i++) { for (index = 0; index < index_max; index++) if (self->audio_codec[index]) { int current_channels = self->audio_codec[index]->channels; uint8_t *src = self->audio_buffer[index] + i * current_channels * sizeof_sample; memcpy(dest, src, current_channels * sizeof_sample); dest += current_channels * sizeof_sample; } } for (index = 0; index < index_max; index++) if (self->audio_codec[index] && self->audio_used[index] >= *samples) { int current_channels = self->audio_codec[index]->channels; uint8_t *src = self->audio_buffer[index] + *samples * current_channels * sizeof_sample; self->audio_used[index] -= *samples; memmove(self->audio_buffer[index], src, self->audio_used[index] * current_channels * sizeof_sample); } } // Copy a single track to the output buffer else { index = self->audio_index; uint8_t silence = *format == mlt_audio_u8 ? 0x80 : 0; // Now handle the audio if we have enough if (self->audio_used[index] > 0) { uint8_t *src = self->audio_buffer[index]; // copy samples from audio_buffer size = self->audio_used[index] < *samples ? self->audio_used[index] : *samples; memcpy(*buffer, src, size * *channels * sizeof_sample); // supply the remaining requested samples as silence if (*samples > self->audio_used[index]) memset(*buffer + size * *channels * sizeof_sample, silence, (*samples - self->audio_used[index]) * *channels * sizeof_sample); // reposition the samples within audio_buffer self->audio_used[index] -= size; memmove(src, src + size * *channels * sizeof_sample, self->audio_used[index] * *channels * sizeof_sample); } else { // Otherwise fill with silence memset(*buffer, silence, *samples * *channels * sizeof_sample); } } mlt_properties_set_int(MLT_FRAME_PROPERTIES(frame), "audio_samples", *samples); if (self->audio_cache) { mlt_cache_put_frame_audio(self->audio_cache, frame); } } else { exit_get_audio: if (*format == mlt_audio_none) { *format = mlt_audio_s16; } // Get silence and don't touch the context mlt_frame_get_audio(frame, buffer, format, frequency, channels, samples); } done_get_audio: // Regardless of speed (other than paused), we expect to get the next frame if (!paused) self->audio_expected = position + 1; pthread_mutex_unlock(&self->audio_mutex); return 0; } /** Initialize the audio codec context. */ static int audio_codec_init(producer_avformat self, int index, mlt_properties properties) { // Initialise the codec if necessary if (!self->audio_codec[index]) { // Get the video stream AVStream *stream = self->audio_format->streams[index]; // Get codec context AVCodecParameters *codec_params = stream->codecpar; // Find the codec const AVCodec *codec = avcodec_find_decoder(codec_params->codec_id); if (mlt_properties_get(properties, "acodec")) { if (!(codec = avcodec_find_decoder_by_name(mlt_properties_get(properties, "acodec")))) codec = avcodec_find_decoder(codec_params->codec_id); } // Setup the codec context AVCodecContext *codec_context = avcodec_alloc_context3(codec); if (!codec_context) { mlt_log_error(MLT_PRODUCER_SERVICE(self->parent), "Failed to allocate the decoder context for audio stream #%d\n", index); self->audio_index = -1; return 0; } int ret = avcodec_parameters_to_context(codec_context, codec_params); if (ret < 0) { mlt_log_error( MLT_PRODUCER_SERVICE(self->parent), "Failed to copy decoder parameters to input decoder context for audio stream #%d\n", index); self->audio_index = -1; return 0; } // If we don't have a codec and we can't initialise it, we can't do much more... pthread_mutex_lock(&self->open_mutex); if (codec && avcodec_open2(codec_context, codec, NULL) >= 0) { // Now store the codec with its destructor if (self->audio_codec[index]) avcodec_close(self->audio_codec[index]); self->audio_codec[index] = codec_context; self->audio_index = index; } else { // Remember that we can't use self later self->audio_index = -1; } pthread_mutex_unlock(&self->open_mutex); // Process properties as AVOptions apply_properties(codec_context, properties, AV_OPT_FLAG_AUDIO_PARAM | AV_OPT_FLAG_DECODING_PARAM); if (codec && codec->priv_class && codec_context->priv_data) apply_properties(codec_context->priv_data, properties, AV_OPT_FLAG_AUDIO_PARAM | AV_OPT_FLAG_DECODING_PARAM); } return self->audio_codec[index] && self->audio_index > -1; } static int pick_audio_stream(producer_avformat self) { AVFormatContext *context = self->audio_format; mlt_properties properties = MLT_PRODUCER_PROPERTIES(self->parent); int absolute_index; if (context && mlt_properties_get(properties, "astream")) { // Get the relative stream index absolute_index = absolute_stream_index(context, AVMEDIA_TYPE_AUDIO, mlt_properties_get_int(properties, "astream")); } else { // Failover to the absolute index absolute_index = mlt_properties_get_int(properties, "audio_index"); if (context) { // Compute the relative stream index mlt_properties_set_int(properties, "astream", relative_stream_index(context, AVMEDIA_TYPE_AUDIO, absolute_index)); } } if (mlt_properties_get_int(properties, "audio_index") != absolute_index) { // Update the absolute index mlt_properties_set_int(properties, "audio_index", absolute_index); self->audio_index = absolute_index; } // Handle all audio tracks if (self->audio_index > -1) { if (mlt_properties_get(properties, "audio_index") && !strcmp(mlt_properties_get(properties, "audio_index"), "all")) { absolute_index = INT_MAX; mlt_properties_set(properties, "astream", "all"); } if (mlt_properties_get(properties, "astream") && !strcmp(mlt_properties_get(properties, "astream"), "all")) { absolute_index = INT_MAX; mlt_properties_set(properties, "audio_index", "all"); } } // Exception handling for audio_index if (context && absolute_index >= (int) context->nb_streams && absolute_index < INT_MAX) { for (absolute_index = context->nb_streams - 1; absolute_index >= 0 && context->streams[absolute_index]->codecpar->codec_type != AVMEDIA_TYPE_AUDIO; absolute_index--) ; mlt_properties_set_int(properties, "audio_index", absolute_index); mlt_properties_set_int(properties, "astream", relative_stream_index(context, AVMEDIA_TYPE_AUDIO, absolute_index)); } if (context && absolute_index > -1 && absolute_index < INT_MAX && context->streams[absolute_index]->codecpar->codec_type != AVMEDIA_TYPE_AUDIO) { absolute_index = self->audio_index; mlt_properties_set_int(properties, "audio_index", absolute_index); mlt_properties_set_int(properties, "astream", relative_stream_index(context, AVMEDIA_TYPE_AUDIO, absolute_index)); } if (context && absolute_index > -1 && absolute_index < INT_MAX && pick_audio_format(context->streams[absolute_index]->codecpar->format) == mlt_audio_none) { absolute_index = -1; } return absolute_index; } /** Set up audio handling. */ static void producer_set_up_audio(producer_avformat self, mlt_frame frame) { // Get the producer mlt_producer producer = self->parent; // Get the properties mlt_properties properties = MLT_PRODUCER_PROPERTIES(producer); // Fetch the audio format context AVFormatContext *context = self->audio_format; mlt_properties frame_properties = MLT_FRAME_PROPERTIES(frame); // Get the audio index int index = mlt_properties_get(properties, "astream") ? mlt_properties_get_int(properties, "astream") : mlt_properties_get_int(properties, "audio_index"); // Reopen the file if necessary if (!context && self->audio_index > -1 && index > -1) { producer_open(self, mlt_service_profile(MLT_PRODUCER_SERVICE(producer)), mlt_properties_get(properties, "resource"), 1, 0); context = self->audio_format; mlt_properties_set_int(properties, "_probe_complete", 0); } index = pick_audio_stream(self); // Update the audio properties if the index changed if (context && self->audio_index > -1 && index != self->audio_index) { self->audio_index = index; pthread_mutex_lock(&self->open_mutex); unsigned i = 0; int index_max = FFMIN(MAX_AUDIO_STREAMS, context->nb_streams); for (i = 0; i < index_max; i++) { avcodec_free_context(&self->audio_codec[i]); } set_up_discard(self, index, self->video_index); mlt_cache_close(self->audio_cache); self->audio_cache = NULL; pthread_mutex_unlock(&self->open_mutex); } // Get the codec(s) if (context && index == INT_MAX) { unsigned int index; mlt_properties_set_int(frame_properties, "audio_frequency", self->max_frequency); mlt_properties_set_int(frame_properties, "audio_channels", self->total_channels); for (index = 0; index < context->nb_streams && index < MAX_AUDIO_STREAMS; index++) { if (context->streams[index]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) audio_codec_init(self, index, properties); } self->audio_index = INT_MAX; } else if (context && index > -1 && index < MAX_AUDIO_STREAMS && audio_codec_init(self, index, properties)) { mlt_properties_set_int(frame_properties, "audio_frequency", self->audio_codec[index]->sample_rate); mlt_properties_set_int(frame_properties, "audio_channels", self->audio_codec[index]->channels); } if (context && index > -1) { // Add our audio operation mlt_frame_push_audio(frame, self); mlt_frame_push_audio(frame, producer_get_audio); } } /** Our get frame implementation. */ static int producer_get_frame(mlt_producer producer, mlt_frame_ptr frame, int index) { // Access the private data (void) index; // unused mlt_service service = MLT_PRODUCER_SERVICE(producer); mlt_cache_item cache_item = mlt_service_cache_get(service, "producer_avformat"); producer_avformat self = mlt_cache_item_data(cache_item, NULL); // If cache miss if (!self) { self = calloc(1, sizeof(struct producer_avformat_s)); self->parent = producer; mlt_service_cache_put(service, "producer_avformat", self, 0, (mlt_destructor) producer_avformat_close); cache_item = mlt_service_cache_get(service, "producer_avformat"); } // Create an empty frame *frame = mlt_frame_init(service); if (*frame == NULL) { mlt_cache_item_close(cache_item); return 1; } mlt_properties frame_properties = MLT_FRAME_PROPERTIES(*frame); mlt_properties_set_data(frame_properties, "avformat_cache", cache_item, 0, (mlt_destructor) mlt_cache_item_close, NULL); // Update timecode on the frame we're creating mlt_frame_set_position(*frame, mlt_producer_position(producer)); // Set up the video producer_set_up_video(self, *frame); // Set up the audio producer_set_up_audio(self, *frame); mlt_properties_set_int(frame_properties, "format", mlt_properties_get_int(MLT_PRODUCER_PROPERTIES(producer), "format")); // Set the position of this producer mlt_position position = mlt_producer_frame(producer); mlt_properties_set_position(frame_properties, "original_position", position); if (!mlt_properties_get_int(MLT_PRODUCER_PROPERTIES(producer), "_probe_complete") && self->video_index < 0) { // If video index is valid, get_image() must be called before the probe is complete mlt_properties_clear(MLT_PRODUCER_PROPERTIES(producer), "meta.media.width"); mlt_properties_clear(MLT_PRODUCER_PROPERTIES(producer), "meta.media.height"); mlt_properties_clear(MLT_PRODUCER_PROPERTIES(producer), "meta.media.color_range"); mlt_properties_clear(MLT_PRODUCER_PROPERTIES(producer), "meta.media.aspect_ratio"); mlt_properties_clear(MLT_PRODUCER_PROPERTIES(producer), "meta.media.progressive"); mlt_properties_clear(MLT_PRODUCER_PROPERTIES(producer), "meta.media.sample_aspect_num"); mlt_properties_clear(MLT_PRODUCER_PROPERTIES(producer), "meta.media.sample_aspect_den"); mlt_properties_clear(MLT_PRODUCER_PROPERTIES(producer), "meta.media.top_field_first"); mlt_properties_clear(MLT_PRODUCER_PROPERTIES(producer), "meta.media.variable_frame_rate"); mlt_properties_clear(MLT_PRODUCER_PROPERTIES(producer), "meta.media.colorspace"); mlt_properties_clear(MLT_PRODUCER_PROPERTIES(producer), "meta.media.color_trc"); mlt_properties_clear(MLT_PRODUCER_PROPERTIES(producer), "meta.media.has_b_frames"); mlt_properties_clear(MLT_PRODUCER_PROPERTIES(producer), "aspect_ratio"); mlt_properties_clear(MLT_PRODUCER_PROPERTIES(producer), "width"); mlt_properties_clear(MLT_PRODUCER_PROPERTIES(producer), "height"); mlt_properties_clear(MLT_PRODUCER_PROPERTIES(producer), "format"); mlt_properties_set_int(MLT_PRODUCER_PROPERTIES(producer), "_probe_complete", 1); } // Calculate the next timecode mlt_producer_prepare_next(producer); return 0; } static int producer_probe(mlt_producer producer) { int error = 0; mlt_properties properties = MLT_PRODUCER_PROPERTIES(producer); if (mlt_properties_get_int(properties, "_probe_complete")) { return error; } if (!mlt_properties_exists(properties, "_probe_complete")) { // If metadata was loaded from XML, no need to probe int video_in_use = mlt_properties_get_int(properties, "vstream") > -1; if (video_in_use && mlt_properties_exists(properties, "meta.media.progressive")) { return error; } else if (!video_in_use && mlt_properties_exists(properties, "meta.media.nb_streams")) { return error; } } mlt_frame fr = NULL; mlt_position save_position = mlt_producer_position(producer); // Call producer_get_frame() directly so that the underlying service will not attach any normalizers mlt_service_lock(MLT_PRODUCER_SERVICE(producer)); error = producer_get_frame(producer, &fr, 0); mlt_service_unlock(MLT_PRODUCER_SERVICE(producer)); if (!error && fr && mlt_properties_get_int(properties, "vstream") > -1) { // Some video metadata is not exposed until after the first get_image call. uint8_t *buffer = NULL; mlt_image_format fmt = mlt_image_none; int w = 0; int h = 0; error = mlt_frame_get_image(fr, &buffer, &fmt, &w, &h, 0); } mlt_frame_close(fr); mlt_producer_seek(producer, save_position); return error; } static void producer_avformat_close(producer_avformat self) { mlt_log_debug(NULL, "producer_avformat_close\n"); pthread_mutex_lock(&self->close_mutex); if (self->parent && self->parent->close) mlt_events_disconnect(MLT_PRODUCER_PROPERTIES(self->parent), self); pthread_mutex_unlock(&self->close_mutex); // Cleanup av contexts av_packet_unref(&self->pkt); av_frame_free(&self->video_frame); av_frame_free(&self->audio_frame); #if USE_HWACCEL av_buffer_unref(&self->hwaccel.device_ctx); #endif if (self->is_mutex_init) pthread_mutex_lock(&self->open_mutex); int i; for (i = 0; i < MAX_AUDIO_STREAMS; i++) { mlt_pool_release(self->audio_buffer[i]); av_free(self->decode_buffer[i]); avcodec_free_context(&self->audio_codec[i]); } avcodec_free_context(&self->video_codec); // Close the file if (self->is_thread_init) { pthread_mutex_lock(&self->packets_mutex); self->packets_thread_stop = 1; pthread_cond_signal(&self->packets_cond); pthread_mutex_unlock(&self->packets_mutex); pthread_join(self->packets_thread, NULL); pthread_cond_destroy(&self->packets_cond); } if (self->dummy_context) avformat_close_input(&self->dummy_context); if (self->seekable && self->audio_format) avformat_close_input(&self->audio_format); if (self->video_format) avformat_close_input(&self->video_format); if (self->is_mutex_init) pthread_mutex_unlock(&self->open_mutex); #ifdef AVFILTER avfilter_graph_free(&self->vfilter_graph); #endif // Cleanup caches. mlt_cache_close(self->image_cache); mlt_cache_close(self->audio_cache); if (self->last_good_frame) mlt_frame_close(self->last_good_frame); // Cleanup the mutexes if (self->is_mutex_init) { pthread_mutex_destroy(&self->audio_mutex); pthread_mutex_destroy(&self->video_mutex); pthread_mutex_destroy(&self->packets_mutex); pthread_mutex_destroy(&self->open_mutex); pthread_mutex_destroy(&self->close_mutex); } // Cleanup the packet queues AVPacket *pkt; if (self->apackets) { while ((pkt = mlt_deque_pop_back(self->apackets))) { av_packet_free(&pkt); } mlt_deque_close(self->apackets); self->apackets = NULL; } if (self->vpackets) { while ((pkt = mlt_deque_pop_back(self->vpackets))) { av_packet_free(&pkt); } mlt_deque_close(self->vpackets); self->vpackets = NULL; } free(self); } static void producer_close(mlt_producer parent) { // Remove this instance from the cache mlt_service_cache_purge(MLT_PRODUCER_SERVICE(parent)); // Detach the producer_avformat struct mlt_cache_item cache_item = mlt_service_cache_get(MLT_PRODUCER_SERVICE(parent), "producer_avformat"); producer_avformat self = mlt_cache_item_data(cache_item, NULL); if (self) { pthread_mutex_lock(&self->close_mutex); self->parent = NULL; parent->close = NULL; pthread_mutex_unlock(&self->close_mutex); } else { parent->close = NULL; } mlt_cache_item_close(cache_item); // Close the parent mlt_producer_close(parent); // Free the memory free(parent); } mlt-7.22.0/src/modules/avformat/producer_avformat.yml000664 000000 000000 00000022076 14531534050 022710 0ustar00rootroot000000 000000 schema_version: 0.3 type: producer identifier: avformat title: FFmpeg Reader version: 3 copyright: Meltytech, LLC license: LGPLv2.1 language: en url: http://www.ffmpeg.org/ tags: - Audio - Video description: Read an audio and/or video file using FFmpeg. notes: > This service uses mlt_cache to prevent many simultaneous, open instances of libavformat and libavcodec contexts, file handles, and threads in memory at the same time. Not only does it save on RAM usage, but kernels enforce maximum open file handles and threads per process. Without caching, large projects could easily run into these limits. The default producer cache size is in mlt_cache at size 4. When using mlt_multitrack, the size is adjusted automatically to be the number of tracks plus one if at least 4. This makes it rather dynamic and automatic; however, there are some service graph configurations or authoring scenarios that do not exclusively use the multitrack for multi-layer operations and may need a larger avformat producer cache size. One can set the environment variable MLT_AVFORMAT_PRODUCER_CACHE to a number to override and increase the size of this cache (or to lower it for limited use cases and seeking to minimize RAM). bugs: - Audio sync discrepancy with some content. - Not all libavformat supported formats are seekable. - > Seeking is not always accurate. Sometimes it doesn't seek to I-frames so you may get junk for a few frames. - > More than 2 channels of audio more than 16 bits is not supported. parameters: - identifier: resource argument: yes title: File/URL type: string description: | A file name specification or URL in the form: [{protocol}|{format}]:{resource}[?{format-parameter}[&{format-parameter}...]] For example, video4linux2:/dev/video1?width=320&height=240 Note: on the bash command line, '&' must be escaped as '\&'. If you need '?' in the resource name it must be escaped as '\?'. Use 'f-list' to see a list of supported file formats. Use 'vcodec-list' to see a list of supported video decoders. Use 'acodec-list' to see a list of supported audio decoders. readonly: no required: yes mutable: no widget: fileopen # could provide a button to use a file-open dialog - identifier: audio_index title: Audio index type: integer description: > Choose the absolute stream index of audio stream to use (-1 is off). When this value is equal to the maximum size of a 32-bit signed integer or the string "all" then all audio tracks are coalesced into a bundle of channels on one audio track. readonly: no mutable: no minimum: -1 default: 0 widget: spinner - identifier: astream title: Audio Stream type: integer description: > Choose the relative stream index (n-th) of audio to use (-1 is off). When this value is equal to the maximum size of a 32-bit signed integer or the string "all" then all audio tracks are coalesced into a bundle of channels on one audio track. This property has a higher priority than audio_index. readonly: no mutable: no minimum: -1 default: 0 widget: spinner - identifier: video_index title: Video index type: integer description: Choose the absolute index of video stream to use (-1 is off) readonly: no mutable: no minimum: -1 default: 0 widget: spinner - identifier: vstream title: Video Stream type: integer description: > Choose the relative stream index (n-th) of video to use (-1 is off). This property has a higher priority than video_index. readonly: no mutable: no minimum: -1 default: 0 widget: spinner - identifier: threads title: Decoding threads type: integer description: Choose the number of threads to use in the decoder(s) readonly: no mutable: no minimum: 0 maximum: 4 default: 1 widget: spinner unit: threads # the unit is a label that appears after the widget - identifier: force_aspect_ratio title: Sample aspect ratio type: float description: Optionally override a (mis)detected aspect ratio readonly: no mutable: yes minimum: 0.001 # just a UI suggestion maximum: 9.999 # just a suggestion # no default property means it should be blank in the UI and not applied unless provided - identifier: source_fps title: Frame rate type: float scale: 2 # scale is the number of digits to display after the decimal point description: the framerate of the resource readonly: yes unit: frames/second - identifier: seekable title: Supports seeking type: boolean description: if the resource can seek readonly: yes - identifier: width title: Width type: integer unit: pixels readonly: yes - identifier: height title: Height type: integer unit: pixels readonly: yes - identifier: noimagecache title: Disable image caching type: boolean widget: checkbox - identifier: cache title: Number of images cache type: integer description: > By default, this producer caches images to facilitate YADIF deinterlace, which needs previous and next frames. Also, caching helps with frame- stepping within a player. The default number of images cached is supplied by the MLT framework, which is currently 4, but you can override it with this property. You can also disable caching by setting it to 0. If you are using parallel processing with YADIF deinterlacing, then you might need to increase caching to prevent inadvertent backward seeks. One can also set this value globally for all instances of avformat by setting the environment variable MLT_AVFORMAT_CACHE. - identifier: force_progressive title: Force progressive description: When provided, this overrides the detection of progressive video. type: boolean widget: checkbox - identifier: force_tff title: Force top field first description: When provided, this overrides the detected field order of interlaced video. type: boolean widget: checkbox - identifier: force_fps title: Force frame rate description: When provided, this attempts to override the detected frame rate of the video. type: float widget: checkbox - identifier: force_full_range title: Force Full Range Color description: When provided, this overrides the detected color range of the video (Y'CbCr only). type: boolean - identifier: force_colorspace title: Force colorspace description: When provided, this overrides the detected colorspace of the video (Y'CbCr only). type: integer values: - 240 # SMPTE 240M - 601 # ITU-R BT.601 - 709 # ITU-R BT.709 - identifier: force_color_trc title: Force transfer characteristic description: > When provided, this overrides the detected gamma transfer of the video. See libavcodec AVColorTransferCharacteristic for values. type: integer - identifier: video_delay title: Video delay description: > Manually adjust A/V synchronization. A negative value advances the video instead of delaying it. type: float default: 0 unit: seconds widget: timecode - identifier: reconnect title: Automatically reconnect? description: > Whether to attempt to automatically reconnect to a live source when a read failure occurs. type: boolean widget: checkbox - identifier: exit_on_disconnect title: Exit upon disconnection? description: > When this is set, the program will terminate with an error if a live source becomes disconnected. type: boolean widget: checkbox - identifier: mute_on_pause (*DEPRECATED*) title: Mute on Pause description: > Mute the audio when the same frame is requested twice in a row. type: boolean default: 0 widget: checkbox - identifier: seek_threshold title: Seek Threshold description: > Number of frames required to trigger a seek forward rather than continuous read when reading forward. This can be useful to optimize some applications which rely on accelerated reading of a media file or in cases where lack of I-frames cause libavformat to face issues in seeking and where user tries to minimize the number of seek calls. type: integer default: 64 unit: frames - identifier: autorotate title: Auto-rotate? type: boolean description: > Whether to automatically compensate image orientation if the file is tagged with appropriate metadata and this resource has video/images. default: 1 - identifier: rotate title: Rotation Override description: > While there is automatic rotation for orientation, some files are missing the metadata for it. This provides an override. It can only rotate by a multiple of 90 degrees. type: integer unit: degrees - identifier: filtergraph title: Filtergraph type: string description: Filtergraph to apply to resource. Uses libavfilter syntax. mlt-7.22.0/src/modules/avformat/resolution_scale.yml000664 000000 000000 00000001152 14531534050 022530 0ustar00rootroot000000 000000 # This files contains a list of plugin parameters that are sensitive to the # image resolution. This only works for parameters with type F0R_PARAM_DOUBLE. # plugin: # parameter#: scale factor in addition to mlt_frame_resolution_scale delogo: x: 1.0 y: 1.0 w: 1.0 h: 1.0 floodfill: x: 1.0 y: 1.0 gblur: sigma: 1.0 sigmaV: 1.0 pad: x: 1.0 y: 1.0 width: 1.0 height: 1.0 perspective: x0: 1.0 y0: 1.0 x1: 1.0 y1: 1.0 x2: 1.0 y2: 1.0 x3: 1.0 y3: 1.0 swaprect: w: 1.0 h: 1.0 x1: 1.0 y1: 1.0 x2: 1.0 y2: 1.0 vignette: x0: 1.0 y0: 1.0 zoompan: x: 1.0 y: 1.0 mlt-7.22.0/src/modules/avformat/yuv_only.txt000664 000000 000000 00000000035 14531534050 021057 0ustar00rootroot000000 000000 fspp smartblur vaguedenoiser mlt-7.22.0/src/modules/core/000775 000000 000000 00000000000 14531534050 015545 5ustar00rootroot000000 000000 mlt-7.22.0/src/modules/core/CMakeLists.txt000664 000000 000000 00000003400 14531534050 020302 0ustar00rootroot000000 000000 add_library(mltcore MODULE consumer_multi.c consumer_null.c factory.c filter_audiochannels.c filter_audioconvert.c filter_audiomap.c filter_audioseam.c filter_audiowave.c filter_autofade.c filter_box_blur.c filter_brightness.c filter_channelcopy.c filter_choppy.c filter_crop.c filter_fieldorder.c filter_gamma.c filter_greyscale.c filter_imageconvert.c filter_luma.c filter_mask_apply.c filter_mask_start.c filter_mirror.c filter_mono.c filter_obscure.c filter_panner.c filter_pillar_echo.c filter_rescale.c filter_resize.c filter_transition.c filter_watermark.c image_proc.c image_proc.h link_timeremap.c producer_blank.c producer_colour.c producer_consumer.c producer_hold.c producer_loader.c producer_melt.c producer_noise.c producer_timewarp.c producer_tone.c transition_composite.c transition_luma.c transition_matte.c transition_mix.c ) file(GLOB YML "*.yml") add_custom_target(Other_core_Files SOURCES ${YML} loader.dict loader.ini ) target_compile_options(mltcore PRIVATE ${MLT_COMPILE_OPTIONS}) target_link_libraries(mltcore PRIVATE m mlt Threads::Threads) if(WIN32) target_sources(mltcore PRIVATE ../../win32/fnmatch.c) target_include_directories(mltcore PRIVATE ../../win32) endif() if(CPU_SSE) target_compile_definitions(mltcore PRIVATE USE_SSE) endif() if(CPU_X86_64) target_sources(mltcore PRIVATE composite_line_yuv_sse2_simple.c) target_compile_definitions(mltcore PRIVATE ARCH_X86_64) endif() set_target_properties(mltcore PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${MLT_MODULE_OUTPUT_DIRECTORY}") install(TARGETS mltcore LIBRARY DESTINATION ${MLT_INSTALL_MODULE_DIR}) install(FILES ${YML} loader.dict loader.ini DESTINATION ${MLT_INSTALL_DATA_DIR}/core ) mlt-7.22.0/src/modules/core/composite_line_yuv_mmx.S000664 000000 000000 00000006666 14531534050 022504 0ustar00rootroot000000 000000 .file "composite_line_yuv_mmx" .version "01.01" gcc2_compiled.: .data .text .align 16 #if !defined(__MINGW32__) && !defined(__CYGWIN__) .globl composite_line_yuv_mmx .type composite_line_yuv_mmx,@function composite_line_yuv_mmx: #else .globl _composite_line_yuv_mmx _composite_line_yuv_mmx: #endif /* * Arguments * * dest: 8(%ebp) %esi * src: 12(%ebp) * width_src: 16(%ebp) * alpha: 20(%ebp) * weight: 24(%ebp) * luma: 28(%ebp) * softness: 32(%ebp) */ /* * Function call entry */ pushl %ebp movl %esp,%ebp subl $28,%esp pushl %edi pushl %esi pushl %ebx /* Initialise */ movl 8(%ebp), %esi # get dest movl $0, %edx # j = 0 .loop: movl $0xffff, %ecx # a = 255 cmpl $0, 20(%ebp) # if alpha == NULL je .noalpha movl 20(%ebp), %edi # a = alpha[ j ] movb (%edi,%edx), %cl .noalpha: movl %ecx, -24(%ebp) # save ecx movl 24(%ebp), %eax # mix = weight cmpl $0, 28(%ebp) # if luma == NULL je .noluma movl 28(%ebp), %edi # mix = ... movl %edx, %ebx sall $1, %ebx movw (%edi,%ebx), %bx # luma[ j*2 ] cmpl %ebx, %eax jl .luma0 movl %ebx, %ecx addl 32(%ebp), %ecx # + softness cmpl %ecx, %eax jge .luma1 /* TODO: linear interpolate between edges */ subw %bx, %ax sall $8, %eax subw %bx, %cx movl %edx, %ebx divw %cx movl %ebx, %edx jmp .noluma .luma0: movl $0, %eax jmp .noluma .luma1: movl $0xffff, %eax .noluma: shrl $8, %eax movl %edx, %ebx # edx will be destroyed by mulw movl -24(%ebp), %ecx # restore ecx mull %ecx # mix = mix * a... movl %ebx, %edx # restore edx shrl $8, %eax # >>8 andl $0xff, %eax /* put alpha and (1-alpha) into mm0 */ /* 0 aa 0 1-a 0 aa 0 1-a */ /* duplicate word */ movl %eax, %ecx shll $16, %ecx orl %eax, %ecx movd %ecx, %mm1 /* (1 << 16) - mix */ movl $0x000000ff, %ecx subl %eax, %ecx andl $0xff, %ecx /* duplicate word */ movl %ecx, %eax shll $16, %eax orl %eax, %ecx movd %ecx, %mm0 /* unpack words into double words */ punpcklwd %mm1, %mm0 /* put src yuv and dest yuv into mm1 */ /* 0 UVs 0 UVd 0 Ys 0 Yd */ movl 12(%ebp), %edi # get src movb (%edi), %cl shll $8, %ecx movb 1(%edi), %al shll $24, %eax orl %eax, %ecx movb (%esi), %al # get dest orl %eax, %ecx movb 1(%esi), %al shll $16, %eax orl %eax, %ecx movd %ecx, %mm1 punpcklbw %mm4, %mm1 /* alpha composite */ pmaddwd %mm1, %mm0 psrld $8, %mm0 /* store result */ movd %mm0, %eax movb %al, (%esi) pextrw $2, %mm0, %eax movl $128, %eax movb %al, 1(%esi) /* for..next */ addl $1, %edx # j++ cmpl 16(%ebp), %edx # if ( j == width_src ) jge .out addl $2, %esi addl $2, 12(%ebp) jmp .loop .out: emms leal -40(%ebp),%esp popl %ebx popl %esi popl %edi movl %ebp,%esp popl %ebp ret /********************************************/ .align 8 #if !defined(__MINGW32__) && !defined(__CYGWIN__) .globl composite_have_mmx .type composite_have_mmx,@function composite_have_mmx: #else .globl _composite_have_mmx _composite_have_mmx: #endif push %ebx # Check if bit 21 in flags word is writeable pushfl popl %eax movl %eax,%ebx xorl $0x00200000, %eax pushl %eax popfl pushfl popl %eax cmpl %eax, %ebx je .notfound # OK, we have CPUID movl $1, %eax cpuid test $0x00800000, %edx jz .notfound movl $1, %eax jmp .out2 .notfound: movl $0, %eax .out2: popl %ebx ret mlt-7.22.0/src/modules/core/composite_line_yuv_sse2_simple.c000664 000000 000000 00000026421 14531534050 024137 0ustar00rootroot000000 000000 /* * composite_line_yuv_sse2_simple.c * Copyright (C) 2003-2014 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include const static unsigned char const1[] = { 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00}; const static unsigned char const2[] = { 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x00}; #define LOAD_CONSTS \ "pxor %%xmm0, %%xmm0 \n\t" /* clear zero register */ \ "movdqu (%[const1]), %%xmm9 \n\t" /* load const1 */ \ "movdqu (%[const2]), %%xmm10 \n\t" /* load const2 */ #define LOAD_WEIGHT \ "movd %[weight], %%xmm1 \n\t" /* load weight and decompose */ \ "movlhps %%xmm1, %%xmm1 \n\t" \ "pshuflw $0, %%xmm1, %%xmm1 \n\t" \ "pshufhw $0, %%xmm1, %%xmm1 \n\t" #define LOAD_SRC_A \ "movq (%[src_a]), %%xmm2 \n\t" /* load source alpha */ \ "punpcklbw %%xmm0, %%xmm2 \n\t" /* unpack alpha 8 8-bits alphas to 8 16-bits values */ #define SRC_A_PREMUL \ "pmullw %%xmm1, %%xmm2 \n\t" /* premultiply source alpha */ \ "psrlw $8, %%xmm2 \n\t" #define DST_A_CALC \ /* DSTa = DSTa + (SRCa * (0xFF - DSTa)) >> 8 */ \ "movq (%[dest_a]), %%xmm3 \n\t" /* load dst alpha */ \ "punpcklbw %%xmm0, %%xmm3 \n\t" /* unpack dst 8 8-bits alphas to 8 16-bits values */ \ "movdqa %%xmm9, %%xmm4 \n\t" \ "psubw %%xmm3, %%xmm4 \n\t" \ "pmullw %%xmm2, %%xmm4 \n\t" \ "movdqa %%xmm4, %%xmm5 \n\t" \ "psrlw $8, %%xmm4 \n\t" \ "paddw %%xmm5, %%xmm4 \n\t" \ "paddw %%xmm10, %%xmm4 \n\t" \ "psrlw $8, %%xmm4 \n\t" \ "paddw %%xmm4, %%xmm3 \n\t" \ "packuswb %%xmm0, %%xmm3 \n\t" \ "movq %%xmm3, (%[dest_a]) \n\t" /* save dst alpha */ #define DST_PIX_CALC \ "movdqu (%[src]), %%xmm3 \n\t" /* load src */ \ "movdqu (%[dest]), %%xmm4 \n\t" /* load dst */ \ "movdqa %%xmm3, %%xmm5 \n\t" /* dub src */ \ "movdqa %%xmm4, %%xmm6 \n\t" /* dub dst */ \ "punpcklbw %%xmm0, %%xmm5 \n\t" /* unpack src low */ \ "punpcklbw %%xmm0, %%xmm6 \n\t" /* unpack dst low */ \ "punpckhbw %%xmm0, %%xmm3 \n\t" /* unpack src high */ \ "punpckhbw %%xmm0, %%xmm4 \n\t" /* unpack dst high */ \ "movdqa %%xmm2, %%xmm7 \n\t" /* dub alpha */ \ "movdqa %%xmm2, %%xmm8 \n\t" /* dub alpha */ \ "movlhps %%xmm7, %%xmm7 \n\t" /* dub low */ \ "movhlps %%xmm8, %%xmm8 \n\t" /* dub high */ \ "pshuflw $0x50, %%xmm7, %%xmm7 \n\t" \ "pshuflw $0x50, %%xmm8, %%xmm8 \n\t" \ "pshufhw $0xFA, %%xmm7, %%xmm7 \n\t" \ "pshufhw $0xFA, %%xmm8, %%xmm8 \n\t" \ "psubw %%xmm4, %%xmm3 \n\t" /* src = src - dst */ \ "psubw %%xmm6, %%xmm5 \n\t" \ "pmullw %%xmm8, %%xmm3 \n\t" /* src = src * alpha */ \ "pmullw %%xmm7, %%xmm5 \n\t" \ "pmullw %%xmm9, %%xmm4 \n\t" /* dst = dst * 0xFF */ \ "pmullw %%xmm9, %%xmm6 \n\t" \ "paddw %%xmm3, %%xmm4 \n\t" /* dst = dst + src */ \ "paddw %%xmm5, %%xmm6 \n\t" \ "movdqa %%xmm4, %%xmm3 \n\t" /* dst = ((dst >> 8) + dst + 128) >> 8 */ \ "movdqa %%xmm6, %%xmm5 \n\t" \ "psrlw $8, %%xmm4 \n\t" \ "psrlw $8, %%xmm6 \n\t" \ "paddw %%xmm3, %%xmm4 \n\t" \ "paddw %%xmm5, %%xmm6 \n\t" \ "paddw %%xmm10, %%xmm4 \n\t" \ "paddw %%xmm10, %%xmm6 \n\t" \ "psrlw $8, %%xmm4 \n\t" \ "psrlw $8, %%xmm6 \n\t" \ "packuswb %%xmm4, %%xmm6 \n\t" \ "movdqu %%xmm6, (%[dest]) \n\t" /* store dst */ #define PIX_POINTER_INC \ "add $0x10, %[src] \n\t" \ "add $0x10, %[dest] \n\t" \ "dec %[width] \n\t" #define CLOBBER_XMM \ "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "%xmm5", "%xmm6", "%xmm7", "%xmm8", "%xmm9", \ "%xmm10" static void blend_case7( uint8_t *dest, uint8_t *src, int width, uint8_t *src_a, uint8_t *dest_a, int weight) { int w8 = width / 8; __asm__ volatile(LOAD_CONSTS LOAD_WEIGHT "loop_start7: \n\t" LOAD_SRC_A SRC_A_PREMUL DST_A_CALC DST_PIX_CALC "add $0x08, %[src_a] \n\t" "add $0x08, %[dest_a] \n\t" PIX_POINTER_INC "jnz loop_start7 \n\t" : [weight] "+r"(weight), [src_a] "+r"(src_a), [src] "+r"(src), [dest] "+r"(dest), [dest_a] "+r"(dest_a), [width] "+r"(w8) : [const1] "r"(const1), [const2] "r"(const2) : CLOBBER_XMM); }; // | 3 | dest_a == NULL | src_a != NULL | weight != 256 | blend: premultiply src alpha static void blend_case3(uint8_t *dest, uint8_t *src, int width, uint8_t *src_a, int weight) { int w8 = width / 8; __asm__ volatile( LOAD_CONSTS LOAD_WEIGHT "loop_start3: \n\t" LOAD_SRC_A SRC_A_PREMUL DST_PIX_CALC "add $0x08, %[src_a] \n\t" PIX_POINTER_INC "jnz loop_start3 \n\t" : [weight] "+r"(weight), [src_a] "+r"(src_a), [src] "+r"(src), [dest] "+r"(dest), [width] "+r"(w8) : [const1] "r"(const1), [const2] "r"(const2) : CLOBBER_XMM); }; // | 2 | dest_a == NULL | src_a != NULL | weight == 255 | blend: only src alpha static void blend_case2(uint8_t *dest, uint8_t *src, int width, uint8_t *src_a) { int w8 = width / 8; __asm__ volatile(LOAD_CONSTS "loop_start2: \n\t" LOAD_SRC_A DST_PIX_CALC "add $0x08, %[src_a] \n\t" PIX_POINTER_INC "jnz loop_start2 \n\t" : [src_a] "+r"(src_a), [src] "+r"(src), [dest] "+r"(dest), [width] "+r"(w8) : [const1] "r"(const1), [const2] "r"(const2) : CLOBBER_XMM); }; // | 1 | dest_a == NULL | src_a == NULL | weight != 256 | blend: with given alpha static void blend_case1(uint8_t *dest, uint8_t *src, int width, int weight) { int w8 = width / 8; __asm__ volatile(LOAD_CONSTS LOAD_WEIGHT "loop_start1: \n\t" "movdqa %%xmm1, %%xmm2 \n\t" /* src alpha cames from weight */ DST_PIX_CALC PIX_POINTER_INC "jnz loop_start1 \n\t" : [weight] "+r"(weight), [src] "+r"(src), [dest] "+r"(dest), [width] "+r"(w8) : [const1] "r"(const1), [const2] "r"(const2) : CLOBBER_XMM); }; // | 5 | dest_a != NULL | src_a == NULL | weight != 256 | blend: with given alpha static void blend_case5(uint8_t *dest, uint8_t *src, int width, uint8_t *dest_a, int weight) { int w8 = width / 8; __asm__ volatile( LOAD_CONSTS LOAD_WEIGHT "loop_start5: \n\t" "movdqa %%xmm1, %%xmm2 \n\t" /* source alpha comes from weight */ DST_A_CALC DST_PIX_CALC "add $0x08, %[dest_a] \n\t" PIX_POINTER_INC "jnz loop_start5 \n\t" : [weight] "+r"(weight), [src] "+r"(src), [dest] "+r"(dest), [dest_a] "+r"(dest_a), [width] "+r"(w8) : [const1] "r"(const1), [const2] "r"(const2) : CLOBBER_XMM); }; // | 6 | dest_a != NULL | src_a != NULL | weight == 256 | blend: full blend without src alpha premutiply static void blend_case6(uint8_t *dest, uint8_t *src, int width, uint8_t *src_a, uint8_t *dest_a) { int w8 = width / 8; __asm__ volatile( LOAD_CONSTS "loop_start6: \n\t" LOAD_SRC_A DST_A_CALC DST_PIX_CALC "add $0x08, %[src_a] \n\t" "add $0x08, %[dest_a] \n\t" PIX_POINTER_INC "jnz loop_start6 \n\t" : [src_a] "+r"(src_a), [src] "+r"(src), [dest] "+r"(dest), [dest_a] "+r"(dest_a), [width] "+r"(w8) : [const1] "r"(const1), [const2] "r"(const2) : CLOBBER_XMM); }; void composite_line_yuv_sse2_simple( uint8_t *dest, uint8_t *src, int width, uint8_t *src_a, uint8_t *dest_a, int weight) { weight >>= 8; /* | 0 | dest_a == NULL | src_a == NULL | weight == 256 | blit | 1 | dest_a == NULL | src_a == NULL | weight != 256 | blend: with given alpha | 2 | dest_a == NULL | src_a != NULL | weight == 256 | blend: only src alpha | 3 | dest_a == NULL | src_a != NULL | weight != 256 | blend: premultiply src alpha | 4 | dest_a != NULL | src_a == NULL | weight == 256 | blit: blit and set dst alpha to FF | 5 | dest_a != NULL | src_a == NULL | weight != 256 | blend: with given alpha | 6 | dest_a != NULL | src_a != NULL | weight == 256 | blend: full blend without src alpha premutiply | 7 | dest_a != NULL | src_a != NULL | weight != 256 | blend: full (origin version) */ int cond = ((dest_a != NULL) ? 4 : 0) + ((src_a != NULL) ? 2 : 0) + ((weight != 256) ? 1 : 0); switch (cond) { case 0: memcpy(dest, src, 2 * width); break; case 1: blend_case1(dest, src, width, weight); break; case 2: blend_case2(dest, src, width, src_a); break; case 3: blend_case3(dest, src, width, src_a, weight); break; case 4: memcpy(dest, src, 2 * width); memset(dest_a, 0xFF, width); break; case 5: blend_case5(dest, src, width, dest_a, weight); break; case 6: blend_case6(dest, src, width, src_a, dest_a); break; case 7: blend_case7(dest, src, width, src_a, dest_a, weight); break; }; }; mlt-7.22.0/src/modules/core/consumer_multi.c000664 000000 000000 00000057150 14531534050 020766 0ustar00rootroot000000 000000 /* * Copyright (C) 2011-2023 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include // Forward references static int start(mlt_consumer consumer); static int stop(mlt_consumer consumer); static int is_stopped(mlt_consumer consumer); static void *consumer_thread(void *arg); static void consumer_close(mlt_consumer consumer); static void purge(mlt_consumer consumer); static mlt_properties normalizers = NULL; /** Initialise the consumer. */ mlt_consumer consumer_multi_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_consumer consumer = mlt_consumer_new(profile); if (consumer) { mlt_properties properties = MLT_CONSUMER_PROPERTIES(consumer); // Set defaults mlt_properties_set(properties, "resource", arg); mlt_properties_set_int(properties, "real_time", -1); mlt_properties_set_int(properties, "terminate_on_pause", 1); // Init state mlt_properties_set_int(properties, "joined", 1); // Assign callbacks consumer->close = consumer_close; consumer->start = start; consumer->stop = stop; consumer->is_stopped = is_stopped; consumer->purge = purge; } return consumer; } static mlt_consumer create_consumer(mlt_profile profile, char *id, char *arg) { char *myid = id ? strdup(id) : NULL; char *myarg = (myid && !arg) ? strchr(myid, ':') : NULL; if (myarg) *myarg++ = '\0'; else myarg = arg; mlt_consumer consumer = mlt_factory_consumer(profile, myid, myarg); free(myid); return consumer; } static void create_filter(mlt_profile profile, mlt_service service, char *effect, int *created) { char *id = strdup(effect); char *arg = strchr(id, ':'); if (arg != NULL) *arg++ = '\0'; // We cannot use GLSL-based filters here. if (strncmp(effect, "movit.", 6) && strncmp(effect, "glsl.", 5)) { mlt_filter filter; // The swscale and avcolor_space filters require resolution as arg to test compatibility if (strncmp(effect, "swscale", 7) == 0 || strncmp(effect, "avcolo", 6) == 0) { int width = mlt_properties_get_int(MLT_SERVICE_PROPERTIES(service), "meta.media.width"); filter = mlt_factory_filter(profile, id, &width); } else { filter = mlt_factory_filter(profile, id, arg); } if (filter) { mlt_properties_set_int(MLT_FILTER_PROPERTIES(filter), "_loader", 1); mlt_service_attach(service, filter); mlt_filter_close(filter); *created = 1; } } free(id); } static void attach_normalizers(mlt_profile profile, mlt_service service) { // Loop variable int i; // Tokeniser mlt_tokeniser tokeniser = mlt_tokeniser_init(); // We only need to load the normalizing properties once if (normalizers == NULL) { char temp[PATH_MAX]; snprintf(temp, sizeof(temp), "%s/core/loader.ini", mlt_environment("MLT_DATA")); normalizers = mlt_properties_load(temp); mlt_factory_register_for_clean_up(normalizers, (mlt_destructor) mlt_properties_close); } // Apply normalizers for (i = 0; i < mlt_properties_count(normalizers); i++) { int j = 0; int created = 0; char *value = mlt_properties_get_value(normalizers, i); mlt_tokeniser_parse_new(tokeniser, value, ","); for (j = 0; !created && j < mlt_tokeniser_count(tokeniser); j++) create_filter(profile, service, mlt_tokeniser_get_string(tokeniser, j), &created); } // Close the tokeniser mlt_tokeniser_close(tokeniser); // Attach the audio and video format converters int created = 0; // movit.convert skips setting the frame->convert_image pointer if GLSL cannot be used. mlt_filter filter = mlt_factory_filter(profile, "movit.convert", NULL); if (filter != NULL) { mlt_properties_set_int(MLT_FILTER_PROPERTIES(filter), "_loader", 1); mlt_service_attach(service, filter); mlt_filter_close(filter); created = 1; } // avcolor_space and imageconvert only set frame->convert_image if it has not been set. create_filter(profile, service, "avcolor_space", &created); if (!created) create_filter(profile, service, "imageconvert", &created); create_filter(profile, service, "audioconvert", &created); } static void on_frame_show(void *dummy, mlt_properties properties, mlt_event_data event_data) { mlt_events_fire(properties, "consumer-frame-show", event_data); } static mlt_consumer generate_consumer(mlt_consumer consumer, mlt_properties props, int index) { mlt_profile profile = NULL; if (mlt_properties_get(props, "mlt_profile")) profile = mlt_profile_init(mlt_properties_get(props, "mlt_profile")); if (!profile) profile = mlt_profile_clone(mlt_service_profile(MLT_CONSUMER_SERVICE(consumer))); mlt_consumer nested = create_consumer(profile, mlt_properties_get(props, "mlt_service"), mlt_properties_get(props, "target")); if (nested) { mlt_properties properties = MLT_CONSUMER_PROPERTIES(consumer); mlt_properties nested_props = MLT_CONSUMER_PROPERTIES(nested); char key[30]; snprintf(key, sizeof(key), "%d.consumer", index); mlt_properties_set_data(properties, key, nested, 0, (mlt_destructor) mlt_consumer_close, NULL); snprintf(key, sizeof(key), "%d.profile", index); mlt_properties_set_data(properties, key, profile, 0, (mlt_destructor) mlt_profile_close, NULL); mlt_properties_set_int(nested_props, "put_mode", 1); mlt_properties_pass_list(nested_props, properties, "terminate_on_pause"); mlt_properties_set(props, "consumer", NULL); // set mlt_profile before other properties to facilitate presets mlt_properties_pass_list(nested_props, props, "mlt_profile"); mlt_properties_inherit(nested_props, props); attach_normalizers(profile, MLT_CONSUMER_SERVICE(nested)); // Relay the first available consumer-frame-show event mlt_event event = mlt_properties_get_data(properties, "frame-show-event", NULL); if (!event) { event = mlt_events_listen(nested_props, properties, "consumer-frame-show", (mlt_listener) on_frame_show); mlt_properties_set_data(properties, "frame-show-event", event, 0, /*mlt_event_close*/ NULL, NULL); } } else { mlt_profile_close(profile); } return nested; } static void foreach_consumer_init(mlt_consumer consumer) { const char *resource = mlt_properties_get(MLT_CONSUMER_PROPERTIES(consumer), "resource"); mlt_properties properties = mlt_properties_parse_yaml(resource); char key[20]; int index = 0; if (mlt_properties_get_data(MLT_CONSUMER_PROPERTIES(consumer), "0", NULL)) { // Properties set directly by application mlt_properties p; if (properties) mlt_properties_close(properties); properties = MLT_CONSUMER_PROPERTIES(consumer); do { snprintf(key, sizeof(key), "%d", index); if ((p = mlt_properties_get_data(properties, key, NULL))) generate_consumer(consumer, p, index++); } while (p); } else if (properties && mlt_properties_get_data(properties, "0", NULL)) { // YAML file supplied mlt_properties p; do { snprintf(key, sizeof(key), "%d", index); if ((p = mlt_properties_get_data(properties, key, NULL))) generate_consumer(consumer, p, index++); } while (p); mlt_properties_close(properties); } else { // properties file supplied or properties on this consumer const char *s; if (properties) mlt_properties_close(properties); if (resource) properties = mlt_properties_load(resource); else properties = MLT_CONSUMER_PROPERTIES(consumer); do { snprintf(key, sizeof(key), "%d", index); if ((s = mlt_properties_get(properties, key))) { mlt_properties p = mlt_properties_new(); if (!p) break; // Terminate mlt_service value at the argument delimiter if supplied. // Needed here instead of just relying upon create_consumer() so that // a properties preset is picked up correctly. char *service = strdup(mlt_properties_get(properties, key)); char *arg = strchr(service, ':'); if (arg) { *arg++ = '\0'; mlt_properties_set(p, "target", arg); } mlt_properties_set(p, "mlt_service", service); free(service); snprintf(key, sizeof(key), "%d.", index); int i, count; count = mlt_properties_count(properties); for (i = 0; i < count; i++) { char *name = mlt_properties_get_name(properties, i); if (!strncmp(name, key, strlen(key))) mlt_properties_set(p, name + strlen(key), mlt_properties_get_value(properties, i)); } generate_consumer(consumer, p, index++); mlt_properties_close(p); } } while (s); if (resource) mlt_properties_close(properties); } } static void foreach_consumer_start(mlt_consumer consumer) { mlt_properties properties = MLT_CONSUMER_PROPERTIES(consumer); mlt_consumer nested = NULL; char key[30]; int index = 0; do { snprintf(key, sizeof(key), "%d.consumer", index++); nested = mlt_properties_get_data(properties, key, NULL); if (nested) { mlt_properties nested_props = MLT_CONSUMER_PROPERTIES(nested); mlt_properties_set_position(nested_props, "_multi_position", mlt_properties_get_position(properties, "in")); mlt_properties_set_data(nested_props, "_multi_audio", NULL, 0, NULL, NULL); mlt_properties_set_int(nested_props, "_multi_samples", 0); mlt_consumer_start(nested); } } while (nested); } static void foreach_consumer_refresh(mlt_consumer consumer) { mlt_properties properties = MLT_CONSUMER_PROPERTIES(consumer); mlt_consumer nested = NULL; char key[30]; int index = 0; do { snprintf(key, sizeof(key), "%d.consumer", index++); nested = mlt_properties_get_data(properties, key, NULL); if (nested) mlt_properties_set_int(MLT_CONSUMER_PROPERTIES(nested), "refresh", 1); } while (nested); } // Update certain properties on this consumer from the child consumers. static void foreach_consumer_update(mlt_consumer consumer) { mlt_properties properties = MLT_CONSUMER_PROPERTIES(consumer); mlt_consumer nested = NULL; char key[30]; int index = 0; do { snprintf(key, sizeof(key), "%d.consumer", index++); nested = mlt_properties_get_data(properties, key, NULL); if (nested) { mlt_properties_pass_list( properties, MLT_CONSUMER_PROPERTIES(nested), "color_trc color_range progressive deinterlacer mlt_image_format"); } } while (nested); } static void foreach_consumer_put(mlt_consumer consumer, mlt_frame frame) { mlt_properties properties = MLT_CONSUMER_PROPERTIES(consumer); mlt_consumer nested = NULL; char key[30]; int index = 0; do { snprintf(key, sizeof(key), "%d.consumer", index++); nested = mlt_properties_get_data(properties, key, NULL); if (nested) { mlt_properties nested_props = MLT_CONSUMER_PROPERTIES(nested); double self_fps = mlt_properties_get_double(properties, "fps"); double nested_fps = mlt_properties_get_double(nested_props, "fps"); mlt_position nested_pos = mlt_properties_get_position(nested_props, "_multi_position"); mlt_position self_pos = mlt_frame_get_position(frame); double self_time = self_pos / self_fps; double nested_time = nested_pos / nested_fps; // get the audio for the current frame uint8_t *buffer = NULL; mlt_audio_format format = mlt_audio_s16; int channels = mlt_properties_get_int(properties, "channels"); int frequency = mlt_properties_get_int(properties, "frequency"); int current_samples = mlt_audio_calculate_frame_samples(self_fps, frequency, self_pos); mlt_frame_get_audio(frame, (void **) &buffer, &format, &frequency, &channels, ¤t_samples); int current_size = mlt_audio_format_size(format, current_samples, channels); // get any leftover audio int prev_size = 0; uint8_t *prev_buffer = mlt_properties_get_data(nested_props, "_multi_audio", &prev_size); uint8_t *new_buffer = NULL; if (prev_size > 0) { new_buffer = mlt_pool_alloc(prev_size + current_size); memcpy(new_buffer, prev_buffer, prev_size); memcpy(new_buffer + prev_size, buffer, current_size); buffer = new_buffer; } current_size += prev_size; current_samples += mlt_properties_get_int(nested_props, "_multi_samples"); while (nested_time <= self_time) { // put ideal number of samples into cloned frame int deeply = index > 1 ? 1 : 0; mlt_frame clone_frame = mlt_frame_clone(frame, deeply); mlt_properties clone_props = MLT_FRAME_PROPERTIES(clone_frame); int nested_samples = mlt_audio_calculate_frame_samples(nested_fps, frequency, nested_pos); // -10 is an optimization to avoid tiny amounts of leftover samples nested_samples = nested_samples > current_samples - 10 ? current_samples : nested_samples; int nested_size = mlt_audio_format_size(format, nested_samples, channels); if (nested_size > 0) { prev_buffer = mlt_pool_alloc(nested_size); memcpy(prev_buffer, buffer, nested_size); } else { prev_buffer = NULL; nested_size = 0; } mlt_frame_set_audio(clone_frame, prev_buffer, format, nested_size, mlt_pool_release); mlt_properties_set_int(clone_props, "audio_samples", nested_samples); mlt_properties_set_int(clone_props, "audio_frequency", frequency); mlt_properties_set_int(clone_props, "audio_channels", channels); // chomp the audio current_samples -= nested_samples; current_size -= nested_size; buffer += nested_size; // Fix some things mlt_properties_set_int(clone_props, "meta.media.width", mlt_properties_get_int(MLT_FRAME_PROPERTIES(frame), "width")); mlt_properties_set_int(clone_props, "meta.media.height", mlt_properties_get_int(MLT_FRAME_PROPERTIES(frame), "height")); // send frame to nested consumer mlt_consumer_put_frame(nested, clone_frame); mlt_properties_set_position(nested_props, "_multi_position", ++nested_pos); nested_time = nested_pos / nested_fps; } // save any remaining audio if (current_size > 0) { prev_buffer = mlt_pool_alloc(current_size); memcpy(prev_buffer, buffer, current_size); } else { prev_buffer = NULL; current_size = 0; } mlt_pool_release(new_buffer); mlt_properties_set_data(nested_props, "_multi_audio", prev_buffer, current_size, mlt_pool_release, NULL); mlt_properties_set_int(nested_props, "_multi_samples", current_samples); } } while (nested); } static void foreach_consumer_stop(mlt_consumer consumer) { mlt_properties properties = MLT_CONSUMER_PROPERTIES(consumer); mlt_consumer nested = NULL; char key[30]; int index = 0; struct timespec tm = {0, 1000 * 1000}; do { snprintf(key, sizeof(key), "%d.consumer", index++); nested = mlt_properties_get_data(properties, key, NULL); if (nested) { // Let consumer with terminate_on_pause stop on their own if (mlt_properties_get_int(MLT_CONSUMER_PROPERTIES(nested), "terminate_on_pause")) { // Send additional dummy frame to unlatch nested consumer's threads mlt_consumer_put_frame(nested, mlt_frame_init(MLT_CONSUMER_SERVICE(consumer))); // wait for stop while (!mlt_consumer_is_stopped(nested)) nanosleep(&tm, NULL); } else { mlt_consumer_stop(nested); } } } while (nested); } /** Start the consumer. */ static int start(mlt_consumer consumer) { // Check that we're not already running if (is_stopped(consumer)) { mlt_properties properties = MLT_CONSUMER_PROPERTIES(consumer); pthread_t *thread = calloc(1, sizeof(pthread_t)); // Assign the thread to properties with automatic dealloc mlt_properties_set_data(properties, "thread", thread, sizeof(pthread_t), free, NULL); // Set the running state mlt_properties_set_int(properties, "running", 1); mlt_properties_set_int(properties, "joined", 0); // Construct and start nested consumers if (!mlt_properties_get_data(properties, "0.consumer", NULL)) foreach_consumer_init(consumer); foreach_consumer_start(consumer); // Create the thread pthread_create(thread, NULL, consumer_thread, consumer); } return 0; } /** Stop the consumer. */ static int stop(mlt_consumer consumer) { // Check that we're running if (!mlt_properties_get_int(MLT_CONSUMER_PROPERTIES(consumer), "joined")) { mlt_properties properties = MLT_CONSUMER_PROPERTIES(consumer); pthread_t *thread = mlt_properties_get_data(properties, "thread", NULL); // Stop the thread mlt_properties_set_int(properties, "running", 0); // Wait for termination if (thread) { foreach_consumer_refresh(consumer); pthread_join(*thread, NULL); } mlt_properties_set_int(properties, "joined", 1); // Stop nested consumers foreach_consumer_stop(consumer); } return 0; } /** Determine if the consumer is stopped. */ static int is_stopped(mlt_consumer consumer) { return !mlt_properties_get_int(MLT_CONSUMER_PROPERTIES(consumer), "running"); } /** Purge each of the child consumers. */ static void purge(mlt_consumer consumer) { mlt_properties properties = MLT_CONSUMER_PROPERTIES(consumer); if (mlt_properties_get_int(properties, "running")) { mlt_consumer nested = NULL; char key[30]; int index = 0; do { snprintf(key, sizeof(key), "%d.consumer", index++); nested = mlt_properties_get_data(properties, key, NULL); mlt_consumer_purge(nested); } while (nested); } } /** The main thread - the argument is simply the consumer. */ static void *consumer_thread(void *arg) { mlt_consumer consumer = arg; mlt_properties properties = MLT_CONSUMER_PROPERTIES(consumer); mlt_frame frame = NULL; // Determine whether to stop at end-of-media int terminate_on_pause = mlt_properties_get_int(properties, "terminate_on_pause"); int terminated = 0; foreach_consumer_update(consumer); // Loop while running while (!terminated && !is_stopped(consumer)) { // Get the next frame frame = mlt_consumer_rt_frame(consumer); // Check for termination if (terminate_on_pause && frame) terminated = mlt_properties_get_double(MLT_FRAME_PROPERTIES(frame), "_speed") == 0.0; // Check that we have a frame to work with if (frame && !terminated && !is_stopped(consumer)) { if (mlt_properties_get_int(MLT_FRAME_PROPERTIES(frame), "rendered")) { if (mlt_properties_get_int(MLT_FRAME_PROPERTIES(frame), "_speed") == 0) foreach_consumer_refresh(consumer); foreach_consumer_put(consumer, frame); } else { int dropped = mlt_properties_get_int(properties, "_dropped"); mlt_log_info(MLT_CONSUMER_SERVICE(consumer), "dropped frame %d\n", ++dropped); mlt_properties_set_int(properties, "_dropped", dropped); } mlt_frame_close(frame); } else { if (frame && terminated) { // Send this termination frame to nested consumers for their cancellation foreach_consumer_put(consumer, frame); } if (frame) mlt_frame_close(frame); terminated = 1; } } // Indicate that the consumer is stopped mlt_consumer_stopped(consumer); return NULL; } /** Close the consumer. */ static void consumer_close(mlt_consumer consumer) { mlt_consumer_stop(consumer); // Close the parent mlt_consumer_close(consumer); free(consumer); } mlt-7.22.0/src/modules/core/consumer_multi.yml000664 000000 000000 00000002667 14531534050 021350 0ustar00rootroot000000 000000 schema_version: 7.0 type: consumer identifier: multi title: Multiple outputs version: 1 copyright: Copyright (C) 2011-2014 Meltytech, LLC license: LGPL language: en creator: Dan Dennedy tags: - Audio - Video description: Use multiple consumers with the same producer. notes: | There are a few ways of defining each of the outputs and their properties. One form is a flat set of properties on this consumer that follows the pattern: = [.=]* For example, 0=sdl 0.rescale=bilinear 1=avformat 1.target=foo.dv ... To change the profile for a particular output set the property "mlt_profile." You can put these into a MLT properties file and supply that to this consumer. Another way is to create a separate properties list for each output and set that on the consumer with a numeric name starting with zero: = ... In this format, to specify the service, use the property name "mlt_service" and, again, to specify the profile, use "mlt_profile." You can put these into a YAML Tiny file and supply that to this consumer. This is also the recommended way for applications to interact with this consumer, which is how melt and the XML producer support multiple consumers. parameters: - identifier: resource argument: yes title: File type: string description: > A properties or YAML file specifying multiple consumers and their properties. required: no mlt-7.22.0/src/modules/core/consumer_null.c000664 000000 000000 00000012042 14531534050 020575 0ustar00rootroot000000 000000 /* * consumer_null.c -- a null consumer * Copyright (C) 2003-2021 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ // mlt Header files #include #include // System header files #include #include #include #include // Forward references. static int consumer_start(mlt_consumer consumer); static int consumer_stop(mlt_consumer consumer); static int consumer_is_stopped(mlt_consumer consumer); static void *consumer_thread(void *arg); static void consumer_close(mlt_consumer consumer); /** Initialise the dv consumer. */ mlt_consumer consumer_null_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { // Allocate the consumer mlt_consumer consumer = mlt_consumer_new(profile); // If memory allocated and initialises without error if (consumer != NULL) { // Assign close callback consumer->close = consumer_close; // Set up start/stop/terminated callbacks consumer->start = consumer_start; consumer->stop = consumer_stop; consumer->is_stopped = consumer_is_stopped; } // Return consumer return consumer; } /** Start the consumer. */ static int consumer_start(mlt_consumer consumer) { // Get the properties mlt_properties properties = MLT_CONSUMER_PROPERTIES(consumer); // Check that we're not already running if (!mlt_properties_get_int(properties, "running")) { // Allocate a thread pthread_t *thread = calloc(1, sizeof(pthread_t)); // Assign the thread to properties mlt_properties_set_data(properties, "thread", thread, sizeof(pthread_t), free, NULL); // Set the running state mlt_properties_set_int(properties, "running", 1); mlt_properties_set_int(properties, "joined", 0); // Create the thread pthread_create(thread, NULL, consumer_thread, consumer); } return 0; } /** Stop the consumer. */ static int consumer_stop(mlt_consumer consumer) { // Get the properties mlt_properties properties = MLT_CONSUMER_PROPERTIES(consumer); // Check that we're running if (!mlt_properties_get_int(properties, "joined")) { // Get the thread pthread_t *thread = mlt_properties_get_data(properties, "thread", NULL); // Stop the thread mlt_properties_set_int(properties, "running", 0); mlt_properties_set_int(properties, "joined", 1); // Wait for termination if (thread) pthread_join(*thread, NULL); } return 0; } /** Determine if the consumer is stopped. */ static int consumer_is_stopped(mlt_consumer consumer) { // Get the properties mlt_properties properties = MLT_CONSUMER_PROPERTIES(consumer); return !mlt_properties_get_int(properties, "running"); } /** The main thread - the argument is simply the consumer. */ static void *consumer_thread(void *arg) { // Map the argument to the object mlt_consumer consumer = arg; // Get the properties mlt_properties properties = MLT_CONSUMER_PROPERTIES(consumer); // Convenience functionality int terminate_on_pause = mlt_properties_get_int(properties, "terminate_on_pause"); int terminated = 0; // Frame and size mlt_frame frame = NULL; // Loop while running while (!terminated && mlt_properties_get_int(properties, "running")) { // Get the frame frame = mlt_consumer_rt_frame(consumer); // Check for termination if (terminate_on_pause && frame != NULL) terminated = mlt_properties_get_double(MLT_FRAME_PROPERTIES(frame), "_speed") == 0.0; // Check that we have a frame to work with if (frame != NULL) { // Close the frame mlt_events_fire(properties, "consumer-frame-show", mlt_event_data_from_frame(frame)); mlt_frame_close(frame); } } // Indicate that the consumer is stopped mlt_properties_set_int(properties, "running", 0); mlt_consumer_stopped(consumer); return NULL; } /** Close the consumer. */ static void consumer_close(mlt_consumer consumer) { // Stop the consumer mlt_consumer_stop(consumer); // Close the parent mlt_consumer_close(consumer); // Free the memory free(consumer); } mlt-7.22.0/src/modules/core/consumer_null.yml000664 000000 000000 00000000674 14531534050 021164 0ustar00rootroot000000 000000 schema_version: 7.0 type: consumer identifier: "null" title: NULL version: 1 copyright: Meltytech, LLC license: LGPL language: en tags: - Audio - Video description: A consumer that does nothing but pull frames. notes: > This is intentionally minimal and does not even request image or audio from frames or fire events. It is handy for benchmarking, howevever, if you set the consumer properties terminate_on_pause=1 and real_time=-1. mlt-7.22.0/src/modules/core/factory.c000664 000000 000000 00000052227 14531534050 017370 0ustar00rootroot000000 000000 /* * factory.c -- the factory method interfaces * Copyright (C) 2003-2023 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include extern mlt_consumer consumer_multi_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_consumer consumer_null_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_audiochannels_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_audioconvert_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_audiomap_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_audiowave_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_autofade_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_box_blur_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_brightness_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_channelcopy_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_choppy_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_crop_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_audioseam_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_fieldorder_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_gamma_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_greyscale_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_imageconvert_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_luma_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_mask_apply_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_mask_start_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_mirror_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_mono_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_obscure_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_panner_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_pillar_echo_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_rescale_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_resize_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_transition_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_watermark_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_link link_timeremap_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_producer producer_blank_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_producer producer_colour_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_producer producer_consumer_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_producer producer_hold_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_producer producer_loader_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_producer producer_melt_file_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_producer producer_melt_init(mlt_profile profile, mlt_service_type type, const char *id, char **argv); extern mlt_producer producer_noise_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_producer producer_timewarp_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_producer producer_tone_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); #include "transition_composite.h" extern mlt_transition transition_luma_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_transition transition_mix_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_transition transition_matte_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); static mlt_properties metadata(mlt_service_type type, const char *id, void *data) { char file[PATH_MAX]; snprintf(file, PATH_MAX, "%s/core/%s", mlt_environment("MLT_DATA"), (char *) data); return mlt_properties_parse_yaml(file); } MLT_REPOSITORY { MLT_REGISTER(mlt_service_consumer_type, "multi", consumer_multi_init); MLT_REGISTER(mlt_service_consumer_type, "null", consumer_null_init); MLT_REGISTER(mlt_service_filter_type, "audiochannels", filter_audiochannels_init); MLT_REGISTER(mlt_service_filter_type, "audioconvert", filter_audioconvert_init); MLT_REGISTER(mlt_service_filter_type, "audiomap", filter_audiomap_init); MLT_REGISTER(mlt_service_filter_type, "audioseam", filter_audioseam_init); MLT_REGISTER(mlt_service_filter_type, "audiowave", filter_audiowave_init); MLT_REGISTER(mlt_service_filter_type, "autofade", filter_autofade_init); MLT_REGISTER(mlt_service_filter_type, "box_blur", filter_box_blur_init); MLT_REGISTER(mlt_service_filter_type, "brightness", filter_brightness_init); MLT_REGISTER(mlt_service_filter_type, "channelcopy", filter_channelcopy_init); MLT_REGISTER(mlt_service_filter_type, "channelswap", filter_channelcopy_init); MLT_REGISTER(mlt_service_filter_type, "choppy", filter_choppy_init); MLT_REGISTER(mlt_service_filter_type, "crop", filter_crop_init); MLT_REGISTER(mlt_service_filter_type, "fieldorder", filter_fieldorder_init); MLT_REGISTER(mlt_service_filter_type, "gamma", filter_gamma_init); MLT_REGISTER(mlt_service_filter_type, "greyscale", filter_greyscale_init); MLT_REGISTER(mlt_service_filter_type, "grayscale", filter_greyscale_init); MLT_REGISTER(mlt_service_filter_type, "imageconvert", filter_imageconvert_init); MLT_REGISTER(mlt_service_filter_type, "luma", filter_luma_init); MLT_REGISTER(mlt_service_filter_type, "mask_apply", filter_mask_apply_init); MLT_REGISTER(mlt_service_filter_type, "mask_start", filter_mask_start_init); MLT_REGISTER(mlt_service_filter_type, "mirror", filter_mirror_init); MLT_REGISTER(mlt_service_filter_type, "mono", filter_mono_init); MLT_REGISTER(mlt_service_filter_type, "obscure", filter_obscure_init); MLT_REGISTER(mlt_service_filter_type, "panner", filter_panner_init); MLT_REGISTER(mlt_service_filter_type, "pillar_echo", filter_pillar_echo_init); MLT_REGISTER(mlt_service_filter_type, "rescale", filter_rescale_init); MLT_REGISTER(mlt_service_filter_type, "resize", filter_resize_init); MLT_REGISTER(mlt_service_filter_type, "transition", filter_transition_init); MLT_REGISTER(mlt_service_filter_type, "watermark", filter_watermark_init); MLT_REGISTER(mlt_service_link_type, "audiochannels", mlt_link_filter_init); MLT_REGISTER(mlt_service_link_type, "audioconvert", mlt_link_filter_init); MLT_REGISTER(mlt_service_link_type, "crop", mlt_link_filter_init); MLT_REGISTER(mlt_service_link_type, "fieldorder", mlt_link_filter_init); MLT_REGISTER(mlt_service_link_type, "imageconvert", mlt_link_filter_init); MLT_REGISTER(mlt_service_link_type, "rescale", mlt_link_filter_init); MLT_REGISTER(mlt_service_link_type, "resize", mlt_link_filter_init); MLT_REGISTER(mlt_service_link_type, "timeremap", link_timeremap_init); MLT_REGISTER(mlt_service_producer_type, "abnormal", producer_loader_init); MLT_REGISTER(mlt_service_producer_type, "blank", producer_blank_init); MLT_REGISTER(mlt_service_producer_type, "color", producer_colour_init); MLT_REGISTER(mlt_service_producer_type, "colour", producer_colour_init); MLT_REGISTER(mlt_service_producer_type, "consumer", producer_consumer_init); MLT_REGISTER(mlt_service_producer_type, "hold", producer_hold_init); MLT_REGISTER(mlt_service_producer_type, "loader", producer_loader_init); MLT_REGISTER(mlt_service_producer_type, "loader-nogl", producer_loader_init); MLT_REGISTER(mlt_service_producer_type, "melt", producer_melt_init); MLT_REGISTER(mlt_service_producer_type, "melt_file", producer_melt_file_init); MLT_REGISTER(mlt_service_producer_type, "noise", producer_noise_init); MLT_REGISTER(mlt_service_producer_type, "timewarp", producer_timewarp_init); MLT_REGISTER(mlt_service_producer_type, "tone", producer_tone_init); MLT_REGISTER(mlt_service_transition_type, "composite", transition_composite_init); MLT_REGISTER(mlt_service_transition_type, "luma", transition_luma_init); MLT_REGISTER(mlt_service_transition_type, "mix", transition_mix_init); MLT_REGISTER(mlt_service_transition_type, "matte", transition_matte_init); MLT_REGISTER_METADATA(mlt_service_consumer_type, "multi", metadata, "consumer_multi.yml"); MLT_REGISTER_METADATA(mlt_service_consumer_type, "null", metadata, "consumer_null.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "audiochannels", metadata, "filter_audiochannels.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "audioconvert", metadata, "filter_audioconvert.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "audiomap", metadata, "filter_audiomap.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "audioseam", metadata, "filter_audioseam.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "audiowave", metadata, "filter_audiowave.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "autofade", metadata, "filter_autofade.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "box_blur", metadata, "filter_box_blur.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "brightness", metadata, "filter_brightness.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "channelcopy", metadata, "filter_channelcopy.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "channelswap", metadata, "filter_channelcopy.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "choppy", metadata, "filter_choppy.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "crop", metadata, "filter_crop.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "fieldorder", metadata, "filter_fieldorder.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "gamma", metadata, "filter_gamma.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "greyscale", metadata, "filter_greyscale.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "grayscale", metadata, "filter_greyscale.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "imageconvert", metadata, "filter_imageconvert.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "luma", metadata, "filter_luma.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "mask_apply", metadata, "filter_mask_apply.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "mask_start", metadata, "filter_mask_start.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "mirror", metadata, "filter_mirror.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "mono", metadata, "filter_mono.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "obscure", metadata, "filter_obscure.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "panner", metadata, "filter_panner.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "pillar_echo", metadata, "filter_pillar_echo.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "rescale", metadata, "filter_rescale.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "resize", metadata, "filter_resize.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "transition", metadata, "filter_transition.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "watermark", metadata, "filter_watermark.yml"); MLT_REGISTER_METADATA(mlt_service_link_type, "audiochannels", mlt_link_filter_metadata, NULL); MLT_REGISTER_METADATA(mlt_service_link_type, "audioconvert", mlt_link_filter_metadata, NULL); MLT_REGISTER_METADATA(mlt_service_link_type, "crop", mlt_link_filter_metadata, NULL); MLT_REGISTER_METADATA(mlt_service_link_type, "fieldorder", mlt_link_filter_metadata, NULL); MLT_REGISTER_METADATA(mlt_service_link_type, "imageconvert", mlt_link_filter_metadata, NULL); MLT_REGISTER_METADATA(mlt_service_link_type, "rescale", mlt_link_filter_metadata, NULL); MLT_REGISTER_METADATA(mlt_service_link_type, "resize", mlt_link_filter_metadata, NULL); MLT_REGISTER_METADATA(mlt_service_link_type, "timeremap", metadata, "link_timeremap.yml"); MLT_REGISTER_METADATA(mlt_service_producer_type, "abnormal", metadata, "producer_abnormal.yml"); MLT_REGISTER_METADATA(mlt_service_producer_type, "blank", metadata, "producer_blank.yml"); MLT_REGISTER_METADATA(mlt_service_producer_type, "colour", metadata, "producer_colour.yml"); MLT_REGISTER_METADATA(mlt_service_producer_type, "color", metadata, "producer_colour.yml"); MLT_REGISTER_METADATA(mlt_service_producer_type, "consumer", metadata, "producer_consumer.yml"); MLT_REGISTER_METADATA(mlt_service_producer_type, "hold", metadata, "producer_hold.yml"); MLT_REGISTER_METADATA(mlt_service_producer_type, "loader", metadata, "producer_loader.yml"); MLT_REGISTER_METADATA(mlt_service_producer_type, "loader-nogl", metadata, "producer_loader-nogl.yml"); MLT_REGISTER_METADATA(mlt_service_producer_type, "melt", metadata, "producer_melt.yml"); MLT_REGISTER_METADATA(mlt_service_producer_type, "melt_file", metadata, "producer_melt_file.yml"); MLT_REGISTER_METADATA(mlt_service_producer_type, "noise", metadata, "producer_noise.yml"); MLT_REGISTER_METADATA(mlt_service_producer_type, "timewarp", metadata, "producer_timewarp.yml"); MLT_REGISTER_METADATA(mlt_service_producer_type, "tone", metadata, "producer_tone.yml"); MLT_REGISTER_METADATA(mlt_service_transition_type, "composite", metadata, "transition_composite.yml"); MLT_REGISTER_METADATA(mlt_service_transition_type, "luma", metadata, "transition_luma.yml"); MLT_REGISTER_METADATA(mlt_service_transition_type, "mix", metadata, "transition_mix.yml"); MLT_REGISTER_METADATA(mlt_service_transition_type, "matte", metadata, "transition_matte.yml"); } mlt-7.22.0/src/modules/core/filter_audiochannels.c000664 000000 000000 00000021726 14531534050 022103 0ustar00rootroot000000 000000 /* * filter_audiochannels.c -- convert from one audio format to another * Copyright (C) 2009-2014 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include static int filter_get_audio(mlt_frame frame, void **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples) { // Used to return number of channels in the source int channels_avail = *channels; // Get the producer's audio int error = mlt_frame_get_audio(frame, buffer, format, frequency, &channels_avail, samples); if (error) return error; if (channels_avail < *channels) { int size = mlt_audio_format_size(*format, *samples, *channels); int16_t *new_buffer = mlt_pool_alloc(size); // Duplicate the existing channels if (*format == mlt_audio_s16) { int i, j, k = 0; for (i = 0; i < *samples; i++) { for (j = 0; j < *channels; j++) { new_buffer[(i * *channels) + j] = (( int16_t *) (*buffer))[(i * channels_avail) + k]; k = (k + 1) % channels_avail; } } } else if (*format == mlt_audio_s32le || *format == mlt_audio_f32le) { int32_t *p = (int32_t *) new_buffer; int i, j, k = 0; for (i = 0; i < *samples; i++) { for (j = 0; j < *channels; j++) { p[(i * *channels) + j] = ((int32_t *) (*buffer))[(i * channels_avail) + k]; k = (k + 1) % channels_avail; } } } else if (*format == mlt_audio_u8) { uint8_t *p = (uint8_t *) new_buffer; int i, j, k = 0; for (i = 0; i < *samples; i++) { for (j = 0; j < *channels; j++) { p[(i * *channels) + j] = ((uint8_t *) (*buffer))[(i * channels_avail) + k]; k = (k + 1) % channels_avail; } } } else { // non-interleaved - s32 or float int size_avail = mlt_audio_format_size(*format, *samples, channels_avail); int32_t *p = (int32_t *) new_buffer; int i = *channels / channels_avail; while (i--) { memcpy(p, *buffer, size_avail); p += size_avail / sizeof(*p); } i = *channels % channels_avail; if (i) { size_avail = mlt_audio_format_size(*format, *samples, i); memcpy(p, *buffer, size_avail); } } // Update the audio buffer now - destroys the old mlt_frame_set_audio(frame, new_buffer, *format, size, mlt_pool_release); *buffer = new_buffer; } else if (channels_avail == 6 && *channels == 2) { // Downmix 5.1 audio to stereo. // Mix levels taken from ATSC A/52 assuming maximum center and surround // mix levels. #define MIX(front, center, surr) (front + (0.707 * center) + (0.5 * surr)) // Convert to a supported format if necessary mlt_audio_format new_format = *format; switch (*format) { default: // Unknown. Try to convert to float anyway. mlt_log_error(NULL, "[audiochannels] Unknown format %d\n", *format); case mlt_audio_float: case mlt_audio_f32le: new_format = mlt_audio_float; break; case mlt_audio_s32le: case mlt_audio_s32: new_format = mlt_audio_s32; break; case mlt_audio_s16: case mlt_audio_u8: new_format = mlt_audio_s16; break; case mlt_audio_none: new_format = mlt_audio_none; break; } if (*format != new_format && frame->convert_audio) frame->convert_audio(frame, buffer, format, new_format); // Perform the downmix. Operate on the buffer in place to avoid realloc. if (*format == mlt_audio_s16) { int16_t *in = *buffer; int16_t *out = *buffer; int i; for (i = 0; i < *samples; i++) { float fl = in[0]; float fr = in[1]; float c = in[2]; // in[3] is LFE float sl = in[4]; float sr = in[5]; *out++ = CLAMP(MIX(fl, c, sl), INT16_MIN, INT16_MAX); // Left *out++ = CLAMP(MIX(fr, c, sr), INT16_MIN, INT16_MAX); // Right in += 6; } } else if (*format == mlt_audio_s32) { int32_t *flin = *buffer; int32_t *frin = *buffer + (*samples * sizeof(float)); int32_t *cin = *buffer + (2 * *samples * sizeof(float)); int32_t *slin = *buffer + (4 * *samples * sizeof(float)); int32_t *srin = *buffer + (5 * *samples * sizeof(float)); int32_t *lout = *buffer; int32_t *rout = *buffer + (*samples * sizeof(float)); int i; for (i = 0; i < *samples; i++) { double fl = *flin++; double fr = *frin++; double c = *cin++; double sl = *slin++; double sr = *srin++; *lout++ = CLAMP(MIX(fl, c, sl), INT32_MIN, INT32_MAX); *rout++ = CLAMP(MIX(fr, c, sr), INT32_MIN, INT32_MAX); } } else if (*format == mlt_audio_float) { float *flin = *buffer; float *frin = *buffer + (*samples * sizeof(float)); float *cin = *buffer + (2 * *samples * sizeof(float)); float *slin = *buffer + (4 * *samples * sizeof(float)); float *srin = *buffer + (5 * *samples * sizeof(float)); float *lout = *buffer; float *rout = *buffer + (*samples * sizeof(float)); int i; for (i = 0; i < *samples; i++) { float fl = *flin++; float fr = *frin++; float c = *cin++; float sl = *slin++; float sr = *srin++; *lout++ = MIX(fl, c, sl); *rout++ = MIX(fr, c, sr); } } else { mlt_log_error(NULL, "[audiochannels] Unable to mix format %d\n", *format); } } else if (channels_avail > *channels) { int size = mlt_audio_format_size(*format, *samples, *channels); int16_t *new_buffer = mlt_pool_alloc(size); int i, j; // Drop all but the first *channels if (*format == mlt_audio_s16) { for (i = 0; i < *samples; i++) for (j = 0; j < *channels; j++) new_buffer[(i * *channels) + j] = (( int16_t *) (*buffer))[(i * channels_avail) + j]; } else if (*format == mlt_audio_s32le || *format == mlt_audio_f32le) { int32_t *p = (int32_t *) new_buffer; for (i = 0; i < *samples; i++) for (j = 0; j < *channels; j++) p[(i * *channels) + j] = ((int32_t *) (*buffer))[(i * channels_avail) + j]; } else if (*format == mlt_audio_u8) { uint8_t *p = (uint8_t *) new_buffer; for (i = 0; i < *samples; i++) for (j = 0; j < *channels; j++) p[(i * *channels) + j] = ((uint8_t *) (*buffer))[(i * channels_avail) + j]; } else { // non-interleaved - s32 or float memcpy(new_buffer, *buffer, size); } // Update the audio buffer now - destroys the old mlt_frame_set_audio(frame, new_buffer, *format, size, mlt_pool_release); *buffer = new_buffer; } return error; } /** Filter processing. */ static mlt_frame filter_process(mlt_filter filter, mlt_frame frame) { mlt_frame_push_audio(frame, filter_get_audio); return frame; } /** Constructor for the filter. */ mlt_filter filter_audiochannels_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = mlt_filter_new(); if (filter) filter->process = filter_process; return filter; } mlt-7.22.0/src/modules/core/filter_audiochannels.yml000664 000000 000000 00000000666 14531534050 022462 0ustar00rootroot000000 000000 schema_version: 7.0 type: filter identifier: audiochannels title: Convert Audio Channel Count version: 1 copyright: Meltytech, LLC license: LGPLv2.1 language: en tags: - Audio description: Converts the number of audio channels. notes: > This is not intended to be created directly. Rather, the loader producer loads it if it is available to automatically all inputs to have the number of audio channels requested by the consumer. mlt-7.22.0/src/modules/core/filter_audioconvert.c000664 000000 000000 00000044376 14531534050 021776 0ustar00rootroot000000 000000 /* * filter_audioconvert.c -- convert from one audio format to another * Copyright (C) 2009-2016 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include static int convert_audio(mlt_frame frame, void **audio, mlt_audio_format *format, mlt_audio_format requested_format) { int error = 1; mlt_properties properties = MLT_FRAME_PROPERTIES(frame); int channels = mlt_properties_get_int(properties, "audio_channels"); int samples = mlt_properties_get_int(properties, "audio_samples"); int size = mlt_audio_format_size(requested_format, samples, channels); if (*format != requested_format) { mlt_log_debug(NULL, "[filter audioconvert] %s -> %s %d channels %d samples\n", mlt_audio_format_name(*format), mlt_audio_format_name(requested_format), channels, samples); switch (*format) { case mlt_audio_s16: switch (requested_format) { case mlt_audio_s32: { int32_t *buffer = mlt_pool_alloc(size); int32_t *p = buffer; int c; for (c = 0; c < channels; c++) { int16_t *q = (int16_t *) *audio + c; int i = samples + 1; while (--i) { *p++ = (int32_t) *q << 16; q += channels; } } *audio = buffer; error = 0; break; } case mlt_audio_float: { float *buffer = mlt_pool_alloc(size); float *p = buffer; int c; for (c = 0; c < channels; c++) { int16_t *q = (int16_t *) *audio + c; int i = samples + 1; while (--i) { *p++ = (float) (*q) / 32768.0; q += channels; } } *audio = buffer; error = 0; break; } case mlt_audio_s32le: { int32_t *buffer = mlt_pool_alloc(size); int32_t *p = buffer; int16_t *q = (int16_t *) *audio; int i = samples * channels + 1; while (--i) *p++ = (int32_t) *q++ << 16; *audio = buffer; error = 0; break; } case mlt_audio_f32le: { float *buffer = mlt_pool_alloc(size); float *p = buffer; int16_t *q = (int16_t *) *audio; int i = samples * channels + 1; while (--i) { float f = (float) (*q++) / 32768.0; *p++ = CLAMP(f, -1.0f, 1.0f); } *audio = buffer; error = 0; break; } case mlt_audio_u8: { uint8_t *buffer = mlt_pool_alloc(size); uint8_t *p = buffer; int16_t *q = (int16_t *) *audio; int i = samples * channels + 1; while (--i) *p++ = (*q++ >> 8) + 128; *audio = buffer; error = 0; break; } default: break; } break; case mlt_audio_s32: switch (requested_format) { case mlt_audio_s16: { int16_t *buffer = mlt_pool_alloc(size); int16_t *p = buffer; int32_t *q = (int32_t *) *audio; int s, c; for (s = 0; s < samples; s++) for (c = 0; c < channels; c++) *p++ = *(q + c * samples + s) >> 16; *audio = buffer; error = 0; break; } case mlt_audio_float: { float *buffer = mlt_pool_alloc(size); float *p = buffer; int32_t *q = (int32_t *) *audio; int i = samples * channels + 1; while (--i) *p++ = (float) (*q++) / 2147483648.0; *audio = buffer; error = 0; break; } case mlt_audio_s32le: { int32_t *buffer = mlt_pool_alloc(size); int32_t *p = buffer; int32_t *q = (int32_t *) *audio; int s, c; for (s = 0; s < samples; s++) for (c = 0; c < channels; c++) *p++ = *(q + c * samples + s); *audio = buffer; error = 0; break; } case mlt_audio_f32le: { float *buffer = mlt_pool_alloc(size); float *p = buffer; int32_t *q = (int32_t *) *audio; int s, c; for (s = 0; s < samples; s++) for (c = 0; c < channels; c++) { float f = (float) (*(q + c * samples + s)) / 2147483648.0; *p++ = CLAMP(f, -1.0f, 1.0f); } *audio = buffer; error = 0; break; } case mlt_audio_u8: { uint8_t *buffer = mlt_pool_alloc(size); uint8_t *p = buffer; int32_t *q = (int32_t *) *audio; int s, c; for (s = 0; s < samples; s++) for (c = 0; c < channels; c++) *p++ = (q[c * samples + s] >> 24) + 128; *audio = buffer; error = 0; break; } default: break; } break; case mlt_audio_float: switch (requested_format) { case mlt_audio_s16: { int16_t *buffer = mlt_pool_alloc(size); int16_t *p = buffer; float *q = (float *) *audio; int s, c; for (s = 0; s < samples; s++) for (c = 0; c < channels; c++) { float f = *(q + c * samples + s); f = CLAMP(f, -1.0f, 1.0f); *p++ = 32767 * f; } *audio = buffer; error = 0; break; } case mlt_audio_s32: { int32_t *buffer = mlt_pool_alloc(size); int32_t *p = buffer; float *q = (float *) *audio; int i = samples * channels + 1; while (--i) { float f = *q++; f = CLAMP(f, -1.0f, 1.0f); int64_t pcm = (f > 0.0f ? 2147483647LL : 2147483648LL) * f; *p++ = CLAMP(pcm, -2147483648LL, 2147483647LL); } *audio = buffer; error = 0; break; } case mlt_audio_s32le: { int32_t *buffer = mlt_pool_alloc(size); int32_t *p = buffer; float *q = (float *) *audio; int s, c; for (s = 0; s < samples; s++) for (c = 0; c < channels; c++) { float f = *(q + c * samples + s); f = CLAMP(f, -1.0f, 1.0f); int64_t pcm = (f > 0.0f ? 2147483647LL : 2147483648LL) * f; *p++ = CLAMP(pcm, -2147483648LL, 2147483647LL); } *audio = buffer; error = 0; break; } case mlt_audio_f32le: { float *buffer = mlt_pool_alloc(size); float *p = buffer; float *q = (float *) *audio; int s, c; for (s = 0; s < samples; s++) for (c = 0; c < channels; c++) *p++ = *(q + c * samples + s); *audio = buffer; error = 0; break; } case mlt_audio_u8: { uint8_t *buffer = mlt_pool_alloc(size); uint8_t *p = buffer; float *q = (float *) *audio; int s, c; for (s = 0; s < samples; s++) for (c = 0; c < channels; c++) { float f = *(q + c * samples + s); f = CLAMP(f, -1.0f, 1.0f); *p++ = (127 * f) + 128; } *audio = buffer; error = 0; break; } default: break; } break; case mlt_audio_s32le: switch (requested_format) { case mlt_audio_s16: { int16_t *buffer = mlt_pool_alloc(size); int16_t *p = buffer; int32_t *q = (int32_t *) *audio; int i = samples * channels + 1; while (--i) *p++ = *q++ >> 16; *audio = buffer; error = 0; break; } case mlt_audio_s32: { int32_t *buffer = mlt_pool_alloc(size); int32_t *p = buffer; int c; for (c = 0; c < channels; c++) { int32_t *q = (int32_t *) *audio + c; int i = samples + 1; while (--i) { *p++ = *q; q += channels; } } *audio = buffer; error = 0; break; } case mlt_audio_float: { float *buffer = mlt_pool_alloc(size); float *p = buffer; int c; for (c = 0; c < channels; c++) { int32_t *q = (int32_t *) *audio + c; int i = samples + 1; while (--i) { *p++ = (float) (*q) / 2147483648.0; q += channels; } } *audio = buffer; error = 0; break; } case mlt_audio_f32le: { float *buffer = mlt_pool_alloc(size); float *p = buffer; int32_t *q = (int32_t *) *audio; int i = samples * channels + 1; while (--i) *p++ = (float) (*q++) / 2147483648.0; *audio = buffer; error = 0; break; } case mlt_audio_u8: { uint8_t *buffer = mlt_pool_alloc(size); uint8_t *p = buffer; int32_t *q = (int32_t *) *audio; int i = samples * channels + 1; while (--i) *p++ = (*q++ >> 24) + 128; *audio = buffer; error = 0; break; } default: break; } break; case mlt_audio_f32le: switch (requested_format) { case mlt_audio_s16: { int16_t *buffer = mlt_pool_alloc(size); int16_t *p = buffer; float *q = (float *) *audio; int i = samples * channels + 1; while (--i) { float f = *q++; f = CLAMP(f, -1.0f, 1.0f); *p++ = 32767 * f; } *audio = buffer; error = 0; break; } case mlt_audio_float: { float *buffer = mlt_pool_alloc(size); float *p = buffer; int c; for (c = 0; c < channels; c++) { float *q = (float *) *audio + c; int i = samples + 1; while (--i) { *p++ = *q; q += channels; } } *audio = buffer; error = 0; break; } case mlt_audio_s32: { int32_t *buffer = mlt_pool_alloc(size); int32_t *p = buffer; int c; for (c = 0; c < channels; c++) { float *q = (float *) *audio + c; int i = samples + 1; while (--i) { float f = *q; f = CLAMP(f, -1.0f, 1.0f); int64_t pcm = (f > 0.0f ? 2147483647LL : 2147483648LL) * f; *p++ = CLAMP(pcm, -2147483648LL, 2147483647LL); q += channels; } } *audio = buffer; error = 0; break; } case mlt_audio_s32le: { int32_t *buffer = mlt_pool_alloc(size); int32_t *p = buffer; float *q = (float *) *audio; int i = samples * channels + 1; while (--i) { float f = *q++; f = CLAMP(f, -1.0f, 1.0f); int64_t pcm = (f > 0.0f ? 2147483647LL : 2147483648LL) * f; *p++ = CLAMP(pcm, -2147483648LL, 2147483647LL); } *audio = buffer; error = 0; break; } case mlt_audio_u8: { uint8_t *buffer = mlt_pool_alloc(size); uint8_t *p = buffer; float *q = (float *) *audio; int i = samples * channels + 1; while (--i) { float f = *q++; f = CLAMP(f, -1.0f, 1.0f); *p++ = (127 * f) + 128; } *audio = buffer; error = 0; break; } default: break; } break; case mlt_audio_u8: switch (requested_format) { case mlt_audio_s32: { int32_t *buffer = mlt_pool_alloc(size); int32_t *p = buffer; int c; for (c = 0; c < channels; c++) { uint8_t *q = (uint8_t *) *audio + c; int i = samples + 1; while (--i) { *p++ = ((int32_t) *q - 128) << 24; q += channels; } } *audio = buffer; error = 0; break; } case mlt_audio_float: { float *buffer = mlt_pool_alloc(size); float *p = buffer; int c; for (c = 0; c < channels; c++) { uint8_t *q = (uint8_t *) *audio + c; int i = samples + 1; while (--i) { *p++ = ((float) *q - 128) / 256.0f; q += channels; } } *audio = buffer; error = 0; break; } case mlt_audio_s16: { int16_t *buffer = mlt_pool_alloc(size); int16_t *p = buffer; uint8_t *q = (uint8_t *) *audio; int i = samples * channels + 1; while (--i) *p++ = ((int16_t) *q++ - 128) << 8; *audio = buffer; error = 0; break; } case mlt_audio_s32le: { int32_t *buffer = mlt_pool_alloc(size); int32_t *p = buffer; uint8_t *q = (uint8_t *) *audio; int i = samples * channels + 1; while (--i) *p++ = ((int32_t) *q++ - 128) << 24; *audio = buffer; error = 0; break; } case mlt_audio_f32le: { float *buffer = mlt_pool_alloc(size); float *p = buffer; uint8_t *q = (uint8_t *) *audio; int i = samples * channels + 1; while (--i) { float f = ((float) *q++ - 128) / 256.0f; *p++ = CLAMP(f, -1.0f, 1.0f); } *audio = buffer; error = 0; break; } default: break; } break; default: break; } } if (!error) { mlt_frame_set_audio(frame, *audio, requested_format, size, mlt_pool_release); *format = requested_format; } return error; } /** Filter processing. */ static mlt_frame filter_process(mlt_filter filter, mlt_frame frame) { frame->convert_audio = convert_audio; return frame; } /** Constructor for the filter. */ mlt_filter filter_audioconvert_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = calloc(1, sizeof(struct mlt_filter_s)); if (mlt_filter_init(filter, filter) == 0) filter->process = filter_process; return filter; } mlt-7.22.0/src/modules/core/filter_audioconvert.yml000664 000000 000000 00000000566 14531534050 022346 0ustar00rootroot000000 000000 schema_version: 7.0 type: filter identifier: audioconvert title: Audio Sample Format Converter version: 1 copyright: Meltytech, LLC license: LGPLv2.1 language: en tags: - Audio description: Converts the audio sample format. notes: > This is not intended to be created directly. Rather, the loader producer loads it to set the convert_audio function pointer on frames. mlt-7.22.0/src/modules/core/filter_audiomap.c000664 000000 000000 00000006065 14531534050 021064 0ustar00rootroot000000 000000 /* * filter_audiomap.c -- remap audio channels * Copyright (C) 2015 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #define MAX_CHANNELS 32 static int filter_get_audio(mlt_frame frame, void **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples) { char prop_name[32], *prop_val; int i, j, l, m[MAX_CHANNELS]; mlt_filter filter = mlt_frame_pop_audio(frame); // Get the producer's audio int error = mlt_frame_get_audio(frame, buffer, format, frequency, channels, samples); if (error) return error; mlt_properties properties = MLT_FILTER_PROPERTIES(filter); /* find samples length */ int len = mlt_audio_format_size(*format, 1, 1); /* pcm samples buffer */ uint8_t *pcm = *buffer; /* build matrix */ for (i = 0; i < MAX_CHANNELS; i++) { m[i] = i; snprintf(prop_name, sizeof(prop_name), "%d", i); if ((prop_val = mlt_properties_get(properties, prop_name))) { j = atoi(prop_val); if (j >= 0 && j < MAX_CHANNELS) m[i] = j; } } /* process samples */ for (i = 0; i < *samples; i++) { uint8_t tmp[MAX_CHANNELS * 4]; for (j = 0; j < MAX_CHANNELS && j < *channels; j++) for (l = 0; l < len; l++) tmp[j * len + l] = pcm[m[j] * len + l]; for (j = 0; j < MAX_CHANNELS && j < *channels; j++) for (l = 0; l < len; l++) pcm[j * len + l] = tmp[j * len + l]; pcm += len * (*channels); } return 0; } /** Filter processing. */ static mlt_frame filter_process(mlt_filter filter, mlt_frame frame) { mlt_frame_push_audio(frame, filter); mlt_frame_push_audio(frame, filter_get_audio); return frame; } /** Constructor for the filter. */ mlt_filter filter_audiomap_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = mlt_filter_new(); if (filter) filter->process = filter_process; return filter; } mlt-7.22.0/src/modules/core/filter_audiomap.yml000664 000000 000000 00000010745 14531534050 021443 0ustar00rootroot000000 000000 schema_version: 0.2 type: filter identifier: audiomap title: Remap Channels version: 1 copyright: Meltytech, LLC creator: Maksym Veremeyenko license: LGPLv2.1 language: en tags: - Audio description: Copy/swap channels according to given mapping. parameters: - identifier: '0' title: Source of channel 0 type: integer mutable: yes minimum: 0 maximum: 31 default: 0 - identifier: '1' title: Source of channel 1 type: integer mutable: yes minimum: 0 maximum: 31 default: 1 - identifier: '2' title: Source of channel 2 type: integer mutable: yes minimum: 0 maximum: 31 default: 2 - identifier: '3' title: Source of channel 3 type: integer mutable: yes minimum: 0 maximum: 31 default: 3 - identifier: '4' title: Source of channel 4 type: integer mutable: yes minimum: 0 maximum: 31 default: 4 - identifier: '5' title: Source of channel 5 type: integer mutable: yes minimum: 0 maximum: 31 default: 5 - identifier: '6' title: Source of channel 6 type: integer mutable: yes minimum: 0 maximum: 31 default: 6 - identifier: '7' title: Source of channel 7 type: integer mutable: yes minimum: 0 maximum: 31 default: 7 - identifier: '8' title: Source of channel 8 type: integer mutable: yes minimum: 0 maximum: 31 default: 8 - identifier: '9' title: Source of channel 9 type: integer mutable: yes minimum: 0 maximum: 31 default: 9 - identifier: '10' title: Source of channel 10 type: integer mutable: yes minimum: 0 maximum: 31 default: 10 - identifier: '11' title: Source of channel 11 type: integer mutable: yes minimum: 0 maximum: 31 default: 11 - identifier: '12' title: Source of channel 12 type: integer mutable: yes minimum: 0 maximum: 31 default: 12 - identifier: '13' title: Source of channel 13 type: integer mutable: yes minimum: 0 maximum: 31 default: 13 - identifier: '14' title: Source of channel 14 type: integer mutable: yes minimum: 0 maximum: 31 default: 14 - identifier: '15' title: Source of channel 15 type: integer mutable: yes minimum: 0 maximum: 31 default: 15 - identifier: '16' title: Source of channel 16 type: integer mutable: yes minimum: 0 maximum: 31 default: 16 - identifier: '17' title: Source of channel 17 type: integer mutable: yes minimum: 0 maximum: 31 default: 17 - identifier: '18' title: Source of channel 18 type: integer mutable: yes minimum: 0 maximum: 31 default: 18 - identifier: '19' title: Source of channel 19 type: integer mutable: yes minimum: 0 maximum: 31 default: 19 - identifier: '20' title: Source of channel 20 type: integer mutable: yes minimum: 0 maximum: 31 default: 20 - identifier: '21' title: Source of channel 21 type: integer mutable: yes minimum: 0 maximum: 31 default: 21 - identifier: '22' title: Source of channel 22 type: integer mutable: yes minimum: 0 maximum: 31 default: 22 - identifier: '23' title: Source of channel 23 type: integer mutable: yes minimum: 0 maximum: 31 default: 23 - identifier: '24' title: Source of channel 24 type: integer mutable: yes minimum: 0 maximum: 31 default: 24 - identifier: '25' title: Source of channel 25 type: integer mutable: yes minimum: 0 maximum: 31 default: 25 - identifier: '26' title: Source of channel 26 type: integer mutable: yes minimum: 0 maximum: 31 default: 26 - identifier: '27' title: Source of channel 27 type: integer mutable: yes minimum: 0 maximum: 31 default: 27 - identifier: '28' title: Source of channel 28 type: integer mutable: yes minimum: 0 maximum: 31 default: 28 - identifier: '29' title: Source of channel 29 type: integer mutable: yes minimum: 0 maximum: 31 default: 29 - identifier: '30' title: Source of channel 30 type: integer mutable: yes minimum: 0 maximum: 31 default: 30 - identifier: '31' title: Source of channel 31 type: integer mutable: yes minimum: 0 maximum: 31 default: 31 mlt-7.22.0/src/modules/core/filter_audioseam.c000664 000000 000000 00000016025 14531534050 021231 0ustar00rootroot000000 000000 /* * filter_audioseam.c -- smooth seams between clips in a playlist * Copyright (C) 2023 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include typedef struct { struct mlt_audio_s prev_audio; } private_data; static float db_delta(float a, float b) { float dba = 0; float dbb = 0; const float essentially_zero = 0.001; // Calculate db from zero if (fabs(a) > essentially_zero) { dba = log10(fabs(a)) * 20; } if (fabs(b) > essentially_zero) { dbb = log10(fabs(b)) * 20; } // Apply sign if (a < 0) { dba *= -1.0; } if (b < 0) { dba *= -1; } return dba - dbb; } static int filter_get_audio(mlt_frame frame, void **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples) { mlt_filter filter = mlt_frame_pop_audio(frame); mlt_properties filter_properties = MLT_FILTER_PROPERTIES(filter); private_data *pdata = (private_data *) filter->child; int clip_position = mlt_properties_get_int(MLT_FRAME_PROPERTIES(frame), "meta.playlist.clip_position"); int clip_length = mlt_properties_get_int(MLT_FRAME_PROPERTIES(frame), "meta.playlist.clip_length"); if (clip_length == 0 || (clip_position != 0 && clip_position != (clip_length - 1))) { // Only operate on the first and last frame of every clip return mlt_frame_get_audio(frame, buffer, format, frequency, channels, samples); } *format = mlt_audio_f32le; int ret = mlt_frame_get_audio(frame, buffer, format, frequency, channels, samples); if (ret != 0) { return ret; } struct mlt_audio_s curr_audio; mlt_audio_set_values(&curr_audio, *buffer, *frequency, *format, *samples, *channels); if (clip_position == 0) { if (!pdata->prev_audio.data) { mlt_log_debug(MLT_FILTER_SERVICE(filter), "Missing previous audio\n"); } else { float *prev_data = pdata->prev_audio.data; float *curr_data = curr_audio.data; float level_delta = db_delta(prev_data[pdata->prev_audio.samples - 1], curr_data[0]); double discontinuity_threshold = mlt_properties_get_double(filter_properties, "discontinuity_threshold"); if (fabs(level_delta) > discontinuity_threshold) { // We have decided to create a transition with the previous frame. // Reverse the prevous frame and use the reversed samples as faux // data that is continuous from the prevous frame. // Mix/fade the reversed previous samples with the new samples to create a transition. mlt_audio_reverse(&pdata->prev_audio); int fade_samples = 1000; if (fade_samples > curr_audio.samples) { fade_samples = curr_audio.samples; } if (fade_samples > pdata->prev_audio.samples) { fade_samples = pdata->prev_audio.samples; } for (int c = 0; c < curr_audio.channels; c++) { curr_data = (float *) curr_audio.data + c; prev_data = (float *) pdata->prev_audio.data + c; for (int i = 0; i < fade_samples; i++) { float mix = (1.0 / fade_samples) * (float) (fade_samples - i); *curr_data = (*prev_data * mix) + (*curr_data * (1.0 - mix)); curr_data += curr_audio.channels; prev_data += curr_audio.channels; } } // If this flag is set, it must be cleared so that other services will know it can't be ignored. mlt_properties_clear(MLT_FRAME_PROPERTIES(frame), "test_audio"); // Increment the counter mlt_properties_set_int(filter_properties, "seam_count", mlt_properties_get_int(filter_properties, "seam_count") + 1); } } mlt_audio_free_data(&pdata->prev_audio); } else if (clip_position == (clip_length - 1)) { // Save the samples of the last frame to be used to mix with the first frame of the next clip. mlt_audio_set_values(&pdata->prev_audio, NULL, *frequency, *format, *samples, *channels); mlt_audio_alloc_data(&pdata->prev_audio); mlt_audio_copy(&pdata->prev_audio, &curr_audio, *samples, 0, 0); } return 0; } static mlt_frame filter_process(mlt_filter filter, mlt_frame frame) { mlt_properties frame_properties = MLT_FRAME_PROPERTIES(frame); int clip_position = mlt_properties_get_int(frame_properties, "meta.playlist.clip_position"); int clip_length = mlt_properties_get_int(frame_properties, "meta.playlist.clip_length"); // Only operate on the first and last frame of every clip if (clip_length > 0 && (clip_position == 0 || clip_position == (clip_length - 1))) { // Be sure to process blanks in a playlist mlt_properties_clear(frame_properties, "test_audio"); mlt_frame_push_audio(frame, filter); mlt_frame_push_audio(frame, filter_get_audio); } return frame; } static void filter_close(mlt_filter filter) { private_data *pdata = (private_data *) filter->child; if (pdata) { mlt_audio_free_data(&pdata->prev_audio); } free(pdata); filter->child = NULL; filter->close = NULL; filter->parent.close = NULL; mlt_service_close(&filter->parent); } mlt_filter filter_audioseam_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = mlt_filter_new(); private_data *pdata = (private_data *) calloc(1, sizeof(private_data)); if (filter && pdata) { filter->close = filter_close; filter->process = filter_process; filter->child = pdata; } else { mlt_filter_close(filter); filter = NULL; free(pdata); } return filter; } mlt-7.22.0/src/modules/core/filter_audioseam.yml000664 000000 000000 00000002061 14531534050 021603 0ustar00rootroot000000 000000 schema_version: 7.0 type: filter identifier: audioseam title: Audio Seam version: 1 copyright: Meltytech, LLC license: LGPLv2.1 language: en tags: - Audio description: > Seam audio splices between clips in a playlist. Only to be used as a filter on playlist producers. Uses the "meta.playlist.clip_position" and "meta.playlist.clip_length" properties that are added by the playlist producer to determine where the seams between clips occur. parameters: - identifier: discontinuity_threshold title: Discontinuity Threshold type: float description: > The delta between the last sample of one clip and the first sample of the following clip that are spliced. If the delta is above the discontinuity threshold, then smoothing will be applied. readonly: no mutable: yes default: 2 minimum: 0 maximum: 30 unit: dB - identifier: seam_count title: Seam Count type: integer description: > The number of splices that have exceeded the discontinuity threshold and have been seamed. readonly: yes mlt-7.22.0/src/modules/core/filter_audiowave.c000664 000000 000000 00000004326 14531534050 021247 0ustar00rootroot000000 000000 /* * filter_audiowave.c -- display audio waveform * Copyright (C) 2010-2014 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include /** Do it :-). */ static int filter_get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { int size = *width * *height * 2; *format = mlt_image_yuv422; *image = mlt_pool_alloc(size); mlt_frame_set_image(frame, *image, size, mlt_pool_release); uint8_t *wave = mlt_frame_get_waveform(frame, *width, *height); if (wave) { uint8_t *p = *image; uint8_t *q = *image + *width * *height * 2; uint8_t *s = wave; while (p != q) { *p++ = *s++; *p++ = 128; } } return (wave == NULL); } /** Filter processing. */ static mlt_frame filter_process(mlt_filter filter, mlt_frame frame) { mlt_frame_push_get_image(frame, filter_get_image); return frame; } /** Constructor for the filter. */ mlt_filter filter_audiowave_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = mlt_filter_new(); if (filter != NULL) filter->process = filter_process; return filter; } mlt-7.22.0/src/modules/core/filter_audiowave.yml000664 000000 000000 00000001130 14531534050 021614 0ustar00rootroot000000 000000 schema_version: 0.1 type: filter identifier: audiowave title: Audio Waveform (*DEPRECATED*) version: 1 copyright: Meltytech, LLC creator: Dan Dennedy license: LGPLv2.1 language: en description: Generate audio waveforms. notes: > This filter is deprecated and will eventually be removed. Use the audiowaveform filter instead. tags: - Video bugs: - > This does not work alone on audio-only clips. It must have video to overwrite. A workaround is to apply this to a multitrack with a color generator. - The quality of the waveforms is not so good especially for high definition video. mlt-7.22.0/src/modules/core/filter_autofade.c000664 000000 000000 00000020771 14531534050 021055 0ustar00rootroot000000 000000 /* * filter_autofade.c -- Automatically fade audio between clips in a playlist. * Copyright (C) 2023 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include static float decay_factor(int position, int count) { float factor = (float) position / (float) (count - 1); if (factor < 0) { factor = 0; } else if (factor > 1.0) { factor = 1.0; } return factor; } static int filter_get_audio(mlt_frame frame, void **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples) { mlt_filter filter = mlt_frame_pop_audio(frame); *format = mlt_audio_f32le; int ret = mlt_frame_get_audio(frame, buffer, format, frequency, channels, samples); if (ret != 0) { return ret; } mlt_properties filter_properties = MLT_FILTER_PROPERTIES(filter); int clip_position = mlt_properties_get_int(MLT_FRAME_PROPERTIES(frame), "meta.playlist.clip_position"); int clip_length = mlt_properties_get_int(MLT_FRAME_PROPERTIES(frame), "meta.playlist.clip_length"); int fade_duration = mlt_properties_get_int(filter_properties, "fade_duration"); int fade_samples = fade_duration * *frequency / 1000; double fps = mlt_profile_fps(mlt_service_profile(MLT_FILTER_SERVICE(filter))); int64_t samples_to_frame_begin = mlt_audio_calculate_samples_to_position(fps, *frequency, clip_position); int64_t samples_in_clip = mlt_audio_calculate_samples_to_position(fps, *frequency, clip_length + 1); int64_t samples_to_clip_end = samples_in_clip - samples_to_frame_begin - *samples; struct mlt_audio_s audio; mlt_audio_set_values(&audio, *buffer, *frequency, *format, *samples, *channels); float *data = (float *) audio.data; if (samples_to_frame_begin <= fade_samples) { // Fade in for (int i = 0; i < audio.samples; i++) { float factor = decay_factor(samples_to_frame_begin + i, fade_samples); for (int c = 0; c < audio.channels; c++) { *data = *data * factor; data++; } } } else if ((samples_to_clip_end - *samples) <= fade_samples) { // Fade out for (int i = 0; i < audio.samples; i++) { float factor = decay_factor(samples_to_clip_end - i, fade_samples); for (int c = 0; c < audio.channels; c++) { *data = *data * factor; data++; } } } return 0; } static int filter_get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { int error = 0; mlt_filter filter = (mlt_filter) mlt_frame_pop_service(frame); // Get the current image *format = mlt_image_rgba; error = mlt_frame_get_image(frame, image, format, width, height, 1); if (error) return error; mlt_properties filter_properties = MLT_FILTER_PROPERTIES(filter); int clip_position = mlt_properties_get_int(MLT_FRAME_PROPERTIES(frame), "meta.playlist.clip_position"); int clip_length = mlt_properties_get_int(MLT_FRAME_PROPERTIES(frame), "meta.playlist.clip_length"); double fade_duration = mlt_properties_get_int(filter_properties, "fade_duration"); double fps = mlt_profile_fps(mlt_service_profile(MLT_FILTER_SERVICE(filter))); int fade_duration_frames = lrint(fade_duration * fps / 1000.0); float image_factor = 1.0; int frames_from_begining = clip_position + 1; int frames_to_end = clip_length - clip_position - 1; if (frames_from_begining <= fade_duration_frames) { // Fade In image_factor = decay_factor(clip_position, fade_duration_frames); } else if (frames_to_end <= fade_duration_frames) { // Fade Out image_factor = decay_factor(frames_to_end, fade_duration_frames); } if (image_factor < 1.0) { mlt_color color = mlt_properties_get_color(filter_properties, "fade_color"); float color_factor = 1.0 - image_factor; float r_value = color.r * color_factor; float g_value = color.g * color_factor; float b_value = color.b * color_factor; float a_value = color.a * color_factor; uint8_t *p = *image; int pixels = *width * *height; for (int i = 0; i < pixels; i++) { p[0] = ((float) p[0] * image_factor) + r_value; p[1] = ((float) p[1] * image_factor) + g_value; p[2] = ((float) p[2] * image_factor) + b_value; p[3] = ((float) p[3] * image_factor) + a_value; p += 4; } } return error; } static mlt_frame filter_process(mlt_filter filter, mlt_frame frame) { mlt_properties filter_properties = MLT_FILTER_PROPERTIES(filter); int clip_position = mlt_properties_get_int(MLT_FRAME_PROPERTIES(frame), "meta.playlist.clip_position"); int clip_length = mlt_properties_get_int(MLT_FRAME_PROPERTIES(frame), "meta.playlist.clip_length"); int fade_duration = mlt_properties_get_int(filter_properties, "fade_duration"); double fps = mlt_profile_fps(mlt_service_profile(MLT_FILTER_SERVICE(filter))); int ms_from_begining = (double) clip_position * 1000.0 / fps; int ms_from_end = (double) (clip_length - clip_position - 1) * 1000.0 / fps; int fade = 0; if (ms_from_begining <= fade_duration) { fade = 1; mlt_properties_set_int(filter_properties, "fade_in_count", mlt_properties_get_int(filter_properties, "fade_in_count") + 1); } else if (ms_from_end <= fade_duration) { fade = 1; mlt_properties_set_int(filter_properties, "fade_out_count", mlt_properties_get_int(filter_properties, "fade_out_count") + 1); } if (fade && mlt_properties_get_int(filter_properties, "fade_audio")) { mlt_frame_push_audio(frame, filter); mlt_frame_push_audio(frame, filter_get_audio); } if (fade && mlt_properties_get_int(filter_properties, "fade_video")) { mlt_frame_push_get_image(frame, (void *) filter); mlt_frame_push_get_image(frame, filter_get_image); } return frame; } static void filter_close(mlt_filter filter) { filter->child = NULL; filter->close = NULL; filter->parent.close = NULL; mlt_service_close(&filter->parent); } mlt_filter filter_autofade_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = mlt_filter_new(); if (filter) { filter->close = filter_close; filter->process = filter_process; mlt_properties filter_properties = MLT_FILTER_PROPERTIES(filter); mlt_properties_set_int(filter_properties, "fade_duration", 20); mlt_properties_set_int(filter_properties, "fade_audio", 1); mlt_properties_set_int(filter_properties, "fade_video", 0); mlt_properties_set_string(filter_properties, "fade_color", "0x000000ff"); } return filter; } mlt-7.22.0/src/modules/core/filter_autofade.yml000664 000000 000000 00000003500 14531534050 021423 0ustar00rootroot000000 000000 schema_version: 7.0 type: filter identifier: autofade title: Auto Fade version: 2 copyright: Meltytech, LLC license: LGPLv2.1 language: en tags: - Audio description: > Automatically fade audio between clips in a playlist. This filter will fade the audio out at the end of a clip and fade the audio in at the begining of a clip. Only to be used as a filter on playlist producers. Uses the "meta.playlist.clip_position" and "meta.playlist.clip_length" properties that are added by the playlist producer to determine where the splices between clips occur. parameters: - identifier: fade_duration title: Fade Duration type: integer description: > The duration of each fade in and fade out. readonly: no mutable: yes default: 20 minimum: 1 maximum: 1000 unit: ms - identifier: fade_video title: Fade Video type: boolean description: Fade the video to/from the fade color mutable: yes default: 0 - identifier: fade_audio title: Fade Audio type: boolean description: Fade the audio to/from silence mutable: yes default: 1 - identifier: fade_color title: Fade color type: color description: > The color to fade to. A color value is a hexadecimal representation of RGB plus alpha channel as 0xrrggbbaa. Colors can also be the words: white, black, red, green, or blue. You can also use a HTML-style color values #rrggbb or #aarrggbb default: 0x000000ff mutable: yes widget: color - identifier: fade_in_count title: Fade In Count type: integer description: > The number of time fade in has been applied. readonly: yes - identifier: fade_out_count title: Fade Out Count type: integer description: > The number of time fade out has been applied. readonly: yes mlt-7.22.0/src/modules/core/filter_box_blur.c000664 000000 000000 00000006570 14531534050 021102 0ustar00rootroot000000 000000 /* * filter_box_blur.c * Copyright (C) 2011-2023 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "image_proc.h" #include #include #include #include #include static int filter_get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { int error = 0; mlt_filter filter = (mlt_filter) mlt_frame_pop_service(frame); mlt_properties properties = MLT_FILTER_PROPERTIES(filter); mlt_position position = mlt_filter_get_position(filter, frame); mlt_position length = mlt_filter_get_length2(filter, frame); mlt_profile profile = mlt_service_profile(MLT_FILTER_SERVICE(filter)); double hradius = mlt_properties_anim_get_double(properties, "hradius", position, length); double vradius = mlt_properties_anim_get_double(properties, "vradius", position, length); int preserve_alpha = mlt_properties_get_int(properties, "preserve_alpha"); // Convert from percent to pixels as a factor of 10% image width. double pixelScale = (double) profile->width * mlt_profile_scale_width(profile, *width) / 1000.0; hradius = MAX(round(hradius * pixelScale), 0); vradius = MAX(round(vradius * pixelScale), 0); if (hradius == 0 && vradius == 0) { // Nothing to blur error = mlt_frame_get_image(frame, image, format, width, height, writable); } else { // Get the image *format = mlt_image_rgba; error = mlt_frame_get_image(frame, image, format, width, height, 1); if (error == 0) { struct mlt_image_s img; mlt_image_set_values(&img, *image, *format, *width, *height); mlt_image_box_blur(&img, hradius, vradius, preserve_alpha); } } return error; } /** Filter processing. */ static mlt_frame filter_process(mlt_filter filter, mlt_frame frame) { mlt_frame_push_service(frame, filter); mlt_frame_push_get_image(frame, filter_get_image); return frame; } /** Constructor for the filter. */ mlt_filter filter_box_blur_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = mlt_filter_new(); if (filter != NULL) { filter->process = filter_process; mlt_properties_set(MLT_FILTER_PROPERTIES(filter), "hradius", "1"); mlt_properties_set(MLT_FILTER_PROPERTIES(filter), "vradius", "1"); } return filter; } mlt-7.22.0/src/modules/core/filter_box_blur.yml000664 000000 000000 00000001653 14531534050 021456 0ustar00rootroot000000 000000 schema_version: 7.0 type: filter identifier: box_blur title: Box Blur version: 2 copyright: Meltytech, LLC license: LGPLv2.1 language: en tags: - Video parameters: - identifier: hradius title: Horizontal radius description: > The horizontal blur radius as a percent of the image size. 100% results in a blur radius of 10% of the image width. type: float unit: percent mutable: yes animation: yes minimum: 0 default: 1 - identifier: vradius title: Vertical radius description: > The vertical blur radius as a percent of the image size. 100% results in a blur radius of 10% of the image width. type: float unit: percent mutable: yes animation: yes minimum: 0 default: 1 - identifier: preserve_alpha title: Preserve Alpha type: boolean description: Exclude the alpha channel from the blur operation mutable: yes default: 0 mlt-7.22.0/src/modules/core/filter_brightness.c000664 000000 000000 00000016411 14531534050 021431 0ustar00rootroot000000 000000 /* * filter_brightness.c -- brightness, fade, and opacity filter * Copyright (C) 2003-2022 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include struct sliced_desc { mlt_image image; double level; double alpha_level; int full_range; }; static int sliced_proc(int id, int index, int jobs, void *cookie) { (void) id; // unused struct sliced_desc *ctx = ((struct sliced_desc *) cookie); int slice_line_start, slice_height = mlt_slices_size_slice(jobs, index, ctx->image->height, &slice_line_start); int min = ctx->full_range ? 0 : 16; int max_luma = ctx->full_range ? 255 : 235; int max_chroma = ctx->full_range ? 255 : 240; // Only process if level is something other than 1 if (ctx->level != 1.0 && ctx->image->format == mlt_image_yuv422) { int32_t m = ctx->level * (1 << 16); int32_t n = 128 * ((1 << 16) - m); for (int line = 0; line < slice_height; line++) { uint8_t *p = ctx->image->planes[0] + ((slice_line_start + line) * ctx->image->strides[0]); for (int pixel = 0; pixel < ctx->image->width; pixel++) { *p = CLAMP((*p * m) >> 16, min, max_luma); p++; *p = CLAMP((*p * m + n) >> 16, min, max_chroma); p++; } } } // Process the alpha channel if requested. if (ctx->alpha_level != 1.0) { int32_t m = ctx->alpha_level * (1 << 16); if (ctx->image->format == mlt_image_rgba) { for (int line = 0; line < slice_height; line++) { uint8_t *p = ctx->image->planes[0] + ((slice_line_start + line) * ctx->image->strides[0]) + 3; for (int pixel = 0; pixel < ctx->image->width; pixel++) { *p = (*p * m) >> 16; p += 4; } } } else { for (int line = 0; line < slice_height; line++) { uint8_t *p = ctx->image->planes[3] + ((slice_line_start + line) * ctx->image->strides[3]); for (int pixel = 0; pixel < ctx->image->width; pixel++) { *p = (*p * m) >> 16; p++; } } } } return 0; } /** Do it :-). */ static int filter_get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { mlt_filter filter = (mlt_filter) mlt_frame_pop_service(frame); mlt_properties properties = MLT_FILTER_PROPERTIES(filter); mlt_position position = mlt_filter_get_position(filter, frame); mlt_position length = mlt_filter_get_length2(filter, frame); double level = 1.0; double alpha_level = 1.0; // Use animated "level" property only if it has been set since init char *level_property = mlt_properties_get(properties, "level"); if (level_property != NULL) { level = mlt_properties_anim_get_double(properties, "level", position, length); } else { // Get level using old "start,"end" mechanics // Get the starting brightness level level = fabs(mlt_properties_get_double(properties, "start")); // If there is an end adjust gain to the range if (mlt_properties_get(properties, "end") != NULL) { // Determine the time position of this frame in the transition duration double end = fabs(mlt_properties_get_double(properties, "end")); level += (end - level) * mlt_filter_get_progress(filter, frame); } } // Do not cause an image conversion unless there is real work to do. if (level != 1.0) *format = mlt_image_yuv422; // Get the image int error = mlt_frame_get_image(frame, image, format, width, height, 1); level = (*format == mlt_image_yuv422) ? level : 1.0; alpha_level = mlt_properties_get(properties, "alpha") ? MIN(mlt_properties_anim_get_double(properties, "alpha", position, length), 1.0) : 1.0; if (alpha_level < 0.0) { alpha_level = level; } // Only process if we have no error. if (!error && (level != 1.0 || alpha_level != 1.0)) { int threads = mlt_properties_get_int(properties, "threads"); struct sliced_desc desc; struct mlt_image_s proc_image; mlt_image_set_values(&proc_image, *image, *format, *width, *height); if (alpha_level != 1.0 && proc_image.format != mlt_image_rgba) { proc_image.planes[3] = mlt_frame_get_alpha(frame); proc_image.strides[3] = proc_image.width; if (!proc_image.planes[3]) { // Alpha will be needed but it does not exist yet. Create opaque alpha. mlt_image_alloc_alpha(&proc_image); mlt_image_fill_opaque(&proc_image); mlt_frame_set_alpha(frame, proc_image.planes[3], proc_image.width * proc_image.height, proc_image.release_alpha); } } desc.level = level; desc.alpha_level = alpha_level; desc.image = &proc_image; desc.full_range = mlt_properties_get_int(MLT_FRAME_PROPERTIES(frame), "full_range"); threads = CLAMP(threads, 0, mlt_slices_count_normal()); if (threads == 1) { sliced_proc(0, 0, 1, &desc); } else { mlt_slices_run_normal(threads, sliced_proc, &desc); } } return error; } /** Filter processing. */ static mlt_frame filter_process(mlt_filter filter, mlt_frame frame) { mlt_frame_push_service(frame, filter); mlt_frame_push_get_image(frame, filter_get_image); return frame; } /** Constructor for the filter. */ mlt_filter filter_brightness_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = mlt_filter_new(); if (filter != NULL) { filter->process = filter_process; mlt_properties_set(MLT_FILTER_PROPERTIES(filter), "start", arg == NULL ? "1" : arg); mlt_properties_set(MLT_FILTER_PROPERTIES(filter), "level", NULL); } return filter; } mlt-7.22.0/src/modules/core/filter_brightness.yml000664 000000 000000 00000002437 14531534050 022013 0ustar00rootroot000000 000000 schema_version: 7.0 type: filter identifier: brightness title: Brightness version: 4 copyright: Meltytech, LLC creator: Dan Dennedy license: LGPLv2.1 language: en description: Adjust the brightness and opacity of the image. tags: - Video parameters: - identifier: start title: Start level (*DEPRECATED*) type: float argument: yes minimum: 0.0 maximum: 15.0 default: 1.0 - identifier: end (*DEPRECATED*) title: End level type: float minimum: 0.0 maximum: 15.0 default: 1.0 - identifier: level title: Level type: float minimum: 0.0 maximum: 15.0 mutable: yes animation: yes - identifier: alpha title: Alpha factor description: > When this is less than zero, the alpha factor follows the level property. Otherwise, you can set this to another value to adjust the alpha component independently. No alpha channel adjustment occurs if this is not set or it equals 1. type: float minimum: -1 maximum: 1 mutable: yes animation: yes - identifier: threads title: Thread count description: > Use 0 to use the slice count, which defaults to the number of detected CPUs. Otherwise, set the number of threads to use up to the slice count. minimum: 0 default: 0 mlt-7.22.0/src/modules/core/filter_channelcopy.c000664 000000 000000 00000013057 14531534050 021567 0ustar00rootroot000000 000000 /* * filter_channelcopy.c -- copy one audio channel to another * Copyright (C) 2003-2018 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include /** Get the audio. */ static int filter_get_audio(mlt_frame frame, void **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples) { // Get the filter service mlt_filter filter = mlt_frame_pop_audio(frame); mlt_properties properties = MLT_FILTER_PROPERTIES(filter); int from = mlt_properties_get_int(properties, "from"); int to = mlt_properties_get_int(properties, "to"); int swap = mlt_properties_get_int(properties, "swap"); // Get the producer's audio mlt_frame_get_audio(frame, buffer, format, frequency, channels, samples); // Copy channels as necessary if (from != to) switch (*format) { case mlt_audio_u8: { uint8_t *f = (uint8_t *) *buffer + from; uint8_t *t = (uint8_t *) *buffer + to; uint8_t x; int i; if (swap) for (i = 0; i < *samples; i++, f += *channels, t += *channels) { x = *t; *t = *f; *f = x; } else for (i = 0; i < *samples; i++, f += *channels, t += *channels) *t = *f; break; } case mlt_audio_s16: { int16_t *f = (int16_t *) *buffer + from; int16_t *t = (int16_t *) *buffer + to; int16_t x; int i; if (swap) for (i = 0; i < *samples; i++, f += *channels, t += *channels) { x = *t; *t = *f; *f = x; } else for (i = 0; i < *samples; i++, f += *channels, t += *channels) *t = *f; break; } case mlt_audio_s32: { int32_t *f = (int32_t *) *buffer + from * *samples; int32_t *t = (int32_t *) *buffer + to * *samples; if (swap) { int32_t *x = malloc(*samples * sizeof(int32_t)); memcpy(x, t, *samples * sizeof(int32_t)); memcpy(t, f, *samples * sizeof(int32_t)); memcpy(f, x, *samples * sizeof(int32_t)); free(x); } else { memcpy(t, f, *samples * sizeof(int32_t)); } break; } case mlt_audio_s32le: case mlt_audio_f32le: { int32_t *f = (int32_t *) *buffer + from; int32_t *t = (int32_t *) *buffer + to; int32_t x; int i; if (swap) for (i = 0; i < *samples; i++, f += *channels, t += *channels) { x = *t; *t = *f; *f = x; } else for (i = 0; i < *samples; i++, f += *channels, t += *channels) *t = *f; break; } case mlt_audio_float: { float *f = (float *) *buffer + from * *samples; float *t = (float *) *buffer + to * *samples; if (swap) { float *x = malloc(*samples * sizeof(float)); memcpy(x, t, *samples * sizeof(float)); memcpy(t, f, *samples * sizeof(float)); memcpy(f, x, *samples * sizeof(float)); free(x); } else { memcpy(t, f, *samples * sizeof(float)); } break; } default: mlt_log_error(MLT_FILTER_SERVICE(filter), "Invalid audio format\n"); break; } return 0; } /** Filter processing. */ static mlt_frame filter_process(mlt_filter filter, mlt_frame frame) { // Override the get_audio method mlt_frame_push_audio(frame, filter); mlt_frame_push_audio(frame, filter_get_audio); return frame; } /** Constructor for the filter. */ mlt_filter filter_channelcopy_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = mlt_filter_new(); if (filter != NULL) { filter->process = filter_process; if (arg != NULL) mlt_properties_set_int(MLT_FILTER_PROPERTIES(filter), "to", atoi(arg)); else mlt_properties_set_int(MLT_FILTER_PROPERTIES(filter), "to", 1); if (strcmp(id, "channelswap") == 0) mlt_properties_set_int(MLT_FILTER_PROPERTIES(filter), "swap", 1); } return filter; } mlt-7.22.0/src/modules/core/filter_channelcopy.yml000664 000000 000000 00000001227 14531534050 022142 0ustar00rootroot000000 000000 schema_version: 0.2 type: filter identifier: channelcopy title: Copy Channels version: 1 copyright: Meltytech, LLC creator: Dan Dennedy license: LGPLv2.1 language: en tags: - Audio description: Copy one audio channel to another. parameters: - identifier: to title: To type: integer argument: true minimum: 0 maximum: 15 default: 1 - identifier: from title: From type: integer minimum: 0 maximum: 15 default: 0 - identifier: swap title: Swap description: Swap the two channels instead of duplicating the source channel. type: integer minimum: 0 maximum: 1 default: 0 widget: checkbox mlt-7.22.0/src/modules/core/filter_choppy.c000664 000000 000000 00000011001 14531534050 020551 0ustar00rootroot000000 000000 /* * filter_choppy.c -- simple frame repeating filter * Copyright (C) 2020-2022 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include static int get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { int error; mlt_filter filter = mlt_frame_pop_service(frame); mlt_properties properties = MLT_FILTER_PROPERTIES(filter); mlt_position position = mlt_filter_get_position(filter, frame); mlt_position length = mlt_filter_get_length2(filter, frame); int amount = mlt_properties_anim_get_int(properties, "amount", position, length) + 1; if (amount > 1) { mlt_service_lock(MLT_FILTER_SERVICE(filter)); mlt_frame cloned_frame = mlt_properties_get_data(properties, "cloned_frame", NULL); mlt_position cloned_pos = mlt_frame_get_position(cloned_frame); position = mlt_frame_get_position(frame); if (!cloned_frame || MLT_POSITION_MOD(position, amount) == 0 || abs(position - cloned_pos) > amount) { error = mlt_frame_get_image(frame, image, format, width, height, writable); cloned_frame = mlt_frame_clone(frame, 1); mlt_properties_set_data(properties, "cloned_frame", cloned_frame, 0, (mlt_destructor) mlt_frame_close, NULL); mlt_service_unlock(MLT_FILTER_SERVICE(filter)); } else { mlt_service_unlock(MLT_FILTER_SERVICE(filter)); error = mlt_frame_get_image(frame, image, format, width, height, writable); if (!error) { mlt_properties cloned_props = MLT_FRAME_PROPERTIES(cloned_frame); int size = 0; void *data = mlt_properties_get_data(cloned_props, "image", &size); if (data) { *width = mlt_properties_get_int(cloned_props, "width"); *height = mlt_properties_get_int(cloned_props, "height"); *format = mlt_properties_get_int(cloned_props, "format"); if (!size) { size = mlt_image_format_size(*format, *width, *height, NULL); } *image = mlt_pool_alloc(size); memcpy(*image, data, size); mlt_frame_set_image(frame, *image, size, mlt_pool_release); data = mlt_frame_get_alpha_size(cloned_frame, &size); if (data) { if (!size) { size = (*width) * (*height); } void *copy = mlt_pool_alloc(size); memcpy(copy, data, size); mlt_frame_set_alpha(frame, copy, size, mlt_pool_release); } } } } } else { error = mlt_frame_get_image(frame, image, format, width, height, writable); } return error; } /** Filter processing. */ static mlt_frame process(mlt_filter filter, mlt_frame frame) { mlt_frame_push_service(frame, filter); mlt_frame_push_get_image(frame, get_image); return frame; } /** Constructor for the filter. */ mlt_filter filter_choppy_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = mlt_filter_new(); if (filter) { filter->process = process; mlt_properties_set(MLT_FILTER_PROPERTIES(filter), "amount", arg ? arg : "0"); } return filter; } mlt-7.22.0/src/modules/core/filter_choppy.yml000664 000000 000000 00000000634 14531534050 021142 0ustar00rootroot000000 000000 schema_version: 7.0 type: filter identifier: choppy title: Choppy version: 2 copyright: Meltytech, LLC creator: Dan Dennedy license: LGPLv2.1 language: en tags: - Video parameters: - identifier: amount title: Repeat type: integer description: The keyframable number of frames to repeat in a row argument: yes default: 0 minimum: 0 mutable: yes animation: yes unit: frames mlt-7.22.0/src/modules/core/filter_crop.c000664 000000 000000 00000022224 14531534050 020223 0ustar00rootroot000000 000000 /* * filter_crop.c -- cropping filter * Copyright (C) 2009-2022 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include #include static void crop(uint8_t *src, uint8_t *dest, int bpp, int width, int height, int left, int right, int top, int bottom) { int src_stride = (width) *bpp; int dest_stride = (width - left - right) * bpp; int y = height - top - bottom + 1; src += top * src_stride + left * bpp; while (--y) { memcpy(dest, src, dest_stride); dest += dest_stride; src += src_stride; } } /** Do it :-). */ static int filter_get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { int error = 0; mlt_profile profile = mlt_frame_pop_service(frame); // Get the properties from the frame mlt_properties properties = MLT_FRAME_PROPERTIES(frame); // Correct Width/height if necessary if (*width == 0 || *height == 0) { *width = profile->width; *height = profile->height; } int left = mlt_properties_get_int(properties, "crop.left"); int right = mlt_properties_get_int(properties, "crop.right"); int top = mlt_properties_get_int(properties, "crop.top"); int bottom = mlt_properties_get_int(properties, "crop.bottom"); // Request the image at its original resolution if (left || right || top || bottom) { mlt_properties_set_int(properties, "rescale_width", mlt_properties_get_int(properties, "crop.original_width")); mlt_properties_set_int(properties, "rescale_height", mlt_properties_get_int(properties, "crop.original_height")); } // Now get the image error = mlt_frame_get_image(frame, image, format, width, height, writable); int owidth = *width - left - right; int oheight = *height - top - bottom; owidth = owidth < 0 ? 0 : owidth; oheight = oheight < 0 ? 0 : oheight; if ((owidth != *width || oheight != *height) && error == 0 && *image != NULL && owidth > 0 && oheight > 0) { int bpp; mlt_image_format requested_format = *format; if (requested_format == mlt_image_yuv420p) { // The crop function does not support planar. requested_format = mlt_image_yuv422; } if (requested_format == mlt_image_yuv422 && (left & 1 || right & 1)) { // Subsampled YUV requires even dimensions. Use RGB for odd dimensions. requested_format = mlt_image_rgb; } if (*format != requested_format && frame->convert_image) { frame->convert_image(frame, image, format, requested_format); } mlt_log_debug(NULL, "[filter crop] %s %dx%d -> %dx%d\n", mlt_image_format_name(*format), *width, *height, owidth, oheight); if (top % 2) mlt_properties_set_int(properties, "top_field_first", !mlt_properties_get_int(properties, "top_field_first")); // Create the output image int size = mlt_image_format_size(*format, owidth, oheight, &bpp); uint8_t *output = mlt_pool_alloc(size); if (output) { // Call the generic resize crop(*image, output, bpp, *width, *height, left, right, top, bottom); // Now update the frame mlt_frame_set_image(frame, output, size, mlt_pool_release); *image = output; } // We should resize the alpha too int alpha_size = 0; uint8_t *alpha = mlt_frame_get_alpha_size(frame, &alpha_size); if (alpha && alpha_size >= (*width * *height)) { uint8_t *newalpha = mlt_pool_alloc(owidth * oheight); if (newalpha) { crop(alpha, newalpha, 1, *width, *height, left, right, top, bottom); mlt_frame_set_alpha(frame, newalpha, owidth * oheight, mlt_pool_release); } } *width = owidth; *height = oheight; } return error; } /** Filter processing. */ static mlt_frame filter_process(mlt_filter filter, mlt_frame frame) { if (mlt_properties_get_int(MLT_FILTER_PROPERTIES(filter), "active")) { // Push the get_image method on to the stack mlt_frame_push_service(frame, mlt_service_profile(MLT_FILTER_SERVICE(filter))); mlt_frame_push_get_image(frame, filter_get_image); } else { mlt_properties filter_props = MLT_FILTER_PROPERTIES(filter); mlt_properties frame_props = MLT_FRAME_PROPERTIES(frame); int left = mlt_properties_get_int(filter_props, "left"); int right = mlt_properties_get_int(filter_props, "right"); int top = mlt_properties_get_int(filter_props, "top"); int bottom = mlt_properties_get_int(filter_props, "bottom"); int width = mlt_properties_get_int(frame_props, "meta.media.width"); int height = mlt_properties_get_int(frame_props, "meta.media.height"); int use_profile = mlt_properties_get_int(filter_props, "use_profile"); mlt_profile profile = mlt_service_profile(MLT_FILTER_SERVICE(filter)); if (use_profile) { top = top * height / profile->height; bottom = bottom * height / profile->height; left = left * width / profile->width; right = right * width / profile->width; } if (mlt_properties_get_int(filter_props, "center")) { double aspect_ratio = mlt_frame_get_aspect_ratio(frame); if (aspect_ratio == 0.0) aspect_ratio = mlt_profile_sar(profile); double input_ar = aspect_ratio * width / height; double output_ar = mlt_profile_dar(mlt_service_profile(MLT_FILTER_SERVICE(filter))); int bias = mlt_properties_get_int(filter_props, "center_bias"); if (input_ar > output_ar) { left = right = (width - rint(output_ar * height / aspect_ratio)) / 2; if (use_profile) bias = bias * width / profile->width; if (abs(bias) > left) bias = bias < 0 ? -left : left; left -= bias; right += bias; } else { top = bottom = (height - rint(aspect_ratio * width / output_ar)) / 2; if (use_profile) bias = bias * height / profile->height; if (abs(bias) > top) bias = bias < 0 ? -top : top; top -= bias; bottom += bias; } } // Coerce the output to an even width because subsampled YUV with odd widths is too // risky for downstream processing to handle correctly. left += (width - left - right) & 1; if (width - left - right < 8) left = right = 0; if (height - top - bottom < 8) top = bottom = 0; mlt_properties_set_int(frame_props, "crop.left", left); mlt_properties_set_int(frame_props, "crop.right", right); mlt_properties_set_int(frame_props, "crop.top", top); mlt_properties_set_int(frame_props, "crop.bottom", bottom); mlt_properties_set_int(frame_props, "crop.original_width", width); mlt_properties_set_int(frame_props, "crop.original_height", height); mlt_properties_set_int(frame_props, "meta.media.width", width - left - right); mlt_properties_set_int(frame_props, "meta.media.height", height - top - bottom); } return frame; } /** Constructor for the filter. */ mlt_filter filter_crop_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = calloc(1, sizeof(struct mlt_filter_s)); if (mlt_filter_init(filter, filter) == 0) { filter->process = filter_process; if (arg) mlt_properties_set_int(MLT_FILTER_PROPERTIES(filter), "active", atoi(arg)); } return filter; } mlt-7.22.0/src/modules/core/filter_crop.yml000664 000000 000000 00000003657 14531534050 020613 0ustar00rootroot000000 000000 schema_version: 7.0 type: filter identifier: crop title: Crop version: 1 copyright: Meltytech, LLC creator: Dan Dennedy license: LGPLv2.1 language: en description: Remove pixels from the edges of the video. tags: - Video notes: > This filter is meant to be included as a normalizing filter attached automatically by the loader so that cropping occurs early in the processing stack and can request the full resolution of the source image. Then, a second instance of the filter may be applied to set the parameters of the crop operation. parameters: - identifier: active argument: yes title: Active description: Whether to do the processing (1) or simply set the parameters. type: integer minimum: 0 maximum: 1 default: 0 - identifier: left title: Left type: integer minimum: 0 default: 0 unit: pixels - identifier: right title: Right type: integer minimum: 0 default: 0 unit: pixels - identifier: top title: Top type: integer minimum: 0 default: 0 unit: pixels - identifier: bottom title: Bottom type: integer minimum: 0 default: 0 unit: pixels - identifier: center title: Center crop description: Whether to automatically crop whatever is needed to fill the output frame and prevent padding. type: integer minimum: 0 maximum: 1 default: 0 widget: checkbox - identifier: center_bias title: Center balance description: When center crop is enabled, offset the center point. type: integer minimum: 0 default: 0 unit: pixels - identifier: use_profile title: Use profile resolution description: > This is useful for proxy editing. Normally all crop values are expressed in terms of pixels of the source footage, but this option makes them relative to the profile resolution. type: integer minimum: 0 maximum: 1 default: 0 widget: checkbox mlt-7.22.0/src/modules/core/filter_fieldorder.c000664 000000 000000 00000012261 14531534050 021377 0ustar00rootroot000000 000000 /* * filter_fieldorder.c -- change field dominance * Copyright (C) 2011-2019 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include static int get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { // Get the properties from the frame mlt_properties properties = MLT_FRAME_PROPERTIES(frame); // Get the input image, width and height int error = mlt_frame_get_image(frame, image, format, width, height, writable); if (!error && *image) { int tff = mlt_properties_get_int(properties, "consumer.top_field_first"); // Provides a manual override for misreported field order if (mlt_properties_get(properties, "meta.top_field_first")) mlt_properties_set_int(properties, "top_field_first", mlt_properties_get_int(properties, "meta.top_field_first")); mlt_log_debug(NULL, "TFF in %d out %d\n", mlt_properties_get_int(properties, "top_field_first"), tff); if (mlt_properties_get_int(properties, "meta.swap_fields") && mlt_properties_get(properties, "progressive") && mlt_properties_get_int(properties, "progressive") == 0) { // We only work with non-planar formats if (*format == mlt_image_yuv420p && frame->convert_image) error = frame->convert_image(frame, image, format, mlt_image_yuv422); // Make a new image int bpp; int size = mlt_image_format_size(*format, *width, *height, &bpp); uint8_t *new_image = mlt_pool_alloc(size); int stride = *width * bpp; int i = *height + 1; uint8_t *src = *image; // Set the new image mlt_frame_set_image(frame, new_image, size, mlt_pool_release); *image = new_image; while (--i) { memcpy(new_image, src + stride * !(i % 2), stride); new_image += stride; src += stride * (i % 2) * 2; } } // Correct field order if needed if (tff != -1 && mlt_properties_get_int(properties, "top_field_first") != tff && mlt_properties_get(properties, "progressive") && mlt_properties_get_int(properties, "progressive") == 0) { mlt_log_timings_begin(); // We only work with non-vertical luma/chroma scaled if (*format == mlt_image_yuv420p) { *format = mlt_image_yuv422; mlt_frame_get_image(frame, image, format, width, height, writable); } // Shift the entire image down by one line int p, strides[4], size; uint8_t *new_planes[4], *old_planes[4], *new_image; size = mlt_image_format_size(*format, *width, *height, NULL); new_image = mlt_pool_alloc(size); mlt_image_format_planes(*format, *width, *height, new_image, new_planes, strides); mlt_image_format_planes(*format, *width, *height, *image, old_planes, strides); for (p = 0; p < 4; p++) { if (!new_planes[p]) continue; memcpy(new_planes[p], old_planes[p], strides[p]); memcpy(new_planes[p] + strides[p], old_planes[p], strides[p] * (*height - 1)); } // Set the new image mlt_frame_set_image(frame, new_image, size, mlt_pool_release); *image = new_image; mlt_log_timings_end(NULL, "shifting_fields"); } // Set the normalized field order mlt_properties_set_int(properties, "top_field_first", tff); mlt_properties_set_int(properties, "meta.top_field_first", tff); } return error; } static mlt_frame process(mlt_filter filter, mlt_frame frame) { mlt_frame_push_get_image(frame, get_image); return frame; } mlt_filter filter_fieldorder_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = calloc(1, sizeof(*filter)); if (mlt_filter_init(filter, NULL) == 0) { filter->process = process; } return filter; } mlt-7.22.0/src/modules/core/filter_fieldorder.yml000664 000000 000000 00000001371 14531534050 021756 0ustar00rootroot000000 000000 schema_version: 0.1 type: filter identifier: fieldorder title: Field order description: Correct the field order of interlaced video. version: 1 copyright: Meltytech, LLC creator: Dan Dennedy license: LGPLv2.1 language: en tags: - Video - Hidden notes: > This filter is automatically invoked by the loader as part of image normalization. It compares the frame property top_field_first to the consumer property with the same name to determine if correction is needed. It performs field order correction by shifting the image down by one line. If you set the property meta.swap_fields=1 on the producer, then this filter swaps the fields of an interlaced frame in addition to any field order correction by shifting the image. mlt-7.22.0/src/modules/core/filter_gamma.c000664 000000 000000 00000005440 14531534050 020343 0ustar00rootroot000000 000000 /* * filter_gamma.c -- gamma filter * Copyright (C) 2003-2014 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include /** Do it :-). */ static int filter_get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { mlt_filter filter = (mlt_filter) mlt_frame_pop_service(frame); mlt_properties properties = MLT_FILTER_PROPERTIES(filter); mlt_position position = mlt_filter_get_position(filter, frame); mlt_position length = mlt_filter_get_length2(filter, frame); *format = mlt_image_yuv422; int error = mlt_frame_get_image(frame, image, format, width, height, 1); if (error == 0) { // Get the gamma value double gamma = mlt_properties_anim_get_double(properties, "gamma", position, length); if (gamma != 1.0) { uint8_t *p = *image; uint8_t *q = *image + *width * *height * 2; // Calculate the look up table double exp = 1 / gamma; uint8_t lookup[256]; int i; for (i = 0; i < 256; i++) lookup[i] = (uint8_t) (pow((double) i / 255.0, exp) * 255); while (p != q) { *p = lookup[*p]; p += 2; } } } return 0; } /** Filter processing. */ static mlt_frame filter_process(mlt_filter filter, mlt_frame frame) { mlt_frame_push_service(frame, filter); mlt_frame_push_get_image(frame, filter_get_image); return frame; } /** Constructor for the filter. */ mlt_filter filter_gamma_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = mlt_filter_new(); if (filter != NULL) { filter->process = filter_process; mlt_properties_set(MLT_FILTER_PROPERTIES(filter), "gamma", arg == NULL ? "1" : arg); } return filter; } mlt-7.22.0/src/modules/core/filter_gamma.yml000664 000000 000000 00000000667 14531534050 020730 0ustar00rootroot000000 000000 schema_version: 7.0 type: filter identifier: gamma title: Gamma version: 1 copyright: Meltytech, LLC creator: Charles Yates license: LGPLv2.1 language: en tags: - Video description: Adjust image luma using a non-linear power-law curve. parameters: - identifier: gamma argument: yes title: Gamma type: float description: The exponential factor of the power-law curve mutable: yes animation: yes default: 1.0 mlt-7.22.0/src/modules/core/filter_greyscale.c000664 000000 000000 00000004017 14531534050 021236 0ustar00rootroot000000 000000 /* * filter_greyscale.c -- greyscale filter * Copyright (C) 2003-2014 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include /** Do it :-). */ static int filter_get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { *format = mlt_image_yuv422; int error = mlt_frame_get_image(frame, image, format, width, height, 1); if (error == 0) { uint8_t *p = *image; uint8_t *q = *image + *width * *height * 2; while (p++ != q) *p++ = 128; } return error; } /** Filter processing. */ static mlt_frame filter_process(mlt_filter filter, mlt_frame frame) { mlt_frame_push_get_image(frame, filter_get_image); return frame; } /** Constructor for the filter. */ mlt_filter filter_greyscale_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = mlt_filter_new(); if (filter != NULL) filter->process = filter_process; return filter; } mlt-7.22.0/src/modules/core/filter_greyscale.yml000664 000000 000000 00000000342 14531534050 021612 0ustar00rootroot000000 000000 schema_version: 0.1 type: filter identifier: greyscale title: Greyscale version: 1 copyright: Meltytech, LLC creator: Charles Yates license: LGPLv2.1 language: en tags: - Video description: Convert colour image to greyscale mlt-7.22.0/src/modules/core/filter_imageconvert.c000664 000000 000000 00000040771 14531534050 021752 0ustar00rootroot000000 000000 /* * filter_imageconvert.c -- colorspace and pixel format converter * Copyright (C) 2009-2021 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include /** This macro converts a YUV value to the RGB color space. */ #define RGB2YUV_601_UNSCALED(r, g, b, y, u, v) \ y = (299 * r + 587 * g + 114 * b) >> 10; \ u = ((-169 * r - 331 * g + 500 * b) >> 10) + 128; \ v = ((500 * r - 419 * g - 81 * b) >> 10) + 128; \ y = y < 16 ? 16 : y; \ u = u < 16 ? 16 : u; \ v = v < 16 ? 16 : v; \ y = y > 235 ? 235 : y; \ u = u > 240 ? 240 : u; \ v = v > 240 ? 240 : v /** This macro converts a YUV value to the RGB color space. */ #define YUV2RGB_601_UNSCALED(y, u, v, r, g, b) \ r = ((1024 * y + 1404 * (v - 128)) >> 10); \ g = ((1024 * y - 715 * (v - 128) - 345 * (u - 128)) >> 10); \ b = ((1024 * y + 1774 * (u - 128)) >> 10); \ r = r < 0 ? 0 : r > 255 ? 255 : r; \ g = g < 0 ? 0 : g > 255 ? 255 : g; \ b = b < 0 ? 0 : b > 255 ? 255 : b; #define SCALED 1 #if SCALED #define RGB2YUV_601 RGB2YUV_601_SCALED #define YUV2RGB_601 YUV2RGB_601_SCALED #else #define RGB2YUV_601 RGB2YUV_601_UNSCALED #define YUV2RGB_601 YUV2RGB_601_UNSCALED #endif static void convert_yuv422_to_rgba(mlt_image src, mlt_image dst) { int yy, uu, vv; int r, g, b; mlt_image_set_values(dst, NULL, mlt_image_rgba, src->width, src->height); mlt_image_alloc_data(dst); for (int line = 0; line < src->height; line++) { uint8_t *pSrc = src->planes[0] + src->strides[0] * line; uint8_t *pAlpha = src->planes[3] + src->strides[3] * line; uint8_t *pDst = dst->planes[0] + dst->strides[0] * line; int total = src->width / 2 + 1; if (pAlpha) while (--total) { yy = pSrc[0]; uu = pSrc[1]; vv = pSrc[3]; YUV2RGB_601(yy, uu, vv, r, g, b); pDst[0] = r; pDst[1] = g; pDst[2] = b; pDst[3] = *pAlpha++; yy = pSrc[2]; YUV2RGB_601(yy, uu, vv, r, g, b); pDst[4] = r; pDst[5] = g; pDst[6] = b; pDst[7] = *pAlpha++; pSrc += 4; pDst += 8; } else while (--total) { yy = pSrc[0]; uu = pSrc[1]; vv = pSrc[3]; YUV2RGB_601(yy, uu, vv, r, g, b); pDst[0] = r; pDst[1] = g; pDst[2] = b; pDst[3] = 0xff; yy = pSrc[2]; YUV2RGB_601(yy, uu, vv, r, g, b); pDst[4] = r; pDst[5] = g; pDst[6] = b; pDst[7] = 0xff; pSrc += 4; pDst += 8; } } } static void convert_yuv422_to_rgb(mlt_image src, mlt_image dst) { int yy, uu, vv; int r, g, b; mlt_image_set_values(dst, NULL, mlt_image_rgb, src->width, src->height); mlt_image_alloc_data(dst); for (int line = 0; line < src->height; line++) { uint8_t *pSrc = src->planes[0] + src->strides[0] * line; uint8_t *pDst = dst->planes[0] + dst->strides[0] * line; int total = src->width / 2 + 1; while (--total) { yy = pSrc[0]; uu = pSrc[1]; vv = pSrc[3]; YUV2RGB_601(yy, uu, vv, r, g, b); pDst[0] = r; pDst[1] = g; pDst[2] = b; yy = pSrc[2]; YUV2RGB_601(yy, uu, vv, r, g, b); pDst[3] = r; pDst[4] = g; pDst[5] = b; pSrc += 4; pDst += 6; } } } static void convert_rgba_to_yuv422(mlt_image src, mlt_image dst) { int y0, y1, u0, u1, v0, v1; int r, g, b; mlt_image_set_values(dst, NULL, mlt_image_yuv422, src->width, src->height); mlt_image_alloc_data(dst); mlt_image_alloc_alpha(dst); for (int line = 0; line < src->height; line++) { uint8_t *pSrc = src->planes[0] + src->strides[0] * line; uint8_t *pDst = dst->planes[0] + dst->strides[0] * line; uint8_t *pAlpha = dst->planes[3] + dst->strides[3] * line; int j = src->width / 2 + 1; while (--j) { r = *pSrc++; g = *pSrc++; b = *pSrc++; *pAlpha++ = *pSrc++; RGB2YUV_601(r, g, b, y0, u0, v0); r = *pSrc++; g = *pSrc++; b = *pSrc++; *pAlpha++ = *pSrc++; RGB2YUV_601(r, g, b, y1, u1, v1); *pDst++ = y0; *pDst++ = (u0 + u1) >> 1; *pDst++ = y1; *pDst++ = (v0 + v1) >> 1; } if (src->width % 2) { r = *pSrc++; g = *pSrc++; b = *pSrc++; *pAlpha++ = *pSrc++; RGB2YUV_601(r, g, b, y0, u0, v0); *pDst++ = y0; *pDst++ = u0; } } } static void convert_rgb_to_yuv422(mlt_image src, mlt_image dst) { int y0, y1, u0, u1, v0, v1; int r, g, b; mlt_image_set_values(dst, NULL, mlt_image_yuv422, src->width, src->height); mlt_image_alloc_data(dst); for (int line = 0; line < src->height; line++) { uint8_t *pSrc = src->planes[0] + src->strides[0] * line; uint8_t *pDst = dst->planes[0] + dst->strides[0] * line; int j = src->width / 2 + 1; while (--j) { r = *pSrc++; g = *pSrc++; b = *pSrc++; RGB2YUV_601(r, g, b, y0, u0, v0); r = *pSrc++; g = *pSrc++; b = *pSrc++; RGB2YUV_601(r, g, b, y1, u1, v1); *pDst++ = y0; *pDst++ = (u0 + u1) >> 1; *pDst++ = y1; *pDst++ = (v0 + v1) >> 1; } if (src->width % 2) { r = *pSrc++; g = *pSrc++; b = *pSrc++; RGB2YUV_601(r, g, b, y0, u0, v0); *pDst++ = y0; *pDst++ = u0; } } } static void convert_yuv420p_to_yuv422(mlt_image src, mlt_image dst) { mlt_image_set_values(dst, NULL, mlt_image_yuv422, src->width, src->height); mlt_image_alloc_data(dst); for (int line = 0; line < src->height; line++) { uint8_t *pSrcY = src->planes[0] + src->strides[0] * line; uint8_t *pSrcU = src->planes[1] + src->strides[1] * line / 2; uint8_t *pSrcV = src->planes[2] + src->strides[2] * line / 2; uint8_t *pDst = dst->planes[0] + dst->strides[0] * line; int j = src->width / 2 + 1; while (--j) { *pDst++ = *pSrcY++; *pDst++ = *pSrcU++; *pDst++ = *pSrcY++; *pDst++ = *pSrcV++; } } } static void convert_yuv420p_to_rgb(mlt_image src, mlt_image dst) { int yy, uu, vv; int r, g, b; mlt_image_set_values(dst, NULL, mlt_image_rgb, src->width, src->height); mlt_image_alloc_data(dst); for (int line = 0; line < src->height; line++) { uint8_t *pSrcY = src->planes[0] + src->strides[0] * line; uint8_t *pSrcU = src->planes[1] + src->strides[1] * line / 2; uint8_t *pSrcV = src->planes[2] + src->strides[2] * line / 2; uint8_t *pDst = dst->planes[0] + dst->strides[0] * line; int total = src->width / 2 + 1; while (--total) { yy = *pSrcY++; uu = *pSrcU++; vv = *pSrcV++; YUV2RGB_601(yy, uu, vv, r, g, b); pDst[0] = r; pDst[1] = g; pDst[2] = b; yy = *pSrcY++; YUV2RGB_601(yy, uu, vv, r, g, b); pDst[3] = r; pDst[4] = g; pDst[5] = b; pDst += 6; } } } static void convert_yuv420p_to_rgba(mlt_image src, mlt_image dst) { int yy, uu, vv; int r, g, b; mlt_image_set_values(dst, NULL, mlt_image_rgba, src->width, src->height); mlt_image_alloc_data(dst); for (int line = 0; line < src->height; line++) { uint8_t *pSrcY = src->planes[0] + src->strides[0] * line; uint8_t *pSrcU = src->planes[1] + src->strides[1] * line / 2; uint8_t *pSrcV = src->planes[2] + src->strides[2] * line / 2; uint8_t *pSrcA = src->planes[3] + src->strides[3] * line; uint8_t *pDst = dst->planes[0] + dst->strides[0] * line; int total = src->width / 2 + 1; if (pSrcA) while (--total) { yy = *pSrcY++; uu = *pSrcU++; vv = *pSrcV++; YUV2RGB_601(yy, uu, vv, r, g, b); pDst[0] = r; pDst[1] = g; pDst[2] = b; pDst[3] = *pSrcA++; yy = *pSrcY++; YUV2RGB_601(yy, uu, vv, r, g, b); pDst[4] = r; pDst[5] = g; pDst[6] = b; pDst[7] = *pSrcA++; pDst += 8; } else while (--total) { yy = *pSrcY++; uu = *pSrcU++; vv = *pSrcV++; YUV2RGB_601(yy, uu, vv, r, g, b); pDst[0] = r; pDst[1] = g; pDst[2] = b; pDst[3] = 0xff; yy = *pSrcY++; YUV2RGB_601(yy, uu, vv, r, g, b); pDst[4] = r; pDst[5] = g; pDst[6] = b; pDst[7] = 0xff; pDst += 8; } } } static void convert_yuv422_to_yuv420p(mlt_image src, mlt_image dst) { int lines = src->height; int pixels = src->width; mlt_image_set_values(dst, NULL, mlt_image_yuv420p, src->width, src->height); mlt_image_alloc_data(dst); // Y for (int line = 0; line < lines; line++) { uint8_t *pSrc = src->planes[0] + src->strides[0] * line; uint8_t *pDst = dst->planes[0] + dst->strides[0] * line; for (int pixel = 0; pixel < pixels; pixel++) { *pDst++ = *pSrc; pSrc += 2; } } lines = src->height / 2; pixels = src->width / 2; // U for (int line = 0; line < lines; line++) { uint8_t *pSrc = src->planes[0] + src->strides[0] * line * 2 + 1; uint8_t *pDst = dst->planes[1] + dst->strides[1] * line; for (int pixel = 0; pixel < pixels; pixel++) { *pDst++ = *pSrc; pSrc += 4; } } // V for (int line = 0; line < lines; line++) { uint8_t *pSrc = src->planes[0] + src->strides[0] * line * 2 + 3; uint8_t *pDst = dst->planes[2] + dst->strides[2] * line; for (int pixel = 0; pixel < pixels; pixel++) { *pDst++ = *pSrc; pSrc += 4; } } } static void convert_rgb_to_rgba(mlt_image src, mlt_image dst) { mlt_image_set_values(dst, NULL, mlt_image_rgba, src->width, src->height); mlt_image_alloc_data(dst); for (int line = 0; line < src->height; line++) { uint8_t *pSrc = src->planes[0] + src->strides[0] * line; uint8_t *pAlpha = src->planes[3] + src->strides[3] * line; uint8_t *pDst = dst->planes[0] + dst->strides[0] * line; int total = src->width + 1; if (pAlpha) while (--total) { *pDst++ = pSrc[0]; *pDst++ = pSrc[1]; *pDst++ = pSrc[2]; *pDst++ = *pAlpha++; pSrc += 3; } else while (--total) { *pDst++ = pSrc[0]; *pDst++ = pSrc[1]; *pDst++ = pSrc[2]; *pDst++ = 0xff; pSrc += 3; } } } static void convert_rgba_to_rgb(mlt_image src, mlt_image dst) { mlt_image_set_values(dst, NULL, mlt_image_rgb, src->width, src->height); mlt_image_alloc_data(dst); mlt_image_alloc_alpha(dst); for (int line = 0; line < src->height; line++) { uint8_t *pSrc = src->planes[0] + src->strides[0] * line; uint8_t *pDst = dst->planes[0] + dst->strides[0] * line; uint8_t *pAlpha = dst->planes[3] + dst->strides[3] * line; int total = src->width + 1; while (--total) { *pDst++ = pSrc[0]; *pDst++ = pSrc[1]; *pDst++ = pSrc[2]; *pAlpha++ = pSrc[3]; pSrc += 4; } } } typedef void (*conversion_function)(mlt_image src, mlt_image dst); static conversion_function conversion_matrix[mlt_image_invalid - 1][mlt_image_invalid - 1] = { {NULL, convert_rgb_to_rgba, convert_rgb_to_yuv422, NULL, NULL, NULL, NULL}, {convert_rgba_to_rgb, NULL, convert_rgba_to_yuv422, NULL, NULL, NULL, NULL}, {convert_yuv422_to_rgb, convert_yuv422_to_rgba, NULL, convert_yuv422_to_yuv420p, NULL, NULL, NULL}, {convert_yuv420p_to_rgb, convert_yuv420p_to_rgba, convert_yuv420p_to_yuv422, NULL, NULL, NULL, NULL}, {NULL, NULL, NULL, NULL, NULL, NULL, NULL}, {NULL, NULL, NULL, NULL, NULL, NULL, NULL}, {NULL, NULL, NULL, NULL, NULL, NULL, NULL}, }; static int convert_image(mlt_frame frame, uint8_t **buffer, mlt_image_format *format, mlt_image_format requested_format) { int error = 0; mlt_properties properties = MLT_FRAME_PROPERTIES(frame); int width = mlt_properties_get_int(properties, "width"); int height = mlt_properties_get_int(properties, "height"); if (*format != requested_format) { conversion_function converter = conversion_matrix[*format - 1][requested_format - 1]; mlt_log_debug(NULL, "[filter imageconvert] %s -> %s @ %dx%d\n", mlt_image_format_name(*format), mlt_image_format_name(requested_format), width, height); if (converter) { struct mlt_image_s src; struct mlt_image_s dst; mlt_image_set_values(&src, *buffer, *format, width, height); if (requested_format == mlt_image_rgba && mlt_frame_get_alpha(frame)) { // imageconvert leaves the alpha buffer alone except in the case of rgba. // For rgba input, an alpha buffer will be created and added to the frame. // For rgba output, the alpha buffer will be copied to the rgba and the buffer is removed from the frame. src.planes[3] = mlt_frame_get_alpha(frame); src.strides[3] = src.width; } converter(&src, &dst); mlt_frame_set_image(frame, dst.data, 0, dst.release_data); if (requested_format == mlt_image_rgba) { // Clear the alpha buffer on the frame mlt_frame_set_alpha(frame, NULL, 0, NULL); } else if (dst.alpha) { mlt_frame_set_alpha(frame, dst.alpha, 0, dst.release_alpha); } *buffer = dst.data; *format = dst.format; } else { mlt_log_error(NULL, "imageconvert: no conversion from %s to %s\n", mlt_image_format_name(*format), mlt_image_format_name(requested_format)); error = 1; } } return error; } /** Filter processing. */ static mlt_frame filter_process(mlt_filter filter, mlt_frame frame) { if (!frame->convert_image) frame->convert_image = convert_image; return frame; } /** Constructor for the filter. */ mlt_filter filter_imageconvert_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = calloc(1, sizeof(struct mlt_filter_s)); if (mlt_filter_init(filter, filter) == 0) { filter->process = filter_process; } return filter; } mlt-7.22.0/src/modules/core/filter_imageconvert.yml000664 000000 000000 00000000763 14531534050 022326 0ustar00rootroot000000 000000 schema_version: 7.0 type: filter identifier: imageconvert title: Basic Image Converter version: 1 copyright: Meltytech, LLC license: LGPLv2.1 language: en tags: - Video description: Converts the colorspace and pixel format. notes: > This is not intended to be created directly. Rather, the loader producer loads it if it is available to set the convert_image function pointer on frames. This implementation is old and naive by assuming all YCbCr video is ITU-R BT.601 and all RGB is sRGB. mlt-7.22.0/src/modules/core/filter_luma.c000664 000000 000000 00000014156 14531534050 020223 0ustar00rootroot000000 000000 /* * filter_luma.c -- luma filter * Copyright (C) 2003-2014 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include #include #include /** Do it :-). */ static int filter_get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { int error = 0; mlt_filter filter = mlt_frame_pop_service(frame); mlt_properties properties = MLT_FILTER_PROPERTIES(filter); mlt_service_lock(MLT_FILTER_SERVICE(filter)); mlt_transition luma = mlt_properties_get_data(properties, "luma", NULL); mlt_frame b_frame = mlt_properties_get_data(properties, "frame", NULL); mlt_properties b_frame_props = b_frame ? MLT_FRAME_PROPERTIES(b_frame) : NULL; int out = mlt_properties_get_int(properties, "period"); int cycle = mlt_properties_get_int(properties, "cycle"); int duration = mlt_properties_get_int(properties, "duration"); mlt_position position = mlt_filter_get_position(filter, frame); out = out ? out + 1 : 25; if (cycle) out = cycle; if (duration < 1 || duration > out) duration = out; *format = mlt_image_yuv422; if (b_frame == NULL || mlt_properties_get_int(b_frame_props, "width") != *width || mlt_properties_get_int(b_frame_props, "height") != *height) { b_frame = mlt_frame_init(MLT_FILTER_SERVICE(filter)); mlt_properties_set_data(properties, "frame", b_frame, 0, (mlt_destructor) mlt_frame_close, NULL); } if (luma == NULL) { char *resource = mlt_properties_get(properties, "resource"); mlt_profile profile = mlt_service_profile(MLT_FILTER_SERVICE(filter)); luma = mlt_factory_transition(profile, "luma", resource); if (luma != NULL) { mlt_properties luma_properties = MLT_TRANSITION_PROPERTIES(luma); mlt_properties_set_int(luma_properties, "in", 0); mlt_properties_set_int(luma_properties, "out", duration - 1); mlt_properties_set_int(luma_properties, "reverse", 1); mlt_properties_set_data(properties, "luma", luma, 0, (mlt_destructor) mlt_transition_close, NULL); } } mlt_position modulo_pos = MLT_POSITION_MOD(position, out); mlt_log_debug(MLT_FILTER_SERVICE(filter), "pos " MLT_POSITION_FMT " mod period " MLT_POSITION_FMT "\n", position, modulo_pos); if (luma != NULL && (mlt_properties_get(properties, "blur") != NULL || (position >= duration && modulo_pos < duration - 1))) { mlt_properties luma_properties = MLT_TRANSITION_PROPERTIES(luma); mlt_properties_pass(luma_properties, properties, "luma."); int in = position / out * out + mlt_frame_get_position(frame) - position; mlt_properties_set_int(luma_properties, "in", in); mlt_properties_set_int(luma_properties, "out", in + duration - 1); mlt_transition_process(luma, frame, b_frame); } error = mlt_frame_get_image(frame, image, format, width, height, 1); // We only need a copy of the last frame in the cycle, but we could miss it // with realtime frame-dropping, so we copy the last several frames of the cycle. if (error == 0 && modulo_pos > out - duration) { mlt_properties a_props = MLT_FRAME_PROPERTIES(frame); int size = 0; uint8_t *src = mlt_properties_get_data(a_props, "image", &size); uint8_t *dst = mlt_pool_alloc(size); if (dst != NULL) { mlt_log_debug(MLT_FILTER_SERVICE(filter), "copying frame " MLT_POSITION_FMT "\n", modulo_pos); mlt_properties b_props = MLT_FRAME_PROPERTIES(b_frame); memcpy(dst, src, size); mlt_frame_set_image(b_frame, dst, size, mlt_pool_release); mlt_properties_set_int(b_props, "width", *width); mlt_properties_set_int(b_props, "height", *height); mlt_properties_set_int(b_props, "format", *format); } } mlt_service_unlock(MLT_FILTER_SERVICE(filter)); return error; } /** Filter processing. */ static mlt_frame filter_process(mlt_filter filter, mlt_frame frame) { // Push the filter on to the stack mlt_frame_push_service(frame, filter); // Push the get_image on to the stack mlt_frame_push_get_image(frame, filter_get_image); return frame; } /** Constructor for the filter. */ mlt_filter filter_luma_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = mlt_filter_new(); if (filter != NULL) { mlt_properties properties = MLT_FILTER_PROPERTIES(filter); filter->process = filter_process; if (arg != NULL) mlt_properties_set(properties, "resource", arg); } return filter; } mlt-7.22.0/src/modules/core/filter_luma.yml000664 000000 000000 00000002111 14531534050 020566 0ustar00rootroot000000 000000 schema_version: 7.0 type: filter identifier: luma title: Wipe version: 1 copyright: Meltytech, LLC creator: Charles Yates license: LGPLv2.1 language: en tags: - Video description: > Applies a luma transition between the current and next frames. Useful for transitions from a slideshow created using producer pixbuf. parameters: - identifier: resource argument: yes title: File type: string description: The luma map file to be used for the transition - identifier: cycle title: Period type: integer description: > The duration between iterations of the transition. For best results set this to a multiple of ttl used in pixbuf. mutable: yes default: 25 - identifier: duration title: Duration type: integer description: The length of the transition. mutable: yes default: 25 - identifier: luma.* title: Luma Properties description: > All properties beginning with "luma." are passed to the encapsulated luma transition. For example, luma.out controls the duration of the wipe. mutable: yes mlt-7.22.0/src/modules/core/filter_mask_apply.c000664 000000 000000 00000012637 14531534050 021427 0ustar00rootroot000000 000000 /* * filter_mask_apply.c -- composite atop a cloned frame made by mask_start * Copyright (C) 2018 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include static int dummy_get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { mlt_properties properties = MLT_FRAME_PROPERTIES(frame); *image = mlt_properties_get_data(properties, "image", NULL); *format = mlt_properties_get_int(properties, "format"); *width = mlt_properties_get_int(properties, "width"); *height = mlt_properties_get_int(properties, "height"); return 0; } static int get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { mlt_transition transition = mlt_frame_pop_service(frame); *format = mlt_frame_pop_service_int(frame); int error = mlt_frame_get_image(frame, image, format, width, height, writable); if (!error) { mlt_properties properties = MLT_FRAME_PROPERTIES(frame); mlt_frame clone = mlt_properties_get_data(properties, "mask frame", NULL); if (clone) { mlt_frame_push_get_image(frame, dummy_get_image); mlt_service_lock(MLT_TRANSITION_SERVICE(transition)); mlt_transition_process(transition, clone, frame); mlt_service_unlock(MLT_TRANSITION_SERVICE(transition)); error = mlt_frame_get_image(clone, image, format, width, height, writable); if (!error) { int size = mlt_image_format_size(*format, *width, *height, NULL); mlt_frame_set_image(frame, *image, size, NULL); } } } return error; } static mlt_frame process(mlt_filter filter, mlt_frame frame) { mlt_properties properties = MLT_FILTER_PROPERTIES(filter); mlt_transition transition = mlt_properties_get_data(properties, "instance", NULL); char *name = mlt_properties_get(MLT_FILTER_PROPERTIES(filter), "transition"); if (!name || !strcmp("", name)) return frame; // Create the transition if needed. if (!transition || !mlt_properties_get(MLT_FILTER_PROPERTIES(transition), "mlt_service") || strcmp(name, mlt_properties_get(MLT_FILTER_PROPERTIES(transition), "mlt_service"))) { mlt_profile profile = mlt_service_profile(MLT_FILTER_SERVICE(filter)); transition = mlt_factory_transition(profile, name, NULL); mlt_properties_set_data(MLT_FILTER_PROPERTIES(filter), "instance", transition, 0, (mlt_destructor) mlt_transition_close, NULL); } if (transition) { mlt_properties transition_props = MLT_TRANSITION_PROPERTIES(transition); int type = mlt_properties_get_int(transition_props, "_transition_type"); int hide = mlt_properties_get_int(MLT_FRAME_PROPERTIES(frame), "hide"); mlt_properties_pass_list(transition_props, properties, "in out"); mlt_properties_pass(transition_props, properties, "transition."); // Only if video transition on visible track. if ((type & 1) && !mlt_frame_is_test_card(frame) && !(hide & 1)) { mlt_frame_push_service_int(frame, mlt_image_format_id( mlt_properties_get(properties, "mlt_image_format"))); mlt_frame_push_service(frame, transition); mlt_frame_push_get_image(frame, get_image); } if (type == 0) mlt_properties_debug(transition_props, "unknown transition type", stderr); } else { mlt_properties_debug(properties, "mask_failed to create transition", stderr); } return frame; } mlt_filter filter_mask_apply_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = mlt_filter_new(); if (filter) { mlt_properties_set(MLT_FILTER_PROPERTIES(filter), "transition", arg ? arg : "frei0r.composition"); mlt_properties_set(MLT_FILTER_PROPERTIES(filter), "mlt_image_format", "rgba"); filter->process = process; } return filter; } mlt-7.22.0/src/modules/core/filter_mask_apply.yml000664 000000 000000 00000001773 14531534050 022005 0ustar00rootroot000000 000000 schema_version: 0.3 type: filter identifier: mask_apply title: Apply a filter mask version: 1 copyright: Meltytech, LLC creator: Dan Dennedy license: LGPLv2.1 language: en tags: - Video description: > This filter works in conjunction with the mask_start filter, which makes a snapshot of the frame. There can be other filters between the two, which are masked by the alpha channel via this filter's compositing transition. parameters: - identifier: transition title: Transition description: The name of a compositing or blending transition type: string argument: yes default: frei0r.composition mutable: yes - identifier: transition.* type: properties description: > Properties to set on the encapsulated transition - identifier: mlt_image_format title: MLT image format type: string description: Set to the same image format that the transition needs. values: mutable: yes default: rgba values: - yuv422 - rgb - rgba mlt-7.22.0/src/modules/core/filter_mask_start.c000664 000000 000000 00000007314 14531534050 021433 0ustar00rootroot000000 000000 /* * filter_mask_start.c -- clone a frame before invoking a filter * Copyright (C) 2018-2022 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include static int get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { mlt_properties properties = MLT_FRAME_PROPERTIES(frame); int error = mlt_frame_get_image(frame, image, format, width, height, writable); if (!error) { mlt_frame clone = mlt_frame_clone(frame, 1); clone->convert_audio = frame->convert_audio; clone->convert_image = frame->convert_image; mlt_properties_set_data(properties, "mask frame", clone, 0, (mlt_destructor) mlt_frame_close, NULL); } return error; } static mlt_frame process(mlt_filter filter, mlt_frame frame) { mlt_properties properties = MLT_FILTER_PROPERTIES(filter); mlt_filter instance = mlt_properties_get_data(properties, "instance", NULL); const char *name = mlt_properties_get(properties, "filter"); if (!name || !strcmp("", name)) return frame; // Create the filter if needed. if (!instance || !mlt_properties_get(MLT_FILTER_PROPERTIES(instance), "mlt_service") || strcmp(name, mlt_properties_get(MLT_FILTER_PROPERTIES(instance), "mlt_service"))) { mlt_profile profile = mlt_service_profile(MLT_FILTER_SERVICE(filter)); instance = mlt_factory_filter(profile, name, NULL); mlt_properties_set_data(properties, "instance", instance, 0, (mlt_destructor) mlt_filter_close, NULL); } if (instance) { mlt_properties instance_props = MLT_FILTER_PROPERTIES(instance); mlt_properties_pass_list(instance_props, properties, "in out"); mlt_properties_pass(instance_props, properties, "filter."); mlt_properties_clear(properties, "filter.producer.refresh"); mlt_frame_push_get_image(frame, get_image); return mlt_filter_process(instance, frame); } else { mlt_properties_debug(properties, "failed to create filter", stderr); return frame; } } mlt_filter filter_mask_start_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = mlt_filter_new(); if (filter) { mlt_properties_set(MLT_FILTER_PROPERTIES(filter), "filter", arg ? arg : "frei0r.alphaspot"); filter->process = process; } return filter; } mlt-7.22.0/src/modules/core/filter_mask_start.yml000664 000000 000000 00000001656 14531534050 022015 0ustar00rootroot000000 000000 schema_version: 0.3 type: filter identifier: mask_start title: Setup a filter mask version: 4 copyright: Meltytech, LLC creator: Dan Dennedy license: LGPLv2.1 language: en tags: - Video description: > This filter works in conjunction with the mask_apply filter to make filter masking easier. This filter makes a snapshop of the frame before applying a filter, typically one to affect the alpha channel defining the mask. Then, the mask_apply filter uses a transition to composite the current frame's image over the snapshot. The typical use case is to add filters in the following sequence: mask_start, zero or more filters, mask_apply. parameters: - identifier: filter title: Filter description: The name of a filter type: string argument: yes default: frei0r.alphaspot mutable: yes - identifier: filter.* type: properties description: > Properties to set on the encapsulated filter mlt-7.22.0/src/modules/core/filter_mirror.c000664 000000 000000 00000030230 14531534050 020566 0ustar00rootroot000000 000000 /* * filter_mirror.c -- mirror filter * Copyright (C) 2003-2021 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include typedef struct { mlt_image image; char *mirror; int reverse; } slice_desc; static int do_slice_proc(int id, int index, int jobs, void *data) { (void) id; // unused slice_desc *desc = (slice_desc *) data; int slice_line_start, slice_height = mlt_slices_size_slice(jobs, index, desc->image->height, &slice_line_start); int slice_line_end = slice_line_start + slice_height; int slice_line_start_half, slice_height_half = mlt_slices_size_slice(jobs, index, desc->image->height / 2, &slice_line_start_half); int slice_line_end_half = slice_line_start_half + slice_height_half; int uneven_w = (desc->image->width % 2) * 2; int i; if (!strcmp(desc->mirror, "horizontal")) { for (i = slice_line_start; i < slice_line_end; i++) { uint8_t *p = desc->image->planes[0] + desc->image->strides[0] * i; uint8_t *q = p + desc->image->width * 2; if (!desc->reverse) { while (p < q) { *p++ = *(q - 2); *p++ = *(q - 3 - uneven_w); *p++ = *(q - 4); *p++ = *(q - 1 - uneven_w); q -= 4; } } else { while (p < q) { *(q - 2) = *p++; *(q - 3 - uneven_w) = *p++; *(q - 4) = *p++; *(q - 1 - uneven_w) = *p++; q -= 4; } } } if (desc->image->planes[3]) { for (i = slice_line_start; i < slice_line_end; i++) { uint8_t *a = desc->image->planes[3] + desc->image->strides[3] * i; uint8_t *b = a + desc->image->width - 1; if (!desc->reverse) { while (a < b) { *a++ = *b--; *a++ = *b--; } } else { while (a < b) { *b-- = *a++; *b-- = *a++; } } } } } else if (!strcmp(desc->mirror, "vertical")) { for (i = slice_line_start_half; i < slice_line_end_half; i++) { uint16_t *p = (uint16_t *) (desc->image->planes[0] + (desc->image->strides[0] * i)); uint16_t *q = (uint16_t *) (desc->image->planes[0] + (desc->image->strides[0] * (desc->image->height - i - 1))); int j = desc->image->width; if (!desc->reverse) { while (j--) { *p++ = *q++; } } else { while (j--) { *q++ = *p++; } } } if (desc->image->planes[3]) { for (i = slice_line_start_half; i < slice_line_end_half; i++) { int j = desc->image->width; uint8_t *a = desc->image->planes[3] + (desc->image->strides[3] * i); uint8_t *b = desc->image->planes[3] + (desc->image->strides[3] * (desc->image->height - i - 1)); if (!desc->reverse) while (j--) *a++ = *b++; else while (j--) *b++ = *a++; } } } else if (!strcmp(desc->mirror, "diagonal")) { for (i = slice_line_start; i < slice_line_end; i++) { uint8_t *p = desc->image->planes[0] + (desc->image->strides[0] * i); uint8_t *q = desc->image->planes[0] + (desc->image->strides[0] * (desc->image->height - i - 1)); int j = ((desc->image->width * (desc->image->height - i)) / desc->image->height) / 2; if (!desc->reverse) { while (j--) { *p++ = *(q - 2); *p++ = *(q - 3 - uneven_w); *p++ = *(q - 4); *p++ = *(q - 1 - uneven_w); q -= 4; } } else { while (j--) { *(q - 2) = *p++; *(q - 3 - uneven_w) = *p++; *(q - 4) = *p++; *(q - 1 - uneven_w) = *p++; q -= 4; } } } if (desc->image->planes[3]) { int i; for (i = slice_line_start; i < slice_line_end; i++) { int j = (desc->image->width * (desc->image->height - i)) / desc->image->height; uint8_t *a = desc->image->planes[3] + (desc->image->strides[3] * i); uint8_t *b = desc->image->planes[3] + (desc->image->strides[3] * (desc->image->height - i - 1)); if (!desc->reverse) while (j--) *a++ = *b--; else while (j--) *b-- = *a++; } } } else if (!strcmp(desc->mirror, "xdiagonal")) { for (i = slice_line_start; i < slice_line_end; i++) { uint8_t *p = desc->image->planes[0] + (desc->image->strides[0] * (i + 1)); uint8_t *q = desc->image->planes[0] + (desc->image->strides[0] * (desc->image->height - i)); int j = ((desc->image->width * (desc->image->height - i)) / desc->image->height) / 2; if (!desc->reverse) { while (j--) { *q++ = *(p - 2); *q++ = *(p - 3 - uneven_w); *q++ = *(p - 4); *q++ = *(p - 1 - uneven_w); p -= 4; } } else { while (j--) { *(p - 2) = *q++; *(p - 3 - uneven_w) = *q++; *(p - 4) = *q++; *(p - 1 - uneven_w) = *q++; p -= 4; } } } if (desc->image->planes[3]) { int i; for (i = slice_line_start; i < slice_line_end; i++) { int j = ((desc->image->width * (desc->image->height - i)) / desc->image->height); uint8_t *a = desc->image->planes[3] + (desc->image->strides[3] * i) + desc->image->width - 1; uint8_t *b = desc->image->planes[3] + (desc->image->strides[3] * (desc->image->height - i - 1)); if (!desc->reverse) while (j--) *b++ = *a--; else while (j--) *a-- = *b++; } } } else if (!strcmp(desc->mirror, "flip")) { uint8_t t[4]; for (i = slice_line_start; i < slice_line_end; i++) { uint8_t *p = desc->image->planes[0] + (desc->image->strides[0] * i); uint8_t *q = p + desc->image->width * 2; while (p < q) { t[0] = p[0]; t[1] = p[1 + uneven_w]; t[2] = p[2]; t[3] = p[3 + uneven_w]; *p++ = *(q - 2); *p++ = *(q - 3 - uneven_w); *p++ = *(q - 4); *p++ = *(q - 1 - uneven_w); *(--q) = t[3]; *(--q) = t[0]; *(--q) = t[1]; *(--q) = t[2]; } } if (desc->image->planes[3]) { uint8_t c; for (i = slice_line_start; i < slice_line_end; i++) { uint8_t *a = desc->image->planes[3] + (desc->image->strides[3] * i); uint8_t *b = a + desc->image->width - 1; while (a < b) { c = *a; *a++ = *b; *b-- = c; } } } } else if (!strcmp(desc->mirror, "flop")) { uint16_t t; for (i = slice_line_start_half; i < slice_line_end_half; i++) { uint16_t *p = (uint16_t *) (desc->image->planes[0] + (desc->image->strides[0] * i)); uint16_t *q = (uint16_t *) (desc->image->planes[0] + (desc->image->strides[0] * (desc->image->height - i - 1))); int j = desc->image->width; while (j--) { t = *p; *p++ = *q; *q++ = t; } } if (desc->image->planes[3]) { uint8_t c; for (i = slice_line_start_half; i < slice_line_end_half; i++) { uint8_t *a = desc->image->planes[3] + (desc->image->strides[3] * i); uint8_t *b = desc->image->planes[3] + (desc->image->strides[3] * (desc->image->height - i - 1)); while (a < b) { c = *a; *a++ = *b; *b-- = c; } } } } return 0; } static int filter_get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { // Pop the mirror filter from the stack mlt_filter filter = mlt_frame_pop_service(frame); // Get the image *format = mlt_image_yuv422; int error = mlt_frame_get_image(frame, image, format, width, height, 1); // If we have an image of the right colour space if (error == 0 && *format == mlt_image_yuv422) { mlt_properties properties = MLT_FILTER_PROPERTIES(filter); slice_desc desc; struct mlt_image_s img; mlt_image_set_values(&img, *image, *format, *width, *height); if (mlt_frame_get_alpha(frame)) { img.planes[3] = mlt_frame_get_alpha(frame); img.strides[3] = img.width; } desc.image = &img; desc.mirror = mlt_properties_get(properties, "mirror"); desc.reverse = mlt_properties_get_int(properties, "reverse"); mlt_slices_run_normal(0, do_slice_proc, &desc); } // Return the error return error; } /** Filter processing. */ static mlt_frame filter_process(mlt_filter filter, mlt_frame frame) { // Push the service on to the stack mlt_frame_push_service(frame, filter); // Push the filter method on to the stack mlt_frame_push_service(frame, filter_get_image); return frame; } /** Constructor for the filter. */ mlt_filter filter_mirror_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { // Construct a new filter mlt_filter filter = mlt_filter_new(); // If we have a filter, initialise it if (filter != NULL) { // Get the properties mlt_properties properties = MLT_FILTER_PROPERTIES(filter); // Set the default mirror type mlt_properties_set_or_default(properties, "mirror", arg, "horizontal"); // Assign the process method filter->process = filter_process; } // Return the filter return filter; } mlt-7.22.0/src/modules/core/filter_mirror.yml000664 000000 000000 00000001202 14531534050 021142 0ustar00rootroot000000 000000 schema_version: 7.0 type: filter identifier: mirror title: Mirror version: 1 copyright: Meltytech, LLC creator: Charles Yates license: LGPLv2.1 language: en tags: - Video description: > Provides various mirror and image reversing effects. parameters: - identifier: mirror argument: yes title: Mirror Type type: string description: Choose the type of mirror operation. values: - horizontal - vertical - diagonal - xdiagonal - flip - flop - identifier: reverse title: Reverse type: integer mutable: no default: 0 minimum: 0 maximum: 1 widget: checkbox mlt-7.22.0/src/modules/core/filter_mono.c000664 000000 000000 00000013037 14531534050 020232 0ustar00rootroot000000 000000 /* * filter_mono.c -- mix all channels to a mono signal across n channels * Copyright (C) 2003-2018 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include /** Get the audio. */ static int filter_get_audio(mlt_frame frame, void **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples) { // Get the properties of the a frame mlt_properties properties = MLT_FRAME_PROPERTIES(frame); int channels_out = mlt_properties_get_int(properties, "mono.channels"); int i, j, size; // Get the producer's audio mlt_frame_get_audio(frame, buffer, format, frequency, channels, samples); if (channels_out == -1) channels_out = *channels; size = mlt_audio_format_size(*format, *samples, channels_out); switch (*format) { case mlt_audio_u8: { uint8_t *new_buffer = mlt_pool_alloc(size); for (i = 0; i < *samples; i++) { uint8_t mixdown = 0; for (j = 0; j < *channels; j++) mixdown += ((uint8_t *) *buffer)[(i * *channels) + j]; for (j = 0; j < channels_out; j++) new_buffer[(i * channels_out) + j] = mixdown; } *buffer = new_buffer; break; } case mlt_audio_s16: { int16_t *new_buffer = mlt_pool_alloc(size); for (i = 0; i < *samples; i++) { int16_t mixdown = 0; for (j = 0; j < *channels; j++) mixdown += ((int16_t *) *buffer)[(i * *channels) + j]; for (j = 0; j < channels_out; j++) new_buffer[(i * channels_out) + j] = mixdown; } *buffer = new_buffer; break; } case mlt_audio_s32le: { int32_t *new_buffer = mlt_pool_alloc(size); for (i = 0; i < *samples; i++) { int32_t mixdown = 0; for (j = 0; j < *channels; j++) mixdown += ((int32_t *) *buffer)[(i * *channels) + j]; for (j = 0; j < channels_out; j++) new_buffer[(i * channels_out) + j] = mixdown; } *buffer = new_buffer; break; } case mlt_audio_f32le: { float *new_buffer = mlt_pool_alloc(size); for (i = 0; i < *samples; i++) { float mixdown = 0; for (j = 0; j < *channels; j++) mixdown += ((float *) *buffer)[(i * *channels) + j]; for (j = 0; j < channels_out; j++) new_buffer[(i * channels_out) + j] = mixdown; } *buffer = new_buffer; break; } case mlt_audio_s32: { int32_t *new_buffer = mlt_pool_alloc(size); for (i = 0; i < *samples; i++) { int32_t mixdown = 0; for (j = 0; j < *channels; j++) mixdown += ((int32_t *) *buffer)[(j * *channels) + i]; for (j = 0; j < channels_out; j++) new_buffer[(j * *samples) + i] = mixdown; } *buffer = new_buffer; break; } case mlt_audio_float: { float *new_buffer = mlt_pool_alloc(size); for (i = 0; i < *samples; i++) { float mixdown = 0; for (j = 0; j < *channels; j++) mixdown += ((float *) *buffer)[(j * *channels) + i]; for (j = 0; j < channels_out; j++) new_buffer[(j * *samples) + i] = mixdown; } *buffer = new_buffer; break; } default: mlt_log_error(NULL, "[filter mono] Invalid audio format\n"); break; } if (size > *samples * channels_out) { mlt_frame_set_audio(frame, *buffer, *format, size, mlt_pool_release); *channels = channels_out; } return 0; } /** Filter processing. */ static mlt_frame filter_process(mlt_filter filter, mlt_frame frame) { mlt_properties properties = MLT_FILTER_PROPERTIES(filter); mlt_properties frame_props = MLT_FRAME_PROPERTIES(frame); // Propagate the parameters mlt_properties_set_int(frame_props, "mono.channels", mlt_properties_get_int(properties, "channels")); // Override the get_audio method mlt_frame_push_audio(frame, filter_get_audio); return frame; } /** Constructor for the filter. */ mlt_filter filter_mono_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = mlt_filter_new(); if (filter != NULL) { filter->process = filter_process; if (arg != NULL) mlt_properties_set_int(MLT_FILTER_PROPERTIES(filter), "channels", atoi(arg)); else mlt_properties_set_int(MLT_FILTER_PROPERTIES(filter), "channels", -1); } return filter; } mlt-7.22.0/src/modules/core/filter_mono.yml000664 000000 000000 00000000770 14531534050 020611 0ustar00rootroot000000 000000 schema_version: 7.0 type: filter identifier: mono title: Mixdown version: 1 copyright: Meltytech, LLC creator: Dan Dennedy license: LGPLv2.1 language: en tags: - Audio description: > Mix all channels of audio into a mono signal and output it as N channels. parameters: - identifier: channels argument: yes title: channels type: integer description: > Set the number of output channels. The default is automatic based on consumer request. required: no minimum: 1 mlt-7.22.0/src/modules/core/filter_obscure.c000664 000000 000000 00000020267 14531534050 020727 0ustar00rootroot000000 000000 /* * filter_obscure.c -- obscure filter * Copyright (C) 2003-2014 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include /** Geometry struct. */ struct geometry_s { int nw; int nh; float x; float y; float w; float h; int mask_w; int mask_h; }; /** Parse a value from a geometry string. */ static inline float parse_value(char **ptr, int normalization, char delim, float defaults) { float value = defaults; if (*ptr != NULL && **ptr != '\0') { char *end = NULL; value = strtod(*ptr, &end); if (end != NULL) { if (*end == '%') value = (value / 100.0) * normalization; while (*end == delim || *end == '%' || (delim == ',' && *end == '/')) end++; } *ptr = end; } return value; } /** Parse a geometry property string. */ static void geometry_parse( struct geometry_s *geometry, struct geometry_s *defaults, char *property, int nw, int nh) { // Assign normalized width and height geometry->nw = nw; geometry->nh = nh; // Assign from defaults if available if (defaults != NULL) { geometry->x = defaults->x; geometry->y = defaults->y; geometry->w = defaults->w; geometry->h = defaults->h; geometry->mask_w = defaults->mask_w; geometry->mask_h = defaults->mask_h; } else { geometry->x = 0; geometry->y = 0; geometry->w = nw; geometry->h = nh; geometry->mask_w = 20; geometry->mask_h = 20; } // Parse the geometry string if (property != NULL) { char *ptr = property; geometry->x = parse_value(&ptr, nw, ',', geometry->x); geometry->y = parse_value(&ptr, nh, ':', geometry->y); geometry->w = parse_value(&ptr, nw, 'x', geometry->w); geometry->h = parse_value(&ptr, nh, ':', geometry->h); geometry->mask_w = parse_value(&ptr, nw, 'x', geometry->mask_w); geometry->mask_h = parse_value(&ptr, nh, ' ', geometry->mask_h); } } /** A Timism but not as clean ;-). */ static float lerp(float value, float lower, float upper) { if (value < lower) return lower; else if (upper > lower && value > upper) return upper; return value; } /** Calculate real geometry. */ static void geometry_calculate(struct geometry_s *output, struct geometry_s *in, struct geometry_s *out, float position, int ow, int oh) { // Calculate this frames geometry output->x = lerp((in->x + (out->x - in->x) * position) / (float) out->nw * ow, 0, ow); output->y = lerp((in->y + (out->y - in->y) * position) / (float) out->nh * oh, 0, oh); output->w = lerp((in->w + (out->w - in->w) * position) / (float) out->nw * ow, 0, ow - output->x); output->h = lerp((in->h + (out->h - in->h) * position) / (float) out->nh * oh, 0, oh - output->y); output->mask_w = lerp(in->mask_w + (out->mask_w - in->mask_w) * position, 1, -1); output->mask_h = lerp(in->mask_h + (out->mask_h - in->mask_h) * position, 1, -1); } /** The averaging function... */ static inline void obscure_average(uint8_t *start, int width, int height, int stride) { register int y; register int x; register int Y = (*start + *(start + 2)) / 2; register int U = *(start + 1); register int V = *(start + 3); register uint8_t *p; register int components = width >> 1; y = height; while (y--) { p = start; x = components; while (x--) { Y = (Y + *p++) >> 1; U = (U + *p++) >> 1; Y = (Y + *p++) >> 1; V = (V + *p++) >> 1; } start += stride; } start -= height * stride; y = height; while (y--) { p = start; x = components; while (x--) { *p++ = Y; *p++ = U; *p++ = Y; *p++ = V; } start += stride; } } /** The obscurer rendering function... */ static void obscure_render(uint8_t *image, int width, int height, struct geometry_s result) { int area_x = result.x; int area_y = result.y; int area_w = result.w; int area_h = result.h; int mw = result.mask_w; int mh = result.mask_h; int w; int h; int aw; int ah; uint8_t *p = image + area_y * width * 2 + area_x * 2; for (w = 0; w < area_w; w += mw) { for (h = 0; h < area_h; h += mh) { aw = w + mw > area_w ? mw - (w + mw - area_w) : mw; ah = h + mh > area_h ? mh - (h + mh - area_h) : mh; if (aw > 1 && ah > 1) obscure_average(p + h * (width << 1) + (w << 1), aw, ah, width << 1); } } } /** Do it :-). */ static int filter_get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { // Pop the top of stack now mlt_filter filter = mlt_frame_pop_service(frame); // Get the image from the frame *format = mlt_image_yuv422; int error = mlt_frame_get_image(frame, image, format, width, height, 1); // Get the image from the frame if (error == 0) { if (filter != NULL) { // Get the filter properties mlt_properties properties = MLT_FILTER_PROPERTIES(filter); mlt_profile profile = mlt_service_profile(MLT_FILTER_SERVICE(filter)); // Structures for geometry struct geometry_s result; struct geometry_s start; struct geometry_s end; // Retrieve the position float position = mlt_filter_get_progress(filter, frame); // Now parse the geometries geometry_parse(&start, NULL, mlt_properties_get(properties, "start"), profile->width, profile->height); geometry_parse(&end, &start, mlt_properties_get(properties, "end"), profile->width, profile->height); // Do the calculation geometry_calculate(&result, &start, &end, position, *width, *height); // Now actually render it obscure_render(*image, *width, *height, result); } } return error; } /** Filter processing. */ static mlt_frame filter_process(mlt_filter filter, mlt_frame frame) { // Push this on to the service stack mlt_frame_push_service(frame, filter); // Push the get image call mlt_frame_push_get_image(frame, filter_get_image); return frame; } /** Constructor for the filter. */ mlt_filter filter_obscure_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = mlt_filter_new(); if (filter != NULL) { mlt_properties properties = MLT_FILTER_PROPERTIES(filter); filter->process = filter_process; mlt_properties_set(properties, "start", arg != NULL ? arg : "0%/0%:100%x100%"); mlt_properties_set(properties, "end", ""); } return filter; } mlt-7.22.0/src/modules/core/filter_obscure.yml000664 000000 000000 00000001213 14531534050 021274 0ustar00rootroot000000 000000 schema_version: 7.0 type: filter identifier: obscure title: Obscure version: 1 copyright: Meltytech, LLC creator: Charles Yates license: LGPLv2.1 language: en tags: - Video description: > Obscuring filter. parameters: - identifier: start argument: yes title: Start type: string description: > The starting rectangle is given in the format X/Y:WxH[:PWxPY] where PWxPY is the size of the averaging region in pixels. - identifier: end title: End type: string description: > The ending rectangle is given in the format X/Y:WxH[:PWxPY] where PWxPY is the size of the averaging region in pixels. mlt-7.22.0/src/modules/core/filter_panner.c000664 000000 000000 00000030141 14531534050 020540 0ustar00rootroot000000 000000 /* * filter_panner.c --pan/balance audio channels * Copyright (C) 2010-2017 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include /** Get the audio. */ static int filter_get_audio(mlt_frame frame, void **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples) { mlt_properties properties = mlt_frame_pop_audio(frame); mlt_filter filter = mlt_frame_pop_audio(frame); mlt_properties filter_props = MLT_FILTER_PROPERTIES(filter); mlt_properties frame_props = MLT_FRAME_PROPERTIES(frame); // We can only mix interleaved 32-bit float. *format = mlt_audio_f32le; mlt_frame_get_audio(frame, (void **) buffer, format, frequency, channels, samples); // Apply silence int silent = mlt_properties_get_int(frame_props, "silent_audio"); mlt_properties_set_int(frame_props, "silent_audio", 0); if (silent) memset(*buffer, 0, *samples * *channels * sizeof(float)); int src_size = 0; float *src = mlt_properties_get_data(filter_props, "scratch_buffer", &src_size); float *dest = *buffer; double v; // sample accumulator int i, out, in; double factors[6][6]; // mixing weights [in][out] double mix_start = 0.5, mix_end = 0.5; if (mlt_properties_get(properties, "previous_mix") != NULL) mix_start = mlt_properties_get_double(properties, "previous_mix"); if (mlt_properties_get(properties, "mix") != NULL) mix_end = mlt_properties_get_double(properties, "mix"); double weight = mix_start; double weight_step = (mix_end - mix_start) / *samples; int active_channel = mlt_properties_get_int(properties, "channel"); int gang = mlt_properties_get_int(properties, "gang") ? 2 : 1; // Setup or resize a scratch buffer if (!src || src_size < *samples * *channels * sizeof(*src)) { // We allocate 4 more samples than we need to deal with jitter in the sample count per frame. src_size = (*samples + 4) * *channels * sizeof(*src); src = mlt_pool_alloc(src_size); if (!src) return 0; mlt_properties_set_data(filter_props, "scratch_buffer", src, src_size, mlt_pool_release, NULL); } // We must use a pristine copy as the source memcpy(src, *buffer, *samples * *channels * sizeof(*src)); // Initialize the mix factors for (i = 0; i < 6; i++) for (out = 0; out < 6; out++) factors[i][out] = 0.0; for (i = 0; i < *samples; i++) { // Recompute the mix factors switch (active_channel) { case -1: // Front L/R balance case -2: // Rear L/R balance { // Gang front/rear balance if requested int g, active = active_channel; for (g = 0; g < gang; g++, active--) { int left = active == -1 ? 0 : 2; int right = left + 1; if (weight < 0.0) { factors[left][left] = 1.0; factors[right][right] = weight + 1.0 < 0.0 ? 0.0 : weight + 1.0; } else { factors[left][left] = 1.0 - weight < 0.0 ? 0.0 : 1.0 - weight; factors[right][right] = 1.0; } } break; } case -3: // Left fade case -4: // right fade { // Gang left/right fade if requested int g, active = active_channel; for (g = 0; g < gang; g++, active--) { int front = active == -3 ? 0 : 1; int rear = front + 2; if (weight < 0.0) { factors[front][front] = 1.0; factors[rear][rear] = weight + 1.0 < 0.0 ? 0.0 : weight + 1.0; } else { factors[front][front] = 1.0 - weight < 0.0 ? 0.0 : 1.0 - weight; factors[rear][rear] = 1.0; } } break; } case 0: // left case 2: { int left = active_channel; int right = left + 1; factors[right][right] = 1.0; if (weight < 0.0) // output left toward left { factors[left][left] = 0.5 - weight * 0.5; factors[left][right] = (1.0 + weight) * 0.5; } else // output left toward right { factors[left][left] = (1.0 - weight) * 0.5; factors[left][right] = 0.5 + weight * 0.5; } break; } case 1: // right case 3: { int right = active_channel; int left = right - 1; factors[left][left] = 1.0; if (weight < 0.0) // output right toward left { factors[right][left] = 0.5 - weight * 0.5; factors[right][right] = (1.0 + weight) * 0.5; } else // output right toward right { factors[right][left] = (1.0 - weight) * 0.5; factors[right][right] = 0.5 + weight * 0.5; } break; } } // Do the mixing for (out = 0; out < *channels && out < 6; out++) { v = 0; for (in = 0; in < *channels && in < 6; in++) v += factors[in][out] * src[i * *channels + in]; dest[i * *channels + out] = v; } weight += weight_step; } return 0; } /** Pan filter processing. */ static mlt_frame filter_process(mlt_filter filter, mlt_frame frame) { mlt_properties properties = MLT_FILTER_PROPERTIES(filter); mlt_properties frame_props = MLT_FRAME_PROPERTIES(frame); mlt_properties instance_props = mlt_properties_new(); // Only if mix is specified, otherwise a producer may set the mix if (mlt_properties_get(properties, "start") != NULL) { // Determine the time position of this frame in the filter duration mlt_properties props = mlt_properties_get_data(frame_props, "_producer", NULL); int always_active = mlt_properties_get_int(properties, "always_active"); mlt_position in = !always_active ? mlt_filter_get_in(filter) : mlt_properties_get_int(props, "in"); mlt_position out = !always_active ? mlt_filter_get_out(filter) : mlt_properties_get_int(props, "out"); int length = mlt_properties_get_int(properties, "length"); mlt_position time = !always_active ? mlt_frame_get_position(frame) : mlt_properties_get_int(props, "_frame"); double mix = (double) (time - in) / (double) (out - in + 1); if (length == 0) { // If there is an end mix level adjust mix to the range if (mlt_properties_get(properties, "end") != NULL) { double start = mlt_properties_get_double(properties, "start"); double end = mlt_properties_get_double(properties, "end"); mix = start + (end - start) * mix; } // Use constant mix level if only start else if (mlt_properties_get(properties, "start") != NULL) { mix = mlt_properties_get_double(properties, "start"); } // Use animated property "split" to get mix level if property is set char *split_property = mlt_properties_get(properties, "split"); if (split_property) { mlt_position pos = mlt_filter_get_position(filter, frame); mlt_position len = mlt_filter_get_length2(filter, frame); mix = mlt_properties_anim_get_double(properties, "split", pos, len); } // Convert it from [0, 1] to [-1, 1] mix = mix * 2.0 - 1.0; // Finally, set the mix property on the frame mlt_properties_set_double(instance_props, "mix", mix); // Initialise filter previous mix value to prevent an inadvertent jump from 0 mlt_position last_position = mlt_properties_get_position(properties, "_last_position"); mlt_position current_position = mlt_frame_get_position(frame); mlt_properties_set_position(properties, "_last_position", current_position); if (mlt_properties_get(properties, "_previous_mix") == NULL || current_position != last_position + 1) mlt_properties_set_double(properties, "_previous_mix", mix); // Tell the frame what the previous mix level was mlt_properties_set_double(instance_props, "previous_mix", mlt_properties_get_double(properties, "_previous_mix")); // Save the current mix level for the next iteration mlt_properties_set_double(properties, "_previous_mix", mix); } else { double level = mlt_properties_get_double(properties, "start"); double mix_start = level; double mix_end = mix_start; double mix_increment = 1.0 / length; if (time - in < length) { mix_start *= (double) (time - in) / length; mix_end = mix_start + mix_increment; } else if (time > out - length) { mix_end = mix_start * ((double) (out - time - in) / length); mix_start = mix_end - mix_increment; } mix_start = mix_start < 0 ? 0 : mix_start > level ? level : mix_start; mix_end = mix_end < 0 ? 0 : mix_end > level ? level : mix_end; mlt_properties_set_double(instance_props, "previous_mix", mix_start); mlt_properties_set_double(instance_props, "mix", mix_end); } mlt_properties_set_int(instance_props, "channel", mlt_properties_get_int(properties, "channel")); mlt_properties_set_int(instance_props, "gang", mlt_properties_get_int(properties, "gang")); } char label[64]; snprintf(label, sizeof(label), "panner %s", mlt_properties_get(properties, "_unique_id")); mlt_properties_set_data(frame_props, label, instance_props, 0, (mlt_destructor) mlt_properties_close, NULL); // Override the get_audio method mlt_frame_push_audio(frame, filter); mlt_frame_push_audio(frame, instance_props); mlt_frame_push_audio(frame, filter_get_audio); return frame; } /** Constructor for the filter. */ mlt_filter filter_panner_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = calloc(1, sizeof(struct mlt_filter_s)); if (filter != NULL && mlt_filter_init(filter, NULL) == 0) { filter->process = filter_process; if (arg != NULL) mlt_properties_set_double(MLT_FILTER_PROPERTIES(filter), "start", atof(arg)); mlt_properties_set_int(MLT_FILTER_PROPERTIES(filter), "channel", -1); mlt_properties_set(MLT_FILTER_PROPERTIES(filter), "split", NULL); } return filter; } mlt-7.22.0/src/modules/core/filter_panner.yml000664 000000 000000 00000004146 14531534050 021125 0ustar00rootroot000000 000000 schema_version: 7.0 type: filter identifier: panner title: Audio Pan version: 2 copyright: Meltytech, LLC creator: Dan Dennedy license: LGPLv2.1 language: en tags: - Audio description: Pan an audio channel, adjust balance, or adjust fade. notes: Only handles up to 6 channels. Needs more work balance for surround and other channel layouts. parameters: - identifier: start (*deprecated*) title: Start description: > The position of the audio relative to its neighbor channel. For example, when channel is set to 0 for left, then start 0 is full left, 0.5 is center, and 1.0 is full right. If value for property "split" is set value of this property is discarded. type: float argument: yes mutable: yes minimum: 0 maximum: 1 - identifier: end (*deprecated*) title: End description: > The ending value of the audio position. It will be interpolated from start to end over the in-out range. If value for property "split" is set value of this property is discarded. type: float mutable: yes minimum: 0 maximum: 1 - identifier: channel title: Channel type: integer description: > For stereo: 0 for front left, 1 for front right, -1 for front balance. For quad: 2 for back left, 3 for back right, -2 for rear balance, -3 for left fade, -4 for right fade. minimum: -4 maximum: 5 default: -1 - identifier: gang title: Gang description: > Whether to gang together the front and back when using balance or to gang together the left and right when using fade. type: integer minimum: 0 maximum: 1 default: 0 widget: checkbox - identifier: split title: Split description: > The animated position of the audio relative to its neighbor channel. For example, when channel is set to 0 for left, then start 0 is full left, 0.5 is center, and 1.0 is full right. If this value is set, values for properties "start" and "end" are discarded. type: float mutable: yes animation: yes minimum: 0 maximum: 1 mlt-7.22.0/src/modules/core/filter_pillar_echo.c000664 000000 000000 00000024025 14531534050 021542 0ustar00rootroot000000 000000 /* * filter_pillar_echo.c -- filter to interpolate pixels outside an area of interest * Copyright (c) 2020-2023 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "image_proc.h" #include #include #include #include /** Constrain a rect to be within the max dimensions */ static mlt_rect constrain_rect(mlt_rect rect, int max_x, int max_y) { if (rect.x < 0) { rect.w = rect.w + rect.x; rect.x = 0; } if (rect.y < 0) { rect.h = rect.h + rect.y; rect.y = 0; } if (rect.x + rect.w < 0) { rect.w = 0; } if (rect.y + rect.h < 0) { rect.h = 0; } if (rect.x + rect.w > max_x) { rect.w = max_x - rect.x; } if (rect.y + rect.h > max_y) { rect.h = max_y - rect.y; } return rect; } typedef struct { mlt_image src; mlt_image dst; mlt_rect rect; } scale_sliced_desc; static int scale_sliced_proc(int id, int index, int jobs, void *data) { (void) id; // unused scale_sliced_desc *desc = ((scale_sliced_desc *) data); mlt_image src = desc->src; mlt_image dst = desc->dst; mlt_rect rect = desc->rect; int slice_line_start, slice_height = mlt_slices_size_slice(jobs, index, src->height, &slice_line_start); int slice_line_end = slice_line_start + slice_height; double srcScale = rect.h / (double) src->height; int linesize = src->width * 4; uint8_t *d = dst->data + (slice_line_start * linesize); for (int y = slice_line_start; y < slice_line_end; y++) { double srcY = rect.y + (double) y * srcScale; int srcYindex = floor(srcY); double fbottom = srcY - srcYindex; double ftop = 1.0 - fbottom; int x = 0; for (x = 0; x < src->width; x++) { double srcX = rect.x + (double) x * srcScale; int srcXindex = floor(srcX); double fright = srcX - srcXindex; double fleft = 1.0 - fright; double valueSum[] = {0.0, 0.0, 0.0, 0.0}; double factorSum[] = {0.0, 0.0, 0.0, 0.0}; uint8_t *s = src->data + (srcYindex * linesize) + (srcXindex * 4); // Top Left double ftl = ftop * fleft; valueSum[0] += s[0] * ftl; factorSum[0] += ftl; valueSum[1] += s[1] * ftl; factorSum[1] += ftl; valueSum[2] += s[2] * ftl; factorSum[2] += ftl; valueSum[3] += s[3] * ftl; factorSum[3] += ftl; // Top Right if (x < src->width - 1) { double ftr = ftop * fright; valueSum[0] += s[4] * ftr; factorSum[0] += ftr; valueSum[1] += s[5] * ftr; factorSum[1] += ftr; valueSum[2] += s[6] * ftr; factorSum[2] += ftr; valueSum[3] += s[7] * ftr; factorSum[3] += ftr; } if (y < src->height - 1) { uint8_t *sb = s + linesize; // Bottom Left double fbl = fbottom * fleft; valueSum[0] += sb[0] * fbl; factorSum[0] += fbl; valueSum[1] += sb[1] * fbl; factorSum[1] += fbl; valueSum[2] += sb[2] * fbl; factorSum[2] += fbl; valueSum[3] += sb[3] * fbl; factorSum[3] += fbl; // Bottom Right if (x < src->width - 1) { double fbr = fbottom * fright; valueSum[0] += sb[4] * fbr; factorSum[0] += fbr; valueSum[1] += sb[5] * fbr; factorSum[1] += fbr; valueSum[2] += sb[6] * fbr; factorSum[2] += fbr; valueSum[3] += sb[7] * fbr; factorSum[3] += fbr; } } d[0] = (uint8_t) round(valueSum[0] / factorSum[0]); d[1] = (uint8_t) round(valueSum[1] / factorSum[1]); d[2] = (uint8_t) round(valueSum[2] / factorSum[2]); d[3] = (uint8_t) round(valueSum[3] / factorSum[3]); d += 4; } } return 0; } /** Perform a bilinear scale from the rect inside the source to fill the destination * * \param src a pointer to the source image * \param dst a pointer to the destination image * \param rect the area of interest in the src to be scaled to fit the dst */ static void bilinear_scale_rgba(mlt_image src, mlt_image dst, mlt_rect rect) { scale_sliced_desc desc; desc.src = src; desc.dst = dst; desc.rect = rect; // Crop out a section of the rect that has the same aspect ratio as the image double destAr = (double) src->width / (double) src->height; double sourceAr = rect.w / rect.h; if (sourceAr > destAr) { // Crop sides to fit height desc.rect.w = rect.w * destAr / sourceAr; desc.rect.x = rect.x + (rect.w - desc.rect.w) / 2.0; } else if (destAr > sourceAr) { // Crop top and bottom to fit width. desc.rect.h = rect.h * sourceAr / destAr; desc.rect.y = rect.y + (rect.h - desc.rect.h) / 2.0; } mlt_slices_run_normal(0, scale_sliced_proc, &desc); } /** Copy pixels from source to destination * * \param src a pointer to the source image * \param dst a pointer to the destination image * \param width the number of values in each row * \param rect the area of interest in the src to be copied to the dst */ static void blit_rect(mlt_image src, mlt_image dst, mlt_rect rect) { int blitHeight = rect.h; int blitWidth = rect.w * 4; int linesize = src->width * 4; uint8_t *s = src->data + (int) rect.y * linesize + (int) rect.x * 4; uint8_t *d = dst->data + (int) rect.y * linesize + (int) rect.x * 4; while (blitHeight--) { memcpy(d, s, blitWidth); s += linesize; d += linesize; } } static int filter_get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { int error = 0; mlt_filter filter = (mlt_filter) mlt_frame_pop_service(frame); mlt_properties filter_properties = MLT_FILTER_PROPERTIES(filter); char *rect_str = mlt_properties_get(filter_properties, "rect"); if (!rect_str) { mlt_log_warning(MLT_FILTER_SERVICE(filter), "rect property not set\n"); return mlt_frame_get_image(frame, image, format, width, height, writable); } mlt_profile profile = mlt_service_profile(MLT_FILTER_SERVICE(filter)); mlt_position position = mlt_filter_get_position(filter, frame); mlt_position length = mlt_filter_get_length2(filter, frame); mlt_rect rect = mlt_properties_anim_get_rect(filter_properties, "rect", position, length); if (strchr(rect_str, '%')) { rect.x *= profile->width; rect.w *= profile->width; rect.y *= profile->height; rect.h *= profile->height; } double scale = mlt_profile_scale_width(profile, *width); rect.x *= scale; rect.w *= scale; scale = mlt_profile_scale_height(profile, *height); rect.y *= scale; rect.h *= scale; rect = constrain_rect(rect, profile->width * scale, profile->height * scale); if (rect.w < 1 || rect.h < 1) { mlt_log_info(MLT_FILTER_SERVICE(filter), "rect invalid\n"); return mlt_frame_get_image(frame, image, format, width, height, writable); } *format = mlt_image_rgba; error = mlt_frame_get_image(frame, image, format, width, height, 0); if (error) return error; if (rect.x <= 0 && rect.y <= 0 && rect.w >= *width && rect.h >= *height) { // Rect fills the image. Nothing to blur return error; } double blur = mlt_properties_anim_get_double(filter_properties, "blur", position, length); // Convert from percent to pixels. blur = blur * (double) profile->width * mlt_profile_scale_width(profile, *width) / 100.0; blur = MAX(round(blur), 0); struct mlt_image_s src; mlt_image_set_values(&src, *image, *format, *width, *height); struct mlt_image_s dst; mlt_image_set_values(&dst, NULL, *format, *width, *height); mlt_image_alloc_data(&dst); bilinear_scale_rgba(&src, &dst, rect); if (blur != 0) { mlt_image_box_blur(&dst, blur, blur, 0); } blit_rect(&src, &dst, rect); *image = dst.data; mlt_frame_set_image(frame, dst.data, 0, dst.release_data); return error; } static mlt_frame filter_process(mlt_filter filter, mlt_frame frame) { mlt_frame_push_service(frame, filter); mlt_frame_push_get_image(frame, filter_get_image); return frame; } mlt_filter filter_pillar_echo_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = mlt_filter_new(); if (filter) { mlt_properties properties = MLT_FILTER_PROPERTIES(filter); mlt_properties_set(properties, "rect", "0% 0% 10% 10%"); mlt_properties_set_double(properties, "blur", 4.0); filter->process = filter_process; } else { mlt_log_error(NULL, "Filter pillar_echo initialization failed\n"); } return filter; } mlt-7.22.0/src/modules/core/filter_pillar_echo.yml000664 000000 000000 00000001602 14531534050 022115 0ustar00rootroot000000 000000 schema_version: 7.0 type: filter identifier: pillar_echo title: Pillar Echo version: 1 copyright: Meltytech, LLC license: LGPLv2.1 language: en tags: - Video description: > Create an echo effect (blur) outside of an area of interest. The area of interest is scaled to fill the image, then blurred. Then the original image is replaced back on top. parameters: - identifier: rect title: Rectangle description: > Defines the rectangle of the area of interest. Format is: "X Y W H". X, Y, W, H are assumed to be pixel units unless they have the suffix '%'. type: rect default: "0 0 10% 10%" readonly: no mutable: yes animation: yes - identifier: blur title: Blur description: > The blur radius as a percent of the image width type: float default: 4.0 readonly: no mutable: yes animation: yes mlt-7.22.0/src/modules/core/filter_rescale.c000664 000000 000000 00000024463 14531534050 020705 0ustar00rootroot000000 000000 /* * filter_rescale.c -- scale the producer video frame size to match the consumer * Copyright (C) 2003-2022 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include #include /** virtual function declaration for an image scaler * * image scaler implementations are expected to support the following in and out formats: * yuv422 -> yuv422 * rgb -> rgb * rgba -> rgba * rgb -> yuv422 * rgba -> yuv422 */ typedef int (*image_scaler)(mlt_frame frame, uint8_t **image, mlt_image_format *format, int iwidth, int iheight, int owidth, int oheight); static int filter_scale(mlt_frame frame, uint8_t **image, mlt_image_format *format, int iwidth, int iheight, int owidth, int oheight) { // Create the output image uint8_t *output = mlt_pool_alloc(owidth * (oheight + 1) * 2); // Calculate strides int istride = iwidth * 2; int ostride = owidth * 2; iwidth = iwidth - (iwidth % 4); // Derived coordinates int dy, dx; // Calculate ranges int out_x_range = owidth / 2; int out_y_range = oheight / 2; int in_x_range = iwidth / 2; int in_y_range = iheight / 2; // Output pointers register uint8_t *out_line = output; register uint8_t *out_ptr; // Calculate a middle pointer uint8_t *in_middle = *image + istride * in_y_range + in_x_range * 2; uint8_t *in_line; // Generate the affine transform scaling values register int scale_width = (iwidth << 16) / owidth; register int scale_height = (iheight << 16) / oheight; register int base = 0; int outer = out_x_range * scale_width; int bottom = out_y_range * scale_height; // Loop for the entirety of our output height. for (dy = -bottom; dy < bottom; dy += scale_height) { // Start at the beginning of the line out_ptr = out_line; // Pointer to the middle of the input line in_line = in_middle + (dy >> 16) * istride; // Loop for the entirety of our output row. for (dx = -outer; dx < outer; dx += scale_width) { base = dx >> 15; base &= 0xfffffffe; *out_ptr++ = *(in_line + base); base &= 0xfffffffc; *out_ptr++ = *(in_line + base + 1); dx += scale_width; base = dx >> 15; base &= 0xfffffffe; *out_ptr++ = *(in_line + base); base &= 0xfffffffc; *out_ptr++ = *(in_line + base + 3); } // Move to next output line out_line += ostride; } // Now update the frame mlt_frame_set_image(frame, output, owidth * (oheight + 1) * 2, mlt_pool_release); *image = output; return 0; } static void scale_alpha(mlt_frame frame, int iwidth, int iheight, int owidth, int oheight) { // Scale the alpha uint8_t *output = NULL; uint8_t *input = mlt_frame_get_alpha(frame); if (input != NULL) { uint8_t *out_line, *in_line; register int i, j, x, y; register int ox = (iwidth << 16) / owidth; register int oy = (iheight << 16) / oheight; output = mlt_pool_alloc(owidth * oheight); out_line = output; // Loop for the entirety of our output height. for (i = 0, y = (oy >> 1); i < oheight; i++, y += oy) { in_line = &input[(y >> 16) * iwidth]; for (j = 0, x = (ox >> 1); j < owidth; j++, x += ox) *out_line++ = in_line[x >> 16]; } // Set it back on the frame mlt_frame_set_alpha(frame, output, owidth * oheight, mlt_pool_release); } } /** Do it :-). */ static int filter_get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { int error = 0; // Get the frame properties mlt_properties properties = MLT_FRAME_PROPERTIES(frame); // Get the filter from the stack mlt_filter filter = mlt_frame_pop_service(frame); // Get the filter properties mlt_properties filter_properties = MLT_FILTER_PROPERTIES(filter); // Get the image scaler method image_scaler scaler_method = mlt_properties_get_data(filter_properties, "method", NULL); // Correct Width/height if necessary if (*width == 0 || *height == 0) { mlt_profile profile = mlt_service_profile(MLT_FILTER_SERVICE(filter)); *width = profile->width; *height = profile->height; } // There can be problems with small images - avoid them (by hacking - gah) if (*width >= 6 && *height >= 6) { int iwidth = *width; int iheight = *height; int owidth = *width; int oheight = *height; char *interps = mlt_properties_get(properties, "consumer.rescale"); if (mlt_properties_get(filter_properties, "factor")) { double factor = mlt_properties_get_double(filter_properties, "factor"); owidth *= factor; oheight *= factor; } // Default from the scaler if not specified on the frame if (interps == NULL) { interps = mlt_properties_get(MLT_FILTER_PROPERTIES(filter), "interpolation"); mlt_properties_set(properties, "consumer.rescale", interps); } // If meta.media.width/height exist, we want that as minimum information if (mlt_properties_get_int(properties, "meta.media.width")) { iwidth = mlt_properties_get_int(properties, "meta.media.width"); iheight = mlt_properties_get_int(properties, "meta.media.height"); } // Let the producer know what we are actually requested to obtain if (strcmp(interps, "none")) { mlt_properties_set_int(properties, "rescale_width", *width); mlt_properties_set_int(properties, "rescale_height", *height); } else { // When no scaling is requested, revert the requested dimensions if possible mlt_properties_set_int(properties, "rescale_width", iwidth); mlt_properties_set_int(properties, "rescale_height", iheight); } // Deinterlace if height is changing to prevent fields mixing on interpolation // One exception: non-interpolated, integral scaling if (iheight != oheight && (strcmp(interps, "nearest") || (iheight % oheight != 0))) mlt_properties_set_int(properties, "consumer.progressive", 1); // Convert the image to yuv422 when using the local scaler if (scaler_method == filter_scale) *format = mlt_image_yuv422; // Get the image as requested mlt_frame_get_image(frame, image, format, &iwidth, &iheight, writable); // Get rescale interpretation again, in case the producer wishes to override scaling interps = mlt_properties_get(properties, "consumer.rescale"); if (*image && strcmp(interps, "none") && (iwidth != owidth || iheight != oheight)) { mlt_log_debug(MLT_FILTER_SERVICE(filter), "%dx%d -> %dx%d (%s) %s\n", iwidth, iheight, owidth, oheight, mlt_image_format_name(*format), interps); // If valid colorspace if (*format == mlt_image_yuv422 || *format == mlt_image_rgb || *format == mlt_image_rgba || *format == mlt_image_yuv420p) { // Call the virtual function scaler_method(frame, image, format, iwidth, iheight, owidth, oheight); *width = owidth; *height = oheight; } else { *width = iwidth; *height = iheight; } // Scale the alpha channel only if exists and not correct size int alpha_size = 0; mlt_frame_get_alpha_size(frame, &alpha_size); if (alpha_size > 0 && alpha_size != (owidth * oheight) && alpha_size != (owidth * (oheight + 1))) scale_alpha(frame, iwidth, iheight, owidth, oheight); } else { *width = iwidth; *height = iheight; } } else { error = 1; } return error; } /** Filter processing. */ static mlt_frame filter_process(mlt_filter filter, mlt_frame frame) { // Push the filter mlt_frame_push_service(frame, filter); // Push the get image method mlt_frame_push_service(frame, filter_get_image); return frame; } /** Constructor for the filter. */ mlt_filter filter_rescale_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { // Create a new scaler mlt_filter filter = mlt_filter_new(); // If successful, then initialise it if (filter != NULL) { // Get the properties mlt_properties properties = MLT_FILTER_PROPERTIES(filter); // Set the process method filter->process = filter_process; // Set the inerpolation mlt_properties_set(properties, "interpolation", arg == NULL ? "bilinear" : arg); // Set the method mlt_properties_set_data(properties, "method", filter_scale, 0, NULL, NULL); } return filter; } mlt-7.22.0/src/modules/core/filter_rescale.yml000664 000000 000000 00000001655 14531534050 021262 0ustar00rootroot000000 000000 schema_version: 0.1 type: filter identifier: rescale title: Rescale version: 1 copyright: Meltytech, LLC creator: Dan Dennedy license: LGPLv2.1 language: en tags: - Video - Hidden description: > Scale the producer video frame size to match the consumer. This filter is designed for use as a normalizer for the loader producer. notes: > If a property "consumer_aspect_ratio" exists on the frame, then rescaler normalizes the producer's aspect ratio and maximises the size of the frame, but may not produce the consumer's requested dimension. Therefore, this option works best in conjunction with the resize filter. This behavior can be disabled by another service by either removing the property, setting it to zero, or setting frame property "distort" to 1. bugs: - > It only implements a nearest neighbour scaling - it is used as the base class for the gtkrescale and swscale filters. mlt-7.22.0/src/modules/core/filter_resize.c000664 000000 000000 00000024714 14531534050 020567 0ustar00rootroot000000 000000 /* * filter_resize.c -- resizing filter * Copyright (C) 2003-2023 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include #include static uint8_t *resize_alpha( uint8_t *input, int owidth, int oheight, int iwidth, int iheight, uint8_t alpha_value) { uint8_t *output = NULL; if (input != NULL && (iwidth != owidth || iheight != oheight) && (owidth > 6 && oheight > 6)) { uint8_t *out_line; int offset_x = (owidth - iwidth) / 2; int offset_y = (oheight - iheight) / 2; int iused = iwidth; output = mlt_pool_alloc(owidth * oheight); memset(output, alpha_value, owidth * oheight); offset_x -= offset_x % 2; out_line = output + offset_y * owidth; out_line += offset_x; // Loop for the entirety of our output height. while (iheight--) { // We're in the input range for this row. memcpy(out_line, input, iused); // Move to next input line input += iwidth; // Move to next output line out_line += owidth; } } return output; } static void resize_image(uint8_t *output, int owidth, int oheight, uint8_t *input, int iwidth, int iheight, int bpp, mlt_image_format format, uint8_t alpha_value) { // Calculate strides int istride = iwidth * bpp; int ostride = owidth * bpp; int offset_x = (owidth - iwidth) / 2 * bpp; int offset_y = (oheight - iheight) / 2; uint8_t *in_line = input; uint8_t *out_line; int size = owidth * oheight; uint8_t *p = output; // Optimisation point if (output == NULL || input == NULL || (owidth <= 6 || oheight <= 6 || iwidth <= 6 || iheight <= 6)) { return; } else if (iwidth == owidth && iheight == oheight) { memcpy(output, input, iheight * istride); return; } if (format == mlt_image_rgba) { memset(p, 0, size * bpp); if (alpha_value != 0) { while (size--) { p[3] = alpha_value; p += 4; } } } else if (bpp == 2) { memset(p, 16, size * bpp); while (size--) { p[1] = 128; p += 2; } offset_x -= offset_x % 4; } else { memset(p, 0, size * bpp); } out_line = output + offset_y * ostride; out_line += offset_x; // Loop for the entirety of our output height. while (iheight--) { // We're in the input range for this row. memcpy(out_line, in_line, istride); // Move to next input line in_line += istride; // Move to next output line out_line += ostride; } } /** A padding function for frames - this does not rescale, but simply resizes. */ static uint8_t *frame_resize_image(mlt_frame frame, int owidth, int oheight, mlt_image_format format) { // Get properties mlt_properties properties = MLT_FRAME_PROPERTIES(frame); // Get the input image, width and height uint8_t *input = mlt_properties_get_data(properties, "image", NULL); uint8_t *alpha = mlt_frame_get_alpha(frame); int alpha_size = 0; mlt_frame_get_alpha_size(frame, &alpha_size); int bpp = 0; mlt_image_format_size(format, owidth, oheight, &bpp); int iwidth = mlt_properties_get_int(properties, "width"); int iheight = mlt_properties_get_int(properties, "height"); // If width and height are correct, don't do anything if (iwidth < owidth || iheight < oheight) { mlt_log_debug(NULL, "[filter_resize] %dx%d -> %dx%d (%s)\n", iwidth, iheight, owidth, oheight, mlt_image_format_name(format)); uint8_t alpha_value = mlt_properties_get_int(properties, "resize_alpha"); // Create the output image uint8_t *output = mlt_pool_alloc(owidth * (oheight + 1) * bpp); // Call the generic resize resize_image(output, owidth, oheight, input, iwidth, iheight, bpp, format, alpha_value); // Now update the frame mlt_frame_set_image(frame, output, owidth * (oheight + 1) * bpp, mlt_pool_release); // We should resize the alpha too if (format != mlt_image_rgba && alpha && alpha_size >= iwidth * iheight) { alpha = resize_alpha(alpha, owidth, oheight, iwidth, iheight, alpha_value); if (alpha) mlt_frame_set_alpha(frame, alpha, owidth * oheight, mlt_pool_release); } // Return the output return output; } // No change, return input return input; } /** Do it :-). */ static int filter_get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { int error = 0; // Get the properties from the frame mlt_properties properties = MLT_FRAME_PROPERTIES(frame); // Pop the top of stack now mlt_filter filter = mlt_frame_pop_service(frame); mlt_profile profile = mlt_service_profile(MLT_FILTER_SERVICE(filter)); // Retrieve the aspect ratio double aspect_ratio = mlt_deque_pop_back_double(MLT_FRAME_IMAGE_STACK(frame)); double consumer_aspect = mlt_profile_sar(mlt_service_profile(MLT_FILTER_SERVICE(filter))); // Correct Width/height if necessary if (*width == 0 || *height == 0) { *width = profile->width; *height = profile->height; } // Assign requested width/height from our subordinate int owidth = *width; int oheight = *height; // Check for the special case - no aspect ratio means no problem :-) if (aspect_ratio == 0.0) aspect_ratio = consumer_aspect; // Reset the aspect ratio mlt_properties_set_double(properties, "aspect_ratio", aspect_ratio); // Skip scaling if requested char *rescale = mlt_properties_get(properties, "consumer.rescale"); if (rescale != NULL && !strcmp(rescale, "none")) return mlt_frame_get_image(frame, image, format, width, height, writable); if (mlt_properties_get_int(properties, "distort") == 0 && profile) { // Normalize the input and out display aspect int normalized_width = profile->width; int normalized_height = profile->height; int real_width = mlt_properties_get_int(properties, "meta.media.width"); int real_height = mlt_properties_get_int(properties, "meta.media.height"); if (real_width == 0) real_width = mlt_properties_get_int(properties, "width"); if (real_height == 0) real_height = mlt_properties_get_int(properties, "height"); double input_ar = aspect_ratio * real_width / real_height; double output_ar = consumer_aspect * owidth / oheight; // fprintf( stderr, "real %dx%d normalized %dx%d output %dx%d sar %f in-dar %f out-dar %f\n", // real_width, real_height, normalized_width, normalized_height, owidth, oheight, aspect_ratio, input_ar, output_ar); // Optimised for the input_ar > output_ar case (e.g. widescreen on standard) int scaled_width = rint((input_ar * normalized_width) / output_ar); int scaled_height = normalized_height; // Now ensure that our images fit in the output frame if (scaled_width > normalized_width) { scaled_width = normalized_width; scaled_height = rint((output_ar * normalized_height) / input_ar); } // Now calculate the actual image size that we want owidth = rint(scaled_width * owidth / normalized_width); oheight = rint(scaled_height * oheight / normalized_height); // Tell frame we have conformed the aspect to the consumer mlt_frame_set_aspect_ratio(frame, consumer_aspect); } mlt_properties_set_int(properties, "distort", 0); // Now pass on the calculations down the line mlt_properties_set_int(properties, "resize_width", *width); mlt_properties_set_int(properties, "resize_height", *height); // If there will be padding, then we need packed image format. if (*format == mlt_image_yuv420p && (owidth < *width || oheight < *height)) { *format = mlt_image_yuv422; } // Now get the image if (*format == mlt_image_yuv422) { owidth -= owidth % 2; *width -= *width % 2; } error = mlt_frame_get_image(frame, image, format, &owidth, &oheight, writable); if (error == 0 && *image && *format != mlt_image_yuv420p) { *image = frame_resize_image(frame, *width, *height, *format); } else { *width = owidth; *height = oheight; } return error; } /** Filter processing. */ static mlt_frame filter_process(mlt_filter filter, mlt_frame frame) { // Store the aspect ratio reported by the source mlt_deque_push_back_double(MLT_FRAME_IMAGE_STACK(frame), mlt_frame_get_aspect_ratio(frame)); // Push this on to the service stack mlt_frame_push_service(frame, filter); // Push the get_image method on to the stack mlt_frame_push_get_image(frame, filter_get_image); return frame; } /** Constructor for the filter. */ mlt_filter filter_resize_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = calloc(1, sizeof(struct mlt_filter_s)); if (mlt_filter_init(filter, filter) == 0) { filter->process = filter_process; } return filter; } mlt-7.22.0/src/modules/core/filter_resize.yml000664 000000 000000 00000001065 14531534050 021140 0ustar00rootroot000000 000000 schema_version: 0.1 type: filter identifier: resize title: Pad version: 2 copyright: Meltytech, LLC creator: Charles Yates license: LGPLv2.1 language: en tags: - Video - Hidden description: Pad an image with black to fulfill the requested image size. notes: > Normally resize is used to pad the producer's output to what the consumer has requested after an upstream rescale filter first scales the image to maximise usage of the image area. This filter is automatically invoked by the loader as part of image normalization. mlt-7.22.0/src/modules/core/filter_transition.c000664 000000 000000 00000012473 14531534050 021457 0ustar00rootroot000000 000000 /* * filter_transition.c -- Convert any transition into a filter * Copyright (C) 2005-2014 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include /** Get the image via the transition. NB: Not all transitions will accept a and b frames being the same... */ static int filter_get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { mlt_transition transition = mlt_frame_pop_service(frame); if (mlt_properties_get_int(MLT_FRAME_PROPERTIES(frame), "image_count") >= 1) mlt_transition_process(transition, frame, frame); return mlt_frame_get_image(frame, image, format, width, height, writable); } /** Get the audio via the transition. NB: Not all transitions will accept a and b frames being the same... */ static int filter_get_audio(mlt_frame frame, void **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples) { // Obtain the transition instance mlt_transition transition = mlt_frame_pop_audio(frame); mlt_transition_process(transition, frame, frame); return mlt_frame_get_audio(frame, buffer, format, frequency, channels, samples); } /** Filter processing. */ static mlt_frame filter_process(mlt_filter filter, mlt_frame frame) { // Obtain the transition instance mlt_transition transition = mlt_properties_get_data(MLT_FILTER_PROPERTIES(filter), "instance", NULL); // If we haven't created the instance, do it now if (transition == NULL) { char *name = mlt_properties_get(MLT_FILTER_PROPERTIES(filter), "transition"); mlt_profile profile = mlt_service_profile(MLT_FILTER_SERVICE(filter)); transition = mlt_factory_transition(profile, name, NULL); mlt_properties_set_data(MLT_FILTER_PROPERTIES(filter), "instance", transition, 0, (mlt_destructor) mlt_transition_close, NULL); } // We may still not have a transition... if (transition != NULL) { // Get the transition type int type = mlt_properties_get_int(MLT_TRANSITION_PROPERTIES(transition), "_transition_type"); // Set the basic info mlt_properties_set_int(MLT_TRANSITION_PROPERTIES(transition), "in", mlt_properties_get_int(MLT_FILTER_PROPERTIES(filter), "in")); mlt_properties_set_int(MLT_TRANSITION_PROPERTIES(transition), "out", mlt_properties_get_int(MLT_FILTER_PROPERTIES(filter), "out")); // Refresh with current user values mlt_properties_pass(MLT_TRANSITION_PROPERTIES(transition), MLT_FILTER_PROPERTIES(filter), "transition."); if (type & 1 && !mlt_frame_is_test_card(frame) && !(mlt_properties_get_int(MLT_FRAME_PROPERTIES(frame), "hide") & 1)) { mlt_frame_push_service(frame, transition); mlt_frame_push_get_image(frame, filter_get_image); } if (type & 2 && !mlt_frame_is_test_audio(frame) && !(mlt_properties_get_int(MLT_FRAME_PROPERTIES(frame), "hide") & 2)) { mlt_frame_push_audio(frame, transition); mlt_frame_push_audio(frame, filter_get_audio); } if (type == 0) mlt_properties_debug(MLT_TRANSITION_PROPERTIES(transition), "unknown transition type", stderr); } else { mlt_properties_debug(MLT_FILTER_PROPERTIES(filter), "no transition", stderr); } return frame; } /** Constructor for the filter. */ mlt_filter filter_transition_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = mlt_filter_new(); if (filter != NULL) { mlt_properties_set(MLT_FILTER_PROPERTIES(filter), "transition", arg); filter->process = filter_process; } return filter; } mlt-7.22.0/src/modules/core/filter_transition.yml000664 000000 000000 00000001247 14531534050 022033 0ustar00rootroot000000 000000 schema_version: 0.2 type: filter identifier: transition title: Transition as Filter (*DEPRECATED*) version: 1 copyright: Meltytech, LLC creator: Charles Yates license: LGPLv2.1 language: en tags: - Audio - Video description: > Use a transition as a filter. The filters supplies the same frame as both the A and B clip to the transition. parameters: - identifier: transition argument: yes type: string title: Transition description: The MLT name of a transition. required: yes - identifier: transition.* type: properties description: > Properties may be set on the encapsulated composite transition. e.g.: transition.valign=c mlt-7.22.0/src/modules/core/filter_watermark.c000664 000000 000000 00000025620 14531534050 021260 0ustar00rootroot000000 000000 /* * filter_watermark.c -- watermark filter * Copyright (C) 2003-2014 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include #include /** Do it :-). */ static int filter_get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { // Error we will return int error = 0; // Get the watermark filter object mlt_filter filter = mlt_frame_pop_service(frame); // Get the properties of the filter mlt_properties properties = MLT_FILTER_PROPERTIES(filter); mlt_service_lock(MLT_FILTER_SERVICE(filter)); // Get the producer from the filter mlt_producer producer = mlt_properties_get_data(properties, "producer", NULL); // Get the composite from the filter mlt_transition composite = mlt_properties_get_data(properties, "composite", NULL); // Get the resource to use char *resource = mlt_properties_get(properties, "resource"); // Get the old resource char *old_resource = mlt_properties_get(properties, "_old_resource"); // Create a composite if we don't have one if (composite == NULL) { // Create composite via the factory mlt_profile profile = mlt_service_profile(MLT_FILTER_SERVICE(filter)); composite = mlt_factory_transition(profile, "composite", NULL); // Register the composite for reuse/destruction if (composite != NULL) mlt_properties_set_data(properties, "composite", composite, 0, (mlt_destructor) mlt_transition_close, NULL); } // If we have one if (composite != NULL) { // Get the properties mlt_properties composite_properties = MLT_TRANSITION_PROPERTIES(composite); // Pass all the composite. properties on the filter down mlt_properties_pass(composite_properties, properties, "composite."); if (mlt_properties_get(properties, "composite.out") == NULL) mlt_properties_set_int(composite_properties, "out", mlt_properties_get_int(properties, "_out")); // Force a refresh mlt_properties_set_int(composite_properties, "refresh", 1); } // Create a producer if don't have one if (producer == NULL || (old_resource != NULL && strcmp(resource, old_resource))) { // Get the factory producer service char *factory = mlt_properties_get(properties, "factory"); // Create the producer mlt_profile profile = mlt_service_profile(MLT_FILTER_SERVICE(filter)); producer = mlt_factory_producer(profile, factory, resource); // If we have one if (producer != NULL) { // Register the producer for reuse/destruction mlt_properties_set_data(properties, "producer", producer, 0, (mlt_destructor) mlt_producer_close, NULL); // Ensure that we loop mlt_properties_set(MLT_PRODUCER_PROPERTIES(producer), "eof", "loop"); // Set the old resource mlt_properties_set(properties, "_old_resource", resource); } } if (producer != NULL) { // Get the producer properties mlt_properties producer_properties = MLT_PRODUCER_PROPERTIES(producer); // Now pass all producer. properties on the filter down mlt_properties_pass(producer_properties, properties, "producer."); } mlt_service_unlock(MLT_FILTER_SERVICE(filter)); // Process all remaining filters first *format = mlt_image_yuv422; error = mlt_frame_get_image(frame, image, format, width, height, 0); // Only continue if we have both producer and composite if (!error && composite != NULL && producer != NULL) { // Get the service of the producer mlt_service service = MLT_PRODUCER_SERVICE(producer); // Create a temporary frame so the original stays in tact. mlt_frame a_frame = mlt_frame_clone(frame, 0); // We will get the 'b frame' from the producer mlt_frame b_frame = NULL; // Get the original producer position mlt_position position = mlt_filter_get_position(filter, frame); // Make sure the producer is in the correct position mlt_producer_seek(producer, position); // Resetting position to appease the composite transition mlt_frame_set_position(a_frame, position); // Get the b frame and process with composite if successful if (mlt_service_get_frame(service, &b_frame, 0) == 0) { // Get the a and b frame properties mlt_properties a_props = MLT_FRAME_PROPERTIES(a_frame); mlt_properties b_props = MLT_FRAME_PROPERTIES(b_frame); mlt_profile profile = mlt_service_profile(service); // Set the b frame to be in the same position and have same consumer requirements mlt_frame_set_position(b_frame, position); mlt_properties_set_int(b_props, "consumer.progressive", mlt_properties_get_int(a_props, "consumer.progressive") || mlt_properties_get_int(properties, "deinterlace")); // Check for the special case - no aspect ratio means no problem :-) if (mlt_frame_get_aspect_ratio(b_frame) == 0) mlt_frame_set_aspect_ratio(b_frame, mlt_profile_sar(profile)); if (mlt_frame_get_aspect_ratio(a_frame) == 0) mlt_frame_set_aspect_ratio(a_frame, mlt_profile_sar(profile)); if (mlt_properties_get_int(properties, "distort")) { mlt_properties_set_int(MLT_TRANSITION_PROPERTIES(composite), "distort", 1); mlt_properties_set_int(a_props, "distort", 1); mlt_properties_set_int(b_props, "distort", 1); } *format = mlt_image_yuv422; if (mlt_properties_get_int(properties, "reverse") == 0) { // Apply all filters that are attached to this filter to the b frame mlt_service_apply_filters(MLT_FILTER_SERVICE(filter), b_frame, 0); // Process the frame mlt_transition_process(composite, a_frame, b_frame); // Get the image error = mlt_frame_get_image(a_frame, image, format, width, height, 1); } else { char temp[132]; int count = 0; uint8_t *alpha = NULL; const char *rescale = mlt_properties_get(a_props, "consumer.rescale"); if (rescale == NULL || !strcmp(rescale, "none")) rescale = "hyper"; mlt_transition_process(composite, b_frame, a_frame); mlt_properties_set_int(a_props, "consumer.progressive", 1); mlt_properties_set_int(b_props, "consumer.progressive", 1); mlt_properties_set(a_props, "consumer.rescale", rescale); mlt_properties_set(b_props, "consumer.rescale", rescale); mlt_service_apply_filters(MLT_FILTER_SERVICE(filter), b_frame, 0); error = mlt_frame_get_image(b_frame, image, format, width, height, 1); alpha = mlt_frame_get_alpha(b_frame); mlt_frame_set_image(frame, *image, *width * *height * 2, NULL); if (alpha) mlt_frame_set_alpha(frame, alpha, *width * *height, NULL); mlt_properties_set_int(a_props, "width", *width); mlt_properties_set_int(a_props, "height", *height); mlt_properties_set_int(a_props, "progressive", 1); mlt_properties_inc_ref(b_props); strcpy(temp, "_b_frame"); while (mlt_properties_get_data(a_props, temp, NULL) != NULL) sprintf(temp, "_b_frame%d", count++); mlt_properties_set_data(a_props, temp, b_frame, 0, (mlt_destructor) mlt_frame_close, NULL); } } // Close the temporary frames mlt_frame_close(a_frame); mlt_frame_close(b_frame); } return error; } /** Filter processing. */ static mlt_frame filter_process(mlt_filter filter, mlt_frame frame) { // Get the properties of the frame mlt_properties properties = MLT_FRAME_PROPERTIES(frame); // Assign the frame out point to the filter (just in case we need it later) mlt_properties_set_int(MLT_FILTER_PROPERTIES(filter), "_out", mlt_properties_get_int(properties, "out")); // Push the filter on to the stack mlt_frame_push_service(frame, filter); // Push the get_image on to the stack mlt_frame_push_get_image(frame, filter_get_image); return frame; } /** Constructor for the filter. */ mlt_filter filter_watermark_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = mlt_filter_new(); if (filter != NULL) { mlt_properties properties = MLT_FILTER_PROPERTIES(filter); filter->process = filter_process; mlt_properties_set(properties, "factory", mlt_environment("MLT_PRODUCER")); if (arg != NULL) mlt_properties_set(properties, "resource", arg); // Ensure that attached filters are handled privately mlt_properties_set_int(properties, "_filter_private", 1); } return filter; } mlt-7.22.0/src/modules/core/filter_watermark.yml000664 000000 000000 00000004215 14531534050 021634 0ustar00rootroot000000 000000 schema_version: 7.0 type: filter identifier: watermark title: Overlay version: 1 copyright: Meltytech, LLC creator: Charles Yates license: LGPLv2.1 language: en tags: - Video description: Overlay text or images onto the video notes: > The watermark filter combines a frame producer and a composite transition to overlay the specified text or image onto the video. Supplying a filename with extension ".txt" causes the loader to load a pango producer. Supplying a file name with an extension supported by gtk-pixbuf causes the loader to load a pixbuf producer. See the pango and pixbuf producers for details. Note: If the filename begins with "+" the pango producer interprets the filename as pango text. Text Example: melt colour:red -filter watermark:"+First Line~Second Line.txt" composite.progressive=1 producer.align=centre composite.valign=c composite.halign=c Image Example: melt clip.dv -filter watermark:logo.png parameters: - identifier: resource argument: yes title: File/URL type: string description: The file to overlay. required: no readonly: no widget: fileopen - identifier: distort title: Allow distorted scaling type: integer default: 0 minimum: 0 maximum: 1 widget: checkbox - identifier: producer.* title: Producer description: | Properties may be set on the encapsulated producer. e.g.: producer.align=centre See "pango" and "pixbuf" producers for details. readonly: no - identifier: composite.* title: Composite description: | Properties may be set on the encapsulated composite. e.g.: composite.valign=c See "composite" transition for details. readonly: no - identifier: reverse title: Reverse type: integer description: Overlay the video to which the filter is applied atop the supplied file. minimum: 0 maximum: 1 mutable: yes widget: checkbox - identifier: deinterlace description: Force the supplied file to be be deinterlaced if it is interlaced. type: integer description: minimum: 0 maximum: 1 mutable: yes widget: checkbox mlt-7.22.0/src/modules/core/image_proc.c000664 000000 000000 00000033337 14531534050 020027 0ustar00rootroot000000 000000 /* * Copyright (c) 2022-2023 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "image_proc.h" #include #include #include typedef struct { mlt_image src; mlt_image dst; int radius; } blur_slice_desc; static int blur_h_proc_rgba(int id, int index, int jobs, void *data) { (void) id; // unused blur_slice_desc *desc = ((blur_slice_desc *) data); int slice_line_start, slice_height = mlt_slices_size_slice(jobs, index, desc->src->height, &slice_line_start); int slice_line_end = slice_line_start + slice_height; int accumulator[] = {0, 0, 0, 0}; int x = 0; int y = 0; int step = 4; int linesize = step * desc->src->width; int radius = desc->radius; if (desc->radius > (desc->src->width / 2)) { radius = desc->src->width / 2; } double diameter = (radius * 2) + 1; for (y = slice_line_start; y < slice_line_end; y++) { uint8_t *first = desc->src->data + (y * linesize); uint8_t *last = first + linesize - step; uint8_t *s1 = first; uint8_t *s2 = first; uint8_t *d = desc->dst->data + (y * linesize); accumulator[0] = first[0] * (radius + 1); accumulator[1] = first[1] * (radius + 1); accumulator[2] = first[2] * (radius + 1); accumulator[3] = first[3] * (radius + 1); for (x = 0; x < radius; x++) { accumulator[0] += s1[0]; accumulator[1] += s1[1]; accumulator[2] += s1[2]; accumulator[3] += s1[3]; s1 += step; } for (x = 0; x <= radius; x++) { accumulator[0] += s1[0] - first[0]; accumulator[1] += s1[1] - first[1]; accumulator[2] += s1[2] - first[2]; accumulator[3] += s1[3] - first[3]; d[0] = lrint((double) accumulator[0] / diameter); d[1] = lrint((double) accumulator[1] / diameter); d[2] = lrint((double) accumulator[2] / diameter); d[3] = lrint((double) accumulator[3] / diameter); s1 += step; d += step; } for (x = radius + 1; x < desc->src->width - radius; x++) { accumulator[0] += s1[0] - s2[0]; accumulator[1] += s1[1] - s2[1]; accumulator[2] += s1[2] - s2[2]; accumulator[3] += s1[3] - s2[3]; d[0] = lrint((double) accumulator[0] / diameter); d[1] = lrint((double) accumulator[1] / diameter); d[2] = lrint((double) accumulator[2] / diameter); d[3] = lrint((double) accumulator[3] / diameter); s1 += step; s2 += step; d += step; } for (x = desc->src->width - radius; x < desc->src->width; x++) { accumulator[0] += last[0] - s2[0]; accumulator[1] += last[1] - s2[1]; accumulator[2] += last[2] - s2[2]; accumulator[3] += last[3] - s2[3]; d[0] = lrint((double) accumulator[0] / diameter); d[1] = lrint((double) accumulator[1] / diameter); d[2] = lrint((double) accumulator[2] / diameter); d[3] = lrint((double) accumulator[3] / diameter); s2 += step; d += step; } } return 0; } static int blur_v_proc_rgba(int id, int index, int jobs, void *data) { (void) id; // unused blur_slice_desc *desc = ((blur_slice_desc *) data); int slice_row_start, slice_width = mlt_slices_size_slice(jobs, index, desc->src->width, &slice_row_start); int slice_row_end = slice_row_start + slice_width; int accumulator[] = {0, 0, 0, 0}; int x = 0; int y = 0; int step = 4; int linesize = step * desc->src->width; int radius = desc->radius; if (desc->radius > (desc->src->height / 2)) { radius = desc->src->height / 2; } double diameter = (radius * 2) + 1; for (x = slice_row_start; x < slice_row_end; x++) { uint8_t *first = desc->src->data + (x * step); uint8_t *last = first + (linesize * (desc->src->height - 1)); uint8_t *s1 = first; uint8_t *s2 = first; uint8_t *d = desc->dst->data + (x * step); accumulator[0] = first[0] * (radius + 1); accumulator[1] = first[1] * (radius + 1); accumulator[2] = first[2] * (radius + 1); accumulator[3] = first[3] * (radius + 1); for (y = 0; y < radius; y++) { accumulator[0] += s1[0]; accumulator[1] += s1[1]; accumulator[2] += s1[2]; accumulator[3] += s1[3]; s1 += linesize; } for (y = 0; y <= radius; y++) { accumulator[0] += s1[0] - first[0]; accumulator[1] += s1[1] - first[1]; accumulator[2] += s1[2] - first[2]; accumulator[3] += s1[3] - first[3]; d[0] = lrint((double) accumulator[0] / diameter); d[1] = lrint((double) accumulator[1] / diameter); d[2] = lrint((double) accumulator[2] / diameter); d[3] = lrint((double) accumulator[3] / diameter); s1 += linesize; d += linesize; } for (y = radius + 1; y < desc->src->height - radius; y++) { accumulator[0] += s1[0] - s2[0]; accumulator[1] += s1[1] - s2[1]; accumulator[2] += s1[2] - s2[2]; accumulator[3] += s1[3] - s2[3]; d[0] = lrint((double) accumulator[0] / diameter); d[1] = lrint((double) accumulator[1] / diameter); d[2] = lrint((double) accumulator[2] / diameter); d[3] = lrint((double) accumulator[3] / diameter); s1 += linesize; s2 += linesize; d += linesize; } for (y = desc->src->height - radius; y < desc->src->height; y++) { accumulator[0] += last[0] - s2[0]; accumulator[1] += last[1] - s2[1]; accumulator[2] += last[2] - s2[2]; accumulator[3] += last[3] - s2[3]; d[0] = lrint((double) accumulator[0] / diameter); d[1] = lrint((double) accumulator[1] / diameter); d[2] = lrint((double) accumulator[2] / diameter); d[3] = lrint((double) accumulator[3] / diameter); s2 += linesize; d += linesize; } } return 0; } static int blur_h_proc_rgbx(int id, int index, int jobs, void *data) { (void) id; // unused blur_slice_desc *desc = ((blur_slice_desc *) data); int slice_line_start, slice_height = mlt_slices_size_slice(jobs, index, desc->src->height, &slice_line_start); int slice_line_end = slice_line_start + slice_height; int accumulator[] = {0, 0, 0}; int x = 0; int y = 0; int step = 4; int linesize = step * desc->src->width; int radius = desc->radius; if (desc->radius > (desc->src->width / 2)) { radius = desc->src->width / 2; } double diameter = (radius * 2) + 1; for (y = slice_line_start; y < slice_line_end; y++) { uint8_t *first = desc->src->data + (y * linesize); uint8_t *last = first + linesize - step; uint8_t *s1 = first; uint8_t *s2 = first; uint8_t *d = desc->dst->data + (y * linesize); accumulator[0] = first[0] * (radius + 1); accumulator[1] = first[1] * (radius + 1); accumulator[2] = first[2] * (radius + 1); for (x = 0; x < radius; x++) { accumulator[0] += s1[0]; accumulator[1] += s1[1]; accumulator[2] += s1[2]; s1 += step; } for (x = 0; x <= radius; x++) { accumulator[0] += s1[0] - first[0]; accumulator[1] += s1[1] - first[1]; accumulator[2] += s1[2] - first[2]; d[0] = lrint((double) accumulator[0] / diameter); d[1] = lrint((double) accumulator[1] / diameter); d[2] = lrint((double) accumulator[2] / diameter); s1 += step; d += step; } for (x = radius + 1; x < desc->src->width - radius; x++) { accumulator[0] += s1[0] - s2[0]; accumulator[1] += s1[1] - s2[1]; accumulator[2] += s1[2] - s2[2]; d[0] = lrint((double) accumulator[0] / diameter); d[1] = lrint((double) accumulator[1] / diameter); d[2] = lrint((double) accumulator[2] / diameter); s1 += step; s2 += step; d += step; } for (x = desc->src->width - radius; x < desc->src->width; x++) { accumulator[0] += last[0] - s2[0]; accumulator[1] += last[1] - s2[1]; accumulator[2] += last[2] - s2[2]; d[0] = lrint((double) accumulator[0] / diameter); d[1] = lrint((double) accumulator[1] / diameter); d[2] = lrint((double) accumulator[2] / diameter); s2 += step; d += step; } } return 0; } static int blur_v_proc_rgbx(int id, int index, int jobs, void *data) { (void) id; // unused blur_slice_desc *desc = ((blur_slice_desc *) data); int slice_row_start, slice_width = mlt_slices_size_slice(jobs, index, desc->src->width, &slice_row_start); int slice_row_end = slice_row_start + slice_width; int accumulator[] = {0, 0, 0}; int x = 0; int y = 0; int step = 4; int linesize = step * desc->src->width; int radius = desc->radius; if (desc->radius > (desc->src->height / 2)) { radius = desc->src->height / 2; } double diameter = (radius * 2) + 1; for (x = slice_row_start; x < slice_row_end; x++) { uint8_t *first = desc->src->data + (x * step); uint8_t *last = first + (linesize * (desc->src->height - 1)); uint8_t *s1 = first; uint8_t *s2 = first; uint8_t *d = desc->dst->data + (x * step); accumulator[0] = first[0] * (radius + 1); accumulator[1] = first[1] * (radius + 1); accumulator[2] = first[2] * (radius + 1); for (y = 0; y < radius; y++) { accumulator[0] += s1[0]; accumulator[1] += s1[1]; accumulator[2] += s1[2]; s1 += linesize; } for (y = 0; y <= radius; y++) { accumulator[0] += s1[0] - first[0]; accumulator[1] += s1[1] - first[1]; accumulator[2] += s1[2] - first[2]; d[0] = lrint((double) accumulator[0] / diameter); d[1] = lrint((double) accumulator[1] / diameter); d[2] = lrint((double) accumulator[2] / diameter); s1 += linesize; d += linesize; } for (y = radius + 1; y < desc->src->height - radius; y++) { accumulator[0] += s1[0] - s2[0]; accumulator[1] += s1[1] - s2[1]; accumulator[2] += s1[2] - s2[2]; d[0] = lrint((double) accumulator[0] / diameter); d[1] = lrint((double) accumulator[1] / diameter); d[2] = lrint((double) accumulator[2] / diameter); s1 += linesize; s2 += linesize; d += linesize; } for (y = desc->src->height - radius; y < desc->src->height; y++) { accumulator[0] += last[0] - s2[0]; accumulator[1] += last[1] - s2[1]; accumulator[2] += last[2] - s2[2]; d[0] = lrint((double) accumulator[0] / diameter); d[1] = lrint((double) accumulator[1] / diameter); d[2] = lrint((double) accumulator[2] / diameter); s2 += linesize; d += linesize; } } return 0; } /** Perform a box blur * * This function uses a sliding window accumulator method - applied * horizontally first and then vertically. * * \param self the Image object * \param hradius the radius of the horizontal blur in pixels * \param vradius radius of the vertical blur in pixels * \param preserve_alpha exclude the alpha channel from the blur operation */ void mlt_image_box_blur(mlt_image self, int hradius, int vradius, int preserve_alpha) { if (self->format != mlt_image_rgba) { mlt_log(NULL, MLT_LOG_ERROR, "Image type %s not supported by box blur\n", mlt_image_format_name(self->format)); return; } // The horizontal blur is performed into a temporary image. // The vertical blur is performed back into the original image. struct mlt_image_s tmpimage; mlt_image_set_values(&tmpimage, NULL, self->format, self->width, self->height); mlt_image_alloc_data(&tmpimage); if (self->alpha) { mlt_image_alloc_alpha(&tmpimage); } blur_slice_desc desc; if (preserve_alpha) { desc.src = self, desc.dst = &tmpimage, desc.radius = hradius, mlt_slices_run_normal(0, blur_h_proc_rgbx, &desc); desc.src = &tmpimage, desc.dst = self, desc.radius = vradius, mlt_slices_run_normal(0, blur_v_proc_rgbx, &desc); } else { desc.src = self, desc.dst = &tmpimage, desc.radius = hradius, mlt_slices_run_normal(0, blur_h_proc_rgba, &desc); desc.src = &tmpimage, desc.dst = self, desc.radius = vradius, mlt_slices_run_normal(0, blur_v_proc_rgba, &desc); } mlt_image_close(&tmpimage); } mlt-7.22.0/src/modules/core/image_proc.h000664 000000 000000 00000001771 14531534050 020031 0ustar00rootroot000000 000000 /* * Copyright (c) 2022-20023 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef IMAGE_PROC_H #define IMAGE_PROC_H #include #include void mlt_image_box_blur(mlt_image self, int hradius, int vradius, int preserve_alpha); #endif // IMAGE_PROC_H mlt-7.22.0/src/modules/core/link_timeremap.c000664 000000 000000 00000067172 14531534050 020726 0ustar00rootroot000000 000000 /* * link_timeremap.c * Copyright (C) 2020-2023 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include #include // Private Types typedef struct { mlt_position prev_integration_position; double prev_integration_time; mlt_frame prev_frame; mlt_filter resample_filter; mlt_filter pitch_filter; } private_data; static void property_changed(mlt_service owner, mlt_link self, mlt_event_data event_data) { const char *name = mlt_event_data_to_string(event_data); if (!name) return; if (strcmp("map", name) == 0) { // Copy the deprecated "map" parameter to the new "time_map" parameter. const char *value = mlt_properties_get(MLT_LINK_PROPERTIES(self), "map"); mlt_properties_set(MLT_LINK_PROPERTIES(self), "time_map", value); } else if (strcmp("speed_map", name) == 0) { // speed_map changed. Need to re-integrate from the beginning. private_data *pdata = (private_data *) self->child; pdata->prev_integration_position = 0; pdata->prev_integration_time = 0.0; } } static double integrate_source_time(mlt_link self, mlt_position position) { private_data *pdata = (private_data *) self->child; mlt_properties properties = MLT_LINK_PROPERTIES(self); mlt_position length = mlt_producer_get_length(MLT_LINK_PRODUCER(self)); mlt_position in = mlt_producer_get_in(MLT_LINK_PRODUCER(self)); double link_fps = mlt_producer_get_fps(MLT_LINK_PRODUCER(self)); mlt_position integration_distance = abs(pdata->prev_integration_position - position); if (pdata->prev_integration_position < in || integration_distance > (position - in)) { // Restart integration from the beginning pdata->prev_integration_position = in; pdata->prev_integration_time = 0.0; } double source_time = pdata->prev_integration_time; if (pdata->prev_integration_position < position) { for (mlt_position p = pdata->prev_integration_position; p < position; p++) { double speed = mlt_properties_anim_get_double(properties, "speed_map", p - in, length); double time_delta = speed / link_fps; source_time += time_delta; } } else if (pdata->prev_integration_position > position) { for (mlt_position p = position; p < pdata->prev_integration_position; p++) { double speed = mlt_properties_anim_get_double(properties, "speed_map", p - in, length); double time_delta = speed / link_fps; source_time -= time_delta; } } // Remember the integration results so that the next frame can start // integration from this position instead of from the beginning. pdata->prev_integration_position = position; pdata->prev_integration_time = source_time; return source_time; } static void link_configure(mlt_link self, mlt_profile chain_profile) { // Timeremap must always work with the same profile as the chain so that // animation, in and out will work. mlt_service_set_profile(MLT_LINK_SERVICE(self), chain_profile); } static int link_get_audio(mlt_frame frame, void **audio, mlt_audio_format *format, int *frequency, int *channels, int *samples) { int requested_frequency = *frequency; int requested_samples = *samples; mlt_link self = (mlt_link) mlt_frame_pop_audio(frame); private_data *pdata = (private_data *) self->child; mlt_properties unique_properties = mlt_frame_get_unique_properties(frame, MLT_LINK_SERVICE(self)); if (!unique_properties) { return 1; } double source_time = mlt_properties_get_double(unique_properties, "source_time"); double source_duration = mlt_properties_get_double(unique_properties, "source_duration"); double source_fps = mlt_properties_get_double(unique_properties, "source_fps"); double source_speed = mlt_properties_get_double(unique_properties, "source_speed"); source_speed = fabs(source_speed); double link_fps = mlt_producer_get_fps(MLT_LINK_PRODUCER(self)); // Validate the request *channels = *channels <= 0 ? 2 : *channels; *frequency = *frequency <= 0 ? 48000 : *frequency; if (source_speed < 0.1 || source_speed > 10) { // Return silent samples for speeds less than 0.1 or > 10 mlt_position position = mlt_frame_original_position(frame); *samples = mlt_audio_calculate_frame_samples(link_fps, *frequency, position); int size = mlt_audio_format_size(*format, *samples, *channels); *audio = mlt_pool_alloc(size); memset(*audio, 0, size); mlt_frame_set_audio(frame, *audio, *format, size, mlt_pool_release); return 0; } int src_frequency = mlt_properties_get_int(MLT_FRAME_PROPERTIES(frame), "audio_frequency"); if (src_frequency > 0) { *frequency = src_frequency; } // Calculate the samples to get from the input frames int link_sample_count = mlt_audio_calculate_frame_samples(link_fps, *frequency, mlt_frame_get_position(frame)); int sample_count = lrint((double) link_sample_count * source_speed); mlt_position in_frame_pos = floor(source_time * source_fps); int64_t first_out_sample = llrint(source_time * (double) *frequency); // Attempt to maintain sample continuity with the previous frame static const int64_t SAMPLE_CONTINUITY_ERROR_MARGIN = 4; int64_t continuity_sample = mlt_properties_get_int64(MLT_LINK_PROPERTIES(self), "_continuity_sample"); int64_t continuity_delta = continuity_sample - first_out_sample; if (source_duration > 0.0 && continuity_delta != 0) { // Forward: Continue from where the previous frame left off if within the margin of error if (continuity_delta > -SAMPLE_CONTINUITY_ERROR_MARGIN && continuity_delta < SAMPLE_CONTINUITY_ERROR_MARGIN) { sample_count = sample_count - continuity_delta; first_out_sample = continuity_sample; mlt_log_debug(MLT_LINK_SERVICE(self), "Maintain Forward Continuity: %d\n", (int) continuity_delta); } } else if (source_duration < 0.0 && continuity_delta != sample_count) { // Reverse: End where the previous frame left off if within the margin of error continuity_delta -= sample_count; if (continuity_delta > -SAMPLE_CONTINUITY_ERROR_MARGIN && continuity_delta < SAMPLE_CONTINUITY_ERROR_MARGIN) { sample_count = sample_count + continuity_delta; mlt_log_debug(MLT_LINK_SERVICE(self), "Maintain Reverse Continuity: %d\n", (int) continuity_delta); } } int64_t first_in_sample = mlt_audio_calculate_samples_to_position(source_fps, *frequency, in_frame_pos); int samples_to_skip = first_out_sample - first_in_sample; if (samples_to_skip < 0) { mlt_log_error(MLT_LINK_SERVICE(self), "Audio too late: %d\t%d\n", (int) first_out_sample, (int) first_in_sample); samples_to_skip = 0; } // Allocate the out buffer struct mlt_audio_s out; mlt_audio_set_values(&out, NULL, *frequency, *format, sample_count, *channels); mlt_audio_alloc_data(&out); // Copy audio from the input frames to the output buffer int samples_copied = 0; int samples_needed = sample_count; while (samples_needed > 0) { char key[19]; sprintf(key, "%d", in_frame_pos); mlt_frame src_frame = (mlt_frame) mlt_properties_get_data(unique_properties, key, NULL); if (!src_frame) { mlt_log_error(MLT_LINK_SERVICE(self), "Frame not found: %d\n", in_frame_pos); break; } int in_samples = mlt_audio_calculate_frame_samples(source_fps, *frequency, in_frame_pos); struct mlt_audio_s in; mlt_audio_set_values(&in, NULL, *frequency, *format, in_samples, *channels); int error = mlt_frame_get_audio(src_frame, &in.data, &in.format, &in.frequency, &in.channels, &in.samples); if (error) { mlt_log_error(MLT_LINK_SERVICE(self), "No audio: %d\n", in_frame_pos); break; } int samples_to_copy = in.samples - samples_to_skip; if (samples_to_copy > samples_needed) { samples_to_copy = samples_needed; } mlt_log_debug(MLT_LINK_SERVICE(self), "Copy: %d\t%d\t%d\t%d\n", samples_to_skip, samples_to_skip + samples_to_copy - 1, samples_to_copy, in.samples); if (samples_to_copy > 0) { mlt_audio_copy(&out, &in, samples_to_copy, samples_to_skip, samples_copied); samples_copied += samples_to_copy; samples_needed -= samples_to_copy; } samples_to_skip = 0; in_frame_pos++; } if (samples_copied != sample_count) { mlt_log_error(MLT_LINK_SERVICE(self), "Sample under run: %d\t%d\n", samples_copied, sample_count); mlt_audio_shrink(&out, samples_copied); } if (source_duration < 0.0) { // Going backwards mlt_audio_reverse(&out); mlt_properties_set_int64(MLT_LINK_PROPERTIES(self), "_continuity_sample", first_out_sample); } else { mlt_properties_set_int64(MLT_LINK_PROPERTIES(self), "_continuity_sample", first_out_sample + sample_count); } int in_frequency = *frequency; out.frequency = lrint((double) out.frequency * (double) sample_count / (double) link_sample_count); mlt_frame_set_audio(frame, out.data, out.format, 0, out.release_data); mlt_audio_get_values(&out, audio, frequency, format, samples, channels); mlt_properties_set_int(MLT_FRAME_PROPERTIES(frame), "audio_frequency", *frequency); mlt_properties_set_int(MLT_FRAME_PROPERTIES(frame), "audio_channels", *channels); mlt_properties_set_int(MLT_FRAME_PROPERTIES(frame), "audio_samples", *samples); mlt_properties_set_int(MLT_FRAME_PROPERTIES(frame), "audio_format", *format); // Apply pitch compensation if requested int pitch_compensate = mlt_properties_get_int(MLT_LINK_PROPERTIES(self), "pitch"); if (pitch_compensate) { if (!pdata->pitch_filter) { pdata->pitch_filter = mlt_factory_filter(mlt_service_profile(MLT_LINK_SERVICE(self)), "rbpitch", NULL); } if (pdata->pitch_filter) { mlt_properties_set_int(MLT_FILTER_PROPERTIES(pdata->pitch_filter), "stretch", 1); // Set the pitchscale to compensate for the difference between the input sampling frequency and the requested sampling frequency. double pitchscale = (double) in_frequency / (double) requested_frequency; mlt_properties_set_double(MLT_FILTER_PROPERTIES(pdata->pitch_filter), "pitchscale", pitchscale); mlt_filter_process(pdata->pitch_filter, frame); } } // Apply a resampler if rbpitch is not stretching to provide if (!pitch_compensate || !pdata->pitch_filter) { if (!pdata->resample_filter) { pdata->resample_filter = mlt_factory_filter(mlt_service_profile(MLT_LINK_SERVICE(self)), "resample", NULL); if (!pdata->resample_filter) { pdata->resample_filter = mlt_factory_filter(mlt_service_profile( MLT_LINK_SERVICE(self)), "swresample", NULL); } } if (pdata->resample_filter) { mlt_filter_process(pdata->resample_filter, frame); } } // Final call to get_audio() to apply the pitch or resample filter *frequency = requested_frequency; *samples = requested_samples; int error = mlt_frame_get_audio(frame, audio, format, frequency, channels, samples); return error; } static int link_get_image_blend(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { static const int MAX_BLEND_IMAGES = 10; mlt_link self = (mlt_link) mlt_frame_pop_get_image(frame); mlt_properties unique_properties = mlt_frame_get_unique_properties(frame, MLT_LINK_SERVICE(self)); if (!unique_properties) { return 1; } int requested_width = *width; int requested_height = *height; double source_time = mlt_properties_get_double(unique_properties, "source_time"); double source_fps = mlt_properties_get_double(unique_properties, "source_fps"); if (*format == mlt_image_movit) { *format = mlt_image_rgba; } // Get pointers to all the images for this frame uint8_t *images[MAX_BLEND_IMAGES]; int image_count = 0; mlt_position in_frame_pos = floor(source_time * source_fps); int colorspace = 0; while (image_count < MAX_BLEND_IMAGES) { char key[19]; sprintf(key, "%d", in_frame_pos); mlt_frame src_frame = (mlt_frame) mlt_properties_get_data(unique_properties, key, NULL); if (!src_frame) { break; } mlt_service_lock(MLT_LINK_SERVICE(self)); int error = mlt_frame_get_image(src_frame, &images[image_count], format, &requested_width, &requested_height, 0); mlt_service_unlock(MLT_LINK_SERVICE(self)); if (error) { mlt_log_error(MLT_LINK_SERVICE(self), "Failed to get image %s\n", key); break; } if (*width != requested_width || *height != requested_height) { mlt_log_error(MLT_LINK_SERVICE(self), "Dimension Mismatch (%s): %dx%d != %dx%d\n", key, requested_width, requested_height, *width, *height); break; } colorspace = mlt_properties_get_int(MLT_FRAME_PROPERTIES(src_frame), "colorspace"); in_frame_pos++; image_count++; } if (image_count <= 0) { mlt_log_error(MLT_LINK_SERVICE(self), "No images to blend\n"); return 1; } // Sum all the images into one image with 16 bit components int size = mlt_image_format_size(*format, *width, *height, NULL); *image = mlt_pool_alloc(size); int s = 0; uint8_t *p = *image; for (s = 0; s < size; s++) { int16_t sum = 0; int i = 0; for (i = 0; i < image_count; i++) { sum += *(images[i]); images[i]++; } *p = sum / image_count; p++; } mlt_frame_set_image(frame, *image, size, mlt_pool_release); mlt_properties_set_int(MLT_FRAME_PROPERTIES(frame), "format", *format); mlt_properties_set_int(MLT_FRAME_PROPERTIES(frame), "width", *width); mlt_properties_set_int(MLT_FRAME_PROPERTIES(frame), "height", *height); mlt_properties_set_int(MLT_FRAME_PROPERTIES(frame), "colorspace", colorspace); return 0; } static int link_get_image_nearest(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { mlt_link self = (mlt_link) mlt_frame_pop_get_image(frame); mlt_properties unique_properties = mlt_frame_get_unique_properties(frame, MLT_LINK_SERVICE(self)); if (!unique_properties) { return 1; } double source_time = mlt_properties_get_double(unique_properties, "source_time"); double source_fps = mlt_properties_get_double(unique_properties, "source_fps"); mlt_position in_frame_pos = floor(source_time * source_fps); char key[19]; sprintf(key, "%d", in_frame_pos); mlt_frame src_frame = (mlt_frame) mlt_properties_get_data(unique_properties, key, NULL); if (src_frame) { uint8_t *in_image; mlt_service_lock(MLT_LINK_SERVICE(self)); mlt_properties_pass_list(MLT_FRAME_PROPERTIES(src_frame), MLT_FRAME_PROPERTIES(frame), "crop.left crop.right crop.top crop.bottom crop.original_width " "crop.original_height meta.media.width meta.media.height"); if (*format == mlt_image_movit) { *format = mlt_image_rgba; } int error = mlt_frame_get_image(src_frame, &in_image, format, width, height, 0); mlt_service_unlock(MLT_LINK_SERVICE(self)); if (!error) { int size = mlt_image_format_size(*format, *width, *height, NULL); *image = mlt_pool_alloc(size); memcpy(*image, in_image, size); mlt_frame_set_image(frame, *image, size, mlt_pool_release); mlt_properties_set_int(MLT_FRAME_PROPERTIES(frame), "format", *format); mlt_properties_set_int(MLT_FRAME_PROPERTIES(frame), "width", *width); mlt_properties_set_int(MLT_FRAME_PROPERTIES(frame), "height", *height); mlt_properties_set_int(MLT_FRAME_PROPERTIES(frame), "colorspace", mlt_properties_get_int(MLT_FRAME_PROPERTIES(src_frame), "colorspace")); uint8_t *in_alpha = mlt_frame_get_alpha(src_frame); if (in_alpha) { size = *width * *height; uint8_t *out_alpha = mlt_pool_alloc(size); memcpy(out_alpha, in_alpha, size); mlt_frame_set_alpha(frame, out_alpha, size, mlt_pool_release); }; return 0; } } return 1; } static int link_get_frame(mlt_link self, mlt_frame_ptr frame, int index) { mlt_properties properties = MLT_LINK_PROPERTIES(self); private_data *pdata = (private_data *) self->child; mlt_position position = mlt_producer_position(MLT_LINK_PRODUCER(self)); mlt_position length = mlt_producer_get_length(MLT_LINK_PRODUCER(self)); double source_time = 0.0; double source_duration = 0.0; double source_fps = mlt_producer_get_fps(self->next); double link_fps = mlt_producer_get_fps(MLT_LINK_PRODUCER(self)); // Assume that the user wants normal speed before the in point. mlt_position in = mlt_producer_get_in(MLT_LINK_PRODUCER(self)); double in_time = (double) in / link_fps; int result = 0; // Create a frame *frame = mlt_frame_init(MLT_LINK_SERVICE(self)); mlt_frame_set_position(*frame, mlt_producer_position(MLT_LINK_PRODUCER(self))); mlt_properties unique_properties = mlt_frame_unique_properties(*frame, MLT_LINK_SERVICE(self)); // Calculate the frames from the next link to be used if (mlt_properties_exists(properties, "speed_map")) { // Speed mapping source_time = integrate_source_time(self, position) + in_time; double next_source_time = integrate_source_time(self, position + 1) + in_time; source_duration = next_source_time - source_time; } else if (mlt_properties_exists(properties, "time_map")) { source_time = mlt_properties_anim_get_double(properties, "time_map", position - in, length) + in_time; double next_source_time = mlt_properties_anim_get_double(properties, "time_map", position - in + 1, length) + in_time; source_duration = next_source_time - source_time; } else { // No mapping specified. Assume 1.0 speed. // This can be used as a frame rate converter. source_time = (double) position / link_fps; source_duration = 1.0 / link_fps; } double frame_duration = 1.0 / link_fps; double source_speed = 0.0; if (source_duration != 0.0) { source_speed = source_duration / frame_duration; } mlt_properties_set_double(unique_properties, "source_fps", source_fps); mlt_properties_set_double(unique_properties, "source_time", source_time); mlt_properties_set_double(unique_properties, "source_duration", source_duration); mlt_properties_set_double(unique_properties, "source_speed", source_speed); mlt_log_debug(MLT_LINK_SERVICE(self), "Get Frame: %f -> %f\t%d\t%d\n", source_fps, link_fps, position, mlt_producer_get_in(MLT_LINK_PRODUCER(self))); // Get frames from the next link and pass them along with the new frame int in_frame_count = 0; mlt_frame src_frame = NULL; mlt_position prev_frame_position = pdata->prev_frame ? mlt_frame_get_position(pdata->prev_frame) : -1; mlt_position in_frame_pos = floor(source_time * source_fps); double frame_time = (double) in_frame_pos / source_fps; double source_end_time = source_time + fabs(source_duration); if (frame_time == source_end_time) { // Force one frame to be sent. source_end_time += 0.0000000001; } while (frame_time < source_end_time) { if (in_frame_pos == prev_frame_position) { // Reuse the previous frame to avoid seeking. src_frame = pdata->prev_frame; mlt_properties_inc_ref(MLT_FRAME_PROPERTIES(src_frame)); } else { mlt_producer_seek(self->next, in_frame_pos); result = mlt_service_get_frame(MLT_PRODUCER_SERVICE(self->next), &src_frame, index); if (result) { break; } } // Save the source frame on the output frame char key[19]; sprintf(key, "%d", in_frame_pos); mlt_properties_set_data(unique_properties, key, src_frame, 0, (mlt_destructor) mlt_frame_close, NULL); // Calculate the next frame in_frame_pos++; frame_time = (double) in_frame_pos / source_fps; in_frame_count++; } if (!src_frame) { mlt_frame_close(*frame); *frame = NULL; return 1; } // Copy some useful properties from one of the source frames. (*frame)->convert_image = src_frame->convert_image; (*frame)->convert_audio = src_frame->convert_audio; mlt_filter cpu_csc = (mlt_filter) mlt_properties_get_data(MLT_FRAME_PROPERTIES(src_frame), "_movit cpu_convert", NULL); if (cpu_csc) { mlt_properties_inc_ref(MLT_FILTER_PROPERTIES(cpu_csc)); mlt_properties_set_data(MLT_FRAME_PROPERTIES(*frame), "_movit cpu_convert", cpu_csc, 0, (mlt_destructor) mlt_filter_close, NULL); } mlt_properties_pass_list(MLT_FRAME_PROPERTIES(*frame), MLT_FRAME_PROPERTIES(src_frame), "audio_frequency"); mlt_properties_set_data(MLT_FRAME_PROPERTIES(*frame), "_producer", mlt_frame_get_original_producer(src_frame), 0, NULL, NULL); if (src_frame != pdata->prev_frame) { // Save the last source frame because it might be requested for the next frame. mlt_frame_close(pdata->prev_frame); mlt_properties_inc_ref(MLT_FRAME_PROPERTIES(src_frame)); pdata->prev_frame = src_frame; } // Setup callbacks char *mode = mlt_properties_get(properties, "image_mode"); mlt_frame_push_get_image(*frame, (void *) self); if (in_frame_count == 1 || !mode || !strcmp(mode, "nearest")) { mlt_frame_push_get_image(*frame, link_get_image_nearest); } else { mlt_frame_push_get_image(*frame, link_get_image_blend); } mlt_frame_push_audio(*frame, (void *) self); mlt_frame_push_audio(*frame, link_get_audio); mlt_producer_prepare_next(MLT_LINK_PRODUCER(self)); mlt_properties_set_double(properties, "speed", source_speed); return result; } static void link_close(mlt_link self) { if (self) { private_data *pdata = (private_data *) self->child; if (pdata) { mlt_frame_close(pdata->prev_frame); mlt_filter_close(pdata->resample_filter); mlt_filter_close(pdata->pitch_filter); free(pdata); } self->close = NULL; mlt_link_close(self); free(self); } } mlt_link link_timeremap_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_link self = mlt_link_init(); private_data *pdata = (private_data *) calloc(1, sizeof(private_data)); if (self && pdata) { self->child = pdata; // Callback registration self->configure = link_configure; self->get_frame = link_get_frame; self->close = link_close; // Signal that this link performs frame rate conversion mlt_properties_set_int(MLT_LINK_PROPERTIES(self), "_frc", 1); mlt_events_listen(MLT_LINK_PROPERTIES(self), self, "property-changed", (mlt_listener) property_changed); } else { free(pdata); mlt_link_close(self); self = NULL; } return self; } mlt-7.22.0/src/modules/core/link_timeremap.yml000664 000000 000000 00000002130 14531534050 021264 0ustar00rootroot000000 000000 schema_version: 7.0 type: link identifier: timeremap title: Time Remap version: 2 copyright: Meltytech, LLC license: LGPLv2.1 language: en tags: - Audio - Video description: Remap frames in time. parameters: - identifier: time_map title: Time Map type: float description: > A map of input frame times to output frame times. Ignore if speed_map is set. mutable: yes animation: yes - identifier: speed_map title: Speed Map type: float description: > A map of input speed to output frame times. Overrides time_map mutable: yes animation: yes - identifier: map (*deprecated*) description: > This parameter is deprecated. Use time_map instead. - identifier: image_mode title: Image Mode type: string description: The image mode to use. values: - nearest # Output the nearest frame - blend # Blend the frames that make up the output mutable: yes - identifier: speed title: Speed type: float description: The instantaneous speed of the last frame that was processed. readonly: yes mlt-7.22.0/src/modules/core/loader.dict000664 000000 000000 00000002004 14531534050 017654 0ustar00rootroot000000 000000 http://*=avformat,webvfx:plain: https://*=webvfx:plain: plain:http://*=webvfx:plain: plain:https://*=webvfx:plain: This is just like the loader producer except it does not add normalizing filters. parameters: - identifier: resource title: File/URL type: string description: The file for the producer to be based on. argument: yes required: yes readonly: yes widget: fileopen mlt-7.22.0/src/modules/core/producer_blank.c000664 000000 000000 00000004343 14531534050 020707 0ustar00rootroot000000 000000 /* * producer_blank.c * Copyright (C) 2023 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include static int producer_get_frame(mlt_producer self, mlt_frame_ptr frame, int index) { // Generate a frame *frame = mlt_frame_init(MLT_PRODUCER_SERVICE(self)); if (*frame) { mlt_properties frame_properties = MLT_FRAME_PROPERTIES(*frame); mlt_frame_set_position(*frame, mlt_producer_position(self)); // Set producer-specific frame properties mlt_properties_set_int(frame_properties, "progressive", 1); } // Calculate the next timecode mlt_producer_prepare_next(self); return 0; } static void producer_close(mlt_producer producer) { producer->close = NULL; mlt_producer_close(producer); free(producer); } mlt_producer producer_blank_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_producer self = calloc(1, sizeof(struct mlt_producer_s)); if (self && !mlt_producer_init(self, NULL)) { mlt_properties_set(MLT_PRODUCER_PROPERTIES(self), "mlt_service", "blank"); mlt_properties_set(MLT_PRODUCER_PROPERTIES(self), "resource", "blank"); // Callback registration self->get_frame = producer_get_frame; self->close = (mlt_destructor) producer_close; } else { free(self); self = NULL; } return self; } mlt-7.22.0/src/modules/core/producer_blank.yml000664 000000 000000 00000000555 14531534050 021267 0ustar00rootroot000000 000000 schema_version: 0.3 type: producer identifier: blank title: Blank version: 1 copyright: Meltytech, LLC license: LGPLv2.1 language: en tags: - Video - Audio description: > A "Blank" producer returns frames that are white and silent. These frames are suitable for creating blank spaces in a playlist. mlt_producer_is_blank() will return 1 on this producer. mlt-7.22.0/src/modules/core/producer_colour.c000664 000000 000000 00000025427 14531534050 021131 0ustar00rootroot000000 000000 /* * producer_colour.c * Copyright (C) 2003-2020 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include static int producer_get_frame(mlt_producer parent, mlt_frame_ptr frame, int index); static void producer_close(mlt_producer parent); mlt_producer producer_colour_init(mlt_profile profile, mlt_service_type type, const char *id, char *colour) { mlt_producer producer = calloc(1, sizeof(struct mlt_producer_s)); if (producer != NULL && mlt_producer_init(producer, NULL) == 0) { // Get the properties interface mlt_properties properties = MLT_PRODUCER_PROPERTIES(producer); // Callback registration producer->get_frame = producer_get_frame; producer->close = (mlt_destructor) producer_close; // Set the default properties mlt_properties_set(properties, "resource", (!colour || !strcmp(colour, "")) ? "0x000000ff" : colour); mlt_properties_set(properties, "_resource", ""); mlt_properties_set_double(properties, "aspect_ratio", mlt_profile_sar(profile)); return producer; } free(producer); return NULL; } static int producer_get_image(mlt_frame frame, uint8_t **buffer, mlt_image_format *format, int *width, int *height, int writable) { // Obtain properties of frame mlt_properties properties = MLT_FRAME_PROPERTIES(frame); // Obtain the producer for this frame mlt_producer producer = mlt_frame_pop_service(frame); mlt_service_lock(MLT_PRODUCER_SERVICE(producer)); // Obtain properties of producer mlt_properties producer_props = MLT_PRODUCER_PROPERTIES(producer); // Get the current and previous colour strings char *now = mlt_properties_get(producer_props, "resource"); char *then = mlt_properties_get(producer_props, "_resource"); // Get the current image and dimensions cached in the producer int size = 0; uint8_t *image = mlt_properties_get_data(producer_props, "image", &size); int current_width = mlt_properties_get_int(producer_props, "_width"); int current_height = mlt_properties_get_int(producer_props, "_height"); mlt_image_format current_format = mlt_properties_get_int(producer_props, "_format"); // Parse the colour mlt_color color = mlt_properties_get_color(producer_props, "resource"); if (mlt_properties_get(producer_props, "mlt_image_format")) *format = mlt_image_format_id(mlt_properties_get(producer_props, "mlt_image_format")); // Choose suitable out values if nothing specific requested if (*format == mlt_image_none || *format == mlt_image_movit) *format = mlt_image_rgba; if (*width <= 0) *width = mlt_service_profile(MLT_PRODUCER_SERVICE(producer))->width; if (*height <= 0) *height = mlt_service_profile(MLT_PRODUCER_SERVICE(producer))->height; // Choose default image format if specific request is unsupported if (*format != mlt_image_yuv420p && *format != mlt_image_yuv422 && *format != mlt_image_rgb && *format != mlt_image_movit && *format != mlt_image_opengl_texture) *format = mlt_image_rgba; // See if we need to regenerate if (!now || (then && strcmp(now, then)) || *width != current_width || *height != current_height || *format != current_format) { // Color the image int i = *width * *height + 1; int bpp; // Allocate the image size = mlt_image_format_size(*format, *width, *height, &bpp); uint8_t *p = image = mlt_pool_alloc(size); // Update the producer mlt_properties_set_data(producer_props, "image", image, size, mlt_pool_release, NULL); mlt_properties_set_int(producer_props, "_width", *width); mlt_properties_set_int(producer_props, "_height", *height); mlt_properties_set_int(producer_props, "_format", *format); mlt_properties_set(producer_props, "_resource", now); mlt_service_unlock(MLT_PRODUCER_SERVICE(producer)); switch (*format) { case mlt_image_yuv420p: { int plane_size = *width * *height; uint8_t y, u, v; RGB2YUV_601_SCALED(color.r, color.g, color.b, y, u, v); memset(p + 0, y, plane_size); memset(p + plane_size, u, plane_size / 4); memset(p + plane_size + plane_size / 4, v, plane_size / 4); mlt_properties_set_int(properties, "colorspace", 601); break; } case mlt_image_yuv422: { int uneven = *width % 2; int count = (*width - uneven) / 2 + 1; uint8_t y, u, v; RGB2YUV_601_SCALED(color.r, color.g, color.b, y, u, v); i = *height + 1; while (--i) { int j = count; while (--j) { *p++ = y; *p++ = u; *p++ = y; *p++ = v; } if (uneven) { *p++ = y; *p++ = u; } } mlt_properties_set_int(properties, "colorspace", 601); break; } case mlt_image_rgb: while (--i) { *p++ = color.r; *p++ = color.g; *p++ = color.b; } break; case mlt_image_movit: case mlt_image_opengl_texture: memset(p, 0, size); break; case mlt_image_rgba: while (--i) { *p++ = color.r; *p++ = color.g; *p++ = color.b; *p++ = color.a; } break; default: mlt_log_error(MLT_PRODUCER_SERVICE(producer), "invalid image format %s\n", mlt_image_format_name(*format)); } } else { mlt_service_unlock(MLT_PRODUCER_SERVICE(producer)); } // Create the alpha channel int alpha_size = 0; uint8_t *alpha = NULL; // Initialise the alpha if (color.a < 255 || *format == mlt_image_rgba) { alpha_size = *width * *height; alpha = mlt_pool_alloc(alpha_size); if (alpha) memset(alpha, color.a, alpha_size); else alpha_size = 0; } // Clone our image if (buffer && image && size > 0) { *buffer = mlt_pool_alloc(size); memcpy(*buffer, image, size); } // Now update properties so we free the copy after mlt_frame_set_image(frame, *buffer, size, mlt_pool_release); mlt_frame_set_alpha(frame, alpha, alpha_size, mlt_pool_release); mlt_properties_set_double(properties, "aspect_ratio", mlt_properties_get_double(producer_props, "aspect_ratio")); mlt_properties_set_int(properties, "meta.media.width", *width); mlt_properties_set_int(properties, "meta.media.height", *height); return 0; } static int producer_get_frame(mlt_producer producer, mlt_frame_ptr frame, int index) { // Generate a frame *frame = mlt_frame_init(MLT_PRODUCER_SERVICE(producer)); if (*frame != NULL) { // Obtain properties of frame and producer mlt_properties properties = MLT_FRAME_PROPERTIES(*frame); // Obtain properties of producer mlt_properties producer_props = MLT_PRODUCER_PROPERTIES(producer); // Update timecode on the frame we're creating mlt_frame_set_position(*frame, mlt_producer_position(producer)); // Set producer-specific frame properties mlt_properties_set_int(properties, "progressive", 1); mlt_profile profile = mlt_service_profile(MLT_PRODUCER_SERVICE(producer)); mlt_properties_set_double(properties, "aspect_ratio", mlt_profile_sar(profile)); mlt_properties_set_int(properties, "meta.media.width", profile->width); mlt_properties_set_int(properties, "meta.media.height", profile->height); // colour is an alias for resource if (mlt_properties_get(producer_props, "colour") != NULL) mlt_properties_set(producer_props, "resource", mlt_properties_get(producer_props, "colour")); char *colorstring = mlt_properties_get(producer_props, "resource"); if (colorstring && strchr(colorstring, '/')) { colorstring = strdup(strrchr(colorstring, '/') + 1); mlt_properties_set(producer_props, "resource", colorstring); free(colorstring); } // Check if we have a predefined image format if (mlt_properties_exists(producer_props, "mlt_image_format")) { int image_format = mlt_image_format_id( mlt_properties_get(producer_props, "mlt_image_format")); mlt_properties_set_int(properties, "format", image_format); } else { mlt_color color = mlt_properties_get_color(producer_props, "resource"); // Inform framework of the default frame format for this producer mlt_properties_set_int(properties, "format", color.a < 255 ? mlt_image_rgba : mlt_image_yuv422); } // Push the get_image method mlt_frame_push_service(*frame, producer); mlt_frame_push_get_image(*frame, producer_get_image); // A hint to scalers and affine transition that this producer does not // benefit from interpolation. mlt_properties_set_int(properties, "interpolation_not_required", 1); } // Calculate the next timecode mlt_producer_prepare_next(producer); return 0; } static void producer_close(mlt_producer producer) { producer->close = NULL; mlt_producer_close(producer); free(producer); } mlt-7.22.0/src/modules/core/producer_colour.yml000664 000000 000000 00000001672 14531534050 021504 0ustar00rootroot000000 000000 schema_version: 0.3 type: producer identifier: colour title: Color version: 1 copyright: Meltytech, LLC creator: Dan Dennedy license: LGPLv2.1 language: en tags: - Video description: A simple color generator. parameters: - identifier: resource argument: yes title: Color description: > A color value is a hexadecimal representation of RGB plus alpha channel as 0xrrggbbaa. Colors can also be the words: white, black, red, green, or blue. You can also use a HTML-style color values #rrggbb or #aarrggbb. type: string required: no readonly: no default: black widget: color - identifier: mlt_image_format title: MLT image format type: string description: > Force to generate image in the specified format. The default behavior is to supply whatever was requested by the connected consumer. mutable: yes values: - yuv420p - yuv422 - rgb - rgba mlt-7.22.0/src/modules/core/producer_consumer.c000664 000000 000000 00000027347 14531534050 021464 0ustar00rootroot000000 000000 /* * producer_consumer.c -- produce as a consumer of an encapsulated producer * Copyright (C) 2008-2022 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #define CONSUMER_PROPERTIES_PREFIX "consumer." #define PRODUCER_PROPERTIES_PREFIX "producer." #include #include #include #include struct context_s { mlt_producer self; mlt_producer producer; mlt_consumer consumer; mlt_profile profile; int64_t audio_counter; mlt_position audio_position; }; typedef struct context_s *context; static int get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { context cx = mlt_frame_pop_service(frame); mlt_frame nested_frame = mlt_frame_pop_service(frame); *width = cx->profile->width; *height = cx->profile->height; int result = mlt_frame_get_image(nested_frame, image, format, width, height, writable); // Allocate the image int size = mlt_image_format_size(*format, *width, *height, NULL); uint8_t *new_image = mlt_pool_alloc(size); // Update the frame mlt_properties properties = mlt_frame_properties(frame); mlt_frame_set_image(frame, new_image, size, mlt_pool_release); memcpy(new_image, *image, size); mlt_properties_set(properties, "progressive", mlt_properties_get(MLT_FRAME_PROPERTIES(nested_frame), "progressive")); *image = new_image; // Copy the alpha channel uint8_t *alpha = mlt_frame_get_alpha_size(nested_frame, &size); if (alpha && size > 0) { new_image = mlt_pool_alloc(size); memcpy(new_image, alpha, size); mlt_frame_set_alpha(frame, new_image, size, mlt_pool_release); } return result; } static int get_audio(mlt_frame frame, void **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples) { context cx = mlt_frame_pop_audio(frame); mlt_frame nested_frame = mlt_frame_pop_audio(frame); int result = 0; // if not repeating last frame if (mlt_frame_get_position(nested_frame) != cx->audio_position) { double fps = mlt_profile_fps(cx->profile); if (mlt_producer_get_fps(cx->self) < fps) { fps = mlt_producer_get_fps(cx->self); mlt_properties_set_double(MLT_FRAME_PROPERTIES(nested_frame), "producer_consumer_fps", fps); } *samples = mlt_audio_calculate_frame_samples(fps, *frequency, cx->audio_counter++); result = mlt_frame_get_audio(nested_frame, buffer, format, frequency, channels, samples); int size = mlt_audio_format_size(*format, *samples, *channels); int16_t *new_buffer = mlt_pool_alloc(size); mlt_frame_set_audio(frame, new_buffer, *format, size, mlt_pool_release); memcpy(new_buffer, *buffer, size); *buffer = new_buffer; cx->audio_position = mlt_frame_get_position(nested_frame); } else { // otherwise return no samples *samples = 0; } return result; } static void property_changed(mlt_properties owner, mlt_consumer self, mlt_event_data event_data) { mlt_properties properties = MLT_PRODUCER_PROPERTIES(self); context cx = mlt_properties_get_data(properties, "context", NULL); if (!cx) return; const char *name = mlt_event_data_to_string(event_data); if (name && name == strstr(name, CONSUMER_PROPERTIES_PREFIX)) mlt_properties_set(MLT_CONSUMER_PROPERTIES(cx->consumer), name + strlen(CONSUMER_PROPERTIES_PREFIX), mlt_properties_get(properties, name)); if (name && name == strstr(name, PRODUCER_PROPERTIES_PREFIX)) mlt_properties_set(MLT_PRODUCER_PROPERTIES(cx->producer), name + strlen(PRODUCER_PROPERTIES_PREFIX), mlt_properties_get(properties, name)); } static int get_frame(mlt_producer self, mlt_frame_ptr frame, int index) { mlt_properties properties = MLT_PRODUCER_PROPERTIES(self); context cx = mlt_properties_get_data(properties, "context", NULL); if (!cx) { // Allocate and initialize our context cx = mlt_pool_alloc(sizeof(struct context_s)); memset(cx, 0, sizeof(*cx)); mlt_properties_set_data(properties, "context", cx, 0, mlt_pool_release, NULL); cx->self = self; char *profile_name = mlt_properties_get(properties, "profile"); if (!profile_name) profile_name = mlt_properties_get(properties, "mlt_profile"); mlt_profile profile = mlt_service_profile(MLT_PRODUCER_SERVICE(self)); if (profile_name) { cx->profile = mlt_profile_init(profile_name); cx->profile->is_explicit = 1; } else { cx->profile = mlt_profile_clone(profile); cx->profile->is_explicit = 0; } // Encapsulate a real producer for the resource cx->producer = mlt_factory_producer(cx->profile, NULL, mlt_properties_get(properties, "resource")); if ((profile_name && !strcmp(profile_name, "auto")) || mlt_properties_get_int(properties, "autoprofile")) { mlt_profile_from_producer(cx->profile, cx->producer); mlt_producer_close(cx->producer); cx->producer = mlt_factory_producer(cx->profile, NULL, mlt_properties_get(properties, "resource")); } // Since we control the seeking, prevent it from seeking on its own mlt_producer_set_speed(cx->producer, 0); cx->audio_position = -1; // We will encapsulate a consumer cx->consumer = mlt_consumer_new(cx->profile); // Do not use _pass_list on real_time so that it defaults to 0 in the absence of // an explicit real_time property. mlt_properties_set_int(MLT_CONSUMER_PROPERTIES(cx->consumer), "real_time", mlt_properties_get_int(properties, "real_time")); mlt_properties_pass_list(MLT_CONSUMER_PROPERTIES(cx->consumer), properties, "buffer, prefill, deinterlacer, deinterlace_method, rescale"); mlt_properties_pass(MLT_CONSUMER_PROPERTIES(cx->consumer), properties, CONSUMER_PROPERTIES_PREFIX); mlt_properties_pass(MLT_PRODUCER_PROPERTIES(cx->producer), properties, PRODUCER_PROPERTIES_PREFIX); mlt_events_listen(properties, self, "property-changed", (mlt_listener) property_changed); // Connect it all together mlt_consumer_connect(cx->consumer, MLT_PRODUCER_SERVICE(cx->producer)); mlt_consumer_start(cx->consumer); } // Generate a frame *frame = mlt_frame_init(MLT_PRODUCER_SERVICE(self)); if (*frame) { // Seek the producer to the correct place // Calculate our positions double actual_position = (double) mlt_producer_frame(self); if (mlt_producer_get_speed(self) != 0) actual_position *= mlt_producer_get_speed(self); mlt_position need_first = floor(actual_position); mlt_producer_seek(cx->producer, lrint(need_first * mlt_profile_fps(cx->profile) / mlt_producer_get_fps(self))); // Get the nested frame mlt_frame nested_frame = mlt_consumer_rt_frame(cx->consumer); // Stack the producer and our methods on the nested frame mlt_frame_push_service(*frame, nested_frame); mlt_frame_push_service(*frame, cx); mlt_frame_push_get_image(*frame, get_image); mlt_frame_push_audio(*frame, nested_frame); mlt_frame_push_audio(*frame, cx); mlt_frame_push_audio(*frame, get_audio); // Give the returned frame temporal identity mlt_frame_set_position(*frame, mlt_producer_position(self)); // Store the nested frame on the produced frame for destruction mlt_properties frame_props = MLT_FRAME_PROPERTIES(*frame); mlt_properties_set_data(frame_props, "_producer_consumer.frame", nested_frame, 0, (mlt_destructor) mlt_frame_close, NULL); // Inform the normalizers about our video properties mlt_properties_set_double(frame_props, "aspect_ratio", mlt_profile_sar(cx->profile)); mlt_properties_set_int(frame_props, "width", cx->profile->width); mlt_properties_set_int(frame_props, "height", cx->profile->height); mlt_properties_set_int(frame_props, "meta.media.width", cx->profile->width); mlt_properties_set_int(frame_props, "meta.media.height", cx->profile->height); mlt_properties_set_int(frame_props, "progressive", cx->profile->progressive); } // Calculate the next timecode mlt_producer_prepare_next(self); return 0; } static void producer_close(mlt_producer self) { context cx = mlt_properties_get_data(MLT_PRODUCER_PROPERTIES(self), "context", NULL); // Shut down all the encapsulated services if (cx) { mlt_consumer_stop(cx->consumer); mlt_consumer_close(cx->consumer); mlt_producer_close(cx->producer); mlt_profile_close(cx->profile); } self->close = NULL; mlt_producer_close(self); free(self); } mlt_producer producer_consumer_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_producer self = mlt_producer_new(profile); // Encapsulate the real producer mlt_profile temp_profile = mlt_profile_clone(profile); temp_profile->is_explicit = 0; mlt_producer real_producer = mlt_factory_producer(temp_profile, NULL, arg); if (self && real_producer) { // Override some producer methods self->close = (mlt_destructor) producer_close; self->get_frame = get_frame; // Get the properties of this producer mlt_properties properties = MLT_PRODUCER_PROPERTIES(self); mlt_properties_set(properties, "resource", arg); mlt_properties_pass_list(properties, MLT_PRODUCER_PROPERTIES(real_producer), "out, length"); // Done with the producer - will re-open later when we have the profile property mlt_producer_close(real_producer); } else { if (self) mlt_producer_close(self); if (real_producer) mlt_producer_close(real_producer); self = NULL; } mlt_profile_close(temp_profile); return self; } mlt-7.22.0/src/modules/core/producer_consumer.yml000664 000000 000000 00000002301 14531534050 022022 0ustar00rootroot000000 000000 schema_version: 7.0 type: producer identifier: consumer title: Consumer as Producer version: 2 copyright: Meltytech, LLC creator: Dan Dennedy license: LGPLv2.1 language: en tags: - Audio - Video parameters: - identifier: resource argument: yes title: File/URL type: string description: A file name, URL, or producer name. required: yes - identifier: profile title: Profile type: string description: > The name of a MLT profile with which to load the resource. This defaults to the composition's profile, but could be overridden by the profile in MLT XML. Also, the value "auto" triggers profile detection. - identifier: autoprofile title: Auto-profile type: integer description: Generate a new, custom profile from the encapsulated resource. minimum: 0 maximum: 1 default: 0 - identifier: producer.* title: Producer properties description: A property and its value to apply to the encapsulated producer. type: properties mutable: yes - identifier: consumer.* title: Consumer properties description: A property and its value to apply to the encapsulated consumer. type: properties mutable: yes mlt-7.22.0/src/modules/core/producer_hold.c000664 000000 000000 00000017256 14531534050 020555 0ustar00rootroot000000 000000 /* * producer_hold.c -- frame holding producer * Copyright (C) 2003-2014 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include // Forward references static int producer_get_frame(mlt_producer producer, mlt_frame_ptr frame, int index); static void producer_close(mlt_producer producer); /** Constructor for the frame holding producer. Basically, all this producer does is provide a producer wrapper for the requested producer, allows the specification of the frame required and will then repeatedly obtain that frame for each get_frame and get_image requested. */ mlt_producer producer_hold_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { // Construct a new holding producer mlt_producer self = mlt_producer_new(profile); // Construct the requested producer via loader mlt_producer producer = mlt_factory_producer(profile, NULL, arg); // Initialise the frame holding capabilities if (self != NULL && producer != NULL) { // Get the properties of this producer mlt_properties properties = MLT_PRODUCER_PROPERTIES(self); // Store the producer mlt_properties_set_data(properties, "producer", producer, 0, (mlt_destructor) mlt_producer_close, NULL); // Set frame, in, out and length for this producer mlt_properties_set_position(properties, "frame", 0); mlt_properties_set_position(properties, "out", 25); mlt_properties_set(properties, "resource", arg); mlt_properties_set(properties, "method", "onefield"); // Override the get_frame method self->get_frame = producer_get_frame; self->close = (mlt_destructor) producer_close; } else { // Clean up (not sure which one failed, can't be bothered to find out, so close both) if (self) mlt_producer_close(self); if (producer) mlt_producer_close(producer); // Make sure we return NULL self = NULL; } // Return this producer return self; } static int producer_get_image(mlt_frame frame, uint8_t **buffer, mlt_image_format *format, int *width, int *height, int writable) { // Get the properties of the frame mlt_properties properties = MLT_FRAME_PROPERTIES(frame); // Obtain the real frame mlt_frame real_frame = mlt_frame_pop_service(frame); // Get the image from the real frame int size = 0; *buffer = mlt_properties_get_data(MLT_FRAME_PROPERTIES(real_frame), "image", &size); *width = mlt_properties_get_int(MLT_FRAME_PROPERTIES(real_frame), "width"); *height = mlt_properties_get_int(MLT_FRAME_PROPERTIES(real_frame), "height"); // If this is the first time, get it from the producer if (*buffer == NULL) { mlt_properties_pass(MLT_FRAME_PROPERTIES(real_frame), properties, ""); // We'll deinterlace on the downstream deinterlacer mlt_properties_set_int(MLT_FRAME_PROPERTIES(real_frame), "consumer.progressive", 1); // We want distorted to ensure we don't hit the resize filter twice mlt_properties_set_int(MLT_FRAME_PROPERTIES(real_frame), "distort", 1); // Get the image mlt_frame_get_image(real_frame, buffer, format, width, height, writable); // Make sure we get the size *buffer = mlt_properties_get_data(MLT_FRAME_PROPERTIES(real_frame), "image", &size); } mlt_properties_pass(properties, MLT_FRAME_PROPERTIES(real_frame), ""); // Set the values obtained on the frame if (*buffer != NULL) { uint8_t *image = mlt_pool_alloc(size); memcpy(image, *buffer, size); *buffer = image; mlt_frame_set_image(frame, *buffer, size, mlt_pool_release); } else { // Pass the current image as is mlt_frame_set_image(frame, *buffer, size, NULL); } // Make sure that no further scaling is done mlt_properties_set(properties, "consumer.rescale", "none"); mlt_properties_set(properties, "scale", "off"); // All done return 0; } static int producer_get_frame(mlt_producer producer, mlt_frame_ptr frame, int index) { // Get the properties of this producer mlt_properties properties = MLT_PRODUCER_PROPERTIES(producer); // Construct a new frame *frame = mlt_frame_init(MLT_PRODUCER_SERVICE(producer)); // If we have a frame, then stack the producer itself and the get_image method if (*frame) { // Define the real frame mlt_frame real_frame = mlt_properties_get_data(properties, "real_frame", NULL); // Obtain real frame if we don't have it if (real_frame == NULL) { // Get the producer mlt_producer producer = mlt_properties_get_data(properties, "producer", NULL); // Get the frame position requested mlt_position position = mlt_properties_get_position(properties, "frame"); // Seek the producer to the correct place mlt_producer_seek(producer, position); // Get the real frame mlt_service_get_frame(MLT_PRODUCER_SERVICE(producer), &real_frame, index); // Ensure that the real frame gets wiped eventually mlt_properties_set_data(properties, "real_frame", real_frame, 0, (mlt_destructor) mlt_frame_close, NULL); } else { // Temporary fix - ensure that we aren't seen as a test frame uint8_t *image = mlt_properties_get_data(MLT_FRAME_PROPERTIES(real_frame), "image", NULL); mlt_frame_set_image(*frame, image, 0, NULL); mlt_properties_set_int(MLT_FRAME_PROPERTIES(*frame), "test_image", 0); } // Stack the real frame and method mlt_frame_push_service(*frame, real_frame); mlt_frame_push_service(*frame, producer_get_image); // Ensure that the consumer sees what the real frame has mlt_properties_pass(MLT_FRAME_PROPERTIES(*frame), MLT_FRAME_PROPERTIES(real_frame), ""); mlt_properties_set(MLT_FRAME_PROPERTIES(real_frame), "consumer.deinterlacer", mlt_properties_get(properties, "method")); } // Move to the next position mlt_producer_prepare_next(producer); return 0; } static void producer_close(mlt_producer producer) { producer->close = NULL; mlt_producer_close(producer); free(producer); } mlt-7.22.0/src/modules/core/producer_hold.yml000664 000000 000000 00000001100 14531534050 021111 0ustar00rootroot000000 000000 schema_version: 0.2 type: producer identifier: hold title: Still version: 1 copyright: Meltytech, LLC creator: Charles Yates license: LGPLv2.1 language: en tags: - Video description: Repeat the same frame for the entirety of the clip. parameters: - identifier: resource argument: yes type: string title: File/URL description: A file name specification, URL, or producer name:argument. required: yes - identifier: frame type: integer title: Frame number description: The frame number of the frame to repeat default: 0 minimum: 0 mlt-7.22.0/src/modules/core/producer_loader-nogl.yml000664 000000 000000 00000001020 14531534050 022367 0ustar00rootroot000000 000000 schema_version: 7.0 type: filter identifier: loader-nogl title: Loader Without OpenGL Normalization Filters version: 1 copyright: Meltytech, LLC license: LGPLv2.1 language: en tags: - Audio - Video - Hidden description: > This is just like the loader producer except it does not add OpenGL normalizing filters. parameters: - identifier: resource title: File/URL type: string description: The file for the producer to be based on. argument: yes required: yes readonly: yes widget: fileopen mlt-7.22.0/src/modules/core/producer_loader.c000664 000000 000000 00000024452 14531534050 021071 0ustar00rootroot000000 000000 /* * producer_loader.c -- auto-load producer by file name extension * Copyright (C) 2003-2023 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include #include static mlt_properties dictionary = NULL; static mlt_properties normalizers = NULL; static mlt_producer create_from(mlt_profile profile, char *file, char *services) { mlt_producer producer = NULL; char *temp = strdup(services); char *service = temp; do { char *p = strchr(service, ','); if (p != NULL) *p++ = '\0'; // If the service name has a colon as field delimiter, then treat the // second field as a prefix for the file/url. char *prefix = strchr(service, ':'); if (prefix) { *prefix++ = '\0'; char *prefix_file = calloc(1, strlen(file) + strlen(prefix) + 1); strcpy(prefix_file, prefix); strcat(prefix_file, file); producer = mlt_factory_producer(profile, service, prefix_file); free(prefix_file); } else { producer = mlt_factory_producer(profile, service, file); } service = p; } while (producer == NULL && service != NULL); free(temp); return producer; } static mlt_producer create_producer(mlt_profile profile, char *file) { mlt_producer result = NULL; // 1st Line - check for service:resource handling // And ignore drive letters on Win32 - no single char services supported. if (strchr(file, ':') > file + 1) { char *temp = strdup(file); char *service = temp; char *resource = strchr(temp, ':'); *resource++ = '\0'; result = mlt_factory_producer(profile, service, resource); free(temp); } // 2nd Line preferences if (result == NULL) { int i = 0; char *lookup = strdup(file); char *p = lookup; // Make backup of profile for determining if we need to use 'consumer' producer. mlt_profile backup_profile = mlt_profile_clone(profile); // We only need to load the dictionary once if (dictionary == NULL) { char temp[PATH_MAX]; snprintf(temp, sizeof(temp), "%s/core/loader.dict", mlt_environment("MLT_DATA")); dictionary = mlt_properties_load(temp); mlt_factory_register_for_clean_up(dictionary, (mlt_destructor) mlt_properties_close); } // Convert the lookup string to lower case while (*p) { *p = tolower(*p); p++; } // Chop off the query string p = strrchr(lookup, '?'); if (p && p > lookup && p[-1] == '\\') p[-1] = '\0'; // Strip file:// prefix p = lookup; if (strncmp(lookup, "file://", 7) == 0) p += 7; // Iterate through the dictionary for (i = 0; result == NULL && i < mlt_properties_count(dictionary); i++) { char *name = mlt_properties_get_name(dictionary, i); if (fnmatch(name, p, 0) == 0) result = create_from(profile, file, mlt_properties_get_value(dictionary, i)); } // Check if the producer changed the profile - xml does this. if (result && backup_profile && backup_profile->is_explicit && (profile->width != backup_profile->width || profile->height != backup_profile->height || profile->sample_aspect_num != backup_profile->sample_aspect_num || profile->sample_aspect_den != backup_profile->sample_aspect_den || profile->frame_rate_num != backup_profile->frame_rate_num || profile->frame_rate_den != backup_profile->frame_rate_den || profile->colorspace != backup_profile->colorspace)) { // Restore the original profile attributes. profile->display_aspect_den = backup_profile->display_aspect_den; profile->display_aspect_num = backup_profile->display_aspect_num; profile->frame_rate_den = backup_profile->frame_rate_den; profile->frame_rate_num = backup_profile->frame_rate_num; profile->height = backup_profile->height; profile->progressive = backup_profile->progressive; profile->sample_aspect_den = backup_profile->sample_aspect_den; profile->sample_aspect_num = backup_profile->sample_aspect_num; profile->width = backup_profile->width; profile->colorspace = backup_profile->colorspace; free(profile->description); profile->description = strdup(backup_profile->description); // Use the 'consumer' producer. mlt_producer_close(result); result = mlt_factory_producer(profile, "consumer", file); } mlt_profile_close(backup_profile); free(lookup); } // Finally, try just loading as service if (result == NULL) result = mlt_factory_producer(profile, file, NULL); return result; } static void create_filter(mlt_profile profile, mlt_producer producer, const char *effect, int *created) { mlt_filter filter = NULL; int i = 0; int exists = 0; char *id = strdup(effect); char *arg = strchr(id, ':'); if (arg != NULL) *arg++ = '\0'; for (i = 0; (filter = mlt_service_filter(MLT_PRODUCER_SERVICE(producer), i)) != NULL; i++) { // Check if this filter already exists char *filter_id = mlt_properties_get(MLT_FILTER_PROPERTIES(filter), "mlt_service"); if (filter_id && strcmp(id, filter_id) == 0) { exists = 1; *created = 1; break; } else if (mlt_properties_get_int(MLT_FILTER_PROPERTIES(filter), "_loader") == 0) { // Stop at the first non-loader filter. This will be the insertion point for the new filter. break; } } if (!exists) { filter = mlt_factory_filter(profile, id, arg); if (filter) { mlt_properties_set_int(MLT_FILTER_PROPERTIES(filter), "_loader", 1); mlt_producer_attach(producer, filter); int last_filter_index = mlt_service_filter_count(MLT_PRODUCER_SERVICE(producer)) - 1; if (i != last_filter_index) { // Move the filter to be before any non-loader filters; mlt_service_move_filter(MLT_PRODUCER_SERVICE(producer), last_filter_index, i); } mlt_filter_close(filter); *created = 1; } } free(id); } static void attach_normalizers(mlt_profile profile, mlt_producer producer, int nogl) { // Loop variable int i; // Tokeniser mlt_tokeniser tokeniser = mlt_tokeniser_init(); // We only need to load the normalizing properties once if (normalizers == NULL) { char temp[PATH_MAX]; snprintf(temp, sizeof(temp), "%s/core/loader.ini", mlt_environment("MLT_DATA")); normalizers = mlt_properties_load(temp); mlt_factory_register_for_clean_up(normalizers, (mlt_destructor) mlt_properties_close); } // Apply normalizers for (i = 0; i < mlt_properties_count(normalizers); i++) { int j = 0; int created = 0; char *value = mlt_properties_get_value(normalizers, i); mlt_tokeniser_parse_new(tokeniser, value, ","); for (j = 0; !created && j < mlt_tokeniser_count(tokeniser); j++) { const char *filter_name = mlt_tokeniser_get_string(tokeniser, j); if (!nogl || (filter_name && strncmp(filter_name, "movit.", 6))) create_filter(profile, producer, filter_name, &created); } } // Close the tokeniser mlt_tokeniser_close(tokeniser); } mlt_producer producer_loader_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { // Create the producer mlt_producer producer = NULL; mlt_properties properties = NULL; int nogl = !strcmp(id, "loader-nogl"); if (arg != NULL) producer = create_producer(profile, arg); if (producer != NULL) properties = MLT_PRODUCER_PROPERTIES(producer); // Attach filters if we have a producer and it isn't already xml'd :-) if (producer && strcmp(id, "abnormal") && strncmp(arg, "abnormal:", 9) && mlt_properties_get(properties, "xml") == NULL && mlt_properties_get(properties, "_xml") == NULL && mlt_service_identify(MLT_PRODUCER_SERVICE(producer)) != mlt_service_chain_type && mlt_properties_get(properties, "loader_normalized") == NULL) attach_normalizers(profile, producer, nogl); if (producer && mlt_service_identify(MLT_PRODUCER_SERVICE(producer)) != mlt_service_chain_type) { // Always let the image and audio be converted int created = 0; // movit.convert skips setting the frame->convert_image pointer if GLSL cannot be used. if (!nogl) create_filter(profile, producer, "movit.convert", &created); // avcolor_space and imageconvert only set frame->convert_image if it has not been set. create_filter(profile, producer, "avcolor_space", &created); if (!created) create_filter(profile, producer, "imageconvert", &created); create_filter(profile, producer, "audioconvert", &created); } // Now make sure we don't lose our identity if (properties != NULL) mlt_properties_set_int(properties, "_mlt_service_hidden", 1); // Return the producer return producer; } mlt-7.22.0/src/modules/core/producer_loader.yml000664 000000 000000 00000001426 14531534050 021444 0ustar00rootroot000000 000000 schema_version: 7.0 type: producer identifier: loader title: Loader version: 1 copyright: Meltytech, LLC creator: Charles Yates license: LGPLv2.1 language: en tags: - Audio - Video - Hidden description: > This producer has two roles: 1. it handles the mappings of all file names to the other producers; 2. it attaches normalizing filters (rescale, resize and resample) to the producers (when necessary). This producer simplifies many aspects of use. Essentially, it ensures that a consumer will receive images and audio precisely as they request them. parameters: - identifier: resource title: File/URL type: string description: The file for the producer to be based on. argument: yes required: yes readonly: yes widget: fileopen mlt-7.22.0/src/modules/core/producer_melt.c000664 000000 000000 00000057253 14531534050 020571 0ustar00rootroot000000 000000 /* * producer_melt.c -- load from melt command line syntax * Copyright (C) 2003-2019 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #define MELT_FILE_MAX_LINES (100000) #define MELT_FILE_MAX_LENGTH (2048) mlt_producer producer_melt_init(mlt_profile profile, mlt_service_type type, const char *id, char **argv); mlt_producer producer_melt_file_init(mlt_profile profile, mlt_service_type type, const char *id, char *file) { FILE *input = mlt_fopen(file, "r"); char **args = calloc(sizeof(char *), MELT_FILE_MAX_LINES); int count = 0; char temp[MELT_FILE_MAX_LENGTH]; if (input != NULL) { while (fgets(temp, MELT_FILE_MAX_LENGTH, input) && count < MELT_FILE_MAX_LINES) { if (temp[strlen(temp) - 1] != '\n') mlt_log_warning(NULL, "Exceeded maximum line length (%d) while reading a melt file.\n", MELT_FILE_MAX_LENGTH); temp[strlen(temp) - 1] = '\0'; if (strcmp(temp, "")) args[count++] = strdup(temp); } fclose(input); if (count == MELT_FILE_MAX_LINES) mlt_log_warning(NULL, "Reached the maximum number of lines (%d) while reading a melt file.\n" "Consider using MLT XML.\n", MELT_FILE_MAX_LINES); } mlt_producer result = producer_melt_init(profile, type, id, args); if (result != NULL) { mlt_properties_set(MLT_PRODUCER_PROPERTIES(result), "resource", file); mlt_properties_set_int(MLT_PRODUCER_PROPERTIES(result), "loader_normalized", 1); } while (count--) free(args[count]); free(args); return result; } static void track_service(mlt_field field, void *service, mlt_destructor destructor) { mlt_properties properties = mlt_field_properties(field); int registered = mlt_properties_get_int(properties, "registered"); char *key = mlt_properties_get(properties, "registered"); mlt_properties_set_data(properties, key, service, 0, destructor, NULL); mlt_properties_set_int(properties, "registered", ++registered); } static mlt_producer create_producer(mlt_profile profile, mlt_field field, char *file) { mlt_producer result = mlt_factory_producer(profile, NULL, file); if (result != NULL) track_service(field, result, (mlt_destructor) mlt_producer_close); return result; } static mlt_filter create_attach(mlt_profile profile, mlt_field field, char *id, int track) { char *temp = strdup(id); char *arg = strchr(temp, ':'); if (arg != NULL) *arg++ = '\0'; mlt_filter filter = mlt_factory_filter(profile, temp, arg); if (filter != NULL) track_service(field, filter, (mlt_destructor) mlt_filter_close); free(temp); return filter; } static mlt_filter create_filter(mlt_profile profile, mlt_field field, char *id, int track) { char *temp = strdup(id); char *arg = strchr(temp, ':'); if (arg != NULL) *arg++ = '\0'; mlt_filter filter = mlt_factory_filter(profile, temp, arg); if (filter != NULL) { mlt_field_plant_filter(field, filter, track); track_service(field, filter, (mlt_destructor) mlt_filter_close); } free(temp); return filter; } static mlt_transition create_transition(mlt_profile profile, mlt_field field, char *id, int track) { char *temp = strdup(id); char *arg = strchr(temp, ':'); if (arg != NULL) *arg++ = '\0'; mlt_transition transition = mlt_factory_transition(profile, temp, arg); if (transition != NULL) { mlt_field_plant_transition(field, transition, track, track + 1); track_service(field, transition, (mlt_destructor) mlt_transition_close); } free(temp); return transition; } static mlt_link create_link(mlt_field field, char *id) { char *temp = strdup(id); char *arg = strchr(temp, ':'); if (arg != NULL) *arg++ = '\0'; mlt_link link = mlt_factory_link(temp, arg); if (link != NULL) track_service(field, link, (mlt_destructor) mlt_link_close); free(temp); return link; } mlt_producer producer_melt_init(mlt_profile profile, mlt_service_type type, const char *id, char **argv) { int i; int track = 0; mlt_producer producer = NULL; mlt_producer first_producer = NULL; mlt_tractor mix = NULL; mlt_playlist playlist = mlt_playlist_new(profile); mlt_properties group = mlt_properties_new(); mlt_tractor tractor = mlt_tractor_new(); mlt_properties properties = MLT_TRACTOR_PROPERTIES(tractor); mlt_field field = mlt_tractor_field(tractor); mlt_properties field_properties = mlt_field_properties(field); mlt_multitrack multitrack = mlt_tractor_multitrack(tractor); mlt_chain chain = NULL; char *title = NULL; // Assistance for template construction (allows -track usage to specify the first track) mlt_properties_set_int(MLT_PLAYLIST_PROPERTIES(playlist), "_melt_first", 1); // We need to track the number of registered filters mlt_properties_set_int(field_properties, "registered", 0); // Parse the arguments if (argv) for (i = 0; argv[i] != NULL; i++) { if (argv[i + 1] == NULL && (!strcmp(argv[i], "-attach") || !strcmp(argv[i], "-attach-cut") || !strcmp(argv[i], "-attach-track") || !strcmp(argv[i], "-attach-clip") || !strcmp(argv[i], "-repeat") || !strcmp(argv[i], "-split") || !strcmp(argv[i], "-join") || !strcmp(argv[i], "-mixer") || !strcmp(argv[i], "-mix") || !strcmp(argv[i], "-filter") || !strcmp(argv[i], "-transition") || !strcmp(argv[i], "-blank"))) { fprintf(stderr, "Argument missing for %s.\n", argv[i]); break; } if (!strcmp(argv[i], "-group")) { if (mlt_properties_count(group) != 0) { mlt_properties_close(group); group = mlt_properties_new(); } if (group != NULL) properties = group; } else if (!strcmp(argv[i], "-attach") || !strcmp(argv[i], "-attach-cut") || !strcmp(argv[i], "-attach-track") || !strcmp(argv[i], "-attach-clip")) { int type = !strcmp(argv[i], "-attach") ? 0 : !strcmp(argv[i], "-attach-cut") ? 1 : !strcmp(argv[i], "-attach-track") ? 2 : 3; mlt_filter filter = create_attach(profile, field, argv[++i], track); if (producer != NULL && !mlt_producer_is_cut(producer)) { mlt_playlist_clip_info info; mlt_playlist_append(playlist, producer); mlt_playlist_get_clip_info(playlist, &info, mlt_playlist_count(playlist) - 1); producer = info.cut; } if (type == 1 || type == 2) { mlt_playlist_clip_info info; mlt_playlist_get_clip_info(playlist, &info, mlt_playlist_count(playlist) - 1); producer = info.cut; } if (filter != NULL && mlt_playlist_count(playlist) > 0) { if (type == 0) mlt_service_attach((mlt_service) properties, filter); else if (type == 1) mlt_service_attach((mlt_service) producer, filter); else if (type == 2) mlt_service_attach((mlt_service) playlist, filter); else if (type == 3) mlt_service_attach((mlt_service) mlt_producer_cut_parent(producer), filter); properties = MLT_FILTER_PROPERTIES(filter); mlt_properties_inherit(properties, group); } else if (filter != NULL) { mlt_service_attach((mlt_service) playlist, filter); properties = MLT_FILTER_PROPERTIES(filter); mlt_properties_inherit(properties, group); } } else if (!strcmp(argv[i], "-repeat")) { int repeat = atoi(argv[++i]); if (producer != NULL && !mlt_producer_is_cut(producer)) mlt_playlist_append(playlist, producer); producer = NULL; if (mlt_playlist_count(playlist) > 0) { mlt_playlist_clip_info info; mlt_playlist_repeat_clip(playlist, mlt_playlist_count(playlist) - 1, repeat); mlt_playlist_get_clip_info(playlist, &info, mlt_playlist_count(playlist) - 1); producer = info.cut; properties = MLT_PRODUCER_PROPERTIES(producer); } } else if (!strcmp(argv[i], "-split")) { int split = atoi(argv[++i]); if (producer != NULL && !mlt_producer_is_cut(producer)) mlt_playlist_append(playlist, producer); producer = NULL; if (mlt_playlist_count(playlist) > 0) { mlt_playlist_clip_info info; mlt_playlist_get_clip_info(playlist, &info, mlt_playlist_count(playlist) - 1); split = split < 0 ? info.frame_out + split : split; mlt_playlist_split(playlist, mlt_playlist_count(playlist) - 1, split); mlt_playlist_get_clip_info(playlist, &info, mlt_playlist_count(playlist) - 1); producer = info.cut; properties = MLT_PRODUCER_PROPERTIES(producer); } } else if (!strcmp(argv[i], "-swap")) { if (producer != NULL && !mlt_producer_is_cut(producer)) mlt_playlist_append(playlist, producer); producer = NULL; if (mlt_playlist_count(playlist) >= 2) { mlt_playlist_clip_info info; mlt_playlist_move(playlist, mlt_playlist_count(playlist) - 2, mlt_playlist_count(playlist) - 1); mlt_playlist_get_clip_info(playlist, &info, mlt_playlist_count(playlist) - 1); producer = info.cut; properties = MLT_PRODUCER_PROPERTIES(producer); } } else if (!strcmp(argv[i], "-join")) { int clips = atoi(argv[++i]); if (producer != NULL && !mlt_producer_is_cut(producer)) mlt_playlist_append(playlist, producer); producer = NULL; if (mlt_playlist_count(playlist) > 0) { mlt_playlist_clip_info info; int clip = clips <= 0 ? 0 : mlt_playlist_count(playlist) - clips - 1; if (clip < 0) clip = 0; if (clip >= mlt_playlist_count(playlist)) clip = mlt_playlist_count(playlist) - 2; if (clips < 0) clips = mlt_playlist_count(playlist) - 1; mlt_playlist_join(playlist, clip, clips, 0); mlt_playlist_get_clip_info(playlist, &info, mlt_playlist_count(playlist) - 1); producer = info.cut; properties = MLT_PRODUCER_PROPERTIES(producer); } } else if (!strcmp(argv[i], "-remove")) { if (producer != NULL && !mlt_producer_is_cut(producer)) mlt_playlist_append(playlist, producer); producer = NULL; if (mlt_playlist_count(playlist) > 0) { mlt_playlist_clip_info info; mlt_playlist_remove(playlist, mlt_playlist_count(playlist) - 1); mlt_playlist_get_clip_info(playlist, &info, mlt_playlist_count(playlist) - 1); producer = info.cut; properties = MLT_PRODUCER_PROPERTIES(producer); } } else if (!strcmp(argv[i], "-mix")) { int length = atoi(argv[++i]); if (producer != NULL && !mlt_producer_is_cut(producer)) mlt_playlist_append(playlist, producer); producer = NULL; if (mlt_playlist_count(playlist) >= 2) { if (mlt_playlist_mix(playlist, mlt_playlist_count(playlist) - 2, length, NULL) == 0) { mlt_playlist_clip_info info; mlt_playlist_get_clip_info(playlist, &info, mlt_playlist_count(playlist) - 1); if (mlt_properties_get_data((mlt_properties) info.producer, "mlt_mix", NULL) == NULL) mlt_playlist_get_clip_info(playlist, &info, mlt_playlist_count(playlist) - 2); mix = (mlt_tractor) mlt_properties_get_data((mlt_properties) info.producer, "mlt_mix", NULL); properties = NULL; } else { fprintf(stderr, "Mix failed?\n"); } } else { fprintf(stderr, "Invalid position for a mix...\n"); } } else if (!strcmp(argv[i], "-mixer")) { if (mix != NULL) { char *id = strdup(argv[++i]); char *arg = strchr(id, ':'); mlt_field field = mlt_tractor_field(mix); mlt_transition transition = NULL; if (arg != NULL) *arg++ = '\0'; transition = mlt_factory_transition(profile, id, arg); if (transition != NULL) { properties = MLT_TRANSITION_PROPERTIES(transition); mlt_properties_inherit(properties, group); mlt_field_plant_transition(field, transition, 0, 1); mlt_properties_set_position(properties, "in", 0); mlt_properties_set_position(properties, "out", mlt_producer_get_out((mlt_producer) mix)); mlt_transition_close(transition); } free(id); } else { fprintf(stderr, "Invalid mixer...\n"); } } else if (!strcmp(argv[i], "-filter")) { mlt_filter filter = create_filter(profile, field, argv[++i], track); if (filter != NULL) { properties = MLT_FILTER_PROPERTIES(filter); mlt_properties_inherit(properties, group); } } else if (!strcmp(argv[i], "-transition")) { mlt_transition transition = create_transition(profile, field, argv[++i], track - 1); if (transition != NULL) { properties = MLT_TRANSITION_PROPERTIES(transition); mlt_properties_inherit(properties, group); } } else if (!strcmp(argv[i], "-blank")) { if (producer != NULL && !mlt_producer_is_cut(producer)) mlt_playlist_append(playlist, producer); producer = NULL; if (strchr(argv[i + 1], ':')) mlt_playlist_blank_time(playlist, argv[++i]); else // support for legacy where plain int is an out point instead of length mlt_playlist_blank(playlist, atof(argv[++i])); } else if (!strcmp(argv[i], "-track") || !strcmp(argv[i], "-null-track") || !strcmp(argv[i], "-video-track") || !strcmp(argv[i], "-audio-track") || !strcmp(argv[i], "-hide-track") || !strcmp(argv[i], "-hide-video") || !strcmp(argv[i], "-hide-audio")) { if (producer != NULL && !mlt_producer_is_cut(producer)) mlt_playlist_append(playlist, producer); producer = NULL; if (!mlt_properties_get_int(MLT_PLAYLIST_PROPERTIES(playlist), "_melt_first") || mlt_producer_get_playtime(MLT_PLAYLIST_PRODUCER(playlist)) > 0) { mlt_multitrack_connect(multitrack, MLT_PLAYLIST_PRODUCER(playlist), track++); track_service(field, playlist, (mlt_destructor) mlt_playlist_close); playlist = mlt_playlist_new(profile); } if (playlist != NULL) { properties = MLT_PLAYLIST_PROPERTIES(playlist); if (!strcmp(argv[i], "-null-track") || !strcmp(argv[i], "-hide-track")) mlt_properties_set_int(properties, "hide", 3); else if (!strcmp(argv[i], "-audio-track") || !strcmp(argv[i], "-hide-video")) mlt_properties_set_int(properties, "hide", 1); else if (!strcmp(argv[i], "-video-track") || !strcmp(argv[i], "-hide-audio")) mlt_properties_set_int(properties, "hide", 2); } } else if (!strcmp(argv[i], "-chain")) { if (chain == NULL && producer != NULL && !mlt_producer_is_cut(producer)) mlt_playlist_append(playlist, producer); else if (chain != NULL && mlt_chain_get_source(chain) != NULL) { mlt_playlist_append(playlist, producer); } chain = mlt_chain_init(profile); if (chain != NULL) { producer = MLT_CHAIN_PRODUCER(chain); properties = MLT_PRODUCER_PROPERTIES(producer); mlt_properties_inherit(properties, group); track_service(field, chain, (mlt_destructor) mlt_chain_close); } } else if (!strcmp(argv[i], "-link")) { if (chain == NULL || mlt_chain_get_source(chain) == NULL) { fprintf(stderr, "A link can only be added to a chain with a producer.\n"); } else { mlt_link link = create_link(field, argv[++i]); if (link != NULL) { mlt_chain_attach(chain, link); properties = MLT_LINK_PROPERTIES(link); mlt_properties_inherit(properties, group); } } } else if (strchr(argv[i], '=') && strstr(argv[i], " strchr(argv[i], '='))) { mlt_properties_parse(properties, argv[i]); } else if (argv[i][0] != '-') { if (chain == NULL && producer != NULL && !mlt_producer_is_cut(producer)) mlt_playlist_append(playlist, producer); else if (chain != NULL && mlt_chain_get_source(chain) != NULL) { mlt_playlist_append(playlist, producer); chain = NULL; } if (title == NULL && strstr(argv[i], " strchr(argv[i], '='))) { i++; backtrack = 1; } if (backtrack) i--; } } // Connect last producer to playlist if (producer != NULL && !mlt_producer_is_cut(producer)) mlt_playlist_append(playlist, producer); // Track the last playlist too track_service(field, playlist, (mlt_destructor) mlt_playlist_close); // We must have a playlist to connect if (playlist && (!mlt_properties_get_int(MLT_PLAYLIST_PROPERTIES(playlist), "_melt_first") || mlt_producer_get_playtime(MLT_PLAYLIST_PRODUCER(playlist)) > 0)) mlt_multitrack_connect(multitrack, MLT_PLAYLIST_PRODUCER(playlist), track); mlt_producer prod = MLT_TRACTOR_PRODUCER(tractor); mlt_producer_optimise(prod); mlt_properties props = MLT_TRACTOR_PROPERTIES(tractor); mlt_properties_set_data(props, "group", group, 0, (mlt_destructor) mlt_properties_close, NULL); mlt_properties_set_data(props, "first_producer", first_producer, 0, NULL, NULL); mlt_properties_set_position(props, "length", mlt_producer_get_out(MLT_MULTITRACK_PRODUCER(multitrack)) + 1); mlt_producer_set_in_and_out(prod, 0, mlt_producer_get_out(MLT_MULTITRACK_PRODUCER(multitrack))); if (title != NULL) mlt_properties_set(props, "title", strchr(title, '/') ? strrchr(title, '/') + 1 : title); // If the last producer has a consumer property connect it tractor if (producer) { mlt_consumer consumer = mlt_properties_get_data(MLT_PRODUCER_PROPERTIES(producer), "consumer", NULL); if (consumer) mlt_consumer_connect(consumer, MLT_PRODUCER_SERVICE(prod)); } return prod; } mlt-7.22.0/src/modules/core/producer_melt.yml000664 000000 000000 00000000335 14531534050 021135 0ustar00rootroot000000 000000 schema_version: 0.1 type: producer identifier: melt title: Melt description: Process melt command line. version: 1 copyright: Meltytech, LLC creator: Charles Yates license: LGPLv2.1 language: en tags: - Audio - Video mlt-7.22.0/src/modules/core/producer_melt_file.yml000664 000000 000000 00000000733 14531534050 022136 0ustar00rootroot000000 000000 schema_version: 0.2 type: producer identifier: melt_file title: Melt description: Process file containing melt command line syntax. version: 1 copyright: Meltytech, LLC creator: Charles Yates license: LGPLv2.1 language: en tags: - Audio - Video notes: Limited to files with 100000 or less lines and maximum line size of 2048 bytes. parameters: - identifier: resource argument: yes title: File description: The name of the melt text file. required: yes mlt-7.22.0/src/modules/core/producer_noise.c000664 000000 000000 00000014546 14531534050 020743 0ustar00rootroot000000 000000 /* * producer_noise.c -- noise generating producer * Copyright (C) 2003-2014 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include /** Random number generator */ typedef struct { unsigned int x; unsigned int y; } rand_seed; static void init_seed(rand_seed *seed, int init) { // Use the initial value to initialize the seed to arbitrary values. // This causes the algorithm to produce consistent results each time for the same frame number. seed->x = 521288629 + init - (init << 16); seed->y = 362436069 - init + (init << 16); } static inline unsigned int fast_rand(rand_seed *seed) { static unsigned int a = 18000, b = 30903; seed->x = a * (seed->x & 65535) + (seed->x >> 16); seed->y = b * (seed->y & 65535) + (seed->y >> 16); return ((seed->x << 16) + (seed->y & 65535)); } // Forward declarations static int producer_get_frame(mlt_producer producer, mlt_frame_ptr frame, int index); static void producer_close(mlt_producer producer); /** Initialise. */ mlt_producer producer_noise_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { // Create a new producer object mlt_producer producer = mlt_producer_new(profile); // Initialise the producer if (producer != NULL) { // Callback registration producer->get_frame = producer_get_frame; producer->close = (mlt_destructor) producer_close; } return producer; } static int producer_get_image(mlt_frame frame, uint8_t **buffer, mlt_image_format *format, int *width, int *height, int writable) { // Choose suitable out values if nothing specific requested if (*width <= 0) *width = mlt_service_profile(MLT_PRODUCER_SERVICE(mlt_frame_get_original_producer(frame))) ->width; if (*height <= 0) *height = mlt_service_profile(MLT_PRODUCER_SERVICE(mlt_frame_get_original_producer(frame))) ->height; // Calculate the size of the image int size = *width * *height * 2; // Set the format being returned *format = mlt_image_yuv422; // Allocate the image *buffer = mlt_pool_alloc(size); // Update the frame mlt_frame_set_image(frame, *buffer, size, mlt_pool_release); // Before we write to the image, make sure we have one if (*buffer != NULL) { // Calculate the end of the buffer uint8_t *p = *buffer + *width * *height * 2; // Value to hold a random number uint32_t value; // Initialize seed from the frame number. rand_seed seed; init_seed(&seed, mlt_frame_get_position(frame)); // Generate random noise while (p != *buffer) { value = fast_rand(&seed) & 0xff; *(--p) = 128; *(--p) = value < 16 ? 16 : value > 240 ? 240 : value; } } return 0; } static int producer_get_audio(mlt_frame frame, int16_t **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples) { int size = 0; // Correct the returns if necessary *samples = *samples <= 0 ? 1920 : *samples; *channels = *channels <= 0 ? 2 : *channels; *frequency = *frequency <= 0 ? 48000 : *frequency; *format = mlt_audio_s16; // Calculate the size of the buffer size = *samples * *channels * sizeof(int16_t); // Allocate the buffer *buffer = mlt_pool_alloc(size); // Make sure we got one and fill it if (*buffer != NULL) { int16_t *p = *buffer + size / 2; rand_seed seed; init_seed(&seed, mlt_frame_get_position(frame)); while (p != *buffer) { int16_t val = (int16_t) fast_rand(&seed); *(--p) = val; } } // Set the buffer for destruction mlt_frame_set_audio(frame, *buffer, *format, size, mlt_pool_release); return 0; } static int producer_get_frame(mlt_producer producer, mlt_frame_ptr frame, int index) { // Generate a frame *frame = mlt_frame_init(MLT_PRODUCER_SERVICE(producer)); // Check that we created a frame and initialise it if (*frame != NULL) { // Obtain properties of frame mlt_properties properties = MLT_FRAME_PROPERTIES(*frame); // Aspect ratio is whatever it needs to be mlt_profile profile = mlt_service_profile(MLT_PRODUCER_SERVICE(producer)); mlt_properties_set_double(properties, "aspect_ratio", mlt_profile_sar(profile)); // Set producer-specific frame properties mlt_properties_set_int(properties, "progressive", 1); // Inform framework that this producer creates yuv frames by default mlt_properties_set_int(properties, "format", mlt_image_yuv422); // Update timecode on the frame we're creating mlt_frame_set_position(*frame, mlt_producer_position(producer)); // Push the get_image method mlt_frame_push_get_image(*frame, producer_get_image); // Specify the audio mlt_frame_push_audio(*frame, producer_get_audio); } // Calculate the next timecode mlt_producer_prepare_next(producer); return 0; } static void producer_close(mlt_producer producer) { producer->close = NULL; mlt_producer_close(producer); free(producer); } mlt-7.22.0/src/modules/core/producer_noise.yml000664 000000 000000 00000000331 14531534050 021305 0ustar00rootroot000000 000000 schema_version: 0.1 type: producer identifier: noise title: Noise version: 1 copyright: Meltytech, LLC creator: Charles Yates license: LGPLv2.1 language: en tags: - Audio - Video description: White noise producer mlt-7.22.0/src/modules/core/producer_timewarp.c000664 000000 000000 00000036437 14531534050 021461 0ustar00rootroot000000 000000 /* * producer_timewarp.c -- modify speed and direction of a clip * Copyright (C) 2015-2021 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include // Private Types typedef struct { int first_frame; double speed; int reverse; mlt_producer clip_producer; mlt_profile clip_profile; mlt_properties clip_parameters; mlt_filter pitch_filter; } private_data; // Private Functions static void timewarp_property_changed(mlt_service owner, mlt_producer producer, mlt_event_data event_data) { private_data *pdata = (private_data *) producer->child; const char *name = mlt_event_data_to_string(event_data); if (mlt_properties_get_int(pdata->clip_parameters, name) || !strcmp(name, "length") || !strcmp(name, "in") || !strcmp(name, "out") || !strcmp(name, "ignore_points") || !strcmp(name, "eof") || !strncmp(name, "meta.", 5)) { // Pass parameter changes from this producer to the encapsulated clip // producer. mlt_properties producer_properties = MLT_PRODUCER_PROPERTIES(producer); mlt_properties clip_properties = MLT_PRODUCER_PROPERTIES(pdata->clip_producer); mlt_events_block(clip_properties, producer); mlt_properties_pass_property(clip_properties, producer_properties, name); mlt_events_unblock(clip_properties, producer); } } static void clip_property_changed(mlt_service owner, mlt_producer producer, mlt_event_data event_data) { private_data *pdata = (private_data *) producer->child; const char *name = mlt_event_data_to_string(event_data); if (mlt_properties_get_int(pdata->clip_parameters, name) || !strcmp(name, "length") || !strcmp(name, "in") || !strcmp(name, "out") || !strcmp(name, "ignore_points") || !strcmp(name, "eof") || !strncmp(name, "meta.", 5)) { // The encapsulated clip producer might change its own parameters. // Pass those changes to this producer. mlt_properties producer_properties = MLT_PRODUCER_PROPERTIES(producer); mlt_properties clip_properties = MLT_PRODUCER_PROPERTIES(pdata->clip_producer); mlt_events_block(producer_properties, producer); mlt_properties_pass_property(producer_properties, clip_properties, name); mlt_events_unblock(producer_properties, producer); } } static int producer_probe(mlt_producer parent) { if (parent && parent->child) { private_data *pdata = (private_data *) parent->child; if (pdata->clip_producer) { return mlt_producer_probe(pdata->clip_producer); } } return 1; } static int producer_get_audio(mlt_frame frame, void **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples) { mlt_producer producer = mlt_frame_pop_audio(frame); private_data *pdata = (private_data *) producer->child; struct mlt_audio_s audio; mlt_audio_set_values(&audio, *buffer, *frequency, *format, *samples, *channels); int error = mlt_frame_get_audio(frame, &audio.data, &audio.format, &audio.frequency, &audio.channels, &audio.samples); // Scale the frequency to account for the speed change. // The resample normalizer will convert it to the requested frequency audio.frequency = (double) audio.frequency * fabs(pdata->speed); if (pdata->speed < 0.0) { mlt_audio_reverse(&audio); } mlt_audio_get_values(&audio, buffer, frequency, format, samples, channels); return error; } static int producer_get_frame(mlt_producer producer, mlt_frame_ptr frame, int index) { private_data *pdata = (private_data *) producer->child; mlt_properties producer_properties = MLT_PRODUCER_PROPERTIES(producer); if (pdata->first_frame && pdata->clip_producer) { // Pass parameters from this producer to the clip producer // Properties that are set after initialization are not always caught by // the property_changed event - so do this once after init. int n = mlt_properties_count(pdata->clip_parameters); int i = 0; mlt_properties clip_properties = MLT_PRODUCER_PROPERTIES(pdata->clip_producer); mlt_events_block(clip_properties, producer); for (i = 0; i < n; i++) { char *name = mlt_properties_get_name(pdata->clip_parameters, i); if (mlt_properties_get_int(clip_properties, name) && mlt_properties_get(producer_properties, name) && strcmp("resource", name)) { mlt_properties_pass_property(clip_properties, producer_properties, name); } } mlt_events_unblock(clip_properties, producer); pdata->first_frame = 0; } if (pdata->clip_producer) { // Seek the clip producer to the appropriate position mlt_position clip_position = mlt_producer_position(producer); if (pdata->speed < 0.0) { clip_position = mlt_properties_get_int(producer_properties, "out") - clip_position; } if (!mlt_properties_get_int(producer_properties, "ignore_points")) { clip_position += mlt_producer_get_in(producer); } mlt_producer_seek(pdata->clip_producer, clip_position); // Get the frame from the clip producer mlt_service_get_frame(MLT_PRODUCER_SERVICE(pdata->clip_producer), frame, index); // Configure callbacks if (!mlt_frame_is_test_audio(*frame)) { mlt_frame_push_audio(*frame, producer); mlt_frame_push_audio(*frame, producer_get_audio); // Pitch compensation does not work well for speed *reduction* of 1/10th or less. if (mlt_properties_get_int(producer_properties, "warp_pitch") && fabs(pdata->speed) >= 0.1) { if (!pdata->pitch_filter) { pdata->pitch_filter = mlt_factory_filter(mlt_service_profile( MLT_PRODUCER_SERVICE(producer)), "rbpitch", NULL); } if (pdata->pitch_filter) { mlt_properties_set_double(MLT_FILTER_PROPERTIES(pdata->pitch_filter), "pitchscale", 1.0 / fabs(pdata->speed)); mlt_filter_process(pdata->pitch_filter, *frame); } } } } else { *frame = mlt_frame_init(MLT_PRODUCER_SERVICE(producer)); } // Set the correct position on the frame mlt_frame_set_position(*frame, mlt_producer_position(producer)); // Calculate the next time code mlt_producer_prepare_next(producer); return 0; } static void producer_close(mlt_producer producer) { private_data *pdata = (private_data *) producer->child; if (pdata) { mlt_producer_close(pdata->clip_producer); mlt_profile_close(pdata->clip_profile); mlt_properties_close(pdata->clip_parameters); mlt_filter_close(pdata->pitch_filter); free(pdata); } producer->child = NULL; producer->close = NULL; mlt_producer_close(producer); free(producer); } mlt_producer producer_timewarp_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { // Create a new producer object mlt_producer producer = mlt_producer_new(profile); private_data *pdata = (private_data *) calloc(1, sizeof(private_data)); if (arg && producer && pdata) { double frame_rate_num_scaled; mlt_properties producer_properties = MLT_PRODUCER_PROPERTIES(producer); // Initialize the producer mlt_properties_set(producer_properties, "resource", arg); producer->child = pdata; producer->get_frame = producer_get_frame; producer->close = (mlt_destructor) producer_close; mlt_properties_set_data(producer_properties, "mlt_producer_probe", producer_probe, 0, NULL, NULL); // Get the resource to be passed to the clip producer char *resource = strchr(arg, ':'); if (resource == NULL) resource = arg; // Apparently speed was not specified. else resource++; // move past the delimiter. // Initialize private data pdata->first_frame = 1; pdata->speed = atof(arg); if (pdata->speed == 0.0) { pdata->speed = 1.0; } pdata->clip_profile = NULL; pdata->clip_parameters = NULL; pdata->clip_producer = NULL; pdata->pitch_filter = NULL; // Create a false profile to be used by the clip producer. pdata->clip_profile = mlt_profile_clone(mlt_service_profile(MLT_PRODUCER_SERVICE(producer))); // Frame rate must be recalculated for the clip profile to change the time base. if (pdata->clip_profile->frame_rate_num < 1000) { // Scale the frame rate fraction so we keep more accuracy when // the speed is factored in. pdata->clip_profile->frame_rate_num *= 1000; pdata->clip_profile->frame_rate_den *= 1000; } frame_rate_num_scaled = (double) pdata->clip_profile->frame_rate_num / fabs(pdata->speed); if (frame_rate_num_scaled > INT_MAX) // Check for overflow in case speed < 1.0 { //scale by denominator to avoid overflow. pdata->clip_profile->frame_rate_den = (double) pdata->clip_profile->frame_rate_den * fabs(pdata->speed); } else { //scale by numerator pdata->clip_profile->frame_rate_num = frame_rate_num_scaled; } // Create a producer for the clip using the false profile. pdata->clip_producer = mlt_factory_producer(pdata->clip_profile, "abnormal", resource); if (pdata->clip_producer) { mlt_properties clip_properties = MLT_PRODUCER_PROPERTIES(pdata->clip_producer); int n = 0; int i = 0; // Set the speed to 0 since we will control the seeking mlt_producer_set_speed(pdata->clip_producer, 0); // Create a list of all parameters used by the clip producer so that // they can be passed between the clip producer and this producer. pdata->clip_parameters = mlt_properties_new(); mlt_repository repository = mlt_factory_repository(); mlt_properties clip_metadata = mlt_repository_metadata(repository, mlt_service_producer_type, mlt_properties_get(clip_properties, "mlt_service")); if (clip_metadata) { mlt_properties params = (mlt_properties) mlt_properties_get_data(clip_metadata, "parameters", NULL); if (params) { n = mlt_properties_count(params); for (i = 0; i < n; i++) { mlt_properties param = (mlt_properties) mlt_properties_get_data(params, mlt_properties_get_name(params, i), NULL); char *identifier = mlt_properties_get(param, "identifier"); if (identifier) { // Set the value to 1 to indicate the parameter exists. mlt_properties_set_int(pdata->clip_parameters, identifier, 1); } } // Explicitly exclude the "resource" parameter since it needs to be different. mlt_properties_set_int(pdata->clip_parameters, "resource", 0); } } // Pass parameters and properties from the clip producer to this producer. // Some properties may have been set during initialization. n = mlt_properties_count(clip_properties); for (i = 0; i < n; i++) { char *name = mlt_properties_get_name(clip_properties, i); if (mlt_properties_get_int(pdata->clip_parameters, name) || !strcmp(name, "length") || !strcmp(name, "in") || !strcmp(name, "out") || !strncmp(name, "meta.", 5)) { mlt_properties_pass_property(producer_properties, clip_properties, name); } } // Initialize warp producer properties mlt_properties_set_double(producer_properties, "warp_speed", pdata->speed); mlt_properties_set(producer_properties, "warp_resource", mlt_properties_get(clip_properties, "resource")); // Monitor property changes from both producers so that the clip // parameters can be passed back and forth. mlt_events_listen(clip_properties, producer, "property-changed", (mlt_listener) clip_property_changed); mlt_events_listen(producer_properties, producer, "property-changed", (mlt_listener) timewarp_property_changed); } } if (!producer || !pdata || !pdata->clip_producer) { if (pdata) { mlt_producer_close(pdata->clip_producer); mlt_profile_close(pdata->clip_profile); mlt_properties_close(pdata->clip_parameters); free(pdata); } if (producer) { producer->child = NULL; producer->close = NULL; mlt_producer_close(producer); free(producer); producer = NULL; } } return producer; } mlt-7.22.0/src/modules/core/producer_timewarp.yml000664 000000 000000 00000004612 14531534050 022026 0ustar00rootroot000000 000000 schema_version: 0.3 type: producer identifier: timewarp title: Time Warp version: 1 copyright: Meltytech, LLC license: LGPLv2.1 language: en tags: - Audio - Video description: > Timewarp is a wrapper producer that allows temporal effects on an encapsulated producer. The encapsulated producer can be modified to change the speed (faster/slower) or direction (forward/reverse) In addition to the parameters listed below, this producer inherits all parameters of the encapsulated producer. The encapsulated producer parameters can be accessed directly (without any prefix) by getting and setting those parameters on the timewarp producer. In addition to modifying the speed of the video, the audio is also slowed down or sped up to match the video. parameters: - identifier: resource title: Speed and Resource type: string argument: yes description: | The speed factor and the producer resource in the form: [speed:resource] The speed can be any decimal number between 20 and 0.01. Negative speed values cause the file to be played backwards. The resource can be a file name or any producer service name. The resource will be passed to the loader to create the encapsulated producer. Examples: File opened for 2x speed forward: "2.0:example.mp4" File opened for 2x speed reverse: "-2.0:example.mp4" File opened for 1x speed reverse: "-1.0:example.mp4" File opened for 0.25x speed (slow motion) forward: "0.25:example.mp4" The most common use for this producer is to change the speed of a file. However, any arbitrary producer can be specified. E.g.: "2.0:colour:red" readonly: no required: yes mutable: no - identifier: warp_pitch title: Pitch Compensation type: boolean description: Enable or disable pitch compensation readonly: no mutable: no default: 0 - identifier: warp_speed title: Warp Speed type: float description: > A convenience parameter to access the speed that was passed as part of the argument. readonly: yes - identifier: warp_resource title: Warp Producer type: string description: > A convenience parameter to access the resource that was passed as part of the argument readonly: yes mlt-7.22.0/src/modules/core/producer_tone.c000664 000000 000000 00000010567 14531534050 020572 0ustar00rootroot000000 000000 /* * producer_tone.c -- audio tone generating producer * Copyright (C) 2014 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include static int producer_get_audio(mlt_frame frame, int16_t **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples) { mlt_producer producer = mlt_frame_pop_audio(frame); mlt_properties producer_properties = MLT_PRODUCER_PROPERTIES(producer); double fps = mlt_producer_get_fps(producer); mlt_position position = mlt_frame_get_position(frame); mlt_position length = mlt_producer_get_length(producer); // Correct the returns if necessary *format = mlt_audio_float; *frequency = *frequency <= 0 ? 48000 : *frequency; *channels = *channels <= 0 ? 2 : *channels; *samples = *samples <= 0 ? mlt_audio_calculate_frame_samples(fps, *frequency, position) : *samples; // Allocate the buffer int size = *samples * *channels * sizeof(float); *buffer = mlt_pool_alloc(size); // Fill the buffer int s = 0; int c = 0; long double first_sample = mlt_audio_calculate_samples_to_position(fps, *frequency, position); float a = mlt_properties_anim_get_double(producer_properties, "level", position, length); long double f = mlt_properties_anim_get_double(producer_properties, "frequency", position, length); long double p = mlt_properties_anim_get_double(producer_properties, "phase", position, length); p = (M_PI / 180) * p; // Convert from degrees to radians a = pow(10, a / 20.0); // Convert from dB to amplitude for (s = 0; s < *samples; s++) { long double t = (first_sample + s) / *frequency; float value = a * sin(2 * M_PI * f * t + p); for (c = 0; c < *channels; c++) { float *sample_ptr = ((float *) *buffer) + (c * *samples) + s; *sample_ptr = value; } } // Set the buffer for destruction mlt_frame_set_audio(frame, *buffer, *format, size, mlt_pool_release); return 0; } static int producer_get_frame(mlt_producer producer, mlt_frame_ptr frame, int index) { // Generate a frame *frame = mlt_frame_init(MLT_PRODUCER_SERVICE(producer)); if (*frame != NULL) { // Update time code on the frame mlt_frame_set_position(*frame, mlt_producer_position(producer)); // Configure callbacks mlt_frame_push_audio(*frame, producer); mlt_frame_push_audio(*frame, producer_get_audio); } // Calculate the next time code mlt_producer_prepare_next(producer); return 0; } static void producer_close(mlt_producer this) { this->close = NULL; mlt_producer_close(this); free(this); } mlt_producer producer_tone_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { // Create a new producer object mlt_producer producer = mlt_producer_new(profile); mlt_properties producer_properties = MLT_PRODUCER_PROPERTIES(producer); // Initialize the producer if (producer) { mlt_properties_set_double(producer_properties, "frequency", 1000.0); mlt_properties_set_double(producer_properties, "phase", 0.0); mlt_properties_set_double(producer_properties, "level", 0.0); // Callback registration producer->get_frame = producer_get_frame; producer->close = (mlt_destructor) producer_close; } return producer; } mlt-7.22.0/src/modules/core/producer_tone.yml000664 000000 000000 00000001421 14531534050 021136 0ustar00rootroot000000 000000 schema_version: 0.1 type: producer identifier: tone title: Tone version: 1 copyright: Meltytech, LLC license: LGPLv2.1 language: en tags: - Audio description: > Generate audio tones. parameters: - identifier: frequency title: Frequency type: float description: > The frequency of the tone. unit: Hz default: 1000.0 readonly: no mutable: yes widget: spinner - identifier: phase title: Phase type: float description: > The phase of the tone. unit: degrees default: 0.0 readonly: no mutable: yes widget: spinner - identifier: level title: Level type: float description: > The peak level of the tone. unit: dB default: 0.0 readonly: no mutable: yes widget: spinner mlt-7.22.0/src/modules/core/transition_composite.c000664 000000 000000 00000135136 14531534050 022176 0ustar00rootroot000000 000000 /* * transition_composite.c -- compose one image over another using alpha channel * Copyright (C) 2003-2022 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "transition_composite.h" #include #include #include #include #include #include #include #include #include typedef void (*composite_line_fn)(uint8_t *dest, uint8_t *src, int width_src, uint8_t *alpha_b, uint8_t *alpha_a, int weight, uint16_t *luma, int softness, uint32_t step); /** Geometry struct. */ struct geometry_s { mlt_rect item; int nw; // normalized width int nh; // normalized height int sw; // scaled width, not including consumer scale based upon w/nw int sh; // scaled height, not including consumer scale based upon h/nh int halign; // horizontal alignment: 0=left, 1=center, 2=right int valign; // vertical alignment: 0=top, 1=middle, 2=bottom int x_src; int y_src; }; /** Parse the alignment properties into the geometry. */ static int alignment_parse(char *align) { int ret = 0; if (align == NULL) ; else if (isdigit(align[0])) ret = atoi(align); else if (align[0] == 'c' || align[0] == 'm') ret = 1; else if (align[0] == 'r' || align[0] == 'b') ret = 2; return ret; } /** Adjust position according to scaled size and alignment properties. */ static void alignment_calculate(struct geometry_s *geometry) { geometry->item.x += (geometry->item.w - geometry->sw) * geometry->halign / 2; geometry->item.y += (geometry->item.h - geometry->sh) * geometry->valign / 2; } /** Calculate the position for this frame. */ static int position_calculate(mlt_transition self, mlt_position position) { // Get the in and out position mlt_position in = mlt_transition_get_in(self); // Now do the calcs return position - in; } /** Calculate the field delta for this frame - position between two frames. */ static int get_value(mlt_properties properties, const char *preferred, const char *fallback) { int value = mlt_properties_get_int(properties, preferred); if (value == 0) value = mlt_properties_get_int(properties, fallback); return value; } /** A smoother, non-linear threshold determination function. */ static inline int32_t smoothstep(int32_t edge1, int32_t edge2, uint32_t a) { if (a < edge1) return 0; if (a >= edge2) return 0x10000; a = ((a - edge1) << 16) / (edge2 - edge1); return (((a * a) >> 16) * ((3 << 16) - (2 * a))) >> 16; } static inline int calculate_mix( uint16_t *luma, int j, int softness, int weight, int alpha, uint32_t step) { return ((luma ? smoothstep(luma[j], luma[j] + softness, step) : weight) * (alpha + 1)) >> 8; } static inline uint8_t sample_mix(uint8_t dest, uint8_t src, int mix) { return (src * mix + dest * ((1 << 16) - mix)) >> 16; } /** Composite a source line over a destination line */ #if defined(USE_SSE) && defined(ARCH_X86_64) void composite_line_yuv_sse2_simple( uint8_t *dest, uint8_t *src, int width, uint8_t *alpha_b, uint8_t *alpha_a, int weight); #endif void composite_line_yuv(uint8_t *dest, uint8_t *src, int width, uint8_t *alpha_b, uint8_t *alpha_a, int weight, uint16_t *luma, int soft, uint32_t step) { register int j = 0; register int mix; #if defined(USE_SSE) && defined(ARCH_X86_64) if (!luma && width > 7) { composite_line_yuv_sse2_simple(dest, src, width, alpha_b, alpha_a, weight); j = width - width % 8; dest += j * 2; src += j * 2; if (alpha_a) alpha_a += j; if (alpha_b) alpha_b += j; } #endif for (; j < width; j++) { mix = calculate_mix(luma, j, soft, weight, alpha_b ? *alpha_b : 255, step); *dest = sample_mix(*dest, *src++, mix); dest++; *dest = sample_mix(*dest, *src++, mix); dest++; if (alpha_a) { *alpha_a = (mix >> 8) | *alpha_a; alpha_a++; } if (alpha_b) alpha_b++; } } static void composite_line_yuv_or(uint8_t *dest, uint8_t *src, int width, uint8_t *alpha_b, uint8_t *alpha_a, int weight, uint16_t *luma, int soft, uint32_t step) { register int j; register int mix; for (j = 0; j < width; j++) { mix = calculate_mix(luma, j, soft, weight, (alpha_b ? *alpha_b : 255) | (alpha_a ? *alpha_a : 255), step); *dest = sample_mix(*dest, *src++, mix); dest++; *dest = sample_mix(*dest, *src++, mix); dest++; if (alpha_a) *alpha_a++ = mix >> 8; if (alpha_b) alpha_b++; } } static void composite_line_yuv_and(uint8_t *dest, uint8_t *src, int width, uint8_t *alpha_b, uint8_t *alpha_a, int weight, uint16_t *luma, int soft, uint32_t step) { register int j; register int mix; for (j = 0; j < width; j++) { mix = calculate_mix(luma, j, soft, weight, (alpha_b ? *alpha_b : 255) & (alpha_a ? *alpha_a : 255), step); *dest = sample_mix(*dest, *src++, mix); dest++; *dest = sample_mix(*dest, *src++, mix); dest++; if (alpha_a) *alpha_a++ = mix >> 8; if (alpha_b) alpha_b++; } } static void composite_line_yuv_xor(uint8_t *dest, uint8_t *src, int width, uint8_t *alpha_b, uint8_t *alpha_a, int weight, uint16_t *luma, int soft, uint32_t step) { register int j; register int mix; for (j = 0; j < width; j++) { mix = calculate_mix(luma, j, soft, weight, (alpha_b ? *alpha_b : 255) ^ (alpha_a ? *alpha_a : 255), step); *dest = sample_mix(*dest, *src++, mix); dest++; *dest = sample_mix(*dest, *src++, mix); dest++; if (alpha_a) *alpha_a++ = mix >> 8; if (alpha_b) alpha_b++; } } struct sliced_composite_desc { int height_src; int step; uint8_t *p_dest; uint8_t *p_src; int width_src; uint8_t *alpha_b; uint8_t *alpha_a; int weight; uint16_t *p_luma; int i_softness; uint32_t luma_step; int stride_src; int stride_dest; int alpha_b_stride; int alpha_a_stride; composite_line_fn line_fn; }; static int sliced_composite_proc(int id, int idx, int jobs, void *cookie) { struct sliced_composite_desc ctx = *((struct sliced_composite_desc *) cookie); int i, ho, hs = mlt_slices_size_slice(jobs, idx, ctx.height_src, &ho); for (i = 0; i < ctx.height_src; i += ctx.step) { if (i >= ho && i < (ho + hs)) ctx.line_fn(ctx.p_dest, ctx.p_src, ctx.width_src, ctx.alpha_b, ctx.alpha_a, ctx.weight, ctx.p_luma, ctx.i_softness, ctx.luma_step); ctx.p_src += ctx.stride_src; ctx.p_dest += ctx.stride_dest; if (ctx.alpha_b) ctx.alpha_b += ctx.alpha_b_stride; if (ctx.alpha_a) ctx.alpha_a += ctx.alpha_a_stride; if (ctx.p_luma) ctx.p_luma += ctx.alpha_b_stride; } return 0; } /** Composite function. */ static int composite_yuv(uint8_t *p_dest, int width_dest, int height_dest, uint8_t *p_src, int width_src, int height_src, uint8_t *alpha_b, uint8_t *alpha_a, const struct geometry_s *geometry, int field, uint16_t *p_luma, double softness, composite_line_fn line_fn, int sliced) { int ret = 0; int i; int x_src = -geometry->x_src, y_src = -geometry->y_src; int uneven_x_src = (x_src % 2); int step = (field > -1) ? 2 : 1; int bpp = 2; int stride_src = geometry->sw * bpp; int stride_dest = width_dest * bpp; int i_softness = (1 << 16) * softness; int weight = ((1 << 16) * geometry->item.o + 50) / 100; uint32_t luma_step = (((1 << 16) - 1) * geometry->item.o + 50) / 100 * (1.0 + softness); // Adjust to consumer scale int x = rint(geometry->item.x * width_dest / geometry->nw); int y = rint(geometry->item.y * height_dest / geometry->nh); int uneven_x = (x % 2); // optimization points - no work to do if (width_src <= 0 || height_src <= 0 || y_src >= height_src || x_src >= width_src) return ret; if ((x < 0 && -x >= width_src) || (y < 0 && -y >= height_src)) return ret; // cropping affects the source width if (x_src > 0) { width_src -= x_src; // and it implies cropping if (width_src > geometry->item.w) width_src = geometry->item.w; } // cropping affects the source height if (y_src > 0) { height_src -= y_src; // and it implies cropping if (height_src > geometry->item.h) height_src = geometry->item.h; } // crop overlay off the left edge of frame if (x < 0) { x_src = -x; width_src -= x_src; x = 0; } // crop overlay beyond right edge of frame if (x + width_src > width_dest) width_src = width_dest - x; // crop overlay off the top edge of the frame if (y < 0) { y_src = -y; height_src -= y_src; y = 0; } // crop overlay below bottom edge of frame if (y + height_src > height_dest) height_src = height_dest - y; // offset pointer into overlay buffer based on cropping p_src += x_src * bpp + y_src * stride_src; // offset pointer into frame buffer based upon positive coordinates only! p_dest += x * bpp + y * stride_dest; // offset pointer into alpha channel based upon cropping if (alpha_b) alpha_b += x_src + y_src * stride_src / bpp; if (alpha_a) alpha_a += x + y * stride_dest / bpp; // offset pointer into luma channel based upon cropping if (p_luma) p_luma += x_src + y_src * stride_src / bpp; // Assuming lower field first // Special care is taken to make sure the b_frame is aligned to the correct field. // field 0 = lower field and y should be odd (y is 0-based). // field 1 = upper field and y should be even. if ((field > -1) && (y % 2 == field)) { if ((field == 1 && y < height_dest - 1) || (field == 0 && y == 0)) p_dest += stride_dest; else p_dest -= stride_dest; } // On the second field, use the other lines from b_frame if (field == 1) { p_src += stride_src; if (alpha_b) alpha_b += stride_src / bpp; if (alpha_a) alpha_a += stride_dest / bpp; height_src--; } stride_src *= step; stride_dest *= step; int alpha_b_stride = stride_src / bpp; int alpha_a_stride = stride_dest / bpp; // Align chroma of source and destination if (uneven_x != uneven_x_src) { p_src += 2; } // now do the compositing only to cropped extents if (!sliced) { for (i = 0; i < height_src; i += step) { line_fn(p_dest, p_src, width_src, alpha_b, alpha_a, weight, p_luma, i_softness, luma_step); p_src += stride_src; p_dest += stride_dest; if (alpha_b) alpha_b += alpha_b_stride; if (alpha_a) alpha_a += alpha_a_stride; if (p_luma) p_luma += alpha_b_stride; } } else { struct sliced_composite_desc s = { .height_src = height_src, .step = step, .p_dest = p_dest, .p_src = p_src, .width_src = width_src, .alpha_b = alpha_b, .alpha_a = alpha_a, .weight = weight, .p_luma = p_luma, .i_softness = i_softness, .luma_step = luma_step, .stride_src = stride_src, .stride_dest = stride_dest, .alpha_b_stride = alpha_b_stride, .alpha_a_stride = alpha_a_stride, .line_fn = line_fn, }; mlt_slices_run_normal(0, sliced_composite_proc, &s); } return ret; } /** Scale 16bit greyscale luma map using nearest neighbor. */ static inline void scale_luma(uint16_t *dest_buf, int dest_width, int dest_height, const uint16_t *src_buf, int src_width, int src_height, int invert) { register int i, j; register int x_step = (src_width << 16) / dest_width; register int y_step = (src_height << 16) / dest_height; register int x, y = 0; for (i = 0; i < dest_height; i++) { const uint16_t *src = src_buf + (y >> 16) * src_width; x = 0; for (j = 0; j < dest_width; j++) { *dest_buf++ = src[x >> 16] ^ invert; x += x_step; } y += y_step; } } static uint16_t *get_luma(mlt_transition self, mlt_properties properties, int width, int height) { // The cached luma map information int luma_width = mlt_properties_get_int(properties, "_luma.width"); int luma_height = mlt_properties_get_int(properties, "_luma.height"); uint16_t *luma_bitmap = mlt_properties_get_data(properties, "_luma.bitmap", NULL); int invert = mlt_properties_get_int(properties, "luma_invert"); // If the filename property changed, reload the map char *resource = mlt_properties_get(properties, "luma"); char *orig_resource = resource; mlt_profile profile = mlt_service_profile(MLT_TRANSITION_SERVICE(self)); char temp[PATH_MAX]; if (luma_width == 0 || luma_height == 0) { luma_width = width; luma_height = height; } if (resource && resource[0] && strchr(resource, '%')) { snprintf(temp, sizeof(temp), "%s/lumas/%s/%s", mlt_environment("MLT_DATA"), mlt_profile_lumas_dir(profile), strchr(resource, '%') + 1); FILE *test = mlt_fopen(temp, "r"); if (!test) { strcat(temp, ".png"); test = mlt_fopen(temp, "r"); } if (test) { fclose(test); resource = temp; } } if (resource && resource[0]) { char *old_luma = mlt_properties_get(properties, "_luma"); int old_invert = mlt_properties_get_int(properties, "_luma_invert"); if (invert != old_invert || (old_luma && old_luma[0] && strcmp(resource, old_luma))) { mlt_properties_set_data(properties, "_luma.orig_bitmap", NULL, 0, NULL, NULL); luma_bitmap = NULL; } } else { char *old_luma = mlt_properties_get(properties, "_luma"); if (old_luma && old_luma[0]) { mlt_properties_set_data(properties, "_luma.orig_bitmap", NULL, 0, NULL, NULL); mlt_properties_set_data(properties, "_luma.bitmap", NULL, 0, NULL, NULL); luma_bitmap = NULL; mlt_properties_set(properties, "_luma", NULL); } } if (resource && resource[0] && (luma_bitmap == NULL || luma_width != width || luma_height != height)) { uint16_t *orig_bitmap = mlt_properties_get_data(properties, "_luma.orig_bitmap", NULL); luma_width = mlt_properties_get_int(properties, "_luma.orig_width"); luma_height = mlt_properties_get_int(properties, "_luma.orig_height"); // Load the original luma once if (orig_bitmap == NULL) { char *extension = strrchr(resource, '.'); // See if it is a PGM int lumaLoaded = 0; if (extension != NULL && strcmp(extension, ".pgm") == 0) { // Load from PGM if (mlt_luma_map_from_pgm(resource, &orig_bitmap, &luma_width, &luma_height)) { // Failed to read file; generate it. mlt_luma_map luma = mlt_luma_map_new(orig_resource); if (profile) { luma->w = profile->width; luma->h = profile->height; } luma_bitmap = mlt_luma_map_render(luma); luma_width = luma->w; luma_height = luma->h; free(luma); } if (luma_width > 0 && luma_height > 0) { // Remember the original size for subsequent scaling mlt_properties_set_data(properties, "_luma.orig_bitmap", orig_bitmap, luma_width * luma_height * 2, mlt_pool_release, NULL); mlt_properties_set_int(properties, "_luma.orig_width", luma_width); mlt_properties_set_int(properties, "_luma.orig_height", luma_height); lumaLoaded = 1; } } if (!lumaLoaded) { // Get the factory producer service char *factory = mlt_properties_get(properties, "factory"); // Create the producer mlt_producer producer = mlt_factory_producer(profile, factory, resource); // If we have one if (producer != NULL) { // Get the producer properties mlt_properties producer_properties = MLT_PRODUCER_PROPERTIES(producer); // Ensure that we loop mlt_properties_set(producer_properties, "eof", "loop"); // Now pass all producer. properties on the transition down mlt_properties_pass(producer_properties, properties, "luma."); // We will get the alpha frame from the producer mlt_frame luma_frame = NULL; // Get the luma frame if (mlt_service_get_frame(MLT_PRODUCER_SERVICE(producer), &luma_frame, 0) == 0) { uint8_t *luma_image; mlt_image_format luma_format = mlt_image_yuv422; // Get image from the luma producer mlt_properties_set(MLT_FRAME_PROPERTIES(luma_frame), "consumer.rescale", "none"); mlt_frame_get_image(luma_frame, &luma_image, &luma_format, &luma_width, &luma_height, 0); // Generate the luma map if (luma_image != NULL && luma_format == mlt_image_yuv422) mlt_luma_map_from_yuv422(luma_image, &orig_bitmap, luma_width, luma_height); // Remember the original size for subsequent scaling mlt_properties_set_data(properties, "_luma.orig_bitmap", orig_bitmap, luma_width * luma_height * 2, mlt_pool_release, NULL); mlt_properties_set_int(properties, "_luma.orig_width", luma_width); mlt_properties_set_int(properties, "_luma.orig_height", luma_height); // Cleanup the luma frame mlt_frame_close(luma_frame); } // Cleanup the luma producer mlt_producer_close(producer); } else { luma_width = 0; luma_height = 0; } } } if (orig_bitmap && luma_width > 0 && luma_height > 0) { // Scale luma map luma_bitmap = mlt_pool_alloc(width * height * sizeof(uint16_t)); scale_luma(luma_bitmap, width, height, orig_bitmap, luma_width, luma_height, invert * ((1 << 16) - 1)); // Remember the scaled luma size to prevent unnecessary scaling mlt_properties_set_int(properties, "_luma.width", width); mlt_properties_set_int(properties, "_luma.height", height); mlt_properties_set_data(properties, "_luma.bitmap", luma_bitmap, width * height * 2, mlt_pool_release, NULL); mlt_properties_set(properties, "_luma", resource); mlt_properties_set_int(properties, "_luma_invert", invert); } } return luma_bitmap; } /** Get the properly sized image from b_frame. */ static int get_b_frame_image(mlt_transition self, mlt_frame b_frame, uint8_t **image, int *width, int *height, struct geometry_s *geometry) { int error = 0; mlt_image_format format = mlt_image_yuv422; // Get the properties objects mlt_properties b_props = MLT_FRAME_PROPERTIES(b_frame); mlt_properties properties = MLT_TRANSITION_PROPERTIES(self); uint8_t resize_alpha = mlt_properties_get_int(b_props, "resize_alpha"); double output_ar = mlt_profile_sar(mlt_service_profile(MLT_TRANSITION_SERVICE(self))); // Do not scale if we are cropping - the compositing rectangle can crop the b image // TODO: Use the animatable w and h of the crop geometry to scale independently of crop rectangle if (mlt_properties_get(properties, "crop")) { int real_width = get_value(b_props, "meta.media.width", "width"); int real_height = get_value(b_props, "meta.media.height", "height"); double input_ar = mlt_properties_get_double(b_props, "aspect_ratio"); int scaled_width = rint((input_ar == 0.0 ? output_ar : input_ar) / output_ar * real_width); int scaled_height = real_height; geometry->sw = scaled_width; geometry->sh = scaled_height; } else if (mlt_properties_get_int(properties, "crop_to_fill")) { int real_width = get_value(b_props, "meta.media.width", "width"); int real_height = get_value(b_props, "meta.media.height", "height"); double input_ar = mlt_properties_get_double(b_props, "aspect_ratio"); int scaled_width = rint((input_ar == 0.0 ? output_ar : input_ar) / output_ar * real_width); int scaled_height = real_height; int normalized_width = geometry->item.w; int normalized_height = geometry->item.h; if (scaled_height > 0 && scaled_width * normalized_height / scaled_height >= normalized_width) { // crop left/right edges scaled_width = rint(scaled_width * normalized_height / scaled_height); scaled_height = normalized_height; } else if (scaled_width > 0) { // crop top/bottom edges scaled_height = rint(scaled_height * normalized_width / scaled_width); scaled_width = normalized_width; } geometry->sw = scaled_width; geometry->sh = scaled_height; } // Normalize aspect ratios and scale preserving aspect ratio else if (mlt_properties_get_int(properties, "aligned") && mlt_properties_get_int(properties, "distort") == 0 && mlt_properties_get_int(b_props, "distort") == 0) { // Adjust b_frame pixel aspect int normalized_width = geometry->item.w; int normalized_height = geometry->item.h; int real_width = get_value(b_props, "meta.media.width", "width"); int real_height = get_value(b_props, "meta.media.height", "height"); double input_ar = mlt_properties_get_double(b_props, "aspect_ratio"); int scaled_width = rint((input_ar == 0.0 ? output_ar : input_ar) / output_ar * real_width); int scaled_height = real_height; // fprintf(stderr, "%s: scaled %dx%d norm %dx%d real %dx%d output_ar %f\n", __FILE__, // scaled_width, scaled_height, normalized_width, normalized_height, real_width, real_height, // output_ar); // Now ensure that our images fit in the normalized frame if (scaled_width > normalized_width) { scaled_height = rint(scaled_height * normalized_width / scaled_width); scaled_width = normalized_width; } if (scaled_height > normalized_height) { scaled_width = rint(scaled_width * normalized_height / scaled_height); scaled_height = normalized_height; } // Honour the fill request - this will scale the image to fill width or height while maintaining a/r // ????: Shouldn't this be the default behaviour? if (mlt_properties_get_int(properties, "fill") && scaled_width > 0 && scaled_height > 0) { if (scaled_height < normalized_height && scaled_width * normalized_height / scaled_height <= normalized_width) { scaled_width = rint(scaled_width * normalized_height / scaled_height); scaled_height = normalized_height; } else if (scaled_width < normalized_width && scaled_height * normalized_width / scaled_width < normalized_height) { scaled_height = rint(scaled_height * normalized_width / scaled_width); scaled_width = normalized_width; } } // Save the new scaled dimensions geometry->sw = scaled_width; geometry->sh = scaled_height; } else { geometry->sw = geometry->item.w; geometry->sh = geometry->item.h; } // We want to ensure that we bypass resize now... if (resize_alpha == 0) mlt_properties_set_int(b_props, "distort", mlt_properties_get_int(properties, "distort")); // If we're not aligned, we want a non-transparent background if (mlt_properties_get_int(properties, "aligned") == 0) mlt_properties_set_int(b_props, "resize_alpha", 255); // Take into consideration alignment for optimisation (titles are a special case) if (!mlt_properties_get_int(properties, "titles") && mlt_properties_get(properties, "crop") == NULL) alignment_calculate(geometry); // Adjust to consumer scale *width = rint(geometry->sw * *width / geometry->nw); *width -= *width % 2; // coerce to even width for yuv422 *height = rint(geometry->sh * *height / geometry->nh); // fprintf(stderr, "%s: scaled %dx%d norm %dx%d resize %dx%d\n", __FILE__, // geometry->sw, geometry->sh, geometry->nw, geometry->nh, *width, *height); error = mlt_frame_get_image(b_frame, image, &format, width, height, 1); // composite_yuv uses geometry->sw to determine source stride, which // should equal the image width if not using crop property. if (!mlt_properties_get(properties, "crop")) geometry->sw = *width; // Set the frame back mlt_properties_set_int(b_props, "resize_alpha", resize_alpha); return !error && image; } static void crop_calculate(mlt_transition self, struct geometry_s *result, double position) { // Get the properties from the transition mlt_properties properties = MLT_TRANSITION_PROPERTIES(self); // Initialize panning info result->x_src = 0; result->y_src = 0; if (mlt_properties_get(properties, "crop")) { mlt_position length = mlt_transition_get_length(self); double cycle = mlt_properties_get_double(properties, "cycle"); // Allow a geometry repeat cycle if (cycle >= 1) length = cycle; else if (cycle > 0) length *= cycle; mlt_rect crop = mlt_properties_anim_get_rect(properties, "crop", position, length); // Repeat processing mlt_animation animation = mlt_properties_get_animation(properties, "crop"); int anim_length = mlt_animation_get_length(animation); int mirror_off = mlt_properties_get_int(properties, "mirror_off"); int repeat_off = mlt_properties_get_int(properties, "repeat_off"); if (!repeat_off && position >= anim_length && anim_length != 0) { int section = position / anim_length; position -= section * anim_length; if (!mirror_off && section % 2 == 1) position = anim_length - position; } // Compute the pan crop = mlt_properties_anim_get_rect(properties, "crop", position, length); if (mlt_properties_get(properties, "crop") && strchr(mlt_properties_get(properties, "crop"), '%')) { mlt_profile profile = mlt_service_profile(MLT_TRANSITION_SERVICE(self)); crop.x *= profile->width; crop.y *= profile->height; } result->x_src = rint(crop.x); result->y_src = rint(crop.y); } } static void composite_calculate(mlt_transition self, struct geometry_s *result, double position) { // Get the properties from the transition mlt_properties properties = MLT_TRANSITION_PROPERTIES(self); // Obtain the normalized width and height from the a_frame mlt_profile profile = mlt_service_profile(MLT_TRANSITION_SERVICE(self)); int normalized_width = profile->width; int normalized_height = profile->height; // Now parse the geometries mlt_position length = mlt_transition_get_length(self); double cycle = mlt_properties_get_double(properties, "cycle"); // Allow a geometry repeat cycle if (cycle >= 1) length = cycle; else if (cycle > 0) length *= cycle; result->item = mlt_properties_anim_get_rect(properties, "geometry", position, length); // Repeat processing mlt_animation animation = mlt_properties_get_animation(properties, "geometry"); int anim_length = mlt_animation_get_length(animation); int mirror_off = mlt_properties_get_int(properties, "mirror_off"); int repeat_off = mlt_properties_get_int(properties, "repeat_off"); if (!repeat_off && position >= anim_length && anim_length != 0) { int section = position / anim_length; position -= section * anim_length; if (!mirror_off && section % 2 == 1) position = anim_length - position; } result->item = mlt_properties_anim_get_rect(properties, "geometry", position, length); if (mlt_properties_get(properties, "geometry") && strchr(mlt_properties_get(properties, "geometry"), '%')) { result->item.x *= normalized_width; result->item.y *= normalized_height; result->item.w *= normalized_width; result->item.h *= normalized_height; } result->item.o = 100.0 * (result->item.o == DBL_MIN ? 1.0 : MIN(result->item.o, 1.0)); // Assign normalized info result->nw = normalized_width; result->nh = normalized_height; // Now parse the alignment result->halign = alignment_parse(mlt_properties_get(properties, "halign")); result->valign = alignment_parse(mlt_properties_get(properties, "valign")); crop_calculate(self, result, position); } /** Get the image. */ static int transition_get_image(mlt_frame a_frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { // Get the b frame from the stack mlt_frame b_frame = mlt_frame_pop_frame(a_frame); // Get the transition from the a frame mlt_transition self = mlt_frame_pop_service(a_frame); // Get in and out double position = mlt_deque_pop_back_double(MLT_FRAME_IMAGE_STACK(a_frame)); int out = mlt_frame_pop_service_int(a_frame); int in = mlt_frame_pop_service_int(a_frame); // Get the properties from the transition mlt_properties properties = MLT_TRANSITION_PROPERTIES(self); // TODO: clean up always_active behaviour if (mlt_properties_get_int(properties, "always_active")) { mlt_events_block(properties, properties); mlt_properties_set_int(properties, "in", in); mlt_properties_set_int(properties, "out", out); mlt_events_unblock(properties, properties); } if (mlt_properties_get_int(properties, "invert")) { mlt_frame c = a_frame; a_frame = b_frame; b_frame = c; } // This compositer is yuv422 only *format = mlt_image_yuv422; if (b_frame != NULL) { // Get the properties of the a frame mlt_properties a_props = MLT_FRAME_PROPERTIES(a_frame); // Get the properties of the b frame mlt_properties b_props = MLT_FRAME_PROPERTIES(b_frame); // Structures for geometry struct geometry_s result; // Calculate the position double delta = mlt_transition_get_progress_delta(self, a_frame); mlt_position length = mlt_transition_get_length(self); // Get the image from the b frame uint8_t *image_b = NULL; mlt_profile profile = mlt_service_profile(MLT_TRANSITION_SERVICE(self)); int width_b = *width > 0 ? *width : profile->width; int height_b = *height > 0 ? *height : profile->height; // Vars for alphas uint8_t *alpha_a = NULL; uint8_t *alpha_b = NULL; // Do the calculation // NB: Locks needed here since the properties are being modified mlt_service_lock(MLT_TRANSITION_SERVICE(self)); composite_calculate(self, &result, position); mlt_service_unlock(MLT_TRANSITION_SERVICE(self)); // Manual option to deinterlace if (mlt_properties_get_int(properties, "deinterlace")) { mlt_properties_set_int(a_props, "consumer.progressive", 1); mlt_properties_set_int(b_props, "consumer.progressive", 1); } // TODO: Dangerous/temporary optimisation - if nothing to do, then do nothing if (mlt_properties_get_int(properties, "no_alpha") && result.item.x == 0 && result.item.y == 0 && result.item.w == *width && result.item.h == *height && result.item.o == 100) { mlt_frame_get_image(b_frame, image, format, width, height, 1); if (!mlt_frame_is_test_card(a_frame)) mlt_frame_replace_image(a_frame, *image, *format, *width, *height); return 0; } if (a_frame == b_frame) { double aspect_ratio = mlt_frame_get_aspect_ratio(b_frame); get_b_frame_image(self, b_frame, &image_b, &width_b, &height_b, &result); alpha_b = mlt_frame_get_alpha(b_frame); mlt_properties_set_double(a_props, "aspect_ratio", aspect_ratio); } // Get the image from the a frame mlt_frame_get_image(a_frame, image, format, width, height, 1); alpha_a = mlt_frame_get_alpha(a_frame); // Optimisation - no compositing required if (result.item.o == 0 || (result.item.w == 0 && result.item.h == 0)) return 0; // Need to keep the width/height of the a_frame on the b_frame for titling if (mlt_properties_get(a_props, "dest_width") == NULL) { mlt_properties_set_int(a_props, "dest_width", *width); mlt_properties_set_int(a_props, "dest_height", *height); mlt_properties_set_int(b_props, "dest_width", *width); mlt_properties_set_int(b_props, "dest_height", *height); } else { mlt_properties_set_int(b_props, "dest_width", mlt_properties_get_int(a_props, "dest_width")); mlt_properties_set_int(b_props, "dest_height", mlt_properties_get_int(a_props, "dest_height")); } // Special case for titling... if (mlt_properties_get_int(properties, "titles")) { if (mlt_properties_get(b_props, "consumer.rescale") == NULL) mlt_properties_set(b_props, "consumer.rescale", "hyper"); width_b = mlt_properties_get_int(a_props, "dest_width"); height_b = mlt_properties_get_int(a_props, "dest_height"); } if (*image != image_b && (image_b || get_b_frame_image(self, b_frame, &image_b, &width_b, &height_b, &result))) { int progressive = mlt_properties_get_int(a_props, "consumer.progressive") || mlt_properties_get_int(properties, "progressive"); int top_field_first = mlt_properties_get_int(a_props, "top_field_first"); int field; int sliced = mlt_properties_get_int(properties, "sliced_composite"); double luma_softness = mlt_properties_get_double(properties, "softness"); mlt_service_lock(MLT_TRANSITION_SERVICE(self)); uint16_t *luma_bitmap = get_luma(self, properties, width_b, height_b); mlt_service_unlock(MLT_TRANSITION_SERVICE(self)); char *operator= mlt_properties_get(properties, "operator"); alpha_b = alpha_b == NULL ? mlt_frame_get_alpha(b_frame) : alpha_b; composite_line_fn line_fn = composite_line_yuv; // Replacement and override if (operator!= NULL) { if (!strcmp(operator, "or")) line_fn = composite_line_yuv_or; if (!strcmp(operator, "and")) line_fn = composite_line_yuv_and; if (!strcmp(operator, "xor")) line_fn = composite_line_yuv_xor; } // Allow the user to completely obliterate the alpha channels from both frames if (mlt_properties_get(properties, "alpha_a") && alpha_a) memset(alpha_a, mlt_properties_get_int(properties, "alpha_a"), *width * *height); if (mlt_properties_get(properties, "alpha_b") && alpha_b) memset(alpha_b, mlt_properties_get_int(properties, "alpha_b"), width_b * height_b); for (field = 0; field < (progressive ? 1 : 2); field++) { // Assume lower field (0) first double field_position = position + field * delta * length; int field_id = progressive ? -1 : (top_field_first ? (1 - field) : field); // Do the calculation if we need to // NB: Locks needed here since the properties are being modified mlt_service_lock(MLT_TRANSITION_SERVICE(self)); composite_calculate(self, &result, field_position); mlt_service_unlock(MLT_TRANSITION_SERVICE(self)); if (mlt_properties_get_int(properties, "titles")) { result.item.w = rint(*width * (result.item.w / result.nw)); result.nw = result.item.w; result.item.h = rint(*height * (result.item.h / result.nh)); result.nh = *height; result.sw = width_b; result.sh = height_b; } // Enforce cropping if (mlt_properties_get(properties, "crop")) { if (result.x_src == 0) width_b = width_b > result.item.w ? result.item.w : width_b; if (result.y_src == 0) height_b = height_b > result.item.h ? result.item.h : height_b; } else if (mlt_properties_get_int(properties, "crop_to_fill")) { if (result.item.w < result.sw) result.x_src = rint((result.item.w - result.sw) * result.halign / 2); if (result.item.h < result.sh) result.y_src = rint((result.item.h - result.sh) * result.valign / 2); // same as crop if (result.x_src == 0) width_b = width_b > result.item.w ? result.item.w : width_b; if (result.y_src == 0) height_b = height_b > result.item.h ? result.item.h : height_b; } else { // Otherwise, align alignment_calculate(&result); } // Composite the b_frame on the a_frame mlt_log_timings_begin() composite_yuv(*image, *width, *height, image_b, width_b, height_b, alpha_b, alpha_a, &result, field_id, luma_bitmap, luma_softness, line_fn, sliced); mlt_log_timings_end(NULL, "composite_yuv") } } } else { mlt_frame_get_image(a_frame, image, format, width, height, 1); } return 0; } /** Composition transition processing. */ static mlt_frame composite_process(mlt_transition self, mlt_frame a_frame, mlt_frame b_frame) { // UGH - this is a TODO - find a more reliable means of obtaining in/out for the always_active case if (mlt_properties_get_int(MLT_TRANSITION_PROPERTIES(self), "always_active") == 0) { mlt_frame_push_service_int(a_frame, mlt_properties_get_int(MLT_TRANSITION_PROPERTIES(self), "in")); mlt_frame_push_service_int(a_frame, mlt_properties_get_int(MLT_TRANSITION_PROPERTIES(self), "out")); mlt_deque_push_back_double(MLT_FRAME_IMAGE_STACK(a_frame), position_calculate(self, mlt_frame_get_position(a_frame))); } else { mlt_properties props = mlt_properties_get_data(MLT_FRAME_PROPERTIES(b_frame), "_producer", NULL); mlt_frame_push_service_int(a_frame, mlt_properties_get_int(props, "in")); mlt_frame_push_service_int(a_frame, mlt_properties_get_int(props, "out")); mlt_deque_push_back_double(MLT_FRAME_IMAGE_STACK(a_frame), mlt_properties_get_int(props, "_frame") - mlt_properties_get_int(props, "in")); } mlt_frame_push_service(a_frame, self); mlt_frame_push_frame(a_frame, b_frame); mlt_frame_push_get_image(a_frame, transition_get_image); return a_frame; } /** Constructor for the filter. */ mlt_transition transition_composite_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_transition self = calloc(1, sizeof(struct mlt_transition_s)); if (self != NULL && mlt_transition_init(self, NULL) == 0) { mlt_properties properties = MLT_TRANSITION_PROPERTIES(self); self->process = composite_process; // Default starting motion and zoom mlt_properties_set(properties, "geometry", arg != NULL ? arg : "0/0:100%x100%"); // Default factory mlt_properties_set(properties, "factory", mlt_environment("MLT_PRODUCER")); // Use alignment (and hence alpha of b frame) mlt_properties_set_int(properties, "aligned", 1); // Default to progressive rendering mlt_properties_set_int(properties, "progressive", 1); // Inform apps and framework that this is a video only transition mlt_properties_set_int(properties, "_transition_type", 1); } return self; } mlt-7.22.0/src/modules/core/transition_composite.h000664 000000 000000 00000003176 14531534050 022201 0ustar00rootroot000000 000000 /* * transition_composite.h -- compose one image over another using alpha channel * Copyright (C) 2003-2021 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _TRANSITION_COMPOSITE_H_ #define _TRANSITION_COMPOSITE_H_ #include extern mlt_transition transition_composite_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern void composite_line_yuv(uint8_t *dest, uint8_t *src, int width, uint8_t *alpha_b, uint8_t *alpha_a, int weight, uint16_t *luma, int soft, uint32_t step); #endif mlt-7.22.0/src/modules/core/transition_composite.yml000664 000000 000000 00000010063 14531534050 022544 0ustar00rootroot000000 000000 schema_version: 7.0 type: transition identifier: composite title: Composite version: 2 copyright: Meltytech, LLC creator: Dan Dennedy license: LGPLv2.1 language: en tags: - Video description: > A key-framable alpha-channel compositor for two frames. notes: > Performs dissolves and luma wipes in addition to alpha compositing. By default, the aspect ratio of the B frame is respected and the size portion of the geometry specification simply defines a bounding rectangle. This performs field-based rendering unless the A frame property "progressive" or "consumer_progressive" or the transition property "progressive" is set to 1. bugs: - Assumes lower field first during field rendering. parameters: - identifier: factory title: Factory type: string description: > The name of a factory service used as a non-PGM producer loader. default: loader - identifier: geometry argument: yes title: Geometry type: rect description: A possibly keyframed rectangle mutable: yes animation: yes - identifier: progressive title: Progressive description: > Enable or disable field-based rendering. type: integer minimum: 0 maximum: 1 mutable: yes widget: checkbox - identifier: distort title: Allow distorted scaling description: > When set, causes the B frame image to fill the WxH completely with no regard to B's aspect ratio. type: integer default: 0 minimum: 0 maximum: 1 mutable: yes widget: checkbox - identifier: crop_to_fill title: Fill by cropping description: > When set, causes the B frame image to fill the WxH completely by cropping edges in order to maintain B's aspect ratio. type: integer default: 0 minimum: 0 maximum: 1 mutable: yes widget: checkbox - identifier: halign title: Horizontal alignment description: > When not distorting, set the horizontal alignment of B within the geometry rectangle. type: string default: left values: - left - centre - right mutable: yes widget: combo - identifier: valign title: Vertical alignment description: > When not distorting, set the vertical alignment of B within the geometry rectangle. type: string default: top values: - top - middle - bottom mutable: yes widget: combo - identifier: luma title: Luma map description: > The luma map file name. If not supplied, a dissolve. type: string mutable: yes widget: fileopen - identifier: softness title: Softness description: > Only when using a luma map, how soft to make the edges between A and B. type: float default: 0.0 minimum: 0.0 maximum: 1.0 mutable: yes - identifier: luma.* title: Luma producer description: > Properties may be set on the encapsulated producer. Any property starting with "luma." is passed to the non-PGM luma producer. readonly: no mutable: yes - identifier: sliced_composite title: Use sliced compositing description: > Enabling this option will start sliced processing of picture compositing, i.e. some parts of picture processed in different thread type: boolean default: 0 mutable: yes widget: checkbox - identifier: fill title: Fill geometry description: > Determines whether the image will be scaled up to fill the geometry. Otherwise, if the B frame image fits within the geometry, it will not be scaled. If 0, and the B frame image exceeds the geometry, then it is scaled down to fit within the geometry. type: boolean default: 1 mutable: yes widget: checkbox - identifier: invert title: Invert description: Whether to swap the A and B clips type: boolean default: 1 mutable: yes widget: checkbox - identifier: crop title: Crop Rectangle type: rect description: Defines a cropping rectangle for the second input mutable: yes animation: yes mlt-7.22.0/src/modules/core/transition_luma.c000664 000000 000000 00000063420 14531534050 021126 0ustar00rootroot000000 000000 /* * transition_luma.c -- a generic dissolve/wipe processor * Copyright (C) 2003-2023 Meltytech, LLC * * Adapted from Kino Plugin Timfx, which is * Copyright (C) 2002 Timothy M. Shead * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include "transition_composite.h" #include #include #include #include #include #include static inline int is_opaque(uint8_t *alpha_channel, int width, int height) { int n = width * height + 1; while (--n) if (*alpha_channel++ != 0xff) return 0; return 1; } static inline float calculate_mix(float weight, float alpha) { return weight * alpha / 255.f; } static inline uint8_t sample_mix(uint8_t dest, uint8_t src, float mix) { return src * mix + dest * (1.f - mix); } static void composite_line_yuv_float( uint8_t *dest, uint8_t *src, int width, uint8_t *alpha_b, uint8_t *alpha_a, float weight) { register int j = 0; float mix_a, mix_b; for (; j < width; j++) { mix_a = calculate_mix(1.0f - weight, alpha_a ? *alpha_a : 255); mix_b = calculate_mix(weight, alpha_b ? *alpha_b : 255); if (alpha_a) { float mix2 = mix_b + mix_a - mix_b * mix_a; *alpha_a = 255 * mix2; if (mix2 != 0.f) mix_b /= mix2; } *dest = sample_mix(*dest, *src++, mix_b); dest++; *dest = sample_mix(*dest, *src++, mix_b); dest++; if (alpha_a) alpha_a++; if (alpha_b) alpha_b++; } } struct dissolve_slice_context { uint8_t *dst_image; uint8_t *src_image; uint8_t *dst_alpha; uint8_t *src_alpha; int width; int height; float weight; }; static int dissolve_slice(int id, int index, int count, void *context) { struct dissolve_slice_context ctx = *((struct dissolve_slice_context *) context); int stride = ctx.width * 2; int slice_start, slice_height = mlt_slices_size_slice(count, index, ctx.height, &slice_start); int i; ctx.dst_image += slice_start * stride; ctx.src_image += slice_start * stride; if (ctx.dst_alpha) ctx.dst_alpha += slice_start * ctx.width; if (ctx.src_alpha) ctx.src_alpha += slice_start * ctx.width; for (i = 0; i < slice_height; i++) { composite_line_yuv_float(ctx.dst_image, ctx.src_image, ctx.width, ctx.src_alpha, ctx.dst_alpha, ctx.weight); ctx.dst_image += stride; ctx.src_image += stride; if (ctx.dst_alpha) ctx.dst_alpha += ctx.width; if (ctx.src_alpha) ctx.src_alpha += ctx.width; } return 0; } static inline int dissolve_yuv(mlt_frame frame, mlt_frame that, float weight, int width, int height, int threads, int alpha_over, int fix_background_alpha) { int ret = 0; int i = height + 1; int width_src = width, height_src = height; mlt_image_format format = (fix_background_alpha && frame->convert_image) ? mlt_image_rgba : mlt_image_yuv422; uint8_t *p_src, *p_dest; uint8_t *alpha_src; uint8_t *alpha_dst; int mix = weight * (1 << 16); if (mlt_properties_get(&frame->parent, "distort")) mlt_properties_set(&that->parent, "distort", mlt_properties_get(&frame->parent, "distort")); mlt_frame_get_image(frame, &p_dest, &format, &width, &height, 1); if (fix_background_alpha && frame->convert_image) frame->convert_image(frame, &p_dest, &format, mlt_image_yuv422); alpha_dst = mlt_frame_get_alpha(frame); if (fix_background_alpha && that->convert_image) format = mlt_image_rgba; mlt_frame_get_image(that, &p_src, &format, &width_src, &height_src, 0); if (that->convert_image) that->convert_image(that, &p_src, &format, mlt_image_yuv422); alpha_src = mlt_frame_get_alpha(that); int is_translucent = (alpha_dst && !is_opaque(alpha_dst, width, height)) || (alpha_src && !is_opaque(alpha_src, width_src, height_src)); // Pick the lesser of two evils ;-) width_src = width_src > width ? width : width_src; height_src = height_src > height ? height : height_src; if (is_translucent && alpha_over) { struct dissolve_slice_context context = {.dst_image = p_dest, .src_image = p_src, .dst_alpha = alpha_dst, .src_alpha = alpha_src, .width = width_src, .height = height_src, .weight = weight}; mlt_slices_run_normal(threads, dissolve_slice, &context); } else { while (--i) { composite_line_yuv(p_dest, p_src, width_src, alpha_src, alpha_dst, mix, NULL, 0, 0); p_src += width_src << 1; p_dest += width << 1; if (alpha_src) alpha_src += width_src; if (alpha_dst) alpha_dst += width; } } return ret; } /** A smoother, non-linear threshold determination function. */ static inline int32_t smoothstep(int32_t edge1, int32_t edge2, uint32_t a) { if (a < edge1) return 0; if (a >= edge2) return 0x10000; a = ((a - edge1) << 16) / (edge2 - edge1); return (((a * a) >> 16) * ((3 << 16) - (2 * a))) >> 16; } static float smoothstep_float(float edge1, float edge2, float a) { if (a < edge1) return 0.f; if (a >= edge2) return 1.f; a = (a - edge1) / (edge2 - edge1); return (a * a) * (3 - (2 * a)); } /** powerful stuff \param field_order -1 = progressive, 0 = lower field first, 1 = top field first */ static void luma_composite(mlt_frame a_frame, mlt_frame b_frame, int luma_width, int luma_height, uint16_t *luma_bitmap, float pos, float frame_delta, float softness, int field_order, int *width, int *height, int invert, int fix_background_alpha) { int width_src = *width, height_src = *height; int width_dest = *width, height_dest = *height; mlt_image_format format_src = (fix_background_alpha && a_frame->convert_image) ? mlt_image_rgba : mlt_image_yuv422; mlt_image_format format_dest = (fix_background_alpha && b_frame->convert_image) ? mlt_image_rgba : mlt_image_yuv422; uint8_t *p_src, *p_dest; uint8_t *alpha_src, *alpha_dest; int i, j; int stride_src; int stride_dest; if (mlt_properties_get(&a_frame->parent, "distort")) mlt_properties_set(&b_frame->parent, "distort", mlt_properties_get(&a_frame->parent, "distort")); mlt_frame_get_image(a_frame, &p_dest, &format_dest, &width_dest, &height_dest, 1); if (fix_background_alpha && a_frame->convert_image) a_frame->convert_image(a_frame, &p_dest, &format_dest, mlt_image_yuv422); alpha_dest = mlt_frame_get_alpha(a_frame); mlt_frame_get_image(b_frame, &p_src, &format_src, &width_src, &height_src, 0); if (fix_background_alpha && b_frame->convert_image) b_frame->convert_image(b_frame, &p_src, &format_src, mlt_image_yuv422); alpha_src = mlt_frame_get_alpha(b_frame); if (*width == 0 || *height == 0) return; int is_translucent = (alpha_dest && !is_opaque(alpha_dest, width_dest, height_dest)) || (alpha_src && !is_opaque(alpha_src, width_src, height_src)); // Pick the lesser of two evils ;-) width_src = width_src > width_dest ? width_dest : width_src; height_src = height_src > height_dest ? height_dest : height_src; stride_src = width_src * 2; stride_dest = width_dest * 2; // Offset the position based on which field we're looking at ... float field_pos[2]; field_pos[0] = (pos + ((field_order == 0 ? 1 : 0) * frame_delta * 0.5f)) * (1.f + softness); field_pos[1] = (pos + ((field_order == 0 ? 0 : 1) * frame_delta * 0.5f)) * (1.f + softness); register uint8_t *p; register uint8_t *q; uint16_t *l; int32_t x_diff = (luma_width << 16) / *width; int32_t y_diff = (luma_height << 16) / *height; int32_t x_offset = 0; int32_t y_offset = 0; uint8_t *p_row; uint8_t *q_row; uint32_t i_softness = softness * (1 << 16); int field_count = field_order < 0 ? 1 : 2; int field_stride_src = field_count * stride_src; int field_stride_dest = field_count * stride_dest; int field = 0; float mix_a, mix_b; // composite using luma map while (field < field_count) { p_row = p_src + field * stride_src; q_row = p_dest + field * stride_dest; y_offset = field << 16; i = field; while (i < height_src) { p = p_row; q = q_row; l = luma_bitmap + (y_offset >> 16) * (luma_width * field_count); x_offset = 0; j = width_src; if (is_translucent) { while (j--) { float weight = l[x_offset >> 16] / 65535.f; float value = smoothstep_float(weight, softness + weight, field_pos[field]); mix_a = calculate_mix(1.0f - value, alpha_dest ? *alpha_dest : 255); mix_b = calculate_mix(value, alpha_src ? *alpha_src : 255); if (invert && alpha_src) { float mix2 = mix_b + mix_a - mix_b * mix_a; *alpha_src = 255 * mix2; if (mix2 != 0.f) mix_b /= mix2; } else if (!invert && alpha_dest) { float mix2 = mix_b + mix_a - mix_b * mix_a; *alpha_dest = 255 * mix2; if (mix2 != 0.f) mix_b /= mix2; } *q = sample_mix(*q, *p++, mix_b); q++; *q = sample_mix(*q, *p++, mix_b); q++; if (alpha_dest) alpha_dest++; if (alpha_src) alpha_src++; x_offset += x_diff; } } else { while (j--) { uint16_t weight = l[x_offset >> 16]; uint32_t value = smoothstep(weight, i_softness + weight, (1 << 16) * field_pos[field]); *q = (*p++ * value + *q * ((1 << 16) - value)) >> 16; q++; *q = (*p++ * value + *q * ((1 << 16) - value)) >> 16; q++; x_offset += x_diff; } } y_offset += y_diff; i += field_count; p_row += field_stride_src; q_row += field_stride_dest; } field++; } } void yuv422_to_luma16(uint8_t *image, uint16_t **map, int width, int height, int full_range) { // allocate the luma bitmap *map = (uint16_t *) mlt_pool_alloc(width * height * sizeof(uint16_t)); if (!*map) return; int i; int n = width * height; uint8_t offset = full_range ? 0 : 16; uint8_t max = full_range ? 255 : 235 - offset; int factor = full_range ? 256 : 299; // 299 = 65535 / 219 // process the image data into the luma bitmap for (i = 0; i < n; i++) { (*map)[i] = CLAMP(image[i << 1] - offset, 0, max) * factor; } } static int transition_get_image(mlt_frame a_frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { // Get the b frame from the stack mlt_frame b_frame = mlt_frame_pop_frame(a_frame); // Get the transition object mlt_transition transition = mlt_frame_pop_service(a_frame); // Get the properties of the transition mlt_properties properties = MLT_TRANSITION_PROPERTIES(transition); // Get the properties of the a frame mlt_properties a_props = MLT_FRAME_PROPERTIES(a_frame); // Get the properties of the b frame mlt_properties b_props = MLT_FRAME_PROPERTIES(b_frame); // This compositer is yuv422 only *format = mlt_image_yuv422; mlt_service_lock(MLT_TRANSITION_SERVICE(transition)); // The cached luma map information int luma_width = mlt_properties_get_int(properties, "width"); int luma_height = mlt_properties_get_int(properties, "height"); uint16_t *luma_bitmap = mlt_properties_get_data(properties, "bitmap", NULL); char *current_resource = mlt_properties_get(properties, "_resource"); mlt_producer producer = mlt_properties_get_data(properties, "producer", NULL); // If the filename property changed, reload the map char *resource = mlt_properties_get(properties, "resource"); // Correct width/height if not specified if (luma_width == 0 || luma_height == 0) { luma_width = *width; luma_height = *height; } if (resource && (producer || !current_resource || strcmp(resource, current_resource))) { char temp[PATH_MAX]; char *extension = strrchr(resource, '.'); char *orig_resource = resource; mlt_profile profile = mlt_service_profile(MLT_TRANSITION_SERVICE(transition)); if (strchr(resource, '%')) { snprintf(temp, sizeof(temp), "%s/lumas/%s/%s", mlt_environment("MLT_DATA"), mlt_profile_lumas_dir(profile), strchr(resource, '%') + 1); FILE *test = mlt_fopen(temp, "r"); if (!test) { strcat(temp, ".png"); test = mlt_fopen(temp, "r"); } if (test) { fclose(test); resource = temp; } extension = strrchr(resource, '.'); } // See if it is a PGM if (extension != NULL && strcmp(extension, ".pgm") == 0) { // Load from PGM luma_bitmap = NULL; if (mlt_luma_map_from_pgm(resource, &luma_bitmap, &luma_width, &luma_height)) { // Failed to read file; generate it. mlt_luma_map luma = mlt_luma_map_new(orig_resource); if (profile) { luma->w = profile->width; luma->h = profile->height; } luma_bitmap = mlt_luma_map_render(luma); luma_width = luma->w; luma_height = luma->h; free(luma); } // Set the transition properties mlt_properties_set_int(properties, "width", luma_width); mlt_properties_set_int(properties, "height", luma_height); mlt_properties_set(properties, "_resource", orig_resource); mlt_properties_set_data(properties, "bitmap", luma_bitmap, luma_width * luma_height * 2, mlt_pool_release, NULL); mlt_properties_clear(properties, "producer"); } else if (!*resource) { luma_bitmap = NULL; mlt_properties_set(properties, "_resource", NULL); mlt_properties_set_data(properties, "bitmap", luma_bitmap, 0, mlt_pool_release, NULL); mlt_properties_clear(properties, "producer"); } else { if (!producer || !current_resource || strcmp(resource, current_resource)) { // Get the factory producer service char *factory = mlt_properties_get(properties, "factory"); // Create the producer producer = mlt_factory_producer(profile, factory, resource); if (producer) mlt_properties_set(properties, "_resource", resource); } // If we have one if (producer) { // Get the producer properties mlt_properties producer_properties = MLT_PRODUCER_PROPERTIES(producer); // Ensure that we loop mlt_properties_set(producer_properties, "eof", "loop"); // Now pass all producer. properties on the transition down mlt_properties_pass(producer_properties, properties, "producer."); // We will get the alpha frame from the producer mlt_frame luma_frame = NULL; // Determine if producer is a video clip or still image const char *service_name = mlt_properties_get(producer_properties, "mlt_service"); int is_clip = mlt_producer_get_length(producer) > 1 && mlt_properties_get_int(producer_properties, "video_index") >= 0 && service_name && !strncmp("avformat", service_name, 8); if (is_clip) { if (!mlt_properties_get(producer_properties, "producer.eof")) mlt_properties_set(producer_properties, "eof", "pause"); mlt_producer_seek(producer, mlt_transition_get_position(transition, a_frame)); } // Get the luma frame if (mlt_service_get_frame(MLT_PRODUCER_SERVICE(producer), &luma_frame, 0) == 0) { uint8_t *luma_image = NULL; mlt_image_format luma_format = mlt_image_yuv422; // Get image from the luma producer mlt_frame_get_image(luma_frame, &luma_image, &luma_format, &luma_width, &luma_height, 0); // Generate the luma map if (luma_image) { if (is_clip) { yuv422_to_luma16(luma_image, &luma_bitmap, luma_width, luma_height, mlt_properties_get_int(MLT_FRAME_PROPERTIES(luma_frame), "full_range")); } else { mlt_luma_map_from_yuv422(luma_image, &luma_bitmap, luma_width, luma_height); } } // Set the transition properties mlt_properties_set_int(properties, "width", luma_width); mlt_properties_set_int(properties, "height", luma_height); mlt_properties_set_data(properties, "bitmap", luma_bitmap, luma_width * luma_height * 2, mlt_pool_release, NULL); // Cleanup the luma frame mlt_frame_close(luma_frame); } if (is_clip) { // Save the producer for getting next frame mlt_properties_set_data(properties, "producer", producer, 0, (mlt_destructor) mlt_producer_close, NULL); } else { // Cleanup the luma producer mlt_properties_clear(properties, "producer"); mlt_producer_close(producer); producer = NULL; } } } } // Arbitrary composite defaults float mix = mlt_transition_get_progress(transition, a_frame); float frame_delta = mlt_transition_get_progress_delta(transition, a_frame); float luma_softness = mlt_properties_get_double(properties, "softness"); int progressive = mlt_properties_get_int(a_props, "consumer.progressive") || mlt_properties_get_int(properties, "progressive") || mlt_properties_get_int(b_props, "luma.progressive"); int top_field_first = mlt_properties_get_int(b_props, "top_field_first"); int reverse = mlt_properties_get_int(properties, "reverse"); int invert = mlt_properties_get_int(properties, "invert"); int threads = CLAMP(mlt_properties_get_int(properties, "threads"), 0, mlt_slices_count_normal()); int alpha_over = mlt_properties_get_int(properties, "alpha_over"); // Honour the reverse here if (mix >= 1.0) mix -= floor(mix); if (mlt_properties_get(properties, "fixed")) mix = mlt_properties_get_double(properties, "fixed"); if (producer) { invert = !invert; mix = 0.5f; } else { mlt_service_unlock(MLT_TRANSITION_SERVICE(transition)); } int fix_background_alpha = mlt_properties_get_int(properties, "fix_background_alpha"); if (luma_width > 0 && luma_height > 0 && luma_bitmap != NULL) { reverse = invert ? !reverse : reverse; mix = reverse ? 1 - mix : mix; frame_delta *= reverse ? -1.0 : 1.0; // Composite the frames using a luma map luma_composite(!invert ? a_frame : b_frame, !invert ? b_frame : a_frame, luma_width, luma_height, luma_bitmap, mix, frame_delta, luma_softness, progressive ? -1 : top_field_first, width, height, invert, fix_background_alpha); } else { mix = (reverse || invert) ? 1 - mix : mix; invert = 0; // Dissolve the frames using the time offset for mix value dissolve_yuv(a_frame, b_frame, mix, *width, *height, threads, alpha_over, fix_background_alpha); } if (producer) { mlt_service_unlock(MLT_TRANSITION_SERVICE(transition)); } // Extract the a_frame image info *width = mlt_properties_get_int(!invert ? a_props : b_props, "width"); *height = mlt_properties_get_int(!invert ? a_props : b_props, "height"); *image = mlt_properties_get_data(!invert ? a_props : b_props, "image", NULL); return 0; } /** Luma transition processing. */ static mlt_frame transition_process(mlt_transition transition, mlt_frame a_frame, mlt_frame b_frame) { // Push the transition on to the frame mlt_frame_push_service(a_frame, transition); // Push the b_frame on to the stack mlt_frame_push_frame(a_frame, b_frame); // Push the transition method mlt_frame_push_get_image(a_frame, transition_get_image); return a_frame; } /** Constructor for the filter. */ mlt_transition transition_luma_init(mlt_profile profile, mlt_service_type type, const char *id, char *lumafile) { mlt_transition transition = mlt_transition_new(); if (transition != NULL) { // Set the methods transition->process = transition_process; // Default factory mlt_properties_set(MLT_TRANSITION_PROPERTIES(transition), "factory", mlt_environment("MLT_PRODUCER")); // Set the main property mlt_properties_set(MLT_TRANSITION_PROPERTIES(transition), "resource", lumafile); // Inform apps and framework that this is a video only transition mlt_properties_set_int(MLT_TRANSITION_PROPERTIES(transition), "_transition_type", 1); return transition; } return NULL; } mlt-7.22.0/src/modules/core/transition_luma.yml000664 000000 000000 00000004630 14531534050 021503 0ustar00rootroot000000 000000 schema_version: 0.3 type: transition identifier: luma title: Wipe version: 2 copyright: Meltytech, LLC creator: Dan Dennedy license: LGPLv2.1 language: en tags: - Video description: > A generic dissolve and wipe transition processor. "luma" gets its name from how it uses a grayscale "map" file. As the luma value varies over time, a threshold filter is applied to the map to determine what parts of frame A vs. frame B to show. It reads PGM files up to 16 bits! Alternatively, it can use the first frame from any producer that outputs yuv, but it will be limited to the luma gamut of 220 values. This performs field-based rendering unless the A frame property "progressive" or "consumer_progressive" or the transition property "progressive" is set to 1. bugs: - Assumes lower field first output. parameters: - identifier: resource title: Luma map file type: string description: > Either PGM or any other producible video. If not supplied, performs a dissolve. argument: yes - identifier: factory title: Factory type: string description: > The name of a factory service used as a non-PGM producer loader. default: loader - identifier: softness title: Softness type: float mutable: yes description: > Only when using a luma map, how soft to make the edges between A and B. 0.0 = no softness. 1.0 = too soft. - identifier: reverse title: Reverse type: integer mutable: yes description: > Reverse the direction of the transition. default: 0 - identifier: producer.* title: Producer mutable: yes description: > Properties may be set on the encapsulated producer. Any property starting with "producer." is passed to the non-PGM luma producer. readonly: no - identifier: alpha_over title: Use over-blending on the alpha channel type: boolean default: 0 mutable: yes - identifier: fix_background_alpha title: Ensure padding is transparent description: > This is a fix for version 2 that ensures the background of sources without an alpha channel and aspect ratio that does not match the profile get padding that includes an alpha channel. Basically, this should be the new default behavior, but a property is needed to not unexpectedly change the result of existing projects and scripts. type: boolean default: 0 mutable: yes mlt-7.22.0/src/modules/core/transition_matte.c000664 000000 000000 00000016741 14531534050 021306 0ustar00rootroot000000 000000 /* * transition_matte.c -- replace alpha channel of track * * Copyright (C) 2003-2014 Meltytech, LLC * Author: Maksym Veremeyenko * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #if defined(USE_SSE) && defined(ARCH_X86_64) static void __attribute__((noinline)) copy_Y_to_A_scaled_luma_sse(uint8_t *alpha_a, uint8_t *image_b, int cnt) { const static unsigned char const1[] = {43, 0, 43, 0, 43, 0, 43, 0, 43, 0, 43, 0, 43, 0, 43, 0}; const static unsigned char const2[] = {16, 0, 16, 0, 16, 0, 16, 0, 16, 0, 16, 0, 16, 0, 16, 0}; const static unsigned char const3[] = {235, 0, 235, 0, 235, 0, 235, 0, 235, 0, 235, 0, 235, 0, 235, 0}; const static unsigned char const4[] = {255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0, 255, 0}; __asm__ volatile( "movdqu (%[equ43]), %%xmm7 \n\t" /* load multiplier 43 */ "movdqu (%[equ16]), %%xmm6 \n\t" /* load bottom value 16 */ "movdqu (%[equ235]), %%xmm5 \n\t" /* load bottom value 235 */ "movdqu (%[equ255]), %%xmm4 \n\t" /* load bottom value 0xff */ "loop_start: \n\t" /* load pixels block 1 */ "movdqu 0(%[image_b]), %%xmm0 \n\t" "add $0x10, %[image_b] \n\t" /* load pixels block 2 */ "movdqu 0(%[image_b]), %%xmm1 \n\t" "add $0x10, %[image_b] \n\t" /* leave only Y */ "pand %%xmm4, %%xmm0 \n\t" "pand %%xmm4, %%xmm1 \n\t" /* upper range clip */ "pminsw %%xmm5, %%xmm0 \n\t" "pminsw %%xmm5, %%xmm1 \n\t" /* upper range clip */ "pmaxsw %%xmm6, %%xmm0 \n\t" "pmaxsw %%xmm6, %%xmm1 \n\t" /* upper range clip */ "psubw %%xmm6, %%xmm0 \n\t" "psubw %%xmm6, %%xmm1 \n\t" /* duplicate values */ "movdqa %%xmm0,%%xmm2 \n\t" "movdqa %%xmm1,%%xmm3 \n\t" /* regA = regA << 8 */ "psllw $8, %%xmm0 \n\t" "psllw $8, %%xmm1 \n\t" /* regB = regB * 47 */ "pmullw %%xmm7, %%xmm2 \n\t" "pmullw %%xmm7, %%xmm3 \n\t" /* regA = regA + regB */ "paddw %%xmm2, %%xmm0 \n\t" "paddw %%xmm3, %%xmm1 \n\t" /* regA = regA >> 8 */ "psrlw $8, %%xmm0 \n\t" "psrlw $8, %%xmm1 \n\t" /* pack to 8 bit value */ "packuswb %%xmm1, %%xmm0 \n\t" /* store */ "movdqu %%xmm0, (%[alpha_a]) \n\t" "add $0x10, %[alpha_a] \n\t" /* loop if we done */ "dec %[cnt] \n\t" "jnz loop_start \n\t" : [cnt] "+r"(cnt), [alpha_a] "+r"(alpha_a), [image_b] "+r"(image_b) : [equ43] "r"(const1), [equ16] "r"(const2), [equ235] "r"(const3), [equ255] "r"(const4) : "%xmm0", "%xmm1", "%xmm2", "%xmm3", "%xmm4", "%xmm5", "%xmm6", "%xmm7"); }; #endif static void copy_Y_to_A_scaled_luma( uint8_t *alpha_a, int stride_a, uint8_t *image_b, int stride_b, int width, int height) { int i, j; for (j = 0; j < height; j++) { i = 0; #if defined(USE_SSE) && defined(ARCH_X86_64) if (width >= 16) { copy_Y_to_A_scaled_luma_sse(alpha_a, image_b, width >> 4); i = (width >> 4) << 4; } #endif for (; i < width; i++) { unsigned int p = image_b[2 * i]; if (p < 16) p = 16; if (p > 235) p = 235; /* p = (p - 16) * 255 / 219; */ p -= 16; p = ((p << 8) + (p * 43)) >> 8; alpha_a[i] = p; }; alpha_a += stride_a; image_b += stride_b; }; }; /** Get the image. */ static int transition_get_image(mlt_frame a_frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { // Get the b frame from the stack mlt_frame b_frame = mlt_frame_pop_frame(a_frame); mlt_frame_get_image(a_frame, image, format, width, height, 1); // Get the properties of the a frame mlt_properties a_props = MLT_FRAME_PROPERTIES(a_frame); int width_a = mlt_properties_get_int(a_props, "width"), width_b = width_a, height_a = mlt_properties_get_int(a_props, "height"), height_b = height_a; uint8_t *alpha_a, *image_b; // This transition is yuv422 only *format = mlt_image_yuv422; // Get the image from the a frame mlt_frame_get_image(b_frame, &image_b, format, &width_b, &height_b, 1); // Create the alpha channel based on the b_image dimensions int alpha_width = MIN(width_a, width_b); int alpha_height = MIN(height_a, height_b); int size = alpha_width * alpha_height; alpha_a = mlt_pool_alloc(size); memset(alpha_a, 255, size); mlt_frame_set_alpha(a_frame, alpha_a, size, mlt_pool_release); // Copy luma from image_b to a_frame's new alpha channel copy_Y_to_A_scaled_luma(alpha_a, width_a, image_b, width_b * 2, alpha_width, alpha_height); // Extract the a_frame image info *width = mlt_properties_get_int(a_props, "width"); *height = mlt_properties_get_int(a_props, "height"); *image = mlt_properties_get_data(a_props, "image", NULL); return 0; } /** Matte transition processing. */ static mlt_frame transition_process(mlt_transition transition, mlt_frame a_frame, mlt_frame b_frame) { // Push the b_frame on to the stack mlt_frame_push_frame(a_frame, b_frame); // Push the transition method mlt_frame_push_get_image(a_frame, transition_get_image); return a_frame; } /** Constructor for the filter. */ mlt_transition transition_matte_init(mlt_profile profile, mlt_service_type type, const char *id, char *lumafile) { mlt_transition transition = mlt_transition_new(); if (transition != NULL) { // Set the methods transition->process = transition_process; // Inform apps and framework that this is a video only transition mlt_properties_set_int(MLT_TRANSITION_PROPERTIES(transition), "_transition_type", 1); return transition; } return NULL; } mlt-7.22.0/src/modules/core/transition_matte.yml000664 000000 000000 00000003553 14531534050 021662 0ustar00rootroot000000 000000 schema_version: 0.1 type: transition identifier: matte title: Matte version: 1 copyright: Meltytech, LLC creator: Maksym Veremeyenko license: LGPLv2.1 language: en tags: - Video description: > Replace the alpha channel of track A with the luma channel from track B. notes: | Please note that the transition automatically detects if the frame from track B has scaled or non-scaled luma. The frame property "full_range" should indicate it. If you need to prepare fill and key (matte) files, you can use melt or ffmpeg to extract alpha channel from existing video: melt sg_gm_2013_clip_title.avi -attach frei0r.alpha0ps 0=0.21 -consumer \ avformat:sg_gm_2013_clip_title.matte_scaled.mp4 crf=10 preset=placebo an=1 ffmpeg -i sg_gm_2013_clip_title.avi -vf "alphaextract" -pix_fmt \ yuv422p -preset placebo -crf 10 -y sg_gm_2013_clip_title.matte_scaled.mp4 Because the example above provides a scaled luma output, the transition performs scaling from [16,235] -> [0, 255]. It is possible to create unscaled (full) range: melt sg_gm_2013_clip_title.avi -attach frei0r.alpha0ps 0=0.21 -consumer \ avformat:sg_gm_2013_clip_title.matte_full.mp4 crf=10 preset=placebo an=1 \ mlt_image_format=rgba pix_fmt=yuvj422p ffmpeg -i sg_gm_2013_clip_title.avi -vf "alphaextract" -pix_fmt \ yuvj422p -preset placebo -crf 10 -y sg_gm_2013_clip_title.matte_full.mp4 The fill can be converted from rgba to yuv422: melt sg_gm_2013_clip_title.avi -consumer avformat:sg_gm_2013_clip_title.fill.mp4 \ crf=10 preset=placebo an=1 ffmpeg -i sg_gm_2013_clip_title.avi -pix_fmt yuv422p -preset placebo -crf 10 -y \ sg_gm_2013_clip_title.fill.mp4 Putting it all together: melt sg_gm_2013_clip_title.matte_full.mp4 -track noise: -track \ sg_gm_2013_clip_title.fill.mp4 -transition matte a_track=2 \ b_track=0 -transition composite a_track=1 b_track=2 mlt-7.22.0/src/modules/core/transition_mix.c000664 000000 000000 00000045706 14531534050 020774 0ustar00rootroot000000 000000 /* * transition_mix.c -- mix two audio streams * Copyright (C) 2003-2020 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include #define MAX_CHANNELS (6) #define MAX_SAMPLES (192000) #define SAMPLE_BYTES(samples, channels) ((samples) * (channels) * sizeof(float)) #define MAX_BYTES SAMPLE_BYTES(MAX_SAMPLES, MAX_CHANNELS) typedef struct transition_mix_s { mlt_transition parent; float src_buffer[MAX_SAMPLES * MAX_CHANNELS]; float dest_buffer[MAX_SAMPLES * MAX_CHANNELS]; int src_buffer_count; int dest_buffer_count; mlt_position previous_frame_a; mlt_position previous_frame_b; } * transition_mix; static void mix_audio(double weight_start, double weight_end, float *buffer_a, float *buffer_b, int channels_a, int channels_b, int channels_out, int samples) { int i, j; double a, b, v; // Compute a smooth ramp over start to end double mix = weight_start; double mix_step = (weight_end - weight_start) / samples; for (i = 0; i < samples; i++) { for (j = 0; j < channels_out; j++) { a = (double) buffer_a[i * channels_a + j]; b = (double) buffer_b[i * channels_b + j]; v = mix * b + (1.0 - mix) * a; buffer_a[i * channels_a + j] = v; } mix += mix_step; } } static void sum_audio(double weight_start, double weight_end, float *buffer_a, float *buffer_b, int channels_a, int channels_b, int channels_out, int samples) { int i, j; double a, b; // Compute a smooth ramp over start to end double mix = weight_start; double mix_step = (weight_end - weight_start) / samples; for (i = 0; i < samples; i++) { for (j = 0; j < channels_out; j++) { a = (double) buffer_a[i * channels_a + j]; b = (double) buffer_b[i * channels_b + j]; buffer_a[i * channels_a + j] = mix * b + a; } mix += mix_step; } } // This filter uses an inline low pass filter to allow mixing without volume hacking. static void combine_audio(double weight, float *buffer_a, float *buffer_b, int channels_a, int channels_b, int channels_out, int samples) { int i, j; double Fc = 0.5; double B = exp(-2.0 * M_PI * Fc); double A = 1.0 - B; double a, b, v; double v_prev[MAX_CHANNELS]; for (j = 0; j < channels_out; j++) v_prev[j] = (double) buffer_a[j]; for (i = 0; i < samples; i++) { for (j = 0; j < channels_out; j++) { a = (double) buffer_a[i * channels_a + j]; b = (double) buffer_b[i * channels_b + j]; v = weight * a + b; v_prev[j] = buffer_a[i * channels_a + j] = v * A + v_prev[j] * B; } } } /** Get the audio. */ static int transition_get_audio(mlt_frame frame_a, void **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples) { int error = 0; // Get the b frame from the stack mlt_frame frame_b = mlt_frame_pop_audio(frame_a); // Get the effect mlt_transition transition = mlt_frame_pop_audio(frame_a); // Get the properties of the b frame mlt_properties b_props = MLT_FRAME_PROPERTIES(frame_b); transition_mix self = transition->child; float *buffer_b, *buffer_a; int frequency_b = *frequency, frequency_a = *frequency; int channels_b = *channels, channels_a = *channels; int samples_b = *samples, samples_a = *samples; // We can only mix interleaved 32-bit float. *format = mlt_audio_f32le; // Get the audio from our producers mlt_frame_get_audio(frame_b, (void **) &buffer_b, format, &frequency_b, &channels_b, &samples_b); mlt_frame_get_audio(frame_a, (void **) &buffer_a, format, &frequency_a, &channels_a, &samples_a); // Prevent dividing by zero. if (!channels_a || !channels_b || !buffer_a || !buffer_b) return 1; if (buffer_b == buffer_a) { *samples = samples_b; *channels = channels_b; *buffer = buffer_b; *frequency = frequency_b; return error; } // I do not recall what these silent_audio properties are about. int silent = mlt_properties_get_int(MLT_FRAME_PROPERTIES(frame_a), "silent_audio"); mlt_properties_set_int(MLT_FRAME_PROPERTIES(frame_a), "silent_audio", 0); if (silent) memset(buffer_a, 0, samples_a * channels_a * sizeof(float)); silent = mlt_properties_get_int(b_props, "silent_audio"); mlt_properties_set_int(b_props, "silent_audio", 0); if (silent) memset(buffer_b, 0, samples_b * channels_b * sizeof(float)); // At this point we have two frames of audio with possibly differing sample // counts. How to reconcile this? #ifdef KEEP_IT_SIMPLE_AND_STUPID // The simple and stupid way to deal with different sample counts was to // use the lesser of the two. This sounds good. You can #define SIMPLE_AND_STUPID // and hear what it sounds like. *samples = MIN(samples_a, samples_b); *channels = MIN(MIN(channels_b, channels_a), MAX_CHANNELS); *frequency = frequency_a; // Note this direct call to sum_audio() skips ramping and the alternative // mixing methods. sum_audio(1, 1, buffer_a, buffer_b, channels_a, channels_b, *channels, *samples); *buffer = buffer_a; return error; #endif // However, the simple and stupid approach drops samples. Over time, this // can accumulate and cause an A/V sync drift, which addressed in b2640656 // by saving the unused samples in a buffer and then using them first on the // next iteration. // determine number of samples to process *samples = MIN(self->src_buffer_count + samples_b, self->dest_buffer_count + samples_a); *channels = MIN(MIN(channels_b, channels_a), MAX_CHANNELS); *frequency = frequency_a; // Prevent src buffer overflow by discarding oldest samples. samples_b = MIN(samples_b, MAX_SAMPLES * MAX_CHANNELS / channels_b); size_t bytes = SAMPLE_BYTES(samples_b, channels_b); if (SAMPLE_BYTES(self->src_buffer_count + samples_b, channels_b) > MAX_BYTES) { mlt_log_verbose(MLT_TRANSITION_SERVICE(transition), "buffer overflow: src_buffer_count %d\n", self->src_buffer_count); self->src_buffer_count = MAX_SAMPLES * MAX_CHANNELS / channels_b - samples_b; memmove(self->src_buffer, &self->src_buffer[MAX_SAMPLES * MAX_CHANNELS - samples_b * channels_b], SAMPLE_BYTES(samples_b, channels_b)); } // Silence src buffer if discontinuity if (self->src_buffer_count > 0 && mlt_frame_get_position(frame_b) != self->previous_frame_b + 1) memset(self->src_buffer, 0, SAMPLE_BYTES(self->src_buffer_count, channels_b)); self->previous_frame_b = mlt_frame_get_position(frame_b); // Append the new samples from frame B to the src buffer memcpy(&self->src_buffer[self->src_buffer_count * channels_b], buffer_b, bytes); self->src_buffer_count += samples_b; buffer_b = self->src_buffer; // Prevent dest buffer overflow by discarding oldest samples. samples_a = MIN(samples_a, MAX_SAMPLES * MAX_CHANNELS / channels_a); bytes = SAMPLE_BYTES(samples_a, channels_a); if (SAMPLE_BYTES(self->dest_buffer_count + samples_a, channels_a) > MAX_BYTES) { mlt_log_verbose(MLT_TRANSITION_SERVICE(transition), "buffer overflow: dest_buffer_count %d\n", self->dest_buffer_count); self->dest_buffer_count = MAX_SAMPLES * MAX_CHANNELS / channels_a - samples_a; memmove(self->dest_buffer, &self->dest_buffer[MAX_SAMPLES * MAX_CHANNELS - samples_a * channels_a], SAMPLE_BYTES(samples_a, channels_a)); } // Silence dest buffer if discontinuity if (self->dest_buffer_count > 0 && mlt_frame_get_position(frame_a) != self->previous_frame_a + 1) memset(self->dest_buffer, 0, SAMPLE_BYTES(self->dest_buffer_count, channels_a)); self->previous_frame_a = mlt_frame_get_position(frame_a); // Append the new samples from frame A to the dest buffer memcpy(&self->dest_buffer[self->dest_buffer_count * channels_a], buffer_a, bytes); self->dest_buffer_count += samples_a; buffer_a = self->dest_buffer; // Do the mixing. if (mlt_properties_get_int(MLT_TRANSITION_PROPERTIES(transition), "sum")) { double mix_start = 1.0, mix_end = 1.0; if (mlt_properties_get(b_props, "audio.previous_mix")) mix_start = mlt_properties_get_double(b_props, "audio.previous_mix"); if (mlt_properties_get(b_props, "audio.mix")) mix_end = mlt_properties_get_double(b_props, "audio.mix"); if (mlt_properties_get_int(b_props, "audio.reverse")) { mix_start = 1.0 - mix_start; mix_end = 1.0 - mix_end; } sum_audio(mix_start, mix_end, buffer_a, buffer_b, channels_a, channels_b, *channels, *samples); } else if (mlt_properties_get_int(MLT_TRANSITION_PROPERTIES(transition), "combine")) { double weight = 1.0; if (mlt_properties_get_int(MLT_FRAME_PROPERTIES(frame_a), "meta.mixdown")) weight = 1.0 - mlt_properties_get_double(MLT_FRAME_PROPERTIES(frame_a), "meta.volume"); combine_audio(weight, buffer_a, buffer_b, channels_a, channels_b, *channels, *samples); } else { double mix_start = 0.5, mix_end = 0.5; if (mlt_properties_get(b_props, "audio.previous_mix")) mix_start = mlt_properties_get_double(b_props, "audio.previous_mix"); if (mlt_properties_get(b_props, "audio.mix")) mix_end = mlt_properties_get_double(b_props, "audio.mix"); if (mlt_properties_get_int(b_props, "audio.reverse")) { mix_start = 1.0 - mix_start; mix_end = 1.0 - mix_end; } mix_audio(mix_start, mix_end, buffer_a, buffer_b, channels_a, channels_b, *channels, *samples); } // Copy the audio from the dest buffer into the frame. bytes = SAMPLE_BYTES(*samples, *channels); *buffer = mlt_pool_alloc(bytes); memcpy(*buffer, buffer_a, bytes); mlt_frame_set_audio(frame_a, *buffer, *format, bytes, mlt_pool_release); if (mlt_properties_get_int(b_props, "_speed") == 0) { // Flush the buffer when paused and scrubbing. samples_b = self->src_buffer_count; samples_a = self->dest_buffer_count; } else { // It is also not good for A/V sync to let many samples accumulate in // the buffer. This part provides a time-based buffer limit. // Determine the maximum amount of latency permitted in the buffer. int max_latency = CLAMP(*frequency / 1000, 0, MAX_SAMPLES); // samples in 1ms // samples_b becomes the new target src buffer count. samples_b = CLAMP(self->src_buffer_count - *samples, 0, max_latency); // samples_b becomes the number of samples to consume: difference between actual and the target. samples_b = self->src_buffer_count - samples_b; // samples_a becomes the new target dest buffer count. samples_a = CLAMP(self->dest_buffer_count - *samples, 0, max_latency); // samples_a becomes the number of samples to consume: difference between actual and the target. samples_a = self->dest_buffer_count - samples_a; } // Consume the src buffer. self->src_buffer_count -= samples_b; if (self->src_buffer_count) { memmove(self->src_buffer, &self->src_buffer[samples_b * channels_b], SAMPLE_BYTES(self->src_buffer_count, channels_b)); } // Consume the dest buffer. self->dest_buffer_count -= samples_a; if (self->dest_buffer_count > 0) { memmove(self->dest_buffer, &self->dest_buffer[samples_a * channels_a], SAMPLE_BYTES(self->dest_buffer_count, channels_a)); } return error; } /** Mix transition processing. */ static mlt_frame transition_process(mlt_transition transition, mlt_frame a_frame, mlt_frame b_frame) { mlt_properties properties = MLT_TRANSITION_PROPERTIES(transition); mlt_properties b_props = MLT_FRAME_PROPERTIES(b_frame); // Only if mix is specified, otherwise a producer may set the mix if (mlt_properties_get(properties, "start")) { // Determine the time position of this frame in the transition duration mlt_properties props = mlt_properties_get_data(MLT_FRAME_PROPERTIES(b_frame), "_producer", NULL); mlt_position in = mlt_properties_get_int(props, "in"); mlt_position out = mlt_properties_get_int(props, "out"); int length = mlt_properties_get_int(properties, "length"); mlt_position time = mlt_properties_get_int(props, "_frame"); double mix = mlt_transition_get_progress(transition, b_frame); if (mlt_properties_get_int(properties, "always_active")) mix = (double) (time - in) / (double) (out - in + 1); // TODO: Check the logic here - shouldn't we be computing current and next mixing levels in all cases? if (length == 0) { // If there is an end mix level adjust mix to the range if (mlt_properties_get(properties, "end")) { double start = mlt_properties_get_double(properties, "start"); double end = mlt_properties_get_double(properties, "end"); mix = start + (end - start) * mix; } // A negative means total crossfade (uses position) else if (mlt_properties_get_double(properties, "start") >= 0) { // Otherwise, start/constructor is a constant mix level mix = mlt_properties_get_double(properties, "start"); } // Finally, set the mix property on the frame mlt_properties_set_double(b_props, "audio.mix", mix); // Initialise transition previous mix value to prevent an inadvertent jump from 0 mlt_position last_position = mlt_properties_get_position(properties, "_last_position"); mlt_position current_position = mlt_frame_get_position(b_frame); mlt_properties_set_position(properties, "_last_position", current_position); if (!mlt_properties_get(properties, "_previous_mix") || current_position != last_position + 1) mlt_properties_set_double(properties, "_previous_mix", mix); // Tell b frame what the previous mix level was mlt_properties_set_double(b_props, "audio.previous_mix", mlt_properties_get_double(properties, "_previous_mix")); // Save the current mix level for the next iteration mlt_properties_set_double(properties, "_previous_mix", mlt_properties_get_double(b_props, "audio.mix")); mlt_properties_set_double(b_props, "audio.reverse", mlt_properties_get_double(properties, "reverse")); } else { double level = mlt_properties_get_double(properties, "start"); double mix_start = level; double mix_end = mix_start; double mix_increment = 1.0 / length; if (time - in < length) { mix_start = mix_start * ((double) (time - in) / length); mix_end = mix_start + mix_increment; } else if (time > out - length) { mix_end = mix_start * ((double) (out - time - in) / length); mix_start = mix_end - mix_increment; } mix_start = mix_start < 0 ? 0 : mix_start > level ? level : mix_start; mix_end = mix_end < 0 ? 0 : mix_end > level ? level : mix_end; mlt_properties_set_double(b_props, "audio.previous_mix", mix_start); mlt_properties_set_double(b_props, "audio.mix", mix_end); } } // Override the get_audio method mlt_frame_push_audio(a_frame, transition); mlt_frame_push_audio(a_frame, b_frame); mlt_frame_push_audio(a_frame, transition_get_audio); // Ensure transition_get_audio is called if test_audio=1. if (mlt_properties_get_int(properties, "accepts_blanks")) mlt_properties_set_int(MLT_FRAME_PROPERTIES(a_frame), "test_audio", 0); return a_frame; } static void transition_close(mlt_transition transition) { free(transition->child); transition->close = NULL; mlt_transition_close(transition); } /** Constructor for the transition. */ mlt_transition transition_mix_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { transition_mix mix = calloc(1, sizeof(struct transition_mix_s)); mlt_transition transition = calloc(1, sizeof(struct mlt_transition_s)); if (mix && transition && !mlt_transition_init(transition, mix)) { mix->parent = transition; transition->close = transition_close; transition->process = transition_process; if (arg) { mlt_properties_set_double(MLT_TRANSITION_PROPERTIES(transition), "start", atof(arg)); if (atof(arg) < 0) mlt_properties_set_int(MLT_TRANSITION_PROPERTIES(transition), "accepts_blanks", 1); } // Inform apps and framework that this is an audio only transition mlt_properties_set_int(MLT_TRANSITION_PROPERTIES(transition), "_transition_type", 2); } else { if (transition) mlt_transition_close(transition); if (mix) free(mix); } return transition; } mlt-7.22.0/src/modules/core/transition_mix.yml000664 000000 000000 00000003530 14531534050 021340 0ustar00rootroot000000 000000 schema_version: 0.2 type: transition identifier: mix title: Mix version: 1 copyright: Meltytech, LLC creator: Dan Dennedy license: LGPLv2.1 language: en tags: - Audio description: Mix two audio tracks. bugs: - Samples from the longer of the two frames are discarded. parameters: - identifier: start title: Start argument: yes type: float mutable: yes description: > The mix level to apply to the second frame. Any negative value causes an automatic crossfade from 0 to 1. - identifier: end title: End type: float mutable: yes description: > The ending value of the mix level. Mix level will be interpolated from start to end over the in-out range. - identifier: reverse title: Reverse type: boolean mutable: yes description: > Set to 1 to reverse the direction of the mix. default: 0 widget: checkbox - identifier: combine title: Use an alternative mixing algorithm description: > Mix using a low pass filter to prevent affecting audio levels. However, this may introduce slight artifacts. This is incompatible with start < 0. type: boolean default: 0 mutable: yes - identifier: sum title: Mix by simply adding samples description: > The default mixing algorithm halves the sample values before adding them to absolutely prevent clipping. However, that affects levels. This algorithm simply adds samples and may clip. In many real world scenarios, the signals being mixed typically have headroom in their level and are rarely correlated and thus often will not clip. Also, one can reduce the gain and add a limiter on the mixed output prior to integer quantization to prevent clipping. This mode is incompatible with start < 0. type: boolean default: 0 mutable: yes mlt-7.22.0/src/modules/decklink/000775 000000 000000 00000000000 14531534050 016401 5ustar00rootroot000000 000000 mlt-7.22.0/src/modules/decklink/CMakeLists.txt000664 000000 000000 00000002217 14531534050 021143 0ustar00rootroot000000 000000 add_library(mltdecklink MODULE common.cpp common.h consumer_decklink.cpp producer_decklink.cpp ) file(GLOB YML "*.yml") add_custom_target(Other_decklink_Files SOURCES ${YML} ) target_compile_options(mltdecklink PRIVATE ${MLT_COMPILE_OPTIONS}) target_link_libraries(mltdecklink PRIVATE mlt Threads::Threads) if(WIN32) target_sources(mltdecklink PRIVATE win/DeckLinkAPI_i.cpp) target_include_directories(mltdecklink PRIVATE win) elseif(APPLE) target_sources(mltdecklink PRIVATE darwin/DeckLinkAPIDispatch.cpp) target_include_directories(mltdecklink PRIVATE darwin) target_link_libraries(mltdecklink PRIVATE "-framework CoreFoundation") else() target_sources(mltdecklink PRIVATE linux/DeckLinkAPIDispatch.cpp) target_include_directories(mltdecklink PRIVATE linux) endif() if(CPU_SSE) target_compile_definitions(mltdecklink PRIVATE USE_SSE) endif() set_target_properties(mltdecklink PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${MLT_MODULE_OUTPUT_DIRECTORY}") install(TARGETS mltdecklink LIBRARY DESTINATION ${MLT_INSTALL_MODULE_DIR}) install(FILES consumer_decklink.yml producer_decklink.yml DESTINATION ${MLT_INSTALL_DATA_DIR}/decklink ) mlt-7.22.0/src/modules/decklink/common.cpp000664 000000 000000 00000006660 14531534050 020405 0ustar00rootroot000000 000000 /* * common.cpp -- Blackmagic Design DeckLink common functions * Copyright (C) 2012 Dan Dennedy * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with consumer library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "common.h" #include #include #ifdef __APPLE__ char *getCString(DLString aDLString) { char *CString = (char *) malloc(64); CFStringGetCString(aDLString, CString, 64, kCFStringEncodingMacRoman); return CString; } void freeCString(char *aCString) { if (aCString) free(aCString); } void freeDLString(DLString aDLString) { if (aDLString) CFRelease(aDLString); } #elif defined(_WIN32) char *getCString(DLString aDLString) { char *CString = NULL; if (aDLString) { int size = WideCharToMultiByte(CP_UTF8, 0, aDLString, -1, NULL, 0, NULL, NULL); if (size) { CString = new char[size]; size = WideCharToMultiByte(CP_UTF8, 0, aDLString, -1, CString, size, NULL, NULL); if (!size) { delete[] CString; CString = NULL; } } } return CString; } void freeCString(char *aCString) { delete[] aCString; } void freeDLString(DLString aDLString) { SysFreeString(aDLString); } #else char *getCString(DLString aDLString) { return aDLString ? (char *) aDLString : NULL; } void freeCString(char *aCString) {} void freeDLString(DLString aDLString) { if (aDLString) free((void *) aDLString); } #endif void swab2(const void *from, void *to, int n) { #if defined(USE_SSE) #define SWAB_STEP 16 int cnt = n / SWAB_STEP; __asm__ volatile( /* "loop_start: \n\t" */ "1: \n\t" /* load */ "movdqa 0(%[from]), %%xmm0 \n\t" "add $0x10, %[from] \n\t" /* duplicate to temp registers */ "movdqa %%xmm0, %%xmm1 \n\t" /* shift right temp register */ "psrlw $8, %%xmm1 \n\t" /* shift left main register */ "psllw $8, %%xmm0 \n\t" /* compose them back */ "por %%xmm0, %%xmm1 \n\t" /* save */ "movdqa %%xmm1, 0(%[to]) \n\t" "add $0x10, %[to] \n\t" "dec %[cnt] \n\t" "jnz 1b \n\t" /* "jnz loop_start \n\t" */ : [from] "+r"(from), [to] "+r"(to), [cnt] "+r"(cnt) : : "xmm0", "xmm1"); from = (unsigned char *) from + n - (n % SWAB_STEP); to = (unsigned char *) to + n - (n % SWAB_STEP); n = (n % SWAB_STEP); #endif swab((char *) from, (char *) to, n); }; mlt-7.22.0/src/modules/decklink/common.h000664 000000 000000 00000002605 14531534050 020045 0ustar00rootroot000000 000000 /* * common.h -- Blackmagic Design DeckLink common functions * Copyright (C) 2012 Dan Dennedy * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with consumer library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef DECKLINK_COMMON_H #define DECKLINK_COMMON_H #ifdef _WIN32 #include "DeckLinkAPI_h.h" #include typedef BSTR DLString; #else #include "DeckLinkAPI.h" #ifdef __APPLE__ typedef CFStringRef DLString; #else typedef const char *DLString; #endif #endif #define SAFE_RELEASE(V) \ if (V) { \ V->Release(); \ V = NULL; \ } char *getCString(DLString aDLString); void freeCString(char *aCString); void freeDLString(DLString aDLString); void swab2(const void *from, void *to, int n); #endif // DECKLINK_COMMON_H mlt-7.22.0/src/modules/decklink/consumer_decklink.cpp000664 000000 000000 00000112426 14531534050 022612 0ustar00rootroot000000 000000 /* * consumer_decklink.cpp -- output through Blackmagic Design DeckLink * Copyright (C) 2010-2023 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with consumer library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #define __STDC_FORMAT_MACROS /* see inttypes.h */ #include "common.h" #include #include #include #include #include #include #include #define SWAB_SLICED_ALIGN_POW 5 static int swab_sliced(int id, int idx, int jobs, void *cookie) { unsigned char **args = (unsigned char **) cookie; ssize_t sz = (ssize_t) args[2]; ssize_t bsz = ((sz / jobs + (1 << SWAB_SLICED_ALIGN_POW) - 1) >> SWAB_SLICED_ALIGN_POW) << SWAB_SLICED_ALIGN_POW; ssize_t offset = bsz * idx; if (offset < sz) { if ((offset + bsz) > sz) bsz = sz - offset; swab2(args[0] + offset, args[1] + offset, bsz); } return 0; }; static const unsigned PREROLL_MINIMUM = 3; enum { OP_NONE = 0, OP_OPEN, OP_START, OP_STOP, OP_EXIT }; class DeckLinkConsumer : public IDeckLinkVideoOutputCallback, public IDeckLinkAudioOutputCallback { private: mlt_consumer_s m_consumer; IDeckLink *m_deckLink; IDeckLinkOutput *m_deckLinkOutput; IDeckLinkDisplayMode *m_displayMode; int m_width; int m_height; BMDTimeValue m_duration; BMDTimeScale m_timescale; double m_fps; uint64_t m_count; int m_outChannels; int m_inChannels; bool m_isAudio; int m_isKeyer; IDeckLinkKeyer *m_deckLinkKeyer; bool m_terminate_on_pause; uint32_t m_preroll; uint32_t m_reprio; mlt_deque m_aqueue; pthread_mutex_t m_aqueue_lock; mlt_deque m_frames; pthread_mutex_t m_op_lock; pthread_mutex_t m_op_arg_mutex; pthread_cond_t m_op_arg_cond; int m_op_id; int m_op_res; int m_op_arg; pthread_t m_op_thread; bool m_sliced_swab; uint8_t *m_buffer; IDeckLinkDisplayMode *getDisplayMode() { mlt_profile profile = mlt_service_profile(MLT_CONSUMER_SERVICE(getConsumer())); IDeckLinkDisplayModeIterator *iter = NULL; IDeckLinkDisplayMode *mode = NULL; IDeckLinkDisplayMode *result = 0; if (m_deckLinkOutput->GetDisplayModeIterator(&iter) == S_OK) { while (!result && iter->Next(&mode) == S_OK) { m_width = mode->GetWidth(); m_height = mode->GetHeight(); mode->GetFrameRate(&m_duration, &m_timescale); m_fps = (double) m_timescale / m_duration; int p = mode->GetFieldDominance() == bmdProgressiveFrame; mlt_log_verbose(getConsumer(), "BMD mode %dx%d %.3f fps prog %d\n", m_width, m_height, m_fps, p); if (m_width == profile->width && p == profile->progressive && (int) m_fps == (int) mlt_profile_fps(profile) && (m_height == profile->height || (m_height == 486 && profile->height == 480))) result = mode; else SAFE_RELEASE(mode); } SAFE_RELEASE(iter); } return result; } public: mlt_consumer getConsumer() { return &m_consumer; } DeckLinkConsumer() { pthread_mutexattr_t mta; m_displayMode = NULL; m_deckLinkKeyer = NULL; m_deckLinkOutput = NULL; m_deckLink = NULL; m_aqueue = mlt_deque_init(); m_frames = mlt_deque_init(); m_buffer = NULL; // operation locks m_op_id = OP_NONE; m_op_arg = 0; pthread_mutexattr_init(&mta); pthread_mutexattr_settype(&mta, PTHREAD_MUTEX_RECURSIVE); pthread_mutex_init(&m_op_lock, &mta); pthread_mutex_init(&m_op_arg_mutex, &mta); pthread_mutex_init(&m_aqueue_lock, &mta); pthread_mutexattr_destroy(&mta); pthread_cond_init(&m_op_arg_cond, NULL); pthread_create(&m_op_thread, NULL, op_main, this); } virtual ~DeckLinkConsumer() { mlt_log_debug(getConsumer(), "%s: entering\n", __FUNCTION__); SAFE_RELEASE(m_displayMode); SAFE_RELEASE(m_deckLinkKeyer); SAFE_RELEASE(m_deckLinkOutput); SAFE_RELEASE(m_deckLink); mlt_deque_close(m_aqueue); mlt_deque_close(m_frames); op(OP_EXIT, 0); mlt_log_debug(getConsumer(), "%s: waiting for op thread\n", __FUNCTION__); pthread_join(m_op_thread, NULL); mlt_log_debug(getConsumer(), "%s: finished op thread\n", __FUNCTION__); pthread_mutex_destroy(&m_aqueue_lock); pthread_mutex_destroy(&m_op_lock); pthread_mutex_destroy(&m_op_arg_mutex); pthread_cond_destroy(&m_op_arg_cond); mlt_log_debug(getConsumer(), "%s: exiting\n", __FUNCTION__); } int op(int op_id, int arg) { int r; // lock operation mutex pthread_mutex_lock(&m_op_lock); mlt_log_debug(getConsumer(), "%s: op_id=%d\n", __FUNCTION__, op_id); // notify op id pthread_mutex_lock(&m_op_arg_mutex); m_op_id = op_id; m_op_arg = arg; pthread_cond_signal(&m_op_arg_cond); pthread_mutex_unlock(&m_op_arg_mutex); // wait op done pthread_mutex_lock(&m_op_arg_mutex); while (OP_NONE != m_op_id) pthread_cond_wait(&m_op_arg_cond, &m_op_arg_mutex); pthread_mutex_unlock(&m_op_arg_mutex); // save result r = m_op_res; mlt_log_debug(getConsumer(), "%s: r=%d\n", __FUNCTION__, r); // unlock operation mutex pthread_mutex_unlock(&m_op_lock); return r; } protected: static void *op_main(void *thisptr) { DeckLinkConsumer *d = static_cast(thisptr); mlt_log_debug(d->getConsumer(), "%s: entering\n", __FUNCTION__); for (;;) { int o, r = 0; // wait op command pthread_mutex_lock(&d->m_op_arg_mutex); while (OP_NONE == d->m_op_id) pthread_cond_wait(&d->m_op_arg_cond, &d->m_op_arg_mutex); pthread_mutex_unlock(&d->m_op_arg_mutex); o = d->m_op_id; mlt_log_debug(d->getConsumer(), "%s:%d d->m_op_id=%d\n", __FUNCTION__, __LINE__, d->m_op_id); switch (d->m_op_id) { case OP_OPEN: r = d->m_op_res = d->open(d->m_op_arg); break; case OP_START: r = d->m_op_res = d->start(d->m_op_arg); break; case OP_STOP: r = d->m_op_res = d->stop(); break; }; // notify op done pthread_mutex_lock(&d->m_op_arg_mutex); d->m_op_id = OP_NONE; pthread_cond_signal(&d->m_op_arg_cond); pthread_mutex_unlock(&d->m_op_arg_mutex); // post for async if (OP_START == o && r) d->preroll(); if (OP_EXIT == o) { mlt_log_debug(d->getConsumer(), "%s: exiting\n", __FUNCTION__); return NULL; } }; return NULL; } bool open(unsigned card = 0) { unsigned i = 0; #ifdef _WIN32 IDeckLinkIterator *deckLinkIterator = NULL; HRESULT result = CoInitialize(NULL); if (FAILED(result)) { mlt_log_error(getConsumer(), "COM initialization failed\n"); return false; } result = CoCreateInstance(CLSID_CDeckLinkIterator, NULL, CLSCTX_ALL, IID_IDeckLinkIterator, (void **) &deckLinkIterator); if (FAILED(result)) { mlt_log_warning(getConsumer(), "The DeckLink drivers not installed.\n"); return false; } #else IDeckLinkIterator *deckLinkIterator = CreateDeckLinkIteratorInstance(); if (!deckLinkIterator) { mlt_log_warning(getConsumer(), "The DeckLink drivers not installed.\n"); return false; } #endif // Connect to the Nth DeckLink instance for (i = 0; deckLinkIterator->Next(&m_deckLink) == S_OK; i++) { if (i == card) break; else SAFE_RELEASE(m_deckLink); } SAFE_RELEASE(deckLinkIterator); if (!m_deckLink) { mlt_log_error(getConsumer(), "DeckLink card not found\n"); return false; } // Obtain the audio/video output interface (IDeckLinkOutput) if (m_deckLink->QueryInterface(IID_IDeckLinkOutput, (void **) &m_deckLinkOutput) != S_OK) { mlt_log_error(getConsumer(), "No DeckLink cards support output\n"); SAFE_RELEASE(m_deckLink); return false; } // Get the keyer interface IDeckLinkAttributes *deckLinkAttributes = 0; if (m_deckLink->QueryInterface(IID_IDeckLinkAttributes, (void **) &deckLinkAttributes) == S_OK) { #ifdef _WIN32 BOOL flag = FALSE; #else bool flag = false; #endif if (deckLinkAttributes->GetFlag(BMDDeckLinkSupportsInternalKeying, &flag) == S_OK && flag) { if (m_deckLink->QueryInterface(IID_IDeckLinkKeyer, (void **) &m_deckLinkKeyer) != S_OK) { mlt_log_error(getConsumer(), "Failed to get keyer\n"); SAFE_RELEASE(m_deckLinkOutput); SAFE_RELEASE(m_deckLink); return false; } } SAFE_RELEASE(deckLinkAttributes); } // Provide this class as a delegate to the audio and video output interfaces m_deckLinkOutput->SetScheduledFrameCompletionCallback(this); m_deckLinkOutput->SetAudioCallback(this); return true; } int preroll() { mlt_properties properties = MLT_CONSUMER_PROPERTIES(getConsumer()); mlt_log_debug(getConsumer(), "%s: starting\n", __FUNCTION__); if (!mlt_properties_get_int(properties, "running")) return 0; mlt_log_verbose(getConsumer(), "preroll %u frames\n", m_preroll); // preroll frames for (unsigned i = 0; i < m_preroll; i++) ScheduleNextFrame(true); // start audio preroll if (m_isAudio) m_deckLinkOutput->BeginAudioPreroll(); else m_deckLinkOutput->StartScheduledPlayback(0, m_timescale, 1.0); mlt_log_debug(getConsumer(), "%s: exiting\n", __FUNCTION__); return 0; } bool start(unsigned preroll) { mlt_properties properties = MLT_CONSUMER_PROPERTIES(getConsumer()); // Initialize members m_count = 0; m_buffer = NULL; preroll = preroll < PREROLL_MINIMUM ? PREROLL_MINIMUM : preroll; m_inChannels = mlt_properties_get_int(properties, "channels"); if (m_inChannels <= 2) { m_outChannels = 2; } else if (m_inChannels <= 8) { m_outChannels = 8; } else { m_outChannels = 16; } m_isAudio = !mlt_properties_get_int(properties, "audio_off"); m_terminate_on_pause = mlt_properties_get_int(properties, "terminate_on_pause"); m_displayMode = getDisplayMode(); if (!m_displayMode) { mlt_log_error(getConsumer(), "Profile is not compatible with decklink.\n"); return false; } mlt_properties_set_int(properties, "top_field_first", m_displayMode->GetFieldDominance() == bmdUpperFieldFirst); // Set the keyer if (m_deckLinkKeyer && (m_isKeyer = mlt_properties_get_int(properties, "keyer"))) { bool external = (m_isKeyer == 2); double level = mlt_properties_get_double(properties, "keyer_level"); if (m_deckLinkKeyer->Enable(external) != S_OK) mlt_log_error(getConsumer(), "Failed to enable %s keyer\n", external ? "external" : "internal"); m_deckLinkKeyer->SetLevel(level <= 1 ? (level > 0 ? 255 * level : 255) : 255); } else if (m_deckLinkKeyer) { m_deckLinkKeyer->Disable(); } // Set the video output mode if (S_OK != m_deckLinkOutput->EnableVideoOutput(m_displayMode->GetDisplayMode(), (BMDVideoOutputFlags) (bmdVideoOutputFlagDefault | bmdVideoOutputRP188 | bmdVideoOutputVITC))) { mlt_log_error(getConsumer(), "Failed to enable video output\n"); return false; } // Set the audio output mode if (m_isAudio && S_OK != m_deckLinkOutput->EnableAudioOutput(bmdAudioSampleRate48kHz, bmdAudioSampleType16bitInteger, m_outChannels, bmdAudioOutputStreamTimestamped)) { mlt_log_error(getConsumer(), "Failed to enable audio output\n"); stop(); return false; } m_preroll = preroll; m_reprio = 2; for (unsigned i = 0; i < (m_preroll + 2); i++) { IDeckLinkMutableVideoFrame *frame; // Generate a DeckLink video frame if (S_OK != m_deckLinkOutput->CreateVideoFrame(m_width, m_height, m_width * (m_isKeyer ? 4 : 2), m_isKeyer ? bmdFormat8BitARGB : bmdFormat8BitYUV, bmdFrameFlagDefault, &frame)) { mlt_log_error(getConsumer(), "%s: CreateVideoFrame (%d) failed\n", __FUNCTION__, i); return false; } mlt_deque_push_back(m_frames, frame); } // Set the running state mlt_properties_set_int(properties, "running", 1); return true; } bool stop() { mlt_properties properties = MLT_CONSUMER_PROPERTIES(getConsumer()); mlt_log_debug(getConsumer(), "%s: starting\n", __FUNCTION__); // Stop the audio and video output streams immediately if (m_deckLinkOutput) { m_deckLinkOutput->StopScheduledPlayback(0, 0, 0); m_deckLinkOutput->DisableAudioOutput(); m_deckLinkOutput->DisableVideoOutput(); } pthread_mutex_lock(&m_aqueue_lock); while (mlt_frame frame = (mlt_frame) mlt_deque_pop_back(m_aqueue)) mlt_frame_close(frame); pthread_mutex_unlock(&m_aqueue_lock); m_buffer = NULL; while (IDeckLinkMutableVideoFrame *frame = (IDeckLinkMutableVideoFrame *) mlt_deque_pop_back(m_frames)) SAFE_RELEASE(frame); // set running state is 0 mlt_properties_set_int(properties, "running", 0); mlt_consumer_stopped(getConsumer()); mlt_log_debug(getConsumer(), "%s: exiting\n", __FUNCTION__); return true; } void renderAudio(mlt_frame frame) { mlt_properties properties; properties = MLT_FRAME_PROPERTIES(frame); mlt_properties_set_int64(properties, "m_count", m_count); mlt_properties_inc_ref(properties); pthread_mutex_lock(&m_aqueue_lock); mlt_deque_push_back(m_aqueue, frame); mlt_log_debug(getConsumer(), "%s:%d frame=%p, len=%d\n", __FUNCTION__, __LINE__, frame, mlt_deque_count(m_aqueue)); pthread_mutex_unlock(&m_aqueue_lock); } void renderVideo(mlt_frame frame) { HRESULT hr; mlt_image_format format = m_isKeyer ? mlt_image_rgba : mlt_image_yuv422; uint8_t *image = 0; int rendered = mlt_properties_get_int(MLT_FRAME_PROPERTIES(frame), "rendered"); mlt_properties consumer_properties = MLT_CONSUMER_PROPERTIES(getConsumer()); int stride = m_width * (m_isKeyer ? 4 : 2); int height = m_height; IDeckLinkMutableVideoFrame *decklinkFrame = static_cast( mlt_deque_pop_front(m_frames)); mlt_log_debug(getConsumer(), "%s: entering\n", __FUNCTION__); m_sliced_swab = mlt_properties_get_int(consumer_properties, "sliced_swab"); if (rendered && !mlt_frame_get_image(frame, &image, &format, &m_width, &height, 0)) { if (decklinkFrame) decklinkFrame->GetBytes((void **) &m_buffer); if (m_buffer) { // NTSC SDI is always 486 lines if (m_height == 486 && height == 480) { // blank first 6 lines if (m_isKeyer) { memset(m_buffer, 0, stride * 6); m_buffer += stride * 6; } else for (int i = 0; i < m_width * 6; i++) { *m_buffer++ = 128; *m_buffer++ = 16; } } if (!m_isKeyer) { unsigned char *arg[3] = {image, m_buffer}; ssize_t size = stride * height; // Normal non-keyer playout - needs byte swapping if (!m_sliced_swab) swab2(arg[0], arg[1], size); else { arg[2] = (unsigned char *) size; mlt_slices_run_fifo(0, swab_sliced, arg); } } else if (!mlt_properties_get_int(MLT_FRAME_PROPERTIES(frame), "test_image")) { // Normal keyer output int y = height + 1; uint32_t *s = (uint32_t *) image; uint32_t *d = (uint32_t *) m_buffer; // Need to relocate alpha channel RGBA => ARGB while (--y) { int x = m_width + 1; while (--x) { *d++ = (*s << 8) | (*s >> 24); s++; } } } else { // Keying blank frames - nullify alpha memset(m_buffer, 0, stride * height); } } } else if (decklinkFrame) { uint8_t *buffer = NULL; decklinkFrame->GetBytes((void **) &buffer); if (buffer) memcpy(buffer, m_buffer, stride * height); } if (decklinkFrame) { char *vitc; // set timecode vitc = mlt_properties_get(MLT_FRAME_PROPERTIES(frame), "meta.attr.vitc.markup"); if (vitc) { int h, m, s, f; if (4 == sscanf(vitc, "%d:%d:%d:%d", &h, &m, &s, &f)) decklinkFrame->SetTimecodeFromComponents(bmdTimecodeVITC, h, m, s, f, bmdTimecodeFlagDefault); } // set userbits vitc = mlt_properties_get(MLT_FRAME_PROPERTIES(frame), "meta.attr.vitc.userbits"); if (vitc) decklinkFrame ->SetTimecodeUserBits(bmdTimecodeVITC, mlt_properties_get_int(MLT_FRAME_PROPERTIES(frame), "meta.attr.vitc.userbits")); hr = m_deckLinkOutput->ScheduleVideoFrame(decklinkFrame, m_count * m_duration, m_duration, m_timescale); if (S_OK != hr) mlt_log_error(getConsumer(), "%s:%d: ScheduleVideoFrame failed, hr=%.8X \n", __FUNCTION__, __LINE__, unsigned(hr)); else mlt_log_debug(getConsumer(), "%s: ScheduleVideoFrame SUCCESS\n", __FUNCTION__); } } HRESULT render(mlt_frame frame) { HRESULT result = S_OK; // Get the audio double speed = mlt_properties_get_double(MLT_FRAME_PROPERTIES(frame), "_speed"); if (m_isAudio && speed == 1.0) renderAudio(frame); // Get the video renderVideo(frame); ++m_count; return result; } void reprio(int target) { int r; pthread_t thread; pthread_attr_t tattr; struct sched_param param; mlt_properties properties; if (m_reprio & target) return; m_reprio |= target; properties = MLT_CONSUMER_PROPERTIES(getConsumer()); if (!mlt_properties_get(properties, "priority")) return; pthread_attr_init(&tattr); pthread_attr_setschedpolicy(&tattr, SCHED_FIFO); if (!strcmp("max", mlt_properties_get(properties, "priority"))) param.sched_priority = sched_get_priority_max(SCHED_FIFO) - 1; else if (!strcmp("min", mlt_properties_get(properties, "priority"))) param.sched_priority = sched_get_priority_min(SCHED_FIFO) + 1; else param.sched_priority = mlt_properties_get_int(properties, "priority"); pthread_attr_setschedparam(&tattr, ¶m); thread = pthread_self(); r = pthread_setschedparam(thread, SCHED_FIFO, ¶m); if (r) mlt_log_error(getConsumer(), "%s: [%d] pthread_setschedparam returned %d\n", __FUNCTION__, target, r); else mlt_log_verbose(getConsumer(), "%s: [%d] param.sched_priority=%d\n", __FUNCTION__, target, param.sched_priority); } // *** DeckLink API implementation of IDeckLinkVideoOutputCallback IDeckLinkAudioOutputCallback *** // // IUnknown needs only a dummy implementation virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, LPVOID *ppv) { return E_NOINTERFACE; } virtual ULONG STDMETHODCALLTYPE AddRef() { return 1; } virtual ULONG STDMETHODCALLTYPE Release() { return 1; } /************************* DeckLink API Delegate Methods *****************************/ #ifdef _WIN32 virtual HRESULT STDMETHODCALLTYPE RenderAudioSamples(BOOL preroll) #else virtual HRESULT STDMETHODCALLTYPE RenderAudioSamples(bool preroll) #endif { pthread_mutex_lock(&m_aqueue_lock); mlt_log_debug(getConsumer(), "%s: ENTERING preroll=%d, len=%d\n", __FUNCTION__, (int) preroll, mlt_deque_count(m_aqueue)); mlt_frame frame = (mlt_frame) mlt_deque_pop_front(m_aqueue); pthread_mutex_unlock(&m_aqueue_lock); reprio(2); if (frame) { mlt_properties properties = MLT_FRAME_PROPERTIES(frame); uint64_t m_count = mlt_properties_get_int64(properties, "m_count"); mlt_audio_format format = mlt_audio_s16; int frequency = bmdAudioSampleRate48kHz; int samples = mlt_audio_calculate_frame_samples(m_fps, frequency, m_count); int16_t *pcm = 0; if (!mlt_frame_get_audio(frame, (void **) &pcm, &format, &frequency, &m_inChannels, &samples)) { HRESULT hr; int16_t *outBuff = NULL; mlt_log_debug(getConsumer(), "%s:%d, samples=%d, channels=%d, freq=%d\n", __FUNCTION__, __LINE__, samples, m_inChannels, frequency); if (m_inChannels != m_outChannels) { int s = 0; int c = 0; int size = mlt_audio_format_size(format, samples, m_outChannels); int16_t *src = pcm; int16_t *dst = (int16_t *) mlt_pool_alloc(size); outBuff = dst; for (s = 0; s < samples; s++) { for (c = 0; c < m_outChannels; c++) { if (c < m_inChannels) { *dst = *src; src++; } else { // Fill silence if there are more out channels than in channels. *dst = 0; } } for (c = 0; c < m_inChannels - m_outChannels; c++) { // Drop samples if there are more in channels than out channels. src++; } } pcm = outBuff; } #ifdef _WIN32 #define DECKLINK_UNSIGNED_FORMAT "%lu" unsigned long written = 0; #else #define DECKLINK_UNSIGNED_FORMAT "%u" uint32_t written = 0; #endif BMDTimeValue streamTime = m_count * frequency * m_duration / m_timescale; #ifdef _WIN32 hr = m_deckLinkOutput->ScheduleAudioSamples(pcm, samples, streamTime, frequency, (unsigned long *) &written); #else hr = m_deckLinkOutput->ScheduleAudioSamples(pcm, samples, streamTime, frequency, &written); #endif if (S_OK != hr) mlt_log_error(getConsumer(), "%s:%d ScheduleAudioSamples failed, hr=%.8X \n", __FUNCTION__, __LINE__, unsigned(hr)); else mlt_log_debug(getConsumer(), "%s:%d ScheduleAudioSamples success " DECKLINK_UNSIGNED_FORMAT " samples\n", __FUNCTION__, __LINE__, written); if (written != (uint32_t) samples) mlt_log_verbose(getConsumer(), "renderAudio: samples=%d, written=" DECKLINK_UNSIGNED_FORMAT "\n", samples, written); mlt_pool_release(outBuff); } else mlt_log_error(getConsumer(), "%s:%d mlt_frame_get_audio failed\n", __FUNCTION__, __LINE__); mlt_frame_close(frame); if (!preroll) RenderAudioSamples(preroll); } if (preroll) m_deckLinkOutput->StartScheduledPlayback(0, m_timescale, 1.0); return S_OK; } virtual HRESULT STDMETHODCALLTYPE ScheduledFrameCompleted( IDeckLinkVideoFrame *completedFrame, BMDOutputFrameCompletionResult completed) { mlt_log_debug(getConsumer(), "%s: ENTERING\n", __FUNCTION__); mlt_deque_push_back(m_frames, completedFrame); // change priority of video callback thread reprio(1); // When a video frame has been released by the API, schedule another video frame to be output // ignore handler if frame was flushed if (bmdOutputFrameFlushed == completed) return S_OK; // schedule next frame ScheduleNextFrame(false); // step forward frames counter if underrun if (bmdOutputFrameDisplayedLate == completed) { mlt_log_verbose(getConsumer(), "ScheduledFrameCompleted: bmdOutputFrameDisplayedLate == completed\n"); } if (bmdOutputFrameDropped == completed) { mlt_log_verbose(getConsumer(), "ScheduledFrameCompleted: bmdOutputFrameDropped == completed\n"); m_count++; ScheduleNextFrame(false); } return S_OK; } virtual HRESULT STDMETHODCALLTYPE ScheduledPlaybackHasStopped() { return mlt_consumer_is_stopped(getConsumer()) ? S_FALSE : S_OK; } void ScheduleNextFrame(bool preroll) { // get the consumer mlt_consumer consumer = getConsumer(); // Get the properties mlt_properties properties = MLT_CONSUMER_PROPERTIES(consumer); // Frame and size mlt_frame frame = NULL; mlt_log_debug(getConsumer(), "%s:%d: preroll=%d\n", __FUNCTION__, __LINE__, preroll); while (!frame && (mlt_properties_get_int(properties, "running") || preroll)) { mlt_log_timings_begin(); frame = mlt_consumer_rt_frame(consumer); mlt_log_timings_end(NULL, "mlt_consumer_rt_frame"); if (frame) { mlt_log_timings_begin(); render(frame); mlt_log_timings_end(NULL, "render"); mlt_events_fire(properties, "consumer-frame-show", mlt_event_data_from_frame(frame)); // terminate on pause if (m_terminate_on_pause && mlt_properties_get_double(MLT_FRAME_PROPERTIES(frame), "_speed") == 0.0) stop(); mlt_frame_close(frame); } else mlt_log_warning(getConsumer(), "%s: mlt_consumer_rt_frame return NULL\n", __FUNCTION__); } } }; /** Start the consumer. */ static int start(mlt_consumer consumer) { // Get the properties mlt_properties properties = MLT_CONSUMER_PROPERTIES(consumer); DeckLinkConsumer *decklink = (DeckLinkConsumer *) consumer->child; return decklink->op(OP_START, mlt_properties_get_int(properties, "preroll")) ? 0 : 1; } /** Stop the consumer. */ static int stop(mlt_consumer consumer) { int r; mlt_log_debug(MLT_CONSUMER_SERVICE(consumer), "%s: entering\n", __FUNCTION__); // Get the properties DeckLinkConsumer *decklink = (DeckLinkConsumer *) consumer->child; r = decklink->op(OP_STOP, 0); mlt_log_debug(MLT_CONSUMER_SERVICE(consumer), "%s: exiting\n", __FUNCTION__); return r; } /** Determine if the consumer is stopped. */ static int is_stopped(mlt_consumer consumer) { // Get the properties mlt_properties properties = MLT_CONSUMER_PROPERTIES(consumer); return !mlt_properties_get_int(properties, "running"); } /** Close the consumer. */ static void close(mlt_consumer consumer) { mlt_log_debug(MLT_CONSUMER_SERVICE(consumer), "%s: entering\n", __FUNCTION__); // Stop the consumer mlt_consumer_stop(consumer); // Close the parent consumer->close = NULL; mlt_consumer_close(consumer); // Free the memory delete (DeckLinkConsumer *) consumer->child; mlt_log_debug(MLT_CONSUMER_SERVICE(consumer), "%s: exiting\n", __FUNCTION__); } extern "C" { // Listen for the list_devices property to be set static void on_property_changed(void *, mlt_properties properties, mlt_event_data event_data) { const char *name = mlt_event_data_to_string(event_data); IDeckLinkIterator *decklinkIterator = NULL; IDeckLink *decklink = NULL; IDeckLinkInput *decklinkOutput = NULL; int i = 0; if (name && !strcmp(name, "list_devices")) mlt_event_block((mlt_event) mlt_properties_get_data(properties, "list-devices-event", NULL)); else return; #ifdef _WIN32 if (FAILED(CoInitialize(NULL))) return; if (FAILED(CoCreateInstance(CLSID_CDeckLinkIterator, NULL, CLSCTX_ALL, IID_IDeckLinkIterator, (void **) &decklinkIterator))) return; #else if (!(decklinkIterator = CreateDeckLinkIteratorInstance())) return; #endif for (; decklinkIterator->Next(&decklink) == S_OK; i++) { if (decklink->QueryInterface(IID_IDeckLinkOutput, (void **) &decklinkOutput) == S_OK) { DLString name = NULL; if (decklink->GetModelName(&name) == S_OK) { char *name_cstr = getCString(name); const char *format = "device.%d"; char *key = (char *) calloc(1, strlen(format) + 10); sprintf(key, format, i); mlt_properties_set(properties, key, name_cstr); free(key); freeDLString(name); freeCString(name_cstr); } SAFE_RELEASE(decklinkOutput); } SAFE_RELEASE(decklink); } SAFE_RELEASE(decklinkIterator); mlt_properties_set_int(properties, "devices", i); } /** Initialise the consumer. */ mlt_consumer consumer_decklink_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { // Allocate the consumer DeckLinkConsumer *decklink = new DeckLinkConsumer(); mlt_consumer consumer = NULL; // If allocated if (!mlt_consumer_init(decklink->getConsumer(), decklink, profile)) { // If initialises without error if (decklink->op(OP_OPEN, arg ? atoi(arg) : 0)) { consumer = decklink->getConsumer(); mlt_properties properties = MLT_CONSUMER_PROPERTIES(consumer); // Setup callbacks consumer->close = close; consumer->start = start; consumer->stop = stop; consumer->is_stopped = is_stopped; mlt_properties_set(properties, "consumer.deinterlacer", "onefield"); mlt_event event = mlt_events_listen(properties, properties, "property-changed", (mlt_listener) on_property_changed); mlt_properties_set_data(properties, "list-devices-event", event, 0, NULL, NULL); } } // Return consumer return consumer; } extern mlt_producer producer_decklink_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); static mlt_properties metadata(mlt_service_type type, const char *id, void *data) { char file[PATH_MAX]; const char *service_type = NULL; switch (type) { case mlt_service_consumer_type: service_type = "consumer"; break; case mlt_service_producer_type: service_type = "producer"; break; default: return NULL; } snprintf(file, PATH_MAX, "%s/decklink/%s_%s.yml", mlt_environment("MLT_DATA"), service_type, id); return mlt_properties_parse_yaml(file); } MLT_REPOSITORY { MLT_REGISTER(mlt_service_consumer_type, "decklink", consumer_decklink_init); MLT_REGISTER(mlt_service_producer_type, "decklink", producer_decklink_init); MLT_REGISTER_METADATA(mlt_service_consumer_type, "decklink", metadata, NULL); MLT_REGISTER_METADATA(mlt_service_producer_type, "decklink", metadata, NULL); } } // extern C mlt-7.22.0/src/modules/decklink/consumer_decklink.yml000664 000000 000000 00000006162 14531534050 022630 0ustar00rootroot000000 000000 schema_version: 7.0 type: consumer identifier: decklink title: Blackmagic Design DeckLink Output version: 2 copyright: Copyright (C) 2010-2018 Meltytech, LLC license: LGPL language: en creator: Dan Dennedy tags: - Audio - Video description: > Output audio and video using Blackmagic Design DeckLink SDI or Intensity HDMI cards. notes: > Please ensure that you use a MLT profile that is compatible with a broadcast standard which the card you are using supports. bugs: - Only internal keying is supported at this time. - Only 8-bit Y'CbCr or RGBA (key) is supported at this time. parameters: - identifier: resource argument: yes title: Card type: integer readonly: no required: no mutable: no default: 0 minimum: 0 widget: spinner - identifier: preroll title: Pre-roll Count type: integer description: > This controls the amount of buffering in the DeckLink driver/library. Increase this if you get video tearing or choppy audio. However, as you increase the amount, you increase the risk of audio and video becoming out of synchronization. readonly: no required: no mutable: no default: 3 minimum: 2 unit: frames widget: spinner - identifier: keyer title: Enable Keyer type: integer description: > Keying is the process of compositing MLT output over a live SDI input. The alpha channel of the MLT video controls the transparent areas, and the keyer supports alpha-blending. You can not control the compositing rectangle. Rather, the entire MLT output overlays the entire video input. Therefore, you must use MLT's compositing services to control the size and position. The value 1 enables the internal keyer, the value 2 enables the external keyer, and the value 0 disables it. readonly: no required: no mutable: no default: 0 minimum: 0 maximum: 2 - identifier: keyer_level title: Key Opacity type: float description: > This controls the level of blending between the key and the input video. 1 is fully opaque and something near 0 is transparent. However, absolute 0 is considered as "not supplied" and also fully opaque. 0.5 is an evenly balanced blending of the key and input video. readonly: no required: no mutable: no minimum: 0 maximum: 1 default: 1 widget: slider - identifier: devices title: Number of devices type: integer readonly: yes minimum: 0 - identifier: device.* title: Device model description: The model name of each device that provides output. type: string readonly: yes - identifier: sliced_swab title: Use sliced swab operation description: This option enables multithreaded parallel swab frame data operation type: boolean readonly: no minimum: 0 maximum: 1 default: 0 widget: checkbox - identifier: terminate_on_pause title: Stop automatically type: boolean description: > Whether to stop playback at the end of the producer or when playback is paused. default: 0 widget: checkbox mlt-7.22.0/src/modules/decklink/darwin/000775 000000 000000 00000000000 14531534050 017665 5ustar00rootroot000000 000000 mlt-7.22.0/src/modules/decklink/darwin/DeckLinkAPI.h000775 000000 000000 00000150510 14531534050 022061 0ustar00rootroot000000 000000 /* -LICENSE-START- ** Copyright (c) 2011 Blackmagic Design ** ** Permission is hereby granted, free of charge, to any person or organization ** obtaining a copy of the software and accompanying documentation covered by ** this license (the "Software") to use, reproduce, display, distribute, ** execute, and transmit the Software, and to prepare derivative works of the ** Software, and to permit third-parties to whom the Software is furnished to ** do so, all subject to the following: ** ** The copyright notices in the Software and this entire statement, including ** the above license grant, this restriction and the following disclaimer, ** must be included in all copies of the Software, in whole or in part, and ** all derivative works of the Software, unless such copies or derivative ** works are solely in the form of machine-executable object code generated by ** a source language processor. ** ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, ** FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT ** SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE ** FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ** ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER ** DEALINGS IN THE SOFTWARE. ** -LICENSE-END- */ /* DeckLinkAPI.h */ #ifndef __DeckLink_API_h__ #define __DeckLink_API_h__ #include #include #include #define BLACKMAGIC_DECKLINK_API_MAGIC 1 // Type Declarations typedef int64_t BMDTimeValue; typedef int64_t BMDTimeScale; typedef uint32_t BMDTimecodeBCD; typedef uint32_t BMDTimecodeUserBits; // Interface ID Declarations #define IID_IDeckLinkVideoOutputCallback /* 20AA5225-1958-47CB-820B-80A8D521A6EE */ (REFIID){0x20,0xAA,0x52,0x25,0x19,0x58,0x47,0xCB,0x82,0x0B,0x80,0xA8,0xD5,0x21,0xA6,0xEE} #define IID_IDeckLinkInputCallback /* DD04E5EC-7415-42AB-AE4A-E80C4DFC044A */ (REFIID){0xDD,0x04,0xE5,0xEC,0x74,0x15,0x42,0xAB,0xAE,0x4A,0xE8,0x0C,0x4D,0xFC,0x04,0x4A} #define IID_IDeckLinkMemoryAllocator /* B36EB6E7-9D29-4AA8-92EF-843B87A289E8 */ (REFIID){0xB3,0x6E,0xB6,0xE7,0x9D,0x29,0x4A,0xA8,0x92,0xEF,0x84,0x3B,0x87,0xA2,0x89,0xE8} #define IID_IDeckLinkAudioOutputCallback /* 403C681B-7F46-4A12-B993-2BB127084EE6 */ (REFIID){0x40,0x3C,0x68,0x1B,0x7F,0x46,0x4A,0x12,0xB9,0x93,0x2B,0xB1,0x27,0x08,0x4E,0xE6} #define IID_IDeckLinkIterator /* 74E936FC-CC28-4A67-81A0-1E94E52D4E69 */ (REFIID){0x74,0xE9,0x36,0xFC,0xCC,0x28,0x4A,0x67,0x81,0xA0,0x1E,0x94,0xE5,0x2D,0x4E,0x69} #define IID_IDeckLinkAPIInformation /* 7BEA3C68-730D-4322-AF34-8A7152B532A4 */ (REFIID){0x7B,0xEA,0x3C,0x68,0x73,0x0D,0x43,0x22,0xAF,0x34,0x8A,0x71,0x52,0xB5,0x32,0xA4} #define IID_IDeckLinkDisplayModeIterator /* 9C88499F-F601-4021-B80B-032E4EB41C35 */ (REFIID){0x9C,0x88,0x49,0x9F,0xF6,0x01,0x40,0x21,0xB8,0x0B,0x03,0x2E,0x4E,0xB4,0x1C,0x35} #define IID_IDeckLinkDisplayMode /* 3EB2C1AB-0A3D-4523-A3AD-F40D7FB14E78 */ (REFIID){0x3E,0xB2,0xC1,0xAB,0x0A,0x3D,0x45,0x23,0xA3,0xAD,0xF4,0x0D,0x7F,0xB1,0x4E,0x78} #define IID_IDeckLink /* 62BFF75D-6569-4E55-8D4D-66AA03829ABC */ (REFIID){0x62,0xBF,0xF7,0x5D,0x65,0x69,0x4E,0x55,0x8D,0x4D,0x66,0xAA,0x03,0x82,0x9A,0xBC} #define IID_IDeckLinkOutput /* A3EF0963-0862-44ED-92A9-EE89ABF431C7 */ (REFIID){0xA3,0xEF,0x09,0x63,0x08,0x62,0x44,0xED,0x92,0xA9,0xEE,0x89,0xAB,0xF4,0x31,0xC7} #define IID_IDeckLinkInput /* 6D40EF78-28B9-4E21-990D-95BB7750A04F */ (REFIID){0x6D,0x40,0xEF,0x78,0x28,0xB9,0x4E,0x21,0x99,0x0D,0x95,0xBB,0x77,0x50,0xA0,0x4F} #define IID_IDeckLinkTimecode /* BC6CFBD3-8317-4325-AC1C-1216391E9340 */ (REFIID){0xBC,0x6C,0xFB,0xD3,0x83,0x17,0x43,0x25,0xAC,0x1C,0x12,0x16,0x39,0x1E,0x93,0x40} #define IID_IDeckLinkVideoFrame /* 3F716FE0-F023-4111-BE5D-EF4414C05B17 */ (REFIID){0x3F,0x71,0x6F,0xE0,0xF0,0x23,0x41,0x11,0xBE,0x5D,0xEF,0x44,0x14,0xC0,0x5B,0x17} #define IID_IDeckLinkMutableVideoFrame /* 69E2639F-40DA-4E19-B6F2-20ACE815C390 */ (REFIID){0x69,0xE2,0x63,0x9F,0x40,0xDA,0x4E,0x19,0xB6,0xF2,0x20,0xAC,0xE8,0x15,0xC3,0x90} #define IID_IDeckLinkVideoFrame3DExtensions /* DA0F7E4A-EDC7-48A8-9CDD-2DB51C729CD7 */ (REFIID){0xDA,0x0F,0x7E,0x4A,0xED,0xC7,0x48,0xA8,0x9C,0xDD,0x2D,0xB5,0x1C,0x72,0x9C,0xD7} #define IID_IDeckLinkVideoInputFrame /* 05CFE374-537C-4094-9A57-680525118F44 */ (REFIID){0x05,0xCF,0xE3,0x74,0x53,0x7C,0x40,0x94,0x9A,0x57,0x68,0x05,0x25,0x11,0x8F,0x44} #define IID_IDeckLinkVideoFrameAncillary /* 732E723C-D1A4-4E29-9E8E-4A88797A0004 */ (REFIID){0x73,0x2E,0x72,0x3C,0xD1,0xA4,0x4E,0x29,0x9E,0x8E,0x4A,0x88,0x79,0x7A,0x00,0x04} #define IID_IDeckLinkAudioInputPacket /* E43D5870-2894-11DE-8C30-0800200C9A66 */ (REFIID){0xE4,0x3D,0x58,0x70,0x28,0x94,0x11,0xDE,0x8C,0x30,0x08,0x00,0x20,0x0C,0x9A,0x66} #define IID_IDeckLinkScreenPreviewCallback /* B1D3F49A-85FE-4C5D-95C8-0B5D5DCCD438 */ (REFIID){0xB1,0xD3,0xF4,0x9A,0x85,0xFE,0x4C,0x5D,0x95,0xC8,0x0B,0x5D,0x5D,0xCC,0xD4,0x38} #define IID_IDeckLinkCocoaScreenPreviewCallback /* D174152F-8F96-4C07-83A5-DD5F5AF0A2AA */ (REFIID){0xD1,0x74,0x15,0x2F,0x8F,0x96,0x4C,0x07,0x83,0xA5,0xDD,0x5F,0x5A,0xF0,0xA2,0xAA} #define IID_IDeckLinkGLScreenPreviewHelper /* 504E2209-CAC7-4C1A-9FB4-C5BB6274D22F */ (REFIID){0x50,0x4E,0x22,0x09,0xCA,0xC7,0x4C,0x1A,0x9F,0xB4,0xC5,0xBB,0x62,0x74,0xD2,0x2F} #define IID_IDeckLinkConfiguration /* C679A35B-610C-4D09-B748-1D0478100FC0 */ (REFIID){0xC6,0x79,0xA3,0x5B,0x61,0x0C,0x4D,0x09,0xB7,0x48,0x1D,0x04,0x78,0x10,0x0F,0xC0} #define IID_IDeckLinkAttributes /* ABC11843-D966-44CB-96E2-A1CB5D3135C4 */ (REFIID){0xAB,0xC1,0x18,0x43,0xD9,0x66,0x44,0xCB,0x96,0xE2,0xA1,0xCB,0x5D,0x31,0x35,0xC4} #define IID_IDeckLinkKeyer /* 89AFCAF5-65F8-421E-98F7-96FE5F5BFBA3 */ (REFIID){0x89,0xAF,0xCA,0xF5,0x65,0xF8,0x42,0x1E,0x98,0xF7,0x96,0xFE,0x5F,0x5B,0xFB,0xA3} #define IID_IDeckLinkVideoConversion /* 3BBCB8A2-DA2C-42D9-B5D8-88083644E99A */ (REFIID){0x3B,0xBC,0xB8,0xA2,0xDA,0x2C,0x42,0xD9,0xB5,0xD8,0x88,0x08,0x36,0x44,0xE9,0x9A} #define IID_IDeckLinkDeckControlStatusCallback /* E5F693C1-4283-4716-B18F-C1431521955B */ (REFIID){0xE5,0xF6,0x93,0xC1,0x42,0x83,0x47,0x16,0xB1,0x8F,0xC1,0x43,0x15,0x21,0x95,0x5B} #define IID_IDeckLinkDeckControl /* 522A9E39-0F3C-4742-94EE-D80DE335DA1D */ (REFIID){0x52,0x2A,0x9E,0x39,0x0F,0x3C,0x47,0x42,0x94,0xEE,0xD8,0x0D,0xE3,0x35,0xDA,0x1D} /* Enum BMDDisplayMode - Video display modes */ typedef uint32_t BMDDisplayMode; enum _BMDDisplayMode { /* SD Modes */ bmdModeNTSC = 'ntsc', bmdModeNTSC2398 = 'nt23', // 3:2 pulldown bmdModePAL = 'pal ', bmdModeNTSCp = 'ntsp', bmdModePALp = 'palp', /* HD 1080 Modes */ bmdModeHD1080p2398 = '23ps', bmdModeHD1080p24 = '24ps', bmdModeHD1080p25 = 'Hp25', bmdModeHD1080p2997 = 'Hp29', bmdModeHD1080p30 = 'Hp30', bmdModeHD1080i50 = 'Hi50', bmdModeHD1080i5994 = 'Hi59', bmdModeHD1080i6000 = 'Hi60', // N.B. This _really_ is 60.00 Hz. bmdModeHD1080p50 = 'Hp50', bmdModeHD1080p5994 = 'Hp59', bmdModeHD1080p6000 = 'Hp60', // N.B. This _really_ is 60.00 Hz. /* HD 720 Modes */ bmdModeHD720p50 = 'hp50', bmdModeHD720p5994 = 'hp59', bmdModeHD720p60 = 'hp60', /* 2k Modes */ bmdMode2k2398 = '2k23', bmdMode2k24 = '2k24', bmdMode2k25 = '2k25', /* DCI Modes (output only) */ bmdMode2kDCI2398 = '2d23', bmdMode2kDCI24 = '2d24', bmdMode2kDCI25 = '2d25', /* 4k Modes */ bmdMode4K2160p2398 = '4k23', bmdMode4K2160p24 = '4k24', bmdMode4K2160p25 = '4k25', bmdMode4K2160p2997 = '4k29', bmdMode4K2160p30 = '4k30', bmdMode4K2160p50 = '4k50', bmdMode4K2160p5994 = '4k59', bmdMode4K2160p60 = '4k60', /* DCI Modes (output only) */ bmdMode4kDCI2398 = '4d23', bmdMode4kDCI24 = '4d24', bmdMode4kDCI25 = '4d25', /* Special Modes */ bmdModeUnknown = 'iunk' }; /* Enum BMDFieldDominance - Video field dominance */ typedef uint32_t BMDFieldDominance; enum _BMDFieldDominance { bmdUnknownFieldDominance = 0, bmdLowerFieldFirst = 'lowr', bmdUpperFieldFirst = 'uppr', bmdProgressiveFrame = 'prog', bmdProgressiveSegmentedFrame = 'psf ' }; /* Enum BMDPixelFormat - Video pixel formats supported for output/input */ typedef uint32_t BMDPixelFormat; enum _BMDPixelFormat { bmdFormat8BitYUV = '2vuy', bmdFormat10BitYUV = 'v210', bmdFormat8BitARGB = 32, bmdFormat8BitBGRA = 'BGRA', bmdFormat10BitRGB = 'r210' // Big-endian RGB 10-bit per component with SMPTE video levels (64-960). Packed as 2:10:10:10 }; /* Enum BMDDisplayModeFlags - Flags to describe the characteristics of an IDeckLinkDisplayMode. */ typedef uint32_t BMDDisplayModeFlags; enum _BMDDisplayModeFlags { bmdDisplayModeSupports3D = 1 << 0, bmdDisplayModeColorspaceRec601 = 1 << 1, bmdDisplayModeColorspaceRec709 = 1 << 2 }; /* Enum BMDVideoOutputFlags - Flags to control the output of ancillary data along with video. */ typedef uint32_t BMDVideoOutputFlags; enum _BMDVideoOutputFlags { bmdVideoOutputFlagDefault = 0, bmdVideoOutputVANC = 1 << 0, bmdVideoOutputVITC = 1 << 1, bmdVideoOutputRP188 = 1 << 2, bmdVideoOutputDualStream3D = 1 << 4 }; /* Enum BMDFrameFlags - Frame flags */ typedef uint32_t BMDFrameFlags; enum _BMDFrameFlags { bmdFrameFlagDefault = 0, bmdFrameFlagFlipVertical = 1 << 0, /* Flags that are applicable only to instances of IDeckLinkVideoInputFrame */ bmdFrameHasNoInputSource = 1 << 31 }; /* Enum BMDVideoInputFlags - Flags applicable to video input */ typedef uint32_t BMDVideoInputFlags; enum _BMDVideoInputFlags { bmdVideoInputFlagDefault = 0, bmdVideoInputEnableFormatDetection = 1 << 0, bmdVideoInputDualStream3D = 1 << 1 }; /* Enum BMDVideoInputFormatChangedEvents - Bitmask passed to the VideoInputFormatChanged notification to identify the properties of the input signal that have changed */ typedef uint32_t BMDVideoInputFormatChangedEvents; enum _BMDVideoInputFormatChangedEvents { bmdVideoInputDisplayModeChanged = 1 << 0, bmdVideoInputFieldDominanceChanged = 1 << 1, bmdVideoInputColorspaceChanged = 1 << 2 }; /* Enum BMDDetectedVideoInputFormatFlags - Flags passed to the VideoInputFormatChanged notification to describe the detected video input signal */ typedef uint32_t BMDDetectedVideoInputFormatFlags; enum _BMDDetectedVideoInputFormatFlags { bmdDetectedVideoInputYCbCr422 = 1 << 0, bmdDetectedVideoInputRGB444 = 1 << 1 }; /* Enum BMDOutputFrameCompletionResult - Frame Completion Callback */ typedef uint32_t BMDOutputFrameCompletionResult; enum _BMDOutputFrameCompletionResult { bmdOutputFrameCompleted, bmdOutputFrameDisplayedLate, bmdOutputFrameDropped, bmdOutputFrameFlushed }; /* Enum BMDReferenceStatus - GenLock input status */ typedef uint32_t BMDReferenceStatus; enum _BMDReferenceStatus { bmdReferenceNotSupportedByHardware = 1 << 0, bmdReferenceLocked = 1 << 1 }; /* Enum BMDAudioSampleRate - Audio sample rates supported for output/input */ typedef uint32_t BMDAudioSampleRate; enum _BMDAudioSampleRate { bmdAudioSampleRate48kHz = 48000 }; /* Enum BMDAudioSampleType - Audio sample sizes supported for output/input */ typedef uint32_t BMDAudioSampleType; enum _BMDAudioSampleType { bmdAudioSampleType16bitInteger = 16, bmdAudioSampleType32bitInteger = 32 }; /* Enum BMDAudioOutputStreamType - Audio output stream type */ typedef uint32_t BMDAudioOutputStreamType; enum _BMDAudioOutputStreamType { bmdAudioOutputStreamContinuous, bmdAudioOutputStreamContinuousDontResample, bmdAudioOutputStreamTimestamped }; /* Enum BMDDisplayModeSupport - Output mode supported flags */ typedef uint32_t BMDDisplayModeSupport; enum _BMDDisplayModeSupport { bmdDisplayModeNotSupported = 0, bmdDisplayModeSupported, bmdDisplayModeSupportedWithConversion }; /* Enum BMDTimecodeFormat - Timecode formats for frame metadata */ typedef uint32_t BMDTimecodeFormat; enum _BMDTimecodeFormat { bmdTimecodeRP188 = 'rp18', bmdTimecodeRP188Field2 = 'rp12', bmdTimecodeVITC = 'vitc', bmdTimecodeVITCField2 = 'vit2', bmdTimecodeSerial = 'seri' }; /* Enum BMDTimecodeFlags - Timecode flags */ typedef uint32_t BMDTimecodeFlags; enum _BMDTimecodeFlags { bmdTimecodeFlagDefault = 0, bmdTimecodeIsDropFrame = 1 << 0 }; /* Enum BMDVideoConnection - Video connection types */ typedef uint32_t BMDVideoConnection; enum _BMDVideoConnection { bmdVideoConnectionSDI = 1 << 0, bmdVideoConnectionHDMI = 1 << 1, bmdVideoConnectionOpticalSDI = 1 << 2, bmdVideoConnectionComponent = 1 << 3, bmdVideoConnectionComposite = 1 << 4, bmdVideoConnectionSVideo = 1 << 5 }; /* Enum BMDAnalogVideoFlags - Analog video display flags */ typedef uint32_t BMDAnalogVideoFlags; enum _BMDAnalogVideoFlags { bmdAnalogVideoFlagCompositeSetup75 = 1 << 0, bmdAnalogVideoFlagComponentBetacamLevels = 1 << 1 }; /* Enum BMDAudioConnection - Audio connection types */ typedef uint32_t BMDAudioConnection; enum _BMDAudioConnection { bmdAudioConnectionEmbedded = 'embd', bmdAudioConnectionAESEBU = 'aes ', bmdAudioConnectionAnalog = 'anlg' }; /* Enum BMDAudioOutputAnalogAESSwitch - Audio output Analog/AESEBU switch */ typedef uint32_t BMDAudioOutputAnalogAESSwitch; enum _BMDAudioOutputAnalogAESSwitch { bmdAudioOutputSwitchAESEBU = 'aes ', bmdAudioOutputSwitchAnalog = 'anlg' }; /* Enum BMDVideoOutputConversionMode - Video/audio conversion mode */ typedef uint32_t BMDVideoOutputConversionMode; enum _BMDVideoOutputConversionMode { bmdNoVideoOutputConversion = 'none', bmdVideoOutputLetterboxDownconversion = 'ltbx', bmdVideoOutputAnamorphicDownconversion = 'amph', bmdVideoOutputHD720toHD1080Conversion = '720c', bmdVideoOutputHardwareLetterboxDownconversion = 'HWlb', bmdVideoOutputHardwareAnamorphicDownconversion = 'HWam', bmdVideoOutputHardwareCenterCutDownconversion = 'HWcc', bmdVideoOutputHardware720p1080pCrossconversion = 'xcap', bmdVideoOutputHardwareAnamorphic720pUpconversion = 'ua7p', bmdVideoOutputHardwareAnamorphic1080iUpconversion = 'ua1i', bmdVideoOutputHardwareAnamorphic149To720pUpconversion = 'u47p', bmdVideoOutputHardwareAnamorphic149To1080iUpconversion = 'u41i', bmdVideoOutputHardwarePillarbox720pUpconversion = 'up7p', bmdVideoOutputHardwarePillarbox1080iUpconversion = 'up1i' }; /* Enum BMDVideoInputConversionMode - Video input conversion mode */ typedef uint32_t BMDVideoInputConversionMode; enum _BMDVideoInputConversionMode { bmdNoVideoInputConversion = 'none', bmdVideoInputLetterboxDownconversionFromHD1080 = '10lb', bmdVideoInputAnamorphicDownconversionFromHD1080 = '10am', bmdVideoInputLetterboxDownconversionFromHD720 = '72lb', bmdVideoInputAnamorphicDownconversionFromHD720 = '72am', bmdVideoInputLetterboxUpconversion = 'lbup', bmdVideoInputAnamorphicUpconversion = 'amup' }; /* Enum BMDVideo3DPackingFormat - Video 3D packing format */ typedef uint32_t BMDVideo3DPackingFormat; enum _BMDVideo3DPackingFormat { bmdVideo3DPackingSidebySideHalf = 'sbsh', bmdVideo3DPackingLinebyLine = 'lbyl', bmdVideo3DPackingTopAndBottom = 'tabo', bmdVideo3DPackingFramePacking = 'frpk', bmdVideo3DPackingLeftOnly = 'left', bmdVideo3DPackingRightOnly = 'righ' }; /* Enum BMDIdleVideoOutputOperation - Video output operation when not playing video */ typedef uint32_t BMDIdleVideoOutputOperation; enum _BMDIdleVideoOutputOperation { bmdIdleVideoOutputBlack = 'blac', bmdIdleVideoOutputLastFrame = 'lafa' }; /* Enum BMDDeckLinkConfigurationID - DeckLink Configuration ID */ typedef uint32_t BMDDeckLinkConfigurationID; enum _BMDDeckLinkConfigurationID { /* Serial port Flags */ bmdDeckLinkConfigSwapSerialRxTx = 'ssrt', /* Video Input/Output Flags */ bmdDeckLinkConfigUse1080pNotPsF = 'fpro', /* Video Input/Output Integers */ bmdDeckLinkConfigHDMI3DPackingFormat = '3dpf', bmdDeckLinkConfigBypass = 'byps', /* Audio Input/Output Flags */ bmdDeckLinkConfigAnalogAudioConsumerLevels = 'aacl', /* Video output flags */ bmdDeckLinkConfigFieldFlickerRemoval = 'fdfr', bmdDeckLinkConfigHD1080p24ToHD1080i5994Conversion = 'to59', bmdDeckLinkConfig444SDIVideoOutput = '444o', bmdDeckLinkConfig3GBpsVideoOutput = '3gbs', bmdDeckLinkConfigBlackVideoOutputDuringCapture = 'bvoc', bmdDeckLinkConfigLowLatencyVideoOutput = 'llvo', /* Video Output Integers */ bmdDeckLinkConfigVideoOutputConnection = 'vocn', bmdDeckLinkConfigVideoOutputConversionMode = 'vocm', bmdDeckLinkConfigAnalogVideoOutputFlags = 'avof', bmdDeckLinkConfigReferenceInputTimingOffset = 'glot', bmdDeckLinkConfigVideoOutputIdleOperation = 'voio', /* Video Output Floats */ bmdDeckLinkConfigVideoOutputComponentLumaGain = 'oclg', bmdDeckLinkConfigVideoOutputComponentChromaBlueGain = 'occb', bmdDeckLinkConfigVideoOutputComponentChromaRedGain = 'occr', bmdDeckLinkConfigVideoOutputCompositeLumaGain = 'oilg', bmdDeckLinkConfigVideoOutputCompositeChromaGain = 'oicg', bmdDeckLinkConfigVideoOutputSVideoLumaGain = 'oslg', bmdDeckLinkConfigVideoOutputSVideoChromaGain = 'oscg', /* Video Input Integers */ bmdDeckLinkConfigVideoInputConnection = 'vicn', bmdDeckLinkConfigAnalogVideoInputFlags = 'avif', bmdDeckLinkConfigVideoInputConversionMode = 'vicm', bmdDeckLinkConfig32PulldownSequenceInitialTimecodeFrame = 'pdif', bmdDeckLinkConfigVANCSourceLine1Mapping = 'vsl1', bmdDeckLinkConfigVANCSourceLine2Mapping = 'vsl2', bmdDeckLinkConfigVANCSourceLine3Mapping = 'vsl3', /* Video Input Floats */ bmdDeckLinkConfigVideoInputComponentLumaGain = 'iclg', bmdDeckLinkConfigVideoInputComponentChromaBlueGain = 'iccb', bmdDeckLinkConfigVideoInputComponentChromaRedGain = 'iccr', bmdDeckLinkConfigVideoInputCompositeLumaGain = 'iilg', bmdDeckLinkConfigVideoInputCompositeChromaGain = 'iicg', bmdDeckLinkConfigVideoInputSVideoLumaGain = 'islg', bmdDeckLinkConfigVideoInputSVideoChromaGain = 'iscg', /* Audio Input Integers */ bmdDeckLinkConfigAudioInputConnection = 'aicn', /* Audio Input Floats */ bmdDeckLinkConfigAnalogAudioInputScaleChannel1 = 'ais1', bmdDeckLinkConfigAnalogAudioInputScaleChannel2 = 'ais2', bmdDeckLinkConfigAnalogAudioInputScaleChannel3 = 'ais3', bmdDeckLinkConfigAnalogAudioInputScaleChannel4 = 'ais4', bmdDeckLinkConfigDigitalAudioInputScale = 'dais', /* Audio Output Integers */ bmdDeckLinkConfigAudioOutputAESAnalogSwitch = 'aoaa', /* Audio Output Floats */ bmdDeckLinkConfigAnalogAudioOutputScaleChannel1 = 'aos1', bmdDeckLinkConfigAnalogAudioOutputScaleChannel2 = 'aos2', bmdDeckLinkConfigAnalogAudioOutputScaleChannel3 = 'aos3', bmdDeckLinkConfigAnalogAudioOutputScaleChannel4 = 'aos4', bmdDeckLinkConfigDigitalAudioOutputScale = 'daos' }; /* Enum BMDDeckLinkAttributeID - DeckLink Attribute ID */ typedef uint32_t BMDDeckLinkAttributeID; enum _BMDDeckLinkAttributeID { /* Flags */ BMDDeckLinkSupportsInternalKeying = 'keyi', BMDDeckLinkSupportsExternalKeying = 'keye', BMDDeckLinkSupportsHDKeying = 'keyh', BMDDeckLinkSupportsInputFormatDetection = 'infd', BMDDeckLinkHasReferenceInput = 'hrin', BMDDeckLinkHasSerialPort = 'hspt', BMDDeckLinkHasAnalogVideoOutputGain = 'avog', BMDDeckLinkCanOnlyAdjustOverallVideoOutputGain = 'ovog', BMDDeckLinkHasVideoInputAntiAliasingFilter = 'aafl', BMDDeckLinkHasBypass = 'byps', /* Integers */ BMDDeckLinkMaximumAudioChannels = 'mach', BMDDeckLinkNumberOfSubDevices = 'nsbd', BMDDeckLinkSubDeviceIndex = 'subi', BMDDeckLinkVideoOutputConnections = 'vocn', BMDDeckLinkVideoInputConnections = 'vicn', /* Floats */ BMDDeckLinkVideoInputGainMinimum = 'vigm', BMDDeckLinkVideoInputGainMaximum = 'vigx', BMDDeckLinkVideoOutputGainMinimum = 'vogm', BMDDeckLinkVideoOutputGainMaximum = 'vogx', /* Strings */ BMDDeckLinkSerialPortDeviceName = 'slpn' }; /* Enum BMDDeckLinkAPIInformationID - DeckLinkAPI information ID */ typedef uint32_t BMDDeckLinkAPIInformationID; enum _BMDDeckLinkAPIInformationID { BMDDeckLinkAPIVersion = 'vers' }; /* Enum BMDDeckControlMode - DeckControl mode */ typedef uint32_t BMDDeckControlMode; enum _BMDDeckControlMode { bmdDeckControlNotOpened = 'ntop', bmdDeckControlVTRControlMode = 'vtrc', bmdDeckControlExportMode = 'expm', bmdDeckControlCaptureMode = 'capm' }; /* Enum BMDDeckControlEvent - DeckControl event */ typedef uint32_t BMDDeckControlEvent; enum _BMDDeckControlEvent { bmdDeckControlAbortedEvent = 'abte', // This event is triggered when a capture or edit-to-tape operation is aborted. /* Export-To-Tape events */ bmdDeckControlPrepareForExportEvent = 'pfee', // This event is triggered a few frames before reaching the in-point. IDeckLinkInput::StartScheduledPlayback() should be called at this point. bmdDeckControlExportCompleteEvent = 'exce', // This event is triggered a few frames after reaching the out-point. At this point, it is safe to stop playback. /* Capture events */ bmdDeckControlPrepareForCaptureEvent = 'pfce', // This event is triggered a few frames before reaching the in-point. The serial timecode attached to IDeckLinkVideoInputFrames is now valid. bmdDeckControlCaptureCompleteEvent = 'ccev' // This event is triggered a few frames after reaching the out-point. }; /* Enum BMDDeckControlVTRControlState - VTR Control state */ typedef uint32_t BMDDeckControlVTRControlState; enum _BMDDeckControlVTRControlState { bmdDeckControlNotInVTRControlMode = 'nvcm', bmdDeckControlVTRControlPlaying = 'vtrp', bmdDeckControlVTRControlRecording = 'vtrr', bmdDeckControlVTRControlStill = 'vtra', bmdDeckControlVTRControlSeeking = 'vtrs', bmdDeckControlVTRControlStopped = 'vtro' }; /* Enum BMDDeckControlStatusFlags - Deck Control status flags */ typedef uint32_t BMDDeckControlStatusFlags; enum _BMDDeckControlStatusFlags { bmdDeckControlStatusDeckConnected = 1 << 0, bmdDeckControlStatusRemoteMode = 1 << 1, bmdDeckControlStatusRecordInhibited = 1 << 2, bmdDeckControlStatusCassetteOut = 1 << 3 }; /* Enum BMDDeckControlExportModeOpsFlags - Export mode flags */ typedef uint32_t BMDDeckControlExportModeOpsFlags; enum _BMDDeckControlExportModeOpsFlags { bmdDeckControlExportModeInsertVideo = 1 << 0, bmdDeckControlExportModeInsertAudio1 = 1 << 1, bmdDeckControlExportModeInsertAudio2 = 1 << 2, bmdDeckControlExportModeInsertAudio3 = 1 << 3, bmdDeckControlExportModeInsertAudio4 = 1 << 4, bmdDeckControlExportModeInsertAudio5 = 1 << 5, bmdDeckControlExportModeInsertAudio6 = 1 << 6, bmdDeckControlExportModeInsertAudio7 = 1 << 7, bmdDeckControlExportModeInsertAudio8 = 1 << 8, bmdDeckControlExportModeInsertAudio9 = 1 << 9, bmdDeckControlExportModeInsertAudio10 = 1 << 10, bmdDeckControlExportModeInsertAudio11 = 1 << 11, bmdDeckControlExportModeInsertAudio12 = 1 << 12, bmdDeckControlExportModeInsertTimeCode = 1 << 13, bmdDeckControlExportModeInsertAssemble = 1 << 14, bmdDeckControlExportModeInsertPreview = 1 << 15, bmdDeckControlUseManualExport = 1 << 16 }; /* Enum BMDDeckControlError - Deck Control error */ typedef uint32_t BMDDeckControlError; enum _BMDDeckControlError { bmdDeckControlNoError = 'noer', bmdDeckControlModeError = 'moer', bmdDeckControlMissedInPointError = 'mier', bmdDeckControlDeckTimeoutError = 'dter', bmdDeckControlCommandFailedError = 'cfer', bmdDeckControlDeviceAlreadyOpenedError = 'dalo', bmdDeckControlFailedToOpenDeviceError = 'fder', bmdDeckControlInLocalModeError = 'lmer', bmdDeckControlEndOfTapeError = 'eter', bmdDeckControlUserAbortError = 'uaer', bmdDeckControlNoTapeInDeckError = 'nter', bmdDeckControlNoVideoFromCardError = 'nvfc', bmdDeckControlNoCommunicationError = 'ncom', bmdDeckControlBufferTooSmallError = 'btsm', bmdDeckControlBadChecksumError = 'chks', bmdDeckControlUnknownError = 'uner' }; /* Enum BMD3DPreviewFormat - Linked Frame preview format */ typedef uint32_t BMD3DPreviewFormat; enum _BMD3DPreviewFormat { bmd3DPreviewFormatDefault = 'defa', bmd3DPreviewFormatLeftOnly = 'left', bmd3DPreviewFormatRightOnly = 'righ', bmd3DPreviewFormatSideBySide = 'side', bmd3DPreviewFormatTopBottom = 'topb' }; #if defined(__cplusplus) // Forward Declarations class IDeckLinkVideoOutputCallback; class IDeckLinkInputCallback; class IDeckLinkMemoryAllocator; class IDeckLinkAudioOutputCallback; class IDeckLinkIterator; class IDeckLinkAPIInformation; class IDeckLinkDisplayModeIterator; class IDeckLinkDisplayMode; class IDeckLink; class IDeckLinkOutput; class IDeckLinkInput; class IDeckLinkTimecode; class IDeckLinkVideoFrame; class IDeckLinkMutableVideoFrame; class IDeckLinkVideoFrame3DExtensions; class IDeckLinkVideoInputFrame; class IDeckLinkVideoFrameAncillary; class IDeckLinkAudioInputPacket; class IDeckLinkScreenPreviewCallback; class IDeckLinkCocoaScreenPreviewCallback; class IDeckLinkGLScreenPreviewHelper; class IDeckLinkConfiguration; class IDeckLinkAttributes; class IDeckLinkKeyer; class IDeckLinkVideoConversion; class IDeckLinkDeckControlStatusCallback; class IDeckLinkDeckControl; /* Interface IDeckLinkVideoOutputCallback - Frame completion callback. */ class IDeckLinkVideoOutputCallback : public IUnknown { public: virtual HRESULT ScheduledFrameCompleted (/* in */ IDeckLinkVideoFrame *completedFrame, /* in */ BMDOutputFrameCompletionResult result) = 0; virtual HRESULT ScheduledPlaybackHasStopped (void) = 0; protected: virtual ~IDeckLinkVideoOutputCallback () {}; // call Release method to drop reference count }; /* Interface IDeckLinkInputCallback - Frame arrival callback. */ class IDeckLinkInputCallback : public IUnknown { public: virtual HRESULT VideoInputFormatChanged (/* in */ BMDVideoInputFormatChangedEvents notificationEvents, /* in */ IDeckLinkDisplayMode *newDisplayMode, /* in */ BMDDetectedVideoInputFormatFlags detectedSignalFlags) = 0; virtual HRESULT VideoInputFrameArrived (/* in */ IDeckLinkVideoInputFrame* videoFrame, /* in */ IDeckLinkAudioInputPacket* audioPacket) = 0; protected: virtual ~IDeckLinkInputCallback () {}; // call Release method to drop reference count }; /* Interface IDeckLinkMemoryAllocator - Memory allocator for video frames. */ class IDeckLinkMemoryAllocator : public IUnknown { public: virtual HRESULT AllocateBuffer (/* in */ uint32_t bufferSize, /* out */ void **allocatedBuffer) = 0; virtual HRESULT ReleaseBuffer (/* in */ void *buffer) = 0; virtual HRESULT Commit (void) = 0; virtual HRESULT Decommit (void) = 0; }; /* Interface IDeckLinkAudioOutputCallback - Optional callback to allow audio samples to be pulled as required. */ class IDeckLinkAudioOutputCallback : public IUnknown { public: virtual HRESULT RenderAudioSamples (/* in */ bool preroll) = 0; }; /* Interface IDeckLinkIterator - enumerates installed DeckLink hardware */ class IDeckLinkIterator : public IUnknown { public: virtual HRESULT Next (/* out */ IDeckLink **deckLinkInstance) = 0; }; /* Interface IDeckLinkAPIInformation - DeckLinkAPI attribute interface */ class IDeckLinkAPIInformation : public IUnknown { public: virtual HRESULT GetFlag (/* in */ BMDDeckLinkAPIInformationID cfgID, /* out */ bool *value) = 0; virtual HRESULT GetInt (/* in */ BMDDeckLinkAPIInformationID cfgID, /* out */ int64_t *value) = 0; virtual HRESULT GetFloat (/* in */ BMDDeckLinkAPIInformationID cfgID, /* out */ double *value) = 0; virtual HRESULT GetString (/* in */ BMDDeckLinkAPIInformationID cfgID, /* out */ CFStringRef *value) = 0; protected: virtual ~IDeckLinkAPIInformation () {}; // call Release method to drop reference count }; /* Interface IDeckLinkDisplayModeIterator - enumerates over supported input/output display modes. */ class IDeckLinkDisplayModeIterator : public IUnknown { public: virtual HRESULT Next (/* out */ IDeckLinkDisplayMode **deckLinkDisplayMode) = 0; protected: virtual ~IDeckLinkDisplayModeIterator () {}; // call Release method to drop reference count }; /* Interface IDeckLinkDisplayMode - represents a display mode */ class IDeckLinkDisplayMode : public IUnknown { public: virtual HRESULT GetName (/* out */ CFStringRef *name) = 0; virtual BMDDisplayMode GetDisplayMode (void) = 0; virtual long GetWidth (void) = 0; virtual long GetHeight (void) = 0; virtual HRESULT GetFrameRate (/* out */ BMDTimeValue *frameDuration, /* out */ BMDTimeScale *timeScale) = 0; virtual BMDFieldDominance GetFieldDominance (void) = 0; virtual BMDDisplayModeFlags GetFlags (void) = 0; protected: virtual ~IDeckLinkDisplayMode () {}; // call Release method to drop reference count }; /* Interface IDeckLink - represents a DeckLink device */ class IDeckLink : public IUnknown { public: virtual HRESULT GetModelName (/* out */ CFStringRef *modelName) = 0; }; /* Interface IDeckLinkOutput - Created by QueryInterface from IDeckLink. */ class IDeckLinkOutput : public IUnknown { public: virtual HRESULT DoesSupportVideoMode (/* in */ BMDDisplayMode displayMode, /* in */ BMDPixelFormat pixelFormat, /* in */ BMDVideoOutputFlags flags, /* out */ BMDDisplayModeSupport *result, /* out */ IDeckLinkDisplayMode **resultDisplayMode) = 0; virtual HRESULT GetDisplayModeIterator (/* out */ IDeckLinkDisplayModeIterator **iterator) = 0; virtual HRESULT SetScreenPreviewCallback (/* in */ IDeckLinkScreenPreviewCallback *previewCallback) = 0; /* Video Output */ virtual HRESULT EnableVideoOutput (/* in */ BMDDisplayMode displayMode, /* in */ BMDVideoOutputFlags flags) = 0; virtual HRESULT DisableVideoOutput (void) = 0; virtual HRESULT SetVideoOutputFrameMemoryAllocator (/* in */ IDeckLinkMemoryAllocator *theAllocator) = 0; virtual HRESULT CreateVideoFrame (/* in */ int32_t width, /* in */ int32_t height, /* in */ int32_t rowBytes, /* in */ BMDPixelFormat pixelFormat, /* in */ BMDFrameFlags flags, /* out */ IDeckLinkMutableVideoFrame **outFrame) = 0; virtual HRESULT CreateAncillaryData (/* in */ BMDPixelFormat pixelFormat, /* out */ IDeckLinkVideoFrameAncillary **outBuffer) = 0; virtual HRESULT DisplayVideoFrameSync (/* in */ IDeckLinkVideoFrame *theFrame) = 0; virtual HRESULT ScheduleVideoFrame (/* in */ IDeckLinkVideoFrame *theFrame, /* in */ BMDTimeValue displayTime, /* in */ BMDTimeValue displayDuration, /* in */ BMDTimeScale timeScale) = 0; virtual HRESULT SetScheduledFrameCompletionCallback (/* in */ IDeckLinkVideoOutputCallback *theCallback) = 0; virtual HRESULT GetBufferedVideoFrameCount (/* out */ uint32_t *bufferedFrameCount) = 0; /* Audio Output */ virtual HRESULT EnableAudioOutput (/* in */ BMDAudioSampleRate sampleRate, /* in */ BMDAudioSampleType sampleType, /* in */ uint32_t channelCount, /* in */ BMDAudioOutputStreamType streamType) = 0; virtual HRESULT DisableAudioOutput (void) = 0; virtual HRESULT WriteAudioSamplesSync (/* in */ void *buffer, /* in */ uint32_t sampleFrameCount, /* out */ uint32_t *sampleFramesWritten) = 0; virtual HRESULT BeginAudioPreroll (void) = 0; virtual HRESULT EndAudioPreroll (void) = 0; virtual HRESULT ScheduleAudioSamples (/* in */ void *buffer, /* in */ uint32_t sampleFrameCount, /* in */ BMDTimeValue streamTime, /* in */ BMDTimeScale timeScale, /* out */ uint32_t *sampleFramesWritten) = 0; virtual HRESULT GetBufferedAudioSampleFrameCount (/* out */ uint32_t *bufferedSampleFrameCount) = 0; virtual HRESULT FlushBufferedAudioSamples (void) = 0; virtual HRESULT SetAudioCallback (/* in */ IDeckLinkAudioOutputCallback *theCallback) = 0; /* Output Control */ virtual HRESULT StartScheduledPlayback (/* in */ BMDTimeValue playbackStartTime, /* in */ BMDTimeScale timeScale, /* in */ double playbackSpeed) = 0; virtual HRESULT StopScheduledPlayback (/* in */ BMDTimeValue stopPlaybackAtTime, /* out */ BMDTimeValue *actualStopTime, /* in */ BMDTimeScale timeScale) = 0; virtual HRESULT IsScheduledPlaybackRunning (/* out */ bool *active) = 0; virtual HRESULT GetScheduledStreamTime (/* in */ BMDTimeScale desiredTimeScale, /* out */ BMDTimeValue *streamTime, /* out */ double *playbackSpeed) = 0; virtual HRESULT GetReferenceStatus (/* out */ BMDReferenceStatus *referenceStatus) = 0; /* Hardware Timing */ virtual HRESULT GetHardwareReferenceClock (/* in */ BMDTimeScale desiredTimeScale, /* out */ BMDTimeValue *hardwareTime, /* out */ BMDTimeValue *timeInFrame, /* out */ BMDTimeValue *ticksPerFrame) = 0; protected: virtual ~IDeckLinkOutput () {}; // call Release method to drop reference count }; /* Interface IDeckLinkInput - Created by QueryInterface from IDeckLink. */ class IDeckLinkInput : public IUnknown { public: virtual HRESULT DoesSupportVideoMode (/* in */ BMDDisplayMode displayMode, /* in */ BMDPixelFormat pixelFormat, /* in */ BMDVideoInputFlags flags, /* out */ BMDDisplayModeSupport *result, /* out */ IDeckLinkDisplayMode **resultDisplayMode) = 0; virtual HRESULT GetDisplayModeIterator (/* out */ IDeckLinkDisplayModeIterator **iterator) = 0; virtual HRESULT SetScreenPreviewCallback (/* in */ IDeckLinkScreenPreviewCallback *previewCallback) = 0; /* Video Input */ virtual HRESULT EnableVideoInput (/* in */ BMDDisplayMode displayMode, /* in */ BMDPixelFormat pixelFormat, /* in */ BMDVideoInputFlags flags) = 0; virtual HRESULT DisableVideoInput (void) = 0; virtual HRESULT GetAvailableVideoFrameCount (/* out */ uint32_t *availableFrameCount) = 0; /* Audio Input */ virtual HRESULT EnableAudioInput (/* in */ BMDAudioSampleRate sampleRate, /* in */ BMDAudioSampleType sampleType, /* in */ uint32_t channelCount) = 0; virtual HRESULT DisableAudioInput (void) = 0; virtual HRESULT GetAvailableAudioSampleFrameCount (/* out */ uint32_t *availableSampleFrameCount) = 0; /* Input Control */ virtual HRESULT StartStreams (void) = 0; virtual HRESULT StopStreams (void) = 0; virtual HRESULT PauseStreams (void) = 0; virtual HRESULT FlushStreams (void) = 0; virtual HRESULT SetCallback (/* in */ IDeckLinkInputCallback *theCallback) = 0; /* Hardware Timing */ virtual HRESULT GetHardwareReferenceClock (/* in */ BMDTimeScale desiredTimeScale, /* out */ BMDTimeValue *hardwareTime, /* out */ BMDTimeValue *timeInFrame, /* out */ BMDTimeValue *ticksPerFrame) = 0; protected: virtual ~IDeckLinkInput () {}; // call Release method to drop reference count }; /* Interface IDeckLinkTimecode - Used for video frame timecode representation. */ class IDeckLinkTimecode : public IUnknown { public: virtual BMDTimecodeBCD GetBCD (void) = 0; virtual HRESULT GetComponents (/* out */ uint8_t *hours, /* out */ uint8_t *minutes, /* out */ uint8_t *seconds, /* out */ uint8_t *frames) = 0; virtual HRESULT GetString (/* out */ CFStringRef *timecode) = 0; virtual BMDTimecodeFlags GetFlags (void) = 0; virtual HRESULT GetTimecodeUserBits (/* out */ BMDTimecodeUserBits *userBits) = 0; protected: virtual ~IDeckLinkTimecode () {}; // call Release method to drop reference count }; /* Interface IDeckLinkVideoFrame - Interface to encapsulate a video frame; can be caller-implemented. */ class IDeckLinkVideoFrame : public IUnknown { public: virtual long GetWidth (void) = 0; virtual long GetHeight (void) = 0; virtual long GetRowBytes (void) = 0; virtual BMDPixelFormat GetPixelFormat (void) = 0; virtual BMDFrameFlags GetFlags (void) = 0; virtual HRESULT GetBytes (/* out */ void **buffer) = 0; virtual HRESULT GetTimecode (/* in */ BMDTimecodeFormat format, /* out */ IDeckLinkTimecode **timecode) = 0; virtual HRESULT GetAncillaryData (/* out */ IDeckLinkVideoFrameAncillary **ancillary) = 0; protected: virtual ~IDeckLinkVideoFrame () {}; // call Release method to drop reference count }; /* Interface IDeckLinkMutableVideoFrame - Created by IDeckLinkOutput::CreateVideoFrame. */ class IDeckLinkMutableVideoFrame : public IDeckLinkVideoFrame { public: virtual HRESULT SetFlags (/* in */ BMDFrameFlags newFlags) = 0; virtual HRESULT SetTimecode (/* in */ BMDTimecodeFormat format, /* in */ IDeckLinkTimecode *timecode) = 0; virtual HRESULT SetTimecodeFromComponents (/* in */ BMDTimecodeFormat format, /* in */ uint8_t hours, /* in */ uint8_t minutes, /* in */ uint8_t seconds, /* in */ uint8_t frames, /* in */ BMDTimecodeFlags flags) = 0; virtual HRESULT SetAncillaryData (/* in */ IDeckLinkVideoFrameAncillary *ancillary) = 0; virtual HRESULT SetTimecodeUserBits (/* in */ BMDTimecodeFormat format, /* in */ BMDTimecodeUserBits userBits) = 0; protected: virtual ~IDeckLinkMutableVideoFrame () {}; // call Release method to drop reference count }; /* Interface IDeckLinkVideoFrame3DExtensions - Optional interface implemented on IDeckLinkVideoFrame to support 3D frames */ class IDeckLinkVideoFrame3DExtensions : public IUnknown { public: virtual BMDVideo3DPackingFormat Get3DPackingFormat (void) = 0; virtual HRESULT GetFrameForRightEye (/* out */ IDeckLinkVideoFrame* *rightEyeFrame) = 0; protected: virtual ~IDeckLinkVideoFrame3DExtensions () {}; // call Release method to drop reference count }; /* Interface IDeckLinkVideoInputFrame - Provided by the IDeckLinkVideoInput frame arrival callback. */ class IDeckLinkVideoInputFrame : public IDeckLinkVideoFrame { public: virtual HRESULT GetStreamTime (/* out */ BMDTimeValue *frameTime, /* out */ BMDTimeValue *frameDuration, /* in */ BMDTimeScale timeScale) = 0; virtual HRESULT GetHardwareReferenceTimestamp (/* in */ BMDTimeScale timeScale, /* out */ BMDTimeValue *frameTime, /* out */ BMDTimeValue *frameDuration) = 0; protected: virtual ~IDeckLinkVideoInputFrame () {}; // call Release method to drop reference count }; /* Interface IDeckLinkVideoFrameAncillary - Obtained through QueryInterface() on an IDeckLinkVideoFrame object. */ class IDeckLinkVideoFrameAncillary : public IUnknown { public: virtual HRESULT GetBufferForVerticalBlankingLine (/* in */ uint32_t lineNumber, /* out */ void **buffer) = 0; virtual BMDPixelFormat GetPixelFormat (void) = 0; virtual BMDDisplayMode GetDisplayMode (void) = 0; protected: virtual ~IDeckLinkVideoFrameAncillary () {}; // call Release method to drop reference count }; /* Interface IDeckLinkAudioInputPacket - Provided by the IDeckLinkInput callback. */ class IDeckLinkAudioInputPacket : public IUnknown { public: virtual long GetSampleFrameCount (void) = 0; virtual HRESULT GetBytes (/* out */ void **buffer) = 0; virtual HRESULT GetPacketTime (/* out */ BMDTimeValue *packetTime, /* in */ BMDTimeScale timeScale) = 0; protected: virtual ~IDeckLinkAudioInputPacket () {}; // call Release method to drop reference count }; /* Interface IDeckLinkScreenPreviewCallback - Screen preview callback */ class IDeckLinkScreenPreviewCallback : public IUnknown { public: virtual HRESULT DrawFrame (/* in */ IDeckLinkVideoFrame *theFrame) = 0; protected: virtual ~IDeckLinkScreenPreviewCallback () {}; // call Release method to drop reference count }; /* Interface IDeckLinkCocoaScreenPreviewCallback - Screen preview callback for Cocoa-based applications */ class IDeckLinkCocoaScreenPreviewCallback : public IDeckLinkScreenPreviewCallback { public: protected: virtual ~IDeckLinkCocoaScreenPreviewCallback () {}; // call Release method to drop reference count }; /* Interface IDeckLinkGLScreenPreviewHelper - Created with CoCreateInstance(). */ class IDeckLinkGLScreenPreviewHelper : public IUnknown { public: /* Methods must be called with OpenGL context set */ virtual HRESULT InitializeGL (void) = 0; virtual HRESULT PaintGL (void) = 0; virtual HRESULT SetFrame (/* in */ IDeckLinkVideoFrame *theFrame) = 0; virtual HRESULT Set3DPreviewFormat (/* in */ BMD3DPreviewFormat previewFormat) = 0; protected: virtual ~IDeckLinkGLScreenPreviewHelper () {}; // call Release method to drop reference count }; /* Interface IDeckLinkConfiguration - DeckLink Configuration interface */ class IDeckLinkConfiguration : public IUnknown { public: virtual HRESULT SetFlag (/* in */ BMDDeckLinkConfigurationID cfgID, /* in */ bool value) = 0; virtual HRESULT GetFlag (/* in */ BMDDeckLinkConfigurationID cfgID, /* out */ bool *value) = 0; virtual HRESULT SetInt (/* in */ BMDDeckLinkConfigurationID cfgID, /* in */ int64_t value) = 0; virtual HRESULT GetInt (/* in */ BMDDeckLinkConfigurationID cfgID, /* out */ int64_t *value) = 0; virtual HRESULT SetFloat (/* in */ BMDDeckLinkConfigurationID cfgID, /* in */ double value) = 0; virtual HRESULT GetFloat (/* in */ BMDDeckLinkConfigurationID cfgID, /* out */ double *value) = 0; virtual HRESULT SetString (/* in */ BMDDeckLinkConfigurationID cfgID, /* in */ CFStringRef value) = 0; virtual HRESULT GetString (/* in */ BMDDeckLinkConfigurationID cfgID, /* out */ CFStringRef *value) = 0; virtual HRESULT WriteConfigurationToPreferences (void) = 0; protected: virtual ~IDeckLinkConfiguration () {}; // call Release method to drop reference count }; /* Interface IDeckLinkAttributes - DeckLink Attribute interface */ class IDeckLinkAttributes : public IUnknown { public: virtual HRESULT GetFlag (/* in */ BMDDeckLinkAttributeID cfgID, /* out */ bool *value) = 0; virtual HRESULT GetInt (/* in */ BMDDeckLinkAttributeID cfgID, /* out */ int64_t *value) = 0; virtual HRESULT GetFloat (/* in */ BMDDeckLinkAttributeID cfgID, /* out */ double *value) = 0; virtual HRESULT GetString (/* in */ BMDDeckLinkAttributeID cfgID, /* out */ CFStringRef *value) = 0; protected: virtual ~IDeckLinkAttributes () {}; // call Release method to drop reference count }; /* Interface IDeckLinkKeyer - DeckLink Keyer interface */ class IDeckLinkKeyer : public IUnknown { public: virtual HRESULT Enable (/* in */ bool isExternal) = 0; virtual HRESULT SetLevel (/* in */ uint8_t level) = 0; virtual HRESULT RampUp (/* in */ uint32_t numberOfFrames) = 0; virtual HRESULT RampDown (/* in */ uint32_t numberOfFrames) = 0; virtual HRESULT Disable (void) = 0; protected: virtual ~IDeckLinkKeyer () {}; // call Release method to drop reference count }; /* Interface IDeckLinkVideoConversion - Created with CoCreateInstance(). */ class IDeckLinkVideoConversion : public IUnknown { public: virtual HRESULT ConvertFrame (/* in */ IDeckLinkVideoFrame* srcFrame, /* in */ IDeckLinkVideoFrame* dstFrame) = 0; protected: virtual ~IDeckLinkVideoConversion () {}; // call Release method to drop reference count }; /* Interface IDeckLinkDeckControlStatusCallback - Deck control state change callback. */ class IDeckLinkDeckControlStatusCallback : public IUnknown { public: virtual HRESULT TimecodeUpdate (/* in */ BMDTimecodeBCD currentTimecode) = 0; virtual HRESULT VTRControlStateChanged (/* in */ BMDDeckControlVTRControlState newState, /* in */ BMDDeckControlError error) = 0; virtual HRESULT DeckControlEventReceived (/* in */ BMDDeckControlEvent event, /* in */ BMDDeckControlError error) = 0; virtual HRESULT DeckControlStatusChanged (/* in */ BMDDeckControlStatusFlags flags, /* in */ uint32_t mask) = 0; protected: virtual ~IDeckLinkDeckControlStatusCallback () {}; // call Release method to drop reference count }; /* Interface IDeckLinkDeckControl - Deck Control main interface */ class IDeckLinkDeckControl : public IUnknown { public: virtual HRESULT Open (/* in */ BMDTimeScale timeScale, /* in */ BMDTimeValue timeValue, /* in */ bool timecodeIsDropFrame, /* out */ BMDDeckControlError *error) = 0; virtual HRESULT Close (/* in */ bool standbyOn) = 0; virtual HRESULT GetCurrentState (/* out */ BMDDeckControlMode *mode, /* out */ BMDDeckControlVTRControlState *vtrControlState, /* out */ BMDDeckControlStatusFlags *flags) = 0; virtual HRESULT SetStandby (/* in */ bool standbyOn) = 0; virtual HRESULT SendCommand (/* in */ uint8_t *inBuffer, /* in */ uint32_t inBufferSize, /* out */ uint8_t *outBuffer, /* out */ uint32_t *outDataSize, /* in */ uint32_t outBufferSize, /* out */ BMDDeckControlError *error) = 0; virtual HRESULT Play (/* out */ BMDDeckControlError *error) = 0; virtual HRESULT Stop (/* out */ BMDDeckControlError *error) = 0; virtual HRESULT TogglePlayStop (/* out */ BMDDeckControlError *error) = 0; virtual HRESULT Eject (/* out */ BMDDeckControlError *error) = 0; virtual HRESULT GoToTimecode (/* in */ BMDTimecodeBCD timecode, /* out */ BMDDeckControlError *error) = 0; virtual HRESULT FastForward (/* in */ bool viewTape, /* out */ BMDDeckControlError *error) = 0; virtual HRESULT Rewind (/* in */ bool viewTape, /* out */ BMDDeckControlError *error) = 0; virtual HRESULT StepForward (/* out */ BMDDeckControlError *error) = 0; virtual HRESULT StepBack (/* out */ BMDDeckControlError *error) = 0; virtual HRESULT Jog (/* in */ double rate, /* out */ BMDDeckControlError *error) = 0; virtual HRESULT Shuttle (/* in */ double rate, /* out */ BMDDeckControlError *error) = 0; virtual HRESULT GetTimecodeString (/* out */ CFStringRef *currentTimeCode, /* out */ BMDDeckControlError *error) = 0; virtual HRESULT GetTimecode (/* out */ IDeckLinkTimecode **currentTimecode, /* out */ BMDDeckControlError *error) = 0; virtual HRESULT GetTimecodeBCD (/* out */ BMDTimecodeBCD *currentTimecode, /* out */ BMDDeckControlError *error) = 0; virtual HRESULT SetPreroll (/* in */ uint32_t prerollSeconds) = 0; virtual HRESULT GetPreroll (/* out */ uint32_t *prerollSeconds) = 0; virtual HRESULT SetExportOffset (/* in */ int32_t exportOffsetFields) = 0; virtual HRESULT GetExportOffset (/* out */ int32_t *exportOffsetFields) = 0; virtual HRESULT GetManualExportOffset (/* out */ int32_t *deckManualExportOffsetFields) = 0; virtual HRESULT SetCaptureOffset (/* in */ int32_t captureOffsetFields) = 0; virtual HRESULT GetCaptureOffset (/* out */ int32_t *captureOffsetFields) = 0; virtual HRESULT StartExport (/* in */ BMDTimecodeBCD inTimecode, /* in */ BMDTimecodeBCD outTimecode, /* in */ BMDDeckControlExportModeOpsFlags exportModeOps, /* out */ BMDDeckControlError *error) = 0; virtual HRESULT StartCapture (/* in */ bool useVITC, /* in */ BMDTimecodeBCD inTimecode, /* in */ BMDTimecodeBCD outTimecode, /* out */ BMDDeckControlError *error) = 0; virtual HRESULT GetDeviceID (/* out */ uint16_t *deviceId, /* out */ BMDDeckControlError *error) = 0; virtual HRESULT Abort (void) = 0; virtual HRESULT CrashRecordStart (/* out */ BMDDeckControlError *error) = 0; virtual HRESULT CrashRecordStop (/* out */ BMDDeckControlError *error) = 0; virtual HRESULT SetCallback (/* in */ IDeckLinkDeckControlStatusCallback *callback) = 0; protected: virtual ~IDeckLinkDeckControl () {}; // call Release method to drop reference count }; /* Functions */ extern "C" { IDeckLinkIterator* CreateDeckLinkIteratorInstance (void); IDeckLinkAPIInformation* CreateDeckLinkAPIInformationInstance (void); IDeckLinkGLScreenPreviewHelper* CreateOpenGLScreenPreviewHelper (void); IDeckLinkCocoaScreenPreviewCallback* CreateCocoaScreenPreview (void* /* (NSView*) */ parentView); IDeckLinkVideoConversion* CreateVideoConversionInstance (void); }; #endif // defined(__cplusplus) #endif // __DeckLink_API_h__ mlt-7.22.0/src/modules/decklink/darwin/DeckLinkAPIDispatch.cpp000775 000000 000000 00000011627 14531534050 024101 0ustar00rootroot000000 000000 /* -LICENSE-START- ** Copyright (c) 2009 Blackmagic Design ** ** Permission is hereby granted, free of charge, to any person or organization ** obtaining a copy of the software and accompanying documentation covered by ** this license (the "Software") to use, reproduce, display, distribute, ** execute, and transmit the Software, and to prepare derivative works of the ** Software, and to permit third-parties to whom the Software is furnished to ** do so, all subject to the following: ** ** The copyright notices in the Software and this entire statement, including ** the above license grant, this restriction and the following disclaimer, ** must be included in all copies of the Software, in whole or in part, and ** all derivative works of the Software, unless such copies or derivative ** works are solely in the form of machine-executable object code generated by ** a source language processor. ** ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, ** FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT ** SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE ** FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ** ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER ** DEALINGS IN THE SOFTWARE. ** -LICENSE-END- */ /* DeckLinkAPIDispatch.cpp */ #include "DeckLinkAPI.h" #include #if BLACKMAGIC_DECKLINK_API_MAGIC != 1 #error The DeckLink API version of DeckLinkAPIDispatch.cpp is not the same version as DeckLinkAPI.h #endif #define kDeckLinkAPI_BundlePath "/Library/Application Support/Blackmagic Design/Blackmagic DeckLink/DeckLinkAPI.bundle" typedef IDeckLinkIterator* (*CreateIteratorFunc)(void); typedef IDeckLinkAPIInformation* (*CreateAPIInformationFunc)(void); typedef IDeckLinkGLScreenPreviewHelper* (*CreateOpenGLScreenPreviewHelperFunc)(void); typedef IDeckLinkCocoaScreenPreviewCallback* (*CreateCocoaScreenPreviewFunc)(void*); typedef IDeckLinkVideoConversion* (*CreateVideoConversionInstanceFunc)(void); static pthread_once_t gDeckLinkOnceControl = PTHREAD_ONCE_INIT; static CFBundleRef gBundleRef = NULL; static CreateIteratorFunc gCreateIteratorFunc = NULL; static CreateAPIInformationFunc gCreateAPIInformationFunc = NULL; static CreateOpenGLScreenPreviewHelperFunc gCreateOpenGLPreviewFunc = NULL; static CreateCocoaScreenPreviewFunc gCreateCocoaPreviewFunc = NULL; static CreateVideoConversionInstanceFunc gCreateVideoConversionFunc = NULL; void InitDeckLinkAPI (void) { CFURLRef bundleURL; bundleURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, CFSTR(kDeckLinkAPI_BundlePath), kCFURLPOSIXPathStyle, true); if (bundleURL != NULL) { gBundleRef = CFBundleCreate(kCFAllocatorDefault, bundleURL); if (gBundleRef != NULL) { gCreateIteratorFunc = (CreateIteratorFunc)CFBundleGetFunctionPointerForName(gBundleRef, CFSTR("CreateDeckLinkIteratorInstance_0001")); gCreateAPIInformationFunc = (CreateAPIInformationFunc)CFBundleGetFunctionPointerForName(gBundleRef, CFSTR("CreateDeckLinkAPIInformationInstance_0001")); gCreateOpenGLPreviewFunc = (CreateOpenGLScreenPreviewHelperFunc)CFBundleGetFunctionPointerForName(gBundleRef, CFSTR("CreateOpenGLScreenPreviewHelper_0001")); gCreateCocoaPreviewFunc = (CreateCocoaScreenPreviewFunc)CFBundleGetFunctionPointerForName(gBundleRef, CFSTR("CreateCocoaScreenPreview_0001")); gCreateVideoConversionFunc = (CreateVideoConversionInstanceFunc)CFBundleGetFunctionPointerForName(gBundleRef, CFSTR("CreateVideoConversionInstance_0001")); } CFRelease(bundleURL); } } bool IsDeckLinkAPIPresent (void) { // If the DeckLink API bundle was successfully loaded, return this knowledge to the caller if (gBundleRef != NULL) return true; return false; } IDeckLinkIterator* CreateDeckLinkIteratorInstance (void) { pthread_once(&gDeckLinkOnceControl, InitDeckLinkAPI); if (gCreateIteratorFunc == NULL) return NULL; return gCreateIteratorFunc(); } IDeckLinkAPIInformation* CreateDeckLinkAPIInformationInstance (void) { pthread_once(&gDeckLinkOnceControl, InitDeckLinkAPI); if (gCreateAPIInformationFunc == NULL) return NULL; return gCreateAPIInformationFunc(); } IDeckLinkGLScreenPreviewHelper* CreateOpenGLScreenPreviewHelper (void) { pthread_once(&gDeckLinkOnceControl, InitDeckLinkAPI); if (gCreateOpenGLPreviewFunc == NULL) return NULL; return gCreateOpenGLPreviewFunc(); } IDeckLinkCocoaScreenPreviewCallback* CreateCocoaScreenPreview (void* parentView) { pthread_once(&gDeckLinkOnceControl, InitDeckLinkAPI); if (gCreateCocoaPreviewFunc == NULL) return NULL; return gCreateCocoaPreviewFunc(parentView); } IDeckLinkVideoConversion* CreateVideoConversionInstance (void) { pthread_once(&gDeckLinkOnceControl, InitDeckLinkAPI); if (gCreateVideoConversionFunc == NULL) return NULL; return gCreateVideoConversionFunc(); } mlt-7.22.0/src/modules/decklink/linux/000775 000000 000000 00000000000 14531534050 017540 5ustar00rootroot000000 000000 mlt-7.22.0/src/modules/decklink/linux/DeckLinkAPI.h000664 000000 000000 00000146750 14531534050 021744 0ustar00rootroot000000 000000 /* -LICENSE-START- ** Copyright (c) 2009 Blackmagic Design ** ** Permission is hereby granted, free of charge, to any person or organization ** obtaining a copy of the software and accompanying documentation covered by ** this license (the "Software") to use, reproduce, display, distribute, ** execute, and transmit the Software, and to prepare derivative works of the ** Software, and to permit third-parties to whom the Software is furnished to ** do so, all subject to the following: ** ** The copyright notices in the Software and this entire statement, including ** the above license grant, this restriction and the following disclaimer, ** must be included in all copies of the Software, in whole or in part, and ** all derivative works of the Software, unless such copies or derivative ** works are solely in the form of machine-executable object code generated by ** a source language processor. ** ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, ** FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT ** SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE ** FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ** ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER ** DEALINGS IN THE SOFTWARE. ** -LICENSE-END- */ /* DeckLinkAPI.h */ #ifndef __DeckLink_API_h__ #define __DeckLink_API_h__ #include #include "LinuxCOM.h" #define BLACKMAGIC_DECKLINK_API_MAGIC 1 // Type Declarations typedef int64_t BMDTimeValue; typedef int64_t BMDTimeScale; typedef uint32_t BMDTimecodeBCD; typedef uint32_t BMDTimecodeUserBits; // Interface ID Declarations #define IID_IDeckLinkVideoOutputCallback /* 20AA5225-1958-47CB-820B-80A8D521A6EE */ (REFIID){0x20,0xAA,0x52,0x25,0x19,0x58,0x47,0xCB,0x82,0x0B,0x80,0xA8,0xD5,0x21,0xA6,0xEE} #define IID_IDeckLinkInputCallback /* DD04E5EC-7415-42AB-AE4A-E80C4DFC044A */ (REFIID){0xDD,0x04,0xE5,0xEC,0x74,0x15,0x42,0xAB,0xAE,0x4A,0xE8,0x0C,0x4D,0xFC,0x04,0x4A} #define IID_IDeckLinkMemoryAllocator /* B36EB6E7-9D29-4AA8-92EF-843B87A289E8 */ (REFIID){0xB3,0x6E,0xB6,0xE7,0x9D,0x29,0x4A,0xA8,0x92,0xEF,0x84,0x3B,0x87,0xA2,0x89,0xE8} #define IID_IDeckLinkAudioOutputCallback /* 403C681B-7F46-4A12-B993-2BB127084EE6 */ (REFIID){0x40,0x3C,0x68,0x1B,0x7F,0x46,0x4A,0x12,0xB9,0x93,0x2B,0xB1,0x27,0x08,0x4E,0xE6} #define IID_IDeckLinkIterator /* 74E936FC-CC28-4A67-81A0-1E94E52D4E69 */ (REFIID){0x74,0xE9,0x36,0xFC,0xCC,0x28,0x4A,0x67,0x81,0xA0,0x1E,0x94,0xE5,0x2D,0x4E,0x69} #define IID_IDeckLinkAPIInformation /* 7BEA3C68-730D-4322-AF34-8A7152B532A4 */ (REFIID){0x7B,0xEA,0x3C,0x68,0x73,0x0D,0x43,0x22,0xAF,0x34,0x8A,0x71,0x52,0xB5,0x32,0xA4} #define IID_IDeckLinkDisplayModeIterator /* 9C88499F-F601-4021-B80B-032E4EB41C35 */ (REFIID){0x9C,0x88,0x49,0x9F,0xF6,0x01,0x40,0x21,0xB8,0x0B,0x03,0x2E,0x4E,0xB4,0x1C,0x35} #define IID_IDeckLinkDisplayMode /* 3EB2C1AB-0A3D-4523-A3AD-F40D7FB14E78 */ (REFIID){0x3E,0xB2,0xC1,0xAB,0x0A,0x3D,0x45,0x23,0xA3,0xAD,0xF4,0x0D,0x7F,0xB1,0x4E,0x78} #define IID_IDeckLink /* 62BFF75D-6569-4E55-8D4D-66AA03829ABC */ (REFIID){0x62,0xBF,0xF7,0x5D,0x65,0x69,0x4E,0x55,0x8D,0x4D,0x66,0xAA,0x03,0x82,0x9A,0xBC} #define IID_IDeckLinkOutput /* A3EF0963-0862-44ED-92A9-EE89ABF431C7 */ (REFIID){0xA3,0xEF,0x09,0x63,0x08,0x62,0x44,0xED,0x92,0xA9,0xEE,0x89,0xAB,0xF4,0x31,0xC7} #define IID_IDeckLinkInput /* 6D40EF78-28B9-4E21-990D-95BB7750A04F */ (REFIID){0x6D,0x40,0xEF,0x78,0x28,0xB9,0x4E,0x21,0x99,0x0D,0x95,0xBB,0x77,0x50,0xA0,0x4F} #define IID_IDeckLinkTimecode /* BC6CFBD3-8317-4325-AC1C-1216391E9340 */ (REFIID){0xBC,0x6C,0xFB,0xD3,0x83,0x17,0x43,0x25,0xAC,0x1C,0x12,0x16,0x39,0x1E,0x93,0x40} #define IID_IDeckLinkVideoFrame /* 3F716FE0-F023-4111-BE5D-EF4414C05B17 */ (REFIID){0x3F,0x71,0x6F,0xE0,0xF0,0x23,0x41,0x11,0xBE,0x5D,0xEF,0x44,0x14,0xC0,0x5B,0x17} #define IID_IDeckLinkMutableVideoFrame /* 69E2639F-40DA-4E19-B6F2-20ACE815C390 */ (REFIID){0x69,0xE2,0x63,0x9F,0x40,0xDA,0x4E,0x19,0xB6,0xF2,0x20,0xAC,0xE8,0x15,0xC3,0x90} #define IID_IDeckLinkVideoFrame3DExtensions /* DA0F7E4A-EDC7-48A8-9CDD-2DB51C729CD7 */ (REFIID){0xDA,0x0F,0x7E,0x4A,0xED,0xC7,0x48,0xA8,0x9C,0xDD,0x2D,0xB5,0x1C,0x72,0x9C,0xD7} #define IID_IDeckLinkVideoInputFrame /* 05CFE374-537C-4094-9A57-680525118F44 */ (REFIID){0x05,0xCF,0xE3,0x74,0x53,0x7C,0x40,0x94,0x9A,0x57,0x68,0x05,0x25,0x11,0x8F,0x44} #define IID_IDeckLinkVideoFrameAncillary /* 732E723C-D1A4-4E29-9E8E-4A88797A0004 */ (REFIID){0x73,0x2E,0x72,0x3C,0xD1,0xA4,0x4E,0x29,0x9E,0x8E,0x4A,0x88,0x79,0x7A,0x00,0x04} #define IID_IDeckLinkAudioInputPacket /* E43D5870-2894-11DE-8C30-0800200C9A66 */ (REFIID){0xE4,0x3D,0x58,0x70,0x28,0x94,0x11,0xDE,0x8C,0x30,0x08,0x00,0x20,0x0C,0x9A,0x66} #define IID_IDeckLinkScreenPreviewCallback /* B1D3F49A-85FE-4C5D-95C8-0B5D5DCCD438 */ (REFIID){0xB1,0xD3,0xF4,0x9A,0x85,0xFE,0x4C,0x5D,0x95,0xC8,0x0B,0x5D,0x5D,0xCC,0xD4,0x38} #define IID_IDeckLinkGLScreenPreviewHelper /* 504E2209-CAC7-4C1A-9FB4-C5BB6274D22F */ (REFIID){0x50,0x4E,0x22,0x09,0xCA,0xC7,0x4C,0x1A,0x9F,0xB4,0xC5,0xBB,0x62,0x74,0xD2,0x2F} #define IID_IDeckLinkConfiguration /* C679A35B-610C-4D09-B748-1D0478100FC0 */ (REFIID){0xC6,0x79,0xA3,0x5B,0x61,0x0C,0x4D,0x09,0xB7,0x48,0x1D,0x04,0x78,0x10,0x0F,0xC0} #define IID_IDeckLinkAttributes /* ABC11843-D966-44CB-96E2-A1CB5D3135C4 */ (REFIID){0xAB,0xC1,0x18,0x43,0xD9,0x66,0x44,0xCB,0x96,0xE2,0xA1,0xCB,0x5D,0x31,0x35,0xC4} #define IID_IDeckLinkKeyer /* 89AFCAF5-65F8-421E-98F7-96FE5F5BFBA3 */ (REFIID){0x89,0xAF,0xCA,0xF5,0x65,0xF8,0x42,0x1E,0x98,0xF7,0x96,0xFE,0x5F,0x5B,0xFB,0xA3} #define IID_IDeckLinkVideoConversion /* 3BBCB8A2-DA2C-42D9-B5D8-88083644E99A */ (REFIID){0x3B,0xBC,0xB8,0xA2,0xDA,0x2C,0x42,0xD9,0xB5,0xD8,0x88,0x08,0x36,0x44,0xE9,0x9A} #define IID_IDeckLinkDeckControlStatusCallback /* E5F693C1-4283-4716-B18F-C1431521955B */ (REFIID){0xE5,0xF6,0x93,0xC1,0x42,0x83,0x47,0x16,0xB1,0x8F,0xC1,0x43,0x15,0x21,0x95,0x5B} #define IID_IDeckLinkDeckControl /* A4D81043-0619-42B7-8ED6-602D29041DF7 */ (REFIID){0xA4,0xD8,0x10,0x43,0x06,0x19,0x42,0xB7,0x8E,0xD6,0x60,0x2D,0x29,0x04,0x1D,0xF7} /* Enum BMDDisplayMode - Video display modes */ typedef uint32_t BMDDisplayMode; enum _BMDDisplayMode { /* SD Modes */ bmdModeNTSC = /* 'ntsc' */ 0x6E747363, bmdModeNTSC2398 = /* 'nt23' */ 0x6E743233, // 3:2 pulldown bmdModePAL = /* 'pal ' */ 0x70616C20, /* HD 1080 Modes */ bmdModeHD1080p2398 = /* '23ps' */ 0x32337073, bmdModeHD1080p24 = /* '24ps' */ 0x32347073, bmdModeHD1080p25 = /* 'Hp25' */ 0x48703235, bmdModeHD1080p2997 = /* 'Hp29' */ 0x48703239, bmdModeHD1080p30 = /* 'Hp30' */ 0x48703330, bmdModeHD1080i50 = /* 'Hi50' */ 0x48693530, bmdModeHD1080i5994 = /* 'Hi59' */ 0x48693539, bmdModeHD1080i6000 = /* 'Hi60' */ 0x48693630, // N.B. This _really_ is 60.00 Hz. bmdModeHD1080p50 = /* 'Hp50' */ 0x48703530, bmdModeHD1080p5994 = /* 'Hp59' */ 0x48703539, bmdModeHD1080p6000 = /* 'Hp60' */ 0x48703630, // N.B. This _really_ is 60.00 Hz. /* HD 720 Modes */ bmdModeHD720p50 = /* 'hp50' */ 0x68703530, bmdModeHD720p5994 = /* 'hp59' */ 0x68703539, bmdModeHD720p60 = /* 'hp60' */ 0x68703630, /* 2k Modes */ bmdMode2k2398 = /* '2k23' */ 0x326B3233, bmdMode2k24 = /* '2k24' */ 0x326B3234, bmdMode2k25 = /* '2k25' */ 0x326B3235, /* DCI Modes (output only) */ bmdMode2kDCI2398 = /* '2d23' */ 0x32643233, bmdMode2kDCI24 = /* '2d24' */ 0x32643234, bmdMode2kDCI25 = /* '2d25' */ 0x32643235, /* 4k Modes */ bmdMode4K2160p2398 = /* '4k23' */ 0x346B3233, bmdMode4K2160p24 = /* '4k24' */ 0x346B3234, bmdMode4K2160p25 = /* '4k25' */ 0x346B3235, bmdMode4K2160p2997 = /* '4k29' */ 0x346B3239, bmdMode4K2160p30 = /* '4k30' */ 0x346B3330, bmdMode4K2160p50 = /* '4k50' */ 0x346B3530, bmdMode4K2160p5994 = /* '4k59' */ 0x346B3539, bmdMode4K2160p60 = /* '4k60' */ 0x346B3630, /* DCI Modes (output only) */ bmdMode4kDCI2398 = /* '4d23' */ 0x34643233, bmdMode4kDCI24 = /* '4d24' */ 0x34643234, bmdMode4kDCI25 = /* '4d25' */ 0x34643235, /* Special Modes */ bmdModeUnknown = /* 'iunk' */ 0x69756E6B }; /* Enum BMDFieldDominance - Video field dominance */ typedef uint32_t BMDFieldDominance; enum _BMDFieldDominance { bmdUnknownFieldDominance = 0, bmdLowerFieldFirst = /* 'lowr' */ 0x6C6F7772, bmdUpperFieldFirst = /* 'uppr' */ 0x75707072, bmdProgressiveFrame = /* 'prog' */ 0x70726F67, bmdProgressiveSegmentedFrame = /* 'psf ' */ 0x70736620 }; /* Enum BMDPixelFormat - Video pixel formats supported for output/input */ typedef uint32_t BMDPixelFormat; enum _BMDPixelFormat { bmdFormat8BitYUV = /* '2vuy' */ 0x32767579, bmdFormat10BitYUV = /* 'v210' */ 0x76323130, bmdFormat8BitARGB = 32, bmdFormat8BitBGRA = /* 'BGRA' */ 0x42475241, bmdFormat10BitRGB = /* 'r210' */ 0x72323130 // Big-endian RGB 10-bit per component with SMPTE video levels (64-960). Packed as 2:10:10:10 }; /* Enum BMDDisplayModeFlags - Flags to describe the characteristics of an IDeckLinkDisplayMode. */ typedef uint32_t BMDDisplayModeFlags; enum _BMDDisplayModeFlags { bmdDisplayModeSupports3D = 1 << 0, bmdDisplayModeColorspaceRec601 = 1 << 1, bmdDisplayModeColorspaceRec709 = 1 << 2 }; /* Enum BMDVideoOutputFlags - Flags to control the output of ancillary data along with video. */ typedef uint32_t BMDVideoOutputFlags; enum _BMDVideoOutputFlags { bmdVideoOutputFlagDefault = 0, bmdVideoOutputVANC = 1 << 0, bmdVideoOutputVITC = 1 << 1, bmdVideoOutputRP188 = 1 << 2, bmdVideoOutputDualStream3D = 1 << 4 }; /* Enum BMDFrameFlags - Frame flags */ typedef uint32_t BMDFrameFlags; enum _BMDFrameFlags { bmdFrameFlagDefault = 0, bmdFrameFlagFlipVertical = 1 << 0, /* Flags that are applicable only to instances of IDeckLinkVideoInputFrame */ bmdFrameHasNoInputSource = 1 << 31 }; /* Enum BMDVideoInputFlags - Flags applicable to video input */ typedef uint32_t BMDVideoInputFlags; enum _BMDVideoInputFlags { bmdVideoInputFlagDefault = 0, bmdVideoInputEnableFormatDetection = 1 << 0, bmdVideoInputDualStream3D = 1 << 1 }; /* Enum BMDVideoInputFormatChangedEvents - Bitmask passed to the VideoInputFormatChanged notification to identify the properties of the input signal that have changed */ typedef uint32_t BMDVideoInputFormatChangedEvents; enum _BMDVideoInputFormatChangedEvents { bmdVideoInputDisplayModeChanged = 1 << 0, bmdVideoInputFieldDominanceChanged = 1 << 1, bmdVideoInputColorspaceChanged = 1 << 2 }; /* Enum BMDDetectedVideoInputFormatFlags - Flags passed to the VideoInputFormatChanged notification to describe the detected video input signal */ typedef uint32_t BMDDetectedVideoInputFormatFlags; enum _BMDDetectedVideoInputFormatFlags { bmdDetectedVideoInputYCbCr422 = 1 << 0, bmdDetectedVideoInputRGB444 = 1 << 1 }; /* Enum BMDOutputFrameCompletionResult - Frame Completion Callback */ typedef uint32_t BMDOutputFrameCompletionResult; enum _BMDOutputFrameCompletionResult { bmdOutputFrameCompleted, bmdOutputFrameDisplayedLate, bmdOutputFrameDropped, bmdOutputFrameFlushed }; /* Enum BMDReferenceStatus - GenLock input status */ typedef uint32_t BMDReferenceStatus; enum _BMDReferenceStatus { bmdReferenceNotSupportedByHardware = 1 << 0, bmdReferenceLocked = 1 << 1 }; /* Enum BMDAudioSampleRate - Audio sample rates supported for output/input */ typedef uint32_t BMDAudioSampleRate; enum _BMDAudioSampleRate { bmdAudioSampleRate48kHz = 48000 }; /* Enum BMDAudioSampleType - Audio sample sizes supported for output/input */ typedef uint32_t BMDAudioSampleType; enum _BMDAudioSampleType { bmdAudioSampleType16bitInteger = 16, bmdAudioSampleType32bitInteger = 32 }; /* Enum BMDAudioOutputStreamType - Audio output stream type */ typedef uint32_t BMDAudioOutputStreamType; enum _BMDAudioOutputStreamType { bmdAudioOutputStreamContinuous, bmdAudioOutputStreamContinuousDontResample, bmdAudioOutputStreamTimestamped }; /* Enum BMDDisplayModeSupport - Output mode supported flags */ typedef uint32_t BMDDisplayModeSupport; enum _BMDDisplayModeSupport { bmdDisplayModeNotSupported = 0, bmdDisplayModeSupported, bmdDisplayModeSupportedWithConversion }; /* Enum BMDTimecodeFormat - Timecode formats for frame metadata */ typedef uint32_t BMDTimecodeFormat; enum _BMDTimecodeFormat { bmdTimecodeRP188 = /* 'rp18' */ 0x72703138, bmdTimecodeVITC = /* 'vitc' */ 0x76697463, bmdTimecodeSerial = /* 'seri' */ 0x73657269 }; /* Enum BMDTimecodeFlags - Timecode flags */ typedef uint32_t BMDTimecodeFlags; enum _BMDTimecodeFlags { bmdTimecodeFlagDefault = 0, bmdTimecodeIsDropFrame = 1 << 0 }; /* Enum BMDVideoConnection - Video connection types */ typedef uint32_t BMDVideoConnection; enum _BMDVideoConnection { bmdVideoConnectionSDI = 1 << 0, bmdVideoConnectionHDMI = 1 << 1, bmdVideoConnectionOpticalSDI = 1 << 2, bmdVideoConnectionComponent = 1 << 3, bmdVideoConnectionComposite = 1 << 4, bmdVideoConnectionSVideo = 1 << 5 }; /* Enum BMDAnalogVideoFlags - Analog video display flags */ typedef uint32_t BMDAnalogVideoFlags; enum _BMDAnalogVideoFlags { bmdAnalogVideoFlagCompositeSetup75 = 1 << 0, bmdAnalogVideoFlagComponentBetacamLevels = 1 << 1 }; /* Enum BMDAudioConnection - Audio connection types */ typedef uint32_t BMDAudioConnection; enum _BMDAudioConnection { bmdAudioConnectionEmbedded = /* 'embd' */ 0x656D6264, bmdAudioConnectionAESEBU = /* 'aes ' */ 0x61657320, bmdAudioConnectionAnalog = /* 'anlg' */ 0x616E6C67 }; /* Enum BMDAudioOutputAnalogAESSwitch - Audio output Analog/AESEBU switch */ typedef uint32_t BMDAudioOutputAnalogAESSwitch; enum _BMDAudioOutputAnalogAESSwitch { bmdAudioOutputSwitchAESEBU = /* 'aes ' */ 0x61657320, bmdAudioOutputSwitchAnalog = /* 'anlg' */ 0x616E6C67 }; /* Enum BMDVideoOutputConversionMode - Video/audio conversion mode */ typedef uint32_t BMDVideoOutputConversionMode; enum _BMDVideoOutputConversionMode { bmdNoVideoOutputConversion = /* 'none' */ 0x6E6F6E65, bmdVideoOutputLetterboxDownconversion = /* 'ltbx' */ 0x6C746278, bmdVideoOutputAnamorphicDownconversion = /* 'amph' */ 0x616D7068, bmdVideoOutputHD720toHD1080Conversion = /* '720c' */ 0x37323063, bmdVideoOutputHardwareLetterboxDownconversion = /* 'HWlb' */ 0x48576C62, bmdVideoOutputHardwareAnamorphicDownconversion = /* 'HWam' */ 0x4857616D, bmdVideoOutputHardwareCenterCutDownconversion = /* 'HWcc' */ 0x48576363, bmdVideoOutputHardware720p1080pCrossconversion = /* 'xcap' */ 0x78636170, bmdVideoOutputHardwareAnamorphic720pUpconversion = /* 'ua7p' */ 0x75613770, bmdVideoOutputHardwareAnamorphic1080iUpconversion = /* 'ua1i' */ 0x75613169, bmdVideoOutputHardwareAnamorphic149To720pUpconversion = /* 'u47p' */ 0x75343770, bmdVideoOutputHardwareAnamorphic149To1080iUpconversion = /* 'u41i' */ 0x75343169, bmdVideoOutputHardwarePillarbox720pUpconversion = /* 'up7p' */ 0x75703770, bmdVideoOutputHardwarePillarbox1080iUpconversion = /* 'up1i' */ 0x75703169 }; /* Enum BMDVideoInputConversionMode - Video input conversion mode */ typedef uint32_t BMDVideoInputConversionMode; enum _BMDVideoInputConversionMode { bmdNoVideoInputConversion = /* 'none' */ 0x6E6F6E65, bmdVideoInputLetterboxDownconversionFromHD1080 = /* '10lb' */ 0x31306C62, bmdVideoInputAnamorphicDownconversionFromHD1080 = /* '10am' */ 0x3130616D, bmdVideoInputLetterboxDownconversionFromHD720 = /* '72lb' */ 0x37326C62, bmdVideoInputAnamorphicDownconversionFromHD720 = /* '72am' */ 0x3732616D, bmdVideoInputLetterboxUpconversion = /* 'lbup' */ 0x6C627570, bmdVideoInputAnamorphicUpconversion = /* 'amup' */ 0x616D7570 }; /* Enum BMDVideo3DPackingFormat - Video 3D packing format */ typedef uint32_t BMDVideo3DPackingFormat; enum _BMDVideo3DPackingFormat { bmdVideo3DPackingSidebySideHalf = /* 'sbsh' */ 0x73627368, bmdVideo3DPackingLinebyLine = /* 'lbyl' */ 0x6C62796C, bmdVideo3DPackingTopAndBottom = /* 'tabo' */ 0x7461626F, bmdVideo3DPackingLeftOnly = /* 'left' */ 0x6C656674, bmdVideo3DPackingRightOnly = /* 'righ' */ 0x72696768 }; /* Enum BMDDeckLinkConfigurationID - DeckLink Configuration ID */ typedef uint32_t BMDDeckLinkConfigurationID; enum _BMDDeckLinkConfigurationID { /* Video Input/Output Flags */ bmdDeckLinkConfigUse1080pNotPsF = /* 'fpro' */ 0x6670726F, /* Video Input/Output Integers */ bmdDeckLinkConfigHDMI3DPackingFormat = /* '3dpf' */ 0x33647066, /* Audio Input/Output Flags */ bmdDeckLinkConfigAnalogAudioConsumerLevels = /* 'aacl' */ 0x6161636C, /* Video output flags */ bmdDeckLinkConfigFieldFlickerRemoval = /* 'fdfr' */ 0x66646672, bmdDeckLinkConfigHD1080p24ToHD1080i5994Conversion = /* 'to59' */ 0x746F3539, bmdDeckLinkConfig444SDIVideoOutput = /* '444o' */ 0x3434346F, bmdDeckLinkConfig3GBpsVideoOutput = /* '3gbs' */ 0x33676273, bmdDeckLinkConfigBlackVideoOutputDuringCapture = /* 'bvoc' */ 0x62766F63, bmdDeckLinkConfigLowLatencyVideoOutput = /* 'llvo' */ 0x6C6C766F, /* Video Output Integers */ bmdDeckLinkConfigVideoOutputConnection = /* 'vocn' */ 0x766F636E, bmdDeckLinkConfigVideoOutputConversionMode = /* 'vocm' */ 0x766F636D, bmdDeckLinkConfigAnalogVideoOutputFlags = /* 'avof' */ 0x61766F66, bmdDeckLinkConfigReferenceInputTimingOffset = /* 'glot' */ 0x676C6F74, /* Video Input Integers */ bmdDeckLinkConfigVideoInputConnection = /* 'vicn' */ 0x7669636E, bmdDeckLinkConfigAnalogVideoInputFlags = /* 'avif' */ 0x61766966, bmdDeckLinkConfigVideoInputConversionMode = /* 'vicm' */ 0x7669636D, bmdDeckLinkConfig32PulldownSequenceInitialTimecodeFrame = /* 'pdif' */ 0x70646966, bmdDeckLinkConfigVANCSourceLine1Mapping = /* 'vsl1' */ 0x76736C31, bmdDeckLinkConfigVANCSourceLine2Mapping = /* 'vsl2' */ 0x76736C32, bmdDeckLinkConfigVANCSourceLine3Mapping = /* 'vsl3' */ 0x76736C33, /* Audio Input Integers */ bmdDeckLinkConfigAudioInputConnection = /* 'aicn' */ 0x6169636E, /* Audio Input Floats */ bmdDeckLinkConfigAnalogAudioInputScaleChannel1 = /* 'ais1' */ 0x61697331, bmdDeckLinkConfigAnalogAudioInputScaleChannel2 = /* 'ais2' */ 0x61697332, bmdDeckLinkConfigAnalogAudioInputScaleChannel3 = /* 'ais3' */ 0x61697333, bmdDeckLinkConfigAnalogAudioInputScaleChannel4 = /* 'ais4' */ 0x61697334, bmdDeckLinkConfigDigitalAudioInputScale = /* 'dais' */ 0x64616973, /* Audio Output Integers */ bmdDeckLinkConfigAudioOutputAESAnalogSwitch = /* 'aoaa' */ 0x616F6161, /* Audio Output Floats */ bmdDeckLinkConfigAnalogAudioOutputScaleChannel1 = /* 'aos1' */ 0x616F7331, bmdDeckLinkConfigAnalogAudioOutputScaleChannel2 = /* 'aos2' */ 0x616F7332, bmdDeckLinkConfigAnalogAudioOutputScaleChannel3 = /* 'aos3' */ 0x616F7333, bmdDeckLinkConfigAnalogAudioOutputScaleChannel4 = /* 'aos4' */ 0x616F7334, bmdDeckLinkConfigDigitalAudioOutputScale = /* 'daos' */ 0x64616F73 }; /* Enum BMDDeckLinkAttributeID - DeckLink Attribute ID */ typedef uint32_t BMDDeckLinkAttributeID; enum _BMDDeckLinkAttributeID { /* Flags */ BMDDeckLinkSupportsInternalKeying = /* 'keyi' */ 0x6B657969, BMDDeckLinkSupportsExternalKeying = /* 'keye' */ 0x6B657965, BMDDeckLinkSupportsHDKeying = /* 'keyh' */ 0x6B657968, BMDDeckLinkSupportsInputFormatDetection = /* 'infd' */ 0x696E6664, BMDDeckLinkHasReferenceInput = /* 'hrin' */ 0x6872696E, BMDDeckLinkHasSerialPort = /* 'hspt' */ 0x68737074, /* Integers */ BMDDeckLinkMaximumAudioChannels = /* 'mach' */ 0x6D616368, BMDDeckLinkNumberOfSubDevices = /* 'nsbd' */ 0x6E736264, BMDDeckLinkSubDeviceIndex = /* 'subi' */ 0x73756269, BMDDeckLinkVideoOutputConnections = /* 'vocn' */ 0x766F636E, BMDDeckLinkVideoInputConnections = /* 'vicn' */ 0x7669636E, /* Strings */ BMDDeckLinkSerialPortDeviceName = /* 'slpn' */ 0x736C706E }; /* Enum BMDDeckLinkAPIInformationID - DeckLinkAPI information ID */ typedef uint32_t BMDDeckLinkAPIInformationID; enum _BMDDeckLinkAPIInformationID { BMDDeckLinkAPIVersion = /* 'vers' */ 0x76657273 }; /* Enum BMDDeckControlMode - DeckControl mode */ typedef uint32_t BMDDeckControlMode; enum _BMDDeckControlMode { bmdDeckControlNotOpened = /* 'ntop' */ 0x6E746F70, bmdDeckControlVTRControlMode = /* 'vtrc' */ 0x76747263, bmdDeckControlExportMode = /* 'expm' */ 0x6578706D, bmdDeckControlCaptureMode = /* 'capm' */ 0x6361706D }; /* Enum BMDDeckControlEvent - DeckControl event */ typedef uint32_t BMDDeckControlEvent; enum _BMDDeckControlEvent { bmdDeckControlAbortedEvent = /* 'abte' */ 0x61627465, // This event is triggered when a capture or edit-to-tape operation is aborted. /* Export-To-Tape events */ bmdDeckControlPrepareForExportEvent = /* 'pfee' */ 0x70666565, // This event is triggered a few frames before reaching the in-point. IDeckLinkInput::StartScheduledPlayback() should be called at this point. bmdDeckControlExportCompleteEvent = /* 'exce' */ 0x65786365, // This event is triggered a few frames after reaching the out-point. At this point, it is safe to stop playback. /* Capture events */ bmdDeckControlPrepareForCaptureEvent = /* 'pfce' */ 0x70666365, // This event is triggered a few frames before reaching the in-point. The serial timecode attached to IDeckLinkVideoInputFrames is now valid. bmdDeckControlCaptureCompleteEvent = /* 'ccev' */ 0x63636576 // This event is triggered a few frames after reaching the out-point. }; /* Enum BMDDeckControlVTRControlState - VTR Control state */ typedef uint32_t BMDDeckControlVTRControlState; enum _BMDDeckControlVTRControlState { bmdDeckControlNotInVTRControlMode = /* 'nvcm' */ 0x6E76636D, bmdDeckControlVTRControlPlaying = /* 'vtrp' */ 0x76747270, bmdDeckControlVTRControlRecording = /* 'vtrr' */ 0x76747272, bmdDeckControlVTRControlStill = /* 'vtra' */ 0x76747261, bmdDeckControlVTRControlSeeking = /* 'vtrs' */ 0x76747273, bmdDeckControlVTRControlStopped = /* 'vtro' */ 0x7674726F }; /* Enum BMDDeckControlStatusFlags - Deck Control status flags */ typedef uint32_t BMDDeckControlStatusFlags; enum _BMDDeckControlStatusFlags { bmdDeckControlStatusDeckConnected = 1 << 0, bmdDeckControlStatusRemoteMode = 1 << 1, bmdDeckControlStatusRecordInhibited = 1 << 2, bmdDeckControlStatusCassetteOut = 1 << 3 }; /* Enum BMDDeckControlExportModeOpsFlags - Export mode flags */ typedef uint32_t BMDDeckControlExportModeOpsFlags; enum _BMDDeckControlExportModeOpsFlags { bmdDeckControlExportModeInsertVideo = 1 << 0, bmdDeckControlExportModeInsertAudio1 = 1 << 1, bmdDeckControlExportModeInsertAudio2 = 1 << 2, bmdDeckControlExportModeInsertAudio3 = 1 << 3, bmdDeckControlExportModeInsertAudio4 = 1 << 4, bmdDeckControlExportModeInsertAudio5 = 1 << 5, bmdDeckControlExportModeInsertAudio6 = 1 << 6, bmdDeckControlExportModeInsertAudio7 = 1 << 7, bmdDeckControlExportModeInsertAudio8 = 1 << 8, bmdDeckControlExportModeInsertAudio9 = 1 << 9, bmdDeckControlExportModeInsertAudio10 = 1 << 10, bmdDeckControlExportModeInsertAudio11 = 1 << 11, bmdDeckControlExportModeInsertAudio12 = 1 << 12, bmdDeckControlExportModeInsertTimeCode = 1 << 13, bmdDeckControlExportModeInsertAssemble = 1 << 14, bmdDeckControlExportModeInsertPreview = 1 << 15, bmdDeckControlUseManualExport = 1 << 16 }; /* Enum BMDDeckControlError - Deck Control error */ typedef uint32_t BMDDeckControlError; enum _BMDDeckControlError { bmdDeckControlNoError = /* 'noer' */ 0x6E6F6572, bmdDeckControlModeError = /* 'moer' */ 0x6D6F6572, bmdDeckControlMissedInPointError = /* 'mier' */ 0x6D696572, bmdDeckControlDeckTimeoutError = /* 'dter' */ 0x64746572, bmdDeckControlCommandFailedError = /* 'cfer' */ 0x63666572, bmdDeckControlDeviceAlreadyOpenedError = /* 'dalo' */ 0x64616C6F, bmdDeckControlFailedToOpenDeviceError = /* 'fder' */ 0x66646572, bmdDeckControlInLocalModeError = /* 'lmer' */ 0x6C6D6572, bmdDeckControlEndOfTapeError = /* 'eter' */ 0x65746572, bmdDeckControlUserAbortError = /* 'uaer' */ 0x75616572, bmdDeckControlNoTapeInDeckError = /* 'nter' */ 0x6E746572, bmdDeckControlNoVideoFromCardError = /* 'nvfc' */ 0x6E766663, bmdDeckControlNoCommunicationError = /* 'ncom' */ 0x6E636F6D, bmdDeckControlUnknownError = /* 'uner' */ 0x756E6572 }; /* Enum BMD3DPreviewFormat - Linked Frame preview format */ typedef uint32_t BMD3DPreviewFormat; enum _BMD3DPreviewFormat { bmd3DPreviewFormatDefault = /* 'defa' */ 0x64656661, bmd3DPreviewFormatLeftOnly = /* 'left' */ 0x6C656674, bmd3DPreviewFormatRightOnly = /* 'righ' */ 0x72696768, bmd3DPreviewFormatSideBySide = /* 'side' */ 0x73696465, bmd3DPreviewFormatTopBottom = /* 'topb' */ 0x746F7062 }; #if defined(__cplusplus) // Forward Declarations class IDeckLinkVideoOutputCallback; class IDeckLinkInputCallback; class IDeckLinkMemoryAllocator; class IDeckLinkAudioOutputCallback; class IDeckLinkIterator; class IDeckLinkAPIInformation; class IDeckLinkDisplayModeIterator; class IDeckLinkDisplayMode; class IDeckLink; class IDeckLinkOutput; class IDeckLinkInput; class IDeckLinkTimecode; class IDeckLinkVideoFrame; class IDeckLinkMutableVideoFrame; class IDeckLinkVideoFrame3DExtensions; class IDeckLinkVideoInputFrame; class IDeckLinkVideoFrameAncillary; class IDeckLinkAudioInputPacket; class IDeckLinkScreenPreviewCallback; class IDeckLinkGLScreenPreviewHelper; class IDeckLinkConfiguration; class IDeckLinkAttributes; class IDeckLinkKeyer; class IDeckLinkVideoConversion; class IDeckLinkDeckControlStatusCallback; class IDeckLinkDeckControl; /* Interface IDeckLinkVideoOutputCallback - Frame completion callback. */ class IDeckLinkVideoOutputCallback : public IUnknown { public: virtual HRESULT ScheduledFrameCompleted (/* in */ IDeckLinkVideoFrame *completedFrame, /* in */ BMDOutputFrameCompletionResult result) = 0; virtual HRESULT ScheduledPlaybackHasStopped (void) = 0; protected: virtual ~IDeckLinkVideoOutputCallback () {}; // call Release method to drop reference count }; /* Interface IDeckLinkInputCallback - Frame arrival callback. */ class IDeckLinkInputCallback : public IUnknown { public: virtual HRESULT VideoInputFormatChanged (/* in */ BMDVideoInputFormatChangedEvents notificationEvents, /* in */ IDeckLinkDisplayMode *newDisplayMode, /* in */ BMDDetectedVideoInputFormatFlags detectedSignalFlags) = 0; virtual HRESULT VideoInputFrameArrived (/* in */ IDeckLinkVideoInputFrame* videoFrame, /* in */ IDeckLinkAudioInputPacket* audioPacket) = 0; protected: virtual ~IDeckLinkInputCallback () {}; // call Release method to drop reference count }; /* Interface IDeckLinkMemoryAllocator - Memory allocator for video frames. */ class IDeckLinkMemoryAllocator : public IUnknown { public: virtual HRESULT AllocateBuffer (/* in */ uint32_t bufferSize, /* out */ void **allocatedBuffer) = 0; virtual HRESULT ReleaseBuffer (/* in */ void *buffer) = 0; virtual HRESULT Commit (void) = 0; virtual HRESULT Decommit (void) = 0; }; /* Interface IDeckLinkAudioOutputCallback - Optional callback to allow audio samples to be pulled as required. */ class IDeckLinkAudioOutputCallback : public IUnknown { public: virtual HRESULT RenderAudioSamples (/* in */ bool preroll) = 0; }; /* Interface IDeckLinkIterator - enumerates installed DeckLink hardware */ class IDeckLinkIterator : public IUnknown { public: virtual HRESULT Next (/* out */ IDeckLink **deckLinkInstance) = 0; }; /* Interface IDeckLinkAPIInformation - DeckLinkAPI attribute interface */ class IDeckLinkAPIInformation : public IUnknown { public: virtual HRESULT GetFlag (/* in */ BMDDeckLinkAPIInformationID cfgID, /* out */ bool *value) = 0; virtual HRESULT GetInt (/* in */ BMDDeckLinkAPIInformationID cfgID, /* out */ int64_t *value) = 0; virtual HRESULT GetFloat (/* in */ BMDDeckLinkAPIInformationID cfgID, /* out */ double *value) = 0; virtual HRESULT GetString (/* in */ BMDDeckLinkAPIInformationID cfgID, /* out */ const char **value) = 0; protected: virtual ~IDeckLinkAPIInformation () {}; // call Release method to drop reference count }; /* Interface IDeckLinkDisplayModeIterator - enumerates over supported input/output display modes. */ class IDeckLinkDisplayModeIterator : public IUnknown { public: virtual HRESULT Next (/* out */ IDeckLinkDisplayMode **deckLinkDisplayMode) = 0; protected: virtual ~IDeckLinkDisplayModeIterator () {}; // call Release method to drop reference count }; /* Interface IDeckLinkDisplayMode - represents a display mode */ class IDeckLinkDisplayMode : public IUnknown { public: virtual HRESULT GetName (/* out */ const char **name) = 0; virtual BMDDisplayMode GetDisplayMode (void) = 0; virtual long GetWidth (void) = 0; virtual long GetHeight (void) = 0; virtual HRESULT GetFrameRate (/* out */ BMDTimeValue *frameDuration, /* out */ BMDTimeScale *timeScale) = 0; virtual BMDFieldDominance GetFieldDominance (void) = 0; virtual BMDDisplayModeFlags GetFlags (void) = 0; protected: virtual ~IDeckLinkDisplayMode () {}; // call Release method to drop reference count }; /* Interface IDeckLink - represents a DeckLink device */ class IDeckLink : public IUnknown { public: virtual HRESULT GetModelName (/* out */ const char **modelName) = 0; }; /* Interface IDeckLinkOutput - Created by QueryInterface from IDeckLink. */ class IDeckLinkOutput : public IUnknown { public: virtual HRESULT DoesSupportVideoMode (/* in */ BMDDisplayMode displayMode, /* in */ BMDPixelFormat pixelFormat, /* in */ BMDVideoOutputFlags flags, /* out */ BMDDisplayModeSupport *result, /* out */ IDeckLinkDisplayMode **resultDisplayMode) = 0; virtual HRESULT GetDisplayModeIterator (/* out */ IDeckLinkDisplayModeIterator **iterator) = 0; virtual HRESULT SetScreenPreviewCallback (/* in */ IDeckLinkScreenPreviewCallback *previewCallback) = 0; /* Video Output */ virtual HRESULT EnableVideoOutput (/* in */ BMDDisplayMode displayMode, /* in */ BMDVideoOutputFlags flags) = 0; virtual HRESULT DisableVideoOutput (void) = 0; virtual HRESULT SetVideoOutputFrameMemoryAllocator (/* in */ IDeckLinkMemoryAllocator *theAllocator) = 0; virtual HRESULT CreateVideoFrame (/* in */ int32_t width, /* in */ int32_t height, /* in */ int32_t rowBytes, /* in */ BMDPixelFormat pixelFormat, /* in */ BMDFrameFlags flags, /* out */ IDeckLinkMutableVideoFrame **outFrame) = 0; virtual HRESULT CreateAncillaryData (/* in */ BMDPixelFormat pixelFormat, /* out */ IDeckLinkVideoFrameAncillary **outBuffer) = 0; virtual HRESULT DisplayVideoFrameSync (/* in */ IDeckLinkVideoFrame *theFrame) = 0; virtual HRESULT ScheduleVideoFrame (/* in */ IDeckLinkVideoFrame *theFrame, /* in */ BMDTimeValue displayTime, /* in */ BMDTimeValue displayDuration, /* in */ BMDTimeScale timeScale) = 0; virtual HRESULT SetScheduledFrameCompletionCallback (/* in */ IDeckLinkVideoOutputCallback *theCallback) = 0; virtual HRESULT GetBufferedVideoFrameCount (/* out */ uint32_t *bufferedFrameCount) = 0; /* Audio Output */ virtual HRESULT EnableAudioOutput (/* in */ BMDAudioSampleRate sampleRate, /* in */ BMDAudioSampleType sampleType, /* in */ uint32_t channelCount, /* in */ BMDAudioOutputStreamType streamType) = 0; virtual HRESULT DisableAudioOutput (void) = 0; virtual HRESULT WriteAudioSamplesSync (/* in */ void *buffer, /* in */ uint32_t sampleFrameCount, /* out */ uint32_t *sampleFramesWritten) = 0; virtual HRESULT BeginAudioPreroll (void) = 0; virtual HRESULT EndAudioPreroll (void) = 0; virtual HRESULT ScheduleAudioSamples (/* in */ void *buffer, /* in */ uint32_t sampleFrameCount, /* in */ BMDTimeValue streamTime, /* in */ BMDTimeScale timeScale, /* out */ uint32_t *sampleFramesWritten) = 0; virtual HRESULT GetBufferedAudioSampleFrameCount (/* out */ uint32_t *bufferedSampleFrameCount) = 0; virtual HRESULT FlushBufferedAudioSamples (void) = 0; virtual HRESULT SetAudioCallback (/* in */ IDeckLinkAudioOutputCallback *theCallback) = 0; /* Output Control */ virtual HRESULT StartScheduledPlayback (/* in */ BMDTimeValue playbackStartTime, /* in */ BMDTimeScale timeScale, /* in */ double playbackSpeed) = 0; virtual HRESULT StopScheduledPlayback (/* in */ BMDTimeValue stopPlaybackAtTime, /* out */ BMDTimeValue *actualStopTime, /* in */ BMDTimeScale timeScale) = 0; virtual HRESULT IsScheduledPlaybackRunning (/* out */ bool *active) = 0; virtual HRESULT GetScheduledStreamTime (/* in */ BMDTimeScale desiredTimeScale, /* out */ BMDTimeValue *streamTime, /* out */ double *playbackSpeed) = 0; virtual HRESULT GetReferenceStatus (/* out */ BMDReferenceStatus *referenceStatus) = 0; /* Hardware Timing */ virtual HRESULT GetHardwareReferenceClock (/* in */ BMDTimeScale desiredTimeScale, /* out */ BMDTimeValue *hardwareTime, /* out */ BMDTimeValue *timeInFrame, /* out */ BMDTimeValue *ticksPerFrame) = 0; protected: virtual ~IDeckLinkOutput () {}; // call Release method to drop reference count }; /* Interface IDeckLinkInput - Created by QueryInterface from IDeckLink. */ class IDeckLinkInput : public IUnknown { public: virtual HRESULT DoesSupportVideoMode (/* in */ BMDDisplayMode displayMode, /* in */ BMDPixelFormat pixelFormat, /* in */ BMDVideoInputFlags flags, /* out */ BMDDisplayModeSupport *result, /* out */ IDeckLinkDisplayMode **resultDisplayMode) = 0; virtual HRESULT GetDisplayModeIterator (/* out */ IDeckLinkDisplayModeIterator **iterator) = 0; virtual HRESULT SetScreenPreviewCallback (/* in */ IDeckLinkScreenPreviewCallback *previewCallback) = 0; /* Video Input */ virtual HRESULT EnableVideoInput (/* in */ BMDDisplayMode displayMode, /* in */ BMDPixelFormat pixelFormat, /* in */ BMDVideoInputFlags flags) = 0; virtual HRESULT DisableVideoInput (void) = 0; virtual HRESULT GetAvailableVideoFrameCount (/* out */ uint32_t *availableFrameCount) = 0; /* Audio Input */ virtual HRESULT EnableAudioInput (/* in */ BMDAudioSampleRate sampleRate, /* in */ BMDAudioSampleType sampleType, /* in */ uint32_t channelCount) = 0; virtual HRESULT DisableAudioInput (void) = 0; virtual HRESULT GetAvailableAudioSampleFrameCount (/* out */ uint32_t *availableSampleFrameCount) = 0; /* Input Control */ virtual HRESULT StartStreams (void) = 0; virtual HRESULT StopStreams (void) = 0; virtual HRESULT PauseStreams (void) = 0; virtual HRESULT FlushStreams (void) = 0; virtual HRESULT SetCallback (/* in */ IDeckLinkInputCallback *theCallback) = 0; /* Hardware Timing */ virtual HRESULT GetHardwareReferenceClock (/* in */ BMDTimeScale desiredTimeScale, /* out */ BMDTimeValue *hardwareTime, /* out */ BMDTimeValue *timeInFrame, /* out */ BMDTimeValue *ticksPerFrame) = 0; protected: virtual ~IDeckLinkInput () {}; // call Release method to drop reference count }; /* Interface IDeckLinkTimecode - Used for video frame timecode representation. */ class IDeckLinkTimecode : public IUnknown { public: virtual BMDTimecodeBCD GetBCD (void) = 0; virtual HRESULT GetComponents (/* out */ uint8_t *hours, /* out */ uint8_t *minutes, /* out */ uint8_t *seconds, /* out */ uint8_t *frames) = 0; virtual HRESULT GetString (/* out */ const char **timecode) = 0; virtual BMDTimecodeFlags GetFlags (void) = 0; virtual HRESULT GetTimecodeUserBits (/* out */ BMDTimecodeUserBits *userBits) = 0; protected: virtual ~IDeckLinkTimecode () {}; // call Release method to drop reference count }; /* Interface IDeckLinkVideoFrame - Interface to encapsulate a video frame; can be caller-implemented. */ class IDeckLinkVideoFrame : public IUnknown { public: virtual long GetWidth (void) = 0; virtual long GetHeight (void) = 0; virtual long GetRowBytes (void) = 0; virtual BMDPixelFormat GetPixelFormat (void) = 0; virtual BMDFrameFlags GetFlags (void) = 0; virtual HRESULT GetBytes (/* out */ void **buffer) = 0; virtual HRESULT GetTimecode (/* in */ BMDTimecodeFormat format, /* out */ IDeckLinkTimecode **timecode) = 0; virtual HRESULT GetAncillaryData (/* out */ IDeckLinkVideoFrameAncillary **ancillary) = 0; protected: virtual ~IDeckLinkVideoFrame () {}; // call Release method to drop reference count }; /* Interface IDeckLinkMutableVideoFrame - Created by IDeckLinkOutput::CreateVideoFrame. */ class IDeckLinkMutableVideoFrame : public IDeckLinkVideoFrame { public: virtual HRESULT SetFlags (/* in */ BMDFrameFlags newFlags) = 0; virtual HRESULT SetTimecode (/* in */ BMDTimecodeFormat format, /* in */ IDeckLinkTimecode *timecode) = 0; virtual HRESULT SetTimecodeFromComponents (/* in */ BMDTimecodeFormat format, /* in */ uint8_t hours, /* in */ uint8_t minutes, /* in */ uint8_t seconds, /* in */ uint8_t frames, /* in */ BMDTimecodeFlags flags) = 0; virtual HRESULT SetAncillaryData (/* in */ IDeckLinkVideoFrameAncillary *ancillary) = 0; virtual HRESULT SetTimecodeUserBits (/* in */ BMDTimecodeFormat format, /* in */ BMDTimecodeUserBits userBits) = 0; protected: virtual ~IDeckLinkMutableVideoFrame () {}; // call Release method to drop reference count }; /* Interface IDeckLinkVideoFrame3DExtensions - Optional interface implemented on IDeckLinkVideoFrame to support 3D frames */ class IDeckLinkVideoFrame3DExtensions : public IUnknown { public: virtual BMDVideo3DPackingFormat Get3DPackingFormat (void) = 0; virtual HRESULT GetFrameForRightEye (/* in */ IDeckLinkVideoFrame* *rightEyeFrame) = 0; protected: virtual ~IDeckLinkVideoFrame3DExtensions () {}; // call Release method to drop reference count }; /* Interface IDeckLinkVideoInputFrame - Provided by the IDeckLinkVideoInput frame arrival callback. */ class IDeckLinkVideoInputFrame : public IDeckLinkVideoFrame { public: virtual HRESULT GetStreamTime (/* out */ BMDTimeValue *frameTime, /* out */ BMDTimeValue *frameDuration, /* in */ BMDTimeScale timeScale) = 0; virtual HRESULT GetHardwareReferenceTimestamp (/* in */ BMDTimeScale timeScale, /* out */ BMDTimeValue *frameTime, /* out */ BMDTimeValue *frameDuration) = 0; protected: virtual ~IDeckLinkVideoInputFrame () {}; // call Release method to drop reference count }; /* Interface IDeckLinkVideoFrameAncillary - Obtained through QueryInterface() on an IDeckLinkVideoFrame object. */ class IDeckLinkVideoFrameAncillary : public IUnknown { public: virtual HRESULT GetBufferForVerticalBlankingLine (/* in */ uint32_t lineNumber, /* out */ void **buffer) = 0; virtual BMDPixelFormat GetPixelFormat (void) = 0; virtual BMDDisplayMode GetDisplayMode (void) = 0; protected: virtual ~IDeckLinkVideoFrameAncillary () {}; // call Release method to drop reference count }; /* Interface IDeckLinkAudioInputPacket - Provided by the IDeckLinkInput callback. */ class IDeckLinkAudioInputPacket : public IUnknown { public: virtual long GetSampleFrameCount (void) = 0; virtual HRESULT GetBytes (/* out */ void **buffer) = 0; virtual HRESULT GetPacketTime (/* out */ BMDTimeValue *packetTime, /* in */ BMDTimeScale timeScale) = 0; protected: virtual ~IDeckLinkAudioInputPacket () {}; // call Release method to drop reference count }; /* Interface IDeckLinkScreenPreviewCallback - Screen preview callback */ class IDeckLinkScreenPreviewCallback : public IUnknown { public: virtual HRESULT DrawFrame (/* in */ IDeckLinkVideoFrame *theFrame) = 0; protected: virtual ~IDeckLinkScreenPreviewCallback () {}; // call Release method to drop reference count }; /* Interface IDeckLinkGLScreenPreviewHelper - Created with CoCreateInstance(). */ class IDeckLinkGLScreenPreviewHelper : public IUnknown { public: /* Methods must be called with OpenGL context set */ virtual HRESULT InitializeGL (void) = 0; virtual HRESULT PaintGL (void) = 0; virtual HRESULT SetFrame (/* in */ IDeckLinkVideoFrame *theFrame) = 0; virtual HRESULT Set3DPreviewFormat (/* in */ BMD3DPreviewFormat previewFormat) = 0; protected: virtual ~IDeckLinkGLScreenPreviewHelper () {}; // call Release method to drop reference count }; /* Interface IDeckLinkConfiguration - DeckLink Configuration interface */ class IDeckLinkConfiguration : public IUnknown { public: virtual HRESULT SetFlag (/* in */ BMDDeckLinkConfigurationID cfgID, /* in */ bool value) = 0; virtual HRESULT GetFlag (/* in */ BMDDeckLinkConfigurationID cfgID, /* out */ bool *value) = 0; virtual HRESULT SetInt (/* in */ BMDDeckLinkConfigurationID cfgID, /* in */ int64_t value) = 0; virtual HRESULT GetInt (/* in */ BMDDeckLinkConfigurationID cfgID, /* out */ int64_t *value) = 0; virtual HRESULT SetFloat (/* in */ BMDDeckLinkConfigurationID cfgID, /* in */ double value) = 0; virtual HRESULT GetFloat (/* in */ BMDDeckLinkConfigurationID cfgID, /* out */ double *value) = 0; virtual HRESULT SetString (/* in */ BMDDeckLinkConfigurationID cfgID, /* in */ const char *value) = 0; virtual HRESULT GetString (/* in */ BMDDeckLinkConfigurationID cfgID, /* out */ const char **value) = 0; virtual HRESULT WriteConfigurationToPreferences (void) = 0; protected: virtual ~IDeckLinkConfiguration () {}; // call Release method to drop reference count }; /* Interface IDeckLinkAttributes - DeckLink Attribute interface */ class IDeckLinkAttributes : public IUnknown { public: virtual HRESULT GetFlag (/* in */ BMDDeckLinkAttributeID cfgID, /* out */ bool *value) = 0; virtual HRESULT GetInt (/* in */ BMDDeckLinkAttributeID cfgID, /* out */ int64_t *value) = 0; virtual HRESULT GetFloat (/* in */ BMDDeckLinkAttributeID cfgID, /* out */ double *value) = 0; virtual HRESULT GetString (/* in */ BMDDeckLinkAttributeID cfgID, /* out */ const char **value) = 0; protected: virtual ~IDeckLinkAttributes () {}; // call Release method to drop reference count }; /* Interface IDeckLinkKeyer - DeckLink Keyer interface */ class IDeckLinkKeyer : public IUnknown { public: virtual HRESULT Enable (/* in */ bool isExternal) = 0; virtual HRESULT SetLevel (/* in */ uint8_t level) = 0; virtual HRESULT RampUp (/* in */ uint32_t numberOfFrames) = 0; virtual HRESULT RampDown (/* in */ uint32_t numberOfFrames) = 0; virtual HRESULT Disable (void) = 0; protected: virtual ~IDeckLinkKeyer () {}; // call Release method to drop reference count }; /* Interface IDeckLinkVideoConversion - Created with CoCreateInstance(). */ class IDeckLinkVideoConversion : public IUnknown { public: virtual HRESULT ConvertFrame (/* in */ IDeckLinkVideoFrame* srcFrame, /* in */ IDeckLinkVideoFrame* dstFrame) = 0; protected: virtual ~IDeckLinkVideoConversion () {}; // call Release method to drop reference count }; /* Interface IDeckLinkDeckControlStatusCallback - Deck control state change callback. */ class IDeckLinkDeckControlStatusCallback : public IUnknown { public: virtual HRESULT TimecodeUpdate (/* in */ BMDTimecodeBCD currentTimecode) = 0; virtual HRESULT VTRControlStateChanged (/* in */ BMDDeckControlVTRControlState newState, /* in */ BMDDeckControlError error) = 0; virtual HRESULT DeckControlEventReceived (/* in */ BMDDeckControlEvent event, /* in */ BMDDeckControlError error) = 0; virtual HRESULT DeckControlStatusChanged (/* in */ BMDDeckControlStatusFlags flags, /* in */ uint32_t mask) = 0; protected: virtual ~IDeckLinkDeckControlStatusCallback () {}; // call Release method to drop reference count }; /* Interface IDeckLinkDeckControl - Deck Control main interface */ class IDeckLinkDeckControl : public IUnknown { public: virtual HRESULT Open (/* in */ BMDTimeScale timeScale, /* in */ BMDTimeValue timeValue, /* in */ bool timecodeIsDropFrame, /* out */ BMDDeckControlError *error) = 0; virtual HRESULT Close (/* in */ bool standbyOn) = 0; virtual HRESULT GetCurrentState (/* out */ BMDDeckControlMode *mode, /* out */ BMDDeckControlVTRControlState *vtrControlState, /* out */ BMDDeckControlStatusFlags *flags) = 0; virtual HRESULT SetStandby (/* in */ bool standbyOn) = 0; virtual HRESULT Play (/* out */ BMDDeckControlError *error) = 0; virtual HRESULT Stop (/* out */ BMDDeckControlError *error) = 0; virtual HRESULT TogglePlayStop (/* out */ BMDDeckControlError *error) = 0; virtual HRESULT Eject (/* out */ BMDDeckControlError *error) = 0; virtual HRESULT GoToTimecode (/* in */ BMDTimecodeBCD timecode, /* out */ BMDDeckControlError *error) = 0; virtual HRESULT FastForward (/* in */ bool viewTape, /* out */ BMDDeckControlError *error) = 0; virtual HRESULT Rewind (/* in */ bool viewTape, /* out */ BMDDeckControlError *error) = 0; virtual HRESULT StepForward (/* out */ BMDDeckControlError *error) = 0; virtual HRESULT StepBack (/* out */ BMDDeckControlError *error) = 0; virtual HRESULT Jog (/* in */ double rate, /* out */ BMDDeckControlError *error) = 0; virtual HRESULT Shuttle (/* in */ double rate, /* out */ BMDDeckControlError *error) = 0; virtual HRESULT GetTimecodeString (/* out */ const char **currentTimeCode, /* out */ BMDDeckControlError *error) = 0; virtual HRESULT GetTimecode (/* out */ IDeckLinkTimecode **currentTimecode, /* out */ BMDDeckControlError *error) = 0; virtual HRESULT GetTimecodeBCD (/* out */ BMDTimecodeBCD *currentTimecode, /* out */ BMDDeckControlError *error) = 0; virtual HRESULT SetPreroll (/* in */ uint32_t prerollSeconds) = 0; virtual HRESULT GetPreroll (/* out */ uint32_t *prerollSeconds) = 0; virtual HRESULT SetExportOffset (/* in */ int32_t exportOffsetFields) = 0; virtual HRESULT GetExportOffset (/* out */ int32_t *exportOffsetFields) = 0; virtual HRESULT GetManualExportOffset (/* out */ int32_t *deckManualExportOffsetFields) = 0; virtual HRESULT SetCaptureOffset (/* in */ int32_t captureOffsetFields) = 0; virtual HRESULT GetCaptureOffset (/* out */ int32_t *captureOffsetFields) = 0; virtual HRESULT StartExport (/* in */ BMDTimecodeBCD inTimecode, /* in */ BMDTimecodeBCD outTimecode, /* in */ BMDDeckControlExportModeOpsFlags exportModeOps, /* out */ BMDDeckControlError *error) = 0; virtual HRESULT StartCapture (/* in */ bool useVITC, /* in */ BMDTimecodeBCD inTimecode, /* in */ BMDTimecodeBCD outTimecode, /* out */ BMDDeckControlError *error) = 0; virtual HRESULT GetDeviceID (/* out */ uint16_t *deviceId, /* out */ BMDDeckControlError *error) = 0; virtual HRESULT Abort (void) = 0; virtual HRESULT CrashRecordStart (/* out */ BMDDeckControlError *error) = 0; virtual HRESULT CrashRecordStop (/* out */ BMDDeckControlError *error) = 0; virtual HRESULT SetCallback (/* in */ IDeckLinkDeckControlStatusCallback *callback) = 0; protected: virtual ~IDeckLinkDeckControl () {}; // call Release method to drop reference count }; /* Functions */ extern "C" { IDeckLinkIterator* CreateDeckLinkIteratorInstance (void); IDeckLinkGLScreenPreviewHelper* CreateOpenGLScreenPreviewHelper (void); IDeckLinkVideoConversion* CreateVideoConversionInstance (void); }; #endif // defined(__cplusplus) #endif // __DeckLink_API_h__ mlt-7.22.0/src/modules/decklink/linux/DeckLinkAPIDispatch.cpp000664 000000 000000 00000007431 14531534050 023747 0ustar00rootroot000000 000000 /* -LICENSE-START- ** Copyright (c) 2009 Blackmagic Design ** ** Permission is hereby granted, free of charge, to any person or organization ** obtaining a copy of the software and accompanying documentation covered by ** this license (the "Software") to use, reproduce, display, distribute, ** execute, and transmit the Software, and to prepare derivative works of the ** Software, and to permit third-parties to whom the Software is furnished to ** do so, all subject to the following: ** ** The copyright notices in the Software and this entire statement, including ** the above license grant, this restriction and the following disclaimer, ** must be included in all copies of the Software, in whole or in part, and ** all derivative works of the Software, unless such copies or derivative ** works are solely in the form of machine-executable object code generated by ** a source language processor. ** ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, ** FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT ** SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE ** FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ** ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER ** DEALINGS IN THE SOFTWARE. ** -LICENSE-END- **/ #include #include #include #include "DeckLinkAPI.h" #define kDeckLinkAPI_Name "libDeckLinkAPI.so" #define KDeckLinkPreviewAPI_Name "libDeckLinkPreviewAPI.so" typedef IDeckLinkIterator* (*CreateIteratorFunc)(void); typedef IDeckLinkGLScreenPreviewHelper* (*CreateOpenGLScreenPreviewHelperFunc)(void); typedef IDeckLinkVideoConversion* (*CreateVideoConversionInstanceFunc)(void); static pthread_once_t gDeckLinkOnceControl = PTHREAD_ONCE_INIT; static pthread_once_t gPreviewOnceControl = PTHREAD_ONCE_INIT; static CreateIteratorFunc gCreateIteratorFunc = NULL; static CreateOpenGLScreenPreviewHelperFunc gCreateOpenGLPreviewFunc = NULL; static CreateVideoConversionInstanceFunc gCreateVideoConversionFunc = NULL; void InitDeckLinkAPI (void) { void *libraryHandle; libraryHandle = dlopen(kDeckLinkAPI_Name, RTLD_NOW|RTLD_GLOBAL); if (!libraryHandle) { fprintf(stderr, "%s\n", dlerror()); return; } gCreateIteratorFunc = (CreateIteratorFunc)dlsym(libraryHandle, "CreateDeckLinkIteratorInstance_0001"); if (!gCreateIteratorFunc) fprintf(stderr, "%s\n", dlerror()); gCreateVideoConversionFunc = (CreateVideoConversionInstanceFunc)dlsym(libraryHandle, "CreateVideoConversionInstance_0001"); if (!gCreateVideoConversionFunc) fprintf(stderr, "%s\n", dlerror()); } void InitDeckLinkPreviewAPI (void) { void *libraryHandle; libraryHandle = dlopen(KDeckLinkPreviewAPI_Name, RTLD_NOW|RTLD_GLOBAL); if (!libraryHandle) { fprintf(stderr, "%s\n", dlerror()); return; } gCreateOpenGLPreviewFunc = (CreateOpenGLScreenPreviewHelperFunc)dlsym(libraryHandle, "CreateOpenGLScreenPreviewHelper_0001"); if (!gCreateOpenGLPreviewFunc) fprintf(stderr, "%s\n", dlerror()); } IDeckLinkIterator* CreateDeckLinkIteratorInstance (void) { pthread_once(&gDeckLinkOnceControl, InitDeckLinkAPI); if (gCreateIteratorFunc == NULL) return NULL; return gCreateIteratorFunc(); } IDeckLinkGLScreenPreviewHelper* CreateOpenGLScreenPreviewHelper (void) { pthread_once(&gDeckLinkOnceControl, InitDeckLinkAPI); pthread_once(&gPreviewOnceControl, InitDeckLinkPreviewAPI); if (gCreateOpenGLPreviewFunc == NULL) return NULL; return gCreateOpenGLPreviewFunc(); } IDeckLinkVideoConversion* CreateVideoConversionInstance (void) { pthread_once(&gDeckLinkOnceControl, InitDeckLinkAPI); if (gCreateVideoConversionFunc == NULL) return NULL; return gCreateVideoConversionFunc(); } mlt-7.22.0/src/modules/decklink/linux/LinuxCOM.h000664 000000 000000 00000006541 14531534050 021355 0ustar00rootroot000000 000000 /* -LICENSE-START- ** Copyright (c) 2009 Blackmagic Design ** ** Permission is hereby granted, free of charge, to any person or organization ** obtaining a copy of the software and accompanying documentation covered by ** this license (the "Software") to use, reproduce, display, distribute, ** execute, and transmit the Software, and to prepare derivative works of the ** Software, and to permit third-parties to whom the Software is furnished to ** do so, all subject to the following: ** ** The copyright notices in the Software and this entire statement, including ** the above license grant, this restriction and the following disclaimer, ** must be included in all copies of the Software, in whole or in part, and ** all derivative works of the Software, unless such copies or derivative ** works are solely in the form of machine-executable object code generated by ** a source language processor. ** ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, ** FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT ** SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE ** FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ** ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER ** DEALINGS IN THE SOFTWARE. ** -LICENSE-END- */ #ifndef __LINUX_COM_H_ #define __LINUX_COM_H_ struct REFIID { unsigned char byte0; unsigned char byte1; unsigned char byte2; unsigned char byte3; unsigned char byte4; unsigned char byte5; unsigned char byte6; unsigned char byte7; unsigned char byte8; unsigned char byte9; unsigned char byte10; unsigned char byte11; unsigned char byte12; unsigned char byte13; unsigned char byte14; unsigned char byte15; }; typedef REFIID CFUUIDBytes; #define CFUUIDGetUUIDBytes(x) x typedef int HRESULT; typedef unsigned long ULONG; typedef void *LPVOID; #define SUCCEEDED(Status) ((HRESULT)(Status) >= 0) #define FAILED(Status) ((HRESULT)(Status)<0) #define IS_ERROR(Status) ((unsigned long)(Status) >> 31 == SEVERITY_ERROR) #define HRESULT_CODE(hr) ((hr) & 0xFFFF) #define HRESULT_FACILITY(hr) (((hr) >> 16) & 0x1fff) #define HRESULT_SEVERITY(hr) (((hr) >> 31) & 0x1) #define SEVERITY_SUCCESS 0 #define SEVERITY_ERROR 1 #define MAKE_HRESULT(sev,fac,code) ((HRESULT) (((unsigned long)(sev)<<31) | ((unsigned long)(fac)<<16) | ((unsigned long)(code))) ) #define S_OK ((HRESULT)0x00000000L) #define S_FALSE ((HRESULT)0x00000001L) #define E_UNEXPECTED ((HRESULT)0x8000FFFFL) #define E_NOTIMPL ((HRESULT)0x80000001L) #define E_OUTOFMEMORY ((HRESULT)0x80000002L) #define E_INVALIDARG ((HRESULT)0x80000003L) #define E_NOINTERFACE ((HRESULT)0x80000004L) #define E_POINTER ((HRESULT)0x80000005L) #define E_HANDLE ((HRESULT)0x80000006L) #define E_ABORT ((HRESULT)0x80000007L) #define E_FAIL ((HRESULT)0x80000008L) #define E_ACCESSDENIED ((HRESULT)0x80000009L) #define STDMETHODCALLTYPE #define IID_IUnknown (REFIID){0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x46} #define IUnknownUUID IID_IUnknown #ifdef __cplusplus class IUnknown { public: virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, LPVOID *ppv) = 0; virtual ULONG STDMETHODCALLTYPE AddRef(void) = 0; virtual ULONG STDMETHODCALLTYPE Release(void) = 0; }; #endif #endif mlt-7.22.0/src/modules/decklink/producer_decklink.cpp000664 000000 000000 00000107571 14531534050 022607 0ustar00rootroot000000 000000 /* * producer_decklink.c -- input from Blackmagic Design DeckLink * Copyright (C) 2011-2022 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with consumer library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "common.h" #include #include #include #include #include #include #include #include struct copy_lines_sliced_desc { BMDPixelFormat in_fmt; mlt_image_format out_fmt; unsigned char *in_buffer, **out_buffers; int in_stride, *out_strides, w, h; }; #define READ_PIXELS(a, b, c) \ val = *v210++; \ *a++ = (val & 0x3FF) << 6; \ *b++ = ((val >> 10) & 0x3FF) << 6; \ *c++ = ((val >> 20) & 0x3FF) << 6; static int copy_lines_sliced_proc(int id, int idx, int jobs, void *cookie) { int c, H, Y, i; struct copy_lines_sliced_desc *ctx = (struct copy_lines_sliced_desc *) cookie; H = mlt_slices_size_slice(jobs, idx, ctx->h, &Y); if (ctx->in_fmt == bmdFormat10BitYUV) // bmdFormat10BitYUV -> mlt_image_yuv422p16 { for (i = 0; i < H; i++) { uint32_t val, *v210 = (uint32_t *) (ctx->in_buffer + (Y + i) * ctx->in_stride); uint16_t *y = (uint16_t *) (ctx->out_buffers[0] + (Y + i) * ctx->out_strides[0]), *u = (uint16_t *) (ctx->out_buffers[1] + (Y + i) * ctx->out_strides[1]), *v = (uint16_t *) (ctx->out_buffers[2] + (Y + i) * ctx->out_strides[2]); for (c = 0; c < ctx->w / 6; c++) { READ_PIXELS(u, y, v); READ_PIXELS(y, u, y); READ_PIXELS(v, y, u); READ_PIXELS(y, v, y); } } } else // bmdFormat8BitYUV -> mlt_image_yuv422 { if (ctx->out_strides[0] == ctx->in_stride) swab2(ctx->in_buffer + Y * ctx->in_stride, ctx->out_buffers[0] + Y * ctx->out_strides[0], H * ctx->in_stride); else for (i = 0; i < H; i++) swab2(ctx->in_buffer + (Y + i) * ctx->in_stride, ctx->out_buffers[0] + (Y + i) * ctx->out_strides[0], MIN(ctx->in_stride, ctx->out_strides[0])); } return 0; } static void copy_lines(BMDPixelFormat in_fmt, unsigned char *in_buffer, int in_stride, mlt_image_format out_fmt, unsigned char *out_buffers[4], int out_strides[4], int w, int h) { struct copy_lines_sliced_desc ctx = {in_fmt, out_fmt, in_buffer, out_buffers, in_stride, out_strides, w, h}; if (h == 1) copy_lines_sliced_proc(0, 0, 1, &ctx); else mlt_slices_run_normal(mlt_slices_count_normal(), copy_lines_sliced_proc, &ctx); } static void fill_line(mlt_image_format out_fmt, unsigned char *in[4], int strides[4], int pattern) { // TODO } class DeckLinkProducer : public IDeckLinkInputCallback { private: mlt_producer m_producer; IDeckLink *m_decklink; IDeckLinkInput *m_decklinkInput; mlt_deque m_queue; pthread_mutex_t m_mutex; pthread_cond_t m_condition; bool m_started; int m_dropped; bool m_isBuffering; int m_topFieldFirst; BMDPixelFormat m_pixel_format; int m_colorspace; int m_vancLines; mlt_cache m_cache; bool m_reprio; BMDDisplayMode getDisplayMode(mlt_profile profile, int vancLines) { IDeckLinkDisplayModeIterator *iter = NULL; IDeckLinkDisplayMode *mode = NULL; BMDDisplayMode result = (BMDDisplayMode) bmdDisplayModeNotSupported; if (m_decklinkInput->GetDisplayModeIterator(&iter) == S_OK) { while (!result && iter->Next(&mode) == S_OK) { int width = mode->GetWidth(); int height = mode->GetHeight(); BMDTimeValue duration; BMDTimeScale timescale; mode->GetFrameRate(&duration, ×cale); double fps = (double) timescale / duration; int p = mode->GetFieldDominance() == bmdProgressiveFrame; m_topFieldFirst = mode->GetFieldDominance() == bmdUpperFieldFirst; m_colorspace = (mode->GetFlags() & bmdDisplayModeColorspaceRec709) ? 709 : 601; mlt_log_verbose(getProducer(), "BMD mode %dx%d %.3f fps prog %d tff %d\n", width, height, fps, p, m_topFieldFirst); if (width == profile->width && p == profile->progressive && (height + vancLines == profile->height || (height == 486 && profile->height == 480 + vancLines)) && (int) fps == (int) mlt_profile_fps(profile)) result = mode->GetDisplayMode(); SAFE_RELEASE(mode); } SAFE_RELEASE(iter); } return result; } public: mlt_profile m_new_input; void setProducer(mlt_producer producer) { m_producer = producer; } mlt_producer getProducer() const { return m_producer; } DeckLinkProducer() { m_producer = NULL; m_decklink = NULL; m_decklinkInput = NULL; m_new_input = NULL; } virtual ~DeckLinkProducer() { if (m_queue) { stop(); mlt_deque_close(m_queue); pthread_mutex_destroy(&m_mutex); pthread_cond_destroy(&m_condition); mlt_cache_close(m_cache); } SAFE_RELEASE(m_decklinkInput); SAFE_RELEASE(m_decklink); } bool open(unsigned card = 0) { IDeckLinkIterator *decklinkIterator = NULL; try { #ifdef _WIN32 HRESULT result = CoInitialize(NULL); if (FAILED(result)) throw "COM initialization failed"; result = CoCreateInstance(CLSID_CDeckLinkIterator, NULL, CLSCTX_ALL, IID_IDeckLinkIterator, (void **) &decklinkIterator); if (FAILED(result)) throw "The DeckLink drivers are not installed."; #else decklinkIterator = CreateDeckLinkIteratorInstance(); if (!decklinkIterator) throw "The DeckLink drivers are not installed."; #endif // Connect to the Nth DeckLink instance for (unsigned i = 0; decklinkIterator->Next(&m_decklink) == S_OK; i++) { if (i == card) break; else SAFE_RELEASE(m_decklink); } SAFE_RELEASE(decklinkIterator); if (!m_decklink) throw "DeckLink card not found."; // Get the input interface if (m_decklink->QueryInterface(IID_IDeckLinkInput, (void **) &m_decklinkInput) != S_OK) throw "No DeckLink cards support input."; // Provide this class as a delegate to the input callback m_decklinkInput->SetCallback(this); // Initialize other members pthread_mutex_init(&m_mutex, NULL); pthread_cond_init(&m_condition, NULL); m_queue = mlt_deque_init(); m_started = false; m_dropped = 0; m_isBuffering = true; m_cache = mlt_cache_init(); // 3 covers YADIF and increasing framerate use cases mlt_cache_set_size(m_cache, 3); } catch (const char *error) { SAFE_RELEASE(m_decklinkInput); SAFE_RELEASE(m_decklink); mlt_log_error(getProducer(), "%s\n", error); return false; } return true; } bool start(mlt_profile profile = 0) { if (m_started) return false; try { // Initialize some members m_vancLines = mlt_properties_get_int(MLT_PRODUCER_PROPERTIES(getProducer()), "vanc"); if (m_vancLines == -1) m_vancLines = profile->height <= 512 ? 26 : 32; if (!profile) profile = mlt_service_profile(MLT_PRODUCER_SERVICE(getProducer())); // Get the display mode BMDDisplayMode displayMode = getDisplayMode(profile, m_vancLines); if (displayMode == (BMDDisplayMode) bmdDisplayModeNotSupported) { mlt_log_info(getProducer(), "profile = %dx%d %f fps %s\n", profile->width, profile->height, mlt_profile_fps(profile), profile->progressive ? "progressive" : "interlace"); throw "Profile is not compatible with decklink."; } // Determine if supports input format detection #ifdef _WIN32 BOOL doesDetectFormat = FALSE; #else bool doesDetectFormat = false; #endif IDeckLinkAttributes *decklinkAttributes = 0; if (m_decklink->QueryInterface(IID_IDeckLinkAttributes, (void **) &decklinkAttributes) == S_OK) { if (decklinkAttributes->GetFlag(BMDDeckLinkSupportsInputFormatDetection, &doesDetectFormat) != S_OK) doesDetectFormat = false; SAFE_RELEASE(decklinkAttributes); } mlt_log_verbose(getProducer(), "%s format detection\n", doesDetectFormat ? "supports" : "does not support"); // Enable video capture m_pixel_format = (10 == mlt_properties_get_int(MLT_PRODUCER_PROPERTIES(getProducer()), "bitdepth")) ? bmdFormat10BitYUV : bmdFormat8BitYUV; BMDVideoInputFlags flags = doesDetectFormat ? bmdVideoInputEnableFormatDetection : bmdVideoInputFlagDefault; if (S_OK != m_decklinkInput->EnableVideoInput(displayMode, m_pixel_format, flags)) throw "Failed to enable video capture."; // Enable audio capture BMDAudioSampleRate sampleRate = bmdAudioSampleRate48kHz; BMDAudioSampleType sampleType = bmdAudioSampleType16bitInteger; int channels = mlt_properties_get_int(MLT_PRODUCER_PROPERTIES(getProducer()), "channels"); if (S_OK != m_decklinkInput->EnableAudioInput(sampleRate, sampleType, channels)) throw "Failed to enable audio capture."; // Start capture m_dropped = 0; mlt_properties_set_int(MLT_PRODUCER_PROPERTIES(getProducer()), "dropped", m_dropped); m_started = m_decklinkInput->StartStreams() == S_OK; if (!m_started) throw "Failed to start capture."; } catch (const char *error) { m_decklinkInput->DisableVideoInput(); mlt_log_error(getProducer(), "%s\n", error); return false; } return true; } void stop() { if (!m_started) return; m_started = false; // Release the wait in getFrame pthread_mutex_lock(&m_mutex); pthread_cond_broadcast(&m_condition); pthread_mutex_unlock(&m_mutex); m_decklinkInput->StopStreams(); m_decklinkInput->DisableVideoInput(); m_decklinkInput->DisableAudioInput(); // Cleanup queue pthread_mutex_lock(&m_mutex); while (mlt_frame frame = (mlt_frame) mlt_deque_pop_back(m_queue)) mlt_frame_close(frame); pthread_mutex_unlock(&m_mutex); } mlt_frame getFrame() { struct timeval now; struct timespec tm; double fps = mlt_producer_get_fps(getProducer()); mlt_position position = mlt_producer_position(getProducer()); mlt_frame frame = mlt_cache_get_frame(m_cache, position); // Allow the buffer to fill to the requested initial buffer level. if (m_isBuffering) { int prefill = mlt_properties_get_int(MLT_PRODUCER_PROPERTIES(getProducer()), "prefill"); int buffer = mlt_properties_get_int(MLT_PRODUCER_PROPERTIES(getProducer()), "buffer"); m_isBuffering = false; prefill = prefill > buffer ? buffer : prefill; pthread_mutex_lock(&m_mutex); while (mlt_deque_count(m_queue) < prefill) { // Wait up to buffer/fps seconds gettimeofday(&now, NULL); long usec = now.tv_sec * 1000000 + now.tv_usec; usec += 1000000 * buffer / fps; tm.tv_sec = usec / 1000000; tm.tv_nsec = (usec % 1000000) * 1000; if (pthread_cond_timedwait(&m_condition, &m_mutex, &tm)) break; } pthread_mutex_unlock(&m_mutex); } if (!frame) { // Wait if queue is empty pthread_mutex_lock(&m_mutex); while (mlt_deque_count(m_queue) < 1) { // Wait up to twice frame duration gettimeofday(&now, NULL); long usec = now.tv_sec * 1000000 + now.tv_usec; usec += 2000000 / fps; tm.tv_sec = usec / 1000000; tm.tv_nsec = (usec % 1000000) * 1000; if (pthread_cond_timedwait(&m_condition, &m_mutex, &tm)) // Stop waiting if error (timed out) break; } frame = (mlt_frame) mlt_deque_pop_front(m_queue); pthread_mutex_unlock(&m_mutex); // add to cache if (frame) { mlt_frame_set_position(frame, position); mlt_cache_put_frame(m_cache, frame); } } // Set frame timestamp and properties if (frame) { mlt_profile profile = mlt_service_profile(MLT_PRODUCER_SERVICE(getProducer())); mlt_properties properties = MLT_FRAME_PROPERTIES(frame); mlt_properties_set_int(properties, "progressive", profile->progressive); mlt_properties_set_int(properties, "meta.media.progressive", profile->progressive); mlt_properties_set_int(properties, "top_field_first", m_topFieldFirst); mlt_properties_set_double(properties, "aspect_ratio", mlt_profile_sar(profile)); mlt_properties_set_int(properties, "meta.media.sample_aspect_num", profile->sample_aspect_num); mlt_properties_set_int(properties, "meta.media.sample_aspect_den", profile->sample_aspect_den); mlt_properties_set_int(properties, "meta.media.frame_rate_num", profile->frame_rate_num); mlt_properties_set_int(properties, "meta.media.frame_rate_den", profile->frame_rate_den); mlt_properties_set_int(properties, "width", profile->width); mlt_properties_set_int(properties, "meta.media.width", profile->width); mlt_properties_set_int(properties, "height", profile->height); mlt_properties_set_int(properties, "meta.media.height", profile->height); mlt_properties_set_int(properties, "format", (m_pixel_format == bmdFormat8BitYUV) ? mlt_image_yuv422 : mlt_image_yuv422p16); mlt_properties_set_int(properties, "colorspace", m_colorspace); mlt_properties_set_int(properties, "meta.media.colorspace", m_colorspace); mlt_properties_set_int(properties, "audio_frequency", 48000); mlt_properties_set_int(properties, "audio_channels", mlt_properties_get_int(MLT_PRODUCER_PROPERTIES(getProducer()), "channels")); } else mlt_log_warning(getProducer(), "buffer underrun\n"); return frame; } // *** DeckLink API implementation of IDeckLinkInputCallback *** // // IUnknown needs only a dummy implementation virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, LPVOID *ppv) { return E_NOINTERFACE; } virtual ULONG STDMETHODCALLTYPE AddRef() { return 1; } virtual ULONG STDMETHODCALLTYPE Release() { return 1; } /************************* DeckLink API Delegate Methods *****************************/ virtual HRESULT STDMETHODCALLTYPE VideoInputFrameArrived(IDeckLinkVideoInputFrame *video, IDeckLinkAudioInputPacket *audio) { mlt_frame frame = NULL; struct timeval arrived; gettimeofday(&arrived, NULL); if (!m_reprio) { mlt_properties properties = MLT_PRODUCER_PROPERTIES(getProducer()); if (mlt_properties_get(properties, "priority")) { int r; pthread_t thread; pthread_attr_t tattr; struct sched_param param; pthread_attr_init(&tattr); pthread_attr_setschedpolicy(&tattr, SCHED_FIFO); if (!strcmp("max", mlt_properties_get(properties, "priority"))) param.sched_priority = sched_get_priority_max(SCHED_FIFO) - 1; else if (!strcmp("min", mlt_properties_get(properties, "priority"))) param.sched_priority = sched_get_priority_min(SCHED_FIFO) + 1; else param.sched_priority = mlt_properties_get_int(properties, "priority"); pthread_attr_setschedparam(&tattr, ¶m); thread = pthread_self(); r = pthread_setschedparam(thread, SCHED_FIFO, ¶m); if (r) mlt_log_verbose(getProducer(), "VideoInputFrameArrived: pthread_setschedparam returned %d\n", r); else mlt_log_verbose(getProducer(), "VideoInputFrameArrived: param.sched_priority=%d\n", param.sched_priority); }; m_reprio = true; }; if (mlt_properties_get_int(MLT_PRODUCER_PROPERTIES(getProducer()), "preview") && mlt_producer_get_speed(getProducer()) == 0.0 && !mlt_deque_count(m_queue)) { pthread_cond_broadcast(&m_condition); return S_OK; } // Copy video if (video) { IDeckLinkTimecode *timecode = 0; if (!(video->GetFlags() & bmdFrameHasNoInputSource)) { int vitc_in = mlt_properties_get_int(MLT_PRODUCER_PROPERTIES(getProducer()), "vitc_in"); if (vitc_in && (S_OK == video->GetTimecode(bmdTimecodeRP188, &timecode) || S_OK == video->GetTimecode(bmdTimecodeVITC, &timecode)) && timecode) { int vitc = timecode->GetBCD(); SAFE_RELEASE(timecode); mlt_log_verbose(getProducer(), "VideoInputFrameArrived: vitc=%.8X vitc_in=%.8X\n", vitc, vitc_in); if (vitc < vitc_in) { pthread_cond_broadcast(&m_condition); return S_OK; } mlt_properties_set_int(MLT_PRODUCER_PROPERTIES(getProducer()), "vitc_in", 0); } void *buffer; int image_strides[4]; unsigned char *image_buffers[4]; mlt_image_format fmt = (m_pixel_format == bmdFormat8BitYUV) ? mlt_image_yuv422 : mlt_image_yuv422p16; int size = mlt_image_format_size(fmt, video->GetWidth(), video->GetHeight() + m_vancLines, NULL); void *image = mlt_pool_alloc(size); mlt_image_format_planes(fmt, video->GetWidth(), video->GetHeight() + m_vancLines, image, image_buffers, image_strides); // Capture VANC if (m_vancLines > 0) { IDeckLinkVideoFrameAncillary *vanc = 0; if (video->GetAncillaryData(&vanc) == S_OK && vanc) { for (int i = 1; i < m_vancLines + 1; i++) { unsigned char *out[4] = {image_buffers[0] + (i - 1) * image_strides[0], image_buffers[1] + (i - 1) * image_strides[1], image_buffers[2] + (i - 1) * image_strides[2], image_buffers[3] + (i - 1) * image_strides[3]}; if (vanc->GetBufferForVerticalBlankingLine(i, &buffer) == S_OK) copy_lines(m_pixel_format, (unsigned char *) buffer, video->GetRowBytes(), fmt, out, image_strides, video->GetWidth(), 1); else { fill_line(fmt, out, image_strides, 0); mlt_log_debug(getProducer(), "failed capture vanc line %d\n", i); } } SAFE_RELEASE(vanc); } } // Capture image video->GetBytes(&buffer); if (image && buffer) { unsigned char *out[4] = {image_buffers[0] + m_vancLines * image_strides[0], image_buffers[1] + m_vancLines * image_strides[1], image_buffers[2] + m_vancLines * image_strides[2], image_buffers[3] + m_vancLines * image_strides[3]}; copy_lines(m_pixel_format, (unsigned char *) buffer, video->GetRowBytes(), fmt, (unsigned char **) out, image_strides, video->GetWidth(), video->GetHeight()); frame = mlt_frame_init(MLT_PRODUCER_SERVICE(getProducer())); mlt_frame_set_image(frame, (uint8_t *) image, size, mlt_pool_release); } else if (image) { mlt_log_verbose(getProducer(), "no video image\n"); mlt_pool_release(image); } } else { mlt_log_verbose(getProducer(), "no signal\n"); } // Get timecode if ((S_OK == video->GetTimecode(bmdTimecodeRP188, &timecode) || S_OK == video->GetTimecode(bmdTimecodeVITC, &timecode)) && timecode) { DLString timecodeString = 0; if (timecode->GetString(&timecodeString) == S_OK) { char *s = getCString(timecodeString); mlt_properties_set(MLT_FRAME_PROPERTIES(frame), "meta.attr.vitc.markup", s); mlt_log_debug(getProducer(), "timecode %s\n", s); freeCString(s); } freeDLString(timecodeString); SAFE_RELEASE(timecode); } } else { mlt_log_verbose(getProducer(), "no video\n"); } // Copy audio if (frame && audio) { int channels = mlt_properties_get_int(MLT_PRODUCER_PROPERTIES(getProducer()), "channels"); int size = audio->GetSampleFrameCount() * channels * sizeof(int16_t); mlt_audio_format format = mlt_audio_s16; void *pcm = mlt_pool_alloc(size); void *buffer = 0; audio->GetBytes(&buffer); if (buffer) { memcpy(pcm, buffer, size); mlt_frame_set_audio(frame, pcm, format, size, mlt_pool_release); mlt_properties_set_int(MLT_FRAME_PROPERTIES(frame), "audio_samples", audio->GetSampleFrameCount()); } else { mlt_log_verbose(getProducer(), "no audio samples\n"); mlt_pool_release(pcm); } } else { mlt_log_verbose(getProducer(), "no audio\n"); } // Put frame in queue if (frame) { mlt_properties_set_int64(MLT_FRAME_PROPERTIES(frame), "arrived", arrived.tv_sec * 1000000LL + arrived.tv_usec); int queueMax = mlt_properties_get_int(MLT_PRODUCER_PROPERTIES(getProducer()), "buffer"); pthread_mutex_lock(&m_mutex); if (mlt_deque_count(m_queue) < queueMax) { mlt_deque_push_back(m_queue, frame); pthread_cond_broadcast(&m_condition); } else { mlt_frame_close(frame); mlt_properties_set_int(MLT_PRODUCER_PROPERTIES(getProducer()), "dropped", ++m_dropped); mlt_log_warning(getProducer(), "buffer overrun, frame dropped %d\n", m_dropped); } pthread_mutex_unlock(&m_mutex); } return S_OK; } virtual HRESULT STDMETHODCALLTYPE VideoInputFormatChanged(BMDVideoInputFormatChangedEvents events, IDeckLinkDisplayMode *mode, BMDDetectedVideoInputFormatFlags flags) { mlt_profile profile = mlt_service_profile(MLT_PRODUCER_SERVICE(getProducer())); if (events & bmdVideoInputDisplayModeChanged) { BMDTimeValue duration; BMDTimeScale timescale; mode->GetFrameRate(&duration, ×cale); profile->width = mode->GetWidth(); profile->height = mode->GetHeight() + m_vancLines; profile->frame_rate_num = timescale; profile->frame_rate_den = duration; if (profile->width == 720) { if (profile->height == 576) { profile->sample_aspect_num = 16; profile->sample_aspect_den = 15; } else { profile->sample_aspect_num = 8; profile->sample_aspect_den = 9; } profile->display_aspect_num = 4; profile->display_aspect_den = 3; } else { profile->sample_aspect_num = 1; profile->sample_aspect_den = 1; profile->display_aspect_num = 16; profile->display_aspect_den = 9; } free(profile->description); profile->description = strdup("decklink"); mlt_log_verbose(getProducer(), "format changed %dx%d %.3f fps\n", profile->width, profile->height, (double) profile->frame_rate_num / profile->frame_rate_den); m_new_input = profile; } if (events & bmdVideoInputFieldDominanceChanged) { profile->progressive = mode->GetFieldDominance() == bmdProgressiveFrame; m_topFieldFirst = mode->GetFieldDominance() == bmdUpperFieldFirst; mlt_log_verbose(getProducer(), "field dominance changed prog %d tff %d\n", profile->progressive, m_topFieldFirst); } if (events & bmdVideoInputColorspaceChanged) { profile->colorspace = m_colorspace = (mode->GetFlags() & bmdDisplayModeColorspaceRec709) ? 709 : 601; mlt_log_verbose(getProducer(), "colorspace changed %d\n", profile->colorspace); } return S_OK; } }; static int get_audio(mlt_frame frame, int16_t **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples) { return mlt_frame_get_audio(frame, (void **) buffer, format, frequency, channels, samples); } static int get_image(mlt_frame frame, uint8_t **buffer, mlt_image_format *format, int *width, int *height, int writable) { return mlt_frame_get_image(frame, buffer, format, width, height, writable); } static int get_frame(mlt_producer producer, mlt_frame_ptr frame, int index) { DeckLinkProducer *decklink = (DeckLinkProducer *) producer->child; mlt_position pos = mlt_producer_position(producer); mlt_position end = mlt_producer_get_playtime(producer); end = (mlt_producer_get_length(producer) < end ? mlt_producer_get_length(producer) : end) - 1; if (decklink && decklink->m_new_input) { decklink->m_new_input = NULL; decklink->stop(); decklink->start(decklink->m_new_input); } // Re-open if needed if (!decklink && pos < end) { producer->child = decklink = new DeckLinkProducer(); decklink->setProducer(producer); decklink->open(mlt_properties_get_int(MLT_PRODUCER_PROPERTIES(producer), "resource")); } // Start if needed if (decklink) { decklink->start(mlt_service_profile(MLT_PRODUCER_SERVICE(producer))); // Get the next frame from the decklink object if ((*frame = decklink->getFrame())) { // Add audio and video getters mlt_frame_push_audio(*frame, (void *) get_audio); mlt_frame_push_get_image(*frame, get_image); } } if (!*frame) *frame = mlt_frame_init(MLT_PRODUCER_SERVICE(producer)); // Calculate the next timecode mlt_producer_prepare_next(producer); // Close DeckLink if at end if (pos >= end && decklink) { decklink->stop(); delete decklink; producer->child = NULL; } return 0; } static void producer_close(mlt_producer producer) { delete (DeckLinkProducer *) producer->child; producer->close = NULL; mlt_producer_close(producer); } extern "C" { // Listen for the list_devices property to be set static void on_property_changed(void *, mlt_properties properties, mlt_event_data event_data) { const char *name = mlt_event_data_to_string(event_data); IDeckLinkIterator *decklinkIterator = NULL; IDeckLink *decklink = NULL; IDeckLinkInput *decklinkInput = NULL; int i = 0; if (name && !strcmp(name, "list_devices")) mlt_event_block((mlt_event) mlt_properties_get_data(properties, "list-devices-event", NULL)); else return; #ifdef _WIN32 if (FAILED(CoInitialize(NULL))) return; if (FAILED(CoCreateInstance(CLSID_CDeckLinkIterator, NULL, CLSCTX_ALL, IID_IDeckLinkIterator, (void **) &decklinkIterator))) return; #else if (!(decklinkIterator = CreateDeckLinkIteratorInstance())) return; #endif for (; decklinkIterator->Next(&decklink) == S_OK; i++) { if (decklink->QueryInterface(IID_IDeckLinkInput, (void **) &decklinkInput) == S_OK) { DLString name = NULL; if (decklink->GetModelName(&name) == S_OK) { char *name_cstr = getCString(name); const char *format = "device.%d"; char *key = (char *) calloc(1, strlen(format) + 17); sprintf(key, format, i); mlt_properties_set(properties, key, name_cstr); free(key); freeDLString(name); freeCString(name_cstr); } SAFE_RELEASE(decklinkInput); } SAFE_RELEASE(decklink); } SAFE_RELEASE(decklinkIterator); mlt_properties_set_int(properties, "devices", i); } /** Initialise the producer. */ mlt_producer producer_decklink_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { // Allocate the producer DeckLinkProducer *decklink = new DeckLinkProducer(); mlt_producer producer = (mlt_producer) calloc(1, sizeof(*producer)); // If allocated and initializes if (decklink && !mlt_producer_init(producer, decklink)) { // Extract resource (card) from arg, removing path prefix, if any. // (modules such as melted may pass arg with root_dir prefix) char *arg_dup = strdup(arg ? arg : ""); const char *resource = strchr(arg_dup, '/') ? strrchr(arg_dup, '/') + 1 : arg_dup; // Handle empty string resource (arg supplied as "" or "/some/path/") resource = strlen(resource) ? resource : "0"; if (decklink->open(atoi(resource))) { mlt_properties properties = MLT_PRODUCER_PROPERTIES(producer); // Close DeckLink and defer re-open to get_frame delete decklink; producer->child = NULL; // Set callbacks producer->close = (mlt_destructor) producer_close; producer->get_frame = get_frame; // Set properties mlt_properties_set(properties, "resource", resource); mlt_properties_set_int(properties, "channels", 2); mlt_properties_set_int(properties, "buffer", 25); mlt_properties_set_int(properties, "prefill", 25); // These properties effectively make it infinite. mlt_properties_set_int(properties, "length", INT_MAX); mlt_properties_set_int(properties, "out", INT_MAX - 1); mlt_properties_set(properties, "eof", "loop"); mlt_event event = mlt_events_listen(properties, properties, "property-changed", (mlt_listener) on_property_changed); mlt_properties_set_data(properties, "list-devices-event", event, 0, NULL, NULL); } free(arg_dup); } return producer; } } mlt-7.22.0/src/modules/decklink/producer_decklink.yml000664 000000 000000 00000006602 14531534050 022617 0ustar00rootroot000000 000000 schema_version: 7.0 type: producer identifier: decklink title: Blackmagic Design DeckLink Capture version: 1 copyright: Copyright (C) 2011-2018 Meltytech, LLC license: LGPL language: en creator: Dan Dennedy tags: - Audio - Video description: > Capture video and audio using Blackmagic Design DeckLink SDI or Intensity HDMI cards. notes: > Please ensure that you use a MLT profile that is compatible with a broadcast standard which the card you are using supports. If you must use an interlaced profile but wish to deinterlace or scale the input, then you must use the consumer producer, e.g.: melt -profile square_pal consumer:decklink: profile=dv_pal bugs: - It is incompatible with the yadif deinterlacer. - Transport controls such as seeking has no affect. - External deck control is not implemented. - Only 8-bit Y'CbCr is supported at this time. parameters: - identifier: resource argument: yes title: Card type: integer default: 0 minimum: 0 widget: spinner - identifier: channels title: Audio channels type: integer default: 2 minimum: 2 maximum: 16 widget: spinner - identifier: buffer title: Maximum buffer description: > There is a queue of frames between this plugin and its consumer. If the consumer has a little, intermittent delay then it reduces the risk of dropping a frame. However, this provides a maximum number of frames that can be buffered to prevent consuming memory unbounded in the case of frequent or sustained delays. type: integer default: 25 minimum: 0 unit: frames widget: spinner - identifier: prefill title: Initial buffer description: Initially fill the buffer with a number of frames. type: integer default: 25 minimum: 0 unit: frames widget: spinner - identifier: vanc title: Vertical ancillary capture description: > Captures vertical ancillary data as image data and places it at the top of the visible/active image. You can either set the number of lines to capture or use -1 for automatic (32 lines) mode. type: integer minimum: -1 default: 0 unit: lines widget: spinner - identifier: preview title: Enable preview description: Support preview monitoring when paused (speed = 0). type: integer minimum: 0 maximum: 1 default: 0 widget: checkbox - identifier: devices title: Number of devices type: integer readonly: yes minimum: 0 - identifier: device.* title: Device model description: The model name of each device that accepts input. type: string readonly: yes - identifier: priority title: Thread priority description: Set the DeckLink thread's scheduling class to realtime and its priority. type: integer minimum: 1 maximum: 99 default: 20 - identifier: vitc_in title: Start timecode type: integer description: > The vertical interval timecode (VITC) in binary-coded decimal (BCD) format. It skips frames that has VITC timecode less then specified. After reaching first frame with timecode greater or equal then specified this property is reset to zero. - identifier: bitdepth title: Bitdepth for capturing description: Enable capturing in 10-bit native SDI signal type: integer values: - 8 # 8-bit data - 10 # 10-bit data mlt-7.22.0/src/modules/decklink/win/000775 000000 000000 00000000000 14531534050 017176 5ustar00rootroot000000 000000 mlt-7.22.0/src/modules/decklink/win/DeckLinkAPI_h.h000664 000000 000000 00001104275 14531534050 021706 0ustar00rootroot000000 000000 /* this ALWAYS GENERATED file contains the definitions for the interfaces */ /* File created by MIDL compiler version 7.00.0555 */ /* at Tue Sep 07 12:31:45 2010 */ /* Compiler settings for video\DeckLinkAPI.idl: Oicf, W1, Zp8, env=Win32 (32b run), target_arch=X86 7.00.0555 protocol : dce , ms_ext, c_ext, robust error checks: allocation ref bounds_check enum stub_data VC __declspec() decoration level: __declspec(uuid()), __declspec(selectany), __declspec(novtable) DECLSPEC_UUID(), MIDL_INTERFACE() */ /* @@MIDL_FILE_HEADING( ) */ //#pragma warning( disable: 4049 ) /* more than 64k source lines */ /* verify that the version is high enough to compile this file*/ #ifndef __REQUIRED_RPCNDR_H_VERSION__ #define __REQUIRED_RPCNDR_H_VERSION__ 475 #endif #include "rpc.h" #include "rpcndr.h" #ifndef __RPCNDR_H_VERSION__ #error this stub requires an updated version of #endif // __RPCNDR_H_VERSION__ #ifndef __DeckLinkAPI_h_h__ #define __DeckLinkAPI_h_h__ #if defined(_MSC_VER) && (_MSC_VER >= 1020) #pragma once #endif /* Forward Declarations */ #ifndef __IDeckLinkVideoOutputCallback_FWD_DEFINED__ #define __IDeckLinkVideoOutputCallback_FWD_DEFINED__ typedef interface IDeckLinkVideoOutputCallback IDeckLinkVideoOutputCallback; #endif /* __IDeckLinkVideoOutputCallback_FWD_DEFINED__ */ #ifndef __IDeckLinkInputCallback_FWD_DEFINED__ #define __IDeckLinkInputCallback_FWD_DEFINED__ typedef interface IDeckLinkInputCallback IDeckLinkInputCallback; #endif /* __IDeckLinkInputCallback_FWD_DEFINED__ */ #ifndef __IDeckLinkMemoryAllocator_FWD_DEFINED__ #define __IDeckLinkMemoryAllocator_FWD_DEFINED__ typedef interface IDeckLinkMemoryAllocator IDeckLinkMemoryAllocator; #endif /* __IDeckLinkMemoryAllocator_FWD_DEFINED__ */ #ifndef __IDeckLinkAudioOutputCallback_FWD_DEFINED__ #define __IDeckLinkAudioOutputCallback_FWD_DEFINED__ typedef interface IDeckLinkAudioOutputCallback IDeckLinkAudioOutputCallback; #endif /* __IDeckLinkAudioOutputCallback_FWD_DEFINED__ */ #ifndef __IDeckLinkIterator_FWD_DEFINED__ #define __IDeckLinkIterator_FWD_DEFINED__ typedef interface IDeckLinkIterator IDeckLinkIterator; #endif /* __IDeckLinkIterator_FWD_DEFINED__ */ #ifndef __IDeckLinkAPIInformation_FWD_DEFINED__ #define __IDeckLinkAPIInformation_FWD_DEFINED__ typedef interface IDeckLinkAPIInformation IDeckLinkAPIInformation; #endif /* __IDeckLinkAPIInformation_FWD_DEFINED__ */ #ifndef __IDeckLinkDisplayModeIterator_FWD_DEFINED__ #define __IDeckLinkDisplayModeIterator_FWD_DEFINED__ typedef interface IDeckLinkDisplayModeIterator IDeckLinkDisplayModeIterator; #endif /* __IDeckLinkDisplayModeIterator_FWD_DEFINED__ */ #ifndef __IDeckLinkDisplayMode_FWD_DEFINED__ #define __IDeckLinkDisplayMode_FWD_DEFINED__ typedef interface IDeckLinkDisplayMode IDeckLinkDisplayMode; #endif /* __IDeckLinkDisplayMode_FWD_DEFINED__ */ #ifndef __IDeckLink_FWD_DEFINED__ #define __IDeckLink_FWD_DEFINED__ typedef interface IDeckLink IDeckLink; #endif /* __IDeckLink_FWD_DEFINED__ */ #ifndef __IDeckLinkOutput_FWD_DEFINED__ #define __IDeckLinkOutput_FWD_DEFINED__ typedef interface IDeckLinkOutput IDeckLinkOutput; #endif /* __IDeckLinkOutput_FWD_DEFINED__ */ #ifndef __IDeckLinkInput_FWD_DEFINED__ #define __IDeckLinkInput_FWD_DEFINED__ typedef interface IDeckLinkInput IDeckLinkInput; #endif /* __IDeckLinkInput_FWD_DEFINED__ */ #ifndef __IDeckLinkTimecode_FWD_DEFINED__ #define __IDeckLinkTimecode_FWD_DEFINED__ typedef interface IDeckLinkTimecode IDeckLinkTimecode; #endif /* __IDeckLinkTimecode_FWD_DEFINED__ */ #ifndef __IDeckLinkVideoFrame_FWD_DEFINED__ #define __IDeckLinkVideoFrame_FWD_DEFINED__ typedef interface IDeckLinkVideoFrame IDeckLinkVideoFrame; #endif /* __IDeckLinkVideoFrame_FWD_DEFINED__ */ #ifndef __IDeckLinkMutableVideoFrame_FWD_DEFINED__ #define __IDeckLinkMutableVideoFrame_FWD_DEFINED__ typedef interface IDeckLinkMutableVideoFrame IDeckLinkMutableVideoFrame; #endif /* __IDeckLinkMutableVideoFrame_FWD_DEFINED__ */ #ifndef __IDeckLinkVideoFrame3DExtensions_FWD_DEFINED__ #define __IDeckLinkVideoFrame3DExtensions_FWD_DEFINED__ typedef interface IDeckLinkVideoFrame3DExtensions IDeckLinkVideoFrame3DExtensions; #endif /* __IDeckLinkVideoFrame3DExtensions_FWD_DEFINED__ */ #ifndef __IDeckLinkVideoInputFrame_FWD_DEFINED__ #define __IDeckLinkVideoInputFrame_FWD_DEFINED__ typedef interface IDeckLinkVideoInputFrame IDeckLinkVideoInputFrame; #endif /* __IDeckLinkVideoInputFrame_FWD_DEFINED__ */ #ifndef __IDeckLinkVideoFrameAncillary_FWD_DEFINED__ #define __IDeckLinkVideoFrameAncillary_FWD_DEFINED__ typedef interface IDeckLinkVideoFrameAncillary IDeckLinkVideoFrameAncillary; #endif /* __IDeckLinkVideoFrameAncillary_FWD_DEFINED__ */ #ifndef __IDeckLinkAudioInputPacket_FWD_DEFINED__ #define __IDeckLinkAudioInputPacket_FWD_DEFINED__ typedef interface IDeckLinkAudioInputPacket IDeckLinkAudioInputPacket; #endif /* __IDeckLinkAudioInputPacket_FWD_DEFINED__ */ #ifndef __IDeckLinkScreenPreviewCallback_FWD_DEFINED__ #define __IDeckLinkScreenPreviewCallback_FWD_DEFINED__ typedef interface IDeckLinkScreenPreviewCallback IDeckLinkScreenPreviewCallback; #endif /* __IDeckLinkScreenPreviewCallback_FWD_DEFINED__ */ #ifndef __IDeckLinkGLScreenPreviewHelper_FWD_DEFINED__ #define __IDeckLinkGLScreenPreviewHelper_FWD_DEFINED__ typedef interface IDeckLinkGLScreenPreviewHelper IDeckLinkGLScreenPreviewHelper; #endif /* __IDeckLinkGLScreenPreviewHelper_FWD_DEFINED__ */ #ifndef __IDeckLinkConfiguration_FWD_DEFINED__ #define __IDeckLinkConfiguration_FWD_DEFINED__ typedef interface IDeckLinkConfiguration IDeckLinkConfiguration; #endif /* __IDeckLinkConfiguration_FWD_DEFINED__ */ #ifndef __IDeckLinkAttributes_FWD_DEFINED__ #define __IDeckLinkAttributes_FWD_DEFINED__ typedef interface IDeckLinkAttributes IDeckLinkAttributes; #endif /* __IDeckLinkAttributes_FWD_DEFINED__ */ #ifndef __IDeckLinkKeyer_FWD_DEFINED__ #define __IDeckLinkKeyer_FWD_DEFINED__ typedef interface IDeckLinkKeyer IDeckLinkKeyer; #endif /* __IDeckLinkKeyer_FWD_DEFINED__ */ #ifndef __IDeckLinkVideoConversion_FWD_DEFINED__ #define __IDeckLinkVideoConversion_FWD_DEFINED__ typedef interface IDeckLinkVideoConversion IDeckLinkVideoConversion; #endif /* __IDeckLinkVideoConversion_FWD_DEFINED__ */ #ifndef __IDeckLinkDeckControlStatusCallback_FWD_DEFINED__ #define __IDeckLinkDeckControlStatusCallback_FWD_DEFINED__ typedef interface IDeckLinkDeckControlStatusCallback IDeckLinkDeckControlStatusCallback; #endif /* __IDeckLinkDeckControlStatusCallback_FWD_DEFINED__ */ #ifndef __IDeckLinkDeckControl_FWD_DEFINED__ #define __IDeckLinkDeckControl_FWD_DEFINED__ typedef interface IDeckLinkDeckControl IDeckLinkDeckControl; #endif /* __IDeckLinkDeckControl_FWD_DEFINED__ */ #ifndef __CDeckLinkIterator_FWD_DEFINED__ #define __CDeckLinkIterator_FWD_DEFINED__ #ifdef __cplusplus typedef class CDeckLinkIterator CDeckLinkIterator; #else typedef struct CDeckLinkIterator CDeckLinkIterator; #endif /* __cplusplus */ #endif /* __CDeckLinkIterator_FWD_DEFINED__ */ #ifndef __CDeckLinkGLScreenPreviewHelper_FWD_DEFINED__ #define __CDeckLinkGLScreenPreviewHelper_FWD_DEFINED__ #ifdef __cplusplus typedef class CDeckLinkGLScreenPreviewHelper CDeckLinkGLScreenPreviewHelper; #else typedef struct CDeckLinkGLScreenPreviewHelper CDeckLinkGLScreenPreviewHelper; #endif /* __cplusplus */ #endif /* __CDeckLinkGLScreenPreviewHelper_FWD_DEFINED__ */ #ifndef __CDeckLinkVideoConversion_FWD_DEFINED__ #define __CDeckLinkVideoConversion_FWD_DEFINED__ #ifdef __cplusplus typedef class CDeckLinkVideoConversion CDeckLinkVideoConversion; #else typedef struct CDeckLinkVideoConversion CDeckLinkVideoConversion; #endif /* __cplusplus */ #endif /* __CDeckLinkVideoConversion_FWD_DEFINED__ */ #ifndef __IDeckLinkDisplayModeIterator_v7_6_FWD_DEFINED__ #define __IDeckLinkDisplayModeIterator_v7_6_FWD_DEFINED__ typedef interface IDeckLinkDisplayModeIterator_v7_6 IDeckLinkDisplayModeIterator_v7_6; #endif /* __IDeckLinkDisplayModeIterator_v7_6_FWD_DEFINED__ */ #ifndef __IDeckLinkDisplayMode_v7_6_FWD_DEFINED__ #define __IDeckLinkDisplayMode_v7_6_FWD_DEFINED__ typedef interface IDeckLinkDisplayMode_v7_6 IDeckLinkDisplayMode_v7_6; #endif /* __IDeckLinkDisplayMode_v7_6_FWD_DEFINED__ */ #ifndef __IDeckLinkOutput_v7_6_FWD_DEFINED__ #define __IDeckLinkOutput_v7_6_FWD_DEFINED__ typedef interface IDeckLinkOutput_v7_6 IDeckLinkOutput_v7_6; #endif /* __IDeckLinkOutput_v7_6_FWD_DEFINED__ */ #ifndef __IDeckLinkInput_v7_6_FWD_DEFINED__ #define __IDeckLinkInput_v7_6_FWD_DEFINED__ typedef interface IDeckLinkInput_v7_6 IDeckLinkInput_v7_6; #endif /* __IDeckLinkInput_v7_6_FWD_DEFINED__ */ #ifndef __IDeckLinkTimecode_v7_6_FWD_DEFINED__ #define __IDeckLinkTimecode_v7_6_FWD_DEFINED__ typedef interface IDeckLinkTimecode_v7_6 IDeckLinkTimecode_v7_6; #endif /* __IDeckLinkTimecode_v7_6_FWD_DEFINED__ */ #ifndef __IDeckLinkVideoFrame_v7_6_FWD_DEFINED__ #define __IDeckLinkVideoFrame_v7_6_FWD_DEFINED__ typedef interface IDeckLinkVideoFrame_v7_6 IDeckLinkVideoFrame_v7_6; #endif /* __IDeckLinkVideoFrame_v7_6_FWD_DEFINED__ */ #ifndef __IDeckLinkMutableVideoFrame_v7_6_FWD_DEFINED__ #define __IDeckLinkMutableVideoFrame_v7_6_FWD_DEFINED__ typedef interface IDeckLinkMutableVideoFrame_v7_6 IDeckLinkMutableVideoFrame_v7_6; #endif /* __IDeckLinkMutableVideoFrame_v7_6_FWD_DEFINED__ */ #ifndef __IDeckLinkVideoInputFrame_v7_6_FWD_DEFINED__ #define __IDeckLinkVideoInputFrame_v7_6_FWD_DEFINED__ typedef interface IDeckLinkVideoInputFrame_v7_6 IDeckLinkVideoInputFrame_v7_6; #endif /* __IDeckLinkVideoInputFrame_v7_6_FWD_DEFINED__ */ #ifndef __IDeckLinkScreenPreviewCallback_v7_6_FWD_DEFINED__ #define __IDeckLinkScreenPreviewCallback_v7_6_FWD_DEFINED__ typedef interface IDeckLinkScreenPreviewCallback_v7_6 IDeckLinkScreenPreviewCallback_v7_6; #endif /* __IDeckLinkScreenPreviewCallback_v7_6_FWD_DEFINED__ */ #ifndef __IDeckLinkGLScreenPreviewHelper_v7_6_FWD_DEFINED__ #define __IDeckLinkGLScreenPreviewHelper_v7_6_FWD_DEFINED__ typedef interface IDeckLinkGLScreenPreviewHelper_v7_6 IDeckLinkGLScreenPreviewHelper_v7_6; #endif /* __IDeckLinkGLScreenPreviewHelper_v7_6_FWD_DEFINED__ */ #ifndef __IDeckLinkVideoConversion_v7_6_FWD_DEFINED__ #define __IDeckLinkVideoConversion_v7_6_FWD_DEFINED__ typedef interface IDeckLinkVideoConversion_v7_6 IDeckLinkVideoConversion_v7_6; #endif /* __IDeckLinkVideoConversion_v7_6_FWD_DEFINED__ */ #ifndef __IDeckLinkConfiguration_v7_6_FWD_DEFINED__ #define __IDeckLinkConfiguration_v7_6_FWD_DEFINED__ typedef interface IDeckLinkConfiguration_v7_6 IDeckLinkConfiguration_v7_6; #endif /* __IDeckLinkConfiguration_v7_6_FWD_DEFINED__ */ #ifndef __IDeckLinkVideoOutputCallback_v7_6_FWD_DEFINED__ #define __IDeckLinkVideoOutputCallback_v7_6_FWD_DEFINED__ typedef interface IDeckLinkVideoOutputCallback_v7_6 IDeckLinkVideoOutputCallback_v7_6; #endif /* __IDeckLinkVideoOutputCallback_v7_6_FWD_DEFINED__ */ #ifndef __IDeckLinkInputCallback_v7_6_FWD_DEFINED__ #define __IDeckLinkInputCallback_v7_6_FWD_DEFINED__ typedef interface IDeckLinkInputCallback_v7_6 IDeckLinkInputCallback_v7_6; #endif /* __IDeckLinkInputCallback_v7_6_FWD_DEFINED__ */ #ifndef __CDeckLinkGLScreenPreviewHelper_v7_6_FWD_DEFINED__ #define __CDeckLinkGLScreenPreviewHelper_v7_6_FWD_DEFINED__ #ifdef __cplusplus typedef class CDeckLinkGLScreenPreviewHelper_v7_6 CDeckLinkGLScreenPreviewHelper_v7_6; #else typedef struct CDeckLinkGLScreenPreviewHelper_v7_6 CDeckLinkGLScreenPreviewHelper_v7_6; #endif /* __cplusplus */ #endif /* __CDeckLinkGLScreenPreviewHelper_v7_6_FWD_DEFINED__ */ #ifndef __CDeckLinkVideoConversion_v7_6_FWD_DEFINED__ #define __CDeckLinkVideoConversion_v7_6_FWD_DEFINED__ #ifdef __cplusplus typedef class CDeckLinkVideoConversion_v7_6 CDeckLinkVideoConversion_v7_6; #else typedef struct CDeckLinkVideoConversion_v7_6 CDeckLinkVideoConversion_v7_6; #endif /* __cplusplus */ #endif /* __CDeckLinkVideoConversion_v7_6_FWD_DEFINED__ */ #ifndef __IDeckLinkInputCallback_v7_3_FWD_DEFINED__ #define __IDeckLinkInputCallback_v7_3_FWD_DEFINED__ typedef interface IDeckLinkInputCallback_v7_3 IDeckLinkInputCallback_v7_3; #endif /* __IDeckLinkInputCallback_v7_3_FWD_DEFINED__ */ #ifndef __IDeckLinkOutput_v7_3_FWD_DEFINED__ #define __IDeckLinkOutput_v7_3_FWD_DEFINED__ typedef interface IDeckLinkOutput_v7_3 IDeckLinkOutput_v7_3; #endif /* __IDeckLinkOutput_v7_3_FWD_DEFINED__ */ #ifndef __IDeckLinkInput_v7_3_FWD_DEFINED__ #define __IDeckLinkInput_v7_3_FWD_DEFINED__ typedef interface IDeckLinkInput_v7_3 IDeckLinkInput_v7_3; #endif /* __IDeckLinkInput_v7_3_FWD_DEFINED__ */ #ifndef __IDeckLinkVideoInputFrame_v7_3_FWD_DEFINED__ #define __IDeckLinkVideoInputFrame_v7_3_FWD_DEFINED__ typedef interface IDeckLinkVideoInputFrame_v7_3 IDeckLinkVideoInputFrame_v7_3; #endif /* __IDeckLinkVideoInputFrame_v7_3_FWD_DEFINED__ */ #ifndef __IDeckLinkDisplayModeIterator_v7_1_FWD_DEFINED__ #define __IDeckLinkDisplayModeIterator_v7_1_FWD_DEFINED__ typedef interface IDeckLinkDisplayModeIterator_v7_1 IDeckLinkDisplayModeIterator_v7_1; #endif /* __IDeckLinkDisplayModeIterator_v7_1_FWD_DEFINED__ */ #ifndef __IDeckLinkDisplayMode_v7_1_FWD_DEFINED__ #define __IDeckLinkDisplayMode_v7_1_FWD_DEFINED__ typedef interface IDeckLinkDisplayMode_v7_1 IDeckLinkDisplayMode_v7_1; #endif /* __IDeckLinkDisplayMode_v7_1_FWD_DEFINED__ */ #ifndef __IDeckLinkVideoFrame_v7_1_FWD_DEFINED__ #define __IDeckLinkVideoFrame_v7_1_FWD_DEFINED__ typedef interface IDeckLinkVideoFrame_v7_1 IDeckLinkVideoFrame_v7_1; #endif /* __IDeckLinkVideoFrame_v7_1_FWD_DEFINED__ */ #ifndef __IDeckLinkVideoInputFrame_v7_1_FWD_DEFINED__ #define __IDeckLinkVideoInputFrame_v7_1_FWD_DEFINED__ typedef interface IDeckLinkVideoInputFrame_v7_1 IDeckLinkVideoInputFrame_v7_1; #endif /* __IDeckLinkVideoInputFrame_v7_1_FWD_DEFINED__ */ #ifndef __IDeckLinkAudioInputPacket_v7_1_FWD_DEFINED__ #define __IDeckLinkAudioInputPacket_v7_1_FWD_DEFINED__ typedef interface IDeckLinkAudioInputPacket_v7_1 IDeckLinkAudioInputPacket_v7_1; #endif /* __IDeckLinkAudioInputPacket_v7_1_FWD_DEFINED__ */ #ifndef __IDeckLinkVideoOutputCallback_v7_1_FWD_DEFINED__ #define __IDeckLinkVideoOutputCallback_v7_1_FWD_DEFINED__ typedef interface IDeckLinkVideoOutputCallback_v7_1 IDeckLinkVideoOutputCallback_v7_1; #endif /* __IDeckLinkVideoOutputCallback_v7_1_FWD_DEFINED__ */ #ifndef __IDeckLinkInputCallback_v7_1_FWD_DEFINED__ #define __IDeckLinkInputCallback_v7_1_FWD_DEFINED__ typedef interface IDeckLinkInputCallback_v7_1 IDeckLinkInputCallback_v7_1; #endif /* __IDeckLinkInputCallback_v7_1_FWD_DEFINED__ */ #ifndef __IDeckLinkOutput_v7_1_FWD_DEFINED__ #define __IDeckLinkOutput_v7_1_FWD_DEFINED__ typedef interface IDeckLinkOutput_v7_1 IDeckLinkOutput_v7_1; #endif /* __IDeckLinkOutput_v7_1_FWD_DEFINED__ */ #ifndef __IDeckLinkInput_v7_1_FWD_DEFINED__ #define __IDeckLinkInput_v7_1_FWD_DEFINED__ typedef interface IDeckLinkInput_v7_1 IDeckLinkInput_v7_1; #endif /* __IDeckLinkInput_v7_1_FWD_DEFINED__ */ /* header files for imported files */ #include "unknwn.h" #ifdef __cplusplus extern "C"{ #endif #ifndef __DeckLinkAPI_LIBRARY_DEFINED__ #define __DeckLinkAPI_LIBRARY_DEFINED__ /* library DeckLinkAPI */ /* [helpstring][version][uuid] */ typedef LONGLONG BMDTimeValue; typedef LONGLONG BMDTimeScale; typedef unsigned long BMDTimecodeBCD; typedef unsigned long BMDTimecodeUserBits; typedef unsigned long BMDDisplayModeFlags; typedef unsigned long BMDFrameFlags; typedef unsigned long BMDVideoInputFlags; typedef unsigned long BMDVideoInputFormatChangedEvents; typedef unsigned long BMDDetectedVideoInputFormatFlags; typedef unsigned long BMDTimecodeFlags; typedef unsigned long BMDAnalogVideoFlags; typedef unsigned long BMDDeckControlStatusFlags; typedef unsigned long BMDDeckControlExportModeOpsFlags; #if 0 typedef enum _BMDDisplayModeFlags BMDDisplayModeFlags; typedef enum _BMDFrameFlags BMDFrameFlags; typedef enum _BMDVideoInputFlags BMDVideoInputFlags; typedef enum _BMDVideoInputFormatChangedEvents BMDVideoInputFormatChangedEvents; typedef enum _BMDDetectedVideoInputFormatFlags BMDDetectedVideoInputFormatFlags; typedef enum _BMDTimecodeFlags BMDTimecodeFlags; typedef enum _BMDAnalogVideoFlags BMDAnalogVideoFlags; typedef enum _BMDDeckControlStatusFlags BMDDeckControlStatusFlags; typedef enum _BMDDeckControlExportModeOpsFlags BMDDeckControlExportModeOpsFlags; #endif typedef /* [v1_enum] */ enum _BMDDisplayMode { bmdModeNTSC = 0x6e747363, bmdModeNTSC2398 = 0x6e743233, bmdModePAL = 0x70616c20, bmdModeHD1080p2398 = 0x32337073, bmdModeHD1080p24 = 0x32347073, bmdModeHD1080p25 = 0x48703235, bmdModeHD1080p2997 = 0x48703239, bmdModeHD1080p30 = 0x48703330, bmdModeHD1080i50 = 0x48693530, bmdModeHD1080i5994 = 0x48693539, bmdModeHD1080i6000 = 0x48693630, bmdModeHD1080p50 = 0x48703530, bmdModeHD1080p5994 = 0x48703539, bmdModeHD1080p6000 = 0x48703630, bmdModeHD720p50 = 0x68703530, bmdModeHD720p5994 = 0x68703539, bmdModeHD720p60 = 0x68703630, bmdMode2k2398 = 0x326b3233, bmdMode2k24 = 0x326b3234, bmdMode2k25 = 0x326b3235, bmdMode2kDCI2398 = 0x32643233, bmdMode2kDCI24 = 0x32643234, bmdMode2kDCI25 = 0x32643235, bmdMode4K2160p2398 = 0x346b3233, bmdMode4K2160p24 = 0x346b3234, bmdMode4K2160p25 = 0x346b3235, bmdMode4K2160p2997 = 0x346b3239, bmdMode4K2160p30 = 0x346b3330, bmdMode4K2160p50 = 0x346b3530, bmdMode4K2160p5994 = 0x346b3539, bmdMode4K2160p60 = 0x346b3630, bmdMode4kDCI2398 = 0x34643233, bmdMode4kDCI24 = 0x34643234, bmdMode4kDCI25 = 0x34643235, bmdModeUnknown = 0x69756e6b } BMDDisplayMode; typedef /* [v1_enum] */ enum _BMDFieldDominance { bmdUnknownFieldDominance = 0, bmdLowerFieldFirst = 0x6c6f7772, bmdUpperFieldFirst = 0x75707072, bmdProgressiveFrame = 0x70726f67, bmdProgressiveSegmentedFrame = 0x70736620 } BMDFieldDominance; typedef /* [v1_enum] */ enum _BMDPixelFormat { bmdFormat8BitYUV = 0x32767579, bmdFormat10BitYUV = 0x76323130, bmdFormat8BitARGB = 32, bmdFormat8BitBGRA = 0x42475241, bmdFormat10BitRGB = 0x72323130 } BMDPixelFormat; /* [v1_enum] */ enum _BMDDisplayModeFlags { bmdDisplayModeSupports3D = ( 1 << 0 ) , bmdDisplayModeColorspaceRec601 = ( 1 << 1 ) , bmdDisplayModeColorspaceRec709 = ( 1 << 2 ) } ; typedef /* [v1_enum] */ enum _BMDVideoOutputFlags { bmdVideoOutputFlagDefault = 0, bmdVideoOutputVANC = ( 1 << 0 ) , bmdVideoOutputVITC = ( 1 << 1 ) , bmdVideoOutputRP188 = ( 1 << 2 ) , bmdVideoOutputDualStream3D = ( 1 << 4 ) } BMDVideoOutputFlags; /* [v1_enum] */ enum _BMDFrameFlags { bmdFrameFlagDefault = 0, bmdFrameFlagFlipVertical = ( 1 << 0 ) , bmdFrameHasNoInputSource = ( 1 << 31 ) } ; /* [v1_enum] */ enum _BMDVideoInputFlags { bmdVideoInputFlagDefault = 0, bmdVideoInputEnableFormatDetection = ( 1 << 0 ) , bmdVideoInputDualStream3D = ( 1 << 1 ) } ; /* [v1_enum] */ enum _BMDVideoInputFormatChangedEvents { bmdVideoInputDisplayModeChanged = ( 1 << 0 ) , bmdVideoInputFieldDominanceChanged = ( 1 << 1 ) , bmdVideoInputColorspaceChanged = ( 1 << 2 ) } ; /* [v1_enum] */ enum _BMDDetectedVideoInputFormatFlags { bmdDetectedVideoInputYCbCr422 = ( 1 << 0 ) , bmdDetectedVideoInputRGB444 = ( 1 << 1 ) } ; typedef /* [v1_enum] */ enum _BMDOutputFrameCompletionResult { bmdOutputFrameCompleted = 0, bmdOutputFrameDisplayedLate = ( bmdOutputFrameCompleted + 1 ) , bmdOutputFrameDropped = ( bmdOutputFrameDisplayedLate + 1 ) , bmdOutputFrameFlushed = ( bmdOutputFrameDropped + 1 ) } BMDOutputFrameCompletionResult; typedef /* [v1_enum] */ enum _BMDReferenceStatus { bmdReferenceNotSupportedByHardware = ( 1 << 0 ) , bmdReferenceLocked = ( 1 << 1 ) } BMDReferenceStatus; typedef /* [v1_enum] */ enum _BMDAudioSampleRate { bmdAudioSampleRate48kHz = 48000 } BMDAudioSampleRate; typedef /* [v1_enum] */ enum _BMDAudioSampleType { bmdAudioSampleType16bitInteger = 16, bmdAudioSampleType32bitInteger = 32 } BMDAudioSampleType; typedef /* [v1_enum] */ enum _BMDAudioOutputStreamType { bmdAudioOutputStreamContinuous = 0, bmdAudioOutputStreamContinuousDontResample = ( bmdAudioOutputStreamContinuous + 1 ) , bmdAudioOutputStreamTimestamped = ( bmdAudioOutputStreamContinuousDontResample + 1 ) } BMDAudioOutputStreamType; typedef /* [v1_enum] */ enum _BMDDisplayModeSupport { bmdDisplayModeNotSupported = 0, bmdDisplayModeSupported = ( bmdDisplayModeNotSupported + 1 ) , bmdDisplayModeSupportedWithConversion = ( bmdDisplayModeSupported + 1 ) } BMDDisplayModeSupport; typedef /* [v1_enum] */ enum _BMDTimecodeFormat { bmdTimecodeRP188 = 0x72703138, bmdTimecodeVITC = 0x76697463, bmdTimecodeSerial = 0x73657269 } BMDTimecodeFormat; /* [v1_enum] */ enum _BMDTimecodeFlags { bmdTimecodeFlagDefault = 0, bmdTimecodeIsDropFrame = ( 1 << 0 ) } ; typedef /* [v1_enum] */ enum _BMDVideoConnection { bmdVideoConnectionSDI = ( 1 << 0 ) , bmdVideoConnectionHDMI = ( 1 << 1 ) , bmdVideoConnectionOpticalSDI = ( 1 << 2 ) , bmdVideoConnectionComponent = ( 1 << 3 ) , bmdVideoConnectionComposite = ( 1 << 4 ) , bmdVideoConnectionSVideo = ( 1 << 5 ) } BMDVideoConnection; /* [v1_enum] */ enum _BMDAnalogVideoFlags { bmdAnalogVideoFlagCompositeSetup75 = ( 1 << 0 ) , bmdAnalogVideoFlagComponentBetacamLevels = ( 1 << 1 ) } ; typedef /* [v1_enum] */ enum _BMDAudioConnection { bmdAudioConnectionEmbedded = 0x656d6264, bmdAudioConnectionAESEBU = 0x61657320, bmdAudioConnectionAnalog = 0x616e6c67 } BMDAudioConnection; typedef /* [v1_enum] */ enum _BMDAudioOutputAnalogAESSwitch { bmdAudioOutputSwitchAESEBU = 0x61657320, bmdAudioOutputSwitchAnalog = 0x616e6c67 } BMDAudioOutputAnalogAESSwitch; typedef /* [v1_enum] */ enum _BMDVideoOutputConversionMode { bmdNoVideoOutputConversion = 0x6e6f6e65, bmdVideoOutputLetterboxDownconversion = 0x6c746278, bmdVideoOutputAnamorphicDownconversion = 0x616d7068, bmdVideoOutputHD720toHD1080Conversion = 0x37323063, bmdVideoOutputHardwareLetterboxDownconversion = 0x48576c62, bmdVideoOutputHardwareAnamorphicDownconversion = 0x4857616d, bmdVideoOutputHardwareCenterCutDownconversion = 0x48576363, bmdVideoOutputHardware720p1080pCrossconversion = 0x78636170, bmdVideoOutputHardwareAnamorphic720pUpconversion = 0x75613770, bmdVideoOutputHardwareAnamorphic1080iUpconversion = 0x75613169, bmdVideoOutputHardwareAnamorphic149To720pUpconversion = 0x75343770, bmdVideoOutputHardwareAnamorphic149To1080iUpconversion = 0x75343169, bmdVideoOutputHardwarePillarbox720pUpconversion = 0x75703770, bmdVideoOutputHardwarePillarbox1080iUpconversion = 0x75703169 } BMDVideoOutputConversionMode; typedef /* [v1_enum] */ enum _BMDVideoInputConversionMode { bmdNoVideoInputConversion = 0x6e6f6e65, bmdVideoInputLetterboxDownconversionFromHD1080 = 0x31306c62, bmdVideoInputAnamorphicDownconversionFromHD1080 = 0x3130616d, bmdVideoInputLetterboxDownconversionFromHD720 = 0x37326c62, bmdVideoInputAnamorphicDownconversionFromHD720 = 0x3732616d, bmdVideoInputLetterboxUpconversion = 0x6c627570, bmdVideoInputAnamorphicUpconversion = 0x616d7570 } BMDVideoInputConversionMode; typedef /* [v1_enum] */ enum _BMDVideo3DPackingFormat { bmdVideo3DPackingSidebySideHalf = 0x73627368, bmdVideo3DPackingLinebyLine = 0x6c62796c, bmdVideo3DPackingTopAndBottom = 0x7461626f, bmdVideo3DPackingLeftOnly = 0x6c656674, bmdVideo3DPackingRightOnly = 0x72696768 } BMDVideo3DPackingFormat; typedef /* [v1_enum] */ enum _BMDDeckLinkConfigurationID { bmdDeckLinkConfigUse1080pNotPsF = 0x6670726f, bmdDeckLinkConfigHDMI3DPackingFormat = 0x33647066, bmdDeckLinkConfigAnalogAudioConsumerLevels = 0x6161636c, bmdDeckLinkConfigFieldFlickerRemoval = 0x66646672, bmdDeckLinkConfigHD1080p24ToHD1080i5994Conversion = 0x746f3539, bmdDeckLinkConfig444SDIVideoOutput = 0x3434346f, bmdDeckLinkConfig3GBpsVideoOutput = 0x33676273, bmdDeckLinkConfigBlackVideoOutputDuringCapture = 0x62766f63, bmdDeckLinkConfigLowLatencyVideoOutput = 0x6c6c766f, bmdDeckLinkConfigVideoOutputConnection = 0x766f636e, bmdDeckLinkConfigVideoOutputConversionMode = 0x766f636d, bmdDeckLinkConfigAnalogVideoOutputFlags = 0x61766f66, bmdDeckLinkConfigReferenceInputTimingOffset = 0x676c6f74, bmdDeckLinkConfigVideoInputConnection = 0x7669636e, bmdDeckLinkConfigAnalogVideoInputFlags = 0x61766966, bmdDeckLinkConfigVideoInputConversionMode = 0x7669636d, bmdDeckLinkConfig32PulldownSequenceInitialTimecodeFrame = 0x70646966, bmdDeckLinkConfigVANCSourceLine1Mapping = 0x76736c31, bmdDeckLinkConfigVANCSourceLine2Mapping = 0x76736c32, bmdDeckLinkConfigVANCSourceLine3Mapping = 0x76736c33, bmdDeckLinkConfigAudioInputConnection = 0x6169636e, bmdDeckLinkConfigAnalogAudioInputScaleChannel1 = 0x61697331, bmdDeckLinkConfigAnalogAudioInputScaleChannel2 = 0x61697332, bmdDeckLinkConfigAnalogAudioInputScaleChannel3 = 0x61697333, bmdDeckLinkConfigAnalogAudioInputScaleChannel4 = 0x61697334, bmdDeckLinkConfigDigitalAudioInputScale = 0x64616973, bmdDeckLinkConfigAudioOutputAESAnalogSwitch = 0x616f6161, bmdDeckLinkConfigAnalogAudioOutputScaleChannel1 = 0x616f7331, bmdDeckLinkConfigAnalogAudioOutputScaleChannel2 = 0x616f7332, bmdDeckLinkConfigAnalogAudioOutputScaleChannel3 = 0x616f7333, bmdDeckLinkConfigAnalogAudioOutputScaleChannel4 = 0x616f7334, bmdDeckLinkConfigDigitalAudioOutputScale = 0x64616f73 } BMDDeckLinkConfigurationID; typedef /* [v1_enum] */ enum _BMDDeckLinkAttributeID { BMDDeckLinkSupportsInternalKeying = 0x6b657969, BMDDeckLinkSupportsExternalKeying = 0x6b657965, BMDDeckLinkSupportsHDKeying = 0x6b657968, BMDDeckLinkSupportsInputFormatDetection = 0x696e6664, BMDDeckLinkHasReferenceInput = 0x6872696e, BMDDeckLinkHasSerialPort = 0x68737074, BMDDeckLinkMaximumAudioChannels = 0x6d616368, BMDDeckLinkNumberOfSubDevices = 0x6e736264, BMDDeckLinkSubDeviceIndex = 0x73756269, BMDDeckLinkVideoOutputConnections = 0x766f636e, BMDDeckLinkVideoInputConnections = 0x7669636e, BMDDeckLinkSerialPortDeviceName = 0x736c706e } BMDDeckLinkAttributeID; typedef /* [v1_enum] */ enum _BMDDeckLinkAPIInformationID { BMDDeckLinkAPIVersion = 0x76657273 } BMDDeckLinkAPIInformationID; typedef /* [v1_enum] */ enum _BMDDeckControlMode { bmdDeckControlNotOpened = 0x6e746f70, bmdDeckControlVTRControlMode = 0x76747263, bmdDeckControlExportMode = 0x6578706d, bmdDeckControlCaptureMode = 0x6361706d } BMDDeckControlMode; typedef /* [v1_enum] */ enum _BMDDeckControlEvent { bmdDeckControlAbortedEvent = 0x61627465, bmdDeckControlPrepareForExportEvent = 0x70666565, bmdDeckControlExportCompleteEvent = 0x65786365, bmdDeckControlPrepareForCaptureEvent = 0x70666365, bmdDeckControlCaptureCompleteEvent = 0x63636576 } BMDDeckControlEvent; typedef /* [v1_enum] */ enum _BMDDeckControlVTRControlState { bmdDeckControlNotInVTRControlMode = 0x6e76636d, bmdDeckControlVTRControlPlaying = 0x76747270, bmdDeckControlVTRControlRecording = 0x76747272, bmdDeckControlVTRControlStill = 0x76747261, bmdDeckControlVTRControlSeeking = 0x76747273, bmdDeckControlVTRControlStopped = 0x7674726f } BMDDeckControlVTRControlState; /* [v1_enum] */ enum _BMDDeckControlStatusFlags { bmdDeckControlStatusDeckConnected = ( 1 << 0 ) , bmdDeckControlStatusRemoteMode = ( 1 << 1 ) , bmdDeckControlStatusRecordInhibited = ( 1 << 2 ) , bmdDeckControlStatusCassetteOut = ( 1 << 3 ) } ; /* [v1_enum] */ enum _BMDDeckControlExportModeOpsFlags { bmdDeckControlExportModeInsertVideo = ( 1 << 0 ) , bmdDeckControlExportModeInsertAudio1 = ( 1 << 1 ) , bmdDeckControlExportModeInsertAudio2 = ( 1 << 2 ) , bmdDeckControlExportModeInsertAudio3 = ( 1 << 3 ) , bmdDeckControlExportModeInsertAudio4 = ( 1 << 4 ) , bmdDeckControlExportModeInsertAudio5 = ( 1 << 5 ) , bmdDeckControlExportModeInsertAudio6 = ( 1 << 6 ) , bmdDeckControlExportModeInsertAudio7 = ( 1 << 7 ) , bmdDeckControlExportModeInsertAudio8 = ( 1 << 8 ) , bmdDeckControlExportModeInsertAudio9 = ( 1 << 9 ) , bmdDeckControlExportModeInsertAudio10 = ( 1 << 10 ) , bmdDeckControlExportModeInsertAudio11 = ( 1 << 11 ) , bmdDeckControlExportModeInsertAudio12 = ( 1 << 12 ) , bmdDeckControlExportModeInsertTimeCode = ( 1 << 13 ) , bmdDeckControlExportModeInsertAssemble = ( 1 << 14 ) , bmdDeckControlExportModeInsertPreview = ( 1 << 15 ) , bmdDeckControlUseManualExport = ( 1 << 16 ) } ; typedef /* [v1_enum] */ enum _BMDDeckControlError { bmdDeckControlNoError = 0x6e6f6572, bmdDeckControlModeError = 0x6d6f6572, bmdDeckControlMissedInPointError = 0x6d696572, bmdDeckControlDeckTimeoutError = 0x64746572, bmdDeckControlCommandFailedError = 0x63666572, bmdDeckControlDeviceAlreadyOpenedError = 0x64616c6f, bmdDeckControlFailedToOpenDeviceError = 0x66646572, bmdDeckControlInLocalModeError = 0x6c6d6572, bmdDeckControlEndOfTapeError = 0x65746572, bmdDeckControlUserAbortError = 0x75616572, bmdDeckControlNoTapeInDeckError = 0x6e746572, bmdDeckControlNoVideoFromCardError = 0x6e766663, bmdDeckControlNoCommunicationError = 0x6e636f6d, bmdDeckControlUnknownError = 0x756e6572 } BMDDeckControlError; typedef /* [v1_enum] */ enum _BMD3DPreviewFormat { bmd3DPreviewFormatDefault = 0x64656661, bmd3DPreviewFormatLeftOnly = 0x6c656674, bmd3DPreviewFormatRightOnly = 0x72696768, bmd3DPreviewFormatSideBySide = 0x73696465, bmd3DPreviewFormatTopBottom = 0x746f7062 } BMD3DPreviewFormat; typedef /* [v1_enum] */ enum _BMDVideoConnection_v7_6 { bmdVideoConnectionSDI_v7_6 = 0x73646920, bmdVideoConnectionHDMI_v7_6 = 0x68646d69, bmdVideoConnectionOpticalSDI_v7_6 = 0x6f707469, bmdVideoConnectionComponent_v7_6 = 0x63706e74, bmdVideoConnectionComposite_v7_6 = 0x636d7374, bmdVideoConnectionSVideo_v7_6 = 0x73766964 } BMDVideoConnection_v7_6; EXTERN_C const IID LIBID_DeckLinkAPI; #ifndef __IDeckLinkVideoOutputCallback_INTERFACE_DEFINED__ #define __IDeckLinkVideoOutputCallback_INTERFACE_DEFINED__ /* interface IDeckLinkVideoOutputCallback */ /* [helpstring][uuid][object] */ EXTERN_C const IID IID_IDeckLinkVideoOutputCallback; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("20AA5225-1958-47CB-820B-80A8D521A6EE") IDeckLinkVideoOutputCallback : public IUnknown { public: virtual HRESULT STDMETHODCALLTYPE ScheduledFrameCompleted( /* [in] */ IDeckLinkVideoFrame *completedFrame, /* [in] */ BMDOutputFrameCompletionResult result) = 0; virtual HRESULT STDMETHODCALLTYPE ScheduledPlaybackHasStopped( void) = 0; }; #else /* C style interface */ typedef struct IDeckLinkVideoOutputCallbackVtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IDeckLinkVideoOutputCallback * This, /* [in] */ REFIID riid, /* [annotation][iid_is][out] */ __RPC__deref_out void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IDeckLinkVideoOutputCallback * This); ULONG ( STDMETHODCALLTYPE *Release )( IDeckLinkVideoOutputCallback * This); HRESULT ( STDMETHODCALLTYPE *ScheduledFrameCompleted )( IDeckLinkVideoOutputCallback * This, /* [in] */ IDeckLinkVideoFrame *completedFrame, /* [in] */ BMDOutputFrameCompletionResult result); HRESULT ( STDMETHODCALLTYPE *ScheduledPlaybackHasStopped )( IDeckLinkVideoOutputCallback * This); END_INTERFACE } IDeckLinkVideoOutputCallbackVtbl; interface IDeckLinkVideoOutputCallback { CONST_VTBL struct IDeckLinkVideoOutputCallbackVtbl *lpVtbl; }; #ifdef COBJMACROS #define IDeckLinkVideoOutputCallback_QueryInterface(This,riid,ppvObject) \ ( (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) ) #define IDeckLinkVideoOutputCallback_AddRef(This) \ ( (This)->lpVtbl -> AddRef(This) ) #define IDeckLinkVideoOutputCallback_Release(This) \ ( (This)->lpVtbl -> Release(This) ) #define IDeckLinkVideoOutputCallback_ScheduledFrameCompleted(This,completedFrame,result) \ ( (This)->lpVtbl -> ScheduledFrameCompleted(This,completedFrame,result) ) #define IDeckLinkVideoOutputCallback_ScheduledPlaybackHasStopped(This) \ ( (This)->lpVtbl -> ScheduledPlaybackHasStopped(This) ) #endif /* COBJMACROS */ #endif /* C style interface */ #endif /* __IDeckLinkVideoOutputCallback_INTERFACE_DEFINED__ */ #ifndef __IDeckLinkInputCallback_INTERFACE_DEFINED__ #define __IDeckLinkInputCallback_INTERFACE_DEFINED__ /* interface IDeckLinkInputCallback */ /* [helpstring][uuid][object] */ EXTERN_C const IID IID_IDeckLinkInputCallback; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("DD04E5EC-7415-42AB-AE4A-E80C4DFC044A") IDeckLinkInputCallback : public IUnknown { public: virtual HRESULT STDMETHODCALLTYPE VideoInputFormatChanged( /* [in] */ BMDVideoInputFormatChangedEvents notificationEvents, /* [in] */ IDeckLinkDisplayMode *newDisplayMode, /* [in] */ BMDDetectedVideoInputFormatFlags detectedSignalFlags) = 0; virtual HRESULT STDMETHODCALLTYPE VideoInputFrameArrived( /* [in] */ IDeckLinkVideoInputFrame *videoFrame, /* [in] */ IDeckLinkAudioInputPacket *audioPacket) = 0; }; #else /* C style interface */ typedef struct IDeckLinkInputCallbackVtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IDeckLinkInputCallback * This, /* [in] */ REFIID riid, /* [annotation][iid_is][out] */ __RPC__deref_out void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IDeckLinkInputCallback * This); ULONG ( STDMETHODCALLTYPE *Release )( IDeckLinkInputCallback * This); HRESULT ( STDMETHODCALLTYPE *VideoInputFormatChanged )( IDeckLinkInputCallback * This, /* [in] */ BMDVideoInputFormatChangedEvents notificationEvents, /* [in] */ IDeckLinkDisplayMode *newDisplayMode, /* [in] */ BMDDetectedVideoInputFormatFlags detectedSignalFlags); HRESULT ( STDMETHODCALLTYPE *VideoInputFrameArrived )( IDeckLinkInputCallback * This, /* [in] */ IDeckLinkVideoInputFrame *videoFrame, /* [in] */ IDeckLinkAudioInputPacket *audioPacket); END_INTERFACE } IDeckLinkInputCallbackVtbl; interface IDeckLinkInputCallback { CONST_VTBL struct IDeckLinkInputCallbackVtbl *lpVtbl; }; #ifdef COBJMACROS #define IDeckLinkInputCallback_QueryInterface(This,riid,ppvObject) \ ( (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) ) #define IDeckLinkInputCallback_AddRef(This) \ ( (This)->lpVtbl -> AddRef(This) ) #define IDeckLinkInputCallback_Release(This) \ ( (This)->lpVtbl -> Release(This) ) #define IDeckLinkInputCallback_VideoInputFormatChanged(This,notificationEvents,newDisplayMode,detectedSignalFlags) \ ( (This)->lpVtbl -> VideoInputFormatChanged(This,notificationEvents,newDisplayMode,detectedSignalFlags) ) #define IDeckLinkInputCallback_VideoInputFrameArrived(This,videoFrame,audioPacket) \ ( (This)->lpVtbl -> VideoInputFrameArrived(This,videoFrame,audioPacket) ) #endif /* COBJMACROS */ #endif /* C style interface */ #endif /* __IDeckLinkInputCallback_INTERFACE_DEFINED__ */ #ifndef __IDeckLinkMemoryAllocator_INTERFACE_DEFINED__ #define __IDeckLinkMemoryAllocator_INTERFACE_DEFINED__ /* interface IDeckLinkMemoryAllocator */ /* [helpstring][local][uuid][object] */ EXTERN_C const IID IID_IDeckLinkMemoryAllocator; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("B36EB6E7-9D29-4AA8-92EF-843B87A289E8") IDeckLinkMemoryAllocator : public IUnknown { public: virtual HRESULT STDMETHODCALLTYPE AllocateBuffer( /* [in] */ unsigned long bufferSize, /* [out] */ void **allocatedBuffer) = 0; virtual HRESULT STDMETHODCALLTYPE ReleaseBuffer( /* [in] */ void *buffer) = 0; virtual HRESULT STDMETHODCALLTYPE Commit( void) = 0; virtual HRESULT STDMETHODCALLTYPE Decommit( void) = 0; }; #else /* C style interface */ typedef struct IDeckLinkMemoryAllocatorVtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IDeckLinkMemoryAllocator * This, /* [in] */ REFIID riid, /* [annotation][iid_is][out] */ __RPC__deref_out void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IDeckLinkMemoryAllocator * This); ULONG ( STDMETHODCALLTYPE *Release )( IDeckLinkMemoryAllocator * This); HRESULT ( STDMETHODCALLTYPE *AllocateBuffer )( IDeckLinkMemoryAllocator * This, /* [in] */ unsigned long bufferSize, /* [out] */ void **allocatedBuffer); HRESULT ( STDMETHODCALLTYPE *ReleaseBuffer )( IDeckLinkMemoryAllocator * This, /* [in] */ void *buffer); HRESULT ( STDMETHODCALLTYPE *Commit )( IDeckLinkMemoryAllocator * This); HRESULT ( STDMETHODCALLTYPE *Decommit )( IDeckLinkMemoryAllocator * This); END_INTERFACE } IDeckLinkMemoryAllocatorVtbl; interface IDeckLinkMemoryAllocator { CONST_VTBL struct IDeckLinkMemoryAllocatorVtbl *lpVtbl; }; #ifdef COBJMACROS #define IDeckLinkMemoryAllocator_QueryInterface(This,riid,ppvObject) \ ( (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) ) #define IDeckLinkMemoryAllocator_AddRef(This) \ ( (This)->lpVtbl -> AddRef(This) ) #define IDeckLinkMemoryAllocator_Release(This) \ ( (This)->lpVtbl -> Release(This) ) #define IDeckLinkMemoryAllocator_AllocateBuffer(This,bufferSize,allocatedBuffer) \ ( (This)->lpVtbl -> AllocateBuffer(This,bufferSize,allocatedBuffer) ) #define IDeckLinkMemoryAllocator_ReleaseBuffer(This,buffer) \ ( (This)->lpVtbl -> ReleaseBuffer(This,buffer) ) #define IDeckLinkMemoryAllocator_Commit(This) \ ( (This)->lpVtbl -> Commit(This) ) #define IDeckLinkMemoryAllocator_Decommit(This) \ ( (This)->lpVtbl -> Decommit(This) ) #endif /* COBJMACROS */ #endif /* C style interface */ #endif /* __IDeckLinkMemoryAllocator_INTERFACE_DEFINED__ */ #ifndef __IDeckLinkAudioOutputCallback_INTERFACE_DEFINED__ #define __IDeckLinkAudioOutputCallback_INTERFACE_DEFINED__ /* interface IDeckLinkAudioOutputCallback */ /* [helpstring][local][uuid][object] */ EXTERN_C const IID IID_IDeckLinkAudioOutputCallback; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("403C681B-7F46-4A12-B993-2BB127084EE6") IDeckLinkAudioOutputCallback : public IUnknown { public: virtual HRESULT STDMETHODCALLTYPE RenderAudioSamples( /* [in] */ BOOL preroll) = 0; }; #else /* C style interface */ typedef struct IDeckLinkAudioOutputCallbackVtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IDeckLinkAudioOutputCallback * This, /* [in] */ REFIID riid, /* [annotation][iid_is][out] */ __RPC__deref_out void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IDeckLinkAudioOutputCallback * This); ULONG ( STDMETHODCALLTYPE *Release )( IDeckLinkAudioOutputCallback * This); HRESULT ( STDMETHODCALLTYPE *RenderAudioSamples )( IDeckLinkAudioOutputCallback * This, /* [in] */ BOOL preroll); END_INTERFACE } IDeckLinkAudioOutputCallbackVtbl; interface IDeckLinkAudioOutputCallback { CONST_VTBL struct IDeckLinkAudioOutputCallbackVtbl *lpVtbl; }; #ifdef COBJMACROS #define IDeckLinkAudioOutputCallback_QueryInterface(This,riid,ppvObject) \ ( (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) ) #define IDeckLinkAudioOutputCallback_AddRef(This) \ ( (This)->lpVtbl -> AddRef(This) ) #define IDeckLinkAudioOutputCallback_Release(This) \ ( (This)->lpVtbl -> Release(This) ) #define IDeckLinkAudioOutputCallback_RenderAudioSamples(This,preroll) \ ( (This)->lpVtbl -> RenderAudioSamples(This,preroll) ) #endif /* COBJMACROS */ #endif /* C style interface */ #endif /* __IDeckLinkAudioOutputCallback_INTERFACE_DEFINED__ */ #ifndef __IDeckLinkIterator_INTERFACE_DEFINED__ #define __IDeckLinkIterator_INTERFACE_DEFINED__ /* interface IDeckLinkIterator */ /* [helpstring][uuid][object] */ EXTERN_C const IID IID_IDeckLinkIterator; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("74E936FC-CC28-4A67-81A0-1E94E52D4E69") IDeckLinkIterator : public IUnknown { public: virtual HRESULT STDMETHODCALLTYPE Next( /* [out] */ IDeckLink **deckLinkInstance) = 0; }; #else /* C style interface */ typedef struct IDeckLinkIteratorVtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IDeckLinkIterator * This, /* [in] */ REFIID riid, /* [annotation][iid_is][out] */ __RPC__deref_out void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IDeckLinkIterator * This); ULONG ( STDMETHODCALLTYPE *Release )( IDeckLinkIterator * This); HRESULT ( STDMETHODCALLTYPE *Next )( IDeckLinkIterator * This, /* [out] */ IDeckLink **deckLinkInstance); END_INTERFACE } IDeckLinkIteratorVtbl; interface IDeckLinkIterator { CONST_VTBL struct IDeckLinkIteratorVtbl *lpVtbl; }; #ifdef COBJMACROS #define IDeckLinkIterator_QueryInterface(This,riid,ppvObject) \ ( (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) ) #define IDeckLinkIterator_AddRef(This) \ ( (This)->lpVtbl -> AddRef(This) ) #define IDeckLinkIterator_Release(This) \ ( (This)->lpVtbl -> Release(This) ) #define IDeckLinkIterator_Next(This,deckLinkInstance) \ ( (This)->lpVtbl -> Next(This,deckLinkInstance) ) #endif /* COBJMACROS */ #endif /* C style interface */ #endif /* __IDeckLinkIterator_INTERFACE_DEFINED__ */ #ifndef __IDeckLinkAPIInformation_INTERFACE_DEFINED__ #define __IDeckLinkAPIInformation_INTERFACE_DEFINED__ /* interface IDeckLinkAPIInformation */ /* [helpstring][uuid][object] */ EXTERN_C const IID IID_IDeckLinkAPIInformation; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("7BEA3C68-730D-4322-AF34-8A7152B532A4") IDeckLinkAPIInformation : public IUnknown { public: virtual HRESULT STDMETHODCALLTYPE GetFlag( /* [in] */ BMDDeckLinkAPIInformationID cfgID, /* [out] */ BOOL *value) = 0; virtual HRESULT STDMETHODCALLTYPE GetInt( /* [in] */ BMDDeckLinkAPIInformationID cfgID, /* [out] */ LONGLONG *value) = 0; virtual HRESULT STDMETHODCALLTYPE GetFloat( /* [in] */ BMDDeckLinkAPIInformationID cfgID, /* [out] */ double *value) = 0; virtual HRESULT STDMETHODCALLTYPE GetString( /* [in] */ BMDDeckLinkAPIInformationID cfgID, /* [out] */ BSTR *value) = 0; }; #else /* C style interface */ typedef struct IDeckLinkAPIInformationVtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IDeckLinkAPIInformation * This, /* [in] */ REFIID riid, /* [annotation][iid_is][out] */ __RPC__deref_out void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IDeckLinkAPIInformation * This); ULONG ( STDMETHODCALLTYPE *Release )( IDeckLinkAPIInformation * This); HRESULT ( STDMETHODCALLTYPE *GetFlag )( IDeckLinkAPIInformation * This, /* [in] */ BMDDeckLinkAPIInformationID cfgID, /* [out] */ BOOL *value); HRESULT ( STDMETHODCALLTYPE *GetInt )( IDeckLinkAPIInformation * This, /* [in] */ BMDDeckLinkAPIInformationID cfgID, /* [out] */ LONGLONG *value); HRESULT ( STDMETHODCALLTYPE *GetFloat )( IDeckLinkAPIInformation * This, /* [in] */ BMDDeckLinkAPIInformationID cfgID, /* [out] */ double *value); HRESULT ( STDMETHODCALLTYPE *GetString )( IDeckLinkAPIInformation * This, /* [in] */ BMDDeckLinkAPIInformationID cfgID, /* [out] */ BSTR *value); END_INTERFACE } IDeckLinkAPIInformationVtbl; interface IDeckLinkAPIInformation { CONST_VTBL struct IDeckLinkAPIInformationVtbl *lpVtbl; }; #ifdef COBJMACROS #define IDeckLinkAPIInformation_QueryInterface(This,riid,ppvObject) \ ( (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) ) #define IDeckLinkAPIInformation_AddRef(This) \ ( (This)->lpVtbl -> AddRef(This) ) #define IDeckLinkAPIInformation_Release(This) \ ( (This)->lpVtbl -> Release(This) ) #define IDeckLinkAPIInformation_GetFlag(This,cfgID,value) \ ( (This)->lpVtbl -> GetFlag(This,cfgID,value) ) #define IDeckLinkAPIInformation_GetInt(This,cfgID,value) \ ( (This)->lpVtbl -> GetInt(This,cfgID,value) ) #define IDeckLinkAPIInformation_GetFloat(This,cfgID,value) \ ( (This)->lpVtbl -> GetFloat(This,cfgID,value) ) #define IDeckLinkAPIInformation_GetString(This,cfgID,value) \ ( (This)->lpVtbl -> GetString(This,cfgID,value) ) #endif /* COBJMACROS */ #endif /* C style interface */ #endif /* __IDeckLinkAPIInformation_INTERFACE_DEFINED__ */ #ifndef __IDeckLinkDisplayModeIterator_INTERFACE_DEFINED__ #define __IDeckLinkDisplayModeIterator_INTERFACE_DEFINED__ /* interface IDeckLinkDisplayModeIterator */ /* [helpstring][uuid][object] */ EXTERN_C const IID IID_IDeckLinkDisplayModeIterator; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("9C88499F-F601-4021-B80B-032E4EB41C35") IDeckLinkDisplayModeIterator : public IUnknown { public: virtual HRESULT STDMETHODCALLTYPE Next( /* [out] */ IDeckLinkDisplayMode **deckLinkDisplayMode) = 0; }; #else /* C style interface */ typedef struct IDeckLinkDisplayModeIteratorVtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IDeckLinkDisplayModeIterator * This, /* [in] */ REFIID riid, /* [annotation][iid_is][out] */ __RPC__deref_out void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IDeckLinkDisplayModeIterator * This); ULONG ( STDMETHODCALLTYPE *Release )( IDeckLinkDisplayModeIterator * This); HRESULT ( STDMETHODCALLTYPE *Next )( IDeckLinkDisplayModeIterator * This, /* [out] */ IDeckLinkDisplayMode **deckLinkDisplayMode); END_INTERFACE } IDeckLinkDisplayModeIteratorVtbl; interface IDeckLinkDisplayModeIterator { CONST_VTBL struct IDeckLinkDisplayModeIteratorVtbl *lpVtbl; }; #ifdef COBJMACROS #define IDeckLinkDisplayModeIterator_QueryInterface(This,riid,ppvObject) \ ( (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) ) #define IDeckLinkDisplayModeIterator_AddRef(This) \ ( (This)->lpVtbl -> AddRef(This) ) #define IDeckLinkDisplayModeIterator_Release(This) \ ( (This)->lpVtbl -> Release(This) ) #define IDeckLinkDisplayModeIterator_Next(This,deckLinkDisplayMode) \ ( (This)->lpVtbl -> Next(This,deckLinkDisplayMode) ) #endif /* COBJMACROS */ #endif /* C style interface */ #endif /* __IDeckLinkDisplayModeIterator_INTERFACE_DEFINED__ */ #ifndef __IDeckLinkDisplayMode_INTERFACE_DEFINED__ #define __IDeckLinkDisplayMode_INTERFACE_DEFINED__ /* interface IDeckLinkDisplayMode */ /* [helpstring][uuid][object] */ EXTERN_C const IID IID_IDeckLinkDisplayMode; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("3EB2C1AB-0A3D-4523-A3AD-F40D7FB14E78") IDeckLinkDisplayMode : public IUnknown { public: virtual HRESULT STDMETHODCALLTYPE GetName( /* [out] */ BSTR *name) = 0; virtual BMDDisplayMode STDMETHODCALLTYPE GetDisplayMode( void) = 0; virtual long STDMETHODCALLTYPE GetWidth( void) = 0; virtual long STDMETHODCALLTYPE GetHeight( void) = 0; virtual HRESULT STDMETHODCALLTYPE GetFrameRate( /* [out] */ BMDTimeValue *frameDuration, /* [out] */ BMDTimeScale *timeScale) = 0; virtual BMDFieldDominance STDMETHODCALLTYPE GetFieldDominance( void) = 0; virtual BMDDisplayModeFlags STDMETHODCALLTYPE GetFlags( void) = 0; }; #else /* C style interface */ typedef struct IDeckLinkDisplayModeVtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IDeckLinkDisplayMode * This, /* [in] */ REFIID riid, /* [annotation][iid_is][out] */ __RPC__deref_out void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IDeckLinkDisplayMode * This); ULONG ( STDMETHODCALLTYPE *Release )( IDeckLinkDisplayMode * This); HRESULT ( STDMETHODCALLTYPE *GetName )( IDeckLinkDisplayMode * This, /* [out] */ BSTR *name); BMDDisplayMode ( STDMETHODCALLTYPE *GetDisplayMode )( IDeckLinkDisplayMode * This); long ( STDMETHODCALLTYPE *GetWidth )( IDeckLinkDisplayMode * This); long ( STDMETHODCALLTYPE *GetHeight )( IDeckLinkDisplayMode * This); HRESULT ( STDMETHODCALLTYPE *GetFrameRate )( IDeckLinkDisplayMode * This, /* [out] */ BMDTimeValue *frameDuration, /* [out] */ BMDTimeScale *timeScale); BMDFieldDominance ( STDMETHODCALLTYPE *GetFieldDominance )( IDeckLinkDisplayMode * This); BMDDisplayModeFlags ( STDMETHODCALLTYPE *GetFlags )( IDeckLinkDisplayMode * This); END_INTERFACE } IDeckLinkDisplayModeVtbl; interface IDeckLinkDisplayMode { CONST_VTBL struct IDeckLinkDisplayModeVtbl *lpVtbl; }; #ifdef COBJMACROS #define IDeckLinkDisplayMode_QueryInterface(This,riid,ppvObject) \ ( (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) ) #define IDeckLinkDisplayMode_AddRef(This) \ ( (This)->lpVtbl -> AddRef(This) ) #define IDeckLinkDisplayMode_Release(This) \ ( (This)->lpVtbl -> Release(This) ) #define IDeckLinkDisplayMode_GetName(This,name) \ ( (This)->lpVtbl -> GetName(This,name) ) #define IDeckLinkDisplayMode_GetDisplayMode(This) \ ( (This)->lpVtbl -> GetDisplayMode(This) ) #define IDeckLinkDisplayMode_GetWidth(This) \ ( (This)->lpVtbl -> GetWidth(This) ) #define IDeckLinkDisplayMode_GetHeight(This) \ ( (This)->lpVtbl -> GetHeight(This) ) #define IDeckLinkDisplayMode_GetFrameRate(This,frameDuration,timeScale) \ ( (This)->lpVtbl -> GetFrameRate(This,frameDuration,timeScale) ) #define IDeckLinkDisplayMode_GetFieldDominance(This) \ ( (This)->lpVtbl -> GetFieldDominance(This) ) #define IDeckLinkDisplayMode_GetFlags(This) \ ( (This)->lpVtbl -> GetFlags(This) ) #endif /* COBJMACROS */ #endif /* C style interface */ #endif /* __IDeckLinkDisplayMode_INTERFACE_DEFINED__ */ #ifndef __IDeckLink_INTERFACE_DEFINED__ #define __IDeckLink_INTERFACE_DEFINED__ /* interface IDeckLink */ /* [helpstring][uuid][object] */ EXTERN_C const IID IID_IDeckLink; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("62BFF75D-6569-4E55-8D4D-66AA03829ABC") IDeckLink : public IUnknown { public: virtual HRESULT STDMETHODCALLTYPE GetModelName( /* [out] */ BSTR *modelName) = 0; }; #else /* C style interface */ typedef struct IDeckLinkVtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IDeckLink * This, /* [in] */ REFIID riid, /* [annotation][iid_is][out] */ __RPC__deref_out void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IDeckLink * This); ULONG ( STDMETHODCALLTYPE *Release )( IDeckLink * This); HRESULT ( STDMETHODCALLTYPE *GetModelName )( IDeckLink * This, /* [out] */ BSTR *modelName); END_INTERFACE } IDeckLinkVtbl; interface IDeckLink { CONST_VTBL struct IDeckLinkVtbl *lpVtbl; }; #ifdef COBJMACROS #define IDeckLink_QueryInterface(This,riid,ppvObject) \ ( (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) ) #define IDeckLink_AddRef(This) \ ( (This)->lpVtbl -> AddRef(This) ) #define IDeckLink_Release(This) \ ( (This)->lpVtbl -> Release(This) ) #define IDeckLink_GetModelName(This,modelName) \ ( (This)->lpVtbl -> GetModelName(This,modelName) ) #endif /* COBJMACROS */ #endif /* C style interface */ #endif /* __IDeckLink_INTERFACE_DEFINED__ */ #ifndef __IDeckLinkOutput_INTERFACE_DEFINED__ #define __IDeckLinkOutput_INTERFACE_DEFINED__ /* interface IDeckLinkOutput */ /* [helpstring][local][uuid][object] */ EXTERN_C const IID IID_IDeckLinkOutput; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("A3EF0963-0862-44ED-92A9-EE89ABF431C7") IDeckLinkOutput : public IUnknown { public: virtual HRESULT STDMETHODCALLTYPE DoesSupportVideoMode( /* [in] */ BMDDisplayMode displayMode, /* [in] */ BMDPixelFormat pixelFormat, /* [in] */ BMDVideoOutputFlags flags, /* [out] */ BMDDisplayModeSupport *result, /* [out] */ IDeckLinkDisplayMode **resultDisplayMode) = 0; virtual HRESULT STDMETHODCALLTYPE GetDisplayModeIterator( /* [out] */ IDeckLinkDisplayModeIterator **iterator) = 0; virtual HRESULT STDMETHODCALLTYPE SetScreenPreviewCallback( /* [in] */ IDeckLinkScreenPreviewCallback *previewCallback) = 0; virtual HRESULT STDMETHODCALLTYPE EnableVideoOutput( /* [in] */ BMDDisplayMode displayMode, /* [in] */ BMDVideoOutputFlags flags) = 0; virtual HRESULT STDMETHODCALLTYPE DisableVideoOutput( void) = 0; virtual HRESULT STDMETHODCALLTYPE SetVideoOutputFrameMemoryAllocator( /* [in] */ IDeckLinkMemoryAllocator *theAllocator) = 0; virtual HRESULT STDMETHODCALLTYPE CreateVideoFrame( /* [in] */ long width, /* [in] */ long height, /* [in] */ long rowBytes, /* [in] */ BMDPixelFormat pixelFormat, /* [in] */ BMDFrameFlags flags, /* [out] */ IDeckLinkMutableVideoFrame **outFrame) = 0; virtual HRESULT STDMETHODCALLTYPE CreateAncillaryData( /* [in] */ BMDPixelFormat pixelFormat, /* [out] */ IDeckLinkVideoFrameAncillary **outBuffer) = 0; virtual HRESULT STDMETHODCALLTYPE DisplayVideoFrameSync( /* [in] */ IDeckLinkVideoFrame *theFrame) = 0; virtual HRESULT STDMETHODCALLTYPE ScheduleVideoFrame( /* [in] */ IDeckLinkVideoFrame *theFrame, /* [in] */ BMDTimeValue displayTime, /* [in] */ BMDTimeValue displayDuration, /* [in] */ BMDTimeScale timeScale) = 0; virtual HRESULT STDMETHODCALLTYPE SetScheduledFrameCompletionCallback( /* [in] */ IDeckLinkVideoOutputCallback *theCallback) = 0; virtual HRESULT STDMETHODCALLTYPE GetBufferedVideoFrameCount( /* [out] */ unsigned long *bufferedFrameCount) = 0; virtual HRESULT STDMETHODCALLTYPE EnableAudioOutput( /* [in] */ BMDAudioSampleRate sampleRate, /* [in] */ BMDAudioSampleType sampleType, /* [in] */ unsigned long channelCount, /* [in] */ BMDAudioOutputStreamType streamType) = 0; virtual HRESULT STDMETHODCALLTYPE DisableAudioOutput( void) = 0; virtual HRESULT STDMETHODCALLTYPE WriteAudioSamplesSync( /* [in] */ void *buffer, /* [in] */ unsigned long sampleFrameCount, /* [out] */ unsigned long *sampleFramesWritten) = 0; virtual HRESULT STDMETHODCALLTYPE BeginAudioPreroll( void) = 0; virtual HRESULT STDMETHODCALLTYPE EndAudioPreroll( void) = 0; virtual HRESULT STDMETHODCALLTYPE ScheduleAudioSamples( /* [in] */ void *buffer, /* [in] */ unsigned long sampleFrameCount, /* [in] */ BMDTimeValue streamTime, /* [in] */ BMDTimeScale timeScale, /* [out] */ unsigned long *sampleFramesWritten) = 0; virtual HRESULT STDMETHODCALLTYPE GetBufferedAudioSampleFrameCount( /* [out] */ unsigned long *bufferedSampleFrameCount) = 0; virtual HRESULT STDMETHODCALLTYPE FlushBufferedAudioSamples( void) = 0; virtual HRESULT STDMETHODCALLTYPE SetAudioCallback( /* [in] */ IDeckLinkAudioOutputCallback *theCallback) = 0; virtual HRESULT STDMETHODCALLTYPE StartScheduledPlayback( /* [in] */ BMDTimeValue playbackStartTime, /* [in] */ BMDTimeScale timeScale, /* [in] */ double playbackSpeed) = 0; virtual HRESULT STDMETHODCALLTYPE StopScheduledPlayback( /* [in] */ BMDTimeValue stopPlaybackAtTime, /* [out] */ BMDTimeValue *actualStopTime, /* [in] */ BMDTimeScale timeScale) = 0; virtual HRESULT STDMETHODCALLTYPE IsScheduledPlaybackRunning( /* [out] */ BOOL *active) = 0; virtual HRESULT STDMETHODCALLTYPE GetScheduledStreamTime( /* [in] */ BMDTimeScale desiredTimeScale, /* [out] */ BMDTimeValue *streamTime, /* [out] */ double *playbackSpeed) = 0; virtual HRESULT STDMETHODCALLTYPE GetReferenceStatus( /* [out] */ BMDReferenceStatus *referenceStatus) = 0; virtual HRESULT STDMETHODCALLTYPE GetHardwareReferenceClock( /* [in] */ BMDTimeScale desiredTimeScale, /* [out] */ BMDTimeValue *hardwareTime, /* [out] */ BMDTimeValue *timeInFrame, /* [out] */ BMDTimeValue *ticksPerFrame) = 0; }; #else /* C style interface */ typedef struct IDeckLinkOutputVtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IDeckLinkOutput * This, /* [in] */ REFIID riid, /* [annotation][iid_is][out] */ __RPC__deref_out void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IDeckLinkOutput * This); ULONG ( STDMETHODCALLTYPE *Release )( IDeckLinkOutput * This); HRESULT ( STDMETHODCALLTYPE *DoesSupportVideoMode )( IDeckLinkOutput * This, /* [in] */ BMDDisplayMode displayMode, /* [in] */ BMDPixelFormat pixelFormat, /* [in] */ BMDVideoOutputFlags flags, /* [out] */ BMDDisplayModeSupport *result, /* [out] */ IDeckLinkDisplayMode **resultDisplayMode); HRESULT ( STDMETHODCALLTYPE *GetDisplayModeIterator )( IDeckLinkOutput * This, /* [out] */ IDeckLinkDisplayModeIterator **iterator); HRESULT ( STDMETHODCALLTYPE *SetScreenPreviewCallback )( IDeckLinkOutput * This, /* [in] */ IDeckLinkScreenPreviewCallback *previewCallback); HRESULT ( STDMETHODCALLTYPE *EnableVideoOutput )( IDeckLinkOutput * This, /* [in] */ BMDDisplayMode displayMode, /* [in] */ BMDVideoOutputFlags flags); HRESULT ( STDMETHODCALLTYPE *DisableVideoOutput )( IDeckLinkOutput * This); HRESULT ( STDMETHODCALLTYPE *SetVideoOutputFrameMemoryAllocator )( IDeckLinkOutput * This, /* [in] */ IDeckLinkMemoryAllocator *theAllocator); HRESULT ( STDMETHODCALLTYPE *CreateVideoFrame )( IDeckLinkOutput * This, /* [in] */ long width, /* [in] */ long height, /* [in] */ long rowBytes, /* [in] */ BMDPixelFormat pixelFormat, /* [in] */ BMDFrameFlags flags, /* [out] */ IDeckLinkMutableVideoFrame **outFrame); HRESULT ( STDMETHODCALLTYPE *CreateAncillaryData )( IDeckLinkOutput * This, /* [in] */ BMDPixelFormat pixelFormat, /* [out] */ IDeckLinkVideoFrameAncillary **outBuffer); HRESULT ( STDMETHODCALLTYPE *DisplayVideoFrameSync )( IDeckLinkOutput * This, /* [in] */ IDeckLinkVideoFrame *theFrame); HRESULT ( STDMETHODCALLTYPE *ScheduleVideoFrame )( IDeckLinkOutput * This, /* [in] */ IDeckLinkVideoFrame *theFrame, /* [in] */ BMDTimeValue displayTime, /* [in] */ BMDTimeValue displayDuration, /* [in] */ BMDTimeScale timeScale); HRESULT ( STDMETHODCALLTYPE *SetScheduledFrameCompletionCallback )( IDeckLinkOutput * This, /* [in] */ IDeckLinkVideoOutputCallback *theCallback); HRESULT ( STDMETHODCALLTYPE *GetBufferedVideoFrameCount )( IDeckLinkOutput * This, /* [out] */ unsigned long *bufferedFrameCount); HRESULT ( STDMETHODCALLTYPE *EnableAudioOutput )( IDeckLinkOutput * This, /* [in] */ BMDAudioSampleRate sampleRate, /* [in] */ BMDAudioSampleType sampleType, /* [in] */ unsigned long channelCount, /* [in] */ BMDAudioOutputStreamType streamType); HRESULT ( STDMETHODCALLTYPE *DisableAudioOutput )( IDeckLinkOutput * This); HRESULT ( STDMETHODCALLTYPE *WriteAudioSamplesSync )( IDeckLinkOutput * This, /* [in] */ void *buffer, /* [in] */ unsigned long sampleFrameCount, /* [out] */ unsigned long *sampleFramesWritten); HRESULT ( STDMETHODCALLTYPE *BeginAudioPreroll )( IDeckLinkOutput * This); HRESULT ( STDMETHODCALLTYPE *EndAudioPreroll )( IDeckLinkOutput * This); HRESULT ( STDMETHODCALLTYPE *ScheduleAudioSamples )( IDeckLinkOutput * This, /* [in] */ void *buffer, /* [in] */ unsigned long sampleFrameCount, /* [in] */ BMDTimeValue streamTime, /* [in] */ BMDTimeScale timeScale, /* [out] */ unsigned long *sampleFramesWritten); HRESULT ( STDMETHODCALLTYPE *GetBufferedAudioSampleFrameCount )( IDeckLinkOutput * This, /* [out] */ unsigned long *bufferedSampleFrameCount); HRESULT ( STDMETHODCALLTYPE *FlushBufferedAudioSamples )( IDeckLinkOutput * This); HRESULT ( STDMETHODCALLTYPE *SetAudioCallback )( IDeckLinkOutput * This, /* [in] */ IDeckLinkAudioOutputCallback *theCallback); HRESULT ( STDMETHODCALLTYPE *StartScheduledPlayback )( IDeckLinkOutput * This, /* [in] */ BMDTimeValue playbackStartTime, /* [in] */ BMDTimeScale timeScale, /* [in] */ double playbackSpeed); HRESULT ( STDMETHODCALLTYPE *StopScheduledPlayback )( IDeckLinkOutput * This, /* [in] */ BMDTimeValue stopPlaybackAtTime, /* [out] */ BMDTimeValue *actualStopTime, /* [in] */ BMDTimeScale timeScale); HRESULT ( STDMETHODCALLTYPE *IsScheduledPlaybackRunning )( IDeckLinkOutput * This, /* [out] */ BOOL *active); HRESULT ( STDMETHODCALLTYPE *GetScheduledStreamTime )( IDeckLinkOutput * This, /* [in] */ BMDTimeScale desiredTimeScale, /* [out] */ BMDTimeValue *streamTime, /* [out] */ double *playbackSpeed); HRESULT ( STDMETHODCALLTYPE *GetReferenceStatus )( IDeckLinkOutput * This, /* [out] */ BMDReferenceStatus *referenceStatus); HRESULT ( STDMETHODCALLTYPE *GetHardwareReferenceClock )( IDeckLinkOutput * This, /* [in] */ BMDTimeScale desiredTimeScale, /* [out] */ BMDTimeValue *hardwareTime, /* [out] */ BMDTimeValue *timeInFrame, /* [out] */ BMDTimeValue *ticksPerFrame); END_INTERFACE } IDeckLinkOutputVtbl; interface IDeckLinkOutput { CONST_VTBL struct IDeckLinkOutputVtbl *lpVtbl; }; #ifdef COBJMACROS #define IDeckLinkOutput_QueryInterface(This,riid,ppvObject) \ ( (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) ) #define IDeckLinkOutput_AddRef(This) \ ( (This)->lpVtbl -> AddRef(This) ) #define IDeckLinkOutput_Release(This) \ ( (This)->lpVtbl -> Release(This) ) #define IDeckLinkOutput_DoesSupportVideoMode(This,displayMode,pixelFormat,flags,result,resultDisplayMode) \ ( (This)->lpVtbl -> DoesSupportVideoMode(This,displayMode,pixelFormat,flags,result,resultDisplayMode) ) #define IDeckLinkOutput_GetDisplayModeIterator(This,iterator) \ ( (This)->lpVtbl -> GetDisplayModeIterator(This,iterator) ) #define IDeckLinkOutput_SetScreenPreviewCallback(This,previewCallback) \ ( (This)->lpVtbl -> SetScreenPreviewCallback(This,previewCallback) ) #define IDeckLinkOutput_EnableVideoOutput(This,displayMode,flags) \ ( (This)->lpVtbl -> EnableVideoOutput(This,displayMode,flags) ) #define IDeckLinkOutput_DisableVideoOutput(This) \ ( (This)->lpVtbl -> DisableVideoOutput(This) ) #define IDeckLinkOutput_SetVideoOutputFrameMemoryAllocator(This,theAllocator) \ ( (This)->lpVtbl -> SetVideoOutputFrameMemoryAllocator(This,theAllocator) ) #define IDeckLinkOutput_CreateVideoFrame(This,width,height,rowBytes,pixelFormat,flags,outFrame) \ ( (This)->lpVtbl -> CreateVideoFrame(This,width,height,rowBytes,pixelFormat,flags,outFrame) ) #define IDeckLinkOutput_CreateAncillaryData(This,pixelFormat,outBuffer) \ ( (This)->lpVtbl -> CreateAncillaryData(This,pixelFormat,outBuffer) ) #define IDeckLinkOutput_DisplayVideoFrameSync(This,theFrame) \ ( (This)->lpVtbl -> DisplayVideoFrameSync(This,theFrame) ) #define IDeckLinkOutput_ScheduleVideoFrame(This,theFrame,displayTime,displayDuration,timeScale) \ ( (This)->lpVtbl -> ScheduleVideoFrame(This,theFrame,displayTime,displayDuration,timeScale) ) #define IDeckLinkOutput_SetScheduledFrameCompletionCallback(This,theCallback) \ ( (This)->lpVtbl -> SetScheduledFrameCompletionCallback(This,theCallback) ) #define IDeckLinkOutput_GetBufferedVideoFrameCount(This,bufferedFrameCount) \ ( (This)->lpVtbl -> GetBufferedVideoFrameCount(This,bufferedFrameCount) ) #define IDeckLinkOutput_EnableAudioOutput(This,sampleRate,sampleType,channelCount,streamType) \ ( (This)->lpVtbl -> EnableAudioOutput(This,sampleRate,sampleType,channelCount,streamType) ) #define IDeckLinkOutput_DisableAudioOutput(This) \ ( (This)->lpVtbl -> DisableAudioOutput(This) ) #define IDeckLinkOutput_WriteAudioSamplesSync(This,buffer,sampleFrameCount,sampleFramesWritten) \ ( (This)->lpVtbl -> WriteAudioSamplesSync(This,buffer,sampleFrameCount,sampleFramesWritten) ) #define IDeckLinkOutput_BeginAudioPreroll(This) \ ( (This)->lpVtbl -> BeginAudioPreroll(This) ) #define IDeckLinkOutput_EndAudioPreroll(This) \ ( (This)->lpVtbl -> EndAudioPreroll(This) ) #define IDeckLinkOutput_ScheduleAudioSamples(This,buffer,sampleFrameCount,streamTime,timeScale,sampleFramesWritten) \ ( (This)->lpVtbl -> ScheduleAudioSamples(This,buffer,sampleFrameCount,streamTime,timeScale,sampleFramesWritten) ) #define IDeckLinkOutput_GetBufferedAudioSampleFrameCount(This,bufferedSampleFrameCount) \ ( (This)->lpVtbl -> GetBufferedAudioSampleFrameCount(This,bufferedSampleFrameCount) ) #define IDeckLinkOutput_FlushBufferedAudioSamples(This) \ ( (This)->lpVtbl -> FlushBufferedAudioSamples(This) ) #define IDeckLinkOutput_SetAudioCallback(This,theCallback) \ ( (This)->lpVtbl -> SetAudioCallback(This,theCallback) ) #define IDeckLinkOutput_StartScheduledPlayback(This,playbackStartTime,timeScale,playbackSpeed) \ ( (This)->lpVtbl -> StartScheduledPlayback(This,playbackStartTime,timeScale,playbackSpeed) ) #define IDeckLinkOutput_StopScheduledPlayback(This,stopPlaybackAtTime,actualStopTime,timeScale) \ ( (This)->lpVtbl -> StopScheduledPlayback(This,stopPlaybackAtTime,actualStopTime,timeScale) ) #define IDeckLinkOutput_IsScheduledPlaybackRunning(This,active) \ ( (This)->lpVtbl -> IsScheduledPlaybackRunning(This,active) ) #define IDeckLinkOutput_GetScheduledStreamTime(This,desiredTimeScale,streamTime,playbackSpeed) \ ( (This)->lpVtbl -> GetScheduledStreamTime(This,desiredTimeScale,streamTime,playbackSpeed) ) #define IDeckLinkOutput_GetReferenceStatus(This,referenceStatus) \ ( (This)->lpVtbl -> GetReferenceStatus(This,referenceStatus) ) #define IDeckLinkOutput_GetHardwareReferenceClock(This,desiredTimeScale,hardwareTime,timeInFrame,ticksPerFrame) \ ( (This)->lpVtbl -> GetHardwareReferenceClock(This,desiredTimeScale,hardwareTime,timeInFrame,ticksPerFrame) ) #endif /* COBJMACROS */ #endif /* C style interface */ #endif /* __IDeckLinkOutput_INTERFACE_DEFINED__ */ #ifndef __IDeckLinkInput_INTERFACE_DEFINED__ #define __IDeckLinkInput_INTERFACE_DEFINED__ /* interface IDeckLinkInput */ /* [helpstring][uuid][object] */ EXTERN_C const IID IID_IDeckLinkInput; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("6D40EF78-28B9-4E21-990D-95BB7750A04F") IDeckLinkInput : public IUnknown { public: virtual HRESULT STDMETHODCALLTYPE DoesSupportVideoMode( /* [in] */ BMDDisplayMode displayMode, /* [in] */ BMDPixelFormat pixelFormat, /* [in] */ BMDVideoInputFlags flags, /* [out] */ BMDDisplayModeSupport *result, /* [out] */ IDeckLinkDisplayMode **resultDisplayMode) = 0; virtual HRESULT STDMETHODCALLTYPE GetDisplayModeIterator( /* [out] */ IDeckLinkDisplayModeIterator **iterator) = 0; virtual HRESULT STDMETHODCALLTYPE SetScreenPreviewCallback( /* [in] */ IDeckLinkScreenPreviewCallback *previewCallback) = 0; virtual HRESULT STDMETHODCALLTYPE EnableVideoInput( /* [in] */ BMDDisplayMode displayMode, /* [in] */ BMDPixelFormat pixelFormat, /* [in] */ BMDVideoInputFlags flags) = 0; virtual HRESULT STDMETHODCALLTYPE DisableVideoInput( void) = 0; virtual HRESULT STDMETHODCALLTYPE GetAvailableVideoFrameCount( /* [out] */ unsigned long *availableFrameCount) = 0; virtual HRESULT STDMETHODCALLTYPE EnableAudioInput( /* [in] */ BMDAudioSampleRate sampleRate, /* [in] */ BMDAudioSampleType sampleType, /* [in] */ unsigned long channelCount) = 0; virtual HRESULT STDMETHODCALLTYPE DisableAudioInput( void) = 0; virtual HRESULT STDMETHODCALLTYPE GetAvailableAudioSampleFrameCount( /* [out] */ unsigned long *availableSampleFrameCount) = 0; virtual HRESULT STDMETHODCALLTYPE StartStreams( void) = 0; virtual HRESULT STDMETHODCALLTYPE StopStreams( void) = 0; virtual HRESULT STDMETHODCALLTYPE PauseStreams( void) = 0; virtual HRESULT STDMETHODCALLTYPE FlushStreams( void) = 0; virtual HRESULT STDMETHODCALLTYPE SetCallback( /* [in] */ IDeckLinkInputCallback *theCallback) = 0; virtual HRESULT STDMETHODCALLTYPE GetHardwareReferenceClock( /* [in] */ BMDTimeScale desiredTimeScale, /* [out] */ BMDTimeValue *hardwareTime, /* [out] */ BMDTimeValue *timeInFrame, /* [out] */ BMDTimeValue *ticksPerFrame) = 0; }; #else /* C style interface */ typedef struct IDeckLinkInputVtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IDeckLinkInput * This, /* [in] */ REFIID riid, /* [annotation][iid_is][out] */ __RPC__deref_out void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IDeckLinkInput * This); ULONG ( STDMETHODCALLTYPE *Release )( IDeckLinkInput * This); HRESULT ( STDMETHODCALLTYPE *DoesSupportVideoMode )( IDeckLinkInput * This, /* [in] */ BMDDisplayMode displayMode, /* [in] */ BMDPixelFormat pixelFormat, /* [in] */ BMDVideoInputFlags flags, /* [out] */ BMDDisplayModeSupport *result, /* [out] */ IDeckLinkDisplayMode **resultDisplayMode); HRESULT ( STDMETHODCALLTYPE *GetDisplayModeIterator )( IDeckLinkInput * This, /* [out] */ IDeckLinkDisplayModeIterator **iterator); HRESULT ( STDMETHODCALLTYPE *SetScreenPreviewCallback )( IDeckLinkInput * This, /* [in] */ IDeckLinkScreenPreviewCallback *previewCallback); HRESULT ( STDMETHODCALLTYPE *EnableVideoInput )( IDeckLinkInput * This, /* [in] */ BMDDisplayMode displayMode, /* [in] */ BMDPixelFormat pixelFormat, /* [in] */ BMDVideoInputFlags flags); HRESULT ( STDMETHODCALLTYPE *DisableVideoInput )( IDeckLinkInput * This); HRESULT ( STDMETHODCALLTYPE *GetAvailableVideoFrameCount )( IDeckLinkInput * This, /* [out] */ unsigned long *availableFrameCount); HRESULT ( STDMETHODCALLTYPE *EnableAudioInput )( IDeckLinkInput * This, /* [in] */ BMDAudioSampleRate sampleRate, /* [in] */ BMDAudioSampleType sampleType, /* [in] */ unsigned long channelCount); HRESULT ( STDMETHODCALLTYPE *DisableAudioInput )( IDeckLinkInput * This); HRESULT ( STDMETHODCALLTYPE *GetAvailableAudioSampleFrameCount )( IDeckLinkInput * This, /* [out] */ unsigned long *availableSampleFrameCount); HRESULT ( STDMETHODCALLTYPE *StartStreams )( IDeckLinkInput * This); HRESULT ( STDMETHODCALLTYPE *StopStreams )( IDeckLinkInput * This); HRESULT ( STDMETHODCALLTYPE *PauseStreams )( IDeckLinkInput * This); HRESULT ( STDMETHODCALLTYPE *FlushStreams )( IDeckLinkInput * This); HRESULT ( STDMETHODCALLTYPE *SetCallback )( IDeckLinkInput * This, /* [in] */ IDeckLinkInputCallback *theCallback); HRESULT ( STDMETHODCALLTYPE *GetHardwareReferenceClock )( IDeckLinkInput * This, /* [in] */ BMDTimeScale desiredTimeScale, /* [out] */ BMDTimeValue *hardwareTime, /* [out] */ BMDTimeValue *timeInFrame, /* [out] */ BMDTimeValue *ticksPerFrame); END_INTERFACE } IDeckLinkInputVtbl; interface IDeckLinkInput { CONST_VTBL struct IDeckLinkInputVtbl *lpVtbl; }; #ifdef COBJMACROS #define IDeckLinkInput_QueryInterface(This,riid,ppvObject) \ ( (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) ) #define IDeckLinkInput_AddRef(This) \ ( (This)->lpVtbl -> AddRef(This) ) #define IDeckLinkInput_Release(This) \ ( (This)->lpVtbl -> Release(This) ) #define IDeckLinkInput_DoesSupportVideoMode(This,displayMode,pixelFormat,flags,result,resultDisplayMode) \ ( (This)->lpVtbl -> DoesSupportVideoMode(This,displayMode,pixelFormat,flags,result,resultDisplayMode) ) #define IDeckLinkInput_GetDisplayModeIterator(This,iterator) \ ( (This)->lpVtbl -> GetDisplayModeIterator(This,iterator) ) #define IDeckLinkInput_SetScreenPreviewCallback(This,previewCallback) \ ( (This)->lpVtbl -> SetScreenPreviewCallback(This,previewCallback) ) #define IDeckLinkInput_EnableVideoInput(This,displayMode,pixelFormat,flags) \ ( (This)->lpVtbl -> EnableVideoInput(This,displayMode,pixelFormat,flags) ) #define IDeckLinkInput_DisableVideoInput(This) \ ( (This)->lpVtbl -> DisableVideoInput(This) ) #define IDeckLinkInput_GetAvailableVideoFrameCount(This,availableFrameCount) \ ( (This)->lpVtbl -> GetAvailableVideoFrameCount(This,availableFrameCount) ) #define IDeckLinkInput_EnableAudioInput(This,sampleRate,sampleType,channelCount) \ ( (This)->lpVtbl -> EnableAudioInput(This,sampleRate,sampleType,channelCount) ) #define IDeckLinkInput_DisableAudioInput(This) \ ( (This)->lpVtbl -> DisableAudioInput(This) ) #define IDeckLinkInput_GetAvailableAudioSampleFrameCount(This,availableSampleFrameCount) \ ( (This)->lpVtbl -> GetAvailableAudioSampleFrameCount(This,availableSampleFrameCount) ) #define IDeckLinkInput_StartStreams(This) \ ( (This)->lpVtbl -> StartStreams(This) ) #define IDeckLinkInput_StopStreams(This) \ ( (This)->lpVtbl -> StopStreams(This) ) #define IDeckLinkInput_PauseStreams(This) \ ( (This)->lpVtbl -> PauseStreams(This) ) #define IDeckLinkInput_FlushStreams(This) \ ( (This)->lpVtbl -> FlushStreams(This) ) #define IDeckLinkInput_SetCallback(This,theCallback) \ ( (This)->lpVtbl -> SetCallback(This,theCallback) ) #define IDeckLinkInput_GetHardwareReferenceClock(This,desiredTimeScale,hardwareTime,timeInFrame,ticksPerFrame) \ ( (This)->lpVtbl -> GetHardwareReferenceClock(This,desiredTimeScale,hardwareTime,timeInFrame,ticksPerFrame) ) #endif /* COBJMACROS */ #endif /* C style interface */ #endif /* __IDeckLinkInput_INTERFACE_DEFINED__ */ #ifndef __IDeckLinkTimecode_INTERFACE_DEFINED__ #define __IDeckLinkTimecode_INTERFACE_DEFINED__ /* interface IDeckLinkTimecode */ /* [helpstring][uuid][object] */ EXTERN_C const IID IID_IDeckLinkTimecode; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("BC6CFBD3-8317-4325-AC1C-1216391E9340") IDeckLinkTimecode : public IUnknown { public: virtual BMDTimecodeBCD STDMETHODCALLTYPE GetBCD( void) = 0; virtual HRESULT STDMETHODCALLTYPE GetComponents( /* [out] */ unsigned char *hours, /* [out] */ unsigned char *minutes, /* [out] */ unsigned char *seconds, /* [out] */ unsigned char *frames) = 0; virtual HRESULT STDMETHODCALLTYPE GetString( /* [out] */ BSTR *timecode) = 0; virtual BMDTimecodeFlags STDMETHODCALLTYPE GetFlags( void) = 0; virtual HRESULT STDMETHODCALLTYPE GetTimecodeUserBits( /* [out] */ BMDTimecodeUserBits *userBits) = 0; }; #else /* C style interface */ typedef struct IDeckLinkTimecodeVtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IDeckLinkTimecode * This, /* [in] */ REFIID riid, /* [annotation][iid_is][out] */ __RPC__deref_out void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IDeckLinkTimecode * This); ULONG ( STDMETHODCALLTYPE *Release )( IDeckLinkTimecode * This); BMDTimecodeBCD ( STDMETHODCALLTYPE *GetBCD )( IDeckLinkTimecode * This); HRESULT ( STDMETHODCALLTYPE *GetComponents )( IDeckLinkTimecode * This, /* [out] */ unsigned char *hours, /* [out] */ unsigned char *minutes, /* [out] */ unsigned char *seconds, /* [out] */ unsigned char *frames); HRESULT ( STDMETHODCALLTYPE *GetString )( IDeckLinkTimecode * This, /* [out] */ BSTR *timecode); BMDTimecodeFlags ( STDMETHODCALLTYPE *GetFlags )( IDeckLinkTimecode * This); HRESULT ( STDMETHODCALLTYPE *GetTimecodeUserBits )( IDeckLinkTimecode * This, /* [out] */ BMDTimecodeUserBits *userBits); END_INTERFACE } IDeckLinkTimecodeVtbl; interface IDeckLinkTimecode { CONST_VTBL struct IDeckLinkTimecodeVtbl *lpVtbl; }; #ifdef COBJMACROS #define IDeckLinkTimecode_QueryInterface(This,riid,ppvObject) \ ( (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) ) #define IDeckLinkTimecode_AddRef(This) \ ( (This)->lpVtbl -> AddRef(This) ) #define IDeckLinkTimecode_Release(This) \ ( (This)->lpVtbl -> Release(This) ) #define IDeckLinkTimecode_GetBCD(This) \ ( (This)->lpVtbl -> GetBCD(This) ) #define IDeckLinkTimecode_GetComponents(This,hours,minutes,seconds,frames) \ ( (This)->lpVtbl -> GetComponents(This,hours,minutes,seconds,frames) ) #define IDeckLinkTimecode_GetString(This,timecode) \ ( (This)->lpVtbl -> GetString(This,timecode) ) #define IDeckLinkTimecode_GetFlags(This) \ ( (This)->lpVtbl -> GetFlags(This) ) #define IDeckLinkTimecode_GetTimecodeUserBits(This,userBits) \ ( (This)->lpVtbl -> GetTimecodeUserBits(This,userBits) ) #endif /* COBJMACROS */ #endif /* C style interface */ #endif /* __IDeckLinkTimecode_INTERFACE_DEFINED__ */ #ifndef __IDeckLinkVideoFrame_INTERFACE_DEFINED__ #define __IDeckLinkVideoFrame_INTERFACE_DEFINED__ /* interface IDeckLinkVideoFrame */ /* [helpstring][local][uuid][object] */ EXTERN_C const IID IID_IDeckLinkVideoFrame; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("3F716FE0-F023-4111-BE5D-EF4414C05B17") IDeckLinkVideoFrame : public IUnknown { public: virtual long STDMETHODCALLTYPE GetWidth( void) = 0; virtual long STDMETHODCALLTYPE GetHeight( void) = 0; virtual long STDMETHODCALLTYPE GetRowBytes( void) = 0; virtual BMDPixelFormat STDMETHODCALLTYPE GetPixelFormat( void) = 0; virtual BMDFrameFlags STDMETHODCALLTYPE GetFlags( void) = 0; virtual HRESULT STDMETHODCALLTYPE GetBytes( /* [out] */ void **buffer) = 0; virtual HRESULT STDMETHODCALLTYPE GetTimecode( /* [in] */ BMDTimecodeFormat format, /* [out] */ IDeckLinkTimecode **timecode) = 0; virtual HRESULT STDMETHODCALLTYPE GetAncillaryData( /* [out] */ IDeckLinkVideoFrameAncillary **ancillary) = 0; }; #else /* C style interface */ typedef struct IDeckLinkVideoFrameVtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IDeckLinkVideoFrame * This, /* [in] */ REFIID riid, /* [annotation][iid_is][out] */ __RPC__deref_out void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IDeckLinkVideoFrame * This); ULONG ( STDMETHODCALLTYPE *Release )( IDeckLinkVideoFrame * This); long ( STDMETHODCALLTYPE *GetWidth )( IDeckLinkVideoFrame * This); long ( STDMETHODCALLTYPE *GetHeight )( IDeckLinkVideoFrame * This); long ( STDMETHODCALLTYPE *GetRowBytes )( IDeckLinkVideoFrame * This); BMDPixelFormat ( STDMETHODCALLTYPE *GetPixelFormat )( IDeckLinkVideoFrame * This); BMDFrameFlags ( STDMETHODCALLTYPE *GetFlags )( IDeckLinkVideoFrame * This); HRESULT ( STDMETHODCALLTYPE *GetBytes )( IDeckLinkVideoFrame * This, /* [out] */ void **buffer); HRESULT ( STDMETHODCALLTYPE *GetTimecode )( IDeckLinkVideoFrame * This, /* [in] */ BMDTimecodeFormat format, /* [out] */ IDeckLinkTimecode **timecode); HRESULT ( STDMETHODCALLTYPE *GetAncillaryData )( IDeckLinkVideoFrame * This, /* [out] */ IDeckLinkVideoFrameAncillary **ancillary); END_INTERFACE } IDeckLinkVideoFrameVtbl; interface IDeckLinkVideoFrame { CONST_VTBL struct IDeckLinkVideoFrameVtbl *lpVtbl; }; #ifdef COBJMACROS #define IDeckLinkVideoFrame_QueryInterface(This,riid,ppvObject) \ ( (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) ) #define IDeckLinkVideoFrame_AddRef(This) \ ( (This)->lpVtbl -> AddRef(This) ) #define IDeckLinkVideoFrame_Release(This) \ ( (This)->lpVtbl -> Release(This) ) #define IDeckLinkVideoFrame_GetWidth(This) \ ( (This)->lpVtbl -> GetWidth(This) ) #define IDeckLinkVideoFrame_GetHeight(This) \ ( (This)->lpVtbl -> GetHeight(This) ) #define IDeckLinkVideoFrame_GetRowBytes(This) \ ( (This)->lpVtbl -> GetRowBytes(This) ) #define IDeckLinkVideoFrame_GetPixelFormat(This) \ ( (This)->lpVtbl -> GetPixelFormat(This) ) #define IDeckLinkVideoFrame_GetFlags(This) \ ( (This)->lpVtbl -> GetFlags(This) ) #define IDeckLinkVideoFrame_GetBytes(This,buffer) \ ( (This)->lpVtbl -> GetBytes(This,buffer) ) #define IDeckLinkVideoFrame_GetTimecode(This,format,timecode) \ ( (This)->lpVtbl -> GetTimecode(This,format,timecode) ) #define IDeckLinkVideoFrame_GetAncillaryData(This,ancillary) \ ( (This)->lpVtbl -> GetAncillaryData(This,ancillary) ) #endif /* COBJMACROS */ #endif /* C style interface */ #endif /* __IDeckLinkVideoFrame_INTERFACE_DEFINED__ */ #ifndef __IDeckLinkMutableVideoFrame_INTERFACE_DEFINED__ #define __IDeckLinkMutableVideoFrame_INTERFACE_DEFINED__ /* interface IDeckLinkMutableVideoFrame */ /* [helpstring][local][uuid][object] */ EXTERN_C const IID IID_IDeckLinkMutableVideoFrame; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("69E2639F-40DA-4E19-B6F2-20ACE815C390") IDeckLinkMutableVideoFrame : public IDeckLinkVideoFrame { public: virtual HRESULT STDMETHODCALLTYPE SetFlags( /* [in] */ BMDFrameFlags newFlags) = 0; virtual HRESULT STDMETHODCALLTYPE SetTimecode( /* [in] */ BMDTimecodeFormat format, /* [in] */ IDeckLinkTimecode *timecode) = 0; virtual HRESULT STDMETHODCALLTYPE SetTimecodeFromComponents( /* [in] */ BMDTimecodeFormat format, /* [in] */ unsigned char hours, /* [in] */ unsigned char minutes, /* [in] */ unsigned char seconds, /* [in] */ unsigned char frames, /* [in] */ BMDTimecodeFlags flags) = 0; virtual HRESULT STDMETHODCALLTYPE SetAncillaryData( /* [in] */ IDeckLinkVideoFrameAncillary *ancillary) = 0; virtual HRESULT STDMETHODCALLTYPE SetTimecodeUserBits( /* [in] */ BMDTimecodeFormat format, /* [in] */ BMDTimecodeUserBits userBits) = 0; }; #else /* C style interface */ typedef struct IDeckLinkMutableVideoFrameVtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IDeckLinkMutableVideoFrame * This, /* [in] */ REFIID riid, /* [annotation][iid_is][out] */ __RPC__deref_out void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IDeckLinkMutableVideoFrame * This); ULONG ( STDMETHODCALLTYPE *Release )( IDeckLinkMutableVideoFrame * This); long ( STDMETHODCALLTYPE *GetWidth )( IDeckLinkMutableVideoFrame * This); long ( STDMETHODCALLTYPE *GetHeight )( IDeckLinkMutableVideoFrame * This); long ( STDMETHODCALLTYPE *GetRowBytes )( IDeckLinkMutableVideoFrame * This); BMDPixelFormat ( STDMETHODCALLTYPE *GetPixelFormat )( IDeckLinkMutableVideoFrame * This); BMDFrameFlags ( STDMETHODCALLTYPE *GetFlags )( IDeckLinkMutableVideoFrame * This); HRESULT ( STDMETHODCALLTYPE *GetBytes )( IDeckLinkMutableVideoFrame * This, /* [out] */ void **buffer); HRESULT ( STDMETHODCALLTYPE *GetTimecode )( IDeckLinkMutableVideoFrame * This, /* [in] */ BMDTimecodeFormat format, /* [out] */ IDeckLinkTimecode **timecode); HRESULT ( STDMETHODCALLTYPE *GetAncillaryData )( IDeckLinkMutableVideoFrame * This, /* [out] */ IDeckLinkVideoFrameAncillary **ancillary); HRESULT ( STDMETHODCALLTYPE *SetFlags )( IDeckLinkMutableVideoFrame * This, /* [in] */ BMDFrameFlags newFlags); HRESULT ( STDMETHODCALLTYPE *SetTimecode )( IDeckLinkMutableVideoFrame * This, /* [in] */ BMDTimecodeFormat format, /* [in] */ IDeckLinkTimecode *timecode); HRESULT ( STDMETHODCALLTYPE *SetTimecodeFromComponents )( IDeckLinkMutableVideoFrame * This, /* [in] */ BMDTimecodeFormat format, /* [in] */ unsigned char hours, /* [in] */ unsigned char minutes, /* [in] */ unsigned char seconds, /* [in] */ unsigned char frames, /* [in] */ BMDTimecodeFlags flags); HRESULT ( STDMETHODCALLTYPE *SetAncillaryData )( IDeckLinkMutableVideoFrame * This, /* [in] */ IDeckLinkVideoFrameAncillary *ancillary); HRESULT ( STDMETHODCALLTYPE *SetTimecodeUserBits )( IDeckLinkMutableVideoFrame * This, /* [in] */ BMDTimecodeFormat format, /* [in] */ BMDTimecodeUserBits userBits); END_INTERFACE } IDeckLinkMutableVideoFrameVtbl; interface IDeckLinkMutableVideoFrame { CONST_VTBL struct IDeckLinkMutableVideoFrameVtbl *lpVtbl; }; #ifdef COBJMACROS #define IDeckLinkMutableVideoFrame_QueryInterface(This,riid,ppvObject) \ ( (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) ) #define IDeckLinkMutableVideoFrame_AddRef(This) \ ( (This)->lpVtbl -> AddRef(This) ) #define IDeckLinkMutableVideoFrame_Release(This) \ ( (This)->lpVtbl -> Release(This) ) #define IDeckLinkMutableVideoFrame_GetWidth(This) \ ( (This)->lpVtbl -> GetWidth(This) ) #define IDeckLinkMutableVideoFrame_GetHeight(This) \ ( (This)->lpVtbl -> GetHeight(This) ) #define IDeckLinkMutableVideoFrame_GetRowBytes(This) \ ( (This)->lpVtbl -> GetRowBytes(This) ) #define IDeckLinkMutableVideoFrame_GetPixelFormat(This) \ ( (This)->lpVtbl -> GetPixelFormat(This) ) #define IDeckLinkMutableVideoFrame_GetFlags(This) \ ( (This)->lpVtbl -> GetFlags(This) ) #define IDeckLinkMutableVideoFrame_GetBytes(This,buffer) \ ( (This)->lpVtbl -> GetBytes(This,buffer) ) #define IDeckLinkMutableVideoFrame_GetTimecode(This,format,timecode) \ ( (This)->lpVtbl -> GetTimecode(This,format,timecode) ) #define IDeckLinkMutableVideoFrame_GetAncillaryData(This,ancillary) \ ( (This)->lpVtbl -> GetAncillaryData(This,ancillary) ) #define IDeckLinkMutableVideoFrame_SetFlags(This,newFlags) \ ( (This)->lpVtbl -> SetFlags(This,newFlags) ) #define IDeckLinkMutableVideoFrame_SetTimecode(This,format,timecode) \ ( (This)->lpVtbl -> SetTimecode(This,format,timecode) ) #define IDeckLinkMutableVideoFrame_SetTimecodeFromComponents(This,format,hours,minutes,seconds,frames,flags) \ ( (This)->lpVtbl -> SetTimecodeFromComponents(This,format,hours,minutes,seconds,frames,flags) ) #define IDeckLinkMutableVideoFrame_SetAncillaryData(This,ancillary) \ ( (This)->lpVtbl -> SetAncillaryData(This,ancillary) ) #define IDeckLinkMutableVideoFrame_SetTimecodeUserBits(This,format,userBits) \ ( (This)->lpVtbl -> SetTimecodeUserBits(This,format,userBits) ) #endif /* COBJMACROS */ #endif /* C style interface */ #endif /* __IDeckLinkMutableVideoFrame_INTERFACE_DEFINED__ */ #ifndef __IDeckLinkVideoFrame3DExtensions_INTERFACE_DEFINED__ #define __IDeckLinkVideoFrame3DExtensions_INTERFACE_DEFINED__ /* interface IDeckLinkVideoFrame3DExtensions */ /* [helpstring][local][uuid][object] */ EXTERN_C const IID IID_IDeckLinkVideoFrame3DExtensions; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("DA0F7E4A-EDC7-48A8-9CDD-2DB51C729CD7") IDeckLinkVideoFrame3DExtensions : public IUnknown { public: virtual BMDVideo3DPackingFormat STDMETHODCALLTYPE Get3DPackingFormat( void) = 0; virtual HRESULT STDMETHODCALLTYPE GetFrameForRightEye( /* [in] */ IDeckLinkVideoFrame **rightEyeFrame) = 0; }; #else /* C style interface */ typedef struct IDeckLinkVideoFrame3DExtensionsVtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IDeckLinkVideoFrame3DExtensions * This, /* [in] */ REFIID riid, /* [annotation][iid_is][out] */ __RPC__deref_out void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IDeckLinkVideoFrame3DExtensions * This); ULONG ( STDMETHODCALLTYPE *Release )( IDeckLinkVideoFrame3DExtensions * This); BMDVideo3DPackingFormat ( STDMETHODCALLTYPE *Get3DPackingFormat )( IDeckLinkVideoFrame3DExtensions * This); HRESULT ( STDMETHODCALLTYPE *GetFrameForRightEye )( IDeckLinkVideoFrame3DExtensions * This, /* [in] */ IDeckLinkVideoFrame **rightEyeFrame); END_INTERFACE } IDeckLinkVideoFrame3DExtensionsVtbl; interface IDeckLinkVideoFrame3DExtensions { CONST_VTBL struct IDeckLinkVideoFrame3DExtensionsVtbl *lpVtbl; }; #ifdef COBJMACROS #define IDeckLinkVideoFrame3DExtensions_QueryInterface(This,riid,ppvObject) \ ( (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) ) #define IDeckLinkVideoFrame3DExtensions_AddRef(This) \ ( (This)->lpVtbl -> AddRef(This) ) #define IDeckLinkVideoFrame3DExtensions_Release(This) \ ( (This)->lpVtbl -> Release(This) ) #define IDeckLinkVideoFrame3DExtensions_Get3DPackingFormat(This) \ ( (This)->lpVtbl -> Get3DPackingFormat(This) ) #define IDeckLinkVideoFrame3DExtensions_GetFrameForRightEye(This,rightEyeFrame) \ ( (This)->lpVtbl -> GetFrameForRightEye(This,rightEyeFrame) ) #endif /* COBJMACROS */ #endif /* C style interface */ #endif /* __IDeckLinkVideoFrame3DExtensions_INTERFACE_DEFINED__ */ #ifndef __IDeckLinkVideoInputFrame_INTERFACE_DEFINED__ #define __IDeckLinkVideoInputFrame_INTERFACE_DEFINED__ /* interface IDeckLinkVideoInputFrame */ /* [helpstring][local][uuid][object] */ EXTERN_C const IID IID_IDeckLinkVideoInputFrame; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("05CFE374-537C-4094-9A57-680525118F44") IDeckLinkVideoInputFrame : public IDeckLinkVideoFrame { public: virtual HRESULT STDMETHODCALLTYPE GetStreamTime( /* [out] */ BMDTimeValue *frameTime, /* [out] */ BMDTimeValue *frameDuration, /* [in] */ BMDTimeScale timeScale) = 0; virtual HRESULT STDMETHODCALLTYPE GetHardwareReferenceTimestamp( /* [in] */ BMDTimeScale timeScale, /* [out] */ BMDTimeValue *frameTime, /* [out] */ BMDTimeValue *frameDuration) = 0; }; #else /* C style interface */ typedef struct IDeckLinkVideoInputFrameVtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IDeckLinkVideoInputFrame * This, /* [in] */ REFIID riid, /* [annotation][iid_is][out] */ __RPC__deref_out void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IDeckLinkVideoInputFrame * This); ULONG ( STDMETHODCALLTYPE *Release )( IDeckLinkVideoInputFrame * This); long ( STDMETHODCALLTYPE *GetWidth )( IDeckLinkVideoInputFrame * This); long ( STDMETHODCALLTYPE *GetHeight )( IDeckLinkVideoInputFrame * This); long ( STDMETHODCALLTYPE *GetRowBytes )( IDeckLinkVideoInputFrame * This); BMDPixelFormat ( STDMETHODCALLTYPE *GetPixelFormat )( IDeckLinkVideoInputFrame * This); BMDFrameFlags ( STDMETHODCALLTYPE *GetFlags )( IDeckLinkVideoInputFrame * This); HRESULT ( STDMETHODCALLTYPE *GetBytes )( IDeckLinkVideoInputFrame * This, /* [out] */ void **buffer); HRESULT ( STDMETHODCALLTYPE *GetTimecode )( IDeckLinkVideoInputFrame * This, /* [in] */ BMDTimecodeFormat format, /* [out] */ IDeckLinkTimecode **timecode); HRESULT ( STDMETHODCALLTYPE *GetAncillaryData )( IDeckLinkVideoInputFrame * This, /* [out] */ IDeckLinkVideoFrameAncillary **ancillary); HRESULT ( STDMETHODCALLTYPE *GetStreamTime )( IDeckLinkVideoInputFrame * This, /* [out] */ BMDTimeValue *frameTime, /* [out] */ BMDTimeValue *frameDuration, /* [in] */ BMDTimeScale timeScale); HRESULT ( STDMETHODCALLTYPE *GetHardwareReferenceTimestamp )( IDeckLinkVideoInputFrame * This, /* [in] */ BMDTimeScale timeScale, /* [out] */ BMDTimeValue *frameTime, /* [out] */ BMDTimeValue *frameDuration); END_INTERFACE } IDeckLinkVideoInputFrameVtbl; interface IDeckLinkVideoInputFrame { CONST_VTBL struct IDeckLinkVideoInputFrameVtbl *lpVtbl; }; #ifdef COBJMACROS #define IDeckLinkVideoInputFrame_QueryInterface(This,riid,ppvObject) \ ( (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) ) #define IDeckLinkVideoInputFrame_AddRef(This) \ ( (This)->lpVtbl -> AddRef(This) ) #define IDeckLinkVideoInputFrame_Release(This) \ ( (This)->lpVtbl -> Release(This) ) #define IDeckLinkVideoInputFrame_GetWidth(This) \ ( (This)->lpVtbl -> GetWidth(This) ) #define IDeckLinkVideoInputFrame_GetHeight(This) \ ( (This)->lpVtbl -> GetHeight(This) ) #define IDeckLinkVideoInputFrame_GetRowBytes(This) \ ( (This)->lpVtbl -> GetRowBytes(This) ) #define IDeckLinkVideoInputFrame_GetPixelFormat(This) \ ( (This)->lpVtbl -> GetPixelFormat(This) ) #define IDeckLinkVideoInputFrame_GetFlags(This) \ ( (This)->lpVtbl -> GetFlags(This) ) #define IDeckLinkVideoInputFrame_GetBytes(This,buffer) \ ( (This)->lpVtbl -> GetBytes(This,buffer) ) #define IDeckLinkVideoInputFrame_GetTimecode(This,format,timecode) \ ( (This)->lpVtbl -> GetTimecode(This,format,timecode) ) #define IDeckLinkVideoInputFrame_GetAncillaryData(This,ancillary) \ ( (This)->lpVtbl -> GetAncillaryData(This,ancillary) ) #define IDeckLinkVideoInputFrame_GetStreamTime(This,frameTime,frameDuration,timeScale) \ ( (This)->lpVtbl -> GetStreamTime(This,frameTime,frameDuration,timeScale) ) #define IDeckLinkVideoInputFrame_GetHardwareReferenceTimestamp(This,timeScale,frameTime,frameDuration) \ ( (This)->lpVtbl -> GetHardwareReferenceTimestamp(This,timeScale,frameTime,frameDuration) ) #endif /* COBJMACROS */ #endif /* C style interface */ #endif /* __IDeckLinkVideoInputFrame_INTERFACE_DEFINED__ */ #ifndef __IDeckLinkVideoFrameAncillary_INTERFACE_DEFINED__ #define __IDeckLinkVideoFrameAncillary_INTERFACE_DEFINED__ /* interface IDeckLinkVideoFrameAncillary */ /* [helpstring][local][uuid][object] */ EXTERN_C const IID IID_IDeckLinkVideoFrameAncillary; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("732E723C-D1A4-4E29-9E8E-4A88797A0004") IDeckLinkVideoFrameAncillary : public IUnknown { public: virtual HRESULT STDMETHODCALLTYPE GetBufferForVerticalBlankingLine( /* [in] */ unsigned long lineNumber, /* [out] */ void **buffer) = 0; virtual BMDPixelFormat STDMETHODCALLTYPE GetPixelFormat( void) = 0; virtual BMDDisplayMode STDMETHODCALLTYPE GetDisplayMode( void) = 0; }; #else /* C style interface */ typedef struct IDeckLinkVideoFrameAncillaryVtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IDeckLinkVideoFrameAncillary * This, /* [in] */ REFIID riid, /* [annotation][iid_is][out] */ __RPC__deref_out void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IDeckLinkVideoFrameAncillary * This); ULONG ( STDMETHODCALLTYPE *Release )( IDeckLinkVideoFrameAncillary * This); HRESULT ( STDMETHODCALLTYPE *GetBufferForVerticalBlankingLine )( IDeckLinkVideoFrameAncillary * This, /* [in] */ unsigned long lineNumber, /* [out] */ void **buffer); BMDPixelFormat ( STDMETHODCALLTYPE *GetPixelFormat )( IDeckLinkVideoFrameAncillary * This); BMDDisplayMode ( STDMETHODCALLTYPE *GetDisplayMode )( IDeckLinkVideoFrameAncillary * This); END_INTERFACE } IDeckLinkVideoFrameAncillaryVtbl; interface IDeckLinkVideoFrameAncillary { CONST_VTBL struct IDeckLinkVideoFrameAncillaryVtbl *lpVtbl; }; #ifdef COBJMACROS #define IDeckLinkVideoFrameAncillary_QueryInterface(This,riid,ppvObject) \ ( (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) ) #define IDeckLinkVideoFrameAncillary_AddRef(This) \ ( (This)->lpVtbl -> AddRef(This) ) #define IDeckLinkVideoFrameAncillary_Release(This) \ ( (This)->lpVtbl -> Release(This) ) #define IDeckLinkVideoFrameAncillary_GetBufferForVerticalBlankingLine(This,lineNumber,buffer) \ ( (This)->lpVtbl -> GetBufferForVerticalBlankingLine(This,lineNumber,buffer) ) #define IDeckLinkVideoFrameAncillary_GetPixelFormat(This) \ ( (This)->lpVtbl -> GetPixelFormat(This) ) #define IDeckLinkVideoFrameAncillary_GetDisplayMode(This) \ ( (This)->lpVtbl -> GetDisplayMode(This) ) #endif /* COBJMACROS */ #endif /* C style interface */ #endif /* __IDeckLinkVideoFrameAncillary_INTERFACE_DEFINED__ */ #ifndef __IDeckLinkAudioInputPacket_INTERFACE_DEFINED__ #define __IDeckLinkAudioInputPacket_INTERFACE_DEFINED__ /* interface IDeckLinkAudioInputPacket */ /* [helpstring][local][uuid][object] */ EXTERN_C const IID IID_IDeckLinkAudioInputPacket; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("E43D5870-2894-11DE-8C30-0800200C9A66") IDeckLinkAudioInputPacket : public IUnknown { public: virtual long STDMETHODCALLTYPE GetSampleFrameCount( void) = 0; virtual HRESULT STDMETHODCALLTYPE GetBytes( /* [out] */ void **buffer) = 0; virtual HRESULT STDMETHODCALLTYPE GetPacketTime( /* [out] */ BMDTimeValue *packetTime, /* [in] */ BMDTimeScale timeScale) = 0; }; #else /* C style interface */ typedef struct IDeckLinkAudioInputPacketVtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IDeckLinkAudioInputPacket * This, /* [in] */ REFIID riid, /* [annotation][iid_is][out] */ __RPC__deref_out void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IDeckLinkAudioInputPacket * This); ULONG ( STDMETHODCALLTYPE *Release )( IDeckLinkAudioInputPacket * This); long ( STDMETHODCALLTYPE *GetSampleFrameCount )( IDeckLinkAudioInputPacket * This); HRESULT ( STDMETHODCALLTYPE *GetBytes )( IDeckLinkAudioInputPacket * This, /* [out] */ void **buffer); HRESULT ( STDMETHODCALLTYPE *GetPacketTime )( IDeckLinkAudioInputPacket * This, /* [out] */ BMDTimeValue *packetTime, /* [in] */ BMDTimeScale timeScale); END_INTERFACE } IDeckLinkAudioInputPacketVtbl; interface IDeckLinkAudioInputPacket { CONST_VTBL struct IDeckLinkAudioInputPacketVtbl *lpVtbl; }; #ifdef COBJMACROS #define IDeckLinkAudioInputPacket_QueryInterface(This,riid,ppvObject) \ ( (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) ) #define IDeckLinkAudioInputPacket_AddRef(This) \ ( (This)->lpVtbl -> AddRef(This) ) #define IDeckLinkAudioInputPacket_Release(This) \ ( (This)->lpVtbl -> Release(This) ) #define IDeckLinkAudioInputPacket_GetSampleFrameCount(This) \ ( (This)->lpVtbl -> GetSampleFrameCount(This) ) #define IDeckLinkAudioInputPacket_GetBytes(This,buffer) \ ( (This)->lpVtbl -> GetBytes(This,buffer) ) #define IDeckLinkAudioInputPacket_GetPacketTime(This,packetTime,timeScale) \ ( (This)->lpVtbl -> GetPacketTime(This,packetTime,timeScale) ) #endif /* COBJMACROS */ #endif /* C style interface */ #endif /* __IDeckLinkAudioInputPacket_INTERFACE_DEFINED__ */ #ifndef __IDeckLinkScreenPreviewCallback_INTERFACE_DEFINED__ #define __IDeckLinkScreenPreviewCallback_INTERFACE_DEFINED__ /* interface IDeckLinkScreenPreviewCallback */ /* [helpstring][local][uuid][object] */ EXTERN_C const IID IID_IDeckLinkScreenPreviewCallback; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("B1D3F49A-85FE-4C5D-95C8-0B5D5DCCD438") IDeckLinkScreenPreviewCallback : public IUnknown { public: virtual HRESULT STDMETHODCALLTYPE DrawFrame( /* [in] */ IDeckLinkVideoFrame *theFrame) = 0; }; #else /* C style interface */ typedef struct IDeckLinkScreenPreviewCallbackVtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IDeckLinkScreenPreviewCallback * This, /* [in] */ REFIID riid, /* [annotation][iid_is][out] */ __RPC__deref_out void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IDeckLinkScreenPreviewCallback * This); ULONG ( STDMETHODCALLTYPE *Release )( IDeckLinkScreenPreviewCallback * This); HRESULT ( STDMETHODCALLTYPE *DrawFrame )( IDeckLinkScreenPreviewCallback * This, /* [in] */ IDeckLinkVideoFrame *theFrame); END_INTERFACE } IDeckLinkScreenPreviewCallbackVtbl; interface IDeckLinkScreenPreviewCallback { CONST_VTBL struct IDeckLinkScreenPreviewCallbackVtbl *lpVtbl; }; #ifdef COBJMACROS #define IDeckLinkScreenPreviewCallback_QueryInterface(This,riid,ppvObject) \ ( (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) ) #define IDeckLinkScreenPreviewCallback_AddRef(This) \ ( (This)->lpVtbl -> AddRef(This) ) #define IDeckLinkScreenPreviewCallback_Release(This) \ ( (This)->lpVtbl -> Release(This) ) #define IDeckLinkScreenPreviewCallback_DrawFrame(This,theFrame) \ ( (This)->lpVtbl -> DrawFrame(This,theFrame) ) #endif /* COBJMACROS */ #endif /* C style interface */ #endif /* __IDeckLinkScreenPreviewCallback_INTERFACE_DEFINED__ */ #ifndef __IDeckLinkGLScreenPreviewHelper_INTERFACE_DEFINED__ #define __IDeckLinkGLScreenPreviewHelper_INTERFACE_DEFINED__ /* interface IDeckLinkGLScreenPreviewHelper */ /* [helpstring][local][uuid][object] */ EXTERN_C const IID IID_IDeckLinkGLScreenPreviewHelper; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("504E2209-CAC7-4C1A-9FB4-C5BB6274D22F") IDeckLinkGLScreenPreviewHelper : public IUnknown { public: virtual HRESULT STDMETHODCALLTYPE InitializeGL( void) = 0; virtual HRESULT STDMETHODCALLTYPE PaintGL( void) = 0; virtual HRESULT STDMETHODCALLTYPE SetFrame( /* [in] */ IDeckLinkVideoFrame *theFrame) = 0; virtual HRESULT STDMETHODCALLTYPE Set3DPreviewFormat( /* [in] */ BMD3DPreviewFormat previewFormat) = 0; }; #else /* C style interface */ typedef struct IDeckLinkGLScreenPreviewHelperVtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IDeckLinkGLScreenPreviewHelper * This, /* [in] */ REFIID riid, /* [annotation][iid_is][out] */ __RPC__deref_out void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IDeckLinkGLScreenPreviewHelper * This); ULONG ( STDMETHODCALLTYPE *Release )( IDeckLinkGLScreenPreviewHelper * This); HRESULT ( STDMETHODCALLTYPE *InitializeGL )( IDeckLinkGLScreenPreviewHelper * This); HRESULT ( STDMETHODCALLTYPE *PaintGL )( IDeckLinkGLScreenPreviewHelper * This); HRESULT ( STDMETHODCALLTYPE *SetFrame )( IDeckLinkGLScreenPreviewHelper * This, /* [in] */ IDeckLinkVideoFrame *theFrame); HRESULT ( STDMETHODCALLTYPE *Set3DPreviewFormat )( IDeckLinkGLScreenPreviewHelper * This, /* [in] */ BMD3DPreviewFormat previewFormat); END_INTERFACE } IDeckLinkGLScreenPreviewHelperVtbl; interface IDeckLinkGLScreenPreviewHelper { CONST_VTBL struct IDeckLinkGLScreenPreviewHelperVtbl *lpVtbl; }; #ifdef COBJMACROS #define IDeckLinkGLScreenPreviewHelper_QueryInterface(This,riid,ppvObject) \ ( (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) ) #define IDeckLinkGLScreenPreviewHelper_AddRef(This) \ ( (This)->lpVtbl -> AddRef(This) ) #define IDeckLinkGLScreenPreviewHelper_Release(This) \ ( (This)->lpVtbl -> Release(This) ) #define IDeckLinkGLScreenPreviewHelper_InitializeGL(This) \ ( (This)->lpVtbl -> InitializeGL(This) ) #define IDeckLinkGLScreenPreviewHelper_PaintGL(This) \ ( (This)->lpVtbl -> PaintGL(This) ) #define IDeckLinkGLScreenPreviewHelper_SetFrame(This,theFrame) \ ( (This)->lpVtbl -> SetFrame(This,theFrame) ) #define IDeckLinkGLScreenPreviewHelper_Set3DPreviewFormat(This,previewFormat) \ ( (This)->lpVtbl -> Set3DPreviewFormat(This,previewFormat) ) #endif /* COBJMACROS */ #endif /* C style interface */ #endif /* __IDeckLinkGLScreenPreviewHelper_INTERFACE_DEFINED__ */ #ifndef __IDeckLinkConfiguration_INTERFACE_DEFINED__ #define __IDeckLinkConfiguration_INTERFACE_DEFINED__ /* interface IDeckLinkConfiguration */ /* [helpstring][local][uuid][object] */ EXTERN_C const IID IID_IDeckLinkConfiguration; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("C679A35B-610C-4D09-B748-1D0478100FC0") IDeckLinkConfiguration : public IUnknown { public: virtual HRESULT STDMETHODCALLTYPE SetFlag( /* [in] */ BMDDeckLinkConfigurationID cfgID, /* [in] */ BOOL value) = 0; virtual HRESULT STDMETHODCALLTYPE GetFlag( /* [in] */ BMDDeckLinkConfigurationID cfgID, /* [out] */ BOOL *value) = 0; virtual HRESULT STDMETHODCALLTYPE SetInt( /* [in] */ BMDDeckLinkConfigurationID cfgID, /* [in] */ LONGLONG value) = 0; virtual HRESULT STDMETHODCALLTYPE GetInt( /* [in] */ BMDDeckLinkConfigurationID cfgID, /* [out] */ LONGLONG *value) = 0; virtual HRESULT STDMETHODCALLTYPE SetFloat( /* [in] */ BMDDeckLinkConfigurationID cfgID, /* [in] */ double value) = 0; virtual HRESULT STDMETHODCALLTYPE GetFloat( /* [in] */ BMDDeckLinkConfigurationID cfgID, /* [out] */ double *value) = 0; virtual HRESULT STDMETHODCALLTYPE SetString( /* [in] */ BMDDeckLinkConfigurationID cfgID, /* [in] */ BSTR value) = 0; virtual HRESULT STDMETHODCALLTYPE GetString( /* [in] */ BMDDeckLinkConfigurationID cfgID, /* [out] */ BSTR *value) = 0; virtual HRESULT STDMETHODCALLTYPE WriteConfigurationToPreferences( void) = 0; }; #else /* C style interface */ typedef struct IDeckLinkConfigurationVtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IDeckLinkConfiguration * This, /* [in] */ REFIID riid, /* [annotation][iid_is][out] */ __RPC__deref_out void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IDeckLinkConfiguration * This); ULONG ( STDMETHODCALLTYPE *Release )( IDeckLinkConfiguration * This); HRESULT ( STDMETHODCALLTYPE *SetFlag )( IDeckLinkConfiguration * This, /* [in] */ BMDDeckLinkConfigurationID cfgID, /* [in] */ BOOL value); HRESULT ( STDMETHODCALLTYPE *GetFlag )( IDeckLinkConfiguration * This, /* [in] */ BMDDeckLinkConfigurationID cfgID, /* [out] */ BOOL *value); HRESULT ( STDMETHODCALLTYPE *SetInt )( IDeckLinkConfiguration * This, /* [in] */ BMDDeckLinkConfigurationID cfgID, /* [in] */ LONGLONG value); HRESULT ( STDMETHODCALLTYPE *GetInt )( IDeckLinkConfiguration * This, /* [in] */ BMDDeckLinkConfigurationID cfgID, /* [out] */ LONGLONG *value); HRESULT ( STDMETHODCALLTYPE *SetFloat )( IDeckLinkConfiguration * This, /* [in] */ BMDDeckLinkConfigurationID cfgID, /* [in] */ double value); HRESULT ( STDMETHODCALLTYPE *GetFloat )( IDeckLinkConfiguration * This, /* [in] */ BMDDeckLinkConfigurationID cfgID, /* [out] */ double *value); HRESULT ( STDMETHODCALLTYPE *SetString )( IDeckLinkConfiguration * This, /* [in] */ BMDDeckLinkConfigurationID cfgID, /* [in] */ BSTR value); HRESULT ( STDMETHODCALLTYPE *GetString )( IDeckLinkConfiguration * This, /* [in] */ BMDDeckLinkConfigurationID cfgID, /* [out] */ BSTR *value); HRESULT ( STDMETHODCALLTYPE *WriteConfigurationToPreferences )( IDeckLinkConfiguration * This); END_INTERFACE } IDeckLinkConfigurationVtbl; interface IDeckLinkConfiguration { CONST_VTBL struct IDeckLinkConfigurationVtbl *lpVtbl; }; #ifdef COBJMACROS #define IDeckLinkConfiguration_QueryInterface(This,riid,ppvObject) \ ( (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) ) #define IDeckLinkConfiguration_AddRef(This) \ ( (This)->lpVtbl -> AddRef(This) ) #define IDeckLinkConfiguration_Release(This) \ ( (This)->lpVtbl -> Release(This) ) #define IDeckLinkConfiguration_SetFlag(This,cfgID,value) \ ( (This)->lpVtbl -> SetFlag(This,cfgID,value) ) #define IDeckLinkConfiguration_GetFlag(This,cfgID,value) \ ( (This)->lpVtbl -> GetFlag(This,cfgID,value) ) #define IDeckLinkConfiguration_SetInt(This,cfgID,value) \ ( (This)->lpVtbl -> SetInt(This,cfgID,value) ) #define IDeckLinkConfiguration_GetInt(This,cfgID,value) \ ( (This)->lpVtbl -> GetInt(This,cfgID,value) ) #define IDeckLinkConfiguration_SetFloat(This,cfgID,value) \ ( (This)->lpVtbl -> SetFloat(This,cfgID,value) ) #define IDeckLinkConfiguration_GetFloat(This,cfgID,value) \ ( (This)->lpVtbl -> GetFloat(This,cfgID,value) ) #define IDeckLinkConfiguration_SetString(This,cfgID,value) \ ( (This)->lpVtbl -> SetString(This,cfgID,value) ) #define IDeckLinkConfiguration_GetString(This,cfgID,value) \ ( (This)->lpVtbl -> GetString(This,cfgID,value) ) #define IDeckLinkConfiguration_WriteConfigurationToPreferences(This) \ ( (This)->lpVtbl -> WriteConfigurationToPreferences(This) ) #endif /* COBJMACROS */ #endif /* C style interface */ #endif /* __IDeckLinkConfiguration_INTERFACE_DEFINED__ */ #ifndef __IDeckLinkAttributes_INTERFACE_DEFINED__ #define __IDeckLinkAttributes_INTERFACE_DEFINED__ /* interface IDeckLinkAttributes */ /* [helpstring][local][uuid][object] */ EXTERN_C const IID IID_IDeckLinkAttributes; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("ABC11843-D966-44CB-96E2-A1CB5D3135C4") IDeckLinkAttributes : public IUnknown { public: virtual HRESULT STDMETHODCALLTYPE GetFlag( /* [in] */ BMDDeckLinkAttributeID cfgID, /* [out] */ BOOL *value) = 0; virtual HRESULT STDMETHODCALLTYPE GetInt( /* [in] */ BMDDeckLinkAttributeID cfgID, /* [out] */ LONGLONG *value) = 0; virtual HRESULT STDMETHODCALLTYPE GetFloat( /* [in] */ BMDDeckLinkAttributeID cfgID, /* [out] */ double *value) = 0; virtual HRESULT STDMETHODCALLTYPE GetString( /* [in] */ BMDDeckLinkAttributeID cfgID, /* [out] */ BSTR *value) = 0; }; #else /* C style interface */ typedef struct IDeckLinkAttributesVtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IDeckLinkAttributes * This, /* [in] */ REFIID riid, /* [annotation][iid_is][out] */ __RPC__deref_out void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IDeckLinkAttributes * This); ULONG ( STDMETHODCALLTYPE *Release )( IDeckLinkAttributes * This); HRESULT ( STDMETHODCALLTYPE *GetFlag )( IDeckLinkAttributes * This, /* [in] */ BMDDeckLinkAttributeID cfgID, /* [out] */ BOOL *value); HRESULT ( STDMETHODCALLTYPE *GetInt )( IDeckLinkAttributes * This, /* [in] */ BMDDeckLinkAttributeID cfgID, /* [out] */ LONGLONG *value); HRESULT ( STDMETHODCALLTYPE *GetFloat )( IDeckLinkAttributes * This, /* [in] */ BMDDeckLinkAttributeID cfgID, /* [out] */ double *value); HRESULT ( STDMETHODCALLTYPE *GetString )( IDeckLinkAttributes * This, /* [in] */ BMDDeckLinkAttributeID cfgID, /* [out] */ BSTR *value); END_INTERFACE } IDeckLinkAttributesVtbl; interface IDeckLinkAttributes { CONST_VTBL struct IDeckLinkAttributesVtbl *lpVtbl; }; #ifdef COBJMACROS #define IDeckLinkAttributes_QueryInterface(This,riid,ppvObject) \ ( (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) ) #define IDeckLinkAttributes_AddRef(This) \ ( (This)->lpVtbl -> AddRef(This) ) #define IDeckLinkAttributes_Release(This) \ ( (This)->lpVtbl -> Release(This) ) #define IDeckLinkAttributes_GetFlag(This,cfgID,value) \ ( (This)->lpVtbl -> GetFlag(This,cfgID,value) ) #define IDeckLinkAttributes_GetInt(This,cfgID,value) \ ( (This)->lpVtbl -> GetInt(This,cfgID,value) ) #define IDeckLinkAttributes_GetFloat(This,cfgID,value) \ ( (This)->lpVtbl -> GetFloat(This,cfgID,value) ) #define IDeckLinkAttributes_GetString(This,cfgID,value) \ ( (This)->lpVtbl -> GetString(This,cfgID,value) ) #endif /* COBJMACROS */ #endif /* C style interface */ #endif /* __IDeckLinkAttributes_INTERFACE_DEFINED__ */ #ifndef __IDeckLinkKeyer_INTERFACE_DEFINED__ #define __IDeckLinkKeyer_INTERFACE_DEFINED__ /* interface IDeckLinkKeyer */ /* [helpstring][local][uuid][object] */ EXTERN_C const IID IID_IDeckLinkKeyer; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("89AFCAF5-65F8-421E-98F7-96FE5F5BFBA3") IDeckLinkKeyer : public IUnknown { public: virtual HRESULT STDMETHODCALLTYPE Enable( /* [in] */ BOOL isExternal) = 0; virtual HRESULT STDMETHODCALLTYPE SetLevel( /* [in] */ unsigned char level) = 0; virtual HRESULT STDMETHODCALLTYPE RampUp( /* [in] */ unsigned long numberOfFrames) = 0; virtual HRESULT STDMETHODCALLTYPE RampDown( /* [in] */ unsigned long numberOfFrames) = 0; virtual HRESULT STDMETHODCALLTYPE Disable( void) = 0; }; #else /* C style interface */ typedef struct IDeckLinkKeyerVtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IDeckLinkKeyer * This, /* [in] */ REFIID riid, /* [annotation][iid_is][out] */ __RPC__deref_out void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IDeckLinkKeyer * This); ULONG ( STDMETHODCALLTYPE *Release )( IDeckLinkKeyer * This); HRESULT ( STDMETHODCALLTYPE *Enable )( IDeckLinkKeyer * This, /* [in] */ BOOL isExternal); HRESULT ( STDMETHODCALLTYPE *SetLevel )( IDeckLinkKeyer * This, /* [in] */ unsigned char level); HRESULT ( STDMETHODCALLTYPE *RampUp )( IDeckLinkKeyer * This, /* [in] */ unsigned long numberOfFrames); HRESULT ( STDMETHODCALLTYPE *RampDown )( IDeckLinkKeyer * This, /* [in] */ unsigned long numberOfFrames); HRESULT ( STDMETHODCALLTYPE *Disable )( IDeckLinkKeyer * This); END_INTERFACE } IDeckLinkKeyerVtbl; interface IDeckLinkKeyer { CONST_VTBL struct IDeckLinkKeyerVtbl *lpVtbl; }; #ifdef COBJMACROS #define IDeckLinkKeyer_QueryInterface(This,riid,ppvObject) \ ( (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) ) #define IDeckLinkKeyer_AddRef(This) \ ( (This)->lpVtbl -> AddRef(This) ) #define IDeckLinkKeyer_Release(This) \ ( (This)->lpVtbl -> Release(This) ) #define IDeckLinkKeyer_Enable(This,isExternal) \ ( (This)->lpVtbl -> Enable(This,isExternal) ) #define IDeckLinkKeyer_SetLevel(This,level) \ ( (This)->lpVtbl -> SetLevel(This,level) ) #define IDeckLinkKeyer_RampUp(This,numberOfFrames) \ ( (This)->lpVtbl -> RampUp(This,numberOfFrames) ) #define IDeckLinkKeyer_RampDown(This,numberOfFrames) \ ( (This)->lpVtbl -> RampDown(This,numberOfFrames) ) #define IDeckLinkKeyer_Disable(This) \ ( (This)->lpVtbl -> Disable(This) ) #endif /* COBJMACROS */ #endif /* C style interface */ #endif /* __IDeckLinkKeyer_INTERFACE_DEFINED__ */ #ifndef __IDeckLinkVideoConversion_INTERFACE_DEFINED__ #define __IDeckLinkVideoConversion_INTERFACE_DEFINED__ /* interface IDeckLinkVideoConversion */ /* [helpstring][local][uuid][object] */ EXTERN_C const IID IID_IDeckLinkVideoConversion; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("3BBCB8A2-DA2C-42D9-B5D8-88083644E99A") IDeckLinkVideoConversion : public IUnknown { public: virtual HRESULT STDMETHODCALLTYPE ConvertFrame( /* [in] */ IDeckLinkVideoFrame *srcFrame, /* [in] */ IDeckLinkVideoFrame *dstFrame) = 0; }; #else /* C style interface */ typedef struct IDeckLinkVideoConversionVtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IDeckLinkVideoConversion * This, /* [in] */ REFIID riid, /* [annotation][iid_is][out] */ __RPC__deref_out void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IDeckLinkVideoConversion * This); ULONG ( STDMETHODCALLTYPE *Release )( IDeckLinkVideoConversion * This); HRESULT ( STDMETHODCALLTYPE *ConvertFrame )( IDeckLinkVideoConversion * This, /* [in] */ IDeckLinkVideoFrame *srcFrame, /* [in] */ IDeckLinkVideoFrame *dstFrame); END_INTERFACE } IDeckLinkVideoConversionVtbl; interface IDeckLinkVideoConversion { CONST_VTBL struct IDeckLinkVideoConversionVtbl *lpVtbl; }; #ifdef COBJMACROS #define IDeckLinkVideoConversion_QueryInterface(This,riid,ppvObject) \ ( (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) ) #define IDeckLinkVideoConversion_AddRef(This) \ ( (This)->lpVtbl -> AddRef(This) ) #define IDeckLinkVideoConversion_Release(This) \ ( (This)->lpVtbl -> Release(This) ) #define IDeckLinkVideoConversion_ConvertFrame(This,srcFrame,dstFrame) \ ( (This)->lpVtbl -> ConvertFrame(This,srcFrame,dstFrame) ) #endif /* COBJMACROS */ #endif /* C style interface */ #endif /* __IDeckLinkVideoConversion_INTERFACE_DEFINED__ */ #ifndef __IDeckLinkDeckControlStatusCallback_INTERFACE_DEFINED__ #define __IDeckLinkDeckControlStatusCallback_INTERFACE_DEFINED__ /* interface IDeckLinkDeckControlStatusCallback */ /* [helpstring][uuid][object] */ EXTERN_C const IID IID_IDeckLinkDeckControlStatusCallback; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("E5F693C1-4283-4716-B18F-C1431521955B") IDeckLinkDeckControlStatusCallback : public IUnknown { public: virtual HRESULT STDMETHODCALLTYPE TimecodeUpdate( /* [in] */ BMDTimecodeBCD currentTimecode) = 0; virtual HRESULT STDMETHODCALLTYPE VTRControlStateChanged( /* [in] */ BMDDeckControlVTRControlState newState, /* [in] */ BMDDeckControlError error) = 0; virtual HRESULT STDMETHODCALLTYPE DeckControlEventReceived( /* [in] */ BMDDeckControlEvent event, /* [in] */ BMDDeckControlError error) = 0; virtual HRESULT STDMETHODCALLTYPE DeckControlStatusChanged( /* [in] */ BMDDeckControlStatusFlags flags, /* [in] */ unsigned long mask) = 0; }; #else /* C style interface */ typedef struct IDeckLinkDeckControlStatusCallbackVtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IDeckLinkDeckControlStatusCallback * This, /* [in] */ REFIID riid, /* [annotation][iid_is][out] */ __RPC__deref_out void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IDeckLinkDeckControlStatusCallback * This); ULONG ( STDMETHODCALLTYPE *Release )( IDeckLinkDeckControlStatusCallback * This); HRESULT ( STDMETHODCALLTYPE *TimecodeUpdate )( IDeckLinkDeckControlStatusCallback * This, /* [in] */ BMDTimecodeBCD currentTimecode); HRESULT ( STDMETHODCALLTYPE *VTRControlStateChanged )( IDeckLinkDeckControlStatusCallback * This, /* [in] */ BMDDeckControlVTRControlState newState, /* [in] */ BMDDeckControlError error); HRESULT ( STDMETHODCALLTYPE *DeckControlEventReceived )( IDeckLinkDeckControlStatusCallback * This, /* [in] */ BMDDeckControlEvent event, /* [in] */ BMDDeckControlError error); HRESULT ( STDMETHODCALLTYPE *DeckControlStatusChanged )( IDeckLinkDeckControlStatusCallback * This, /* [in] */ BMDDeckControlStatusFlags flags, /* [in] */ unsigned long mask); END_INTERFACE } IDeckLinkDeckControlStatusCallbackVtbl; interface IDeckLinkDeckControlStatusCallback { CONST_VTBL struct IDeckLinkDeckControlStatusCallbackVtbl *lpVtbl; }; #ifdef COBJMACROS #define IDeckLinkDeckControlStatusCallback_QueryInterface(This,riid,ppvObject) \ ( (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) ) #define IDeckLinkDeckControlStatusCallback_AddRef(This) \ ( (This)->lpVtbl -> AddRef(This) ) #define IDeckLinkDeckControlStatusCallback_Release(This) \ ( (This)->lpVtbl -> Release(This) ) #define IDeckLinkDeckControlStatusCallback_TimecodeUpdate(This,currentTimecode) \ ( (This)->lpVtbl -> TimecodeUpdate(This,currentTimecode) ) #define IDeckLinkDeckControlStatusCallback_VTRControlStateChanged(This,newState,error) \ ( (This)->lpVtbl -> VTRControlStateChanged(This,newState,error) ) #define IDeckLinkDeckControlStatusCallback_DeckControlEventReceived(This,event,error) \ ( (This)->lpVtbl -> DeckControlEventReceived(This,event,error) ) #define IDeckLinkDeckControlStatusCallback_DeckControlStatusChanged(This,flags,mask) \ ( (This)->lpVtbl -> DeckControlStatusChanged(This,flags,mask) ) #endif /* COBJMACROS */ #endif /* C style interface */ #endif /* __IDeckLinkDeckControlStatusCallback_INTERFACE_DEFINED__ */ #ifndef __IDeckLinkDeckControl_INTERFACE_DEFINED__ #define __IDeckLinkDeckControl_INTERFACE_DEFINED__ /* interface IDeckLinkDeckControl */ /* [helpstring][uuid][object] */ EXTERN_C const IID IID_IDeckLinkDeckControl; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("A4D81043-0619-42B7-8ED6-602D29041DF7") IDeckLinkDeckControl : public IUnknown { public: virtual HRESULT STDMETHODCALLTYPE Open( /* [in] */ BMDTimeScale timeScale, /* [in] */ BMDTimeValue timeValue, /* [in] */ BOOL timecodeIsDropFrame, /* [out] */ BMDDeckControlError *error) = 0; virtual HRESULT STDMETHODCALLTYPE Close( /* [in] */ BOOL standbyOn) = 0; virtual HRESULT STDMETHODCALLTYPE GetCurrentState( /* [out] */ BMDDeckControlMode *mode, /* [out] */ BMDDeckControlVTRControlState *vtrControlState, /* [out] */ BMDDeckControlStatusFlags *flags) = 0; virtual HRESULT STDMETHODCALLTYPE SetStandby( /* [in] */ BOOL standbyOn) = 0; virtual HRESULT STDMETHODCALLTYPE Play( /* [out] */ BMDDeckControlError *error) = 0; virtual HRESULT STDMETHODCALLTYPE Stop( /* [out] */ BMDDeckControlError *error) = 0; virtual HRESULT STDMETHODCALLTYPE TogglePlayStop( /* [out] */ BMDDeckControlError *error) = 0; virtual HRESULT STDMETHODCALLTYPE Eject( /* [out] */ BMDDeckControlError *error) = 0; virtual HRESULT STDMETHODCALLTYPE GoToTimecode( /* [in] */ BMDTimecodeBCD timecode, /* [out] */ BMDDeckControlError *error) = 0; virtual HRESULT STDMETHODCALLTYPE FastForward( /* [in] */ BOOL viewTape, /* [out] */ BMDDeckControlError *error) = 0; virtual HRESULT STDMETHODCALLTYPE Rewind( /* [in] */ BOOL viewTape, /* [out] */ BMDDeckControlError *error) = 0; virtual HRESULT STDMETHODCALLTYPE StepForward( /* [out] */ BMDDeckControlError *error) = 0; virtual HRESULT STDMETHODCALLTYPE StepBack( /* [out] */ BMDDeckControlError *error) = 0; virtual HRESULT STDMETHODCALLTYPE Jog( /* [in] */ double rate, /* [out] */ BMDDeckControlError *error) = 0; virtual HRESULT STDMETHODCALLTYPE Shuttle( /* [in] */ double rate, /* [out] */ BMDDeckControlError *error) = 0; virtual HRESULT STDMETHODCALLTYPE GetTimecodeString( /* [out] */ BSTR *currentTimeCode, /* [out] */ BMDDeckControlError *error) = 0; virtual HRESULT STDMETHODCALLTYPE GetTimecode( /* [out] */ IDeckLinkTimecode **currentTimecode, /* [out] */ BMDDeckControlError *error) = 0; virtual HRESULT STDMETHODCALLTYPE GetTimecodeBCD( /* [out] */ BMDTimecodeBCD *currentTimecode, /* [out] */ BMDDeckControlError *error) = 0; virtual HRESULT STDMETHODCALLTYPE SetPreroll( /* [in] */ unsigned long prerollSeconds) = 0; virtual HRESULT STDMETHODCALLTYPE GetPreroll( /* [out] */ unsigned long *prerollSeconds) = 0; virtual HRESULT STDMETHODCALLTYPE SetExportOffset( /* [in] */ long exportOffsetFields) = 0; virtual HRESULT STDMETHODCALLTYPE GetExportOffset( /* [out] */ long *exportOffsetFields) = 0; virtual HRESULT STDMETHODCALLTYPE GetManualExportOffset( /* [out] */ long *deckManualExportOffsetFields) = 0; virtual HRESULT STDMETHODCALLTYPE SetCaptureOffset( /* [in] */ long captureOffsetFields) = 0; virtual HRESULT STDMETHODCALLTYPE GetCaptureOffset( /* [out] */ long *captureOffsetFields) = 0; virtual HRESULT STDMETHODCALLTYPE StartExport( /* [in] */ BMDTimecodeBCD inTimecode, /* [in] */ BMDTimecodeBCD outTimecode, /* [in] */ BMDDeckControlExportModeOpsFlags exportModeOps, /* [out] */ BMDDeckControlError *error) = 0; virtual HRESULT STDMETHODCALLTYPE StartCapture( /* [in] */ BOOL useVITC, /* [in] */ BMDTimecodeBCD inTimecode, /* [in] */ BMDTimecodeBCD outTimecode, /* [out] */ BMDDeckControlError *error) = 0; virtual HRESULT STDMETHODCALLTYPE GetDeviceID( /* [out] */ unsigned short *deviceId, /* [out] */ BMDDeckControlError *error) = 0; virtual HRESULT STDMETHODCALLTYPE Abort( void) = 0; virtual HRESULT STDMETHODCALLTYPE CrashRecordStart( /* [out] */ BMDDeckControlError *error) = 0; virtual HRESULT STDMETHODCALLTYPE CrashRecordStop( /* [out] */ BMDDeckControlError *error) = 0; virtual HRESULT STDMETHODCALLTYPE SetCallback( /* [in] */ IDeckLinkDeckControlStatusCallback *callback) = 0; }; #else /* C style interface */ typedef struct IDeckLinkDeckControlVtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IDeckLinkDeckControl * This, /* [in] */ REFIID riid, /* [annotation][iid_is][out] */ __RPC__deref_out void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IDeckLinkDeckControl * This); ULONG ( STDMETHODCALLTYPE *Release )( IDeckLinkDeckControl * This); HRESULT ( STDMETHODCALLTYPE *Open )( IDeckLinkDeckControl * This, /* [in] */ BMDTimeScale timeScale, /* [in] */ BMDTimeValue timeValue, /* [in] */ BOOL timecodeIsDropFrame, /* [out] */ BMDDeckControlError *error); HRESULT ( STDMETHODCALLTYPE *Close )( IDeckLinkDeckControl * This, /* [in] */ BOOL standbyOn); HRESULT ( STDMETHODCALLTYPE *GetCurrentState )( IDeckLinkDeckControl * This, /* [out] */ BMDDeckControlMode *mode, /* [out] */ BMDDeckControlVTRControlState *vtrControlState, /* [out] */ BMDDeckControlStatusFlags *flags); HRESULT ( STDMETHODCALLTYPE *SetStandby )( IDeckLinkDeckControl * This, /* [in] */ BOOL standbyOn); HRESULT ( STDMETHODCALLTYPE *Play )( IDeckLinkDeckControl * This, /* [out] */ BMDDeckControlError *error); HRESULT ( STDMETHODCALLTYPE *Stop )( IDeckLinkDeckControl * This, /* [out] */ BMDDeckControlError *error); HRESULT ( STDMETHODCALLTYPE *TogglePlayStop )( IDeckLinkDeckControl * This, /* [out] */ BMDDeckControlError *error); HRESULT ( STDMETHODCALLTYPE *Eject )( IDeckLinkDeckControl * This, /* [out] */ BMDDeckControlError *error); HRESULT ( STDMETHODCALLTYPE *GoToTimecode )( IDeckLinkDeckControl * This, /* [in] */ BMDTimecodeBCD timecode, /* [out] */ BMDDeckControlError *error); HRESULT ( STDMETHODCALLTYPE *FastForward )( IDeckLinkDeckControl * This, /* [in] */ BOOL viewTape, /* [out] */ BMDDeckControlError *error); HRESULT ( STDMETHODCALLTYPE *Rewind )( IDeckLinkDeckControl * This, /* [in] */ BOOL viewTape, /* [out] */ BMDDeckControlError *error); HRESULT ( STDMETHODCALLTYPE *StepForward )( IDeckLinkDeckControl * This, /* [out] */ BMDDeckControlError *error); HRESULT ( STDMETHODCALLTYPE *StepBack )( IDeckLinkDeckControl * This, /* [out] */ BMDDeckControlError *error); HRESULT ( STDMETHODCALLTYPE *Jog )( IDeckLinkDeckControl * This, /* [in] */ double rate, /* [out] */ BMDDeckControlError *error); HRESULT ( STDMETHODCALLTYPE *Shuttle )( IDeckLinkDeckControl * This, /* [in] */ double rate, /* [out] */ BMDDeckControlError *error); HRESULT ( STDMETHODCALLTYPE *GetTimecodeString )( IDeckLinkDeckControl * This, /* [out] */ BSTR *currentTimeCode, /* [out] */ BMDDeckControlError *error); HRESULT ( STDMETHODCALLTYPE *GetTimecode )( IDeckLinkDeckControl * This, /* [out] */ IDeckLinkTimecode **currentTimecode, /* [out] */ BMDDeckControlError *error); HRESULT ( STDMETHODCALLTYPE *GetTimecodeBCD )( IDeckLinkDeckControl * This, /* [out] */ BMDTimecodeBCD *currentTimecode, /* [out] */ BMDDeckControlError *error); HRESULT ( STDMETHODCALLTYPE *SetPreroll )( IDeckLinkDeckControl * This, /* [in] */ unsigned long prerollSeconds); HRESULT ( STDMETHODCALLTYPE *GetPreroll )( IDeckLinkDeckControl * This, /* [out] */ unsigned long *prerollSeconds); HRESULT ( STDMETHODCALLTYPE *SetExportOffset )( IDeckLinkDeckControl * This, /* [in] */ long exportOffsetFields); HRESULT ( STDMETHODCALLTYPE *GetExportOffset )( IDeckLinkDeckControl * This, /* [out] */ long *exportOffsetFields); HRESULT ( STDMETHODCALLTYPE *GetManualExportOffset )( IDeckLinkDeckControl * This, /* [out] */ long *deckManualExportOffsetFields); HRESULT ( STDMETHODCALLTYPE *SetCaptureOffset )( IDeckLinkDeckControl * This, /* [in] */ long captureOffsetFields); HRESULT ( STDMETHODCALLTYPE *GetCaptureOffset )( IDeckLinkDeckControl * This, /* [out] */ long *captureOffsetFields); HRESULT ( STDMETHODCALLTYPE *StartExport )( IDeckLinkDeckControl * This, /* [in] */ BMDTimecodeBCD inTimecode, /* [in] */ BMDTimecodeBCD outTimecode, /* [in] */ BMDDeckControlExportModeOpsFlags exportModeOps, /* [out] */ BMDDeckControlError *error); HRESULT ( STDMETHODCALLTYPE *StartCapture )( IDeckLinkDeckControl * This, /* [in] */ BOOL useVITC, /* [in] */ BMDTimecodeBCD inTimecode, /* [in] */ BMDTimecodeBCD outTimecode, /* [out] */ BMDDeckControlError *error); HRESULT ( STDMETHODCALLTYPE *GetDeviceID )( IDeckLinkDeckControl * This, /* [out] */ unsigned short *deviceId, /* [out] */ BMDDeckControlError *error); HRESULT ( STDMETHODCALLTYPE *Abort )( IDeckLinkDeckControl * This); HRESULT ( STDMETHODCALLTYPE *CrashRecordStart )( IDeckLinkDeckControl * This, /* [out] */ BMDDeckControlError *error); HRESULT ( STDMETHODCALLTYPE *CrashRecordStop )( IDeckLinkDeckControl * This, /* [out] */ BMDDeckControlError *error); HRESULT ( STDMETHODCALLTYPE *SetCallback )( IDeckLinkDeckControl * This, /* [in] */ IDeckLinkDeckControlStatusCallback *callback); END_INTERFACE } IDeckLinkDeckControlVtbl; interface IDeckLinkDeckControl { CONST_VTBL struct IDeckLinkDeckControlVtbl *lpVtbl; }; #ifdef COBJMACROS #define IDeckLinkDeckControl_QueryInterface(This,riid,ppvObject) \ ( (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) ) #define IDeckLinkDeckControl_AddRef(This) \ ( (This)->lpVtbl -> AddRef(This) ) #define IDeckLinkDeckControl_Release(This) \ ( (This)->lpVtbl -> Release(This) ) #define IDeckLinkDeckControl_Open(This,timeScale,timeValue,timecodeIsDropFrame,error) \ ( (This)->lpVtbl -> Open(This,timeScale,timeValue,timecodeIsDropFrame,error) ) #define IDeckLinkDeckControl_Close(This,standbyOn) \ ( (This)->lpVtbl -> Close(This,standbyOn) ) #define IDeckLinkDeckControl_GetCurrentState(This,mode,vtrControlState,flags) \ ( (This)->lpVtbl -> GetCurrentState(This,mode,vtrControlState,flags) ) #define IDeckLinkDeckControl_SetStandby(This,standbyOn) \ ( (This)->lpVtbl -> SetStandby(This,standbyOn) ) #define IDeckLinkDeckControl_Play(This,error) \ ( (This)->lpVtbl -> Play(This,error) ) #define IDeckLinkDeckControl_Stop(This,error) \ ( (This)->lpVtbl -> Stop(This,error) ) #define IDeckLinkDeckControl_TogglePlayStop(This,error) \ ( (This)->lpVtbl -> TogglePlayStop(This,error) ) #define IDeckLinkDeckControl_Eject(This,error) \ ( (This)->lpVtbl -> Eject(This,error) ) #define IDeckLinkDeckControl_GoToTimecode(This,timecode,error) \ ( (This)->lpVtbl -> GoToTimecode(This,timecode,error) ) #define IDeckLinkDeckControl_FastForward(This,viewTape,error) \ ( (This)->lpVtbl -> FastForward(This,viewTape,error) ) #define IDeckLinkDeckControl_Rewind(This,viewTape,error) \ ( (This)->lpVtbl -> Rewind(This,viewTape,error) ) #define IDeckLinkDeckControl_StepForward(This,error) \ ( (This)->lpVtbl -> StepForward(This,error) ) #define IDeckLinkDeckControl_StepBack(This,error) \ ( (This)->lpVtbl -> StepBack(This,error) ) #define IDeckLinkDeckControl_Jog(This,rate,error) \ ( (This)->lpVtbl -> Jog(This,rate,error) ) #define IDeckLinkDeckControl_Shuttle(This,rate,error) \ ( (This)->lpVtbl -> Shuttle(This,rate,error) ) #define IDeckLinkDeckControl_GetTimecodeString(This,currentTimeCode,error) \ ( (This)->lpVtbl -> GetTimecodeString(This,currentTimeCode,error) ) #define IDeckLinkDeckControl_GetTimecode(This,currentTimecode,error) \ ( (This)->lpVtbl -> GetTimecode(This,currentTimecode,error) ) #define IDeckLinkDeckControl_GetTimecodeBCD(This,currentTimecode,error) \ ( (This)->lpVtbl -> GetTimecodeBCD(This,currentTimecode,error) ) #define IDeckLinkDeckControl_SetPreroll(This,prerollSeconds) \ ( (This)->lpVtbl -> SetPreroll(This,prerollSeconds) ) #define IDeckLinkDeckControl_GetPreroll(This,prerollSeconds) \ ( (This)->lpVtbl -> GetPreroll(This,prerollSeconds) ) #define IDeckLinkDeckControl_SetExportOffset(This,exportOffsetFields) \ ( (This)->lpVtbl -> SetExportOffset(This,exportOffsetFields) ) #define IDeckLinkDeckControl_GetExportOffset(This,exportOffsetFields) \ ( (This)->lpVtbl -> GetExportOffset(This,exportOffsetFields) ) #define IDeckLinkDeckControl_GetManualExportOffset(This,deckManualExportOffsetFields) \ ( (This)->lpVtbl -> GetManualExportOffset(This,deckManualExportOffsetFields) ) #define IDeckLinkDeckControl_SetCaptureOffset(This,captureOffsetFields) \ ( (This)->lpVtbl -> SetCaptureOffset(This,captureOffsetFields) ) #define IDeckLinkDeckControl_GetCaptureOffset(This,captureOffsetFields) \ ( (This)->lpVtbl -> GetCaptureOffset(This,captureOffsetFields) ) #define IDeckLinkDeckControl_StartExport(This,inTimecode,outTimecode,exportModeOps,error) \ ( (This)->lpVtbl -> StartExport(This,inTimecode,outTimecode,exportModeOps,error) ) #define IDeckLinkDeckControl_StartCapture(This,useVITC,inTimecode,outTimecode,error) \ ( (This)->lpVtbl -> StartCapture(This,useVITC,inTimecode,outTimecode,error) ) #define IDeckLinkDeckControl_GetDeviceID(This,deviceId,error) \ ( (This)->lpVtbl -> GetDeviceID(This,deviceId,error) ) #define IDeckLinkDeckControl_Abort(This) \ ( (This)->lpVtbl -> Abort(This) ) #define IDeckLinkDeckControl_CrashRecordStart(This,error) \ ( (This)->lpVtbl -> CrashRecordStart(This,error) ) #define IDeckLinkDeckControl_CrashRecordStop(This,error) \ ( (This)->lpVtbl -> CrashRecordStop(This,error) ) #define IDeckLinkDeckControl_SetCallback(This,callback) \ ( (This)->lpVtbl -> SetCallback(This,callback) ) #endif /* COBJMACROS */ #endif /* C style interface */ #endif /* __IDeckLinkDeckControl_INTERFACE_DEFINED__ */ EXTERN_C const CLSID CLSID_CDeckLinkIterator; #ifdef __cplusplus class DECLSPEC_UUID("D9EDA3B3-2887-41FA-B724-017CF1EB1D37") CDeckLinkIterator; #endif EXTERN_C const CLSID CLSID_CDeckLinkGLScreenPreviewHelper; #ifdef __cplusplus class DECLSPEC_UUID("F63E77C7-B655-4A4A-9AD0-3CA85D394343") CDeckLinkGLScreenPreviewHelper; #endif EXTERN_C const CLSID CLSID_CDeckLinkVideoConversion; #ifdef __cplusplus class DECLSPEC_UUID("7DBBBB11-5B7B-467D-AEA4-CEA468FD368C") CDeckLinkVideoConversion; #endif #ifndef __IDeckLinkDisplayModeIterator_v7_6_INTERFACE_DEFINED__ #define __IDeckLinkDisplayModeIterator_v7_6_INTERFACE_DEFINED__ /* interface IDeckLinkDisplayModeIterator_v7_6 */ /* [helpstring][uuid][object] */ EXTERN_C const IID IID_IDeckLinkDisplayModeIterator_v7_6; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("455D741F-1779-4800-86F5-0B5D13D79751") IDeckLinkDisplayModeIterator_v7_6 : public IUnknown { public: virtual HRESULT STDMETHODCALLTYPE Next( /* [out] */ IDeckLinkDisplayMode_v7_6 **deckLinkDisplayMode) = 0; }; #else /* C style interface */ typedef struct IDeckLinkDisplayModeIterator_v7_6Vtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IDeckLinkDisplayModeIterator_v7_6 * This, /* [in] */ REFIID riid, /* [annotation][iid_is][out] */ __RPC__deref_out void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IDeckLinkDisplayModeIterator_v7_6 * This); ULONG ( STDMETHODCALLTYPE *Release )( IDeckLinkDisplayModeIterator_v7_6 * This); HRESULT ( STDMETHODCALLTYPE *Next )( IDeckLinkDisplayModeIterator_v7_6 * This, /* [out] */ IDeckLinkDisplayMode_v7_6 **deckLinkDisplayMode); END_INTERFACE } IDeckLinkDisplayModeIterator_v7_6Vtbl; interface IDeckLinkDisplayModeIterator_v7_6 { CONST_VTBL struct IDeckLinkDisplayModeIterator_v7_6Vtbl *lpVtbl; }; #ifdef COBJMACROS #define IDeckLinkDisplayModeIterator_v7_6_QueryInterface(This,riid,ppvObject) \ ( (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) ) #define IDeckLinkDisplayModeIterator_v7_6_AddRef(This) \ ( (This)->lpVtbl -> AddRef(This) ) #define IDeckLinkDisplayModeIterator_v7_6_Release(This) \ ( (This)->lpVtbl -> Release(This) ) #define IDeckLinkDisplayModeIterator_v7_6_Next(This,deckLinkDisplayMode) \ ( (This)->lpVtbl -> Next(This,deckLinkDisplayMode) ) #endif /* COBJMACROS */ #endif /* C style interface */ #endif /* __IDeckLinkDisplayModeIterator_v7_6_INTERFACE_DEFINED__ */ #ifndef __IDeckLinkDisplayMode_v7_6_INTERFACE_DEFINED__ #define __IDeckLinkDisplayMode_v7_6_INTERFACE_DEFINED__ /* interface IDeckLinkDisplayMode_v7_6 */ /* [helpstring][uuid][object] */ EXTERN_C const IID IID_IDeckLinkDisplayMode_v7_6; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("87451E84-2B7E-439E-A629-4393EA4A8550") IDeckLinkDisplayMode_v7_6 : public IUnknown { public: virtual HRESULT STDMETHODCALLTYPE GetName( /* [out] */ BSTR *name) = 0; virtual BMDDisplayMode STDMETHODCALLTYPE GetDisplayMode( void) = 0; virtual long STDMETHODCALLTYPE GetWidth( void) = 0; virtual long STDMETHODCALLTYPE GetHeight( void) = 0; virtual HRESULT STDMETHODCALLTYPE GetFrameRate( /* [out] */ BMDTimeValue *frameDuration, /* [out] */ BMDTimeScale *timeScale) = 0; virtual BMDFieldDominance STDMETHODCALLTYPE GetFieldDominance( void) = 0; }; #else /* C style interface */ typedef struct IDeckLinkDisplayMode_v7_6Vtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IDeckLinkDisplayMode_v7_6 * This, /* [in] */ REFIID riid, /* [annotation][iid_is][out] */ __RPC__deref_out void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IDeckLinkDisplayMode_v7_6 * This); ULONG ( STDMETHODCALLTYPE *Release )( IDeckLinkDisplayMode_v7_6 * This); HRESULT ( STDMETHODCALLTYPE *GetName )( IDeckLinkDisplayMode_v7_6 * This, /* [out] */ BSTR *name); BMDDisplayMode ( STDMETHODCALLTYPE *GetDisplayMode )( IDeckLinkDisplayMode_v7_6 * This); long ( STDMETHODCALLTYPE *GetWidth )( IDeckLinkDisplayMode_v7_6 * This); long ( STDMETHODCALLTYPE *GetHeight )( IDeckLinkDisplayMode_v7_6 * This); HRESULT ( STDMETHODCALLTYPE *GetFrameRate )( IDeckLinkDisplayMode_v7_6 * This, /* [out] */ BMDTimeValue *frameDuration, /* [out] */ BMDTimeScale *timeScale); BMDFieldDominance ( STDMETHODCALLTYPE *GetFieldDominance )( IDeckLinkDisplayMode_v7_6 * This); END_INTERFACE } IDeckLinkDisplayMode_v7_6Vtbl; interface IDeckLinkDisplayMode_v7_6 { CONST_VTBL struct IDeckLinkDisplayMode_v7_6Vtbl *lpVtbl; }; #ifdef COBJMACROS #define IDeckLinkDisplayMode_v7_6_QueryInterface(This,riid,ppvObject) \ ( (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) ) #define IDeckLinkDisplayMode_v7_6_AddRef(This) \ ( (This)->lpVtbl -> AddRef(This) ) #define IDeckLinkDisplayMode_v7_6_Release(This) \ ( (This)->lpVtbl -> Release(This) ) #define IDeckLinkDisplayMode_v7_6_GetName(This,name) \ ( (This)->lpVtbl -> GetName(This,name) ) #define IDeckLinkDisplayMode_v7_6_GetDisplayMode(This) \ ( (This)->lpVtbl -> GetDisplayMode(This) ) #define IDeckLinkDisplayMode_v7_6_GetWidth(This) \ ( (This)->lpVtbl -> GetWidth(This) ) #define IDeckLinkDisplayMode_v7_6_GetHeight(This) \ ( (This)->lpVtbl -> GetHeight(This) ) #define IDeckLinkDisplayMode_v7_6_GetFrameRate(This,frameDuration,timeScale) \ ( (This)->lpVtbl -> GetFrameRate(This,frameDuration,timeScale) ) #define IDeckLinkDisplayMode_v7_6_GetFieldDominance(This) \ ( (This)->lpVtbl -> GetFieldDominance(This) ) #endif /* COBJMACROS */ #endif /* C style interface */ #endif /* __IDeckLinkDisplayMode_v7_6_INTERFACE_DEFINED__ */ #ifndef __IDeckLinkOutput_v7_6_INTERFACE_DEFINED__ #define __IDeckLinkOutput_v7_6_INTERFACE_DEFINED__ /* interface IDeckLinkOutput_v7_6 */ /* [helpstring][local][uuid][object] */ EXTERN_C const IID IID_IDeckLinkOutput_v7_6; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("29228142-EB8C-4141-A621-F74026450955") IDeckLinkOutput_v7_6 : public IUnknown { public: virtual HRESULT STDMETHODCALLTYPE DoesSupportVideoMode( BMDDisplayMode displayMode, BMDPixelFormat pixelFormat, /* [out] */ BMDDisplayModeSupport *result) = 0; virtual HRESULT STDMETHODCALLTYPE GetDisplayModeIterator( /* [out] */ IDeckLinkDisplayModeIterator_v7_6 **iterator) = 0; virtual HRESULT STDMETHODCALLTYPE SetScreenPreviewCallback( /* [in] */ IDeckLinkScreenPreviewCallback_v7_6 *previewCallback) = 0; virtual HRESULT STDMETHODCALLTYPE EnableVideoOutput( BMDDisplayMode displayMode, BMDVideoOutputFlags flags) = 0; virtual HRESULT STDMETHODCALLTYPE DisableVideoOutput( void) = 0; virtual HRESULT STDMETHODCALLTYPE SetVideoOutputFrameMemoryAllocator( /* [in] */ IDeckLinkMemoryAllocator *theAllocator) = 0; virtual HRESULT STDMETHODCALLTYPE CreateVideoFrame( long width, long height, long rowBytes, BMDPixelFormat pixelFormat, BMDFrameFlags flags, /* [out] */ IDeckLinkMutableVideoFrame_v7_6 **outFrame) = 0; virtual HRESULT STDMETHODCALLTYPE CreateAncillaryData( BMDPixelFormat pixelFormat, /* [out] */ IDeckLinkVideoFrameAncillary **outBuffer) = 0; virtual HRESULT STDMETHODCALLTYPE DisplayVideoFrameSync( /* [in] */ IDeckLinkVideoFrame_v7_6 *theFrame) = 0; virtual HRESULT STDMETHODCALLTYPE ScheduleVideoFrame( /* [in] */ IDeckLinkVideoFrame_v7_6 *theFrame, BMDTimeValue displayTime, BMDTimeValue displayDuration, BMDTimeScale timeScale) = 0; virtual HRESULT STDMETHODCALLTYPE SetScheduledFrameCompletionCallback( /* [in] */ IDeckLinkVideoOutputCallback_v7_6 *theCallback) = 0; virtual HRESULT STDMETHODCALLTYPE GetBufferedVideoFrameCount( /* [out] */ unsigned long *bufferedFrameCount) = 0; virtual HRESULT STDMETHODCALLTYPE EnableAudioOutput( BMDAudioSampleRate sampleRate, BMDAudioSampleType sampleType, unsigned long channelCount, BMDAudioOutputStreamType streamType) = 0; virtual HRESULT STDMETHODCALLTYPE DisableAudioOutput( void) = 0; virtual HRESULT STDMETHODCALLTYPE WriteAudioSamplesSync( /* [in] */ void *buffer, unsigned long sampleFrameCount, /* [out] */ unsigned long *sampleFramesWritten) = 0; virtual HRESULT STDMETHODCALLTYPE BeginAudioPreroll( void) = 0; virtual HRESULT STDMETHODCALLTYPE EndAudioPreroll( void) = 0; virtual HRESULT STDMETHODCALLTYPE ScheduleAudioSamples( /* [in] */ void *buffer, unsigned long sampleFrameCount, BMDTimeValue streamTime, BMDTimeScale timeScale, /* [out] */ unsigned long *sampleFramesWritten) = 0; virtual HRESULT STDMETHODCALLTYPE GetBufferedAudioSampleFrameCount( /* [out] */ unsigned long *bufferedSampleFrameCount) = 0; virtual HRESULT STDMETHODCALLTYPE FlushBufferedAudioSamples( void) = 0; virtual HRESULT STDMETHODCALLTYPE SetAudioCallback( /* [in] */ IDeckLinkAudioOutputCallback *theCallback) = 0; virtual HRESULT STDMETHODCALLTYPE StartScheduledPlayback( BMDTimeValue playbackStartTime, BMDTimeScale timeScale, double playbackSpeed) = 0; virtual HRESULT STDMETHODCALLTYPE StopScheduledPlayback( BMDTimeValue stopPlaybackAtTime, /* [out] */ BMDTimeValue *actualStopTime, BMDTimeScale timeScale) = 0; virtual HRESULT STDMETHODCALLTYPE IsScheduledPlaybackRunning( /* [out] */ BOOL *active) = 0; virtual HRESULT STDMETHODCALLTYPE GetScheduledStreamTime( BMDTimeScale desiredTimeScale, /* [out] */ BMDTimeValue *streamTime, /* [out] */ double *playbackSpeed) = 0; virtual HRESULT STDMETHODCALLTYPE GetHardwareReferenceClock( BMDTimeScale desiredTimeScale, /* [out] */ BMDTimeValue *hardwareTime, /* [out] */ BMDTimeValue *timeInFrame, /* [out] */ BMDTimeValue *ticksPerFrame) = 0; }; #else /* C style interface */ typedef struct IDeckLinkOutput_v7_6Vtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IDeckLinkOutput_v7_6 * This, /* [in] */ REFIID riid, /* [annotation][iid_is][out] */ __RPC__deref_out void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IDeckLinkOutput_v7_6 * This); ULONG ( STDMETHODCALLTYPE *Release )( IDeckLinkOutput_v7_6 * This); HRESULT ( STDMETHODCALLTYPE *DoesSupportVideoMode )( IDeckLinkOutput_v7_6 * This, BMDDisplayMode displayMode, BMDPixelFormat pixelFormat, /* [out] */ BMDDisplayModeSupport *result); HRESULT ( STDMETHODCALLTYPE *GetDisplayModeIterator )( IDeckLinkOutput_v7_6 * This, /* [out] */ IDeckLinkDisplayModeIterator_v7_6 **iterator); HRESULT ( STDMETHODCALLTYPE *SetScreenPreviewCallback )( IDeckLinkOutput_v7_6 * This, /* [in] */ IDeckLinkScreenPreviewCallback_v7_6 *previewCallback); HRESULT ( STDMETHODCALLTYPE *EnableVideoOutput )( IDeckLinkOutput_v7_6 * This, BMDDisplayMode displayMode, BMDVideoOutputFlags flags); HRESULT ( STDMETHODCALLTYPE *DisableVideoOutput )( IDeckLinkOutput_v7_6 * This); HRESULT ( STDMETHODCALLTYPE *SetVideoOutputFrameMemoryAllocator )( IDeckLinkOutput_v7_6 * This, /* [in] */ IDeckLinkMemoryAllocator *theAllocator); HRESULT ( STDMETHODCALLTYPE *CreateVideoFrame )( IDeckLinkOutput_v7_6 * This, long width, long height, long rowBytes, BMDPixelFormat pixelFormat, BMDFrameFlags flags, /* [out] */ IDeckLinkMutableVideoFrame_v7_6 **outFrame); HRESULT ( STDMETHODCALLTYPE *CreateAncillaryData )( IDeckLinkOutput_v7_6 * This, BMDPixelFormat pixelFormat, /* [out] */ IDeckLinkVideoFrameAncillary **outBuffer); HRESULT ( STDMETHODCALLTYPE *DisplayVideoFrameSync )( IDeckLinkOutput_v7_6 * This, /* [in] */ IDeckLinkVideoFrame_v7_6 *theFrame); HRESULT ( STDMETHODCALLTYPE *ScheduleVideoFrame )( IDeckLinkOutput_v7_6 * This, /* [in] */ IDeckLinkVideoFrame_v7_6 *theFrame, BMDTimeValue displayTime, BMDTimeValue displayDuration, BMDTimeScale timeScale); HRESULT ( STDMETHODCALLTYPE *SetScheduledFrameCompletionCallback )( IDeckLinkOutput_v7_6 * This, /* [in] */ IDeckLinkVideoOutputCallback_v7_6 *theCallback); HRESULT ( STDMETHODCALLTYPE *GetBufferedVideoFrameCount )( IDeckLinkOutput_v7_6 * This, /* [out] */ unsigned long *bufferedFrameCount); HRESULT ( STDMETHODCALLTYPE *EnableAudioOutput )( IDeckLinkOutput_v7_6 * This, BMDAudioSampleRate sampleRate, BMDAudioSampleType sampleType, unsigned long channelCount, BMDAudioOutputStreamType streamType); HRESULT ( STDMETHODCALLTYPE *DisableAudioOutput )( IDeckLinkOutput_v7_6 * This); HRESULT ( STDMETHODCALLTYPE *WriteAudioSamplesSync )( IDeckLinkOutput_v7_6 * This, /* [in] */ void *buffer, unsigned long sampleFrameCount, /* [out] */ unsigned long *sampleFramesWritten); HRESULT ( STDMETHODCALLTYPE *BeginAudioPreroll )( IDeckLinkOutput_v7_6 * This); HRESULT ( STDMETHODCALLTYPE *EndAudioPreroll )( IDeckLinkOutput_v7_6 * This); HRESULT ( STDMETHODCALLTYPE *ScheduleAudioSamples )( IDeckLinkOutput_v7_6 * This, /* [in] */ void *buffer, unsigned long sampleFrameCount, BMDTimeValue streamTime, BMDTimeScale timeScale, /* [out] */ unsigned long *sampleFramesWritten); HRESULT ( STDMETHODCALLTYPE *GetBufferedAudioSampleFrameCount )( IDeckLinkOutput_v7_6 * This, /* [out] */ unsigned long *bufferedSampleFrameCount); HRESULT ( STDMETHODCALLTYPE *FlushBufferedAudioSamples )( IDeckLinkOutput_v7_6 * This); HRESULT ( STDMETHODCALLTYPE *SetAudioCallback )( IDeckLinkOutput_v7_6 * This, /* [in] */ IDeckLinkAudioOutputCallback *theCallback); HRESULT ( STDMETHODCALLTYPE *StartScheduledPlayback )( IDeckLinkOutput_v7_6 * This, BMDTimeValue playbackStartTime, BMDTimeScale timeScale, double playbackSpeed); HRESULT ( STDMETHODCALLTYPE *StopScheduledPlayback )( IDeckLinkOutput_v7_6 * This, BMDTimeValue stopPlaybackAtTime, /* [out] */ BMDTimeValue *actualStopTime, BMDTimeScale timeScale); HRESULT ( STDMETHODCALLTYPE *IsScheduledPlaybackRunning )( IDeckLinkOutput_v7_6 * This, /* [out] */ BOOL *active); HRESULT ( STDMETHODCALLTYPE *GetScheduledStreamTime )( IDeckLinkOutput_v7_6 * This, BMDTimeScale desiredTimeScale, /* [out] */ BMDTimeValue *streamTime, /* [out] */ double *playbackSpeed); HRESULT ( STDMETHODCALLTYPE *GetHardwareReferenceClock )( IDeckLinkOutput_v7_6 * This, BMDTimeScale desiredTimeScale, /* [out] */ BMDTimeValue *hardwareTime, /* [out] */ BMDTimeValue *timeInFrame, /* [out] */ BMDTimeValue *ticksPerFrame); END_INTERFACE } IDeckLinkOutput_v7_6Vtbl; interface IDeckLinkOutput_v7_6 { CONST_VTBL struct IDeckLinkOutput_v7_6Vtbl *lpVtbl; }; #ifdef COBJMACROS #define IDeckLinkOutput_v7_6_QueryInterface(This,riid,ppvObject) \ ( (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) ) #define IDeckLinkOutput_v7_6_AddRef(This) \ ( (This)->lpVtbl -> AddRef(This) ) #define IDeckLinkOutput_v7_6_Release(This) \ ( (This)->lpVtbl -> Release(This) ) #define IDeckLinkOutput_v7_6_DoesSupportVideoMode(This,displayMode,pixelFormat,result) \ ( (This)->lpVtbl -> DoesSupportVideoMode(This,displayMode,pixelFormat,result) ) #define IDeckLinkOutput_v7_6_GetDisplayModeIterator(This,iterator) \ ( (This)->lpVtbl -> GetDisplayModeIterator(This,iterator) ) #define IDeckLinkOutput_v7_6_SetScreenPreviewCallback(This,previewCallback) \ ( (This)->lpVtbl -> SetScreenPreviewCallback(This,previewCallback) ) #define IDeckLinkOutput_v7_6_EnableVideoOutput(This,displayMode,flags) \ ( (This)->lpVtbl -> EnableVideoOutput(This,displayMode,flags) ) #define IDeckLinkOutput_v7_6_DisableVideoOutput(This) \ ( (This)->lpVtbl -> DisableVideoOutput(This) ) #define IDeckLinkOutput_v7_6_SetVideoOutputFrameMemoryAllocator(This,theAllocator) \ ( (This)->lpVtbl -> SetVideoOutputFrameMemoryAllocator(This,theAllocator) ) #define IDeckLinkOutput_v7_6_CreateVideoFrame(This,width,height,rowBytes,pixelFormat,flags,outFrame) \ ( (This)->lpVtbl -> CreateVideoFrame(This,width,height,rowBytes,pixelFormat,flags,outFrame) ) #define IDeckLinkOutput_v7_6_CreateAncillaryData(This,pixelFormat,outBuffer) \ ( (This)->lpVtbl -> CreateAncillaryData(This,pixelFormat,outBuffer) ) #define IDeckLinkOutput_v7_6_DisplayVideoFrameSync(This,theFrame) \ ( (This)->lpVtbl -> DisplayVideoFrameSync(This,theFrame) ) #define IDeckLinkOutput_v7_6_ScheduleVideoFrame(This,theFrame,displayTime,displayDuration,timeScale) \ ( (This)->lpVtbl -> ScheduleVideoFrame(This,theFrame,displayTime,displayDuration,timeScale) ) #define IDeckLinkOutput_v7_6_SetScheduledFrameCompletionCallback(This,theCallback) \ ( (This)->lpVtbl -> SetScheduledFrameCompletionCallback(This,theCallback) ) #define IDeckLinkOutput_v7_6_GetBufferedVideoFrameCount(This,bufferedFrameCount) \ ( (This)->lpVtbl -> GetBufferedVideoFrameCount(This,bufferedFrameCount) ) #define IDeckLinkOutput_v7_6_EnableAudioOutput(This,sampleRate,sampleType,channelCount,streamType) \ ( (This)->lpVtbl -> EnableAudioOutput(This,sampleRate,sampleType,channelCount,streamType) ) #define IDeckLinkOutput_v7_6_DisableAudioOutput(This) \ ( (This)->lpVtbl -> DisableAudioOutput(This) ) #define IDeckLinkOutput_v7_6_WriteAudioSamplesSync(This,buffer,sampleFrameCount,sampleFramesWritten) \ ( (This)->lpVtbl -> WriteAudioSamplesSync(This,buffer,sampleFrameCount,sampleFramesWritten) ) #define IDeckLinkOutput_v7_6_BeginAudioPreroll(This) \ ( (This)->lpVtbl -> BeginAudioPreroll(This) ) #define IDeckLinkOutput_v7_6_EndAudioPreroll(This) \ ( (This)->lpVtbl -> EndAudioPreroll(This) ) #define IDeckLinkOutput_v7_6_ScheduleAudioSamples(This,buffer,sampleFrameCount,streamTime,timeScale,sampleFramesWritten) \ ( (This)->lpVtbl -> ScheduleAudioSamples(This,buffer,sampleFrameCount,streamTime,timeScale,sampleFramesWritten) ) #define IDeckLinkOutput_v7_6_GetBufferedAudioSampleFrameCount(This,bufferedSampleFrameCount) \ ( (This)->lpVtbl -> GetBufferedAudioSampleFrameCount(This,bufferedSampleFrameCount) ) #define IDeckLinkOutput_v7_6_FlushBufferedAudioSamples(This) \ ( (This)->lpVtbl -> FlushBufferedAudioSamples(This) ) #define IDeckLinkOutput_v7_6_SetAudioCallback(This,theCallback) \ ( (This)->lpVtbl -> SetAudioCallback(This,theCallback) ) #define IDeckLinkOutput_v7_6_StartScheduledPlayback(This,playbackStartTime,timeScale,playbackSpeed) \ ( (This)->lpVtbl -> StartScheduledPlayback(This,playbackStartTime,timeScale,playbackSpeed) ) #define IDeckLinkOutput_v7_6_StopScheduledPlayback(This,stopPlaybackAtTime,actualStopTime,timeScale) \ ( (This)->lpVtbl -> StopScheduledPlayback(This,stopPlaybackAtTime,actualStopTime,timeScale) ) #define IDeckLinkOutput_v7_6_IsScheduledPlaybackRunning(This,active) \ ( (This)->lpVtbl -> IsScheduledPlaybackRunning(This,active) ) #define IDeckLinkOutput_v7_6_GetScheduledStreamTime(This,desiredTimeScale,streamTime,playbackSpeed) \ ( (This)->lpVtbl -> GetScheduledStreamTime(This,desiredTimeScale,streamTime,playbackSpeed) ) #define IDeckLinkOutput_v7_6_GetHardwareReferenceClock(This,desiredTimeScale,hardwareTime,timeInFrame,ticksPerFrame) \ ( (This)->lpVtbl -> GetHardwareReferenceClock(This,desiredTimeScale,hardwareTime,timeInFrame,ticksPerFrame) ) #endif /* COBJMACROS */ #endif /* C style interface */ #endif /* __IDeckLinkOutput_v7_6_INTERFACE_DEFINED__ */ #ifndef __IDeckLinkInput_v7_6_INTERFACE_DEFINED__ #define __IDeckLinkInput_v7_6_INTERFACE_DEFINED__ /* interface IDeckLinkInput_v7_6 */ /* [helpstring][uuid][object] */ EXTERN_C const IID IID_IDeckLinkInput_v7_6; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("300C135A-9F43-48E2-9906-6D7911D93CF1") IDeckLinkInput_v7_6 : public IUnknown { public: virtual HRESULT STDMETHODCALLTYPE DoesSupportVideoMode( BMDDisplayMode displayMode, BMDPixelFormat pixelFormat, /* [out] */ BMDDisplayModeSupport *result) = 0; virtual HRESULT STDMETHODCALLTYPE GetDisplayModeIterator( /* [out] */ IDeckLinkDisplayModeIterator_v7_6 **iterator) = 0; virtual HRESULT STDMETHODCALLTYPE SetScreenPreviewCallback( /* [in] */ IDeckLinkScreenPreviewCallback_v7_6 *previewCallback) = 0; virtual HRESULT STDMETHODCALLTYPE EnableVideoInput( BMDDisplayMode displayMode, BMDPixelFormat pixelFormat, BMDVideoInputFlags flags) = 0; virtual HRESULT STDMETHODCALLTYPE DisableVideoInput( void) = 0; virtual HRESULT STDMETHODCALLTYPE GetAvailableVideoFrameCount( /* [out] */ unsigned long *availableFrameCount) = 0; virtual HRESULT STDMETHODCALLTYPE EnableAudioInput( BMDAudioSampleRate sampleRate, BMDAudioSampleType sampleType, unsigned long channelCount) = 0; virtual HRESULT STDMETHODCALLTYPE DisableAudioInput( void) = 0; virtual HRESULT STDMETHODCALLTYPE GetAvailableAudioSampleFrameCount( /* [out] */ unsigned long *availableSampleFrameCount) = 0; virtual HRESULT STDMETHODCALLTYPE StartStreams( void) = 0; virtual HRESULT STDMETHODCALLTYPE StopStreams( void) = 0; virtual HRESULT STDMETHODCALLTYPE PauseStreams( void) = 0; virtual HRESULT STDMETHODCALLTYPE FlushStreams( void) = 0; virtual HRESULT STDMETHODCALLTYPE SetCallback( /* [in] */ IDeckLinkInputCallback_v7_6 *theCallback) = 0; virtual HRESULT STDMETHODCALLTYPE GetHardwareReferenceClock( BMDTimeScale desiredTimeScale, /* [out] */ BMDTimeValue *hardwareTime, /* [out] */ BMDTimeValue *timeInFrame, /* [out] */ BMDTimeValue *ticksPerFrame) = 0; }; #else /* C style interface */ typedef struct IDeckLinkInput_v7_6Vtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IDeckLinkInput_v7_6 * This, /* [in] */ REFIID riid, /* [annotation][iid_is][out] */ __RPC__deref_out void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IDeckLinkInput_v7_6 * This); ULONG ( STDMETHODCALLTYPE *Release )( IDeckLinkInput_v7_6 * This); HRESULT ( STDMETHODCALLTYPE *DoesSupportVideoMode )( IDeckLinkInput_v7_6 * This, BMDDisplayMode displayMode, BMDPixelFormat pixelFormat, /* [out] */ BMDDisplayModeSupport *result); HRESULT ( STDMETHODCALLTYPE *GetDisplayModeIterator )( IDeckLinkInput_v7_6 * This, /* [out] */ IDeckLinkDisplayModeIterator_v7_6 **iterator); HRESULT ( STDMETHODCALLTYPE *SetScreenPreviewCallback )( IDeckLinkInput_v7_6 * This, /* [in] */ IDeckLinkScreenPreviewCallback_v7_6 *previewCallback); HRESULT ( STDMETHODCALLTYPE *EnableVideoInput )( IDeckLinkInput_v7_6 * This, BMDDisplayMode displayMode, BMDPixelFormat pixelFormat, BMDVideoInputFlags flags); HRESULT ( STDMETHODCALLTYPE *DisableVideoInput )( IDeckLinkInput_v7_6 * This); HRESULT ( STDMETHODCALLTYPE *GetAvailableVideoFrameCount )( IDeckLinkInput_v7_6 * This, /* [out] */ unsigned long *availableFrameCount); HRESULT ( STDMETHODCALLTYPE *EnableAudioInput )( IDeckLinkInput_v7_6 * This, BMDAudioSampleRate sampleRate, BMDAudioSampleType sampleType, unsigned long channelCount); HRESULT ( STDMETHODCALLTYPE *DisableAudioInput )( IDeckLinkInput_v7_6 * This); HRESULT ( STDMETHODCALLTYPE *GetAvailableAudioSampleFrameCount )( IDeckLinkInput_v7_6 * This, /* [out] */ unsigned long *availableSampleFrameCount); HRESULT ( STDMETHODCALLTYPE *StartStreams )( IDeckLinkInput_v7_6 * This); HRESULT ( STDMETHODCALLTYPE *StopStreams )( IDeckLinkInput_v7_6 * This); HRESULT ( STDMETHODCALLTYPE *PauseStreams )( IDeckLinkInput_v7_6 * This); HRESULT ( STDMETHODCALLTYPE *FlushStreams )( IDeckLinkInput_v7_6 * This); HRESULT ( STDMETHODCALLTYPE *SetCallback )( IDeckLinkInput_v7_6 * This, /* [in] */ IDeckLinkInputCallback_v7_6 *theCallback); HRESULT ( STDMETHODCALLTYPE *GetHardwareReferenceClock )( IDeckLinkInput_v7_6 * This, BMDTimeScale desiredTimeScale, /* [out] */ BMDTimeValue *hardwareTime, /* [out] */ BMDTimeValue *timeInFrame, /* [out] */ BMDTimeValue *ticksPerFrame); END_INTERFACE } IDeckLinkInput_v7_6Vtbl; interface IDeckLinkInput_v7_6 { CONST_VTBL struct IDeckLinkInput_v7_6Vtbl *lpVtbl; }; #ifdef COBJMACROS #define IDeckLinkInput_v7_6_QueryInterface(This,riid,ppvObject) \ ( (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) ) #define IDeckLinkInput_v7_6_AddRef(This) \ ( (This)->lpVtbl -> AddRef(This) ) #define IDeckLinkInput_v7_6_Release(This) \ ( (This)->lpVtbl -> Release(This) ) #define IDeckLinkInput_v7_6_DoesSupportVideoMode(This,displayMode,pixelFormat,result) \ ( (This)->lpVtbl -> DoesSupportVideoMode(This,displayMode,pixelFormat,result) ) #define IDeckLinkInput_v7_6_GetDisplayModeIterator(This,iterator) \ ( (This)->lpVtbl -> GetDisplayModeIterator(This,iterator) ) #define IDeckLinkInput_v7_6_SetScreenPreviewCallback(This,previewCallback) \ ( (This)->lpVtbl -> SetScreenPreviewCallback(This,previewCallback) ) #define IDeckLinkInput_v7_6_EnableVideoInput(This,displayMode,pixelFormat,flags) \ ( (This)->lpVtbl -> EnableVideoInput(This,displayMode,pixelFormat,flags) ) #define IDeckLinkInput_v7_6_DisableVideoInput(This) \ ( (This)->lpVtbl -> DisableVideoInput(This) ) #define IDeckLinkInput_v7_6_GetAvailableVideoFrameCount(This,availableFrameCount) \ ( (This)->lpVtbl -> GetAvailableVideoFrameCount(This,availableFrameCount) ) #define IDeckLinkInput_v7_6_EnableAudioInput(This,sampleRate,sampleType,channelCount) \ ( (This)->lpVtbl -> EnableAudioInput(This,sampleRate,sampleType,channelCount) ) #define IDeckLinkInput_v7_6_DisableAudioInput(This) \ ( (This)->lpVtbl -> DisableAudioInput(This) ) #define IDeckLinkInput_v7_6_GetAvailableAudioSampleFrameCount(This,availableSampleFrameCount) \ ( (This)->lpVtbl -> GetAvailableAudioSampleFrameCount(This,availableSampleFrameCount) ) #define IDeckLinkInput_v7_6_StartStreams(This) \ ( (This)->lpVtbl -> StartStreams(This) ) #define IDeckLinkInput_v7_6_StopStreams(This) \ ( (This)->lpVtbl -> StopStreams(This) ) #define IDeckLinkInput_v7_6_PauseStreams(This) \ ( (This)->lpVtbl -> PauseStreams(This) ) #define IDeckLinkInput_v7_6_FlushStreams(This) \ ( (This)->lpVtbl -> FlushStreams(This) ) #define IDeckLinkInput_v7_6_SetCallback(This,theCallback) \ ( (This)->lpVtbl -> SetCallback(This,theCallback) ) #define IDeckLinkInput_v7_6_GetHardwareReferenceClock(This,desiredTimeScale,hardwareTime,timeInFrame,ticksPerFrame) \ ( (This)->lpVtbl -> GetHardwareReferenceClock(This,desiredTimeScale,hardwareTime,timeInFrame,ticksPerFrame) ) #endif /* COBJMACROS */ #endif /* C style interface */ #endif /* __IDeckLinkInput_v7_6_INTERFACE_DEFINED__ */ #ifndef __IDeckLinkTimecode_v7_6_INTERFACE_DEFINED__ #define __IDeckLinkTimecode_v7_6_INTERFACE_DEFINED__ /* interface IDeckLinkTimecode_v7_6 */ /* [helpstring][uuid][object] */ EXTERN_C const IID IID_IDeckLinkTimecode_v7_6; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("EFB9BCA6-A521-44F7-BD69-2332F24D9EE6") IDeckLinkTimecode_v7_6 : public IUnknown { public: virtual BMDTimecodeBCD STDMETHODCALLTYPE GetBCD( void) = 0; virtual HRESULT STDMETHODCALLTYPE GetComponents( /* [out] */ unsigned char *hours, /* [out] */ unsigned char *minutes, /* [out] */ unsigned char *seconds, /* [out] */ unsigned char *frames) = 0; virtual HRESULT STDMETHODCALLTYPE GetString( /* [out] */ BSTR *timecode) = 0; virtual BMDTimecodeFlags STDMETHODCALLTYPE GetFlags( void) = 0; }; #else /* C style interface */ typedef struct IDeckLinkTimecode_v7_6Vtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IDeckLinkTimecode_v7_6 * This, /* [in] */ REFIID riid, /* [annotation][iid_is][out] */ __RPC__deref_out void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IDeckLinkTimecode_v7_6 * This); ULONG ( STDMETHODCALLTYPE *Release )( IDeckLinkTimecode_v7_6 * This); BMDTimecodeBCD ( STDMETHODCALLTYPE *GetBCD )( IDeckLinkTimecode_v7_6 * This); HRESULT ( STDMETHODCALLTYPE *GetComponents )( IDeckLinkTimecode_v7_6 * This, /* [out] */ unsigned char *hours, /* [out] */ unsigned char *minutes, /* [out] */ unsigned char *seconds, /* [out] */ unsigned char *frames); HRESULT ( STDMETHODCALLTYPE *GetString )( IDeckLinkTimecode_v7_6 * This, /* [out] */ BSTR *timecode); BMDTimecodeFlags ( STDMETHODCALLTYPE *GetFlags )( IDeckLinkTimecode_v7_6 * This); END_INTERFACE } IDeckLinkTimecode_v7_6Vtbl; interface IDeckLinkTimecode_v7_6 { CONST_VTBL struct IDeckLinkTimecode_v7_6Vtbl *lpVtbl; }; #ifdef COBJMACROS #define IDeckLinkTimecode_v7_6_QueryInterface(This,riid,ppvObject) \ ( (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) ) #define IDeckLinkTimecode_v7_6_AddRef(This) \ ( (This)->lpVtbl -> AddRef(This) ) #define IDeckLinkTimecode_v7_6_Release(This) \ ( (This)->lpVtbl -> Release(This) ) #define IDeckLinkTimecode_v7_6_GetBCD(This) \ ( (This)->lpVtbl -> GetBCD(This) ) #define IDeckLinkTimecode_v7_6_GetComponents(This,hours,minutes,seconds,frames) \ ( (This)->lpVtbl -> GetComponents(This,hours,minutes,seconds,frames) ) #define IDeckLinkTimecode_v7_6_GetString(This,timecode) \ ( (This)->lpVtbl -> GetString(This,timecode) ) #define IDeckLinkTimecode_v7_6_GetFlags(This) \ ( (This)->lpVtbl -> GetFlags(This) ) #endif /* COBJMACROS */ #endif /* C style interface */ #endif /* __IDeckLinkTimecode_v7_6_INTERFACE_DEFINED__ */ #ifndef __IDeckLinkVideoFrame_v7_6_INTERFACE_DEFINED__ #define __IDeckLinkVideoFrame_v7_6_INTERFACE_DEFINED__ /* interface IDeckLinkVideoFrame_v7_6 */ /* [helpstring][local][uuid][object] */ EXTERN_C const IID IID_IDeckLinkVideoFrame_v7_6; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("A8D8238E-6B18-4196-99E1-5AF717B83D32") IDeckLinkVideoFrame_v7_6 : public IUnknown { public: virtual long STDMETHODCALLTYPE GetWidth( void) = 0; virtual long STDMETHODCALLTYPE GetHeight( void) = 0; virtual long STDMETHODCALLTYPE GetRowBytes( void) = 0; virtual BMDPixelFormat STDMETHODCALLTYPE GetPixelFormat( void) = 0; virtual BMDFrameFlags STDMETHODCALLTYPE GetFlags( void) = 0; virtual HRESULT STDMETHODCALLTYPE GetBytes( /* [out] */ void **buffer) = 0; virtual HRESULT STDMETHODCALLTYPE GetTimecode( BMDTimecodeFormat format, /* [out] */ IDeckLinkTimecode_v7_6 **timecode) = 0; virtual HRESULT STDMETHODCALLTYPE GetAncillaryData( /* [out] */ IDeckLinkVideoFrameAncillary **ancillary) = 0; }; #else /* C style interface */ typedef struct IDeckLinkVideoFrame_v7_6Vtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IDeckLinkVideoFrame_v7_6 * This, /* [in] */ REFIID riid, /* [annotation][iid_is][out] */ __RPC__deref_out void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IDeckLinkVideoFrame_v7_6 * This); ULONG ( STDMETHODCALLTYPE *Release )( IDeckLinkVideoFrame_v7_6 * This); long ( STDMETHODCALLTYPE *GetWidth )( IDeckLinkVideoFrame_v7_6 * This); long ( STDMETHODCALLTYPE *GetHeight )( IDeckLinkVideoFrame_v7_6 * This); long ( STDMETHODCALLTYPE *GetRowBytes )( IDeckLinkVideoFrame_v7_6 * This); BMDPixelFormat ( STDMETHODCALLTYPE *GetPixelFormat )( IDeckLinkVideoFrame_v7_6 * This); BMDFrameFlags ( STDMETHODCALLTYPE *GetFlags )( IDeckLinkVideoFrame_v7_6 * This); HRESULT ( STDMETHODCALLTYPE *GetBytes )( IDeckLinkVideoFrame_v7_6 * This, /* [out] */ void **buffer); HRESULT ( STDMETHODCALLTYPE *GetTimecode )( IDeckLinkVideoFrame_v7_6 * This, BMDTimecodeFormat format, /* [out] */ IDeckLinkTimecode_v7_6 **timecode); HRESULT ( STDMETHODCALLTYPE *GetAncillaryData )( IDeckLinkVideoFrame_v7_6 * This, /* [out] */ IDeckLinkVideoFrameAncillary **ancillary); END_INTERFACE } IDeckLinkVideoFrame_v7_6Vtbl; interface IDeckLinkVideoFrame_v7_6 { CONST_VTBL struct IDeckLinkVideoFrame_v7_6Vtbl *lpVtbl; }; #ifdef COBJMACROS #define IDeckLinkVideoFrame_v7_6_QueryInterface(This,riid,ppvObject) \ ( (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) ) #define IDeckLinkVideoFrame_v7_6_AddRef(This) \ ( (This)->lpVtbl -> AddRef(This) ) #define IDeckLinkVideoFrame_v7_6_Release(This) \ ( (This)->lpVtbl -> Release(This) ) #define IDeckLinkVideoFrame_v7_6_GetWidth(This) \ ( (This)->lpVtbl -> GetWidth(This) ) #define IDeckLinkVideoFrame_v7_6_GetHeight(This) \ ( (This)->lpVtbl -> GetHeight(This) ) #define IDeckLinkVideoFrame_v7_6_GetRowBytes(This) \ ( (This)->lpVtbl -> GetRowBytes(This) ) #define IDeckLinkVideoFrame_v7_6_GetPixelFormat(This) \ ( (This)->lpVtbl -> GetPixelFormat(This) ) #define IDeckLinkVideoFrame_v7_6_GetFlags(This) \ ( (This)->lpVtbl -> GetFlags(This) ) #define IDeckLinkVideoFrame_v7_6_GetBytes(This,buffer) \ ( (This)->lpVtbl -> GetBytes(This,buffer) ) #define IDeckLinkVideoFrame_v7_6_GetTimecode(This,format,timecode) \ ( (This)->lpVtbl -> GetTimecode(This,format,timecode) ) #define IDeckLinkVideoFrame_v7_6_GetAncillaryData(This,ancillary) \ ( (This)->lpVtbl -> GetAncillaryData(This,ancillary) ) #endif /* COBJMACROS */ #endif /* C style interface */ #endif /* __IDeckLinkVideoFrame_v7_6_INTERFACE_DEFINED__ */ #ifndef __IDeckLinkMutableVideoFrame_v7_6_INTERFACE_DEFINED__ #define __IDeckLinkMutableVideoFrame_v7_6_INTERFACE_DEFINED__ /* interface IDeckLinkMutableVideoFrame_v7_6 */ /* [helpstring][local][uuid][object] */ EXTERN_C const IID IID_IDeckLinkMutableVideoFrame_v7_6; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("46FCEE00-B4E6-43D0-91C0-023A7FCEB34F") IDeckLinkMutableVideoFrame_v7_6 : public IDeckLinkVideoFrame_v7_6 { public: virtual HRESULT STDMETHODCALLTYPE SetFlags( BMDFrameFlags newFlags) = 0; virtual HRESULT STDMETHODCALLTYPE SetTimecode( BMDTimecodeFormat format, /* [in] */ IDeckLinkTimecode_v7_6 *timecode) = 0; virtual HRESULT STDMETHODCALLTYPE SetTimecodeFromComponents( BMDTimecodeFormat format, unsigned char hours, unsigned char minutes, unsigned char seconds, unsigned char frames, BMDTimecodeFlags flags) = 0; virtual HRESULT STDMETHODCALLTYPE SetAncillaryData( /* [in] */ IDeckLinkVideoFrameAncillary *ancillary) = 0; }; #else /* C style interface */ typedef struct IDeckLinkMutableVideoFrame_v7_6Vtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IDeckLinkMutableVideoFrame_v7_6 * This, /* [in] */ REFIID riid, /* [annotation][iid_is][out] */ __RPC__deref_out void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IDeckLinkMutableVideoFrame_v7_6 * This); ULONG ( STDMETHODCALLTYPE *Release )( IDeckLinkMutableVideoFrame_v7_6 * This); long ( STDMETHODCALLTYPE *GetWidth )( IDeckLinkMutableVideoFrame_v7_6 * This); long ( STDMETHODCALLTYPE *GetHeight )( IDeckLinkMutableVideoFrame_v7_6 * This); long ( STDMETHODCALLTYPE *GetRowBytes )( IDeckLinkMutableVideoFrame_v7_6 * This); BMDPixelFormat ( STDMETHODCALLTYPE *GetPixelFormat )( IDeckLinkMutableVideoFrame_v7_6 * This); BMDFrameFlags ( STDMETHODCALLTYPE *GetFlags )( IDeckLinkMutableVideoFrame_v7_6 * This); HRESULT ( STDMETHODCALLTYPE *GetBytes )( IDeckLinkMutableVideoFrame_v7_6 * This, /* [out] */ void **buffer); HRESULT ( STDMETHODCALLTYPE *GetTimecode )( IDeckLinkMutableVideoFrame_v7_6 * This, BMDTimecodeFormat format, /* [out] */ IDeckLinkTimecode_v7_6 **timecode); HRESULT ( STDMETHODCALLTYPE *GetAncillaryData )( IDeckLinkMutableVideoFrame_v7_6 * This, /* [out] */ IDeckLinkVideoFrameAncillary **ancillary); HRESULT ( STDMETHODCALLTYPE *SetFlags )( IDeckLinkMutableVideoFrame_v7_6 * This, BMDFrameFlags newFlags); HRESULT ( STDMETHODCALLTYPE *SetTimecode )( IDeckLinkMutableVideoFrame_v7_6 * This, BMDTimecodeFormat format, /* [in] */ IDeckLinkTimecode_v7_6 *timecode); HRESULT ( STDMETHODCALLTYPE *SetTimecodeFromComponents )( IDeckLinkMutableVideoFrame_v7_6 * This, BMDTimecodeFormat format, unsigned char hours, unsigned char minutes, unsigned char seconds, unsigned char frames, BMDTimecodeFlags flags); HRESULT ( STDMETHODCALLTYPE *SetAncillaryData )( IDeckLinkMutableVideoFrame_v7_6 * This, /* [in] */ IDeckLinkVideoFrameAncillary *ancillary); END_INTERFACE } IDeckLinkMutableVideoFrame_v7_6Vtbl; interface IDeckLinkMutableVideoFrame_v7_6 { CONST_VTBL struct IDeckLinkMutableVideoFrame_v7_6Vtbl *lpVtbl; }; #ifdef COBJMACROS #define IDeckLinkMutableVideoFrame_v7_6_QueryInterface(This,riid,ppvObject) \ ( (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) ) #define IDeckLinkMutableVideoFrame_v7_6_AddRef(This) \ ( (This)->lpVtbl -> AddRef(This) ) #define IDeckLinkMutableVideoFrame_v7_6_Release(This) \ ( (This)->lpVtbl -> Release(This) ) #define IDeckLinkMutableVideoFrame_v7_6_GetWidth(This) \ ( (This)->lpVtbl -> GetWidth(This) ) #define IDeckLinkMutableVideoFrame_v7_6_GetHeight(This) \ ( (This)->lpVtbl -> GetHeight(This) ) #define IDeckLinkMutableVideoFrame_v7_6_GetRowBytes(This) \ ( (This)->lpVtbl -> GetRowBytes(This) ) #define IDeckLinkMutableVideoFrame_v7_6_GetPixelFormat(This) \ ( (This)->lpVtbl -> GetPixelFormat(This) ) #define IDeckLinkMutableVideoFrame_v7_6_GetFlags(This) \ ( (This)->lpVtbl -> GetFlags(This) ) #define IDeckLinkMutableVideoFrame_v7_6_GetBytes(This,buffer) \ ( (This)->lpVtbl -> GetBytes(This,buffer) ) #define IDeckLinkMutableVideoFrame_v7_6_GetTimecode(This,format,timecode) \ ( (This)->lpVtbl -> GetTimecode(This,format,timecode) ) #define IDeckLinkMutableVideoFrame_v7_6_GetAncillaryData(This,ancillary) \ ( (This)->lpVtbl -> GetAncillaryData(This,ancillary) ) #define IDeckLinkMutableVideoFrame_v7_6_SetFlags(This,newFlags) \ ( (This)->lpVtbl -> SetFlags(This,newFlags) ) #define IDeckLinkMutableVideoFrame_v7_6_SetTimecode(This,format,timecode) \ ( (This)->lpVtbl -> SetTimecode(This,format,timecode) ) #define IDeckLinkMutableVideoFrame_v7_6_SetTimecodeFromComponents(This,format,hours,minutes,seconds,frames,flags) \ ( (This)->lpVtbl -> SetTimecodeFromComponents(This,format,hours,minutes,seconds,frames,flags) ) #define IDeckLinkMutableVideoFrame_v7_6_SetAncillaryData(This,ancillary) \ ( (This)->lpVtbl -> SetAncillaryData(This,ancillary) ) #endif /* COBJMACROS */ #endif /* C style interface */ #endif /* __IDeckLinkMutableVideoFrame_v7_6_INTERFACE_DEFINED__ */ #ifndef __IDeckLinkVideoInputFrame_v7_6_INTERFACE_DEFINED__ #define __IDeckLinkVideoInputFrame_v7_6_INTERFACE_DEFINED__ /* interface IDeckLinkVideoInputFrame_v7_6 */ /* [helpstring][local][uuid][object] */ EXTERN_C const IID IID_IDeckLinkVideoInputFrame_v7_6; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("9A74FA41-AE9F-47AC-8CF4-01F42DD59965") IDeckLinkVideoInputFrame_v7_6 : public IDeckLinkVideoFrame_v7_6 { public: virtual HRESULT STDMETHODCALLTYPE GetStreamTime( /* [out] */ BMDTimeValue *frameTime, /* [out] */ BMDTimeValue *frameDuration, BMDTimeScale timeScale) = 0; virtual HRESULT STDMETHODCALLTYPE GetHardwareReferenceTimestamp( BMDTimeScale timeScale, /* [out] */ BMDTimeValue *frameTime, /* [out] */ BMDTimeValue *frameDuration) = 0; }; #else /* C style interface */ typedef struct IDeckLinkVideoInputFrame_v7_6Vtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IDeckLinkVideoInputFrame_v7_6 * This, /* [in] */ REFIID riid, /* [annotation][iid_is][out] */ __RPC__deref_out void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IDeckLinkVideoInputFrame_v7_6 * This); ULONG ( STDMETHODCALLTYPE *Release )( IDeckLinkVideoInputFrame_v7_6 * This); long ( STDMETHODCALLTYPE *GetWidth )( IDeckLinkVideoInputFrame_v7_6 * This); long ( STDMETHODCALLTYPE *GetHeight )( IDeckLinkVideoInputFrame_v7_6 * This); long ( STDMETHODCALLTYPE *GetRowBytes )( IDeckLinkVideoInputFrame_v7_6 * This); BMDPixelFormat ( STDMETHODCALLTYPE *GetPixelFormat )( IDeckLinkVideoInputFrame_v7_6 * This); BMDFrameFlags ( STDMETHODCALLTYPE *GetFlags )( IDeckLinkVideoInputFrame_v7_6 * This); HRESULT ( STDMETHODCALLTYPE *GetBytes )( IDeckLinkVideoInputFrame_v7_6 * This, /* [out] */ void **buffer); HRESULT ( STDMETHODCALLTYPE *GetTimecode )( IDeckLinkVideoInputFrame_v7_6 * This, BMDTimecodeFormat format, /* [out] */ IDeckLinkTimecode_v7_6 **timecode); HRESULT ( STDMETHODCALLTYPE *GetAncillaryData )( IDeckLinkVideoInputFrame_v7_6 * This, /* [out] */ IDeckLinkVideoFrameAncillary **ancillary); HRESULT ( STDMETHODCALLTYPE *GetStreamTime )( IDeckLinkVideoInputFrame_v7_6 * This, /* [out] */ BMDTimeValue *frameTime, /* [out] */ BMDTimeValue *frameDuration, BMDTimeScale timeScale); HRESULT ( STDMETHODCALLTYPE *GetHardwareReferenceTimestamp )( IDeckLinkVideoInputFrame_v7_6 * This, BMDTimeScale timeScale, /* [out] */ BMDTimeValue *frameTime, /* [out] */ BMDTimeValue *frameDuration); END_INTERFACE } IDeckLinkVideoInputFrame_v7_6Vtbl; interface IDeckLinkVideoInputFrame_v7_6 { CONST_VTBL struct IDeckLinkVideoInputFrame_v7_6Vtbl *lpVtbl; }; #ifdef COBJMACROS #define IDeckLinkVideoInputFrame_v7_6_QueryInterface(This,riid,ppvObject) \ ( (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) ) #define IDeckLinkVideoInputFrame_v7_6_AddRef(This) \ ( (This)->lpVtbl -> AddRef(This) ) #define IDeckLinkVideoInputFrame_v7_6_Release(This) \ ( (This)->lpVtbl -> Release(This) ) #define IDeckLinkVideoInputFrame_v7_6_GetWidth(This) \ ( (This)->lpVtbl -> GetWidth(This) ) #define IDeckLinkVideoInputFrame_v7_6_GetHeight(This) \ ( (This)->lpVtbl -> GetHeight(This) ) #define IDeckLinkVideoInputFrame_v7_6_GetRowBytes(This) \ ( (This)->lpVtbl -> GetRowBytes(This) ) #define IDeckLinkVideoInputFrame_v7_6_GetPixelFormat(This) \ ( (This)->lpVtbl -> GetPixelFormat(This) ) #define IDeckLinkVideoInputFrame_v7_6_GetFlags(This) \ ( (This)->lpVtbl -> GetFlags(This) ) #define IDeckLinkVideoInputFrame_v7_6_GetBytes(This,buffer) \ ( (This)->lpVtbl -> GetBytes(This,buffer) ) #define IDeckLinkVideoInputFrame_v7_6_GetTimecode(This,format,timecode) \ ( (This)->lpVtbl -> GetTimecode(This,format,timecode) ) #define IDeckLinkVideoInputFrame_v7_6_GetAncillaryData(This,ancillary) \ ( (This)->lpVtbl -> GetAncillaryData(This,ancillary) ) #define IDeckLinkVideoInputFrame_v7_6_GetStreamTime(This,frameTime,frameDuration,timeScale) \ ( (This)->lpVtbl -> GetStreamTime(This,frameTime,frameDuration,timeScale) ) #define IDeckLinkVideoInputFrame_v7_6_GetHardwareReferenceTimestamp(This,timeScale,frameTime,frameDuration) \ ( (This)->lpVtbl -> GetHardwareReferenceTimestamp(This,timeScale,frameTime,frameDuration) ) #endif /* COBJMACROS */ #endif /* C style interface */ #endif /* __IDeckLinkVideoInputFrame_v7_6_INTERFACE_DEFINED__ */ #ifndef __IDeckLinkScreenPreviewCallback_v7_6_INTERFACE_DEFINED__ #define __IDeckLinkScreenPreviewCallback_v7_6_INTERFACE_DEFINED__ /* interface IDeckLinkScreenPreviewCallback_v7_6 */ /* [helpstring][local][uuid][object] */ EXTERN_C const IID IID_IDeckLinkScreenPreviewCallback_v7_6; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("373F499D-4B4D-4518-AD22-6354E5A5825E") IDeckLinkScreenPreviewCallback_v7_6 : public IUnknown { public: virtual HRESULT STDMETHODCALLTYPE DrawFrame( /* [in] */ IDeckLinkVideoFrame_v7_6 *theFrame) = 0; }; #else /* C style interface */ typedef struct IDeckLinkScreenPreviewCallback_v7_6Vtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IDeckLinkScreenPreviewCallback_v7_6 * This, /* [in] */ REFIID riid, /* [annotation][iid_is][out] */ __RPC__deref_out void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IDeckLinkScreenPreviewCallback_v7_6 * This); ULONG ( STDMETHODCALLTYPE *Release )( IDeckLinkScreenPreviewCallback_v7_6 * This); HRESULT ( STDMETHODCALLTYPE *DrawFrame )( IDeckLinkScreenPreviewCallback_v7_6 * This, /* [in] */ IDeckLinkVideoFrame_v7_6 *theFrame); END_INTERFACE } IDeckLinkScreenPreviewCallback_v7_6Vtbl; interface IDeckLinkScreenPreviewCallback_v7_6 { CONST_VTBL struct IDeckLinkScreenPreviewCallback_v7_6Vtbl *lpVtbl; }; #ifdef COBJMACROS #define IDeckLinkScreenPreviewCallback_v7_6_QueryInterface(This,riid,ppvObject) \ ( (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) ) #define IDeckLinkScreenPreviewCallback_v7_6_AddRef(This) \ ( (This)->lpVtbl -> AddRef(This) ) #define IDeckLinkScreenPreviewCallback_v7_6_Release(This) \ ( (This)->lpVtbl -> Release(This) ) #define IDeckLinkScreenPreviewCallback_v7_6_DrawFrame(This,theFrame) \ ( (This)->lpVtbl -> DrawFrame(This,theFrame) ) #endif /* COBJMACROS */ #endif /* C style interface */ #endif /* __IDeckLinkScreenPreviewCallback_v7_6_INTERFACE_DEFINED__ */ #ifndef __IDeckLinkGLScreenPreviewHelper_v7_6_INTERFACE_DEFINED__ #define __IDeckLinkGLScreenPreviewHelper_v7_6_INTERFACE_DEFINED__ /* interface IDeckLinkGLScreenPreviewHelper_v7_6 */ /* [helpstring][local][uuid][object] */ EXTERN_C const IID IID_IDeckLinkGLScreenPreviewHelper_v7_6; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("BA575CD9-A15E-497B-B2C2-F9AFE7BE4EBA") IDeckLinkGLScreenPreviewHelper_v7_6 : public IUnknown { public: virtual HRESULT STDMETHODCALLTYPE InitializeGL( void) = 0; virtual HRESULT STDMETHODCALLTYPE PaintGL( void) = 0; virtual HRESULT STDMETHODCALLTYPE SetFrame( /* [in] */ IDeckLinkVideoFrame_v7_6 *theFrame) = 0; }; #else /* C style interface */ typedef struct IDeckLinkGLScreenPreviewHelper_v7_6Vtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IDeckLinkGLScreenPreviewHelper_v7_6 * This, /* [in] */ REFIID riid, /* [annotation][iid_is][out] */ __RPC__deref_out void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IDeckLinkGLScreenPreviewHelper_v7_6 * This); ULONG ( STDMETHODCALLTYPE *Release )( IDeckLinkGLScreenPreviewHelper_v7_6 * This); HRESULT ( STDMETHODCALLTYPE *InitializeGL )( IDeckLinkGLScreenPreviewHelper_v7_6 * This); HRESULT ( STDMETHODCALLTYPE *PaintGL )( IDeckLinkGLScreenPreviewHelper_v7_6 * This); HRESULT ( STDMETHODCALLTYPE *SetFrame )( IDeckLinkGLScreenPreviewHelper_v7_6 * This, /* [in] */ IDeckLinkVideoFrame_v7_6 *theFrame); END_INTERFACE } IDeckLinkGLScreenPreviewHelper_v7_6Vtbl; interface IDeckLinkGLScreenPreviewHelper_v7_6 { CONST_VTBL struct IDeckLinkGLScreenPreviewHelper_v7_6Vtbl *lpVtbl; }; #ifdef COBJMACROS #define IDeckLinkGLScreenPreviewHelper_v7_6_QueryInterface(This,riid,ppvObject) \ ( (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) ) #define IDeckLinkGLScreenPreviewHelper_v7_6_AddRef(This) \ ( (This)->lpVtbl -> AddRef(This) ) #define IDeckLinkGLScreenPreviewHelper_v7_6_Release(This) \ ( (This)->lpVtbl -> Release(This) ) #define IDeckLinkGLScreenPreviewHelper_v7_6_InitializeGL(This) \ ( (This)->lpVtbl -> InitializeGL(This) ) #define IDeckLinkGLScreenPreviewHelper_v7_6_PaintGL(This) \ ( (This)->lpVtbl -> PaintGL(This) ) #define IDeckLinkGLScreenPreviewHelper_v7_6_SetFrame(This,theFrame) \ ( (This)->lpVtbl -> SetFrame(This,theFrame) ) #endif /* COBJMACROS */ #endif /* C style interface */ #endif /* __IDeckLinkGLScreenPreviewHelper_v7_6_INTERFACE_DEFINED__ */ #ifndef __IDeckLinkVideoConversion_v7_6_INTERFACE_DEFINED__ #define __IDeckLinkVideoConversion_v7_6_INTERFACE_DEFINED__ /* interface IDeckLinkVideoConversion_v7_6 */ /* [helpstring][local][uuid][object] */ EXTERN_C const IID IID_IDeckLinkVideoConversion_v7_6; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("3EB504C9-F97D-40FE-A158-D407D48CB53B") IDeckLinkVideoConversion_v7_6 : public IUnknown { public: virtual HRESULT STDMETHODCALLTYPE ConvertFrame( /* [in] */ IDeckLinkVideoFrame_v7_6 *srcFrame, /* [in] */ IDeckLinkVideoFrame_v7_6 *dstFrame) = 0; }; #else /* C style interface */ typedef struct IDeckLinkVideoConversion_v7_6Vtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IDeckLinkVideoConversion_v7_6 * This, /* [in] */ REFIID riid, /* [annotation][iid_is][out] */ __RPC__deref_out void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IDeckLinkVideoConversion_v7_6 * This); ULONG ( STDMETHODCALLTYPE *Release )( IDeckLinkVideoConversion_v7_6 * This); HRESULT ( STDMETHODCALLTYPE *ConvertFrame )( IDeckLinkVideoConversion_v7_6 * This, /* [in] */ IDeckLinkVideoFrame_v7_6 *srcFrame, /* [in] */ IDeckLinkVideoFrame_v7_6 *dstFrame); END_INTERFACE } IDeckLinkVideoConversion_v7_6Vtbl; interface IDeckLinkVideoConversion_v7_6 { CONST_VTBL struct IDeckLinkVideoConversion_v7_6Vtbl *lpVtbl; }; #ifdef COBJMACROS #define IDeckLinkVideoConversion_v7_6_QueryInterface(This,riid,ppvObject) \ ( (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) ) #define IDeckLinkVideoConversion_v7_6_AddRef(This) \ ( (This)->lpVtbl -> AddRef(This) ) #define IDeckLinkVideoConversion_v7_6_Release(This) \ ( (This)->lpVtbl -> Release(This) ) #define IDeckLinkVideoConversion_v7_6_ConvertFrame(This,srcFrame,dstFrame) \ ( (This)->lpVtbl -> ConvertFrame(This,srcFrame,dstFrame) ) #endif /* COBJMACROS */ #endif /* C style interface */ #endif /* __IDeckLinkVideoConversion_v7_6_INTERFACE_DEFINED__ */ #ifndef __IDeckLinkConfiguration_v7_6_INTERFACE_DEFINED__ #define __IDeckLinkConfiguration_v7_6_INTERFACE_DEFINED__ /* interface IDeckLinkConfiguration_v7_6 */ /* [helpstring][uuid][object] */ EXTERN_C const IID IID_IDeckLinkConfiguration_v7_6; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("B8EAD569-B764-47F0-A73F-AE40DF6CBF10") IDeckLinkConfiguration_v7_6 : public IUnknown { public: virtual HRESULT STDMETHODCALLTYPE GetConfigurationValidator( /* [out] */ IDeckLinkConfiguration_v7_6 **configObject) = 0; virtual HRESULT STDMETHODCALLTYPE WriteConfigurationToPreferences( void) = 0; virtual HRESULT STDMETHODCALLTYPE SetVideoOutputFormat( /* [in] */ BMDVideoConnection_v7_6 videoOutputConnection) = 0; virtual HRESULT STDMETHODCALLTYPE IsVideoOutputActive( /* [in] */ BMDVideoConnection_v7_6 videoOutputConnection, /* [out] */ BOOL *active) = 0; virtual HRESULT STDMETHODCALLTYPE SetAnalogVideoOutputFlags( /* [in] */ BMDAnalogVideoFlags analogVideoFlags) = 0; virtual HRESULT STDMETHODCALLTYPE GetAnalogVideoOutputFlags( /* [out] */ BMDAnalogVideoFlags *analogVideoFlags) = 0; virtual HRESULT STDMETHODCALLTYPE EnableFieldFlickerRemovalWhenPaused( /* [in] */ BOOL enable) = 0; virtual HRESULT STDMETHODCALLTYPE IsEnabledFieldFlickerRemovalWhenPaused( /* [out] */ BOOL *enabled) = 0; virtual HRESULT STDMETHODCALLTYPE Set444And3GBpsVideoOutput( /* [in] */ BOOL enable444VideoOutput, /* [in] */ BOOL enable3GbsOutput) = 0; virtual HRESULT STDMETHODCALLTYPE Get444And3GBpsVideoOutput( /* [out] */ BOOL *is444VideoOutputEnabled, /* [out] */ BOOL *threeGbsOutputEnabled) = 0; virtual HRESULT STDMETHODCALLTYPE SetVideoOutputConversionMode( /* [in] */ BMDVideoOutputConversionMode conversionMode) = 0; virtual HRESULT STDMETHODCALLTYPE GetVideoOutputConversionMode( /* [out] */ BMDVideoOutputConversionMode *conversionMode) = 0; virtual HRESULT STDMETHODCALLTYPE Set_HD1080p24_to_HD1080i5994_Conversion( /* [in] */ BOOL enable) = 0; virtual HRESULT STDMETHODCALLTYPE Get_HD1080p24_to_HD1080i5994_Conversion( /* [out] */ BOOL *enabled) = 0; virtual HRESULT STDMETHODCALLTYPE SetVideoInputFormat( /* [in] */ BMDVideoConnection_v7_6 videoInputFormat) = 0; virtual HRESULT STDMETHODCALLTYPE GetVideoInputFormat( /* [out] */ BMDVideoConnection_v7_6 *videoInputFormat) = 0; virtual HRESULT STDMETHODCALLTYPE SetAnalogVideoInputFlags( /* [in] */ BMDAnalogVideoFlags analogVideoFlags) = 0; virtual HRESULT STDMETHODCALLTYPE GetAnalogVideoInputFlags( /* [out] */ BMDAnalogVideoFlags *analogVideoFlags) = 0; virtual HRESULT STDMETHODCALLTYPE SetVideoInputConversionMode( /* [in] */ BMDVideoInputConversionMode conversionMode) = 0; virtual HRESULT STDMETHODCALLTYPE GetVideoInputConversionMode( /* [out] */ BMDVideoInputConversionMode *conversionMode) = 0; virtual HRESULT STDMETHODCALLTYPE SetBlackVideoOutputDuringCapture( /* [in] */ BOOL blackOutInCapture) = 0; virtual HRESULT STDMETHODCALLTYPE GetBlackVideoOutputDuringCapture( /* [out] */ BOOL *blackOutInCapture) = 0; virtual HRESULT STDMETHODCALLTYPE Set32PulldownSequenceInitialTimecodeFrame( /* [in] */ unsigned long aFrameTimecode) = 0; virtual HRESULT STDMETHODCALLTYPE Get32PulldownSequenceInitialTimecodeFrame( /* [out] */ unsigned long *aFrameTimecode) = 0; virtual HRESULT STDMETHODCALLTYPE SetVancSourceLineMapping( /* [in] */ unsigned long activeLine1VANCsource, /* [in] */ unsigned long activeLine2VANCsource, /* [in] */ unsigned long activeLine3VANCsource) = 0; virtual HRESULT STDMETHODCALLTYPE GetVancSourceLineMapping( /* [out] */ unsigned long *activeLine1VANCsource, /* [out] */ unsigned long *activeLine2VANCsource, /* [out] */ unsigned long *activeLine3VANCsource) = 0; virtual HRESULT STDMETHODCALLTYPE SetAudioInputFormat( /* [in] */ BMDAudioConnection audioInputFormat) = 0; virtual HRESULT STDMETHODCALLTYPE GetAudioInputFormat( /* [out] */ BMDAudioConnection *audioInputFormat) = 0; }; #else /* C style interface */ typedef struct IDeckLinkConfiguration_v7_6Vtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IDeckLinkConfiguration_v7_6 * This, /* [in] */ REFIID riid, /* [annotation][iid_is][out] */ __RPC__deref_out void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IDeckLinkConfiguration_v7_6 * This); ULONG ( STDMETHODCALLTYPE *Release )( IDeckLinkConfiguration_v7_6 * This); HRESULT ( STDMETHODCALLTYPE *GetConfigurationValidator )( IDeckLinkConfiguration_v7_6 * This, /* [out] */ IDeckLinkConfiguration_v7_6 **configObject); HRESULT ( STDMETHODCALLTYPE *WriteConfigurationToPreferences )( IDeckLinkConfiguration_v7_6 * This); HRESULT ( STDMETHODCALLTYPE *SetVideoOutputFormat )( IDeckLinkConfiguration_v7_6 * This, /* [in] */ BMDVideoConnection_v7_6 videoOutputConnection); HRESULT ( STDMETHODCALLTYPE *IsVideoOutputActive )( IDeckLinkConfiguration_v7_6 * This, /* [in] */ BMDVideoConnection_v7_6 videoOutputConnection, /* [out] */ BOOL *active); HRESULT ( STDMETHODCALLTYPE *SetAnalogVideoOutputFlags )( IDeckLinkConfiguration_v7_6 * This, /* [in] */ BMDAnalogVideoFlags analogVideoFlags); HRESULT ( STDMETHODCALLTYPE *GetAnalogVideoOutputFlags )( IDeckLinkConfiguration_v7_6 * This, /* [out] */ BMDAnalogVideoFlags *analogVideoFlags); HRESULT ( STDMETHODCALLTYPE *EnableFieldFlickerRemovalWhenPaused )( IDeckLinkConfiguration_v7_6 * This, /* [in] */ BOOL enable); HRESULT ( STDMETHODCALLTYPE *IsEnabledFieldFlickerRemovalWhenPaused )( IDeckLinkConfiguration_v7_6 * This, /* [out] */ BOOL *enabled); HRESULT ( STDMETHODCALLTYPE *Set444And3GBpsVideoOutput )( IDeckLinkConfiguration_v7_6 * This, /* [in] */ BOOL enable444VideoOutput, /* [in] */ BOOL enable3GbsOutput); HRESULT ( STDMETHODCALLTYPE *Get444And3GBpsVideoOutput )( IDeckLinkConfiguration_v7_6 * This, /* [out] */ BOOL *is444VideoOutputEnabled, /* [out] */ BOOL *threeGbsOutputEnabled); HRESULT ( STDMETHODCALLTYPE *SetVideoOutputConversionMode )( IDeckLinkConfiguration_v7_6 * This, /* [in] */ BMDVideoOutputConversionMode conversionMode); HRESULT ( STDMETHODCALLTYPE *GetVideoOutputConversionMode )( IDeckLinkConfiguration_v7_6 * This, /* [out] */ BMDVideoOutputConversionMode *conversionMode); HRESULT ( STDMETHODCALLTYPE *Set_HD1080p24_to_HD1080i5994_Conversion )( IDeckLinkConfiguration_v7_6 * This, /* [in] */ BOOL enable); HRESULT ( STDMETHODCALLTYPE *Get_HD1080p24_to_HD1080i5994_Conversion )( IDeckLinkConfiguration_v7_6 * This, /* [out] */ BOOL *enabled); HRESULT ( STDMETHODCALLTYPE *SetVideoInputFormat )( IDeckLinkConfiguration_v7_6 * This, /* [in] */ BMDVideoConnection_v7_6 videoInputFormat); HRESULT ( STDMETHODCALLTYPE *GetVideoInputFormat )( IDeckLinkConfiguration_v7_6 * This, /* [out] */ BMDVideoConnection_v7_6 *videoInputFormat); HRESULT ( STDMETHODCALLTYPE *SetAnalogVideoInputFlags )( IDeckLinkConfiguration_v7_6 * This, /* [in] */ BMDAnalogVideoFlags analogVideoFlags); HRESULT ( STDMETHODCALLTYPE *GetAnalogVideoInputFlags )( IDeckLinkConfiguration_v7_6 * This, /* [out] */ BMDAnalogVideoFlags *analogVideoFlags); HRESULT ( STDMETHODCALLTYPE *SetVideoInputConversionMode )( IDeckLinkConfiguration_v7_6 * This, /* [in] */ BMDVideoInputConversionMode conversionMode); HRESULT ( STDMETHODCALLTYPE *GetVideoInputConversionMode )( IDeckLinkConfiguration_v7_6 * This, /* [out] */ BMDVideoInputConversionMode *conversionMode); HRESULT ( STDMETHODCALLTYPE *SetBlackVideoOutputDuringCapture )( IDeckLinkConfiguration_v7_6 * This, /* [in] */ BOOL blackOutInCapture); HRESULT ( STDMETHODCALLTYPE *GetBlackVideoOutputDuringCapture )( IDeckLinkConfiguration_v7_6 * This, /* [out] */ BOOL *blackOutInCapture); HRESULT ( STDMETHODCALLTYPE *Set32PulldownSequenceInitialTimecodeFrame )( IDeckLinkConfiguration_v7_6 * This, /* [in] */ unsigned long aFrameTimecode); HRESULT ( STDMETHODCALLTYPE *Get32PulldownSequenceInitialTimecodeFrame )( IDeckLinkConfiguration_v7_6 * This, /* [out] */ unsigned long *aFrameTimecode); HRESULT ( STDMETHODCALLTYPE *SetVancSourceLineMapping )( IDeckLinkConfiguration_v7_6 * This, /* [in] */ unsigned long activeLine1VANCsource, /* [in] */ unsigned long activeLine2VANCsource, /* [in] */ unsigned long activeLine3VANCsource); HRESULT ( STDMETHODCALLTYPE *GetVancSourceLineMapping )( IDeckLinkConfiguration_v7_6 * This, /* [out] */ unsigned long *activeLine1VANCsource, /* [out] */ unsigned long *activeLine2VANCsource, /* [out] */ unsigned long *activeLine3VANCsource); HRESULT ( STDMETHODCALLTYPE *SetAudioInputFormat )( IDeckLinkConfiguration_v7_6 * This, /* [in] */ BMDAudioConnection audioInputFormat); HRESULT ( STDMETHODCALLTYPE *GetAudioInputFormat )( IDeckLinkConfiguration_v7_6 * This, /* [out] */ BMDAudioConnection *audioInputFormat); END_INTERFACE } IDeckLinkConfiguration_v7_6Vtbl; interface IDeckLinkConfiguration_v7_6 { CONST_VTBL struct IDeckLinkConfiguration_v7_6Vtbl *lpVtbl; }; #ifdef COBJMACROS #define IDeckLinkConfiguration_v7_6_QueryInterface(This,riid,ppvObject) \ ( (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) ) #define IDeckLinkConfiguration_v7_6_AddRef(This) \ ( (This)->lpVtbl -> AddRef(This) ) #define IDeckLinkConfiguration_v7_6_Release(This) \ ( (This)->lpVtbl -> Release(This) ) #define IDeckLinkConfiguration_v7_6_GetConfigurationValidator(This,configObject) \ ( (This)->lpVtbl -> GetConfigurationValidator(This,configObject) ) #define IDeckLinkConfiguration_v7_6_WriteConfigurationToPreferences(This) \ ( (This)->lpVtbl -> WriteConfigurationToPreferences(This) ) #define IDeckLinkConfiguration_v7_6_SetVideoOutputFormat(This,videoOutputConnection) \ ( (This)->lpVtbl -> SetVideoOutputFormat(This,videoOutputConnection) ) #define IDeckLinkConfiguration_v7_6_IsVideoOutputActive(This,videoOutputConnection,active) \ ( (This)->lpVtbl -> IsVideoOutputActive(This,videoOutputConnection,active) ) #define IDeckLinkConfiguration_v7_6_SetAnalogVideoOutputFlags(This,analogVideoFlags) \ ( (This)->lpVtbl -> SetAnalogVideoOutputFlags(This,analogVideoFlags) ) #define IDeckLinkConfiguration_v7_6_GetAnalogVideoOutputFlags(This,analogVideoFlags) \ ( (This)->lpVtbl -> GetAnalogVideoOutputFlags(This,analogVideoFlags) ) #define IDeckLinkConfiguration_v7_6_EnableFieldFlickerRemovalWhenPaused(This,enable) \ ( (This)->lpVtbl -> EnableFieldFlickerRemovalWhenPaused(This,enable) ) #define IDeckLinkConfiguration_v7_6_IsEnabledFieldFlickerRemovalWhenPaused(This,enabled) \ ( (This)->lpVtbl -> IsEnabledFieldFlickerRemovalWhenPaused(This,enabled) ) #define IDeckLinkConfiguration_v7_6_Set444And3GBpsVideoOutput(This,enable444VideoOutput,enable3GbsOutput) \ ( (This)->lpVtbl -> Set444And3GBpsVideoOutput(This,enable444VideoOutput,enable3GbsOutput) ) #define IDeckLinkConfiguration_v7_6_Get444And3GBpsVideoOutput(This,is444VideoOutputEnabled,threeGbsOutputEnabled) \ ( (This)->lpVtbl -> Get444And3GBpsVideoOutput(This,is444VideoOutputEnabled,threeGbsOutputEnabled) ) #define IDeckLinkConfiguration_v7_6_SetVideoOutputConversionMode(This,conversionMode) \ ( (This)->lpVtbl -> SetVideoOutputConversionMode(This,conversionMode) ) #define IDeckLinkConfiguration_v7_6_GetVideoOutputConversionMode(This,conversionMode) \ ( (This)->lpVtbl -> GetVideoOutputConversionMode(This,conversionMode) ) #define IDeckLinkConfiguration_v7_6_Set_HD1080p24_to_HD1080i5994_Conversion(This,enable) \ ( (This)->lpVtbl -> Set_HD1080p24_to_HD1080i5994_Conversion(This,enable) ) #define IDeckLinkConfiguration_v7_6_Get_HD1080p24_to_HD1080i5994_Conversion(This,enabled) \ ( (This)->lpVtbl -> Get_HD1080p24_to_HD1080i5994_Conversion(This,enabled) ) #define IDeckLinkConfiguration_v7_6_SetVideoInputFormat(This,videoInputFormat) \ ( (This)->lpVtbl -> SetVideoInputFormat(This,videoInputFormat) ) #define IDeckLinkConfiguration_v7_6_GetVideoInputFormat(This,videoInputFormat) \ ( (This)->lpVtbl -> GetVideoInputFormat(This,videoInputFormat) ) #define IDeckLinkConfiguration_v7_6_SetAnalogVideoInputFlags(This,analogVideoFlags) \ ( (This)->lpVtbl -> SetAnalogVideoInputFlags(This,analogVideoFlags) ) #define IDeckLinkConfiguration_v7_6_GetAnalogVideoInputFlags(This,analogVideoFlags) \ ( (This)->lpVtbl -> GetAnalogVideoInputFlags(This,analogVideoFlags) ) #define IDeckLinkConfiguration_v7_6_SetVideoInputConversionMode(This,conversionMode) \ ( (This)->lpVtbl -> SetVideoInputConversionMode(This,conversionMode) ) #define IDeckLinkConfiguration_v7_6_GetVideoInputConversionMode(This,conversionMode) \ ( (This)->lpVtbl -> GetVideoInputConversionMode(This,conversionMode) ) #define IDeckLinkConfiguration_v7_6_SetBlackVideoOutputDuringCapture(This,blackOutInCapture) \ ( (This)->lpVtbl -> SetBlackVideoOutputDuringCapture(This,blackOutInCapture) ) #define IDeckLinkConfiguration_v7_6_GetBlackVideoOutputDuringCapture(This,blackOutInCapture) \ ( (This)->lpVtbl -> GetBlackVideoOutputDuringCapture(This,blackOutInCapture) ) #define IDeckLinkConfiguration_v7_6_Set32PulldownSequenceInitialTimecodeFrame(This,aFrameTimecode) \ ( (This)->lpVtbl -> Set32PulldownSequenceInitialTimecodeFrame(This,aFrameTimecode) ) #define IDeckLinkConfiguration_v7_6_Get32PulldownSequenceInitialTimecodeFrame(This,aFrameTimecode) \ ( (This)->lpVtbl -> Get32PulldownSequenceInitialTimecodeFrame(This,aFrameTimecode) ) #define IDeckLinkConfiguration_v7_6_SetVancSourceLineMapping(This,activeLine1VANCsource,activeLine2VANCsource,activeLine3VANCsource) \ ( (This)->lpVtbl -> SetVancSourceLineMapping(This,activeLine1VANCsource,activeLine2VANCsource,activeLine3VANCsource) ) #define IDeckLinkConfiguration_v7_6_GetVancSourceLineMapping(This,activeLine1VANCsource,activeLine2VANCsource,activeLine3VANCsource) \ ( (This)->lpVtbl -> GetVancSourceLineMapping(This,activeLine1VANCsource,activeLine2VANCsource,activeLine3VANCsource) ) #define IDeckLinkConfiguration_v7_6_SetAudioInputFormat(This,audioInputFormat) \ ( (This)->lpVtbl -> SetAudioInputFormat(This,audioInputFormat) ) #define IDeckLinkConfiguration_v7_6_GetAudioInputFormat(This,audioInputFormat) \ ( (This)->lpVtbl -> GetAudioInputFormat(This,audioInputFormat) ) #endif /* COBJMACROS */ #endif /* C style interface */ #endif /* __IDeckLinkConfiguration_v7_6_INTERFACE_DEFINED__ */ #ifndef __IDeckLinkVideoOutputCallback_v7_6_INTERFACE_DEFINED__ #define __IDeckLinkVideoOutputCallback_v7_6_INTERFACE_DEFINED__ /* interface IDeckLinkVideoOutputCallback_v7_6 */ /* [helpstring][uuid][object] */ EXTERN_C const IID IID_IDeckLinkVideoOutputCallback_v7_6; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("E763A626-4A3C-49D1-BF13-E7AD3692AE52") IDeckLinkVideoOutputCallback_v7_6 : public IUnknown { public: virtual HRESULT STDMETHODCALLTYPE ScheduledFrameCompleted( /* [in] */ IDeckLinkVideoFrame_v7_6 *completedFrame, /* [in] */ BMDOutputFrameCompletionResult result) = 0; virtual HRESULT STDMETHODCALLTYPE ScheduledPlaybackHasStopped( void) = 0; }; #else /* C style interface */ typedef struct IDeckLinkVideoOutputCallback_v7_6Vtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IDeckLinkVideoOutputCallback_v7_6 * This, /* [in] */ REFIID riid, /* [annotation][iid_is][out] */ __RPC__deref_out void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IDeckLinkVideoOutputCallback_v7_6 * This); ULONG ( STDMETHODCALLTYPE *Release )( IDeckLinkVideoOutputCallback_v7_6 * This); HRESULT ( STDMETHODCALLTYPE *ScheduledFrameCompleted )( IDeckLinkVideoOutputCallback_v7_6 * This, /* [in] */ IDeckLinkVideoFrame_v7_6 *completedFrame, /* [in] */ BMDOutputFrameCompletionResult result); HRESULT ( STDMETHODCALLTYPE *ScheduledPlaybackHasStopped )( IDeckLinkVideoOutputCallback_v7_6 * This); END_INTERFACE } IDeckLinkVideoOutputCallback_v7_6Vtbl; interface IDeckLinkVideoOutputCallback_v7_6 { CONST_VTBL struct IDeckLinkVideoOutputCallback_v7_6Vtbl *lpVtbl; }; #ifdef COBJMACROS #define IDeckLinkVideoOutputCallback_v7_6_QueryInterface(This,riid,ppvObject) \ ( (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) ) #define IDeckLinkVideoOutputCallback_v7_6_AddRef(This) \ ( (This)->lpVtbl -> AddRef(This) ) #define IDeckLinkVideoOutputCallback_v7_6_Release(This) \ ( (This)->lpVtbl -> Release(This) ) #define IDeckLinkVideoOutputCallback_v7_6_ScheduledFrameCompleted(This,completedFrame,result) \ ( (This)->lpVtbl -> ScheduledFrameCompleted(This,completedFrame,result) ) #define IDeckLinkVideoOutputCallback_v7_6_ScheduledPlaybackHasStopped(This) \ ( (This)->lpVtbl -> ScheduledPlaybackHasStopped(This) ) #endif /* COBJMACROS */ #endif /* C style interface */ #endif /* __IDeckLinkVideoOutputCallback_v7_6_INTERFACE_DEFINED__ */ #ifndef __IDeckLinkInputCallback_v7_6_INTERFACE_DEFINED__ #define __IDeckLinkInputCallback_v7_6_INTERFACE_DEFINED__ /* interface IDeckLinkInputCallback_v7_6 */ /* [helpstring][uuid][object] */ EXTERN_C const IID IID_IDeckLinkInputCallback_v7_6; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("31D28EE7-88B6-4CB1-897A-CDBF79A26414") IDeckLinkInputCallback_v7_6 : public IUnknown { public: virtual HRESULT STDMETHODCALLTYPE VideoInputFormatChanged( /* [in] */ BMDVideoInputFormatChangedEvents notificationEvents, /* [in] */ IDeckLinkDisplayMode_v7_6 *newDisplayMode, /* [in] */ BMDDetectedVideoInputFormatFlags detectedSignalFlags) = 0; virtual HRESULT STDMETHODCALLTYPE VideoInputFrameArrived( /* [in] */ IDeckLinkVideoInputFrame_v7_6 *videoFrame, /* [in] */ IDeckLinkAudioInputPacket *audioPacket) = 0; }; #else /* C style interface */ typedef struct IDeckLinkInputCallback_v7_6Vtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IDeckLinkInputCallback_v7_6 * This, /* [in] */ REFIID riid, /* [annotation][iid_is][out] */ __RPC__deref_out void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IDeckLinkInputCallback_v7_6 * This); ULONG ( STDMETHODCALLTYPE *Release )( IDeckLinkInputCallback_v7_6 * This); HRESULT ( STDMETHODCALLTYPE *VideoInputFormatChanged )( IDeckLinkInputCallback_v7_6 * This, /* [in] */ BMDVideoInputFormatChangedEvents notificationEvents, /* [in] */ IDeckLinkDisplayMode_v7_6 *newDisplayMode, /* [in] */ BMDDetectedVideoInputFormatFlags detectedSignalFlags); HRESULT ( STDMETHODCALLTYPE *VideoInputFrameArrived )( IDeckLinkInputCallback_v7_6 * This, /* [in] */ IDeckLinkVideoInputFrame_v7_6 *videoFrame, /* [in] */ IDeckLinkAudioInputPacket *audioPacket); END_INTERFACE } IDeckLinkInputCallback_v7_6Vtbl; interface IDeckLinkInputCallback_v7_6 { CONST_VTBL struct IDeckLinkInputCallback_v7_6Vtbl *lpVtbl; }; #ifdef COBJMACROS #define IDeckLinkInputCallback_v7_6_QueryInterface(This,riid,ppvObject) \ ( (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) ) #define IDeckLinkInputCallback_v7_6_AddRef(This) \ ( (This)->lpVtbl -> AddRef(This) ) #define IDeckLinkInputCallback_v7_6_Release(This) \ ( (This)->lpVtbl -> Release(This) ) #define IDeckLinkInputCallback_v7_6_VideoInputFormatChanged(This,notificationEvents,newDisplayMode,detectedSignalFlags) \ ( (This)->lpVtbl -> VideoInputFormatChanged(This,notificationEvents,newDisplayMode,detectedSignalFlags) ) #define IDeckLinkInputCallback_v7_6_VideoInputFrameArrived(This,videoFrame,audioPacket) \ ( (This)->lpVtbl -> VideoInputFrameArrived(This,videoFrame,audioPacket) ) #endif /* COBJMACROS */ #endif /* C style interface */ #endif /* __IDeckLinkInputCallback_v7_6_INTERFACE_DEFINED__ */ EXTERN_C const CLSID CLSID_CDeckLinkGLScreenPreviewHelper_v7_6; #ifdef __cplusplus class DECLSPEC_UUID("D398CEE7-4434-4CA3-9BA6-5AE34556B905") CDeckLinkGLScreenPreviewHelper_v7_6; #endif EXTERN_C const CLSID CLSID_CDeckLinkVideoConversion_v7_6; #ifdef __cplusplus class DECLSPEC_UUID("FFA84F77-73BE-4FB7-B03E-B5E44B9F759B") CDeckLinkVideoConversion_v7_6; #endif #ifndef __IDeckLinkInputCallback_v7_3_INTERFACE_DEFINED__ #define __IDeckLinkInputCallback_v7_3_INTERFACE_DEFINED__ /* interface IDeckLinkInputCallback_v7_3 */ /* [helpstring][uuid][object] */ EXTERN_C const IID IID_IDeckLinkInputCallback_v7_3; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("FD6F311D-4D00-444B-9ED4-1F25B5730AD0") IDeckLinkInputCallback_v7_3 : public IUnknown { public: virtual HRESULT STDMETHODCALLTYPE VideoInputFormatChanged( /* [in] */ BMDVideoInputFormatChangedEvents notificationEvents, /* [in] */ IDeckLinkDisplayMode_v7_6 *newDisplayMode, /* [in] */ BMDDetectedVideoInputFormatFlags detectedSignalFlags) = 0; virtual HRESULT STDMETHODCALLTYPE VideoInputFrameArrived( /* [in] */ IDeckLinkVideoInputFrame_v7_3 *videoFrame, /* [in] */ IDeckLinkAudioInputPacket *audioPacket) = 0; }; #else /* C style interface */ typedef struct IDeckLinkInputCallback_v7_3Vtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IDeckLinkInputCallback_v7_3 * This, /* [in] */ REFIID riid, /* [annotation][iid_is][out] */ __RPC__deref_out void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IDeckLinkInputCallback_v7_3 * This); ULONG ( STDMETHODCALLTYPE *Release )( IDeckLinkInputCallback_v7_3 * This); HRESULT ( STDMETHODCALLTYPE *VideoInputFormatChanged )( IDeckLinkInputCallback_v7_3 * This, /* [in] */ BMDVideoInputFormatChangedEvents notificationEvents, /* [in] */ IDeckLinkDisplayMode_v7_6 *newDisplayMode, /* [in] */ BMDDetectedVideoInputFormatFlags detectedSignalFlags); HRESULT ( STDMETHODCALLTYPE *VideoInputFrameArrived )( IDeckLinkInputCallback_v7_3 * This, /* [in] */ IDeckLinkVideoInputFrame_v7_3 *videoFrame, /* [in] */ IDeckLinkAudioInputPacket *audioPacket); END_INTERFACE } IDeckLinkInputCallback_v7_3Vtbl; interface IDeckLinkInputCallback_v7_3 { CONST_VTBL struct IDeckLinkInputCallback_v7_3Vtbl *lpVtbl; }; #ifdef COBJMACROS #define IDeckLinkInputCallback_v7_3_QueryInterface(This,riid,ppvObject) \ ( (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) ) #define IDeckLinkInputCallback_v7_3_AddRef(This) \ ( (This)->lpVtbl -> AddRef(This) ) #define IDeckLinkInputCallback_v7_3_Release(This) \ ( (This)->lpVtbl -> Release(This) ) #define IDeckLinkInputCallback_v7_3_VideoInputFormatChanged(This,notificationEvents,newDisplayMode,detectedSignalFlags) \ ( (This)->lpVtbl -> VideoInputFormatChanged(This,notificationEvents,newDisplayMode,detectedSignalFlags) ) #define IDeckLinkInputCallback_v7_3_VideoInputFrameArrived(This,videoFrame,audioPacket) \ ( (This)->lpVtbl -> VideoInputFrameArrived(This,videoFrame,audioPacket) ) #endif /* COBJMACROS */ #endif /* C style interface */ #endif /* __IDeckLinkInputCallback_v7_3_INTERFACE_DEFINED__ */ #ifndef __IDeckLinkOutput_v7_3_INTERFACE_DEFINED__ #define __IDeckLinkOutput_v7_3_INTERFACE_DEFINED__ /* interface IDeckLinkOutput_v7_3 */ /* [helpstring][local][uuid][object] */ EXTERN_C const IID IID_IDeckLinkOutput_v7_3; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("271C65E3-C323-4344-A30F-D908BCB20AA3") IDeckLinkOutput_v7_3 : public IUnknown { public: virtual HRESULT STDMETHODCALLTYPE DoesSupportVideoMode( BMDDisplayMode displayMode, BMDPixelFormat pixelFormat, /* [out] */ BMDDisplayModeSupport *result) = 0; virtual HRESULT STDMETHODCALLTYPE GetDisplayModeIterator( /* [out] */ IDeckLinkDisplayModeIterator_v7_6 **iterator) = 0; virtual HRESULT STDMETHODCALLTYPE SetScreenPreviewCallback( /* [in] */ IDeckLinkScreenPreviewCallback *previewCallback) = 0; virtual HRESULT STDMETHODCALLTYPE EnableVideoOutput( BMDDisplayMode displayMode, BMDVideoOutputFlags flags) = 0; virtual HRESULT STDMETHODCALLTYPE DisableVideoOutput( void) = 0; virtual HRESULT STDMETHODCALLTYPE SetVideoOutputFrameMemoryAllocator( /* [in] */ IDeckLinkMemoryAllocator *theAllocator) = 0; virtual HRESULT STDMETHODCALLTYPE CreateVideoFrame( long width, long height, long rowBytes, BMDPixelFormat pixelFormat, BMDFrameFlags flags, /* [out] */ IDeckLinkMutableVideoFrame_v7_6 **outFrame) = 0; virtual HRESULT STDMETHODCALLTYPE CreateAncillaryData( BMDPixelFormat pixelFormat, /* [out] */ IDeckLinkVideoFrameAncillary **outBuffer) = 0; virtual HRESULT STDMETHODCALLTYPE DisplayVideoFrameSync( /* [in] */ IDeckLinkVideoFrame_v7_6 *theFrame) = 0; virtual HRESULT STDMETHODCALLTYPE ScheduleVideoFrame( /* [in] */ IDeckLinkVideoFrame_v7_6 *theFrame, BMDTimeValue displayTime, BMDTimeValue displayDuration, BMDTimeScale timeScale) = 0; virtual HRESULT STDMETHODCALLTYPE SetScheduledFrameCompletionCallback( /* [in] */ IDeckLinkVideoOutputCallback *theCallback) = 0; virtual HRESULT STDMETHODCALLTYPE GetBufferedVideoFrameCount( /* [out] */ unsigned long *bufferedFrameCount) = 0; virtual HRESULT STDMETHODCALLTYPE EnableAudioOutput( BMDAudioSampleRate sampleRate, BMDAudioSampleType sampleType, unsigned long channelCount, BMDAudioOutputStreamType streamType) = 0; virtual HRESULT STDMETHODCALLTYPE DisableAudioOutput( void) = 0; virtual HRESULT STDMETHODCALLTYPE WriteAudioSamplesSync( /* [in] */ void *buffer, unsigned long sampleFrameCount, /* [out] */ unsigned long *sampleFramesWritten) = 0; virtual HRESULT STDMETHODCALLTYPE BeginAudioPreroll( void) = 0; virtual HRESULT STDMETHODCALLTYPE EndAudioPreroll( void) = 0; virtual HRESULT STDMETHODCALLTYPE ScheduleAudioSamples( /* [in] */ void *buffer, unsigned long sampleFrameCount, BMDTimeValue streamTime, BMDTimeScale timeScale, /* [out] */ unsigned long *sampleFramesWritten) = 0; virtual HRESULT STDMETHODCALLTYPE GetBufferedAudioSampleFrameCount( /* [out] */ unsigned long *bufferedSampleFrameCount) = 0; virtual HRESULT STDMETHODCALLTYPE FlushBufferedAudioSamples( void) = 0; virtual HRESULT STDMETHODCALLTYPE SetAudioCallback( /* [in] */ IDeckLinkAudioOutputCallback *theCallback) = 0; virtual HRESULT STDMETHODCALLTYPE StartScheduledPlayback( BMDTimeValue playbackStartTime, BMDTimeScale timeScale, double playbackSpeed) = 0; virtual HRESULT STDMETHODCALLTYPE StopScheduledPlayback( BMDTimeValue stopPlaybackAtTime, /* [out] */ BMDTimeValue *actualStopTime, BMDTimeScale timeScale) = 0; virtual HRESULT STDMETHODCALLTYPE IsScheduledPlaybackRunning( /* [out] */ BOOL *active) = 0; virtual HRESULT STDMETHODCALLTYPE GetHardwareReferenceClock( BMDTimeScale desiredTimeScale, /* [out] */ BMDTimeValue *elapsedTimeSinceSchedulerBegan) = 0; }; #else /* C style interface */ typedef struct IDeckLinkOutput_v7_3Vtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IDeckLinkOutput_v7_3 * This, /* [in] */ REFIID riid, /* [annotation][iid_is][out] */ __RPC__deref_out void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IDeckLinkOutput_v7_3 * This); ULONG ( STDMETHODCALLTYPE *Release )( IDeckLinkOutput_v7_3 * This); HRESULT ( STDMETHODCALLTYPE *DoesSupportVideoMode )( IDeckLinkOutput_v7_3 * This, BMDDisplayMode displayMode, BMDPixelFormat pixelFormat, /* [out] */ BMDDisplayModeSupport *result); HRESULT ( STDMETHODCALLTYPE *GetDisplayModeIterator )( IDeckLinkOutput_v7_3 * This, /* [out] */ IDeckLinkDisplayModeIterator_v7_6 **iterator); HRESULT ( STDMETHODCALLTYPE *SetScreenPreviewCallback )( IDeckLinkOutput_v7_3 * This, /* [in] */ IDeckLinkScreenPreviewCallback *previewCallback); HRESULT ( STDMETHODCALLTYPE *EnableVideoOutput )( IDeckLinkOutput_v7_3 * This, BMDDisplayMode displayMode, BMDVideoOutputFlags flags); HRESULT ( STDMETHODCALLTYPE *DisableVideoOutput )( IDeckLinkOutput_v7_3 * This); HRESULT ( STDMETHODCALLTYPE *SetVideoOutputFrameMemoryAllocator )( IDeckLinkOutput_v7_3 * This, /* [in] */ IDeckLinkMemoryAllocator *theAllocator); HRESULT ( STDMETHODCALLTYPE *CreateVideoFrame )( IDeckLinkOutput_v7_3 * This, long width, long height, long rowBytes, BMDPixelFormat pixelFormat, BMDFrameFlags flags, /* [out] */ IDeckLinkMutableVideoFrame_v7_6 **outFrame); HRESULT ( STDMETHODCALLTYPE *CreateAncillaryData )( IDeckLinkOutput_v7_3 * This, BMDPixelFormat pixelFormat, /* [out] */ IDeckLinkVideoFrameAncillary **outBuffer); HRESULT ( STDMETHODCALLTYPE *DisplayVideoFrameSync )( IDeckLinkOutput_v7_3 * This, /* [in] */ IDeckLinkVideoFrame_v7_6 *theFrame); HRESULT ( STDMETHODCALLTYPE *ScheduleVideoFrame )( IDeckLinkOutput_v7_3 * This, /* [in] */ IDeckLinkVideoFrame_v7_6 *theFrame, BMDTimeValue displayTime, BMDTimeValue displayDuration, BMDTimeScale timeScale); HRESULT ( STDMETHODCALLTYPE *SetScheduledFrameCompletionCallback )( IDeckLinkOutput_v7_3 * This, /* [in] */ IDeckLinkVideoOutputCallback *theCallback); HRESULT ( STDMETHODCALLTYPE *GetBufferedVideoFrameCount )( IDeckLinkOutput_v7_3 * This, /* [out] */ unsigned long *bufferedFrameCount); HRESULT ( STDMETHODCALLTYPE *EnableAudioOutput )( IDeckLinkOutput_v7_3 * This, BMDAudioSampleRate sampleRate, BMDAudioSampleType sampleType, unsigned long channelCount, BMDAudioOutputStreamType streamType); HRESULT ( STDMETHODCALLTYPE *DisableAudioOutput )( IDeckLinkOutput_v7_3 * This); HRESULT ( STDMETHODCALLTYPE *WriteAudioSamplesSync )( IDeckLinkOutput_v7_3 * This, /* [in] */ void *buffer, unsigned long sampleFrameCount, /* [out] */ unsigned long *sampleFramesWritten); HRESULT ( STDMETHODCALLTYPE *BeginAudioPreroll )( IDeckLinkOutput_v7_3 * This); HRESULT ( STDMETHODCALLTYPE *EndAudioPreroll )( IDeckLinkOutput_v7_3 * This); HRESULT ( STDMETHODCALLTYPE *ScheduleAudioSamples )( IDeckLinkOutput_v7_3 * This, /* [in] */ void *buffer, unsigned long sampleFrameCount, BMDTimeValue streamTime, BMDTimeScale timeScale, /* [out] */ unsigned long *sampleFramesWritten); HRESULT ( STDMETHODCALLTYPE *GetBufferedAudioSampleFrameCount )( IDeckLinkOutput_v7_3 * This, /* [out] */ unsigned long *bufferedSampleFrameCount); HRESULT ( STDMETHODCALLTYPE *FlushBufferedAudioSamples )( IDeckLinkOutput_v7_3 * This); HRESULT ( STDMETHODCALLTYPE *SetAudioCallback )( IDeckLinkOutput_v7_3 * This, /* [in] */ IDeckLinkAudioOutputCallback *theCallback); HRESULT ( STDMETHODCALLTYPE *StartScheduledPlayback )( IDeckLinkOutput_v7_3 * This, BMDTimeValue playbackStartTime, BMDTimeScale timeScale, double playbackSpeed); HRESULT ( STDMETHODCALLTYPE *StopScheduledPlayback )( IDeckLinkOutput_v7_3 * This, BMDTimeValue stopPlaybackAtTime, /* [out] */ BMDTimeValue *actualStopTime, BMDTimeScale timeScale); HRESULT ( STDMETHODCALLTYPE *IsScheduledPlaybackRunning )( IDeckLinkOutput_v7_3 * This, /* [out] */ BOOL *active); HRESULT ( STDMETHODCALLTYPE *GetHardwareReferenceClock )( IDeckLinkOutput_v7_3 * This, BMDTimeScale desiredTimeScale, /* [out] */ BMDTimeValue *elapsedTimeSinceSchedulerBegan); END_INTERFACE } IDeckLinkOutput_v7_3Vtbl; interface IDeckLinkOutput_v7_3 { CONST_VTBL struct IDeckLinkOutput_v7_3Vtbl *lpVtbl; }; #ifdef COBJMACROS #define IDeckLinkOutput_v7_3_QueryInterface(This,riid,ppvObject) \ ( (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) ) #define IDeckLinkOutput_v7_3_AddRef(This) \ ( (This)->lpVtbl -> AddRef(This) ) #define IDeckLinkOutput_v7_3_Release(This) \ ( (This)->lpVtbl -> Release(This) ) #define IDeckLinkOutput_v7_3_DoesSupportVideoMode(This,displayMode,pixelFormat,result) \ ( (This)->lpVtbl -> DoesSupportVideoMode(This,displayMode,pixelFormat,result) ) #define IDeckLinkOutput_v7_3_GetDisplayModeIterator(This,iterator) \ ( (This)->lpVtbl -> GetDisplayModeIterator(This,iterator) ) #define IDeckLinkOutput_v7_3_SetScreenPreviewCallback(This,previewCallback) \ ( (This)->lpVtbl -> SetScreenPreviewCallback(This,previewCallback) ) #define IDeckLinkOutput_v7_3_EnableVideoOutput(This,displayMode,flags) \ ( (This)->lpVtbl -> EnableVideoOutput(This,displayMode,flags) ) #define IDeckLinkOutput_v7_3_DisableVideoOutput(This) \ ( (This)->lpVtbl -> DisableVideoOutput(This) ) #define IDeckLinkOutput_v7_3_SetVideoOutputFrameMemoryAllocator(This,theAllocator) \ ( (This)->lpVtbl -> SetVideoOutputFrameMemoryAllocator(This,theAllocator) ) #define IDeckLinkOutput_v7_3_CreateVideoFrame(This,width,height,rowBytes,pixelFormat,flags,outFrame) \ ( (This)->lpVtbl -> CreateVideoFrame(This,width,height,rowBytes,pixelFormat,flags,outFrame) ) #define IDeckLinkOutput_v7_3_CreateAncillaryData(This,pixelFormat,outBuffer) \ ( (This)->lpVtbl -> CreateAncillaryData(This,pixelFormat,outBuffer) ) #define IDeckLinkOutput_v7_3_DisplayVideoFrameSync(This,theFrame) \ ( (This)->lpVtbl -> DisplayVideoFrameSync(This,theFrame) ) #define IDeckLinkOutput_v7_3_ScheduleVideoFrame(This,theFrame,displayTime,displayDuration,timeScale) \ ( (This)->lpVtbl -> ScheduleVideoFrame(This,theFrame,displayTime,displayDuration,timeScale) ) #define IDeckLinkOutput_v7_3_SetScheduledFrameCompletionCallback(This,theCallback) \ ( (This)->lpVtbl -> SetScheduledFrameCompletionCallback(This,theCallback) ) #define IDeckLinkOutput_v7_3_GetBufferedVideoFrameCount(This,bufferedFrameCount) \ ( (This)->lpVtbl -> GetBufferedVideoFrameCount(This,bufferedFrameCount) ) #define IDeckLinkOutput_v7_3_EnableAudioOutput(This,sampleRate,sampleType,channelCount,streamType) \ ( (This)->lpVtbl -> EnableAudioOutput(This,sampleRate,sampleType,channelCount,streamType) ) #define IDeckLinkOutput_v7_3_DisableAudioOutput(This) \ ( (This)->lpVtbl -> DisableAudioOutput(This) ) #define IDeckLinkOutput_v7_3_WriteAudioSamplesSync(This,buffer,sampleFrameCount,sampleFramesWritten) \ ( (This)->lpVtbl -> WriteAudioSamplesSync(This,buffer,sampleFrameCount,sampleFramesWritten) ) #define IDeckLinkOutput_v7_3_BeginAudioPreroll(This) \ ( (This)->lpVtbl -> BeginAudioPreroll(This) ) #define IDeckLinkOutput_v7_3_EndAudioPreroll(This) \ ( (This)->lpVtbl -> EndAudioPreroll(This) ) #define IDeckLinkOutput_v7_3_ScheduleAudioSamples(This,buffer,sampleFrameCount,streamTime,timeScale,sampleFramesWritten) \ ( (This)->lpVtbl -> ScheduleAudioSamples(This,buffer,sampleFrameCount,streamTime,timeScale,sampleFramesWritten) ) #define IDeckLinkOutput_v7_3_GetBufferedAudioSampleFrameCount(This,bufferedSampleFrameCount) \ ( (This)->lpVtbl -> GetBufferedAudioSampleFrameCount(This,bufferedSampleFrameCount) ) #define IDeckLinkOutput_v7_3_FlushBufferedAudioSamples(This) \ ( (This)->lpVtbl -> FlushBufferedAudioSamples(This) ) #define IDeckLinkOutput_v7_3_SetAudioCallback(This,theCallback) \ ( (This)->lpVtbl -> SetAudioCallback(This,theCallback) ) #define IDeckLinkOutput_v7_3_StartScheduledPlayback(This,playbackStartTime,timeScale,playbackSpeed) \ ( (This)->lpVtbl -> StartScheduledPlayback(This,playbackStartTime,timeScale,playbackSpeed) ) #define IDeckLinkOutput_v7_3_StopScheduledPlayback(This,stopPlaybackAtTime,actualStopTime,timeScale) \ ( (This)->lpVtbl -> StopScheduledPlayback(This,stopPlaybackAtTime,actualStopTime,timeScale) ) #define IDeckLinkOutput_v7_3_IsScheduledPlaybackRunning(This,active) \ ( (This)->lpVtbl -> IsScheduledPlaybackRunning(This,active) ) #define IDeckLinkOutput_v7_3_GetHardwareReferenceClock(This,desiredTimeScale,elapsedTimeSinceSchedulerBegan) \ ( (This)->lpVtbl -> GetHardwareReferenceClock(This,desiredTimeScale,elapsedTimeSinceSchedulerBegan) ) #endif /* COBJMACROS */ #endif /* C style interface */ #endif /* __IDeckLinkOutput_v7_3_INTERFACE_DEFINED__ */ #ifndef __IDeckLinkInput_v7_3_INTERFACE_DEFINED__ #define __IDeckLinkInput_v7_3_INTERFACE_DEFINED__ /* interface IDeckLinkInput_v7_3 */ /* [helpstring][uuid][object] */ EXTERN_C const IID IID_IDeckLinkInput_v7_3; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("4973F012-9925-458C-871C-18774CDBBECB") IDeckLinkInput_v7_3 : public IUnknown { public: virtual HRESULT STDMETHODCALLTYPE DoesSupportVideoMode( BMDDisplayMode displayMode, BMDPixelFormat pixelFormat, /* [out] */ BMDDisplayModeSupport *result) = 0; virtual HRESULT STDMETHODCALLTYPE GetDisplayModeIterator( /* [out] */ IDeckLinkDisplayModeIterator_v7_6 **iterator) = 0; virtual HRESULT STDMETHODCALLTYPE SetScreenPreviewCallback( /* [in] */ IDeckLinkScreenPreviewCallback *previewCallback) = 0; virtual HRESULT STDMETHODCALLTYPE EnableVideoInput( BMDDisplayMode displayMode, BMDPixelFormat pixelFormat, BMDVideoInputFlags flags) = 0; virtual HRESULT STDMETHODCALLTYPE DisableVideoInput( void) = 0; virtual HRESULT STDMETHODCALLTYPE GetAvailableVideoFrameCount( /* [out] */ unsigned long *availableFrameCount) = 0; virtual HRESULT STDMETHODCALLTYPE EnableAudioInput( BMDAudioSampleRate sampleRate, BMDAudioSampleType sampleType, unsigned long channelCount) = 0; virtual HRESULT STDMETHODCALLTYPE DisableAudioInput( void) = 0; virtual HRESULT STDMETHODCALLTYPE GetAvailableAudioSampleFrameCount( /* [out] */ unsigned long *availableSampleFrameCount) = 0; virtual HRESULT STDMETHODCALLTYPE StartStreams( void) = 0; virtual HRESULT STDMETHODCALLTYPE StopStreams( void) = 0; virtual HRESULT STDMETHODCALLTYPE PauseStreams( void) = 0; virtual HRESULT STDMETHODCALLTYPE FlushStreams( void) = 0; virtual HRESULT STDMETHODCALLTYPE SetCallback( /* [in] */ IDeckLinkInputCallback_v7_3 *theCallback) = 0; }; #else /* C style interface */ typedef struct IDeckLinkInput_v7_3Vtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IDeckLinkInput_v7_3 * This, /* [in] */ REFIID riid, /* [annotation][iid_is][out] */ __RPC__deref_out void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IDeckLinkInput_v7_3 * This); ULONG ( STDMETHODCALLTYPE *Release )( IDeckLinkInput_v7_3 * This); HRESULT ( STDMETHODCALLTYPE *DoesSupportVideoMode )( IDeckLinkInput_v7_3 * This, BMDDisplayMode displayMode, BMDPixelFormat pixelFormat, /* [out] */ BMDDisplayModeSupport *result); HRESULT ( STDMETHODCALLTYPE *GetDisplayModeIterator )( IDeckLinkInput_v7_3 * This, /* [out] */ IDeckLinkDisplayModeIterator_v7_6 **iterator); HRESULT ( STDMETHODCALLTYPE *SetScreenPreviewCallback )( IDeckLinkInput_v7_3 * This, /* [in] */ IDeckLinkScreenPreviewCallback *previewCallback); HRESULT ( STDMETHODCALLTYPE *EnableVideoInput )( IDeckLinkInput_v7_3 * This, BMDDisplayMode displayMode, BMDPixelFormat pixelFormat, BMDVideoInputFlags flags); HRESULT ( STDMETHODCALLTYPE *DisableVideoInput )( IDeckLinkInput_v7_3 * This); HRESULT ( STDMETHODCALLTYPE *GetAvailableVideoFrameCount )( IDeckLinkInput_v7_3 * This, /* [out] */ unsigned long *availableFrameCount); HRESULT ( STDMETHODCALLTYPE *EnableAudioInput )( IDeckLinkInput_v7_3 * This, BMDAudioSampleRate sampleRate, BMDAudioSampleType sampleType, unsigned long channelCount); HRESULT ( STDMETHODCALLTYPE *DisableAudioInput )( IDeckLinkInput_v7_3 * This); HRESULT ( STDMETHODCALLTYPE *GetAvailableAudioSampleFrameCount )( IDeckLinkInput_v7_3 * This, /* [out] */ unsigned long *availableSampleFrameCount); HRESULT ( STDMETHODCALLTYPE *StartStreams )( IDeckLinkInput_v7_3 * This); HRESULT ( STDMETHODCALLTYPE *StopStreams )( IDeckLinkInput_v7_3 * This); HRESULT ( STDMETHODCALLTYPE *PauseStreams )( IDeckLinkInput_v7_3 * This); HRESULT ( STDMETHODCALLTYPE *FlushStreams )( IDeckLinkInput_v7_3 * This); HRESULT ( STDMETHODCALLTYPE *SetCallback )( IDeckLinkInput_v7_3 * This, /* [in] */ IDeckLinkInputCallback_v7_3 *theCallback); END_INTERFACE } IDeckLinkInput_v7_3Vtbl; interface IDeckLinkInput_v7_3 { CONST_VTBL struct IDeckLinkInput_v7_3Vtbl *lpVtbl; }; #ifdef COBJMACROS #define IDeckLinkInput_v7_3_QueryInterface(This,riid,ppvObject) \ ( (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) ) #define IDeckLinkInput_v7_3_AddRef(This) \ ( (This)->lpVtbl -> AddRef(This) ) #define IDeckLinkInput_v7_3_Release(This) \ ( (This)->lpVtbl -> Release(This) ) #define IDeckLinkInput_v7_3_DoesSupportVideoMode(This,displayMode,pixelFormat,result) \ ( (This)->lpVtbl -> DoesSupportVideoMode(This,displayMode,pixelFormat,result) ) #define IDeckLinkInput_v7_3_GetDisplayModeIterator(This,iterator) \ ( (This)->lpVtbl -> GetDisplayModeIterator(This,iterator) ) #define IDeckLinkInput_v7_3_SetScreenPreviewCallback(This,previewCallback) \ ( (This)->lpVtbl -> SetScreenPreviewCallback(This,previewCallback) ) #define IDeckLinkInput_v7_3_EnableVideoInput(This,displayMode,pixelFormat,flags) \ ( (This)->lpVtbl -> EnableVideoInput(This,displayMode,pixelFormat,flags) ) #define IDeckLinkInput_v7_3_DisableVideoInput(This) \ ( (This)->lpVtbl -> DisableVideoInput(This) ) #define IDeckLinkInput_v7_3_GetAvailableVideoFrameCount(This,availableFrameCount) \ ( (This)->lpVtbl -> GetAvailableVideoFrameCount(This,availableFrameCount) ) #define IDeckLinkInput_v7_3_EnableAudioInput(This,sampleRate,sampleType,channelCount) \ ( (This)->lpVtbl -> EnableAudioInput(This,sampleRate,sampleType,channelCount) ) #define IDeckLinkInput_v7_3_DisableAudioInput(This) \ ( (This)->lpVtbl -> DisableAudioInput(This) ) #define IDeckLinkInput_v7_3_GetAvailableAudioSampleFrameCount(This,availableSampleFrameCount) \ ( (This)->lpVtbl -> GetAvailableAudioSampleFrameCount(This,availableSampleFrameCount) ) #define IDeckLinkInput_v7_3_StartStreams(This) \ ( (This)->lpVtbl -> StartStreams(This) ) #define IDeckLinkInput_v7_3_StopStreams(This) \ ( (This)->lpVtbl -> StopStreams(This) ) #define IDeckLinkInput_v7_3_PauseStreams(This) \ ( (This)->lpVtbl -> PauseStreams(This) ) #define IDeckLinkInput_v7_3_FlushStreams(This) \ ( (This)->lpVtbl -> FlushStreams(This) ) #define IDeckLinkInput_v7_3_SetCallback(This,theCallback) \ ( (This)->lpVtbl -> SetCallback(This,theCallback) ) #endif /* COBJMACROS */ #endif /* C style interface */ #endif /* __IDeckLinkInput_v7_3_INTERFACE_DEFINED__ */ #ifndef __IDeckLinkVideoInputFrame_v7_3_INTERFACE_DEFINED__ #define __IDeckLinkVideoInputFrame_v7_3_INTERFACE_DEFINED__ /* interface IDeckLinkVideoInputFrame_v7_3 */ /* [helpstring][local][uuid][object] */ EXTERN_C const IID IID_IDeckLinkVideoInputFrame_v7_3; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("CF317790-2894-11DE-8C30-0800200C9A66") IDeckLinkVideoInputFrame_v7_3 : public IDeckLinkVideoFrame_v7_6 { public: virtual HRESULT STDMETHODCALLTYPE GetStreamTime( /* [out] */ BMDTimeValue *frameTime, /* [out] */ BMDTimeValue *frameDuration, BMDTimeScale timeScale) = 0; }; #else /* C style interface */ typedef struct IDeckLinkVideoInputFrame_v7_3Vtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IDeckLinkVideoInputFrame_v7_3 * This, /* [in] */ REFIID riid, /* [annotation][iid_is][out] */ __RPC__deref_out void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IDeckLinkVideoInputFrame_v7_3 * This); ULONG ( STDMETHODCALLTYPE *Release )( IDeckLinkVideoInputFrame_v7_3 * This); long ( STDMETHODCALLTYPE *GetWidth )( IDeckLinkVideoInputFrame_v7_3 * This); long ( STDMETHODCALLTYPE *GetHeight )( IDeckLinkVideoInputFrame_v7_3 * This); long ( STDMETHODCALLTYPE *GetRowBytes )( IDeckLinkVideoInputFrame_v7_3 * This); BMDPixelFormat ( STDMETHODCALLTYPE *GetPixelFormat )( IDeckLinkVideoInputFrame_v7_3 * This); BMDFrameFlags ( STDMETHODCALLTYPE *GetFlags )( IDeckLinkVideoInputFrame_v7_3 * This); HRESULT ( STDMETHODCALLTYPE *GetBytes )( IDeckLinkVideoInputFrame_v7_3 * This, /* [out] */ void **buffer); HRESULT ( STDMETHODCALLTYPE *GetTimecode )( IDeckLinkVideoInputFrame_v7_3 * This, BMDTimecodeFormat format, /* [out] */ IDeckLinkTimecode_v7_6 **timecode); HRESULT ( STDMETHODCALLTYPE *GetAncillaryData )( IDeckLinkVideoInputFrame_v7_3 * This, /* [out] */ IDeckLinkVideoFrameAncillary **ancillary); HRESULT ( STDMETHODCALLTYPE *GetStreamTime )( IDeckLinkVideoInputFrame_v7_3 * This, /* [out] */ BMDTimeValue *frameTime, /* [out] */ BMDTimeValue *frameDuration, BMDTimeScale timeScale); END_INTERFACE } IDeckLinkVideoInputFrame_v7_3Vtbl; interface IDeckLinkVideoInputFrame_v7_3 { CONST_VTBL struct IDeckLinkVideoInputFrame_v7_3Vtbl *lpVtbl; }; #ifdef COBJMACROS #define IDeckLinkVideoInputFrame_v7_3_QueryInterface(This,riid,ppvObject) \ ( (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) ) #define IDeckLinkVideoInputFrame_v7_3_AddRef(This) \ ( (This)->lpVtbl -> AddRef(This) ) #define IDeckLinkVideoInputFrame_v7_3_Release(This) \ ( (This)->lpVtbl -> Release(This) ) #define IDeckLinkVideoInputFrame_v7_3_GetWidth(This) \ ( (This)->lpVtbl -> GetWidth(This) ) #define IDeckLinkVideoInputFrame_v7_3_GetHeight(This) \ ( (This)->lpVtbl -> GetHeight(This) ) #define IDeckLinkVideoInputFrame_v7_3_GetRowBytes(This) \ ( (This)->lpVtbl -> GetRowBytes(This) ) #define IDeckLinkVideoInputFrame_v7_3_GetPixelFormat(This) \ ( (This)->lpVtbl -> GetPixelFormat(This) ) #define IDeckLinkVideoInputFrame_v7_3_GetFlags(This) \ ( (This)->lpVtbl -> GetFlags(This) ) #define IDeckLinkVideoInputFrame_v7_3_GetBytes(This,buffer) \ ( (This)->lpVtbl -> GetBytes(This,buffer) ) #define IDeckLinkVideoInputFrame_v7_3_GetTimecode(This,format,timecode) \ ( (This)->lpVtbl -> GetTimecode(This,format,timecode) ) #define IDeckLinkVideoInputFrame_v7_3_GetAncillaryData(This,ancillary) \ ( (This)->lpVtbl -> GetAncillaryData(This,ancillary) ) #define IDeckLinkVideoInputFrame_v7_3_GetStreamTime(This,frameTime,frameDuration,timeScale) \ ( (This)->lpVtbl -> GetStreamTime(This,frameTime,frameDuration,timeScale) ) #endif /* COBJMACROS */ #endif /* C style interface */ #endif /* __IDeckLinkVideoInputFrame_v7_3_INTERFACE_DEFINED__ */ #ifndef __IDeckLinkDisplayModeIterator_v7_1_INTERFACE_DEFINED__ #define __IDeckLinkDisplayModeIterator_v7_1_INTERFACE_DEFINED__ /* interface IDeckLinkDisplayModeIterator_v7_1 */ /* [helpstring][uuid][object] */ EXTERN_C const IID IID_IDeckLinkDisplayModeIterator_v7_1; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("B28131B6-59AC-4857-B5AC-CD75D5883E2F") IDeckLinkDisplayModeIterator_v7_1 : public IUnknown { public: virtual HRESULT STDMETHODCALLTYPE Next( /* [out] */ IDeckLinkDisplayMode_v7_1 **deckLinkDisplayMode) = 0; }; #else /* C style interface */ typedef struct IDeckLinkDisplayModeIterator_v7_1Vtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IDeckLinkDisplayModeIterator_v7_1 * This, /* [in] */ REFIID riid, /* [annotation][iid_is][out] */ __RPC__deref_out void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IDeckLinkDisplayModeIterator_v7_1 * This); ULONG ( STDMETHODCALLTYPE *Release )( IDeckLinkDisplayModeIterator_v7_1 * This); HRESULT ( STDMETHODCALLTYPE *Next )( IDeckLinkDisplayModeIterator_v7_1 * This, /* [out] */ IDeckLinkDisplayMode_v7_1 **deckLinkDisplayMode); END_INTERFACE } IDeckLinkDisplayModeIterator_v7_1Vtbl; interface IDeckLinkDisplayModeIterator_v7_1 { CONST_VTBL struct IDeckLinkDisplayModeIterator_v7_1Vtbl *lpVtbl; }; #ifdef COBJMACROS #define IDeckLinkDisplayModeIterator_v7_1_QueryInterface(This,riid,ppvObject) \ ( (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) ) #define IDeckLinkDisplayModeIterator_v7_1_AddRef(This) \ ( (This)->lpVtbl -> AddRef(This) ) #define IDeckLinkDisplayModeIterator_v7_1_Release(This) \ ( (This)->lpVtbl -> Release(This) ) #define IDeckLinkDisplayModeIterator_v7_1_Next(This,deckLinkDisplayMode) \ ( (This)->lpVtbl -> Next(This,deckLinkDisplayMode) ) #endif /* COBJMACROS */ #endif /* C style interface */ #endif /* __IDeckLinkDisplayModeIterator_v7_1_INTERFACE_DEFINED__ */ #ifndef __IDeckLinkDisplayMode_v7_1_INTERFACE_DEFINED__ #define __IDeckLinkDisplayMode_v7_1_INTERFACE_DEFINED__ /* interface IDeckLinkDisplayMode_v7_1 */ /* [helpstring][uuid][object] */ EXTERN_C const IID IID_IDeckLinkDisplayMode_v7_1; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("AF0CD6D5-8376-435E-8433-54F9DD530AC3") IDeckLinkDisplayMode_v7_1 : public IUnknown { public: virtual HRESULT STDMETHODCALLTYPE GetName( /* [out] */ BSTR *name) = 0; virtual BMDDisplayMode STDMETHODCALLTYPE GetDisplayMode( void) = 0; virtual long STDMETHODCALLTYPE GetWidth( void) = 0; virtual long STDMETHODCALLTYPE GetHeight( void) = 0; virtual HRESULT STDMETHODCALLTYPE GetFrameRate( /* [out] */ BMDTimeValue *frameDuration, /* [out] */ BMDTimeScale *timeScale) = 0; }; #else /* C style interface */ typedef struct IDeckLinkDisplayMode_v7_1Vtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IDeckLinkDisplayMode_v7_1 * This, /* [in] */ REFIID riid, /* [annotation][iid_is][out] */ __RPC__deref_out void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IDeckLinkDisplayMode_v7_1 * This); ULONG ( STDMETHODCALLTYPE *Release )( IDeckLinkDisplayMode_v7_1 * This); HRESULT ( STDMETHODCALLTYPE *GetName )( IDeckLinkDisplayMode_v7_1 * This, /* [out] */ BSTR *name); BMDDisplayMode ( STDMETHODCALLTYPE *GetDisplayMode )( IDeckLinkDisplayMode_v7_1 * This); long ( STDMETHODCALLTYPE *GetWidth )( IDeckLinkDisplayMode_v7_1 * This); long ( STDMETHODCALLTYPE *GetHeight )( IDeckLinkDisplayMode_v7_1 * This); HRESULT ( STDMETHODCALLTYPE *GetFrameRate )( IDeckLinkDisplayMode_v7_1 * This, /* [out] */ BMDTimeValue *frameDuration, /* [out] */ BMDTimeScale *timeScale); END_INTERFACE } IDeckLinkDisplayMode_v7_1Vtbl; interface IDeckLinkDisplayMode_v7_1 { CONST_VTBL struct IDeckLinkDisplayMode_v7_1Vtbl *lpVtbl; }; #ifdef COBJMACROS #define IDeckLinkDisplayMode_v7_1_QueryInterface(This,riid,ppvObject) \ ( (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) ) #define IDeckLinkDisplayMode_v7_1_AddRef(This) \ ( (This)->lpVtbl -> AddRef(This) ) #define IDeckLinkDisplayMode_v7_1_Release(This) \ ( (This)->lpVtbl -> Release(This) ) #define IDeckLinkDisplayMode_v7_1_GetName(This,name) \ ( (This)->lpVtbl -> GetName(This,name) ) #define IDeckLinkDisplayMode_v7_1_GetDisplayMode(This) \ ( (This)->lpVtbl -> GetDisplayMode(This) ) #define IDeckLinkDisplayMode_v7_1_GetWidth(This) \ ( (This)->lpVtbl -> GetWidth(This) ) #define IDeckLinkDisplayMode_v7_1_GetHeight(This) \ ( (This)->lpVtbl -> GetHeight(This) ) #define IDeckLinkDisplayMode_v7_1_GetFrameRate(This,frameDuration,timeScale) \ ( (This)->lpVtbl -> GetFrameRate(This,frameDuration,timeScale) ) #endif /* COBJMACROS */ #endif /* C style interface */ #endif /* __IDeckLinkDisplayMode_v7_1_INTERFACE_DEFINED__ */ #ifndef __IDeckLinkVideoFrame_v7_1_INTERFACE_DEFINED__ #define __IDeckLinkVideoFrame_v7_1_INTERFACE_DEFINED__ /* interface IDeckLinkVideoFrame_v7_1 */ /* [helpstring][local][uuid][object] */ EXTERN_C const IID IID_IDeckLinkVideoFrame_v7_1; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("333F3A10-8C2D-43CF-B79D-46560FEEA1CE") IDeckLinkVideoFrame_v7_1 : public IUnknown { public: virtual long STDMETHODCALLTYPE GetWidth( void) = 0; virtual long STDMETHODCALLTYPE GetHeight( void) = 0; virtual long STDMETHODCALLTYPE GetRowBytes( void) = 0; virtual BMDPixelFormat STDMETHODCALLTYPE GetPixelFormat( void) = 0; virtual BMDFrameFlags STDMETHODCALLTYPE GetFlags( void) = 0; virtual HRESULT STDMETHODCALLTYPE GetBytes( void **buffer) = 0; }; #else /* C style interface */ typedef struct IDeckLinkVideoFrame_v7_1Vtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IDeckLinkVideoFrame_v7_1 * This, /* [in] */ REFIID riid, /* [annotation][iid_is][out] */ __RPC__deref_out void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IDeckLinkVideoFrame_v7_1 * This); ULONG ( STDMETHODCALLTYPE *Release )( IDeckLinkVideoFrame_v7_1 * This); long ( STDMETHODCALLTYPE *GetWidth )( IDeckLinkVideoFrame_v7_1 * This); long ( STDMETHODCALLTYPE *GetHeight )( IDeckLinkVideoFrame_v7_1 * This); long ( STDMETHODCALLTYPE *GetRowBytes )( IDeckLinkVideoFrame_v7_1 * This); BMDPixelFormat ( STDMETHODCALLTYPE *GetPixelFormat )( IDeckLinkVideoFrame_v7_1 * This); BMDFrameFlags ( STDMETHODCALLTYPE *GetFlags )( IDeckLinkVideoFrame_v7_1 * This); HRESULT ( STDMETHODCALLTYPE *GetBytes )( IDeckLinkVideoFrame_v7_1 * This, void **buffer); END_INTERFACE } IDeckLinkVideoFrame_v7_1Vtbl; interface IDeckLinkVideoFrame_v7_1 { CONST_VTBL struct IDeckLinkVideoFrame_v7_1Vtbl *lpVtbl; }; #ifdef COBJMACROS #define IDeckLinkVideoFrame_v7_1_QueryInterface(This,riid,ppvObject) \ ( (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) ) #define IDeckLinkVideoFrame_v7_1_AddRef(This) \ ( (This)->lpVtbl -> AddRef(This) ) #define IDeckLinkVideoFrame_v7_1_Release(This) \ ( (This)->lpVtbl -> Release(This) ) #define IDeckLinkVideoFrame_v7_1_GetWidth(This) \ ( (This)->lpVtbl -> GetWidth(This) ) #define IDeckLinkVideoFrame_v7_1_GetHeight(This) \ ( (This)->lpVtbl -> GetHeight(This) ) #define IDeckLinkVideoFrame_v7_1_GetRowBytes(This) \ ( (This)->lpVtbl -> GetRowBytes(This) ) #define IDeckLinkVideoFrame_v7_1_GetPixelFormat(This) \ ( (This)->lpVtbl -> GetPixelFormat(This) ) #define IDeckLinkVideoFrame_v7_1_GetFlags(This) \ ( (This)->lpVtbl -> GetFlags(This) ) #define IDeckLinkVideoFrame_v7_1_GetBytes(This,buffer) \ ( (This)->lpVtbl -> GetBytes(This,buffer) ) #endif /* COBJMACROS */ #endif /* C style interface */ #endif /* __IDeckLinkVideoFrame_v7_1_INTERFACE_DEFINED__ */ #ifndef __IDeckLinkVideoInputFrame_v7_1_INTERFACE_DEFINED__ #define __IDeckLinkVideoInputFrame_v7_1_INTERFACE_DEFINED__ /* interface IDeckLinkVideoInputFrame_v7_1 */ /* [helpstring][local][uuid][object] */ EXTERN_C const IID IID_IDeckLinkVideoInputFrame_v7_1; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("C8B41D95-8848-40EE-9B37-6E3417FB114B") IDeckLinkVideoInputFrame_v7_1 : public IDeckLinkVideoFrame_v7_1 { public: virtual HRESULT STDMETHODCALLTYPE GetFrameTime( BMDTimeValue *frameTime, BMDTimeValue *frameDuration, BMDTimeScale timeScale) = 0; }; #else /* C style interface */ typedef struct IDeckLinkVideoInputFrame_v7_1Vtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IDeckLinkVideoInputFrame_v7_1 * This, /* [in] */ REFIID riid, /* [annotation][iid_is][out] */ __RPC__deref_out void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IDeckLinkVideoInputFrame_v7_1 * This); ULONG ( STDMETHODCALLTYPE *Release )( IDeckLinkVideoInputFrame_v7_1 * This); long ( STDMETHODCALLTYPE *GetWidth )( IDeckLinkVideoInputFrame_v7_1 * This); long ( STDMETHODCALLTYPE *GetHeight )( IDeckLinkVideoInputFrame_v7_1 * This); long ( STDMETHODCALLTYPE *GetRowBytes )( IDeckLinkVideoInputFrame_v7_1 * This); BMDPixelFormat ( STDMETHODCALLTYPE *GetPixelFormat )( IDeckLinkVideoInputFrame_v7_1 * This); BMDFrameFlags ( STDMETHODCALLTYPE *GetFlags )( IDeckLinkVideoInputFrame_v7_1 * This); HRESULT ( STDMETHODCALLTYPE *GetBytes )( IDeckLinkVideoInputFrame_v7_1 * This, void **buffer); HRESULT ( STDMETHODCALLTYPE *GetFrameTime )( IDeckLinkVideoInputFrame_v7_1 * This, BMDTimeValue *frameTime, BMDTimeValue *frameDuration, BMDTimeScale timeScale); END_INTERFACE } IDeckLinkVideoInputFrame_v7_1Vtbl; interface IDeckLinkVideoInputFrame_v7_1 { CONST_VTBL struct IDeckLinkVideoInputFrame_v7_1Vtbl *lpVtbl; }; #ifdef COBJMACROS #define IDeckLinkVideoInputFrame_v7_1_QueryInterface(This,riid,ppvObject) \ ( (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) ) #define IDeckLinkVideoInputFrame_v7_1_AddRef(This) \ ( (This)->lpVtbl -> AddRef(This) ) #define IDeckLinkVideoInputFrame_v7_1_Release(This) \ ( (This)->lpVtbl -> Release(This) ) #define IDeckLinkVideoInputFrame_v7_1_GetWidth(This) \ ( (This)->lpVtbl -> GetWidth(This) ) #define IDeckLinkVideoInputFrame_v7_1_GetHeight(This) \ ( (This)->lpVtbl -> GetHeight(This) ) #define IDeckLinkVideoInputFrame_v7_1_GetRowBytes(This) \ ( (This)->lpVtbl -> GetRowBytes(This) ) #define IDeckLinkVideoInputFrame_v7_1_GetPixelFormat(This) \ ( (This)->lpVtbl -> GetPixelFormat(This) ) #define IDeckLinkVideoInputFrame_v7_1_GetFlags(This) \ ( (This)->lpVtbl -> GetFlags(This) ) #define IDeckLinkVideoInputFrame_v7_1_GetBytes(This,buffer) \ ( (This)->lpVtbl -> GetBytes(This,buffer) ) #define IDeckLinkVideoInputFrame_v7_1_GetFrameTime(This,frameTime,frameDuration,timeScale) \ ( (This)->lpVtbl -> GetFrameTime(This,frameTime,frameDuration,timeScale) ) #endif /* COBJMACROS */ #endif /* C style interface */ #endif /* __IDeckLinkVideoInputFrame_v7_1_INTERFACE_DEFINED__ */ #ifndef __IDeckLinkAudioInputPacket_v7_1_INTERFACE_DEFINED__ #define __IDeckLinkAudioInputPacket_v7_1_INTERFACE_DEFINED__ /* interface IDeckLinkAudioInputPacket_v7_1 */ /* [helpstring][local][uuid][object] */ EXTERN_C const IID IID_IDeckLinkAudioInputPacket_v7_1; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("C86DE4F6-A29F-42E3-AB3A-1363E29F0788") IDeckLinkAudioInputPacket_v7_1 : public IUnknown { public: virtual long STDMETHODCALLTYPE GetSampleCount( void) = 0; virtual HRESULT STDMETHODCALLTYPE GetBytes( void **buffer) = 0; virtual HRESULT STDMETHODCALLTYPE GetAudioPacketTime( BMDTimeValue *packetTime, BMDTimeScale timeScale) = 0; }; #else /* C style interface */ typedef struct IDeckLinkAudioInputPacket_v7_1Vtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IDeckLinkAudioInputPacket_v7_1 * This, /* [in] */ REFIID riid, /* [annotation][iid_is][out] */ __RPC__deref_out void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IDeckLinkAudioInputPacket_v7_1 * This); ULONG ( STDMETHODCALLTYPE *Release )( IDeckLinkAudioInputPacket_v7_1 * This); long ( STDMETHODCALLTYPE *GetSampleCount )( IDeckLinkAudioInputPacket_v7_1 * This); HRESULT ( STDMETHODCALLTYPE *GetBytes )( IDeckLinkAudioInputPacket_v7_1 * This, void **buffer); HRESULT ( STDMETHODCALLTYPE *GetAudioPacketTime )( IDeckLinkAudioInputPacket_v7_1 * This, BMDTimeValue *packetTime, BMDTimeScale timeScale); END_INTERFACE } IDeckLinkAudioInputPacket_v7_1Vtbl; interface IDeckLinkAudioInputPacket_v7_1 { CONST_VTBL struct IDeckLinkAudioInputPacket_v7_1Vtbl *lpVtbl; }; #ifdef COBJMACROS #define IDeckLinkAudioInputPacket_v7_1_QueryInterface(This,riid,ppvObject) \ ( (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) ) #define IDeckLinkAudioInputPacket_v7_1_AddRef(This) \ ( (This)->lpVtbl -> AddRef(This) ) #define IDeckLinkAudioInputPacket_v7_1_Release(This) \ ( (This)->lpVtbl -> Release(This) ) #define IDeckLinkAudioInputPacket_v7_1_GetSampleCount(This) \ ( (This)->lpVtbl -> GetSampleCount(This) ) #define IDeckLinkAudioInputPacket_v7_1_GetBytes(This,buffer) \ ( (This)->lpVtbl -> GetBytes(This,buffer) ) #define IDeckLinkAudioInputPacket_v7_1_GetAudioPacketTime(This,packetTime,timeScale) \ ( (This)->lpVtbl -> GetAudioPacketTime(This,packetTime,timeScale) ) #endif /* COBJMACROS */ #endif /* C style interface */ #endif /* __IDeckLinkAudioInputPacket_v7_1_INTERFACE_DEFINED__ */ #ifndef __IDeckLinkVideoOutputCallback_v7_1_INTERFACE_DEFINED__ #define __IDeckLinkVideoOutputCallback_v7_1_INTERFACE_DEFINED__ /* interface IDeckLinkVideoOutputCallback_v7_1 */ /* [helpstring][uuid][object] */ EXTERN_C const IID IID_IDeckLinkVideoOutputCallback_v7_1; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("EBD01AFA-E4B0-49C6-A01D-EDB9D1B55FD9") IDeckLinkVideoOutputCallback_v7_1 : public IUnknown { public: virtual HRESULT STDMETHODCALLTYPE ScheduledFrameCompleted( /* [in] */ IDeckLinkVideoFrame_v7_1 *completedFrame, /* [in] */ BMDOutputFrameCompletionResult result) = 0; }; #else /* C style interface */ typedef struct IDeckLinkVideoOutputCallback_v7_1Vtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IDeckLinkVideoOutputCallback_v7_1 * This, /* [in] */ REFIID riid, /* [annotation][iid_is][out] */ __RPC__deref_out void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IDeckLinkVideoOutputCallback_v7_1 * This); ULONG ( STDMETHODCALLTYPE *Release )( IDeckLinkVideoOutputCallback_v7_1 * This); HRESULT ( STDMETHODCALLTYPE *ScheduledFrameCompleted )( IDeckLinkVideoOutputCallback_v7_1 * This, /* [in] */ IDeckLinkVideoFrame_v7_1 *completedFrame, /* [in] */ BMDOutputFrameCompletionResult result); END_INTERFACE } IDeckLinkVideoOutputCallback_v7_1Vtbl; interface IDeckLinkVideoOutputCallback_v7_1 { CONST_VTBL struct IDeckLinkVideoOutputCallback_v7_1Vtbl *lpVtbl; }; #ifdef COBJMACROS #define IDeckLinkVideoOutputCallback_v7_1_QueryInterface(This,riid,ppvObject) \ ( (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) ) #define IDeckLinkVideoOutputCallback_v7_1_AddRef(This) \ ( (This)->lpVtbl -> AddRef(This) ) #define IDeckLinkVideoOutputCallback_v7_1_Release(This) \ ( (This)->lpVtbl -> Release(This) ) #define IDeckLinkVideoOutputCallback_v7_1_ScheduledFrameCompleted(This,completedFrame,result) \ ( (This)->lpVtbl -> ScheduledFrameCompleted(This,completedFrame,result) ) #endif /* COBJMACROS */ #endif /* C style interface */ #endif /* __IDeckLinkVideoOutputCallback_v7_1_INTERFACE_DEFINED__ */ #ifndef __IDeckLinkInputCallback_v7_1_INTERFACE_DEFINED__ #define __IDeckLinkInputCallback_v7_1_INTERFACE_DEFINED__ /* interface IDeckLinkInputCallback_v7_1 */ /* [helpstring][uuid][object] */ EXTERN_C const IID IID_IDeckLinkInputCallback_v7_1; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("7F94F328-5ED4-4E9F-9729-76A86BDC99CC") IDeckLinkInputCallback_v7_1 : public IUnknown { public: virtual HRESULT STDMETHODCALLTYPE VideoInputFrameArrived( /* [in] */ IDeckLinkVideoInputFrame_v7_1 *videoFrame, /* [in] */ IDeckLinkAudioInputPacket_v7_1 *audioPacket) = 0; }; #else /* C style interface */ typedef struct IDeckLinkInputCallback_v7_1Vtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IDeckLinkInputCallback_v7_1 * This, /* [in] */ REFIID riid, /* [annotation][iid_is][out] */ __RPC__deref_out void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IDeckLinkInputCallback_v7_1 * This); ULONG ( STDMETHODCALLTYPE *Release )( IDeckLinkInputCallback_v7_1 * This); HRESULT ( STDMETHODCALLTYPE *VideoInputFrameArrived )( IDeckLinkInputCallback_v7_1 * This, /* [in] */ IDeckLinkVideoInputFrame_v7_1 *videoFrame, /* [in] */ IDeckLinkAudioInputPacket_v7_1 *audioPacket); END_INTERFACE } IDeckLinkInputCallback_v7_1Vtbl; interface IDeckLinkInputCallback_v7_1 { CONST_VTBL struct IDeckLinkInputCallback_v7_1Vtbl *lpVtbl; }; #ifdef COBJMACROS #define IDeckLinkInputCallback_v7_1_QueryInterface(This,riid,ppvObject) \ ( (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) ) #define IDeckLinkInputCallback_v7_1_AddRef(This) \ ( (This)->lpVtbl -> AddRef(This) ) #define IDeckLinkInputCallback_v7_1_Release(This) \ ( (This)->lpVtbl -> Release(This) ) #define IDeckLinkInputCallback_v7_1_VideoInputFrameArrived(This,videoFrame,audioPacket) \ ( (This)->lpVtbl -> VideoInputFrameArrived(This,videoFrame,audioPacket) ) #endif /* COBJMACROS */ #endif /* C style interface */ #endif /* __IDeckLinkInputCallback_v7_1_INTERFACE_DEFINED__ */ #ifndef __IDeckLinkOutput_v7_1_INTERFACE_DEFINED__ #define __IDeckLinkOutput_v7_1_INTERFACE_DEFINED__ /* interface IDeckLinkOutput_v7_1 */ /* [helpstring][local][uuid][object] */ EXTERN_C const IID IID_IDeckLinkOutput_v7_1; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("AE5B3E9B-4E1E-4535-B6E8-480FF52F6CE5") IDeckLinkOutput_v7_1 : public IUnknown { public: virtual HRESULT STDMETHODCALLTYPE DoesSupportVideoMode( BMDDisplayMode displayMode, BMDPixelFormat pixelFormat, /* [out] */ BMDDisplayModeSupport *result) = 0; virtual HRESULT STDMETHODCALLTYPE GetDisplayModeIterator( /* [out] */ IDeckLinkDisplayModeIterator_v7_1 **iterator) = 0; virtual HRESULT STDMETHODCALLTYPE EnableVideoOutput( BMDDisplayMode displayMode) = 0; virtual HRESULT STDMETHODCALLTYPE DisableVideoOutput( void) = 0; virtual HRESULT STDMETHODCALLTYPE SetVideoOutputFrameMemoryAllocator( /* [in] */ IDeckLinkMemoryAllocator *theAllocator) = 0; virtual HRESULT STDMETHODCALLTYPE CreateVideoFrame( long width, long height, long rowBytes, BMDPixelFormat pixelFormat, BMDFrameFlags flags, IDeckLinkVideoFrame_v7_1 **outFrame) = 0; virtual HRESULT STDMETHODCALLTYPE CreateVideoFrameFromBuffer( void *buffer, long width, long height, long rowBytes, BMDPixelFormat pixelFormat, BMDFrameFlags flags, IDeckLinkVideoFrame_v7_1 **outFrame) = 0; virtual HRESULT STDMETHODCALLTYPE DisplayVideoFrameSync( IDeckLinkVideoFrame_v7_1 *theFrame) = 0; virtual HRESULT STDMETHODCALLTYPE ScheduleVideoFrame( IDeckLinkVideoFrame_v7_1 *theFrame, BMDTimeValue displayTime, BMDTimeValue displayDuration, BMDTimeScale timeScale) = 0; virtual HRESULT STDMETHODCALLTYPE SetScheduledFrameCompletionCallback( /* [in] */ IDeckLinkVideoOutputCallback_v7_1 *theCallback) = 0; virtual HRESULT STDMETHODCALLTYPE EnableAudioOutput( BMDAudioSampleRate sampleRate, BMDAudioSampleType sampleType, unsigned long channelCount) = 0; virtual HRESULT STDMETHODCALLTYPE DisableAudioOutput( void) = 0; virtual HRESULT STDMETHODCALLTYPE WriteAudioSamplesSync( void *buffer, unsigned long sampleFrameCount, /* [out] */ unsigned long *sampleFramesWritten) = 0; virtual HRESULT STDMETHODCALLTYPE BeginAudioPreroll( void) = 0; virtual HRESULT STDMETHODCALLTYPE EndAudioPreroll( void) = 0; virtual HRESULT STDMETHODCALLTYPE ScheduleAudioSamples( void *buffer, unsigned long sampleFrameCount, BMDTimeValue streamTime, BMDTimeScale timeScale, /* [out] */ unsigned long *sampleFramesWritten) = 0; virtual HRESULT STDMETHODCALLTYPE GetBufferedAudioSampleFrameCount( /* [out] */ unsigned long *bufferedSampleCount) = 0; virtual HRESULT STDMETHODCALLTYPE FlushBufferedAudioSamples( void) = 0; virtual HRESULT STDMETHODCALLTYPE SetAudioCallback( /* [in] */ IDeckLinkAudioOutputCallback *theCallback) = 0; virtual HRESULT STDMETHODCALLTYPE StartScheduledPlayback( BMDTimeValue playbackStartTime, BMDTimeScale timeScale, double playbackSpeed) = 0; virtual HRESULT STDMETHODCALLTYPE StopScheduledPlayback( BMDTimeValue stopPlaybackAtTime, BMDTimeValue *actualStopTime, BMDTimeScale timeScale) = 0; virtual HRESULT STDMETHODCALLTYPE GetHardwareReferenceClock( BMDTimeScale desiredTimeScale, BMDTimeValue *elapsedTimeSinceSchedulerBegan) = 0; }; #else /* C style interface */ typedef struct IDeckLinkOutput_v7_1Vtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IDeckLinkOutput_v7_1 * This, /* [in] */ REFIID riid, /* [annotation][iid_is][out] */ __RPC__deref_out void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IDeckLinkOutput_v7_1 * This); ULONG ( STDMETHODCALLTYPE *Release )( IDeckLinkOutput_v7_1 * This); HRESULT ( STDMETHODCALLTYPE *DoesSupportVideoMode )( IDeckLinkOutput_v7_1 * This, BMDDisplayMode displayMode, BMDPixelFormat pixelFormat, /* [out] */ BMDDisplayModeSupport *result); HRESULT ( STDMETHODCALLTYPE *GetDisplayModeIterator )( IDeckLinkOutput_v7_1 * This, /* [out] */ IDeckLinkDisplayModeIterator_v7_1 **iterator); HRESULT ( STDMETHODCALLTYPE *EnableVideoOutput )( IDeckLinkOutput_v7_1 * This, BMDDisplayMode displayMode); HRESULT ( STDMETHODCALLTYPE *DisableVideoOutput )( IDeckLinkOutput_v7_1 * This); HRESULT ( STDMETHODCALLTYPE *SetVideoOutputFrameMemoryAllocator )( IDeckLinkOutput_v7_1 * This, /* [in] */ IDeckLinkMemoryAllocator *theAllocator); HRESULT ( STDMETHODCALLTYPE *CreateVideoFrame )( IDeckLinkOutput_v7_1 * This, long width, long height, long rowBytes, BMDPixelFormat pixelFormat, BMDFrameFlags flags, IDeckLinkVideoFrame_v7_1 **outFrame); HRESULT ( STDMETHODCALLTYPE *CreateVideoFrameFromBuffer )( IDeckLinkOutput_v7_1 * This, void *buffer, long width, long height, long rowBytes, BMDPixelFormat pixelFormat, BMDFrameFlags flags, IDeckLinkVideoFrame_v7_1 **outFrame); HRESULT ( STDMETHODCALLTYPE *DisplayVideoFrameSync )( IDeckLinkOutput_v7_1 * This, IDeckLinkVideoFrame_v7_1 *theFrame); HRESULT ( STDMETHODCALLTYPE *ScheduleVideoFrame )( IDeckLinkOutput_v7_1 * This, IDeckLinkVideoFrame_v7_1 *theFrame, BMDTimeValue displayTime, BMDTimeValue displayDuration, BMDTimeScale timeScale); HRESULT ( STDMETHODCALLTYPE *SetScheduledFrameCompletionCallback )( IDeckLinkOutput_v7_1 * This, /* [in] */ IDeckLinkVideoOutputCallback_v7_1 *theCallback); HRESULT ( STDMETHODCALLTYPE *EnableAudioOutput )( IDeckLinkOutput_v7_1 * This, BMDAudioSampleRate sampleRate, BMDAudioSampleType sampleType, unsigned long channelCount); HRESULT ( STDMETHODCALLTYPE *DisableAudioOutput )( IDeckLinkOutput_v7_1 * This); HRESULT ( STDMETHODCALLTYPE *WriteAudioSamplesSync )( IDeckLinkOutput_v7_1 * This, void *buffer, unsigned long sampleFrameCount, /* [out] */ unsigned long *sampleFramesWritten); HRESULT ( STDMETHODCALLTYPE *BeginAudioPreroll )( IDeckLinkOutput_v7_1 * This); HRESULT ( STDMETHODCALLTYPE *EndAudioPreroll )( IDeckLinkOutput_v7_1 * This); HRESULT ( STDMETHODCALLTYPE *ScheduleAudioSamples )( IDeckLinkOutput_v7_1 * This, void *buffer, unsigned long sampleFrameCount, BMDTimeValue streamTime, BMDTimeScale timeScale, /* [out] */ unsigned long *sampleFramesWritten); HRESULT ( STDMETHODCALLTYPE *GetBufferedAudioSampleFrameCount )( IDeckLinkOutput_v7_1 * This, /* [out] */ unsigned long *bufferedSampleCount); HRESULT ( STDMETHODCALLTYPE *FlushBufferedAudioSamples )( IDeckLinkOutput_v7_1 * This); HRESULT ( STDMETHODCALLTYPE *SetAudioCallback )( IDeckLinkOutput_v7_1 * This, /* [in] */ IDeckLinkAudioOutputCallback *theCallback); HRESULT ( STDMETHODCALLTYPE *StartScheduledPlayback )( IDeckLinkOutput_v7_1 * This, BMDTimeValue playbackStartTime, BMDTimeScale timeScale, double playbackSpeed); HRESULT ( STDMETHODCALLTYPE *StopScheduledPlayback )( IDeckLinkOutput_v7_1 * This, BMDTimeValue stopPlaybackAtTime, BMDTimeValue *actualStopTime, BMDTimeScale timeScale); HRESULT ( STDMETHODCALLTYPE *GetHardwareReferenceClock )( IDeckLinkOutput_v7_1 * This, BMDTimeScale desiredTimeScale, BMDTimeValue *elapsedTimeSinceSchedulerBegan); END_INTERFACE } IDeckLinkOutput_v7_1Vtbl; interface IDeckLinkOutput_v7_1 { CONST_VTBL struct IDeckLinkOutput_v7_1Vtbl *lpVtbl; }; #ifdef COBJMACROS #define IDeckLinkOutput_v7_1_QueryInterface(This,riid,ppvObject) \ ( (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) ) #define IDeckLinkOutput_v7_1_AddRef(This) \ ( (This)->lpVtbl -> AddRef(This) ) #define IDeckLinkOutput_v7_1_Release(This) \ ( (This)->lpVtbl -> Release(This) ) #define IDeckLinkOutput_v7_1_DoesSupportVideoMode(This,displayMode,pixelFormat,result) \ ( (This)->lpVtbl -> DoesSupportVideoMode(This,displayMode,pixelFormat,result) ) #define IDeckLinkOutput_v7_1_GetDisplayModeIterator(This,iterator) \ ( (This)->lpVtbl -> GetDisplayModeIterator(This,iterator) ) #define IDeckLinkOutput_v7_1_EnableVideoOutput(This,displayMode) \ ( (This)->lpVtbl -> EnableVideoOutput(This,displayMode) ) #define IDeckLinkOutput_v7_1_DisableVideoOutput(This) \ ( (This)->lpVtbl -> DisableVideoOutput(This) ) #define IDeckLinkOutput_v7_1_SetVideoOutputFrameMemoryAllocator(This,theAllocator) \ ( (This)->lpVtbl -> SetVideoOutputFrameMemoryAllocator(This,theAllocator) ) #define IDeckLinkOutput_v7_1_CreateVideoFrame(This,width,height,rowBytes,pixelFormat,flags,outFrame) \ ( (This)->lpVtbl -> CreateVideoFrame(This,width,height,rowBytes,pixelFormat,flags,outFrame) ) #define IDeckLinkOutput_v7_1_CreateVideoFrameFromBuffer(This,buffer,width,height,rowBytes,pixelFormat,flags,outFrame) \ ( (This)->lpVtbl -> CreateVideoFrameFromBuffer(This,buffer,width,height,rowBytes,pixelFormat,flags,outFrame) ) #define IDeckLinkOutput_v7_1_DisplayVideoFrameSync(This,theFrame) \ ( (This)->lpVtbl -> DisplayVideoFrameSync(This,theFrame) ) #define IDeckLinkOutput_v7_1_ScheduleVideoFrame(This,theFrame,displayTime,displayDuration,timeScale) \ ( (This)->lpVtbl -> ScheduleVideoFrame(This,theFrame,displayTime,displayDuration,timeScale) ) #define IDeckLinkOutput_v7_1_SetScheduledFrameCompletionCallback(This,theCallback) \ ( (This)->lpVtbl -> SetScheduledFrameCompletionCallback(This,theCallback) ) #define IDeckLinkOutput_v7_1_EnableAudioOutput(This,sampleRate,sampleType,channelCount) \ ( (This)->lpVtbl -> EnableAudioOutput(This,sampleRate,sampleType,channelCount) ) #define IDeckLinkOutput_v7_1_DisableAudioOutput(This) \ ( (This)->lpVtbl -> DisableAudioOutput(This) ) #define IDeckLinkOutput_v7_1_WriteAudioSamplesSync(This,buffer,sampleFrameCount,sampleFramesWritten) \ ( (This)->lpVtbl -> WriteAudioSamplesSync(This,buffer,sampleFrameCount,sampleFramesWritten) ) #define IDeckLinkOutput_v7_1_BeginAudioPreroll(This) \ ( (This)->lpVtbl -> BeginAudioPreroll(This) ) #define IDeckLinkOutput_v7_1_EndAudioPreroll(This) \ ( (This)->lpVtbl -> EndAudioPreroll(This) ) #define IDeckLinkOutput_v7_1_ScheduleAudioSamples(This,buffer,sampleFrameCount,streamTime,timeScale,sampleFramesWritten) \ ( (This)->lpVtbl -> ScheduleAudioSamples(This,buffer,sampleFrameCount,streamTime,timeScale,sampleFramesWritten) ) #define IDeckLinkOutput_v7_1_GetBufferedAudioSampleFrameCount(This,bufferedSampleCount) \ ( (This)->lpVtbl -> GetBufferedAudioSampleFrameCount(This,bufferedSampleCount) ) #define IDeckLinkOutput_v7_1_FlushBufferedAudioSamples(This) \ ( (This)->lpVtbl -> FlushBufferedAudioSamples(This) ) #define IDeckLinkOutput_v7_1_SetAudioCallback(This,theCallback) \ ( (This)->lpVtbl -> SetAudioCallback(This,theCallback) ) #define IDeckLinkOutput_v7_1_StartScheduledPlayback(This,playbackStartTime,timeScale,playbackSpeed) \ ( (This)->lpVtbl -> StartScheduledPlayback(This,playbackStartTime,timeScale,playbackSpeed) ) #define IDeckLinkOutput_v7_1_StopScheduledPlayback(This,stopPlaybackAtTime,actualStopTime,timeScale) \ ( (This)->lpVtbl -> StopScheduledPlayback(This,stopPlaybackAtTime,actualStopTime,timeScale) ) #define IDeckLinkOutput_v7_1_GetHardwareReferenceClock(This,desiredTimeScale,elapsedTimeSinceSchedulerBegan) \ ( (This)->lpVtbl -> GetHardwareReferenceClock(This,desiredTimeScale,elapsedTimeSinceSchedulerBegan) ) #endif /* COBJMACROS */ #endif /* C style interface */ #endif /* __IDeckLinkOutput_v7_1_INTERFACE_DEFINED__ */ #ifndef __IDeckLinkInput_v7_1_INTERFACE_DEFINED__ #define __IDeckLinkInput_v7_1_INTERFACE_DEFINED__ /* interface IDeckLinkInput_v7_1 */ /* [helpstring][uuid][object] */ EXTERN_C const IID IID_IDeckLinkInput_v7_1; #if defined(__cplusplus) && !defined(CINTERFACE) MIDL_INTERFACE("2B54EDEF-5B32-429F-BA11-BB990596EACD") IDeckLinkInput_v7_1 : public IUnknown { public: virtual HRESULT STDMETHODCALLTYPE DoesSupportVideoMode( BMDDisplayMode displayMode, BMDPixelFormat pixelFormat, /* [out] */ BMDDisplayModeSupport *result) = 0; virtual HRESULT STDMETHODCALLTYPE GetDisplayModeIterator( /* [out] */ IDeckLinkDisplayModeIterator_v7_1 **iterator) = 0; virtual HRESULT STDMETHODCALLTYPE EnableVideoInput( BMDDisplayMode displayMode, BMDPixelFormat pixelFormat, BMDVideoInputFlags flags) = 0; virtual HRESULT STDMETHODCALLTYPE DisableVideoInput( void) = 0; virtual HRESULT STDMETHODCALLTYPE EnableAudioInput( BMDAudioSampleRate sampleRate, BMDAudioSampleType sampleType, unsigned long channelCount) = 0; virtual HRESULT STDMETHODCALLTYPE DisableAudioInput( void) = 0; virtual HRESULT STDMETHODCALLTYPE ReadAudioSamples( void *buffer, unsigned long sampleFrameCount, /* [out] */ unsigned long *sampleFramesRead, /* [out] */ BMDTimeValue *audioPacketTime, BMDTimeScale timeScale) = 0; virtual HRESULT STDMETHODCALLTYPE GetBufferedAudioSampleFrameCount( /* [out] */ unsigned long *bufferedSampleCount) = 0; virtual HRESULT STDMETHODCALLTYPE StartStreams( void) = 0; virtual HRESULT STDMETHODCALLTYPE StopStreams( void) = 0; virtual HRESULT STDMETHODCALLTYPE PauseStreams( void) = 0; virtual HRESULT STDMETHODCALLTYPE SetCallback( /* [in] */ IDeckLinkInputCallback_v7_1 *theCallback) = 0; }; #else /* C style interface */ typedef struct IDeckLinkInput_v7_1Vtbl { BEGIN_INTERFACE HRESULT ( STDMETHODCALLTYPE *QueryInterface )( IDeckLinkInput_v7_1 * This, /* [in] */ REFIID riid, /* [annotation][iid_is][out] */ __RPC__deref_out void **ppvObject); ULONG ( STDMETHODCALLTYPE *AddRef )( IDeckLinkInput_v7_1 * This); ULONG ( STDMETHODCALLTYPE *Release )( IDeckLinkInput_v7_1 * This); HRESULT ( STDMETHODCALLTYPE *DoesSupportVideoMode )( IDeckLinkInput_v7_1 * This, BMDDisplayMode displayMode, BMDPixelFormat pixelFormat, /* [out] */ BMDDisplayModeSupport *result); HRESULT ( STDMETHODCALLTYPE *GetDisplayModeIterator )( IDeckLinkInput_v7_1 * This, /* [out] */ IDeckLinkDisplayModeIterator_v7_1 **iterator); HRESULT ( STDMETHODCALLTYPE *EnableVideoInput )( IDeckLinkInput_v7_1 * This, BMDDisplayMode displayMode, BMDPixelFormat pixelFormat, BMDVideoInputFlags flags); HRESULT ( STDMETHODCALLTYPE *DisableVideoInput )( IDeckLinkInput_v7_1 * This); HRESULT ( STDMETHODCALLTYPE *EnableAudioInput )( IDeckLinkInput_v7_1 * This, BMDAudioSampleRate sampleRate, BMDAudioSampleType sampleType, unsigned long channelCount); HRESULT ( STDMETHODCALLTYPE *DisableAudioInput )( IDeckLinkInput_v7_1 * This); HRESULT ( STDMETHODCALLTYPE *ReadAudioSamples )( IDeckLinkInput_v7_1 * This, void *buffer, unsigned long sampleFrameCount, /* [out] */ unsigned long *sampleFramesRead, /* [out] */ BMDTimeValue *audioPacketTime, BMDTimeScale timeScale); HRESULT ( STDMETHODCALLTYPE *GetBufferedAudioSampleFrameCount )( IDeckLinkInput_v7_1 * This, /* [out] */ unsigned long *bufferedSampleCount); HRESULT ( STDMETHODCALLTYPE *StartStreams )( IDeckLinkInput_v7_1 * This); HRESULT ( STDMETHODCALLTYPE *StopStreams )( IDeckLinkInput_v7_1 * This); HRESULT ( STDMETHODCALLTYPE *PauseStreams )( IDeckLinkInput_v7_1 * This); HRESULT ( STDMETHODCALLTYPE *SetCallback )( IDeckLinkInput_v7_1 * This, /* [in] */ IDeckLinkInputCallback_v7_1 *theCallback); END_INTERFACE } IDeckLinkInput_v7_1Vtbl; interface IDeckLinkInput_v7_1 { CONST_VTBL struct IDeckLinkInput_v7_1Vtbl *lpVtbl; }; #ifdef COBJMACROS #define IDeckLinkInput_v7_1_QueryInterface(This,riid,ppvObject) \ ( (This)->lpVtbl -> QueryInterface(This,riid,ppvObject) ) #define IDeckLinkInput_v7_1_AddRef(This) \ ( (This)->lpVtbl -> AddRef(This) ) #define IDeckLinkInput_v7_1_Release(This) \ ( (This)->lpVtbl -> Release(This) ) #define IDeckLinkInput_v7_1_DoesSupportVideoMode(This,displayMode,pixelFormat,result) \ ( (This)->lpVtbl -> DoesSupportVideoMode(This,displayMode,pixelFormat,result) ) #define IDeckLinkInput_v7_1_GetDisplayModeIterator(This,iterator) \ ( (This)->lpVtbl -> GetDisplayModeIterator(This,iterator) ) #define IDeckLinkInput_v7_1_EnableVideoInput(This,displayMode,pixelFormat,flags) \ ( (This)->lpVtbl -> EnableVideoInput(This,displayMode,pixelFormat,flags) ) #define IDeckLinkInput_v7_1_DisableVideoInput(This) \ ( (This)->lpVtbl -> DisableVideoInput(This) ) #define IDeckLinkInput_v7_1_EnableAudioInput(This,sampleRate,sampleType,channelCount) \ ( (This)->lpVtbl -> EnableAudioInput(This,sampleRate,sampleType,channelCount) ) #define IDeckLinkInput_v7_1_DisableAudioInput(This) \ ( (This)->lpVtbl -> DisableAudioInput(This) ) #define IDeckLinkInput_v7_1_ReadAudioSamples(This,buffer,sampleFrameCount,sampleFramesRead,audioPacketTime,timeScale) \ ( (This)->lpVtbl -> ReadAudioSamples(This,buffer,sampleFrameCount,sampleFramesRead,audioPacketTime,timeScale) ) #define IDeckLinkInput_v7_1_GetBufferedAudioSampleFrameCount(This,bufferedSampleCount) \ ( (This)->lpVtbl -> GetBufferedAudioSampleFrameCount(This,bufferedSampleCount) ) #define IDeckLinkInput_v7_1_StartStreams(This) \ ( (This)->lpVtbl -> StartStreams(This) ) #define IDeckLinkInput_v7_1_StopStreams(This) \ ( (This)->lpVtbl -> StopStreams(This) ) #define IDeckLinkInput_v7_1_PauseStreams(This) \ ( (This)->lpVtbl -> PauseStreams(This) ) #define IDeckLinkInput_v7_1_SetCallback(This,theCallback) \ ( (This)->lpVtbl -> SetCallback(This,theCallback) ) #endif /* COBJMACROS */ #endif /* C style interface */ #endif /* __IDeckLinkInput_v7_1_INTERFACE_DEFINED__ */ #endif /* __DeckLinkAPI_LIBRARY_DEFINED__ */ /* Additional Prototypes for ALL interfaces */ /* end of Additional Prototypes */ #ifdef __cplusplus } #endif #endif mlt-7.22.0/src/modules/decklink/win/DeckLinkAPI_i.cpp000664 000000 000000 00000021046 14531534050 022233 0ustar00rootroot000000 000000 /* this ALWAYS GENERATED file contains the IIDs and CLSIDs */ /* link this file in with the server and any clients */ /* File created by MIDL compiler version 7.00.0555 */ /* at Tue Sep 07 12:00:00 2010 */ /* Compiler settings for video\DeckLinkAPI.idl: Oicf, W1, Zp8, env=Win32 (32b run), target_arch=X86 7.00.0555 protocol : dce , ms_ext, c_ext, robust error checks: allocation ref bounds_check enum stub_data VC __declspec() decoration level: __declspec(uuid()), __declspec(selectany), __declspec(novtable) DECLSPEC_UUID(), MIDL_INTERFACE() */ /* @@MIDL_FILE_HEADING( ) */ //#pragma warning( disable: 4049 ) /* more than 64k source lines */ #ifdef __cplusplus extern "C"{ #endif #include "DeckLinkAPI_h.h" #include #include #ifdef _MIDL_USE_GUIDDEF_ #ifndef INITGUID #define INITGUID #include #undef INITGUID #else #include #endif #define MIDL_DEFINE_GUID(type,name,l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8) \ DEFINE_GUID(name,l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8) #else // !_MIDL_USE_GUIDDEF_ #ifndef __IID_DEFINED__ #define __IID_DEFINED__ typedef struct _IID { unsigned long x; unsigned short s1; unsigned short s2; unsigned char c[8]; } IID; #endif // __IID_DEFINED__ #ifndef CLSID_DEFINED #define CLSID_DEFINED typedef IID CLSID; #endif // CLSID_DEFINED #define MIDL_DEFINE_GUID(type,name,l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8) \ const type name = {l,w1,w2,{b1,b2,b3,b4,b5,b6,b7,b8}} #endif // !_MIDL_USE_GUIDDEF_ MIDL_DEFINE_GUID(IID, LIBID_DeckLinkAPI,0xD864517A,0xEDD5,0x466D,0x86,0x7D,0xC8,0x19,0xF1,0xC0,0x52,0xBB); MIDL_DEFINE_GUID(IID, IID_IDeckLinkVideoOutputCallback,0x20AA5225,0x1958,0x47CB,0x82,0x0B,0x80,0xA8,0xD5,0x21,0xA6,0xEE); MIDL_DEFINE_GUID(IID, IID_IDeckLinkInputCallback,0xDD04E5EC,0x7415,0x42AB,0xAE,0x4A,0xE8,0x0C,0x4D,0xFC,0x04,0x4A); MIDL_DEFINE_GUID(IID, IID_IDeckLinkMemoryAllocator,0xB36EB6E7,0x9D29,0x4AA8,0x92,0xEF,0x84,0x3B,0x87,0xA2,0x89,0xE8); MIDL_DEFINE_GUID(IID, IID_IDeckLinkAudioOutputCallback,0x403C681B,0x7F46,0x4A12,0xB9,0x93,0x2B,0xB1,0x27,0x08,0x4E,0xE6); MIDL_DEFINE_GUID(IID, IID_IDeckLinkIterator,0x74E936FC,0xCC28,0x4A67,0x81,0xA0,0x1E,0x94,0xE5,0x2D,0x4E,0x69); MIDL_DEFINE_GUID(IID, IID_IDeckLinkAPIInformation,0x7BEA3C68,0x730D,0x4322,0xAF,0x34,0x8A,0x71,0x52,0xB5,0x32,0xA4); MIDL_DEFINE_GUID(IID, IID_IDeckLinkDisplayModeIterator,0x9C88499F,0xF601,0x4021,0xB8,0x0B,0x03,0x2E,0x4E,0xB4,0x1C,0x35); MIDL_DEFINE_GUID(IID, IID_IDeckLinkDisplayMode,0x3EB2C1AB,0x0A3D,0x4523,0xA3,0xAD,0xF4,0x0D,0x7F,0xB1,0x4E,0x78); MIDL_DEFINE_GUID(IID, IID_IDeckLink,0x62BFF75D,0x6569,0x4E55,0x8D,0x4D,0x66,0xAA,0x03,0x82,0x9A,0xBC); MIDL_DEFINE_GUID(IID, IID_IDeckLinkOutput,0xA3EF0963,0x0862,0x44ED,0x92,0xA9,0xEE,0x89,0xAB,0xF4,0x31,0xC7); MIDL_DEFINE_GUID(IID, IID_IDeckLinkInput,0x6D40EF78,0x28B9,0x4E21,0x99,0x0D,0x95,0xBB,0x77,0x50,0xA0,0x4F); MIDL_DEFINE_GUID(IID, IID_IDeckLinkTimecode,0xBC6CFBD3,0x8317,0x4325,0xAC,0x1C,0x12,0x16,0x39,0x1E,0x93,0x40); MIDL_DEFINE_GUID(IID, IID_IDeckLinkVideoFrame,0x3F716FE0,0xF023,0x4111,0xBE,0x5D,0xEF,0x44,0x14,0xC0,0x5B,0x17); MIDL_DEFINE_GUID(IID, IID_IDeckLinkMutableVideoFrame,0x69E2639F,0x40DA,0x4E19,0xB6,0xF2,0x20,0xAC,0xE8,0x15,0xC3,0x90); MIDL_DEFINE_GUID(IID, IID_IDeckLinkVideoFrame3DExtensions,0xDA0F7E4A,0xEDC7,0x48A8,0x9C,0xDD,0x2D,0xB5,0x1C,0x72,0x9C,0xD7); MIDL_DEFINE_GUID(IID, IID_IDeckLinkVideoInputFrame,0x05CFE374,0x537C,0x4094,0x9A,0x57,0x68,0x05,0x25,0x11,0x8F,0x44); MIDL_DEFINE_GUID(IID, IID_IDeckLinkVideoFrameAncillary,0x732E723C,0xD1A4,0x4E29,0x9E,0x8E,0x4A,0x88,0x79,0x7A,0x00,0x04); MIDL_DEFINE_GUID(IID, IID_IDeckLinkAudioInputPacket,0xE43D5870,0x2894,0x11DE,0x8C,0x30,0x08,0x00,0x20,0x0C,0x9A,0x66); MIDL_DEFINE_GUID(IID, IID_IDeckLinkScreenPreviewCallback,0xB1D3F49A,0x85FE,0x4C5D,0x95,0xC8,0x0B,0x5D,0x5D,0xCC,0xD4,0x38); MIDL_DEFINE_GUID(IID, IID_IDeckLinkGLScreenPreviewHelper,0x504E2209,0xCAC7,0x4C1A,0x9F,0xB4,0xC5,0xBB,0x62,0x74,0xD2,0x2F); MIDL_DEFINE_GUID(IID, IID_IDeckLinkConfiguration,0xC679A35B,0x610C,0x4D09,0xB7,0x48,0x1D,0x04,0x78,0x10,0x0F,0xC0); MIDL_DEFINE_GUID(IID, IID_IDeckLinkAttributes,0xABC11843,0xD966,0x44CB,0x96,0xE2,0xA1,0xCB,0x5D,0x31,0x35,0xC4); MIDL_DEFINE_GUID(IID, IID_IDeckLinkKeyer,0x89AFCAF5,0x65F8,0x421E,0x98,0xF7,0x96,0xFE,0x5F,0x5B,0xFB,0xA3); MIDL_DEFINE_GUID(IID, IID_IDeckLinkVideoConversion,0x3BBCB8A2,0xDA2C,0x42D9,0xB5,0xD8,0x88,0x08,0x36,0x44,0xE9,0x9A); MIDL_DEFINE_GUID(IID, IID_IDeckLinkDeckControlStatusCallback,0xE5F693C1,0x4283,0x4716,0xB1,0x8F,0xC1,0x43,0x15,0x21,0x95,0x5B); MIDL_DEFINE_GUID(IID, IID_IDeckLinkDeckControl,0xA4D81043,0x0619,0x42B7,0x8E,0xD6,0x60,0x2D,0x29,0x04,0x1D,0xF7); MIDL_DEFINE_GUID(CLSID, CLSID_CDeckLinkIterator,0xD9EDA3B3,0x2887,0x41FA,0xB7,0x24,0x01,0x7C,0xF1,0xEB,0x1D,0x37); MIDL_DEFINE_GUID(CLSID, CLSID_CDeckLinkGLScreenPreviewHelper,0xF63E77C7,0xB655,0x4A4A,0x9A,0xD0,0x3C,0xA8,0x5D,0x39,0x43,0x43); MIDL_DEFINE_GUID(CLSID, CLSID_CDeckLinkVideoConversion,0x7DBBBB11,0x5B7B,0x467D,0xAE,0xA4,0xCE,0xA4,0x68,0xFD,0x36,0x8C); MIDL_DEFINE_GUID(IID, IID_IDeckLinkDisplayModeIterator_v7_6,0x455D741F,0x1779,0x4800,0x86,0xF5,0x0B,0x5D,0x13,0xD7,0x97,0x51); MIDL_DEFINE_GUID(IID, IID_IDeckLinkDisplayMode_v7_6,0x87451E84,0x2B7E,0x439E,0xA6,0x29,0x43,0x93,0xEA,0x4A,0x85,0x50); MIDL_DEFINE_GUID(IID, IID_IDeckLinkOutput_v7_6,0x29228142,0xEB8C,0x4141,0xA6,0x21,0xF7,0x40,0x26,0x45,0x09,0x55); MIDL_DEFINE_GUID(IID, IID_IDeckLinkInput_v7_6,0x300C135A,0x9F43,0x48E2,0x99,0x06,0x6D,0x79,0x11,0xD9,0x3C,0xF1); MIDL_DEFINE_GUID(IID, IID_IDeckLinkTimecode_v7_6,0xEFB9BCA6,0xA521,0x44F7,0xBD,0x69,0x23,0x32,0xF2,0x4D,0x9E,0xE6); MIDL_DEFINE_GUID(IID, IID_IDeckLinkVideoFrame_v7_6,0xA8D8238E,0x6B18,0x4196,0x99,0xE1,0x5A,0xF7,0x17,0xB8,0x3D,0x32); MIDL_DEFINE_GUID(IID, IID_IDeckLinkMutableVideoFrame_v7_6,0x46FCEE00,0xB4E6,0x43D0,0x91,0xC0,0x02,0x3A,0x7F,0xCE,0xB3,0x4F); MIDL_DEFINE_GUID(IID, IID_IDeckLinkVideoInputFrame_v7_6,0x9A74FA41,0xAE9F,0x47AC,0x8C,0xF4,0x01,0xF4,0x2D,0xD5,0x99,0x65); MIDL_DEFINE_GUID(IID, IID_IDeckLinkScreenPreviewCallback_v7_6,0x373F499D,0x4B4D,0x4518,0xAD,0x22,0x63,0x54,0xE5,0xA5,0x82,0x5E); MIDL_DEFINE_GUID(IID, IID_IDeckLinkGLScreenPreviewHelper_v7_6,0xBA575CD9,0xA15E,0x497B,0xB2,0xC2,0xF9,0xAF,0xE7,0xBE,0x4E,0xBA); MIDL_DEFINE_GUID(IID, IID_IDeckLinkVideoConversion_v7_6,0x3EB504C9,0xF97D,0x40FE,0xA1,0x58,0xD4,0x07,0xD4,0x8C,0xB5,0x3B); MIDL_DEFINE_GUID(IID, IID_IDeckLinkConfiguration_v7_6,0xB8EAD569,0xB764,0x47F0,0xA7,0x3F,0xAE,0x40,0xDF,0x6C,0xBF,0x10); MIDL_DEFINE_GUID(IID, IID_IDeckLinkVideoOutputCallback_v7_6,0xE763A626,0x4A3C,0x49D1,0xBF,0x13,0xE7,0xAD,0x36,0x92,0xAE,0x52); MIDL_DEFINE_GUID(IID, IID_IDeckLinkInputCallback_v7_6,0x31D28EE7,0x88B6,0x4CB1,0x89,0x7A,0xCD,0xBF,0x79,0xA2,0x64,0x14); MIDL_DEFINE_GUID(CLSID, CLSID_CDeckLinkGLScreenPreviewHelper_v7_6,0xD398CEE7,0x4434,0x4CA3,0x9B,0xA6,0x5A,0xE3,0x45,0x56,0xB9,0x05); MIDL_DEFINE_GUID(CLSID, CLSID_CDeckLinkVideoConversion_v7_6,0xFFA84F77,0x73BE,0x4FB7,0xB0,0x3E,0xB5,0xE4,0x4B,0x9F,0x75,0x9B); MIDL_DEFINE_GUID(IID, IID_IDeckLinkInputCallback_v7_3,0xFD6F311D,0x4D00,0x444B,0x9E,0xD4,0x1F,0x25,0xB5,0x73,0x0A,0xD0); MIDL_DEFINE_GUID(IID, IID_IDeckLinkOutput_v7_3,0x271C65E3,0xC323,0x4344,0xA3,0x0F,0xD9,0x08,0xBC,0xB2,0x0A,0xA3); MIDL_DEFINE_GUID(IID, IID_IDeckLinkInput_v7_3,0x4973F012,0x9925,0x458C,0x87,0x1C,0x18,0x77,0x4C,0xDB,0xBE,0xCB); MIDL_DEFINE_GUID(IID, IID_IDeckLinkVideoInputFrame_v7_3,0xCF317790,0x2894,0x11DE,0x8C,0x30,0x08,0x00,0x20,0x0C,0x9A,0x66); MIDL_DEFINE_GUID(IID, IID_IDeckLinkDisplayModeIterator_v7_1,0xB28131B6,0x59AC,0x4857,0xB5,0xAC,0xCD,0x75,0xD5,0x88,0x3E,0x2F); MIDL_DEFINE_GUID(IID, IID_IDeckLinkDisplayMode_v7_1,0xAF0CD6D5,0x8376,0x435E,0x84,0x33,0x54,0xF9,0xDD,0x53,0x0A,0xC3); MIDL_DEFINE_GUID(IID, IID_IDeckLinkVideoFrame_v7_1,0x333F3A10,0x8C2D,0x43CF,0xB7,0x9D,0x46,0x56,0x0F,0xEE,0xA1,0xCE); MIDL_DEFINE_GUID(IID, IID_IDeckLinkVideoInputFrame_v7_1,0xC8B41D95,0x8848,0x40EE,0x9B,0x37,0x6E,0x34,0x17,0xFB,0x11,0x4B); MIDL_DEFINE_GUID(IID, IID_IDeckLinkAudioInputPacket_v7_1,0xC86DE4F6,0xA29F,0x42E3,0xAB,0x3A,0x13,0x63,0xE2,0x9F,0x07,0x88); MIDL_DEFINE_GUID(IID, IID_IDeckLinkVideoOutputCallback_v7_1,0xEBD01AFA,0xE4B0,0x49C6,0xA0,0x1D,0xED,0xB9,0xD1,0xB5,0x5F,0xD9); MIDL_DEFINE_GUID(IID, IID_IDeckLinkInputCallback_v7_1,0x7F94F328,0x5ED4,0x4E9F,0x97,0x29,0x76,0xA8,0x6B,0xDC,0x99,0xCC); MIDL_DEFINE_GUID(IID, IID_IDeckLinkOutput_v7_1,0xAE5B3E9B,0x4E1E,0x4535,0xB6,0xE8,0x48,0x0F,0xF5,0x2F,0x6C,0xE5); MIDL_DEFINE_GUID(IID, IID_IDeckLinkInput_v7_1,0x2B54EDEF,0x5B32,0x429F,0xBA,0x11,0xBB,0x99,0x05,0x96,0xEA,0xCD); #undef MIDL_DEFINE_GUID #ifdef __cplusplus } #endif mlt-7.22.0/src/modules/frei0r/000775 000000 000000 00000000000 14531534050 016004 5ustar00rootroot000000 000000 mlt-7.22.0/src/modules/frei0r/CMakeLists.txt000664 000000 000000 00000001667 14531534050 020556 0ustar00rootroot000000 000000 add_library(mltfrei0r MODULE factory.c filter_cairoblend_mode.c filter_frei0r.c frei0r_helper.c producer_frei0r.c transition_frei0r.c ) file(GLOB YML "*.yml") add_custom_target(Other_frei0r_Files SOURCES ${YML} aliases.yaml blacklist.txt not_thread_safe.txt param_name_map.yaml ) target_compile_options(mltfrei0r PRIVATE ${MLT_COMPILE_OPTIONS}) target_include_directories(mltfrei0r PRIVATE ${FREI0R_INCLUDE_DIRS}) if(RELOCATABLE) target_compile_definitions(mltfrei0r PRIVATE RELOCATABLE) endif() target_link_libraries(mltfrei0r PRIVATE mlt m ${CMAKE_DL_LIBS}) set_target_properties(mltfrei0r PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${MLT_MODULE_OUTPUT_DIRECTORY}") install(TARGETS mltfrei0r LIBRARY DESTINATION ${MLT_INSTALL_MODULE_DIR}) install(FILES aliases.yaml filter_cairoblend_mode.yml resolution_scale.yml param_name_map.yaml blacklist.txt not_thread_safe.txt DESTINATION ${MLT_INSTALL_DATA_DIR}/frei0r ) mlt-7.22.0/src/modules/frei0r/aliases.yaml000664 000000 000000 00000000573 14531534050 020316 0ustar00rootroot000000 000000 # MLT frei0r plugin name mapping from new name to old ones # new: # - old frei0r.measure_pr0be: - frei0r.pr0be frei0r.measure_pr0file: - frei0r.pr0file frei0r.tehroxx0r: - frei0r.tehRoxx0r frei0r.alpha0ps_alpha0ps: - frei0r.alpha0ps frei0r.alpha0ps_alphagrad: - frei0r.alphagrad frei0r.alpha0ps_alphaspot: - frei0r.alphaspot frei0r.denoise_hqdn3d: - frei0r.hqdn3d mlt-7.22.0/src/modules/frei0r/blacklist.txt000664 000000 000000 00000000014 14531534050 020510 0ustar00rootroot000000 000000 perspective mlt-7.22.0/src/modules/frei0r/factory.c000664 000000 000000 00000065570 14531534050 017634 0ustar00rootroot000000 000000 /* * factory.c -- the factory method interfaces * Copyright (c) 2008 Marco Gittler * Copyright (C) 2009-2023 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include #include #include #include #include #include #ifdef _WIN32 #define LIBSUF ".dll" #define FREI0R_PLUGIN_PATH "\\lib\\frei0r-1" #else #define LIBSUF ".so" #ifdef RELOCATABLE #ifdef __APPLE__ #define FREI0R_PLUGIN_PATH "/PlugIns/frei0r-1" #else #define FREI0R_PLUGIN_PATH "/lib/frei0r-1" #endif #else #define FREI0R_PLUGIN_PATH \ "/usr/lib/frei0r-1:/usr/lib64/frei0r-1:/opt/local/lib/frei0r-1:/usr/local/lib/frei0r-1:$HOME/" \ ".frei0r-1/lib" #endif #endif extern mlt_filter filter_frei0r_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_cairoblend_mode_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_frame filter_process(mlt_filter filter, mlt_frame frame); extern void filter_close(mlt_filter filter); extern int producer_get_frame(mlt_producer producer, mlt_frame_ptr frame, int index); extern void producer_close(mlt_producer producer); extern void transition_close(mlt_transition transition); extern mlt_frame transition_process(mlt_transition transition, mlt_frame a_frame, mlt_frame b_frame); static char *get_frei0r_path() { #if defined(_WIN32) || defined(RELOCATABLE) char *dirname = malloc(strlen(mlt_environment("MLT_APPDIR")) + strlen(FREI0R_PLUGIN_PATH) + 1); strcpy(dirname, mlt_environment("MLT_APPDIR")); strcat(dirname, FREI0R_PLUGIN_PATH); return dirname; #else return strdup(getenv("FREI0R_PATH") ? getenv("FREI0R_PATH") : getenv("MLT_FREI0R_PLUGIN_PATH") ? getenv("MLT_FREI0R_PLUGIN_PATH") : FREI0R_PLUGIN_PATH); #endif } static void check_thread_safe(mlt_properties properties, const char *name) { char dirname[PATH_MAX]; snprintf(dirname, PATH_MAX, "%s/frei0r/not_thread_safe.txt", mlt_environment("MLT_DATA")); mlt_properties not_thread_safe = mlt_properties_load(dirname); double version = mlt_properties_get_double(properties, "version"); int i; for (i = 0; i < mlt_properties_count(not_thread_safe); i++) { if (!strcmp(name, mlt_properties_get_name(not_thread_safe, i))) { double thread_safe_version = mlt_properties_get_double(not_thread_safe, name); if (thread_safe_version == 0.0 || version < thread_safe_version) mlt_properties_set_int(properties, "_not_thread_safe", 1); break; } } mlt_properties_close(not_thread_safe); } static mlt_properties fill_param_info(mlt_service_type type, const char *service_name, char *name) { char file[PATH_MAX]; char servicetype[1024] = ""; struct stat stat_buff; switch (type) { case mlt_service_producer_type: strcpy(servicetype, "producer"); break; case mlt_service_filter_type: strcpy(servicetype, "filter"); break; case mlt_service_transition_type: strcpy(servicetype, "transition"); break; default: strcpy(servicetype, ""); } snprintf(file, PATH_MAX, "%s/frei0r/%s_%s.yml", mlt_environment("MLT_DATA"), servicetype, service_name); memset(&stat_buff, 0, sizeof(stat_buff)); stat(file, &stat_buff); if (S_ISREG(stat_buff.st_mode)) { return mlt_properties_parse_yaml(file); } void *handle = dlopen(name, RTLD_LAZY); if (!handle) return NULL; void (*plginfo)(f0r_plugin_info_t *) = dlsym(handle, "f0r_get_plugin_info"); void (*param_info)(f0r_param_info_t *, int param_index) = dlsym(handle, "f0r_get_param_info"); void (*f0r_init)(void) = dlsym(handle, "f0r_init"); void (*f0r_deinit)(void) = dlsym(handle, "f0r_deinit"); f0r_instance_t (*f0r_construct)(unsigned int, unsigned int) = dlsym(handle, "f0r_construct"); void (*f0r_destruct)(f0r_instance_t) = dlsym(handle, "f0r_destruct"); void (*f0r_get_param_value)(f0r_instance_t instance, f0r_param_t param, int param_index) = dlsym(handle, "f0r_get_param_value"); if (!plginfo || !param_info) { dlclose(handle); return NULL; } mlt_properties metadata = mlt_properties_new(); f0r_plugin_info_t info; char string[48]; int j = 0; f0r_init(); f0r_instance_t instance = f0r_construct(720, 576); if (!instance) { f0r_deinit(); dlclose(handle); mlt_properties_close(metadata); return NULL; } plginfo(&info); snprintf(string, sizeof(string), "%d", info.minor_version); mlt_properties_set(metadata, "schema_version", "7.0"); mlt_properties_set(metadata, "title", info.name); mlt_properties_set_double(metadata, "version", info.major_version + info.minor_version / pow(10, strlen(string))); mlt_properties_set(metadata, "identifier", service_name); mlt_properties_set(metadata, "description", info.explanation); mlt_properties_set(metadata, "creator", info.author); switch (type) { case mlt_service_producer_type: mlt_properties_set(metadata, "type", "producer"); break; case mlt_service_filter_type: mlt_properties_set(metadata, "type", "filter"); break; case mlt_service_transition_type: mlt_properties_set(metadata, "type", "transition"); break; default: break; } mlt_properties tags = mlt_properties_new(); mlt_properties_set_data(metadata, "tags", tags, 0, (mlt_destructor) mlt_properties_close, NULL); mlt_properties_set(tags, "0", "Video"); mlt_properties parameter = mlt_properties_new(); mlt_properties_set_data(metadata, "parameters", parameter, 0, (mlt_destructor) mlt_properties_close, NULL); for (j = 0; j < info.num_params; j++) { snprintf(string, sizeof(string), "%d", j); mlt_properties pnum = mlt_properties_new(); mlt_properties_set_data(parameter, string, pnum, 0, (mlt_destructor) mlt_properties_close, NULL); f0r_param_info_t paraminfo; param_info(¶minfo, j); mlt_properties_set(pnum, "identifier", string); mlt_properties_set(pnum, "title", paraminfo.name); mlt_properties_set(pnum, "description", paraminfo.explanation); if (paraminfo.type == F0R_PARAM_DOUBLE) { double deflt = 0; mlt_properties_set(pnum, "type", "float"); mlt_properties_set(pnum, "minimum", "0"); mlt_properties_set(pnum, "maximum", "1"); f0r_get_param_value(instance, &deflt, j); mlt_properties_set_double(pnum, "default", CLAMP(deflt, 0.0, 1.0)); mlt_properties_set(pnum, "mutable", "yes"); mlt_properties_set(pnum, "animation", "yes"); mlt_properties_set(pnum, "widget", "spinner"); } else if (paraminfo.type == F0R_PARAM_BOOL) { double deflt = 0; mlt_properties_set(pnum, "type", "boolean"); mlt_properties_set(pnum, "minimum", "0"); mlt_properties_set(pnum, "maximum", "1"); f0r_get_param_value(instance, &deflt, j); mlt_properties_set_int(pnum, "default", deflt != 0.0); mlt_properties_set(pnum, "mutable", "yes"); mlt_properties_set(pnum, "animation", "yes"); mlt_properties_set(pnum, "widget", "checkbox"); } else if (paraminfo.type == F0R_PARAM_COLOR) { char colorstr[8]; f0r_param_color_t deflt = {0, 0, 0}; mlt_properties_set(pnum, "type", "color"); f0r_get_param_value(instance, &deflt, j); sprintf(colorstr, "#%02x%02x%02x", (unsigned) CLAMP(deflt.r * 255, 0, 255), (unsigned) CLAMP(deflt.g * 255, 0, 255), (unsigned) CLAMP(deflt.b * 255, 0, 255)); colorstr[7] = 0; mlt_properties_set(pnum, "default", colorstr); mlt_properties_set(pnum, "mutable", "yes"); mlt_properties_set(pnum, "animation", "yes"); mlt_properties_set(pnum, "widget", "color"); } else if (paraminfo.type == F0R_PARAM_STRING) { char *deflt = NULL; mlt_properties_set(pnum, "type", "string"); f0r_get_param_value(instance, &deflt, j); mlt_properties_set(pnum, "default", deflt); mlt_properties_set(pnum, "mutable", "yes"); mlt_properties_set(pnum, "widget", "text"); } } f0r_destruct(instance); f0r_deinit(); dlclose(handle); return metadata; } static void *load_lib(mlt_profile profile, mlt_service_type type, void *handle, const char *name) { int i = 0; void (*f0r_get_plugin_info)(f0r_plugin_info_t *), *f0r_construct, *f0r_update, *f0r_destruct, (*f0r_get_param_info)(f0r_param_info_t * info, int param_index), (*f0r_init)(void), *f0r_deinit, (*f0r_set_param_value)(f0r_instance_t instance, f0r_param_t param, int param_index), (*f0r_get_param_value)(f0r_instance_t instance, f0r_param_t param, int param_index), (*f0r_update2)(f0r_instance_t instance, double time, const uint32_t *inframe1, const uint32_t *inframe2, const uint32_t *inframe3, uint32_t *outframe); if ((f0r_construct = dlsym(handle, "f0r_construct")) && (f0r_destruct = dlsym(handle, "f0r_destruct")) && (f0r_get_plugin_info = dlsym(handle, "f0r_get_plugin_info")) && (f0r_get_param_info = dlsym(handle, "f0r_get_param_info")) && (f0r_set_param_value = dlsym(handle, "f0r_set_param_value")) && (f0r_get_param_value = dlsym(handle, "f0r_get_param_value")) && (f0r_init = dlsym(handle, "f0r_init")) && (f0r_deinit = dlsym(handle, "f0r_deinit"))) { f0r_update = dlsym(handle, "f0r_update"); f0r_update2 = dlsym(handle, "f0r_update2"); f0r_plugin_info_t info; f0r_get_plugin_info(&info); void *ret = NULL; mlt_properties properties = NULL; char minor[12]; if (type == mlt_service_producer_type && info.plugin_type == F0R_PLUGIN_TYPE_SOURCE) { mlt_producer producer = mlt_producer_new(profile); if (producer) { producer->get_frame = producer_get_frame; producer->close = (mlt_destructor) producer_close; f0r_init(); properties = MLT_PRODUCER_PROPERTIES(producer); for (i = 0; i < info.num_params; i++) { f0r_param_info_t pinfo; f0r_get_param_info(&pinfo, i); } ret = producer; } } else if (type == mlt_service_filter_type && info.plugin_type == F0R_PLUGIN_TYPE_FILTER) { mlt_filter filter = mlt_filter_new(); if (filter) { filter->process = filter_process; filter->close = filter_close; f0r_init(); properties = MLT_FILTER_PROPERTIES(filter); for (i = 0; i < info.num_params; i++) { f0r_param_info_t pinfo; f0r_get_param_info(&pinfo, i); } ret = filter; } } else if (type == mlt_service_transition_type && info.plugin_type == F0R_PLUGIN_TYPE_MIXER2) { mlt_transition transition = mlt_transition_new(); if (transition) { transition->process = transition_process; transition->close = transition_close; f0r_init(); properties = MLT_TRANSITION_PROPERTIES(transition); mlt_properties_set_int(properties, "_transition_type", 1); ret = transition; } } mlt_properties_set_data(properties, "_dlclose_handle", handle, sizeof(handle), NULL, NULL); mlt_properties_set_data(properties, "_dlclose", dlclose, sizeof(void *), NULL, NULL); mlt_properties_set_data(properties, "f0r_construct", f0r_construct, sizeof(f0r_construct), NULL, NULL); mlt_properties_set_data(properties, "f0r_update", f0r_update, sizeof(f0r_update), NULL, NULL); if (f0r_update2) mlt_properties_set_data(properties, "f0r_update2", f0r_update2, sizeof(f0r_update2), NULL, NULL); mlt_properties_set_data(properties, "f0r_destruct", f0r_destruct, sizeof(f0r_destruct), NULL, NULL); mlt_properties_set_data(properties, "f0r_get_plugin_info", f0r_get_plugin_info, sizeof(void *), NULL, NULL); mlt_properties_set_data(properties, "f0r_get_param_info", f0r_get_param_info, sizeof(void *), NULL, NULL); mlt_properties_set_data(properties, "f0r_set_param_value", f0r_set_param_value, sizeof(void *), NULL, NULL); mlt_properties_set_data(properties, "f0r_get_param_value", f0r_get_param_value, sizeof(void *), NULL, NULL); // Let frei0r plugin version be serialized using same format as metadata snprintf(minor, sizeof(minor), "%d", info.minor_version); mlt_properties_set_double(properties, "version", info.major_version + info.minor_version / pow(10, strlen(minor))); check_thread_safe(properties, name); // Use the global param name map for backwards compatibility when // param names change and setting frei0r params by name instead of index. mlt_properties param_name_map = mlt_properties_get_data(mlt_global_properties(), "frei0r.param_name_map", NULL); if (param_name_map) { // Lookup my plugin in the map param_name_map = mlt_properties_get_data(param_name_map, name, NULL); mlt_properties_set_data(properties, "_param_name_map", param_name_map, 0, NULL, NULL); } param_name_map = mlt_properties_get_data(mlt_global_properties(), "frei0r.resolution_scale", NULL); if (param_name_map) { // Lookup my plugin in the map param_name_map = mlt_properties_get_data(param_name_map, name, NULL); mlt_properties_set_data(properties, "_resolution_scale", param_name_map, 0, NULL, NULL); } return ret; } else { mlt_log_error(NULL, "frei0r plugin \"%s\" is missing a function\n", name); dlerror(); } return NULL; } static void *create_frei0r_item(mlt_profile profile, mlt_service_type type, const char *id, void *arg) { mlt_tokeniser tokeniser = mlt_tokeniser_init(); char *frei0r_path = get_frei0r_path(); int dircount = mlt_tokeniser_parse_new(tokeniser, frei0r_path, MLT_DIRLIST_DELIMITER); void *ret = NULL; while (dircount-- && !ret) { char soname[PATH_MAX]; char *myid = strdup(id); #ifdef _WIN32 char *firstname = strtok(myid, "."); #else char *save_firstptr = NULL; char *firstname = strtok_r(myid, ".", &save_firstptr); #endif char *directory = mlt_tokeniser_get_string(tokeniser, dircount); #ifdef _WIN32 firstname = strtok(NULL, "."); #else firstname = strtok_r(NULL, ".", &save_firstptr); #endif if (strncmp(directory, "$HOME", 5)) snprintf(soname, PATH_MAX, "%s/%s" LIBSUF, directory, firstname); else snprintf(soname, PATH_MAX, "%s%s/%s" LIBSUF, getenv("HOME"), strchr(directory, '/'), firstname); if (firstname) { const char *alias = mlt_properties_get(mlt_properties_get_data(mlt_global_properties(), "frei0r.aliases", NULL), id); void *handle = dlopen(alias ? alias : soname, RTLD_LAZY); if (handle) { ret = load_lib(profile, type, handle, firstname); } else { dlerror(); } } free(myid); } mlt_tokeniser_close(tokeniser); free(frei0r_path); return ret; } static mlt_properties metadata(mlt_service_type type, const char *id, void *data) { char file[PATH_MAX]; snprintf(file, PATH_MAX, "%s/frei0r/%s", mlt_environment("MLT_DATA"), (char *) data); return mlt_properties_parse_yaml(file); } MLT_REPOSITORY { mlt_tokeniser tokeniser = mlt_tokeniser_init(); char *frei0r_path = get_frei0r_path(); int dircount = mlt_tokeniser_parse_new(tokeniser, frei0r_path, MLT_DIRLIST_DELIMITER); char dirname[PATH_MAX]; snprintf(dirname, PATH_MAX, "%s/frei0r/blacklist.txt", mlt_environment("MLT_DATA")); mlt_properties blacklist = mlt_properties_load(dirname); // Load a param name map into global properties for backwards compatibility when // param names change and setting frei0r params by name instead of index. snprintf(dirname, PATH_MAX, "%s/frei0r/param_name_map.yaml", mlt_environment("MLT_DATA")); mlt_properties_set_data(mlt_global_properties(), "frei0r.param_name_map", mlt_properties_parse_yaml(dirname), 0, (mlt_destructor) mlt_properties_close, NULL); // Load a list of parameters impacted by consumer scale into global properties. snprintf(dirname, PATH_MAX, "%s/frei0r/resolution_scale.yml", mlt_environment("MLT_DATA")); mlt_properties_set_data(mlt_global_properties(), "frei0r.resolution_scale", mlt_properties_parse_yaml(dirname), 0, (mlt_destructor) mlt_properties_close, NULL); // Load a list of plugin alias names. snprintf(dirname, PATH_MAX, "%s/frei0r/aliases.yaml", mlt_environment("MLT_DATA")); mlt_properties aliases = mlt_properties_parse_yaml(dirname); mlt_properties reverse_aliases = mlt_properties_new(); mlt_properties_set_data(mlt_global_properties(), "frei0r.aliases", reverse_aliases, 0, (mlt_destructor) mlt_properties_close, NULL); while (dircount--) { mlt_properties direntries = mlt_properties_new(); char *directory = mlt_tokeniser_get_string(tokeniser, dircount); if (strncmp(directory, "$HOME", 5)) snprintf(dirname, PATH_MAX, "%s", directory); else snprintf(dirname, PATH_MAX, "%s%s", getenv("HOME"), strchr(directory, '/')); mlt_properties_dir_list(direntries, dirname, "*" LIBSUF, 1); for (int i = 0; i < mlt_properties_count(direntries); i++) { char *name = mlt_properties_get_value(direntries, i); char *shortname = name + strlen(dirname) + 1; #ifdef _WIN32 char *firstname = strtok(shortname, "."); #else char *save_firstptr = NULL; char *firstname = strtok_r(shortname, ".", &save_firstptr); #endif char pluginname[1024] = "frei0r."; if (firstname) strncat(pluginname, firstname, sizeof(pluginname) - strlen(pluginname) - 1); if (firstname && mlt_properties_exists(blacklist, firstname)) continue; mlt_properties plugin_aliases = mlt_properties_get_data(aliases, pluginname, NULL); void *handle = dlopen(strcat(name, LIBSUF), RTLD_LAZY); if (handle) { void (*plginfo)(f0r_plugin_info_t *) = dlsym(handle, "f0r_get_plugin_info"); if (plginfo) { f0r_plugin_info_t info; plginfo(&info); if (firstname && info.plugin_type == F0R_PLUGIN_TYPE_SOURCE) { if (mlt_properties_get(mlt_repository_producers(repository), pluginname)) { dlclose(handle); continue; } MLT_REGISTER(mlt_service_producer_type, pluginname, create_frei0r_item); MLT_REGISTER_METADATA(mlt_service_producer_type, pluginname, fill_param_info, name); for (int j = 0; j < mlt_properties_count(plugin_aliases); j++) { const char *alias = mlt_properties_get_value(plugin_aliases, j); mlt_properties_set(reverse_aliases, alias, name); MLT_REGISTER(mlt_service_producer_type, alias, create_frei0r_item); MLT_REGISTER_METADATA(mlt_service_producer_type, alias, fill_param_info, name); } } else if (firstname && info.plugin_type == F0R_PLUGIN_TYPE_FILTER) { if (mlt_properties_get(mlt_repository_filters(repository), pluginname)) { dlclose(handle); continue; } MLT_REGISTER(mlt_service_filter_type, pluginname, create_frei0r_item); MLT_REGISTER_METADATA(mlt_service_filter_type, pluginname, fill_param_info, name); for (int j = 0; j < mlt_properties_count(plugin_aliases); j++) { const char *alias = mlt_properties_get_value(plugin_aliases, j); mlt_properties_set(reverse_aliases, alias, name); MLT_REGISTER(mlt_service_filter_type, alias, create_frei0r_item); MLT_REGISTER_METADATA(mlt_service_filter_type, alias, fill_param_info, name); } } else if (firstname && info.plugin_type == F0R_PLUGIN_TYPE_MIXER2) { if (mlt_properties_get(mlt_repository_transitions(repository), pluginname)) { dlclose(handle); continue; } MLT_REGISTER(mlt_service_transition_type, pluginname, create_frei0r_item); MLT_REGISTER_METADATA(mlt_service_transition_type, pluginname, fill_param_info, name); for (int j = 0; j < mlt_properties_count(plugin_aliases); j++) { const char *alias = mlt_properties_get_value(plugin_aliases, j); mlt_properties_set(reverse_aliases, alias, name); MLT_REGISTER(mlt_service_transition_type, alias, create_frei0r_item); MLT_REGISTER_METADATA(mlt_service_transition_type, alias, fill_param_info, name); } } } dlclose(handle); } } mlt_factory_register_for_clean_up(direntries, (mlt_destructor) mlt_properties_close); } mlt_tokeniser_close(tokeniser); mlt_properties_close(blacklist); free(frei0r_path); MLT_REGISTER(mlt_service_filter_type, "cairoblend_mode", filter_cairoblend_mode_init); MLT_REGISTER_METADATA(mlt_service_filter_type, "cairoblend_mode", metadata, "filter_cairoblend_mode.yml"); } mlt-7.22.0/src/modules/frei0r/filter_cairoblend_mode.c000664 000000 000000 00000003212 14531534050 022621 0ustar00rootroot000000 000000 /* * filter_cairoblend_mode.c * Copyright (C) 2019 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "frei0r_helper.h" #include #include #include #include static mlt_frame filter_process(mlt_filter filter, mlt_frame frame) { mlt_properties_set(MLT_FRAME_PROPERTIES(frame), CAIROBLEND_MODE_PROPERTY, mlt_properties_get(MLT_FILTER_PROPERTIES(filter), "mode")); return frame; } mlt_filter filter_cairoblend_mode_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = mlt_filter_new(); if (filter) { filter->process = filter_process; mlt_properties_set(MLT_FILTER_PROPERTIES(filter), "mode", arg ? arg : "normal"); } return filter; } mlt-7.22.0/src/modules/frei0r/filter_cairoblend_mode.yml000664 000000 000000 00000001045 14531534050 023202 0ustar00rootroot000000 000000 schema_version: 0.3 type: filter identifier: cairoblend_mode title: Set Cairographics Blend Mode version: 1 copyright: Meltytech, LLC creator: Dan Dennedy license: LGPLv2.1 language: en tags: - Video description: Change the blend mode used by the frei0r.cairoblend transition. parameters: - identifier: mode argument: yes title: Blend mode type: string description: > This filter can be used to change the blend mode for a single clip when using the cairoblend transition to composite tracks. default: normal mlt-7.22.0/src/modules/frei0r/filter_frei0r.c000664 000000 000000 00000004670 14531534050 020713 0ustar00rootroot000000 000000 /* * filter_frei0r.c -- frei0r filter * Copyright (c) 2008 Marco Gittler * Copyright (C) 2009-2019 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "frei0r_helper.h" #include static int filter_get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { mlt_filter filter = mlt_frame_pop_service(frame); *format = mlt_image_rgba; mlt_log_debug(MLT_FILTER_SERVICE(filter), "frei0r %dx%d\n", *width, *height); int error = mlt_frame_get_image(frame, image, format, width, height, 0); if (error == 0 && *image) { mlt_position position = mlt_filter_get_position(filter, frame); mlt_profile profile = mlt_service_profile(MLT_FILTER_SERVICE(filter)); double time = (double) position / mlt_profile_fps(profile); int length = mlt_filter_get_length2(filter, frame); process_frei0r_item(MLT_FILTER_SERVICE(filter), position, time, length, frame, image, width, height); } return error; } mlt_frame filter_process(mlt_filter filter, mlt_frame frame) { mlt_frame_push_service(frame, filter); mlt_frame_push_get_image(frame, filter_get_image); return frame; } void filter_close(mlt_filter filter) { destruct(MLT_FILTER_PROPERTIES(filter)); filter->close = NULL; filter->parent.close = NULL; mlt_service_close(&filter->parent); } mlt-7.22.0/src/modules/frei0r/frei0r_helper.c000664 000000 000000 00000034326 14531534050 020706 0ustar00rootroot000000 000000 /* * frei0r_helper.c -- frei0r helper * Copyright (c) 2008 Marco Gittler * Copyright (C) 2009-2023 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "frei0r_helper.h" #include #include #include #ifdef _WIN32 #include #endif const char *CAIROBLEND_MODE_PROPERTY = "frei0r.cairoblend.mode"; static void rgba_bgra(uint8_t *src, uint8_t *dst, int width, int height) { int n = width * height + 1; while (--n) { *dst++ = src[2]; *dst++ = src[1]; *dst++ = src[0]; *dst++ = src[3]; src += 4; } } struct update_context { f0r_instance_t frei0r; int width; int height; double time; uint32_t *inputs[2]; uint32_t *output; void (*f0r_update)(f0r_instance_t instance, double time, const uint32_t *inframe, uint32_t *outframe); void (*f0r_update2)(f0r_instance_t instance, double time, const uint32_t *inframe1, const uint32_t *inframe2, const uint32_t *inframe3, uint32_t *outframe); }; static int f0r_update_slice(int id, int index, int count, void *context) { struct update_context *ctx = context; int slice_height = ctx->height / count; int slice_offset = index * slice_height * ctx->width; uint32_t *input = ctx->inputs[0] + slice_offset; uint32_t *output = ctx->output + slice_offset; ctx->f0r_update(ctx->frei0r, ctx->time, input, output); return 0; } static int f0r_update2_slice(int id, int index, int count, void *context) { struct update_context *ctx = context; int slice_height = ctx->height / count; int slice_offset = index * slice_height * ctx->width; uint32_t *inputs[2] = {ctx->inputs[0] + slice_offset, ctx->inputs[1] + slice_offset}; uint32_t *output = ctx->output + slice_offset; ctx->f0r_update2(ctx->frei0r, ctx->time, inputs[0], inputs[1], NULL, output); return 0; } int process_frei0r_item(mlt_service service, mlt_position position, double time, int length, mlt_frame frame, uint8_t **image, int *width, int *height) { int i = 0; mlt_properties prop = MLT_SERVICE_PROPERTIES(service); f0r_instance_t (*f0r_construct)(unsigned int, unsigned int) = mlt_properties_get_data(prop, "f0r_construct", NULL); if (!f0r_construct) { return -1; } void (*f0r_update)(f0r_instance_t instance, double time, const uint32_t *inframe, uint32_t *outframe) = mlt_properties_get_data(prop, "f0r_update", NULL); void (*f0r_get_plugin_info)(f0r_plugin_info_t *) = mlt_properties_get_data(prop, "f0r_get_plugin_info", NULL); void (*f0r_get_param_info)(f0r_param_info_t * info, int param_index) = mlt_properties_get_data(prop, "f0r_get_param_info", NULL); void (*f0r_set_param_value)(f0r_instance_t instance, f0r_param_t param, int param_index) = mlt_properties_get_data(prop, "f0r_set_param_value", NULL); void (*f0r_get_param_value)(f0r_instance_t instance, f0r_param_t param, int param_index) = mlt_properties_get_data(prop, "f0r_get_param_value", NULL); void (*f0r_update2)(f0r_instance_t instance, double time, const uint32_t *inframe1, const uint32_t *inframe2, const uint32_t *inframe3, uint32_t *outframe) = mlt_properties_get_data(prop, "f0r_update2", NULL); mlt_service_type type = mlt_service_identify(service); int not_thread_safe = mlt_properties_get_int(prop, "_not_thread_safe"); int slice_count = mlt_properties_get(prop, "threads") ? mlt_properties_get_int(prop, "threads") : -1; const char *service_name = mlt_properties_get(prop, "mlt_service"); int is_cairoblend = service_name && !strcmp("frei0r.cairoblend", service_name); double scale = mlt_profile_scale_width(mlt_service_profile(service), *width); mlt_properties scale_map = mlt_properties_get_data(prop, "_resolution_scale", NULL); // Determine the number of slices and slice height if (slice_count == 0) { slice_count = mlt_slices_count_normal(); } else { slice_count = CLAMP(slice_count, 1, mlt_slices_count_normal()); } // Reduce the slice count until the height is a multiple of slices while (slice_count > 1 && (*height % slice_count)) { --slice_count; } int slice_height = *height / slice_count; // Use width and height in the frei0r instance key char ctorname[1024] = ""; if (not_thread_safe) sprintf(ctorname, "_ctor-"); else #ifdef _WIN32 sprintf(ctorname, "_ctor-%lu", GetCurrentThreadId()); #else sprintf(ctorname, "_ctor-%p", (void *) pthread_self()); #endif mlt_service_lock(service); mlt_properties ctor = mlt_properties_get_data(prop, ctorname, NULL); if (!ctor || mlt_properties_get_int(ctor, "width") != *width || mlt_properties_get_int(ctor, "height") != slice_height) { mlt_properties_clear(prop, ctorname); ctor = mlt_properties_new(); f0r_instance_t new_inst = f0r_construct(*width, slice_height); mlt_properties_set_int(ctor, "width", *width); mlt_properties_set_int(ctor, "height", slice_height); mlt_properties_set_data(ctor, "inst", new_inst, 0, mlt_properties_get_data(prop, "f0r_destruct", NULL), NULL); mlt_properties_set_data(prop, ctorname, ctor, 0, (mlt_destructor) mlt_properties_close, NULL); } f0r_instance_t inst = mlt_properties_get_data(ctor, "inst", NULL); if (!inst) { mlt_service_unlock(service); return -1; } if (!not_thread_safe && slice_count == 1) mlt_service_unlock(service); f0r_plugin_info_t info; memset(&info, 0, sizeof(info)); if (f0r_get_plugin_info) { f0r_get_plugin_info(&info); for (i = 0; i < info.num_params; i++) { prop = MLT_SERVICE_PROPERTIES(service); f0r_param_info_t pinfo; f0r_get_param_info(&pinfo, i); char index[20]; snprintf(index, sizeof(index), "%d", i); const char *name = index; char *val = mlt_properties_get(prop, name); // Special cairoblend handling for an override from the cairoblend_mode filter. if (is_cairoblend && i == 1) { if (mlt_properties_get(MLT_FRAME_PROPERTIES(frame), CAIROBLEND_MODE_PROPERTY)) { name = CAIROBLEND_MODE_PROPERTY; prop = MLT_FRAME_PROPERTIES(frame); val = mlt_properties_get(prop, name); } else if (!val && !mlt_properties_get(MLT_FRAME_PROPERTIES(frame), name)) { // Reset plugin back to its default value. char *default_val = "normal"; char *plugin_val = NULL; f0r_get_param_value(inst, &plugin_val, i); if (plugin_val && strcmp(default_val, plugin_val)) { f0r_set_param_value(inst, &default_val, i); continue; } } } if (!val) { name = pinfo.name; val = mlt_properties_get(prop, name); } if (!val) { // Use the backwards-compatibility param name map. mlt_properties map = mlt_properties_get_data(prop, "_param_name_map", NULL); if (map) { int j; for (j = 0; !val && j < mlt_properties_count(map); j++) { if (!strcmp(mlt_properties_get_value(map, j), index)) { name = mlt_properties_get_name(map, j); val = mlt_properties_get(prop, name); } } } } if (val) { switch (pinfo.type) { case F0R_PARAM_DOUBLE: case F0R_PARAM_BOOL: { double t = mlt_properties_anim_get_double(prop, name, position, length); if (scale != 1.0) { double scale2 = mlt_properties_get_double(scale_map, name); if (scale2 != 0.0) t *= scale * scale2; } f0r_set_param_value(inst, &t, i); break; } case F0R_PARAM_COLOR: { f0r_param_color_t f_color; mlt_color m_color = mlt_properties_get(prop, index) ? mlt_properties_anim_get_color(prop, index, position, length) : mlt_properties_anim_get_color(prop, pinfo.name, position, length); f_color.r = (float) m_color.r / 255.0f; f_color.g = (float) m_color.g / 255.0f; f_color.b = (float) m_color.b / 255.0f; f0r_set_param_value(inst, &f_color, i); break; } case F0R_PARAM_STRING: { val = mlt_properties_anim_get(prop, name, position, length); f0r_set_param_value(inst, &val, i); break; } } } } } int video_area = *width * *height; uint32_t *result = mlt_pool_alloc(video_area * sizeof(uint32_t)); uint32_t *extra = NULL; uint32_t *source[2] = {(uint32_t *) image[0], (uint32_t *) image[1]}; uint32_t *dest = result; if (info.color_model == F0R_COLOR_MODEL_BGRA8888) { if (type == mlt_service_producer_type) { dest = source[0]; } else { rgba_bgra(image[0], (uint8_t *) result, *width, *height); source[0] = result; dest = (uint32_t *) image[0]; if (type == mlt_service_transition_type && f0r_update2) { extra = mlt_pool_alloc(video_area * sizeof(uint32_t)); rgba_bgra(image[1], (uint8_t *) extra, *width, *height); source[1] = extra; } } } if (type == mlt_service_producer_type) { if (slice_count > 1) { struct update_context ctx = {.frei0r = inst, .width = *width, .height = *height, .time = time, .inputs = {NULL, NULL}, .output = dest, .f0r_update = f0r_update}; mlt_slices_run_normal(slice_count, f0r_update_slice, &ctx); } else { f0r_update(inst, time, NULL, dest); } } else if (type == mlt_service_filter_type) { if (slice_count > 1) { struct update_context ctx = {.frei0r = inst, .width = *width, .height = *height, .time = time, .inputs = {source[0], NULL}, .output = dest, .f0r_update = f0r_update}; mlt_slices_run_normal(slice_count, f0r_update_slice, &ctx); } else { f0r_update(inst, time, source[0], dest); } } else if (type == mlt_service_transition_type && f0r_update2) { if (slice_count > 1) { struct update_context ctx = {.frei0r = inst, .width = *width, .height = *height, .time = time, .inputs = {source[0], source[1]}, .output = dest, .f0r_update2 = f0r_update2}; mlt_slices_run_normal(slice_count, f0r_update2_slice, &ctx); } else { f0r_update2(inst, time, source[0], source[1], NULL, dest); } } if (not_thread_safe || slice_count != 1) mlt_service_unlock(service); if (info.color_model == F0R_COLOR_MODEL_BGRA8888) { rgba_bgra((uint8_t *) dest, (uint8_t *) result, *width, *height); } *image = (uint8_t *) result; mlt_frame_set_image(frame, (uint8_t *) result, video_area * sizeof(uint32_t), mlt_pool_release); if (extra) mlt_pool_release(extra); return 0; } void destruct(mlt_properties prop) { void (*f0r_deinit)(void) = mlt_properties_get_data(prop, "f0r_deinit", NULL); int i = 0; if (f0r_deinit) f0r_deinit(); for (i = 0; i < mlt_properties_count(prop); i++) { if (strstr(mlt_properties_get_name(prop, i), "_ctor-")) { mlt_properties_clear(prop, mlt_properties_get_name(prop, i)); } } void (*dlclose)(void *) = mlt_properties_get_data(prop, "_dlclose", NULL); void *handle = mlt_properties_get_data(prop, "_dlclose_handle", NULL); if (handle && dlclose) dlclose(handle); } mlt-7.22.0/src/modules/frei0r/frei0r_helper.h000664 000000 000000 00000002426 14531534050 020707 0ustar00rootroot000000 000000 /* * frei0r_helper.h -- frei0r helper * Copyright (c) 2008 Marco Gittler * Copyright (C) 2009-2019 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include int process_frei0r_item(mlt_service, mlt_position position, double time, int length, mlt_frame, uint8_t **image, int *width, int *height); void destruct(mlt_properties prop); extern const char *CAIROBLEND_MODE_PROPERTY; mlt-7.22.0/src/modules/frei0r/not_thread_safe.txt000664 000000 000000 00000000510 14531534050 021666 0ustar00rootroot000000 000000 # plugin name = lowest version that is thread safe or empty if not yet thread safe baltan bigsh0t_eq_mask bigsh0t_eq_to_rect bigsh0t_eq_to_stereo bigsh0t_hemi_to_eq bigsh0t_rect_to_eq bigsh0t_stabilize_360 bigsh0t_transform_360 colorhalftone delay0r delaygrab distort0r ising0r medians nervous partik0l plasma tehRoxx0r vertigo mlt-7.22.0/src/modules/frei0r/param_name_map.yaml000664 000000 000000 00000000370 14531534050 021625 0ustar00rootroot000000 000000 # MLT frei0r param name mapping from old name to current index # plugin: # param_name: index lenscorrection: xcenter: 0 ycenter: 1 correctionnearcenter: 2 correctionnearedges: 3 brightness: 4 pixeliz0r: BlockSizeX: 0 BlockSizeY: 1 mlt-7.22.0/src/modules/frei0r/producer_frei0r.c000664 000000 000000 00000007526 14531534050 021254 0ustar00rootroot000000 000000 /* * producer_frei0r.c -- frei0r producer * Copyright (c) 2009 Jean-Baptiste Mardelle * Copyright (C) 2009-2019 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include "frei0r_helper.h" #include #include static int producer_get_image(mlt_frame frame, uint8_t **buffer, mlt_image_format *format, int *width, int *height, int writable) { mlt_producer producer = mlt_frame_pop_service(frame); // Choose suitable out values if nothing specific requested if (*width <= 0) *width = mlt_service_profile(MLT_PRODUCER_SERVICE(producer))->width; if (*height <= 0) *height = mlt_service_profile(MLT_PRODUCER_SERVICE(producer))->height; // Allocate the image *format = mlt_image_rgba; int size = mlt_image_format_size(*format, *width, *height, NULL); // Allocate the image *buffer = mlt_pool_alloc(size); // Update the frame mlt_frame_set_image(frame, *buffer, size, mlt_pool_release); if (*buffer != NULL) { mlt_position position = mlt_frame_get_position(frame); mlt_profile profile = mlt_service_profile(MLT_PRODUCER_SERVICE(producer)); double time = (double) position / mlt_profile_fps(profile); int length = mlt_producer_get_playtime(producer); process_frei0r_item(MLT_PRODUCER_SERVICE(producer), position, time, length, frame, buffer, width, height); } return 0; } int producer_get_frame(mlt_producer producer, mlt_frame_ptr frame, int index) { // Generate a frame *frame = mlt_frame_init(MLT_PRODUCER_SERVICE(producer)); if (*frame != NULL) { // Obtain properties of frame and producer mlt_properties properties = MLT_FRAME_PROPERTIES(*frame); // Update timecode on the frame we're creating mlt_frame_set_position(*frame, mlt_producer_position(producer)); // Set producer-specific frame properties mlt_properties_set_int(properties, "progressive", 1); mlt_profile profile = mlt_service_profile(MLT_PRODUCER_SERVICE(producer)); mlt_properties_set_double(properties, "aspect_ratio", mlt_profile_sar(profile)); mlt_properties_set_int(properties, "meta.media.width", profile->width); mlt_properties_set_int(properties, "meta.media.height", profile->height); // Inform framework that this producer creates rgba frames by default mlt_properties_set_int(properties, "format", mlt_image_rgba); // Push the get_image method mlt_frame_push_service(*frame, producer); mlt_frame_push_get_image(*frame, producer_get_image); } // Calculate the next timecode mlt_producer_prepare_next(producer); return 0; } void producer_close(mlt_producer producer) { producer->close = NULL; mlt_producer_close(producer); free(producer); } mlt-7.22.0/src/modules/frei0r/resolution_scale.yml000664 000000 000000 00000000426 14531534050 022103 0ustar00rootroot000000 000000 # This files contains a list of plugin parameters that are sensitive to the # image resolution. This only works for parameters with type F0R_PARAM_DOUBLE. # plugin: # parameter#: scale factor in addition to mlt_frame_resolution_scale colorhalftone: 0: 1.0 IIRblur: 0: 1.7 mlt-7.22.0/src/modules/frei0r/transition_frei0r.c000664 000000 000000 00000013102 14531534050 021606 0ustar00rootroot000000 000000 /* * transition_frei0r.c -- frei0r transition * Copyright (c) 2008 Marco Gittler * Copyright (C) 2009-2020 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "frei0r_helper.h" #include #include static int transition_get_image(mlt_frame a_frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { mlt_frame b_frame = mlt_frame_pop_frame(a_frame); mlt_transition transition = mlt_frame_pop_service(a_frame); mlt_properties properties = MLT_TRANSITION_PROPERTIES(transition); mlt_properties a_props = MLT_FRAME_PROPERTIES(a_frame); mlt_properties b_props = MLT_FRAME_PROPERTIES(b_frame); int invert = mlt_properties_get_int(properties, "invert"); uint8_t *images[] = {NULL, NULL, NULL}; int request_width = *width; int request_height = *height; int error = 0; // Get the B-frame. *format = mlt_image_rgba; error = mlt_frame_get_image(b_frame, &images[1], format, width, height, 0); if (error) return error; if (b_frame->convert_image && (*width != request_width || *height != request_height)) { mlt_properties_set_int(b_props, "convert_image_width", request_width); mlt_properties_set_int(b_props, "convert_image_height", request_height); b_frame->convert_image(b_frame, &images[1], format, *format); *width = request_width; *height = request_height; } const char *service_name = mlt_properties_get(properties, "mlt_service"); int is_cairoblend = service_name && !strcmp("frei0r.cairoblend", service_name); const char *blend_mode = mlt_properties_get(b_props, CAIROBLEND_MODE_PROPERTY); // An optimization for cairoblend in normal (over) mode and opaque B frame. if (is_cairoblend && (!mlt_properties_get(properties, "0") || mlt_properties_get_double(properties, "0") == 1.0) && (!mlt_properties_get(properties, "1") || !strcmp("normal", mlt_properties_get(properties, "1"))) && (!blend_mode || !strcmp("normal", blend_mode)) // Check if the alpha channel is entirely opaque. && mlt_image_rgba_opaque(images[1], *width, *height)) { if (invert) { error = mlt_frame_get_image(a_frame, image, format, width, height, 0); } else { // Pass all required frame properties mlt_properties_pass_list(a_props, b_props, "progressive,distort,colorspace,full_range,force_full_luma," "top_field_first,color_trc"); *image = images[1]; } } else { error = mlt_frame_get_image(a_frame, &images[0], format, width, height, 0); if (error) return error; if (a_frame->convert_image && (*width != request_width || *height != request_height)) { mlt_properties_set_int(a_props, "convert_image_width", request_width); mlt_properties_set_int(a_props, "convert_image_height", request_height); a_frame->convert_image(a_frame, &images[0], format, *format); *width = request_width; *height = request_height; } mlt_position position = mlt_transition_get_position(transition, a_frame); mlt_profile profile = mlt_service_profile(MLT_TRANSITION_SERVICE(transition)); double time = (double) position / mlt_profile_fps(profile); int length = mlt_transition_get_length(transition); // Special cairoblend handling for an override from the cairoblend_mode filter. if (is_cairoblend) { mlt_properties_set(a_props, CAIROBLEND_MODE_PROPERTY, blend_mode); } process_frei0r_item(MLT_TRANSITION_SERVICE(transition), position, time, length, !invert ? a_frame : b_frame, images, width, height); *width = mlt_properties_get_int(!invert ? a_props : b_props, "width"); *height = mlt_properties_get_int(!invert ? a_props : b_props, "height"); *image = mlt_properties_get_data(!invert ? a_props : b_props, "image", NULL); } return error; } mlt_frame transition_process(mlt_transition transition, mlt_frame a_frame, mlt_frame b_frame) { mlt_frame_push_service(a_frame, transition); mlt_frame_push_frame(a_frame, b_frame); mlt_frame_push_get_image(a_frame, transition_get_image); return a_frame; } void transition_close(mlt_transition transition) { destruct(MLT_TRANSITION_PROPERTIES(transition)); transition->close = NULL; mlt_transition_close(transition); } mlt-7.22.0/src/modules/gdk/000775 000000 000000 00000000000 14531534050 015362 5ustar00rootroot000000 000000 mlt-7.22.0/src/modules/gdk/CMakeLists.txt000664 000000 000000 00000002636 14531534050 020131 0ustar00rootroot000000 000000 add_library(mltgdk MODULE factory.c filter_rescale.c pixops.c producer_pixbuf.c ) file(GLOB YML "*.yml") add_custom_target(Other_gdk_Files SOURCES ${YML} ) target_compile_options(mltgdk PRIVATE ${MLT_COMPILE_OPTIONS}) target_link_libraries(mltgdk PRIVATE mlt m Threads::Threads PkgConfig::GdkPixbuf) target_compile_definitions(mltgdk PRIVATE USE_PIXBUF) if(TARGET PkgConfig::libexif) target_link_libraries(mltgdk PRIVATE PkgConfig::libexif) target_compile_definitions(mltgdk PRIVATE USE_EXIF) endif() if(TARGET PkgConfig::pango AND TARGET PkgConfig::fontconfig) target_sources(mltgdk PRIVATE producer_pango.c) target_link_libraries(mltgdk PRIVATE PkgConfig::pango PkgConfig::fontconfig PkgConfig::pangoft2) if(APPLE OR MINGW) target_link_libraries(mltgdk PRIVATE iconv) endif() target_compile_definitions(mltgdk PRIVATE USE_PANGO) install(FILES producer_pango.yml DESTINATION ${MLT_INSTALL_DATA_DIR}/gdk) endif() if(CPU_MMX) target_sources(mltgdk PRIVATE have_mmx.S scale_line_22_yuv_mmx.S) target_compile_definitions(mltgdk PRIVATE USE_MMX) endif() if(CPU_X86_64) target_compile_definitions(mltgdk PRIVATE ARCH_X86_64) endif() set_target_properties(mltgdk PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${MLT_MODULE_OUTPUT_DIRECTORY}") install(TARGETS mltgdk LIBRARY DESTINATION ${MLT_INSTALL_MODULE_DIR}) install(FILES filter_rescale.yml producer_pixbuf.yml DESTINATION ${MLT_INSTALL_DATA_DIR}/gdk) mlt-7.22.0/src/modules/gdk/factory.c000664 000000 000000 00000006244 14531534050 017203 0ustar00rootroot000000 000000 /* * factory.c -- the factory method interfaces * Copyright (C) 2003-2020 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #ifdef USE_PIXBUF extern mlt_producer producer_pixbuf_init(char *filename); extern mlt_filter filter_rescale_init(mlt_profile profile, char *arg); #endif #ifdef USE_PANGO extern mlt_producer producer_pango_init(const char *filename); #endif static void initialise() { static int init = 0; if (init == 0) { init = 1; #if !GLIB_CHECK_VERSION(2, 35, 0) g_type_init(); #endif if (getenv("MLT_PIXBUF_PRODUCER_CACHE")) { int n = atoi(getenv("MLT_PIXBUF_PRODUCER_CACHE")); mlt_service_cache_set_size(NULL, "pixbuf.image", n); mlt_service_cache_set_size(NULL, "pixbuf.alpha", n); mlt_service_cache_set_size(NULL, "pixbuf.pixbuf", n); } if (getenv("MLT_PANGO_PRODUCER_CACHE")) { int n = atoi(getenv("MLT_PANGO_PRODUCER_CACHE")); mlt_service_cache_set_size(NULL, "pango.image", n); } } } void *create_service(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { initialise(); #ifdef USE_PIXBUF if (!strcmp(id, "pixbuf")) return producer_pixbuf_init(arg); #endif #ifdef USE_PANGO if (!strcmp(id, "pango")) return producer_pango_init(arg); #endif #ifdef USE_PIXBUF if (!strcmp(id, "gtkrescale")) return filter_rescale_init(profile, arg); #endif return NULL; } static mlt_properties metadata(mlt_service_type type, const char *id, void *data) { char file[PATH_MAX]; snprintf(file, PATH_MAX, "%s/gdk/%s", mlt_environment("MLT_DATA"), (char *) data); return mlt_properties_parse_yaml(file); } MLT_REPOSITORY { MLT_REGISTER(mlt_service_filter_type, "gtkrescale", create_service); MLT_REGISTER(mlt_service_link_type, "gtkrescale", mlt_link_filter_init); MLT_REGISTER(mlt_service_producer_type, "pango", create_service); MLT_REGISTER(mlt_service_producer_type, "pixbuf", create_service); MLT_REGISTER_METADATA(mlt_service_filter_type, "gtkrescale", metadata, "filter_rescale.yml"); MLT_REGISTER_METADATA(mlt_service_link_type, "gtkrescale", mlt_link_filter_metadata, NULL); MLT_REGISTER_METADATA(mlt_service_producer_type, "pango", metadata, "producer_pango.yml"); MLT_REGISTER_METADATA(mlt_service_producer_type, "pixbuf", metadata, "producer_pixbuf.yml"); } mlt-7.22.0/src/modules/gdk/filter_rescale.c000664 000000 000000 00000012174 14531534050 020516 0ustar00rootroot000000 000000 /* * filter_rescale.c -- scale the producer video frame size to match the consumer * Copyright (C) 2003-2014 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "pixops.h" #include #include #include #include #include #include #include static int filter_scale(mlt_frame this, uint8_t **image, mlt_image_format *format, int iwidth, int iheight, int owidth, int oheight) { // Get the properties mlt_properties properties = MLT_FRAME_PROPERTIES(this); // Get the requested interpolation method char *interps = mlt_properties_get(properties, "consumer.rescale"); // Convert to the GTK flag int interp = PIXOPS_INTERP_BILINEAR; if (strcmp(interps, "nearest") == 0) interp = PIXOPS_INTERP_NEAREST; else if (strcmp(interps, "tiles") == 0) interp = PIXOPS_INTERP_TILES; else if (strcmp(interps, "hyper") == 0 || strcmp(interps, "bicubic") == 0) interp = PIXOPS_INTERP_HYPER; int bpp; int size = mlt_image_format_size(*format, owidth, oheight, &bpp); // Carry out the rescaling switch (*format) { case mlt_image_yuv422: { // Create the output image uint8_t *output = mlt_pool_alloc(size); // Calculate strides int istride = iwidth * 2; int ostride = owidth * 2; yuv422_scale_simple(output, owidth, oheight, ostride, *image, iwidth, iheight, istride, interp); // Now update the frame mlt_frame_set_image(this, output, size, mlt_pool_release); // Return the output *image = output; break; } case mlt_image_rgb: case mlt_image_rgba: { if (strcmp(interps, "none") && (iwidth != owidth || iheight != oheight)) { // Create the output image uint8_t *output = mlt_pool_alloc(size); GdkPixbuf *pixbuf = gdk_pixbuf_new_from_data(*image, GDK_COLORSPACE_RGB, (*format == mlt_image_rgba), 8, iwidth, iheight, iwidth * bpp, NULL, NULL); GdkPixbuf *scaled = gdk_pixbuf_scale_simple(pixbuf, owidth, oheight, interp); g_object_unref(pixbuf); int src_stride = gdk_pixbuf_get_rowstride(scaled); int dst_stride = owidth * bpp; if (src_stride != dst_stride) { int y = oheight; uint8_t *src = gdk_pixbuf_get_pixels(scaled); uint8_t *dst = output; while (y--) { memcpy(dst, src, dst_stride); dst += dst_stride; src += src_stride; } } else { memcpy(output, gdk_pixbuf_get_pixels(scaled), owidth * oheight * bpp); } g_object_unref(scaled); // Now update the frame mlt_frame_set_image(this, output, size, mlt_pool_release); // Return the output *image = output; } break; } default: break; } return 0; } /** Constructor for the filter. */ mlt_filter filter_rescale_init(mlt_profile profile, char *arg) { // Create a new scaler mlt_filter this = mlt_factory_filter(profile, "rescale", arg); // If successful, then initialise it if (this != NULL) { // Get the properties mlt_properties properties = MLT_FILTER_PROPERTIES(this); // Set the inerpolation mlt_properties_set(properties, "interpolation", arg == NULL ? "bilinear" : arg); // Set the method mlt_properties_set_data(properties, "method", filter_scale, 0, NULL, NULL); } return this; } mlt-7.22.0/src/modules/gdk/filter_rescale.yml000664 000000 000000 00000002232 14531534050 021067 0ustar00rootroot000000 000000 schema_version: 7.0 type: filter identifier: gtkrescale title: Gtk Rescale version: 1 copyright: Meltytech, LLC creator: Dan Dennedy license: LGPLv2.1 language: en tags: - Video - Hidden description: > Scale the producer video frame size to match the consumer. This filter is designed for use as a normalizer for the loader producer. notes: > If a property "consumer_aspect_ratio" exists on the frame, then rescaler normalizes the producer's aspect ratio and maximises the size of the frame, but may not produce the consumer's requested dimension. Therefore, this option works best in conjunction with the resize filter. This behavior can be disabled by another service by either removing the property, setting it to zero, or setting frame property "distort" to 1. parameters: - identifier: interpolation argument: yes title: Interpolation type: string description: The rescaling method. values: - nearest (lowest quality, fastest) - tiles - bilinear (good quality, moderate speed) - hyper (best quality, slowest) required: no readonly: no default: bilinear widget: combo mlt-7.22.0/src/modules/gdk/have_mmx.S000664 000000 000000 00000003045 14531534050 017314 0ustar00rootroot000000 000000 /* * Copyright (C) 2000 Red Hat, Inc * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301, USA. */ .file "have_mmx.S" .version "01.01" gcc2_compiled.: .text .align 16 #if !defined(__MINGW32__) && !defined(__CYGWIN__) && !defined(__INTERIX) /* Magic indicating no need for an executable stack */ #if !defined __powerpc64__ && !defined __ia64__ .section .note.GNU-stack; .previous #endif .globl _pixops_have_mmx .type _pixops_have_mmx,@function _pixops_have_mmx: #else .globl __pixops_have_mmx __pixops_have_mmx: #endif push %ebx # Check if bit 21 in flags word is writeable pushfl popl %eax movl %eax,%ebx xorl $0x00200000, %eax pushl %eax popfl pushfl popl %eax cmpl %eax, %ebx je .notfound # OK, we have CPUID movl $1, %eax cpuid test $0x00800000, %edx jz .notfound movl $1, %eax jmp .out .notfound: movl $0, %eax .out: popl %ebx ret mlt-7.22.0/src/modules/gdk/pixops.c000664 000000 000000 00000047202 14531534050 017055 0ustar00rootroot000000 000000 /* GdkPixbuf library - Scaling and compositing functions * * Original: * Copyright (C) 2000 Red Hat, Inc * Author: Owen Taylor * * Modification for MLT: * Copyright (C) 2003-2014 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301, USA. */ #include #include #include #include "pixops.h" #define SUBSAMPLE_BITS 4 #define SUBSAMPLE (1 << SUBSAMPLE_BITS) #define SUBSAMPLE_MASK ((1 << SUBSAMPLE_BITS)-1) #define SCALE_SHIFT 16 typedef struct _PixopsFilter PixopsFilter; typedef struct _PixopsFilterDimension PixopsFilterDimension; struct _PixopsFilterDimension { int n; double offset; double *weights; }; struct _PixopsFilter { PixopsFilterDimension x; PixopsFilterDimension y; double overall_alpha; }; typedef guchar *( *PixopsLineFunc ) ( int *weights, int n_x, int n_y, guchar *dest, int dest_x, guchar *dest_end, guchar **src, int x_init, int x_step, int src_width ); typedef void ( *PixopsPixelFunc ) ( guchar *dest, guint y1, guint cr, guint y2, guint cb ); /* mmx function declarations */ #if defined(USE_MMX) && !defined(ARCH_X86_64) guchar *pixops_scale_line_22_yuv_mmx ( guint32 weights[ 16 ][ 8 ], guchar *p, guchar *q1, guchar *q2, int x_step, guchar *p_stop, int x_init, int destx ); int _pixops_have_mmx ( void ); #endif static inline int get_check_shift ( int check_size ) { int check_shift = 0; g_return_val_if_fail ( check_size >= 0, 4 ); while ( !( check_size & 1 ) ) { check_shift++; check_size >>= 1; } return check_shift; } static inline void pixops_scale_nearest ( guchar *dest_buf, int render_x0, int render_y0, int render_x1, int render_y1, int dest_rowstride, const guchar *src_buf, int src_width, int src_height, int src_rowstride, double scale_x, double scale_y ) { register int i, j; register int x_step = ( 1 << SCALE_SHIFT ) / scale_x; register int y_step = ( 1 << SCALE_SHIFT ) / scale_y; register int x, x_scaled; for ( i = 0; i < ( render_y1 - render_y0 ); i++ ) { const guchar *src = src_buf + ( ( ( i + render_y0 ) * y_step + ( y_step >> 1 ) ) >> SCALE_SHIFT ) * src_rowstride; guchar *dest = dest_buf + i * dest_rowstride; x = render_x0 * x_step + ( x_step >> 1 ); for ( j = 0; j < ( render_x1 - render_x0 ); j++ ) { x_scaled = x >> SCALE_SHIFT; *dest++ = src[ x_scaled << 1 ]; *dest++ = src[ ( ( x_scaled >> 1 ) << 2 ) + ( ( j & 1 ) << 1 ) + 1 ]; x += x_step; } } } static inline guchar * scale_line ( int *weights, int n_x, int n_y, guchar *dest, int dest_x, guchar *dest_end, guchar **src, int x_init, int x_step, int src_width ) { register int x = x_init; register int i, j, x_scaled, y_index, uv_index; while ( dest < dest_end ) { unsigned int y = 0, uv = 0; int *pixel_weights = weights + ( ( x >> ( SCALE_SHIFT - SUBSAMPLE_BITS ) ) & SUBSAMPLE_MASK ) * n_x * n_y; x_scaled = x >> SCALE_SHIFT; y_index = x_scaled << 1; uv_index = ( ( x_scaled >> 1 ) << 2 ) + ( ( dest_x & 1 ) << 1 ) + 1; for ( i = 0; i < n_y; i++ ) { int *line_weights = pixel_weights + n_x * i; guchar *q = src[ i ]; for ( j = 0; j < n_x; j ++ ) { unsigned int ta = line_weights[ j ]; y += ta * q[ y_index ]; uv += ta * q[ uv_index ]; } } *dest++ = ( y + 0xffff ) >> SCALE_SHIFT; *dest++ = ( uv + 0xffff ) >> SCALE_SHIFT; x += x_step; dest_x++; } return dest; } #if defined(USE_MMX) && !defined(ARCH_X86_64) static inline guchar * scale_line_22_yuv_mmx_stub ( int *weights, int n_x, int n_y, guchar *dest, int dest_x, guchar *dest_end, guchar **src, int x_init, int x_step, int src_width ) { guint32 mmx_weights[ 16 ][ 8 ]; int j; for ( j = 0; j < 16; j++ ) { mmx_weights[ j ][ 0 ] = 0x00010001 * ( weights[ 4 * j ] >> 8 ); mmx_weights[ j ][ 1 ] = 0x00010001 * ( weights[ 4 * j ] >> 8 ); mmx_weights[ j ][ 2 ] = 0x00010001 * ( weights[ 4 * j + 1 ] >> 8 ); mmx_weights[ j ][ 3 ] = 0x00010001 * ( weights[ 4 * j + 1 ] >> 8 ); mmx_weights[ j ][ 4 ] = 0x00010001 * ( weights[ 4 * j + 2 ] >> 8 ); mmx_weights[ j ][ 5 ] = 0x00010001 * ( weights[ 4 * j + 2 ] >> 8 ); mmx_weights[ j ][ 6 ] = 0x00010001 * ( weights[ 4 * j + 3 ] >> 8 ); mmx_weights[ j ][ 7 ] = 0x00010001 * ( weights[ 4 * j + 3 ] >> 8 ); } return pixops_scale_line_22_yuv_mmx ( mmx_weights, dest, src[ 0 ], src[ 1 ], x_step, dest_end, x_init, dest_x ); } #endif /* USE_MMX */ static inline guchar * scale_line_22_yuv ( int *weights, int n_x, int n_y, guchar *dest, int dest_x, guchar *dest_end, guchar **src, int x_init, int x_step, int src_width ) { register int x = x_init; register guchar *src0 = src[ 0 ]; register guchar *src1 = src[ 1 ]; register unsigned int p; register guchar *q0, *q1; register int w1, w2, w3, w4; register int x_scaled, x_aligned, uv_index; while ( dest < dest_end ) { int *pixel_weights = weights + ( ( x >> ( SCALE_SHIFT - SUBSAMPLE_BITS ) ) & SUBSAMPLE_MASK ) * 4; x_scaled = x >> SCALE_SHIFT; w1 = pixel_weights[ 0 ]; w2 = pixel_weights[ 1 ]; w3 = pixel_weights[ 2 ]; w4 = pixel_weights[ 3 ]; /* process Y */ q0 = src0 + ( x_scaled << 1 ); q1 = src1 + ( x_scaled << 1 ); p = w1 * q0[ 0 ]; p += w2 * q0[ 2 ]; p += w3 * q1[ 0 ]; p += w4 * q1[ 2 ]; *dest++ = ( p + 0x8000 ) >> SCALE_SHIFT; /* process U/V */ x_aligned = ( ( x_scaled >> 1 ) << 2 ); uv_index = ( ( dest_x & 1 ) << 1 ) + 1; q0 = src0 + x_aligned; q1 = src1 + x_aligned; p = w1 * q0[ uv_index ]; p += w3 * q1[ uv_index ]; p += w2 * q0[ uv_index ]; p += w4 * q1[ uv_index ]; x += x_step; dest_x ++; *dest++ = ( p + 0x8000 ) >> SCALE_SHIFT; } return dest; } static inline void process_pixel ( int *weights, int n_x, int n_y, guchar *dest, int dest_x, int dest_channels, guchar **src, int src_channels, int x_start, int src_width ) { register unsigned int y = 0, uv = 0; register int i, j; int uv_index = ( ( dest_x & 1 ) << 1 ) + 1; for ( i = 0; i < n_y; i++ ) { int *line_weights = weights + n_x * i; for ( j = 0; j < n_x; j++ ) { unsigned int ta = 0xff * line_weights[ j ]; if ( x_start + j < 0 ) { y += ta * src[ i ][ 0 ]; uv += ta * src[ i ][ uv_index ]; } else if ( x_start + j < src_width ) { y += ta * src[ i ][ ( x_start + j ) << 1 ]; uv += ta * src[ i ][ ( ( ( x_start + j ) >> 1 ) << 2) + uv_index ]; } else { y += ta * src[ i ][ ( src_width - 1 ) << 1 ]; uv += ta * src[ i ][ ( ( ( src_width - 1 ) >> 1 ) << 2) + uv_index ]; } } } *dest++ = ( y + 0xffffff ) >> 24; *dest++ = ( uv + 0xffffff ) >> 24; } static inline void correct_total ( int *weights, int n_x, int n_y, int total, double overall_alpha ) { int correction = ( int ) ( 0.5 + 65536 * overall_alpha ) - total; int remaining, c, d, i; if ( correction != 0 ) { remaining = correction; for ( d = 1, c = correction; c != 0 && remaining != 0; d++, c = correction / d ) for ( i = n_x * n_y - 1; i >= 0 && c != 0 && remaining != 0; i-- ) if ( *( weights + i ) + c >= 0 ) { *( weights + i ) += c; remaining -= c; if ( ( 0 < remaining && remaining < c ) || ( 0 > remaining && remaining > c ) ) c = remaining; } } } static inline int * make_filter_table ( PixopsFilter *filter ) { int i_offset, j_offset; int n_x = filter->x.n; int n_y = filter->y.n; int *weights = g_new ( int, SUBSAMPLE * SUBSAMPLE * n_x * n_y ); for ( i_offset = 0; i_offset < SUBSAMPLE; i_offset++ ) for ( j_offset = 0; j_offset < SUBSAMPLE; j_offset++ ) { double weight; int *pixel_weights = weights + ( ( i_offset * SUBSAMPLE ) + j_offset ) * n_x * n_y; int total = 0; int i, j; for ( i = 0; i < n_y; i++ ) for ( j = 0; j < n_x; j++ ) { weight = filter->x.weights[ ( j_offset * n_x ) + j ] * filter->y.weights[ ( i_offset * n_y ) + i ] * filter->overall_alpha * 65536 + 0.5; total += ( int ) weight; *( pixel_weights + n_x * i + j ) = weight; } correct_total ( pixel_weights, n_x, n_y, total, filter->overall_alpha ); } return weights; } static inline void pixops_process ( guchar *dest_buf, int render_x0, int render_y0, int render_x1, int render_y1, int dest_rowstride, int dest_channels, gboolean dest_has_alpha, const guchar *src_buf, int src_width, int src_height, int src_rowstride, int src_channels, gboolean src_has_alpha, double scale_x, double scale_y, int check_x, int check_y, int check_size, guint32 color1, guint32 color2, PixopsFilter *filter, PixopsLineFunc line_func ) { int i, j; int x, y; /* X and Y position in source (fixed_point) */ guchar **line_bufs = g_new ( guchar *, filter->y.n ); int *filter_weights = make_filter_table ( filter ); int x_step = ( 1 << SCALE_SHIFT ) / scale_x; /* X step in source (fixed point) */ int y_step = ( 1 << SCALE_SHIFT ) / scale_y; /* Y step in source (fixed point) */ int scaled_x_offset = floor ( filter->x.offset * ( 1 << SCALE_SHIFT ) ); /* Compute the index where we run off the end of the source buffer. The furthest * source pixel we access at index i is: * * ((render_x0 + i) * x_step + scaled_x_offset) >> SCALE_SHIFT + filter->x.n - 1 * * So, run_end_index is the smallest i for which this pixel is src_width, i.e, for which: * * (i + render_x0) * x_step >= ((src_width - filter->x.n + 1) << SCALE_SHIFT) - scaled_x_offset * */ #define MYDIV(a,b) ((a) > 0 ? (a) / (b) : ((a) - (b) + 1) / (b)) /* Division so that -1/5 = -1 */ int run_end_x = ( ( ( src_width - filter->x.n + 1 ) << SCALE_SHIFT ) - scaled_x_offset ); int run_end_index = MYDIV ( run_end_x + x_step - 1, x_step ) - render_x0; run_end_index = MIN ( run_end_index, render_x1 - render_x0 ); y = render_y0 * y_step + floor ( filter->y.offset * ( 1 << SCALE_SHIFT ) ); for ( i = 0; i < ( render_y1 - render_y0 ); i++ ) { int dest_x; int y_start = y >> SCALE_SHIFT; int x_start; int *run_weights = filter_weights + ( ( y >> ( SCALE_SHIFT - SUBSAMPLE_BITS ) ) & SUBSAMPLE_MASK ) * filter->x.n * filter->y.n * SUBSAMPLE; guchar *new_outbuf; guchar *outbuf = dest_buf + dest_rowstride * i; guchar *outbuf_end = outbuf + dest_channels * ( render_x1 - render_x0 ); for ( j = 0; j < filter->y.n; j++ ) { if ( y_start < 0 ) line_bufs[ j ] = ( guchar * ) src_buf; else if ( y_start < src_height ) line_bufs[ j ] = ( guchar * ) src_buf + src_rowstride * y_start; else line_bufs[ j ] = ( guchar * ) src_buf + src_rowstride * ( src_height - 1 ); y_start++; } dest_x = check_x; x = render_x0 * x_step + scaled_x_offset; x_start = x >> SCALE_SHIFT; while ( x_start < 0 && outbuf < outbuf_end ) { process_pixel ( run_weights + ( ( x >> ( SCALE_SHIFT - SUBSAMPLE_BITS ) ) & SUBSAMPLE_MASK ) * ( filter->x.n * filter->y.n ), filter->x.n, filter->y.n, outbuf, dest_x, dest_channels, line_bufs, src_channels, x >> SCALE_SHIFT, src_width ); x += x_step; x_start = x >> SCALE_SHIFT; dest_x++; outbuf += dest_channels; } new_outbuf = ( *line_func ) ( run_weights, filter->x.n, filter->y.n, outbuf, dest_x, dest_buf + dest_rowstride * i + run_end_index * dest_channels, line_bufs, x, x_step, src_width ); dest_x += ( new_outbuf - outbuf ) / dest_channels; x = ( dest_x - check_x + render_x0 ) * x_step + scaled_x_offset; outbuf = new_outbuf; while ( outbuf < outbuf_end ) { process_pixel ( run_weights + ( ( x >> ( SCALE_SHIFT - SUBSAMPLE_BITS ) ) & SUBSAMPLE_MASK ) * ( filter->x.n * filter->y.n ), filter->x.n, filter->y.n, outbuf, dest_x, dest_channels, line_bufs, src_channels, x >> SCALE_SHIFT, src_width ); x += x_step; dest_x++; outbuf += dest_channels; } y += y_step; } g_free ( line_bufs ); g_free ( filter_weights ); } /* Compute weights for reconstruction by replication followed by * sampling with a box filter */ static inline void tile_make_weights ( PixopsFilterDimension *dim, double scale ) { int n = ceil ( 1 / scale + 1 ); double *pixel_weights = g_new ( double, SUBSAMPLE * n ); int offset; int i; dim->n = n; dim->offset = 0; dim->weights = pixel_weights; for ( offset = 0; offset < SUBSAMPLE; offset++ ) { double x = ( double ) offset / SUBSAMPLE; double a = x + 1 / scale; for ( i = 0; i < n; i++ ) { if ( i < x ) { if ( i + 1 > x ) * ( pixel_weights++ ) = ( MIN ( i + 1, a ) - x ) * scale; else *( pixel_weights++ ) = 0; } else { if ( a > i ) * ( pixel_weights++ ) = ( MIN ( i + 1, a ) - i ) * scale; else *( pixel_weights++ ) = 0; } } } } /* Compute weights for a filter that, for minification * is the same as 'tiles', and for magnification, is bilinear * reconstruction followed by a sampling with a delta function. */ static inline void bilinear_magnify_make_weights ( PixopsFilterDimension *dim, double scale ) { double * pixel_weights; int n; int offset; int i; if ( scale > 1.0 ) /* Linear */ { n = 2; dim->offset = 0.5 * ( 1 / scale - 1 ); } else /* Tile */ { n = ceil ( 1.0 + 1.0 / scale ); dim->offset = 0.0; } dim->n = n; dim->weights = g_new ( double, SUBSAMPLE * n ); pixel_weights = dim->weights; for ( offset = 0; offset < SUBSAMPLE; offset++ ) { double x = ( double ) offset / SUBSAMPLE; if ( scale > 1.0 ) /* Linear */ { for ( i = 0; i < n; i++ ) *( pixel_weights++ ) = ( ( ( i == 0 ) ? ( 1 - x ) : x ) / scale ) * scale; } else /* Tile */ { double a = x + 1 / scale; /* x * ---------|--.-|----|--.-|------- SRC * ------------|---------|--------- DEST */ for ( i = 0; i < n; i++ ) { if ( i < x ) { if ( i + 1 > x ) * ( pixel_weights++ ) = ( MIN ( i + 1, a ) - x ) * scale; else *( pixel_weights++ ) = 0; } else { if ( a > i ) * ( pixel_weights++ ) = ( MIN ( i + 1, a ) - i ) * scale; else *( pixel_weights++ ) = 0; } } } } } /* Computes the integral from b0 to b1 of * * f(x) = x; 0 <= x < 1 * f(x) = 0; otherwise * * We combine two of these to compute the convolution of * a box filter with a triangular spike. */ static inline double linear_box_half ( double b0, double b1 ) { double a0, a1; double x0, x1; a0 = 0.; a1 = 1.; if ( a0 < b0 ) { if ( a1 > b0 ) { x0 = b0; x1 = MIN ( a1, b1 ); } else return 0; } else { if ( b1 > a0 ) { x0 = a0; x1 = MIN ( a1, b1 ); } else return 0; } return 0.5 * ( x1 * x1 - x0 * x0 ); } /* Compute weights for reconstructing with bilinear * interpolation, then sampling with a box filter */ static inline void bilinear_box_make_weights ( PixopsFilterDimension *dim, double scale ) { int n = ceil ( 1 / scale + 2.0 ); double *pixel_weights = g_new ( double, SUBSAMPLE * n ); double w; int offset, i; dim->offset = -1.0; dim->n = n; dim->weights = pixel_weights; for ( offset = 0 ; offset < SUBSAMPLE; offset++ ) { double x = ( double ) offset / SUBSAMPLE; double a = x + 1 / scale; for ( i = 0; i < n; i++ ) { w = linear_box_half ( 0.5 + i - a, 0.5 + i - x ); w += linear_box_half ( 1.5 + x - i, 1.5 + a - i ); *( pixel_weights++ ) = w * scale; } } } static inline void make_weights ( PixopsFilter *filter, PixopsInterpType interp_type, double scale_x, double scale_y ) { switch ( interp_type ) { case PIXOPS_INTERP_NEAREST: g_assert_not_reached (); break; case PIXOPS_INTERP_TILES: tile_make_weights ( &filter->x, scale_x ); tile_make_weights ( &filter->y, scale_y ); break; case PIXOPS_INTERP_BILINEAR: bilinear_magnify_make_weights ( &filter->x, scale_x ); bilinear_magnify_make_weights ( &filter->y, scale_y ); break; case PIXOPS_INTERP_HYPER: bilinear_box_make_weights ( &filter->x, scale_x ); bilinear_box_make_weights ( &filter->y, scale_y ); break; } } void yuv422_scale ( guchar *dest_buf, int render_x0, int render_y0, int render_x1, int render_y1, int dest_rowstride, int dest_channels, gboolean dest_has_alpha, const guchar *src_buf, int src_width, int src_height, int src_rowstride, int src_channels, gboolean src_has_alpha, double scale_x, double scale_y, PixopsInterpType interp_type ) { PixopsFilter filter = { { 0, 0, 0}, { 0, 0, 0 }, 0 }; PixopsLineFunc line_func; #if defined(USE_MMX) && !defined(ARCH_X86_64) gboolean found_mmx = _pixops_have_mmx(); #endif //g_return_if_fail ( !( dest_channels == 3 && dest_has_alpha ) ); //g_return_if_fail ( !( src_channels == 3 && src_has_alpha ) ); //g_return_if_fail ( !( src_has_alpha && !dest_has_alpha ) ); if ( scale_x == 0 || scale_y == 0 ) return ; if ( interp_type == PIXOPS_INTERP_NEAREST ) { pixops_scale_nearest ( dest_buf, render_x0, render_y0, render_x1, render_y1, dest_rowstride, src_buf, src_width, src_height, src_rowstride, scale_x, scale_y ); return; } filter.overall_alpha = 1.0; make_weights ( &filter, interp_type, scale_x, scale_y ); if ( filter.x.n == 2 && filter.y.n == 2 ) { #if defined(USE_MMX) && !defined(ARCH_X86_64) if ( found_mmx ) { //fprintf( stderr, "rescale: using mmx\n" ); line_func = scale_line_22_yuv_mmx_stub; } else #endif line_func = scale_line_22_yuv; } else line_func = scale_line; pixops_process ( dest_buf, render_x0, render_y0, render_x1, render_y1, dest_rowstride, dest_channels, dest_has_alpha, src_buf, src_width, src_height, src_rowstride, src_channels, src_has_alpha, scale_x, scale_y, 0, 0, 0, 0, 0, &filter, line_func ); g_free ( filter.x.weights ); g_free ( filter.y.weights ); } mlt-7.22.0/src/modules/gdk/pixops.h000664 000000 000000 00000004735 14531534050 017066 0ustar00rootroot000000 000000 /* GdkPixbuf library - Scaling and compositing functions * * Original: * Copyright (C) 2000 Red Hat, Inc * Author: Owen Taylor * * Modification for MLT: * Copyright (C) 2003-2014 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, * Boston, MA 02110-1301, USA. */ #ifndef PIXOPS_H #define PIXOPS_H #include /* Interpolation modes; must match GdkInterpType */ typedef enum { PIXOPS_INTERP_NEAREST, PIXOPS_INTERP_TILES, PIXOPS_INTERP_BILINEAR, PIXOPS_INTERP_HYPER } PixopsInterpType; /* Scale src_buf from src_width / src_height by factors scale_x, scale_y * and composite the portion corresponding to * render_x, render_y, render_width, render_height in the new * coordinate system into dest_buf starting at 0, 0 */ void yuv422_scale (guchar *dest_buf, int render_x0, int render_y0, int render_x1, int render_y1, int dest_rowstride, int dest_channels, int dest_has_alpha, const guchar *src_buf, int src_width, int src_height, int src_rowstride, int src_channels, int src_has_alpha, double scale_x, double scale_y, PixopsInterpType interp_type); #define yuv422_scale_simple( dest_buf, dest_width, dest_height, dest_rowstride, src_buf, src_width, src_height, src_rowstride, interp_type ) \ yuv422_scale( (dest_buf), 0, 0, \ (dest_width), (dest_height), \ (dest_rowstride), 2, 0, \ (src_buf), (src_width), (src_height), \ (src_rowstride), 2, 0, \ (double) (dest_width) / (src_width), (double) (dest_height) / (src_height), \ (PixopsInterpType) interp_type ); #endif mlt-7.22.0/src/modules/gdk/producer_pango.c000664 000000 000000 00000126326 14531534050 020547 0ustar00rootroot000000 000000 /* * producer_pango.c -- a pango-based titler * Copyright (C) 2003-2021 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include #include #include #include #include FT_FREETYPE_H #include #include #include typedef struct producer_pango_s *producer_pango; typedef enum { pango_align_left = 0, pango_align_center, pango_align_right } pango_align; static pthread_mutex_t pango_mutex = PTHREAD_MUTEX_INITIALIZER; struct pango_cached_image_s { uint8_t *image, *alpha; mlt_image_format format; int width, height; }; static void pango_cached_image_destroy(void *p) { struct pango_cached_image_s *i = p; if (!i) return; if (i->image) mlt_pool_release(i->image); if (i->alpha) mlt_pool_release(i->alpha); mlt_pool_release(i); }; struct producer_pango_s { struct mlt_producer_s parent; int width; int height; GdkPixbuf *pixbuf; char *fgcolor; char *bgcolor; char *olcolor; int align; int pad; int outline; char *markup; char *text; char *font; char *family; int size; int style; int weight; int stretch; int rotate; int width_crop; int width_fit; int wrap_type; int wrap_width; int line_spacing; double aspect_ratio; }; static void clean_cached(producer_pango self) { mlt_service_cache_put(MLT_PRODUCER_SERVICE(&self->parent), "pango.image", NULL, 0, NULL); } // special color type used by internal pango routines typedef struct { uint8_t r, g, b, a; } rgba_color; // Forward declarations static int producer_get_frame(mlt_producer parent, mlt_frame_ptr frame, int index); static void producer_close(mlt_producer parent); static void pango_draw_background(GdkPixbuf *pixbuf, rgba_color bg); static GdkPixbuf *pango_get_pixbuf(const char *markup, const char *text, const char *font, rgba_color fg, rgba_color bg, rgba_color ol, int pad, int align, char *family, int style, int weight, int stretch, int size, int outline, int rotate, int width_crop, int width_fit, int wrap_type, int wrap_width, int line_spacing, double aspect_ratio); static void fill_pixbuf(GdkPixbuf *pixbuf, FT_Bitmap *bitmap, int w, int h, int pad, int align, rgba_color fg, rgba_color bg); static void fill_pixbuf_with_outline(GdkPixbuf *pixbuf, FT_Bitmap *bitmap, int w, int h, int pad, int align, rgba_color fg, rgba_color bg, rgba_color ol, int outline); /** Return nonzero if the two strings are equal, ignoring case, up to the first n characters. */ int strncaseeq(const char *s1, const char *s2, size_t n) { for (; n > 0; n--) { if (tolower(*s1++) != tolower(*s2++)) return 0; } return 1; } /** Parse the alignment property. */ static int parse_alignment(char *align) { int ret = pango_align_left; if (align == NULL) ; else if (isdigit(align[0])) ret = atoi(align); else if (align[0] == 'c' || align[0] == 'm') ret = pango_align_center; else if (align[0] == 'r') ret = pango_align_right; return ret; } /** Parse the style property. */ static int parse_style(char *style) { int ret = PANGO_STYLE_NORMAL; if (!strncmp(style, "italic", 6)) ret = PANGO_STYLE_ITALIC; if (!strncmp(style, "oblique", 7)) ret = PANGO_STYLE_OBLIQUE; return ret; } static PangoFT2FontMap *fontmap = NULL; static void on_fontmap_reload(); mlt_producer producer_pango_init(const char *filename) { producer_pango self = calloc(1, sizeof(struct producer_pango_s)); if (self != NULL && mlt_producer_init(&self->parent, self) == 0) { mlt_producer producer = &self->parent; pthread_mutex_lock(&pango_mutex); if (fontmap == NULL) fontmap = (PangoFT2FontMap *) pango_ft2_font_map_new(); pthread_mutex_unlock(&pango_mutex); producer->get_frame = producer_get_frame; producer->close = (mlt_destructor) producer_close; // Get the properties interface mlt_properties properties = MLT_PRODUCER_PROPERTIES(&self->parent); mlt_events_register(properties, "fontmap-reload"); mlt_events_listen(properties, producer, "fontmap-reload", (mlt_listener) on_fontmap_reload); // Set the default properties mlt_properties_set_string(properties, "fgcolour", "0xffffffff"); mlt_properties_set_string(properties, "bgcolour", "0x00000000"); mlt_properties_set_string(properties, "olcolour", "0x00000000"); mlt_properties_set_int(properties, "align", pango_align_left); mlt_properties_set_int(properties, "pad", 0); mlt_properties_set_int(properties, "outline", 0); mlt_properties_set_string(properties, "text", ""); mlt_properties_set_string(properties, "font", NULL); mlt_properties_set_string(properties, "family", "Sans"); mlt_properties_set_int(properties, "size", 48); mlt_properties_set_string(properties, "style", "normal"); mlt_properties_set_string(properties, "encoding", "UTF-8"); mlt_properties_set_int(properties, "weight", PANGO_WEIGHT_NORMAL); mlt_properties_set_int(properties, "stretch", PANGO_STRETCH_NORMAL + 1); mlt_properties_set_int(properties, "rotate", 0); mlt_properties_set_int(properties, "seekable", 1); mlt_properties_set_int(properties, "meta.media.progressive", 1); if (filename == NULL || (filename && (!strcmp(filename, "") || strstr(filename, "") // workaround for old kdenlive countdown generator || strstr(filename, "<producer>")))) { mlt_properties_set_string(properties, "markup", ""); } else if (filename[0] == '+' || strstr(filename, "/+")) { char *copy = strdup(filename + 1); char *markup = copy; if (strstr(markup, "/+")) markup = strstr(markup, "/+") + 2; if (strrchr(markup, '.')) (*strrchr(markup, '.')) = '\0'; while (strchr(markup, '~')) (*strchr(markup, '~')) = '\n'; mlt_properties_set_string(properties, "resource", filename); mlt_properties_set_string(properties, "markup", markup); free(copy); } else if (strstr(filename, ".mpl")) { int i = 0; mlt_position out_point = 0; mlt_properties contents = mlt_properties_load(filename); mlt_animation key_frames = mlt_animation_new(); struct mlt_animation_item_s item; item.property = NULL; item.keyframe_type = mlt_keyframe_discrete; mlt_properties_set_string(properties, "resource", filename); mlt_properties_set_data(properties, "contents", contents, 0, (mlt_destructor) mlt_properties_close, NULL); mlt_properties_set_data(properties, "key_frames", key_frames, 0, (mlt_destructor) mlt_animation_close, NULL); // Make sure we have at least one entry if (mlt_properties_get(contents, "0") == NULL) mlt_properties_set_string(contents, "0", ""); for (i = 0; i < mlt_properties_count(contents); i++) { char *name = mlt_properties_get_name(contents, i); char *value = mlt_properties_get_value(contents, i); while (value != NULL && strchr(value, '~')) (*strchr(value, '~')) = '\n'; item.frame = atoi(name); mlt_animation_insert(key_frames, &item); out_point = MAX(out_point, item.frame); } mlt_animation_interpolate(key_frames); mlt_properties_set_position(properties, "length", out_point + 1); mlt_properties_set_position(properties, "out", out_point); } else { mlt_properties_set_string(properties, "resource", filename); FILE *f = mlt_fopen(filename, "r"); if (f != NULL) { char line[81]; char *markup = NULL; size_t size = 0; line[80] = '\0'; while (fgets(line, 80, f)) { size += strlen(line) + 1; if (markup) { markup = realloc(markup, size); if (markup) strcat(markup, line); } else { markup = strdup(line); } } fclose(f); if (markup && markup[strlen(markup) - 1] == '\n') markup[strlen(markup) - 1] = '\0'; if (markup) mlt_properties_set_string(properties, "markup", markup); else mlt_properties_set_string(properties, "markup", ""); free(markup); } else { producer->close = NULL; mlt_producer_close(producer); producer = NULL; free(self); } } return producer; } free(self); return NULL; } static void set_string(char **string, const char *value, const char *fallback) { if (value != NULL) { free(*string); *string = strdup(value); } else if (*string == NULL && fallback != NULL) { *string = strdup(fallback); } else if (*string != NULL && fallback == NULL) { free(*string); *string = NULL; } } rgba_color parse_color(char *color, unsigned int color_int) { rgba_color result = {0xff, 0xff, 0xff, 0xff}; if (!strcmp(color, "red")) { result.r = 0xff; result.g = 0x00; result.b = 0x00; } else if (!strcmp(color, "green")) { result.r = 0x00; result.g = 0xff; result.b = 0x00; } else if (!strcmp(color, "blue")) { result.r = 0x00; result.g = 0x00; result.b = 0xff; } else if (strcmp(color, "white")) { result.r = (color_int >> 24) & 0xff; result.g = (color_int >> 16) & 0xff; result.b = (color_int >> 8) & 0xff; result.a = (color_int) &0xff; } return result; } /** Convert a string property to UTF-8 */ static int iconv_utf8(mlt_properties properties, const char *prop_name, const char *encoding) { char *text = mlt_properties_get(properties, prop_name); int result = -1; iconv_t cd = iconv_open("UTF-8", encoding); if (text && (cd != (iconv_t) -1)) { char *inbuf_p = text; size_t inbuf_n = strlen(text); size_t outbuf_n = inbuf_n * 6; char *outbuf = mlt_pool_alloc(outbuf_n); char *outbuf_p = outbuf; memset(outbuf, 0, outbuf_n); if (text != NULL && strcmp(text, "") && iconv(cd, &inbuf_p, &inbuf_n, &outbuf_p, &outbuf_n) != -1) mlt_properties_set_string(properties, prop_name, outbuf); else mlt_properties_set_string(properties, prop_name, ""); mlt_pool_release(outbuf); result = 0; } iconv_close(cd); return result; } static void refresh_image(producer_pango self, mlt_frame frame, int width, int height) { // Pixbuf GdkPixbuf *pixbuf = mlt_properties_get_data(MLT_FRAME_PROPERTIES(frame), "pixbuf", NULL); // Obtain properties of frame mlt_properties properties = MLT_FRAME_PROPERTIES(frame); // Obtain the producer mlt_producer producer = &self->parent; // Obtain the producer properties mlt_properties producer_props = MLT_PRODUCER_PROPERTIES(producer); // Get producer properties char *fg = mlt_properties_get(producer_props, "fgcolour"); char *bg = mlt_properties_get(producer_props, "bgcolour"); char *ol = mlt_properties_get(producer_props, "olcolour"); int align = parse_alignment(mlt_properties_get(producer_props, "align")); int pad = mlt_properties_get_int(producer_props, "pad"); int outline = mlt_properties_get_int(producer_props, "outline"); char *markup = mlt_properties_get(producer_props, "markup"); char *text = mlt_properties_get(producer_props, "text"); char *font = mlt_properties_get(producer_props, "font"); char *family = mlt_properties_get(producer_props, "family"); int style = parse_style(mlt_properties_get(producer_props, "style")); char *encoding = mlt_properties_get(producer_props, "encoding"); int weight = mlt_properties_get_int(producer_props, "weight"); int stretch = mlt_properties_get_int(producer_props, "stretch"); int rotate = mlt_properties_get_int(producer_props, "rotate"); int size = mlt_properties_get_int(producer_props, "size"); int width_crop = mlt_properties_get_int(producer_props, "width_crop"); int width_fit = mlt_properties_get_int(producer_props, "width_fit"); int wrap_type = mlt_properties_get_int(producer_props, "wrap_type"); int wrap_width = mlt_properties_get_int(producer_props, "wrap_width"); int line_spacing = mlt_properties_get_int(properties, "line_spacing"); double aspect_ratio = mlt_properties_get_double(properties, "aspect_ratio"); int property_changed = 0; if (pixbuf == NULL) { // Check for file support mlt_properties contents = mlt_properties_get_data(producer_props, "contents", NULL); mlt_animation key_frames = mlt_properties_get_data(producer_props, "key_frames", NULL); if (contents != NULL) { char temp[20]; struct mlt_animation_item_s item; item.property = NULL; mlt_animation_prev_key(key_frames, &item, mlt_frame_original_position(frame)); sprintf(temp, "%d", item.frame); markup = mlt_properties_get(contents, temp); } // See if any properties changed property_changed = (align != self->align); property_changed = property_changed || (self->fgcolor == NULL || (fg && strcmp(fg, self->fgcolor))); property_changed = property_changed || (self->bgcolor == NULL || (bg && strcmp(bg, self->bgcolor))); property_changed = property_changed || (self->olcolor == NULL || (ol && strcmp(ol, self->olcolor))); property_changed = property_changed || (pad != self->pad); property_changed = property_changed || (outline != self->outline); property_changed = property_changed || (markup && self->markup && strcmp(markup, self->markup)); property_changed = property_changed || (text && self->text && strcmp(text, self->text)); property_changed = property_changed || (font && self->font && strcmp(font, self->font)); property_changed = property_changed || (family && self->family && strcmp(family, self->family)); property_changed = property_changed || (weight != self->weight); property_changed = property_changed || (stretch != self->stretch); property_changed = property_changed || (rotate != self->rotate); property_changed = property_changed || (style != self->style); property_changed = property_changed || (size != self->size); property_changed = property_changed || (width_crop != self->width_crop); property_changed = property_changed || (width_fit != self->width_fit); property_changed = property_changed || (wrap_type != self->wrap_type); property_changed = property_changed || (wrap_width != self->wrap_width); property_changed = property_changed || (line_spacing != self->line_spacing); property_changed = property_changed || (aspect_ratio != self->aspect_ratio); // Save the properties for next comparison self->align = align; self->pad = pad; self->outline = outline; set_string(&self->fgcolor, fg, "0xffffffff"); set_string(&self->bgcolor, bg, "0x00000000"); set_string(&self->olcolor, ol, "0x00000000"); set_string(&self->markup, markup, NULL); set_string(&self->text, text, NULL); set_string(&self->font, font, NULL); set_string(&self->family, family, "Sans"); self->weight = weight; self->stretch = stretch; self->rotate = rotate; self->style = style; self->size = size; self->width_crop = width_crop; self->width_fit = width_fit; self->wrap_type = wrap_type; self->wrap_width = wrap_width; self->line_spacing = line_spacing; self->aspect_ratio = aspect_ratio; } if (pixbuf == NULL && property_changed) { rgba_color fgcolor = parse_color(self->fgcolor, mlt_properties_get_int(producer_props, "fgcolour")); rgba_color bgcolor = parse_color(self->bgcolor, mlt_properties_get_int(producer_props, "bgcolour")); rgba_color olcolor = parse_color(self->olcolor, mlt_properties_get_int(producer_props, "olcolour")); if (self->pixbuf) g_object_unref(self->pixbuf); self->pixbuf = NULL; clean_cached(self); // Convert from specified encoding to UTF-8 if (encoding != NULL && !strncaseeq(encoding, "utf-8", 5) && !strncaseeq(encoding, "utf8", 4)) { if (markup != NULL && iconv_utf8(producer_props, "markup", encoding) != -1) { markup = mlt_properties_get(producer_props, "markup"); set_string(&self->markup, markup, NULL); } if (text != NULL && iconv_utf8(producer_props, "text", encoding) != -1) { text = mlt_properties_get(producer_props, "text"); set_string(&self->text, text, NULL); } } // Render the title pixbuf = pango_get_pixbuf(markup, text, font, fgcolor, bgcolor, olcolor, pad, align, family, style, weight, stretch, size, outline, rotate, width_crop, width_fit, wrap_type, wrap_width, line_spacing, aspect_ratio); if (pixbuf != NULL) { // Register self pixbuf for destruction and reuse mlt_properties_set_data(producer_props, "pixbuf", pixbuf, 0, (mlt_destructor) g_object_unref, NULL); g_object_ref(pixbuf); mlt_properties_set_data(MLT_FRAME_PROPERTIES(frame), "pixbuf", pixbuf, 0, (mlt_destructor) g_object_unref, NULL); mlt_properties_set_int(producer_props, "meta.media.width", gdk_pixbuf_get_width(pixbuf)); mlt_properties_set_int(producer_props, "meta.media.height", gdk_pixbuf_get_height(pixbuf)); // Store the width/height of the pixbuf temporarily self->width = gdk_pixbuf_get_width(pixbuf); self->height = gdk_pixbuf_get_height(pixbuf); } } else if (pixbuf == NULL && width > 0 && (self->pixbuf == NULL || width != self->width || height != self->height)) { if (self->pixbuf) g_object_unref(self->pixbuf); self->pixbuf = NULL; clean_cached(self); pixbuf = mlt_properties_get_data(producer_props, "pixbuf", NULL); } // If we have a pixbuf and a valid width if (pixbuf && width > 0) { char *interps = mlt_properties_get(properties, "consumer.rescale"); int interp = GDK_INTERP_BILINEAR; if (strcmp(interps, "nearest") == 0) interp = GDK_INTERP_NEAREST; else if (strcmp(interps, "tiles") == 0) interp = GDK_INTERP_TILES; else if (strcmp(interps, "hyper") == 0 || strcmp(interps, "bicubic") == 0) interp = GDK_INTERP_HYPER; // fprintf(stderr,"%s: scaling from %dx%d to %dx%d\n", __FILE__, self->width, self->height, width, height); // Note - the original pixbuf is already safe and ready for destruction self->pixbuf = gdk_pixbuf_scale_simple(pixbuf, width, height, interp); clean_cached(self); // Store width and height self->width = width; self->height = height; int has_alpha = gdk_pixbuf_get_has_alpha(self->pixbuf); mlt_properties_set_int(properties, "format", has_alpha ? mlt_image_rgba : mlt_image_rgb); } // Set width/height mlt_properties_set_int(properties, "width", self->width); mlt_properties_set_int(properties, "height", self->height); } static int producer_get_image(mlt_frame frame, uint8_t **buffer, mlt_image_format *format, int *width, int *height, int writable) { int error = 0; producer_pango self = (producer_pango) mlt_frame_pop_service(frame); // Obtain properties of frame mlt_properties properties = MLT_FRAME_PROPERTIES(frame); *width = mlt_properties_get_int(properties, "rescale_width"); *height = mlt_properties_get_int(properties, "rescale_height"); mlt_service_lock(MLT_PRODUCER_SERVICE(&self->parent)); // Refresh the image pthread_mutex_lock(&pango_mutex); refresh_image(self, frame, *width, *height); // Get width and height *width = self->width; *height = self->height; // Always clone here to allow 'animated' text if (self->pixbuf) { int size, bpp; uint8_t *buf; mlt_cache_item cached_item = mlt_service_cache_get(MLT_PRODUCER_SERVICE(&self->parent), "pango.image"); struct pango_cached_image_s *cached = mlt_cache_item_data(cached_item, NULL); // destroy cached data if request is differ if (!cached || (cached && (cached->format != *format || cached->width != *width || cached->height != *height))) { mlt_cache_item_close(cached_item); cached_item = NULL; cached = NULL; clean_cached(self); } // create cached image if (!cached) { int dst_stride, src_stride; cached = mlt_pool_alloc(sizeof(struct pango_cached_image_s)); cached->width = self->width; cached->height = self->height; cached->format = gdk_pixbuf_get_has_alpha(self->pixbuf) ? mlt_image_rgba : mlt_image_rgb; cached->alpha = NULL; cached->image = NULL; src_stride = gdk_pixbuf_get_rowstride(self->pixbuf); dst_stride = self->width * (mlt_image_rgba == cached->format ? 4 : 3); size = mlt_image_format_size(cached->format, cached->width, cached->height, &bpp); buf = mlt_pool_alloc(size); uint8_t *buf_save = buf; if (src_stride != dst_stride) { int y = self->height; uint8_t *src = gdk_pixbuf_get_pixels(self->pixbuf); uint8_t *dst = buf; while (y--) { memcpy(dst, src, dst_stride); dst += dst_stride; src += src_stride; } } else { memcpy(buf, gdk_pixbuf_get_pixels(self->pixbuf), src_stride * self->height); } // convert image if (frame->convert_image && cached->format != *format) { frame->convert_image(frame, &buf, &cached->format, *format); *format = cached->format; if (buf != buf_save) mlt_pool_release(buf_save); } size = mlt_image_format_size(cached->format, cached->width, cached->height, &bpp); cached->image = mlt_pool_alloc(size); memcpy(cached->image, buf, size); if ((buf = mlt_frame_get_alpha(frame))) { size = cached->width * cached->height; cached->alpha = mlt_pool_alloc(size); memcpy(cached->alpha, buf, size); } } if (cached) { // clone image surface size = mlt_image_format_size(cached->format, cached->width, cached->height, &bpp); buf = mlt_pool_alloc(size); memcpy(buf, cached->image, size); // set image surface mlt_frame_set_image(frame, buf, size, mlt_pool_release); *buffer = buf; // set alpha if (cached->alpha) { size = cached->width * cached->height; buf = mlt_pool_alloc(size); memcpy(buf, cached->alpha, size); mlt_frame_set_alpha(frame, buf, size, mlt_pool_release); } } if (cached_item) mlt_cache_item_close(cached_item); else mlt_service_cache_put(MLT_PRODUCER_SERVICE(&self->parent), "pango.image", cached, sizeof(struct pango_cached_image_s), pango_cached_image_destroy); } else { error = 1; } pthread_mutex_unlock(&pango_mutex); mlt_service_unlock(MLT_PRODUCER_SERVICE(&self->parent)); return error; } static int producer_get_frame(mlt_producer producer, mlt_frame_ptr frame, int index) { producer_pango self = producer->child; // Fetch the producers properties mlt_properties producer_properties = MLT_PRODUCER_PROPERTIES(producer); // Generate a frame *frame = mlt_frame_init(MLT_PRODUCER_SERVICE(producer)); // Obtain properties of frame and producer mlt_properties properties = MLT_FRAME_PROPERTIES(*frame); // Update timecode on the frame we're creating mlt_frame_set_position(*frame, mlt_producer_position(producer)); // Set producer-specific frame properties mlt_properties_set_int(properties, "progressive", 1); double force_ratio = mlt_properties_get_double(producer_properties, "force_aspect_ratio"); if (force_ratio > 0.0) mlt_properties_set_double(properties, "aspect_ratio", force_ratio); else { mlt_profile profile = mlt_service_profile(MLT_PRODUCER_SERVICE(producer)); mlt_properties_set_double(properties, "aspect_ratio", mlt_profile_sar(profile)); } // Refresh the pango image pthread_mutex_lock(&pango_mutex); refresh_image(self, *frame, 0, 0); pthread_mutex_unlock(&pango_mutex); // Stack the get image callback mlt_frame_push_service(*frame, self); mlt_frame_push_get_image(*frame, producer_get_image); // Calculate the next timecode mlt_producer_prepare_next(producer); return 0; } static void producer_close(mlt_producer parent) { producer_pango self = parent->child; if (self->pixbuf) g_object_unref(self->pixbuf); mlt_service_cache_purge(MLT_PRODUCER_SERVICE(parent)); free(self->fgcolor); free(self->bgcolor); free(self->olcolor); free(self->markup); free(self->text); free(self->font); free(self->family); parent->close = NULL; mlt_producer_close(parent); free(self); } static void pango_draw_background(GdkPixbuf *pixbuf, rgba_color bg) { int ww = gdk_pixbuf_get_width(pixbuf); int hh = gdk_pixbuf_get_height(pixbuf); uint8_t *p = gdk_pixbuf_get_pixels(pixbuf); int i, j; for (j = 0; j < hh; j++) { for (i = 0; i < ww; i++) { *p++ = bg.r; *p++ = bg.g; *p++ = bg.b; *p++ = bg.a; } } } static GdkPixbuf *pango_get_pixbuf(const char *markup, const char *text, const char *font, rgba_color fg, rgba_color bg, rgba_color ol, int pad, int align, char *family, int style, int weight, int stretch, int size, int outline, int rotate, int width_crop, int width_fit, int wrap_type, int wrap_width, int line_spacing, double aspect_ratio) { PangoContext *context = pango_ft2_font_map_create_context(fontmap); PangoLayout *layout = pango_layout_new(context); int w, h; int x = 0, y = 0; GdkPixbuf *pixbuf = NULL; FT_Bitmap bitmap; PangoFontDescription *desc = NULL; desc = pango_font_description_from_string(family); pango_font_description_set_size(desc, PANGO_SCALE * size); pango_font_description_set_style(desc, (PangoStyle) style); pango_font_description_set_weight(desc, (PangoWeight) weight); if (stretch) pango_font_description_set_stretch(desc, (PangoStretch) (stretch - 1)); // set line_spacing if (line_spacing) pango_layout_set_spacing(layout, PANGO_SCALE * line_spacing); // set wrapping constraints if (wrap_width <= 0) pango_layout_set_width(layout, -1); else { pango_layout_set_width(layout, PANGO_SCALE * wrap_width); pango_layout_set_wrap(layout, (PangoWrapMode) wrap_type); } pango_layout_set_font_description(layout, desc); pango_layout_set_alignment(layout, (PangoAlignment) align); if (markup != NULL && strcmp(markup, "") != 0) { pango_layout_set_markup(layout, markup, strlen(markup)); } else if (text != NULL && strcmp(text, "") != 0) { pango_layout_set_text(layout, text, strlen(text)); } else { // Pango doesn't like empty strings pango_layout_set_text(layout, " ", 2); } if (rotate) { double n_x, n_y; PangoRectangle rect; PangoMatrix m_layout = PANGO_MATRIX_INIT, m_offset = PANGO_MATRIX_INIT; pango_matrix_rotate(&m_layout, rotate); pango_matrix_rotate(&m_offset, -rotate); pango_context_set_base_gravity(context, PANGO_GRAVITY_AUTO); pango_context_set_matrix(context, &m_layout); pango_layout_context_changed(layout); pango_layout_get_extents(layout, NULL, &rect); pango_matrix_transform_rectangle(pango_context_get_matrix(context), &rect); n_x = -rect.x; n_y = -rect.y; pango_matrix_transform_point(&m_offset, &n_x, &n_y); rect.x = n_x; rect.y = n_y; pango_extents_to_pixels(&rect, NULL); w = rect.width; h = rect.height; x = rect.x; y = rect.y; } else pango_layout_get_pixel_size(layout, &w, &h); // respect aspect ratio if (0.0 < aspect_ratio && aspect_ratio != 1.0) { double n_x, n_y; PangoRectangle rect; PangoMatrix m_layout = PANGO_MATRIX_INIT, m_offset = PANGO_MATRIX_INIT; #if 1 pango_matrix_scale(&m_layout, 1.0 / aspect_ratio, 1.0); pango_matrix_scale(&m_offset, 1.0 / aspect_ratio, 1.0); #else pango_matrix_scale(&m_layout, 1.0, aspect_ratio); pango_matrix_scale(&m_offset, 1.0, aspect_ratio); #endif pango_context_set_base_gravity(context, PANGO_GRAVITY_AUTO); pango_context_set_matrix(context, &m_layout); pango_layout_context_changed(layout); pango_layout_get_extents(layout, NULL, &rect); pango_matrix_transform_rectangle(pango_context_get_matrix(context), &rect); n_x = -rect.x; n_y = -rect.y; pango_matrix_transform_point(&m_offset, &n_x, &n_y); rect.x = n_x; rect.y = n_y; pango_extents_to_pixels(&rect, NULL); w = rect.width; h = rect.height; x = rect.x; y = rect.y; } // limit width if (width_crop && w > width_crop) w = width_crop; else if (width_fit && w > width_fit) { double n_x, n_y; PangoRectangle rect; PangoMatrix m_layout = PANGO_MATRIX_INIT, m_offset = PANGO_MATRIX_INIT; pango_matrix_scale(&m_layout, width_fit / (double) w, 1.0); pango_matrix_scale(&m_offset, width_fit / (double) w, 1.0); pango_context_set_base_gravity(context, PANGO_GRAVITY_AUTO); pango_context_set_matrix(context, &m_layout); pango_layout_context_changed(layout); pango_layout_get_extents(layout, NULL, &rect); pango_matrix_transform_rectangle(pango_context_get_matrix(context), &rect); n_x = -rect.x; n_y = -rect.y; pango_matrix_transform_point(&m_offset, &n_x, &n_y); rect.x = n_x; rect.y = n_y; pango_extents_to_pixels(&rect, NULL); w = rect.width; h = rect.height; x = rect.x; y = rect.y; } if (pad == 0) pad = 1; pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE /* has alpha */, 8, w + 2 * pad, h + 2 * pad); pango_draw_background(pixbuf, bg); bitmap.width = w; bitmap.pitch = 32 * ((w + 31) / 31); bitmap.rows = h; bitmap.buffer = mlt_pool_alloc(h * bitmap.pitch); bitmap.num_grays = 256; bitmap.pixel_mode = ft_pixel_mode_grays; memset(bitmap.buffer, 0, h * bitmap.pitch); pango_ft2_render_layout(&bitmap, layout, x, y); if (outline) { fill_pixbuf_with_outline(pixbuf, &bitmap, w, h, pad, align, fg, bg, ol, outline); } else { fill_pixbuf(pixbuf, &bitmap, w, h, pad, align, fg, bg); } mlt_pool_release(bitmap.buffer); pango_font_description_free(desc); g_object_unref(layout); g_object_unref(context); return pixbuf; } static void fill_pixbuf(GdkPixbuf *pixbuf, FT_Bitmap *bitmap, int w, int h, int pad, int align, rgba_color fg, rgba_color bg) { int stride = gdk_pixbuf_get_rowstride(pixbuf); uint8_t *src = bitmap->buffer; int x = (gdk_pixbuf_get_width(pixbuf) - w - 2 * pad) * align / 2 + pad; uint8_t *dest = gdk_pixbuf_get_pixels(pixbuf) + 4 * x + pad * stride; int j = h; int i = 0; uint8_t *d, *s, a; while (j--) { d = dest; s = src; i = w; while (i--) { a = *s++; *d++ = (a * fg.r + (255 - a) * bg.r) >> 8; *d++ = (a * fg.g + (255 - a) * bg.g) >> 8; *d++ = (a * fg.b + (255 - a) * bg.b) >> 8; *d++ = (a * fg.a + (255 - a) * bg.a) >> 8; } dest += stride; src += bitmap->pitch; } } static void fill_pixbuf_with_outline(GdkPixbuf *pixbuf, FT_Bitmap *bitmap, int w, int h, int pad, int align, rgba_color fg, rgba_color bg, rgba_color ol, int outline) { int stride = gdk_pixbuf_get_rowstride(pixbuf); int x = (gdk_pixbuf_get_width(pixbuf) - w - 2 * pad) * align / 2 + pad; uint8_t *dest = gdk_pixbuf_get_pixels(pixbuf) + 4 * x + pad * stride; int j, i; uint8_t *d = NULL; float a_ol = 0; float a_fg = 0; for (j = 0; j < h; j++) { d = dest; for (i = 0; i < w; i++) { #define geta(x, y) (float) bitmap->buffer[(y) *bitmap->pitch + (x)] / 255.0 a_ol = geta(i, j); // One pixel fake circle if (i > 0) a_ol = MAX(a_ol, geta(i - 1, j)); if (i < w - 1) a_ol = MAX(a_ol, geta(i + 1, j)); if (j > 0) a_ol = MAX(a_ol, geta(i, j - 1)); if (j < h - 1) a_ol = MAX(a_ol, geta(i, j + 1)); if (outline >= 2) { // Two pixels fake circle if (i > 1) { a_ol = MAX(a_ol, geta(i - 2, j)); if (j > 0) a_ol = MAX(a_ol, geta(i - 2, j - 1)); if (j < h - 1) a_ol = MAX(a_ol, geta(i - 2, j + 1)); } if (i > 0) { if (j > 0) a_ol = MAX(a_ol, geta(i - 1, j - 1)); if (j > 1) a_ol = MAX(a_ol, geta(i - 1, j - 2)); if (j < h - 1) a_ol = MAX(a_ol, geta(i - 1, j + 1)); if (j < h - 2) a_ol = MAX(a_ol, geta(i - 1, j + 2)); } if (j > 1) a_ol = MAX(a_ol, geta(i, j - 2)); if (j < h - 2) a_ol = MAX(a_ol, geta(i, j + 2)); if (i < w - 1) { if (j > 0) a_ol = MAX(a_ol, geta(i + 1, j - 1)); if (j > 1) a_ol = MAX(a_ol, geta(i + 1, j - 2)); if (j < h - 1) a_ol = MAX(a_ol, geta(i + 1, j + 1)); if (j < h - 2) a_ol = MAX(a_ol, geta(i + 1, j + 2)); } if (i < w - 2) { a_ol = MAX(a_ol, geta(i + 2, j)); if (j > 0) a_ol = MAX(a_ol, geta(i + 2, j - 1)); if (j < h - 1) a_ol = MAX(a_ol, geta(i + 2, j + 1)); } } if (outline >= 3) { // Three pixels fake circle if (i > 2) { a_ol = MAX(a_ol, geta(i - 3, j)); if (j > 0) a_ol = MAX(a_ol, geta(i - 3, j - 1)); if (j < h - 1) a_ol = MAX(a_ol, geta(i - 3, j + 1)); } if (i > 1) { if (j > 1) a_ol = MAX(a_ol, geta(i - 2, j - 2)); if (j < h - 2) a_ol = MAX(a_ol, geta(i - 2, j + 2)); } if (i > 0) { if (j > 2) a_ol = MAX(a_ol, geta(i - 1, j - 3)); if (j < h - 3) a_ol = MAX(a_ol, geta(i - 1, j + 3)); } if (j > 2) a_ol = MAX(a_ol, geta(i, j - 3)); if (j < h - 3) a_ol = MAX(a_ol, geta(i, j + 3)); if (i < w - 1) { if (j > 2) a_ol = MAX(a_ol, geta(i + 1, j - 3)); if (j < h - 3) a_ol = MAX(a_ol, geta(i + 1, j + 3)); } if (i < w - 2) { if (j > 1) a_ol = MAX(a_ol, geta(i + 2, j - 2)); if (j < h - 2) a_ol = MAX(a_ol, geta(i + 2, j + 2)); } if (i < w - 3) { a_ol = MAX(a_ol, geta(i + 3, j)); if (j > 0) a_ol = MAX(a_ol, geta(i + 3, j - 1)); if (j < h - 1) a_ol = MAX(a_ol, geta(i + 3, j + 1)); } } a_fg = (float) bitmap->buffer[j * bitmap->pitch + i] / 255.0; *d++ = (int) (a_fg * fg.r + (1 - a_fg) * (a_ol * ol.r + (1 - a_ol) * bg.r)); *d++ = (int) (a_fg * fg.g + (1 - a_fg) * (a_ol * ol.g + (1 - a_ol) * bg.g)); *d++ = (int) (a_fg * fg.b + (1 - a_fg) * (a_ol * ol.b + (1 - a_ol) * bg.b)); *d++ = (int) (a_fg * fg.a + (1 - a_fg) * (a_ol * ol.a + (1 - a_ol) * bg.a)); } dest += stride; } } static void on_fontmap_reload() { PangoFT2FontMap *new_fontmap = NULL, *old_fontmap = NULL; FcInitReinitialize(); new_fontmap = (PangoFT2FontMap *) pango_ft2_font_map_new(); pthread_mutex_lock(&pango_mutex); old_fontmap = fontmap; fontmap = new_fontmap; pthread_mutex_unlock(&pango_mutex); if (old_fontmap) g_object_unref(old_fontmap); } mlt-7.22.0/src/modules/gdk/producer_pango.yml000664 000000 000000 00000020645 14531534050 021123 0ustar00rootroot000000 000000 schema_version: 0.3 type: producer identifier: pango title: Pango version: 2 copyright: Meltytech, LLC creator: Dan Dennedy license: LGPLv2.1 language: en tags: - Video description: > A title generator that uses the Pango international text layout and Freetype2 font renderer. notes: > Supplying a filename with extension ".txt" causes the loader producer to load with pango. If the filename begins with "+" the pango producer interprets the filename as pango text. This is a shortcut to embed titles in melt commands. For MLT XML, it is recommended that you embed the title text in the property value. Pango has builtin scaling. It will rescale the originally rendered title to whatever the consumer requests. Therefore, it will lose its aspect ratio if so requested, and it is up to the consumer to request a proper width and height that maintains the image aspect. Environment variable MLT_PANGO_PRODUCER_CACHE could be used to override and increase the size of cached converted images of simultaneous use. Fontset used by pango producer loaded once. That behavior prevents using new fonts till process used pango producer been restarted. To force fontmap reload you need to send signal "fontmap-reload" to pango producer: { mlt_profile profile = mlt_profile_init("dv_pal"); mlt_producer producer = mlt_factory_producer(profile, "pango", NULL); mlt_events_fire(mlt_producer_properties(producer), "fontmap-reload", NULL ); mlt_producer_close(producer); mlt_profile_close(profile); }; parameters: - identifier: resource title: File type: string description: | A text file containing Pango markup, see: https://developer.gnome.org/pango/stable/PangoMarkupFormat.html requires xml-like encoding special chars from: <, >, & -to- <, >, & readonly: no argument: yes mutable: no widget: fileopen - identifier: markup title: Markup type: string description: | A string containing Pango markup see: http://developer.gnome.org/doc/API/2.0/pango/PangoMarkupFormat.html requires xml-like encoding special chars from: <, >, & -to- <, >, & readonly: no argument: yes mutable: yes widget: textbox - identifier: fgcolour title: Foreground color type: string description: > A color value is a hexadecimal representation of RGB plus alpha channel as 0xrrggbbaa. Colors can also be the words: white, black, red, green, or blue. You can also use a HTML-style color values #rrggbb or #aarrggbb. readonly: no mutable: yes widget: color - identifier: bgcolour title: Background color type: string description: > A color value is a hexadecimal representation of RGB plus alpha channel as 0xrrggbbaa. Colors can also be the words: white, black, red, green, or blue. You can also use a HTML-style color values #rrggbb or #aarrggbb. readonly: no mutable: yes widget: color - identifier: olcolour title: Outline color type: string description: > A color value is a hexadecimal representation of RGB plus alpha channel as 0xrrggbbaa. Colors can also be the words: white, black, red, green, or blue. You can also use a HTML-style color values #rrggbb or #aarrggbb. readonly: no mutable: yes widget: color - identifier: outline title: Outline Width type: string description: > The width of the outline in pixels. readonly: no default: 0 minimum: 0 maximum: 3 mutable: yes widget: spinner unit: pixels - identifier: align title: Paragraph alignment type: string description: > left, centre, right (also, numbers 0, 1 and 2 can be used respectively) readonly: no default: left mutable: yes widget: combo - identifier: pad title: Padding type: integer description: > The number of pixels to pad the background rectangle beyond edges of text. readonly: no default: 0 mutable: yes widget: spinner unit: pixels - identifier: text title: Text type: string description: | A non-markup string in UTF-8 encoding (can contain markup chars un-encoded) readonly: no mutable: yes widget: textbox - identifier: family title: Font family type: string description: > The default typeface to use when not using markup. default: Sans readonly: no mutable: yes widget: combo - identifier: size title: Font size type: integer description: > The size in pixels of the font to use when not using markup. default: 48 readonly: no mutable: yes widget: spinner unit: pixels - identifier: style title: Font style type: string description: > The style of the font to use when not using markup. values: - normal - italic - oblique default: normal readonly: no mutable: yes widget: combo - identifier: weight title: Font weight type: integer description: The weight of the font. minimum: 100 maximum: 1000 default: 400 readonly: no mutable: yes widget: spinner - identifier: encoding title: Encoding type: string description: > The text encoding type of the input if not UTF-8. See 'iconv --list' for a list of possible inputs. default: UTF-8 readonly: no mutable: yes widget: combo - identifier: real_width title: Real width type: integer description: The original, unscaled width of the rendered title. readonly: yes unit: pixels - identifier: real_height title: Real height type: integer description: The original, unscaled height of the rendered title. readonly: yes unit: pixels - identifier: width title: Width type: integer description: The last requested scaled image width. readonly: yes unit: pixels - identifier: height title: Height type: integer description: The last requested scaled image height. readonly: yes unit: pixels - identifier: force_aspect_ratio title: Sample aspect ratio type: float description: Optionally override a (mis)detected aspect ratio mutable: yes - identifier: rotate title: Rotation angle type: integer description: > The angle of text rotation in degrees. Positive value is clockwise. default: 0 readonly: no mutable: yes widget: spinner unit: degrees - identifier: width_crop title: Width to crop type: integer description: > Limit width of rendered image. default: 0 readonly: no mutable: yes widget: spinner unit: pixels - identifier: width_fit title: Fit width type: integer description: > Scale pango layout to fit specified width. default: 0 readonly: no mutable: yes widget: spinner unit: pixels - identifier: line_spacing title: Sets lines spacing type: integer description: > Sets the amount of spacing between the lines of the layout. default: 0 readonly: no mutable: yes widget: spinner - identifier: stretch title: Font stretch type: integer description: > The stretch feature of pango's font description. Possible values: 1 - ULTRA_CONDENSED 2 - EXTRA_CONDENSED 3 - CONDENSED 4 - SEMI_CONDENSED 5 - NORMAL 6 - SEMI_EXPANDED 7 - EXPANDED 8 - EXTRA_EXPANDED 9 - ULTRA_EXPANDED minimum: 0 maximum: 9 default: 4 readonly: no mutable: yes widget: spinner - identifier: wrap_width title: Sets the width to wrap to type: integer description: > Sets the width to which the lines of the PangoLayout should wrap. default: 0 readonly: no mutable: yes widget: spinner unit: pixels - identifier: wrap_type title: Sets the wrap mode type: integer description: > Sets the wrap mode; the wrap mode only has effect if a 'wrap_width' is set. Possible values: 0 - wrap lines at word boundaries 1 - wrap lines at character boundaries 2 - wrap lines at word boundaries, but fall back to character boundaries if there is not enough space for a full word default: 0 readonly: no mutable: yes widget: spinner mlt-7.22.0/src/modules/gdk/producer_pixbuf.c000664 000000 000000 00000073540 14531534050 020737 0ustar00rootroot000000 000000 /* * producer_pixbuf.c -- raster image loader based upon gdk-pixbuf * Copyright (C) 2003-2021 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #ifdef USE_EXIF #include #endif #include #include #include #include #include #include #include #include #include #include // this protects concurrent access to gdk_pixbuf static pthread_mutex_t g_mutex = PTHREAD_MUTEX_INITIALIZER; typedef struct producer_pixbuf_s *producer_pixbuf; struct producer_pixbuf_s { struct mlt_producer_s parent; // File name list mlt_properties filenames; mlt_position *outs; int count; int image_idx; int pixbuf_idx; int width; int height; uint8_t *alpha; uint8_t *image; mlt_cache_item image_cache; mlt_cache_item alpha_cache; mlt_cache_item pixbuf_cache; GdkPixbuf *pixbuf; mlt_image_format format; }; static void load_filenames(producer_pixbuf self, mlt_properties producer_properties); static int refresh_pixbuf(producer_pixbuf self, mlt_frame frame); static int producer_get_frame(mlt_producer parent, mlt_frame_ptr frame, int index); static void producer_close(mlt_producer parent); static void refresh_length(mlt_properties properties, producer_pixbuf self) { if (self->count > mlt_properties_get_int(properties, "length") || mlt_properties_get_int(properties, "autolength")) { int ttl = mlt_properties_get_int(properties, "ttl"); mlt_position length = self->count * ttl; mlt_properties_set_position(properties, "length", length); mlt_properties_set_position(properties, "out", length - 1); } } static void on_property_changed(mlt_service owner, mlt_producer producer, mlt_event_data event_data) { const char *name = mlt_event_data_to_string(event_data); if (name && !strcmp(name, "ttl")) refresh_length(MLT_PRODUCER_PROPERTIES(producer), producer->child); } mlt_producer producer_pixbuf_init(char *filename) { producer_pixbuf self = calloc(1, sizeof(struct producer_pixbuf_s)); if (self != NULL && mlt_producer_init(&self->parent, self) == 0) { mlt_producer producer = &self->parent; // Get the properties interface mlt_properties properties = MLT_PRODUCER_PROPERTIES(&self->parent); // Reject if animation. GError *error = NULL; pthread_mutex_lock(&g_mutex); GdkPixbufAnimation *anim = gdk_pixbuf_animation_new_from_file(filename, &error); if (anim) { gboolean is_anim = !gdk_pixbuf_animation_is_static_image(anim); g_object_unref(anim); if (is_anim) { pthread_mutex_unlock(&g_mutex); mlt_producer_close(&self->parent); free(self); return NULL; } } pthread_mutex_unlock(&g_mutex); // Callback registration producer->get_frame = producer_get_frame; producer->close = (mlt_destructor) producer_close; // Set the default properties mlt_properties_set(properties, "resource", filename); mlt_properties_set_int(properties, "ttl", 25); mlt_properties_set_int(properties, "aspect_ratio", 1); mlt_properties_set_int(properties, "progressive", 1); mlt_properties_set_int(properties, "seekable", 1); mlt_properties_set_int(properties, "loop", 1); mlt_properties_set_int(properties, "meta.media.progressive", 1); // Validate the resource if (filename) load_filenames(self, properties); if (self->count) { mlt_frame frame = mlt_frame_init(MLT_PRODUCER_SERVICE(producer)); if (frame) { mlt_properties frame_properties = MLT_FRAME_PROPERTIES(frame); mlt_properties_set_data(frame_properties, "producer_pixbuf", self, 0, NULL, NULL); mlt_frame_set_position(frame, mlt_producer_position(producer)); refresh_pixbuf(self, frame); mlt_cache_item_close(self->pixbuf_cache); mlt_frame_close(frame); } } if (self->width == 0) { producer_close(producer); producer = NULL; } else { mlt_events_listen(properties, self, "property-changed", (mlt_listener) on_property_changed); } return producer; } free(self); return NULL; } static int load_svg(producer_pixbuf self, mlt_properties properties, const char *filename) { int result = 0; // Read xml string if (strstr(filename, " -1) { // Write the svg into the temp file ssize_t remaining_bytes; const char *xml = filename; // Strip leading crap while (xml[0] != '<') xml++; remaining_bytes = strlen(xml); while (remaining_bytes > 0) remaining_bytes -= write(fd, xml + strlen(xml) - remaining_bytes, remaining_bytes); close(fd); mlt_properties_set(self->filenames, "0", fullname); // Teehe - when the producer closes, delete the temp file and the space allo mlt_properties_set_data(properties, "__temporary_file__", fullname, 0, (mlt_destructor) unlink, NULL); result = 1; } } return result; } static int load_sequence_sprintf(producer_pixbuf self, mlt_properties properties, const char *filename) { int result = 0; // Obtain filenames with pattern if (strchr(filename, '%') != NULL) { // handle picture sequences int i = mlt_properties_get_int(properties, "begin"); int gap = 0; char full[1024]; int keyvalue = 0; char key[50]; while (gap < 100) { struct stat buf; snprintf(full, 1023, filename, i++); if (stat(full, &buf) == 0) { sprintf(key, "%d", keyvalue++); mlt_properties_set(self->filenames, key, full); gap = 0; } else { gap++; } } if (mlt_properties_count(self->filenames) > 0) { mlt_properties_set_int(properties, "ttl", 1); result = 1; } } return result; } static int load_sequence_deprecated(producer_pixbuf self, mlt_properties properties, const char *filename) { int result = 0; const char *start; // This approach is deprecated in favor of the begin querystring parameter. // Obtain filenames with pattern containing a begin value, e.g. foo%1234d.png if ((start = strchr(filename, '%'))) { const char *end = ++start; while (isdigit(*end)) end++; if (end > start && (end[0] == 'd' || end[0] == 'i' || end[0] == 'u')) { int n = end - start; char *s = calloc(1, n + 1); strncpy(s, start, n); mlt_properties_set(properties, "begin", s); free(s); s = calloc(1, strlen(filename) + 2); strncpy(s, filename, start - filename); sprintf(s + (start - filename), ".%d%s", n, end); result = load_sequence_sprintf(self, properties, s); free(s); } } return result; } static int load_sequence_querystring(producer_pixbuf self, mlt_properties properties, const char *filename) { int result = 0; // Obtain filenames with pattern and begin value in query string if (strchr(filename, '%') && strchr(filename, '?')) { // Split filename into pattern and query string char *s = strdup(filename); char *querystring = strrchr(s, '?'); *querystring++ = '\0'; if (strstr(filename, "begin=")) mlt_properties_set(properties, "begin", strstr(querystring, "begin=") + 6); else if (strstr(filename, "begin:")) mlt_properties_set(properties, "begin", strstr(querystring, "begin:") + 6); // Coerce to an int value so serialization does not have any extra query string cruft mlt_properties_set_int(properties, "begin", mlt_properties_get_int(properties, "begin")); result = load_sequence_sprintf(self, properties, s); free(s); } return result; } static int load_folder(producer_pixbuf self, mlt_properties properties, const char *filename) { int result = 0; // Obtain filenames with folder if (strstr(filename, "/.all.") != NULL) { char wildcard[1024]; char *dir_name = strdup(filename); char *extension = strrchr(dir_name, '.'); *(strstr(dir_name, "/.all.") + 1) = '\0'; sprintf(wildcard, "*%s", extension); mlt_properties_dir_list(self->filenames, dir_name, wildcard, 1); free(dir_name); result = 1; } return result; } static int load_sequence_csv(producer_pixbuf self, mlt_properties properties, const char *filename) { int result = 0; const char *csv_extension = strstr(filename, ".csv"); if (csv_extension != NULL && csv_extension[4] == '\0') { FILE *csv = fopen(filename, "r"); if (csv != NULL) { int keyvalue = 0; int out = 0; int nlines = 0; while (!feof(csv)) { char line[1024]; if (fgets(line, 1024, csv) != NULL) { nlines++; } } self->outs = (mlt_position *) malloc(nlines * sizeof(mlt_position)); fseek(csv, 0, SEEK_SET); int index = 0; while (!feof(csv)) { char line[1024]; if (fgets(line, 1024, csv) != NULL) { char *sep = strchr(line, ';'); if (sep != NULL) { int ttl = 0; int n = 0; char key[50]; struct stat buf; *sep++ = '\0'; n = sscanf(sep, "%d", &ttl); if (n == 0) { break; } if (stat(line, &buf) != 0) { break; } out += ttl; mlt_log_debug(MLT_PRODUCER_SERVICE(&self->parent), "file:'%s' ttl=%d out=%d\n", line, ttl, out); sprintf(key, "%d", keyvalue++); mlt_properties_set(self->filenames, key, line); self->outs[index++] = out; } } } fclose(csv); result = 1; } } return result; } static void load_filenames(producer_pixbuf self, mlt_properties properties) { char *filename = mlt_properties_get(properties, "resource"); self->filenames = mlt_properties_new(); self->outs = NULL; if (!load_svg(self, properties, filename) && !load_sequence_querystring(self, properties, filename) && !load_sequence_sprintf(self, properties, filename) && !load_sequence_deprecated(self, properties, filename) && !load_sequence_csv(self, properties, filename) && !load_folder(self, properties, filename)) { mlt_properties_set(self->filenames, "0", filename); } self->count = mlt_properties_count(self->filenames); refresh_length(properties, self); } static GdkPixbuf *reorient_with_exif(producer_pixbuf self, int image_idx, GdkPixbuf *pixbuf) { #ifdef USE_EXIF mlt_properties producer_props = MLT_PRODUCER_PROPERTIES(&self->parent); ExifData *d = exif_data_new_from_file(mlt_properties_get_value(self->filenames, image_idx)); ExifEntry *entry; int exif_orientation = 0; /* get orientation and rotate image accordingly if necessary */ if (d) { if ((entry = exif_content_get_entry(d->ifd[EXIF_IFD_0], EXIF_TAG_ORIENTATION))) exif_orientation = exif_get_short(entry->data, exif_data_get_byte_order(d)); /* Free the EXIF data */ exif_data_unref(d); } // Remember EXIF value, might be useful for someone mlt_properties_set_int(producer_props, "_exif_orientation", exif_orientation); if (exif_orientation > 1) { GdkPixbuf *processed = NULL; GdkPixbufRotation matrix = GDK_PIXBUF_ROTATE_NONE; // Rotate image according to exif data switch (exif_orientation) { case 2: processed = gdk_pixbuf_flip(pixbuf, TRUE); break; case 3: matrix = GDK_PIXBUF_ROTATE_UPSIDEDOWN; processed = pixbuf; break; case 4: processed = gdk_pixbuf_flip(pixbuf, FALSE); break; case 5: matrix = GDK_PIXBUF_ROTATE_COUNTERCLOCKWISE; processed = gdk_pixbuf_flip(pixbuf, TRUE); break; case 6: matrix = GDK_PIXBUF_ROTATE_CLOCKWISE; processed = pixbuf; break; case 7: matrix = GDK_PIXBUF_ROTATE_CLOCKWISE; processed = gdk_pixbuf_flip(pixbuf, TRUE); break; case 8: matrix = GDK_PIXBUF_ROTATE_COUNTERCLOCKWISE; processed = pixbuf; break; } if (processed) { pixbuf = gdk_pixbuf_rotate_simple(processed, matrix); g_object_unref(processed); } } #endif return pixbuf; } static int refresh_pixbuf(producer_pixbuf self, mlt_frame frame) { // Obtain properties of frame and producer mlt_properties properties = MLT_FRAME_PROPERTIES(frame); mlt_producer producer = &self->parent; mlt_properties producer_props = MLT_PRODUCER_PROPERTIES(producer); // Check if user wants us to reload the image if (mlt_properties_get_int(producer_props, "force_reload")) { self->pixbuf = NULL; self->image = NULL; mlt_properties_set_int(producer_props, "force_reload", 0); } // Get the original position of this frame mlt_position position = mlt_frame_original_position(frame); position += mlt_producer_get_in(producer); int loop = mlt_properties_get_int(producer_props, "loop"); // Image index int current_idx = 0; if (!self->outs) { // Get the time to live for each frame double ttl = mlt_properties_get_int(producer_props, "ttl"); if (loop) { current_idx = (int) floor((double) position / ttl) % self->count; } else { current_idx = MIN((double) position / ttl, self->count - 1); } } else { int total_ttl = (int) floor(self->outs[self->count - 1]); mlt_position looped_pos = loop ? (int) floor(position) % total_ttl : position; while (current_idx < self->count) { if (self->outs[current_idx] > looped_pos) { break; } current_idx++; } if (current_idx >= self->count) { current_idx = self->count - 1; } mlt_log_debug(MLT_PRODUCER_SERVICE(&self->parent), "position=%d current_idx=%d\n", position, current_idx); } int disable_exif = mlt_properties_get_int(producer_props, "disable_exif"); if (current_idx != self->pixbuf_idx) self->pixbuf = NULL; if (!self->pixbuf || mlt_properties_get_int(producer_props, "_disable_exif") != disable_exif) { GError *error = NULL; self->image = NULL; pthread_mutex_lock(&g_mutex); self->pixbuf = gdk_pixbuf_new_from_file(mlt_properties_get_value(self->filenames, current_idx), &error); if (self->pixbuf) { // Read the exif value for this file if (!disable_exif) self->pixbuf = reorient_with_exif(self, current_idx, self->pixbuf); // Register this pixbuf for destruction and reuse mlt_cache_item_close(self->pixbuf_cache); mlt_service_cache_put(MLT_PRODUCER_SERVICE(producer), "pixbuf.pixbuf", self->pixbuf, 0, (mlt_destructor) g_object_unref); self->pixbuf_cache = mlt_service_cache_get(MLT_PRODUCER_SERVICE(producer), "pixbuf.pixbuf"); self->pixbuf_idx = current_idx; // Store the width/height of the pixbuf temporarily self->width = gdk_pixbuf_get_width(self->pixbuf); self->height = gdk_pixbuf_get_height(self->pixbuf); mlt_events_block(producer_props, NULL); mlt_properties_set_int(producer_props, "meta.media.width", self->width); mlt_properties_set_int(producer_props, "meta.media.height", self->height); mlt_properties_set_int(producer_props, "_disable_exif", disable_exif); int has_alpha = gdk_pixbuf_get_has_alpha(self->pixbuf); mlt_properties_set_int(properties, "format", has_alpha ? mlt_image_rgba : mlt_image_rgb); mlt_events_unblock(producer_props, NULL); } pthread_mutex_unlock(&g_mutex); } // Set width/height of frame mlt_properties_set_int(properties, "width", self->width); mlt_properties_set_int(properties, "height", self->height); return current_idx; } static void refresh_image( producer_pixbuf self, mlt_frame frame, mlt_image_format format, int width, int height) { // Obtain properties of frame and producer mlt_properties properties = MLT_FRAME_PROPERTIES(frame); mlt_producer producer = &self->parent; // Get index and pixbuf int current_idx = refresh_pixbuf(self, frame); // optimization for subsequent iterations on single picture if (current_idx != self->image_idx || width != self->width || height != self->height) self->image = NULL; mlt_log_debug(MLT_PRODUCER_SERVICE(producer), "image %p pixbuf %p idx %d current_idx %d pixbuf_idx %d width %d\n", self->image, self->pixbuf, current_idx, self->image_idx, self->pixbuf_idx, width); // If we have a pixbuf and we need an image if (self->pixbuf && (!self->image || (format != mlt_image_none && format != mlt_image_movit && format != self->format))) { char *interps = mlt_properties_get(properties, "consumer.rescale"); if (interps) interps = strdup(interps); int interp = GDK_INTERP_BILINEAR; if (!interps) { // Keep bilinear by default } else if (strcmp(interps, "nearest") == 0) interp = GDK_INTERP_NEAREST; else if (strcmp(interps, "tiles") == 0) interp = GDK_INTERP_TILES; else if (strcmp(interps, "hyper") == 0 || strcmp(interps, "bicubic") == 0) interp = GDK_INTERP_HYPER; free(interps); // Note - the original pixbuf is already safe and ready for destruction pthread_mutex_lock(&g_mutex); GdkPixbuf *pixbuf = gdk_pixbuf_scale_simple(self->pixbuf, width, height, interp); // Store width and height self->width = width; self->height = height; // Allocate/define image int has_alpha = gdk_pixbuf_get_has_alpha(pixbuf); int src_stride = gdk_pixbuf_get_rowstride(pixbuf); int dst_stride = self->width * (has_alpha ? 4 : 3); self->format = has_alpha ? mlt_image_rgba : mlt_image_rgb; int image_size = mlt_image_format_size(self->format, width, height, NULL); self->image = mlt_pool_alloc(image_size); self->alpha = NULL; if (src_stride != dst_stride) { int y = self->height; uint8_t *src = gdk_pixbuf_get_pixels(pixbuf); uint8_t *dst = self->image; while (y--) { memcpy(dst, src, dst_stride); dst += dst_stride; src += src_stride; } } else { memcpy(self->image, gdk_pixbuf_get_pixels(pixbuf), src_stride * height); } pthread_mutex_unlock(&g_mutex); // Convert image to requested format if (format != mlt_image_none && format != mlt_image_movit && format != self->format && frame->convert_image) { // cache copies of the image and alpha buffers uint8_t *buffer = self->image; if (buffer) { mlt_frame_set_image(frame, self->image, image_size, mlt_pool_release); mlt_properties_set_int(properties, "width", self->width); mlt_properties_set_int(properties, "height", self->height); mlt_properties_set_int(properties, "format", self->format); if (!frame->convert_image(frame, &self->image, &self->format, format)) { buffer = self->image; image_size = mlt_image_format_size(self->format, self->width, self->height, NULL); self->image = mlt_pool_alloc(image_size); memcpy(self->image, buffer, mlt_image_format_size(self->format, self->width, self->height, NULL)); } } if ((buffer = mlt_frame_get_alpha(frame))) { self->alpha = mlt_pool_alloc(width * height); memcpy(self->alpha, buffer, width * height); } } // Update the cache mlt_cache_item_close(self->image_cache); mlt_service_cache_put(MLT_PRODUCER_SERVICE(producer), "pixbuf.image", self->image, image_size, mlt_pool_release); self->image_cache = mlt_service_cache_get(MLT_PRODUCER_SERVICE(producer), "pixbuf.image"); self->image_idx = current_idx; mlt_cache_item_close(self->alpha_cache); self->alpha_cache = NULL; if (self->alpha) { mlt_service_cache_put(MLT_PRODUCER_SERVICE(producer), "pixbuf.alpha", self->alpha, width * height, mlt_pool_release); self->alpha_cache = mlt_service_cache_get(MLT_PRODUCER_SERVICE(producer), "pixbuf.alpha"); } // Finished with pixbuf now g_object_unref(pixbuf); } // Set width/height of frame mlt_properties_set_int(properties, "width", self->width); mlt_properties_set_int(properties, "height", self->height); } static int producer_get_image(mlt_frame frame, uint8_t **buffer, mlt_image_format *format, int *width, int *height, int writable) { int error = 0; // Obtain properties of frame and producer mlt_properties properties = MLT_FRAME_PROPERTIES(frame); producer_pixbuf self = mlt_properties_get_data(properties, "producer_pixbuf", NULL); mlt_producer producer = &self->parent; // Use the width and height suggested by the rescale filter because we can do our own scaling. if (mlt_properties_get_int(properties, "rescale_width") > 0) *width = mlt_properties_get_int(properties, "rescale_width"); if (mlt_properties_get_int(properties, "rescale_height") > 0) *height = mlt_properties_get_int(properties, "rescale_height"); // Restore pixbuf and image mlt_service_lock(MLT_PRODUCER_SERVICE(producer)); self->pixbuf_cache = mlt_service_cache_get(MLT_PRODUCER_SERVICE(producer), "pixbuf.pixbuf"); self->pixbuf = mlt_cache_item_data(self->pixbuf_cache, NULL); self->image_cache = mlt_service_cache_get(MLT_PRODUCER_SERVICE(producer), "pixbuf.image"); self->image = mlt_cache_item_data(self->image_cache, NULL); self->alpha_cache = mlt_service_cache_get(MLT_PRODUCER_SERVICE(producer), "pixbuf.alpha"); self->alpha = mlt_cache_item_data(self->alpha_cache, NULL); // Refresh the image refresh_image(self, frame, *format, *width, *height); // Get width and height (may have changed during the refresh) *width = self->width; *height = self->height; *format = self->format; // NB: Cloning is necessary with this producer (due to processing of images ahead of use) // The fault is not in the design of mlt, but in the implementation of the pixbuf producer... if (self->image) { // Clone the image int image_size = mlt_image_format_size(self->format, self->width, self->height, NULL); uint8_t *image_copy = mlt_pool_alloc(image_size); memcpy(image_copy, self->image, mlt_image_format_size(self->format, self->width, self->height, NULL)); // Now update properties so we free the copy after mlt_frame_set_image(frame, image_copy, image_size, mlt_pool_release); // We're going to pass the copy on *buffer = image_copy; mlt_log_debug(MLT_PRODUCER_SERVICE(&self->parent), "%dx%d (%s)\n", self->width, self->height, mlt_image_format_name(*format)); // Clone the alpha channel if (self->alpha) { image_copy = mlt_pool_alloc(self->width * self->height); memcpy(image_copy, self->alpha, self->width * self->height); mlt_frame_set_alpha(frame, image_copy, self->width * self->height, mlt_pool_release); } } else { error = 1; } // Release references and locks mlt_cache_item_close(self->pixbuf_cache); mlt_cache_item_close(self->image_cache); mlt_cache_item_close(self->alpha_cache); mlt_service_unlock(MLT_PRODUCER_SERVICE(&self->parent)); return error; } static int producer_get_frame(mlt_producer producer, mlt_frame_ptr frame, int index) { // Get the real structure for this producer producer_pixbuf self = producer->child; // Fetch the producers properties mlt_properties producer_properties = MLT_PRODUCER_PROPERTIES(producer); if (self->filenames == NULL && mlt_properties_get(producer_properties, "resource") != NULL) load_filenames(self, producer_properties); // Generate a frame *frame = mlt_frame_init(MLT_PRODUCER_SERVICE(producer)); if (*frame != NULL && self->count > 0) { // Obtain properties of frame and producer mlt_properties properties = MLT_FRAME_PROPERTIES(*frame); // Set the producer on the frame properties mlt_properties_set_data(properties, "producer_pixbuf", self, 0, NULL, NULL); // Update timecode on the frame we're creating mlt_frame_set_position(*frame, mlt_producer_position(producer)); // Refresh the pixbuf self->pixbuf_cache = mlt_service_cache_get(MLT_PRODUCER_SERVICE(producer), "pixbuf.pixbuf"); self->pixbuf = mlt_cache_item_data(self->pixbuf_cache, NULL); refresh_pixbuf(self, *frame); mlt_cache_item_close(self->pixbuf_cache); // Set producer-specific frame properties mlt_properties_set_int(properties, "progressive", mlt_properties_get_int(producer_properties, "progressive")); double force_ratio = mlt_properties_get_double(producer_properties, "force_aspect_ratio"); if (force_ratio > 0.0) mlt_properties_set_double(properties, "aspect_ratio", force_ratio); else mlt_properties_set_double(properties, "aspect_ratio", mlt_properties_get_double(producer_properties, "aspect_ratio")); // Push the get_image method mlt_frame_push_get_image(*frame, producer_get_image); } // Calculate the next timecode mlt_producer_prepare_next(producer); return 0; } static void producer_close(mlt_producer parent) { producer_pixbuf self = parent->child; parent->close = NULL; mlt_service_cache_purge(MLT_PRODUCER_SERVICE(parent)); mlt_producer_close(parent); free(self->outs); self->outs = NULL; mlt_properties_close(self->filenames); free(self); } mlt-7.22.0/src/modules/gdk/producer_pixbuf.yml000664 000000 000000 00000007740 14531534050 021315 0ustar00rootroot000000 000000 schema_version: 0.3 type: producer identifier: pixbuf title: GDK-PixBuf version: 2 copyright: Meltytech, LLC creator: Dan Dennedy license: LGPLv2.1 language: en tags: - Video description: > A still graphics to video generator using gdk-pixbuf notes: > Pixbuf has builtin scaling. It will rescale the originally rendered title to whatever the consumer requests. Therefore, it will lose its aspect ratio if so requested, and it is up to the consumer to request a proper width and height that maintains the image aspect. Environment variable MLT_PIXBUF_PRODUCER_CACHE could be used to to override /increase the number of cached converted images for simultaneous use. parameters: - identifier: resource title: File type: string description: > The name of a graphics file loadable by a gdk-pixbuf loader. See the output of gdk-pixbuf-query-loaders. Definitely png, jpeg, tiff, pnm, and xpm will work. If "%" in filename, the filename is used with sprintf to generate a filename from a counter for multi-file/flipbook animation. The file sequence ends when numeric discontinuity >100. If the file sequence does not begin within the count of 100 you can pass the begin property like a query string parameter, for example: anim-%04d.png?begin=1000. If filename contains "/.all.", suffix with an extension to load all pictures with matching extension from a directory. If filename contains the string " Reload the file instead of using its cached image. This property automatically resets itself once it has been set 1 and processed. minimum: 0 maximum: 1 mutable: yes - identifier: disable_exif title: Disable auto-rotation type: boolean default: 0 widget: checkbox - identifier: force_aspect_ratio title: Sample aspect ratio type: float description: Optionally override a (mis)detected aspect ratio mutable: yes - identifier: loop title: Loop sequence of images indefinitively description: when 1 (default) loop sequences of images, when 0, play them only once type: boolean default: 1 widget: checkbox - identifier: autolength title: Automatically compute length description: Whether to automatically compute the length and out point for an image sequence. type: boolean default: 0 widget: checkbox mlt-7.22.0/src/modules/gdk/scale_line_22_yuv_mmx.S000664 000000 000000 00000012602 14531534050 021674 0ustar00rootroot000000 000000 /* * scale_line_22_yuv_mmx.S -- scale line in YUY2 format * Copyright (C) 2003-2014 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ .file "scale_line_22_yuv_mmx.S" .version "01.01" #if !defined(__MINGW32__) && !defined(__CYGWIN__) .section .note.GNU-stack,"",%progbits #endif .extern printf gcc2_compiled.: .data MSG: .ascii "scale_line_22_yuv_mmx: %d %d\n" .text .align 16 #if !defined(__MINGW32__) && !defined(__CYGWIN__) .globl pixops_scale_line_22_yuv_mmx .type pixops_scale_line_22_yuv_mmx,@function pixops_scale_line_22_yuv_mmx: #else .globl _pixops_scale_line_22_yuv_mmx _pixops_scale_line_22_yuv_mmx: #endif /* * Arguments * * weights: 8(%ebp) * p (dest): 12(%ebp) %esi * q1 (src0): 16(%ebp) * q2 (src1): 20(%ebp) * xstep: 24(%ebp) * p_end: 28(%ebp) * xinit: 32(%ebp) * dest_x: 36(%ebp) * */ /* * Function call entry */ pushl %ebp movl %esp,%ebp subl $28,%esp pushl %edi pushl %esi pushl %ebx /* Locals: * int x %ebx * int x_scaled -24(%ebp) * int dest_x 36(%ebp) */ /* * Setup */ /* Initialize variables */ movl 36(%ebp),%eax # destx movl %eax,36(%ebp) movl 32(%ebp),%ebx # x movl 12(%ebp),%esi # dest cmpl 28(%ebp),%esi # dest == dest_end ? jnb .out /* For the body of this loop, %mm0, %mm1, %mm2, %mm3 hold the 4 adjoining * points we are interpolating between, as: * * 00VV00Y200UU00Y1 */ pxor %mm4, %mm4 /* * Load next component values into mm1 (src0) and mm3 (src1) */ movl %ebx, %eax # x_scaled sarl $15, %eax andl $0xfffffffe, %eax movl %eax, %edx # x_aligned andl $0xfffffffc, %edx movl 16(%ebp), %edi # get src0 movl (%edi,%eax), %ecx # get y andl $0x00ff00ff, %ecx # mask off y movl (%edi,%edx), %eax # get uv andl $0xff00ff00, %eax # mask off uv orl %eax, %ecx # composite y, uv movd %ecx, %mm1 # move to mmx1 punpcklbw %mm4, %mm1 movl 20(%ebp), %edi # get src1 movl (%edi,%edx), %ecx # get y andl $0x00ff00ff, %ecx # mask off y movl (%edi,%edx), %eax # get uv andl $0xff00ff00, %eax # mask off uv orl %eax, %ecx # composite y, uv movd %ecx, %mm3 # move to mmx3 punpcklbw %mm4, %mm3 jmp .newx .p2align 4,,7 .loop: /* short *pixel_weights = weights + ((x >> (SCALE_SHIFT - SUBSAMPLE_BITS)) & SUBSAMPLE_MASK) * n_x * n_y * 16 4 0xf 2 2 */ movl 8(%ebp), %edi # get weights pointer movl %ebx, %eax andl $0xf000, %eax shrl $7, %eax /* At this point, %edi holds weights. Load the 4 weights into * %mm4,%mm5,%mm6,%mm7, multiply and accumulate. */ movq (%edi,%eax), %mm4 pmullw %mm0, %mm4 movq 8(%edi,%eax), %mm5 pmullw %mm1, %mm5 movq 16(%edi,%eax), %mm6 pmullw %mm2,%mm6 movq 24(%edi,%eax), %mm7 pmullw %mm3,%mm7 paddw %mm4, %mm5 paddw %mm6, %mm7 paddw %mm5, %mm7 /* %mm7 holds the accumulated sum. Compute (C + 0x80) / 256 */ pxor %mm4, %mm4 movl $0x80808080, %eax movd %eax, %mm6 punpcklbw %mm4, %mm6 paddw %mm6, %mm7 psrlw $8, %mm7 /* Pack into %eax and store result */ packuswb %mm7, %mm7 movd %mm7, %eax movb %al, (%esi) # *dest = y movl 36(%ebp), %ecx # get dest_x andl $1, %ecx # select u or v sall $1, %ecx # determine offset addl $1, %ecx # relative to x_aligned sall $3, %ecx # offset * 8 bits/byte movd %mm7, %eax shrl %cl, %eax movb %al, 1(%esi) # *dest = uv addl $2, %esi # dest += 2 cmpl %esi,28(%ebp) # if dest == dest_end je .out # then exit addl $1, 36(%ebp) # dest_x++ .newx: addl 24(%ebp), %ebx # x += x_step /* * Load current component values into mm0 (src0) and mm2 (src1) */ movq %mm1, %mm0 movq %mm3, %mm2 /* * Load next component values into mm1 (src0) and mm3 (src1) */ movl %ebx, %eax # x_scaled sarl $15, %eax andl $0xfffffffe, %eax movl %eax, %edx # x_aligned andl $0xfffffffc, %edx movl 16(%ebp), %edi # get src0 movl (%edi,%eax), %ecx # get y andl $0x00ff00ff, %ecx # mask off y movl (%edi,%edx), %eax # get uv andl $0xff00ff00, %eax # mask off uv orl %eax, %ecx # composite y, uv movd %ecx, %mm1 # move to mmx1 punpcklbw %mm4, %mm1 movl 20(%ebp), %edi # get src1 movl (%edi,%edx), %ecx # get y andl $0x00ff00ff, %ecx # mask off y movl (%edi,%edx), %eax # get uv andl $0xff00ff00, %eax # mask off uv orl %eax, %ecx # composite y, uv movd %ecx, %mm3 # move to mmx3 punpcklbw %mm4, %mm3 jmp .loop .out: movl %esi,%eax emms leal -40(%ebp),%esp popl %ebx popl %esi popl %edi movl %ebp,%esp popl %ebp ret mlt-7.22.0/src/modules/glaxnimate/000775 000000 000000 00000000000 14531534050 016746 5ustar00rootroot000000 000000 mlt-7.22.0/src/modules/glaxnimate/CMakeLists.txt000664 000000 000000 00000016026 14531534050 021513 0ustar00rootroot000000 000000 set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTOUIC ON) set(CMAKE_AUTORCC ON) set(CMAKE_INCLUDE_CURRENT_DIR ON) set(CORE_DIR glaxnimate/src/core/) set(APP_DIR glaxnimate/external/QtAppSetup/src/) set(GLAX_SOURCES ${APP_DIR}app/application.cpp ${APP_DIR}app/settings/settings_group.cpp ${APP_DIR}app/settings/settings.cpp ${APP_DIR}app/settings/palette_settings.cpp ${APP_DIR}app/settings/keyboard_shortcuts.cpp ${APP_DIR}app/settings/keyboard_shortcuts_model.cpp ${APP_DIR}app/translation_service.cpp ${APP_DIR}app/scripting/script_engine.cpp ${APP_DIR}app/widgets/settings_dialog.cpp ${APP_DIR}app/widgets/settings_dialog.ui ${APP_DIR}app/widgets/widget_palette_editor.cpp ${APP_DIR}app/widgets/widget_palette_editor.ui ${APP_DIR}app/widgets/clearable_keysequence_edit.cpp ${APP_DIR}app/widgets/clearable_keysequence_edit.ui ${APP_DIR}app/widgets/keyboard_settings_widget.cpp ${APP_DIR}app/widgets/keyboard_settings_widget.ui ${APP_DIR}app/log/logger.cpp ${APP_DIR}app/log/log_model.cpp ${APP_DIR}app/cli.cpp ${CORE_DIR}app_info.cpp ${CORE_DIR}command/structure_commands.cpp ${CORE_DIR}command/shape_commands.cpp ${CORE_DIR}command/animation_commands.cpp ${CORE_DIR}io/base.cpp ${CORE_DIR}io/binary_stream.cpp ${CORE_DIR}io/utils.cpp ${CORE_DIR}io/glaxnimate/glaxnimate_format.cpp ${CORE_DIR}io/glaxnimate/glaxnimate_importer.cpp ${CORE_DIR}io/glaxnimate/glaxnimate_mime.cpp ${CORE_DIR}io/lottie/cbor_write_json.cpp ${CORE_DIR}io/lottie/lottie_format.cpp ${CORE_DIR}io/lottie/lottie_html_format.cpp ${CORE_DIR}io/lottie/tgs_format.cpp ${CORE_DIR}io/lottie/validation.cpp ${CORE_DIR}io/mime/mime_serializer.cpp ${CORE_DIR}io/raster/raster_format.cpp ${CORE_DIR}io/raster/spritesheet_format.cpp ${CORE_DIR}io/rive/rive_format.cpp ${CORE_DIR}io/rive/rive_html_format.cpp ${CORE_DIR}io/rive/rive_loader.cpp ${CORE_DIR}io/rive/rive_serializer.cpp ${CORE_DIR}io/rive/type_def.cpp ${CORE_DIR}io/rive/type_system.cpp ${CORE_DIR}io/svg/detail.cpp ${CORE_DIR}io/svg/svg_format.cpp ${CORE_DIR}io/svg/svg_parser.cpp ${CORE_DIR}io/svg/svg_renderer.cpp ${CORE_DIR}io/avd/avd_parser.cpp ${CORE_DIR}io/avd/avd_format.cpp ${CORE_DIR}io/avd/avd_renderer.cpp ${CORE_DIR}io/aep/aep_format.cpp ${CORE_DIR}io/aep/aep_loader.cpp ${CORE_DIR}io/aep/string_decoder.cpp ${CORE_DIR}io/aep/gradient_xml.cpp ${CORE_DIR}math/geom.cpp ${CORE_DIR}math/polynomial.cpp ${CORE_DIR}math/ellipse_solver.cpp ${CORE_DIR}math/bezier/bezier.cpp ${CORE_DIR}math/bezier/point.cpp ${CORE_DIR}math/bezier/operations.cpp ${CORE_DIR}math/bezier/cubic_struts.cpp ${CORE_DIR}math/bezier/meta.cpp ${CORE_DIR}math/bezier/bezier_length.cpp ${CORE_DIR}model/document.cpp ${CORE_DIR}model/document_node.cpp ${CORE_DIR}model/object.cpp ${CORE_DIR}model/transform.cpp ${CORE_DIR}model/factory.cpp ${CORE_DIR}model/animation_container.cpp ${CORE_DIR}model/stretchable_time.cpp ${CORE_DIR}model/comp_graph.cpp ${CORE_DIR}model/mask_settings.cpp ${CORE_DIR}model/visitor.cpp ${CORE_DIR}model/custom_font.cpp ${CORE_DIR}model/animation/keyframe_transition.cpp ${CORE_DIR}model/animation/animatable.cpp ${CORE_DIR}model/animation/animatable_path.cpp ${CORE_DIR}model/property/property.cpp ${CORE_DIR}model/property/reference_property.cpp ${CORE_DIR}model/property/option_list_property.cpp ${CORE_DIR}model/assets/assets.cpp ${CORE_DIR}model/assets/brush_style.cpp ${CORE_DIR}model/assets/named_color.cpp ${CORE_DIR}model/assets/bitmap.cpp ${CORE_DIR}model/assets/gradient.cpp ${CORE_DIR}model/assets/asset_base.cpp ${CORE_DIR}model/assets/asset.cpp ${CORE_DIR}model/assets/composition.cpp ${CORE_DIR}model/assets/embedded_font.cpp ${CORE_DIR}model/assets/network_downloader.cpp ${CORE_DIR}model/shapes/shape.cpp ${CORE_DIR}model/shapes/fill.cpp ${CORE_DIR}model/shapes/rect.cpp ${CORE_DIR}model/shapes/group.cpp ${CORE_DIR}model/shapes/ellipse.cpp ${CORE_DIR}model/shapes/path.cpp ${CORE_DIR}model/shapes/stroke.cpp ${CORE_DIR}model/shapes/polystar.cpp ${CORE_DIR}model/shapes/styler.cpp ${CORE_DIR}model/shapes/layer.cpp ${CORE_DIR}model/shapes/image.cpp ${CORE_DIR}model/shapes/precomp_layer.cpp ${CORE_DIR}model/shapes/text.cpp ${CORE_DIR}model/shapes/repeater.cpp ${CORE_DIR}model/shapes/trim.cpp ${CORE_DIR}model/shapes/inflate_deflate.cpp ${CORE_DIR}model/shapes/path_modifier.cpp ${CORE_DIR}model/shapes/round_corners.cpp ${CORE_DIR}model/shapes/offset_path.cpp ${CORE_DIR}model/shapes/zig_zag.cpp ${CORE_DIR}plugin/plugin.cpp ${CORE_DIR}plugin/action.cpp ${CORE_DIR}plugin/io.cpp ${CORE_DIR}utils/gzip.cpp ) file(GLOB YML "*.yml") add_custom_target(Other_glaxnimate_Files SOURCES ${YML} ) function(mlt_add_glaxnimate_module ARG_TARGET) cmake_parse_arguments(PARSE_ARGV 1 ARG "" "QT_VERSION;DATADIR" "") if ("${ARG_TARGET}" STREQUAL "") message(FATAL_ERROR "mlt_add_glaxnimate_module called without a valid target name.") endif() if (NOT (("${ARG_QT_VERSION}" STREQUAL "5") OR ("${ARG_QT_VERSION}" STREQUAL "6"))) message(FATAL_ERROR "mlt_add_glaxnimate_module called without a valid Qt Version (allowed are 5 or 6).") endif() if ("${ARG_DATADIR}" STREQUAL "") message(FATAL_ERROR "mlt_add_glaxnimate_module called without a valid data dir name.") endif() configure_file( "${CMAKE_CURRENT_SOURCE_DIR}/${CORE_DIR}application_info_generated.in.hpp" "${CMAKE_CURRENT_SOURCE_DIR}/${CORE_DIR}application_info_generated.hpp" ) include_directories(${CMAKE_CURRENT_SOURCE_DIR}/${APP_DIR}) include_directories(${CMAKE_CURRENT_SOURCE_DIR}/${CORE_DIR}) include_directories(${LibArchive_INCLUDE_DIRS}) add_library(${ARG_TARGET} MODULE producer_glaxnimate.cpp ${GLAX_SOURCES} ) target_compile_options(${ARG_TARGET} PRIVATE ${MLT_COMPILE_OPTIONS}) add_definitions(-DWITHOUT_POTRACE -DWITHOUT_QT_COLOR_WIDGETS) find_package(LibArchive REQUIRED) find_package(ZLIB REQUIRED) set_property(TARGET ${ARG_TARGET} APPEND PROPERTY AUTOMOC_MACRO_NAMES "GLAXNIMATE_OBJECT") target_link_libraries(${ARG_TARGET} PRIVATE mlt++ mlt m Threads::Threads Qt${ARG_QT_VERSION}::Core Qt${ARG_QT_VERSION}::Gui Qt${ARG_QT_VERSION}::Network Qt${ARG_QT_VERSION}::Widgets Qt${ARG_QT_VERSION}::Xml ${LibArchive_LIBRARIES} ZLIB::ZLIB ) if(NOT WINDOWS_DEPLOY) target_compile_definitions(${ARG_TARGET} PRIVATE NODEPLOY) endif() set_target_properties(${ARG_TARGET} PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${MLT_MODULE_OUTPUT_DIRECTORY}") install(TARGETS ${ARG_TARGET} LIBRARY DESTINATION ${MLT_INSTALL_MODULE_DIR}) install(FILES producer_glaxnimate.yml DESTINATION ${MLT_INSTALL_DATA_DIR}/${ARG_DATADIR} ) endfunction() if(MOD_GLAXNIMATE) mlt_add_glaxnimate_module(mltglaxnimate QT_VERSION 5 DATADIR glaxnimate) endif() if(MOD_GLAXNIMATE_QT6) mlt_add_glaxnimate_module(mltglaxnimate-qt6 QT_VERSION 6 DATADIR glaxnimate-qt6) endif() mlt-7.22.0/src/modules/glaxnimate/glaxnimate/000775 000000 000000 00000000000 14531534050 021077 5ustar00rootroot000000 000000 mlt-7.22.0/src/modules/glaxnimate/gpl000664 000000 000000 00000000000 14531534050 017441 0ustar00rootroot000000 000000 mlt-7.22.0/src/modules/glaxnimate/producer_glaxnimate.cpp000664 000000 000000 00000024227 14531534050 023515 0ustar00rootroot000000 000000 /* * producer_glaxnimate.cpp -- a Glaxnimate/Qt based producer for MLT * Copyright (C) 2022-2023 Meltytech, LLC * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see */ #include #include #include #include #include #include #include #include "io/io_registry.hpp" #include "model/assets/assets.hpp" #include "model/assets/composition.hpp" #include "model/document.hpp" using namespace glaxnimate; class Glaxnimate { private: mlt_producer m_producer = nullptr; std::unique_ptr m_document; public: mlt_profile m_profile = nullptr; void setProducer(mlt_producer producer) { m_producer = producer; } mlt_producer producer() const { return m_producer; } mlt_service service() const { return MLT_PRODUCER_SERVICE(m_producer); } mlt_properties properties() const { return MLT_PRODUCER_PROPERTIES(m_producer); } glaxnimate::model::Composition *composition() const { return m_document->assets()->compositions->values[0]; } QSize size() const { return composition()->size(); } int duration() const { auto frames = composition()->animation->last_frame.get() - composition()->animation->first_frame.get() + 1.f; return toMltFps(frames); } int toMltFps(float frame) const { return qRound(frame / fps() * m_profile->frame_rate_num / m_profile->frame_rate_den); } float toGlaxnimateFps(float frame) const { return frame * fps() * m_profile->frame_rate_den / m_profile->frame_rate_num; } int firstFrame() const { return toMltFps(composition()->animation->first_frame.get()); } float fps() const { return composition()->get_fps(); } int getImage(mlt_frame frame, uint8_t **buffer, mlt_image_format *format, int *width, int *height, int writable) { int error = 0; auto pos = mlt_frame_original_position(frame); if (mlt_properties_get(properties(), "eof") && !::strcmp("loop", mlt_properties_get(properties(), "eof"))) { pos %= duration(); } auto bg = mlt_properties_get_color(properties(), "background"); auto background = QColor(bg.r, bg.g, bg.b, bg.a); pos += toMltFps(composition()->animation->first_frame.get()); auto image = composition()->render_image(toGlaxnimateFps(pos), {*width, *height}, background); *format = mlt_image_rgba; int size = mlt_image_format_size(*format, *width, *height, NULL); *buffer = static_cast(mlt_pool_alloc(size)); memcpy(*buffer, image.constBits(), size); error = mlt_frame_set_image(frame, *buffer, size, mlt_pool_release); return error; } bool open(const char *fileName) { auto filename = QString::fromUtf8(fileName); auto importer = io::IoRegistry::instance().from_filename(filename, io::ImportExport::Import); if (!importer || !importer->can_open()) { mlt_log_error(service(), "Unknown importer\n"); return false; } QFile file(filename); if (!file.open(QIODevice::ReadOnly)) { mlt_log_error(service(), "Could not open input file for reading\n"); return false; } m_document.reset(new model::Document(filename)); QVariantMap settings; if (!importer->open(file, filename, m_document.get(), settings)) { mlt_log_error(service(), "Error loading input file\n"); return false; } return true; } }; static bool createQApplicationIfNeeded(mlt_service service) { if (!qApp) { #if defined(Q_OS_WIN) && defined(NODEPLOY) QCoreApplication::addLibraryPath(QString(mlt_environment("MLT_APPDIR")) + QStringLiteral("/bin")); QCoreApplication::addLibraryPath(QString(mlt_environment("MLT_APPDIR")) + QStringLiteral("/plugins")); #endif #if defined(Q_OS_UNIX) && !defined(Q_OS_MAC) if (getenv("DISPLAY") == 0) { mlt_log_error( service, "The MLT Qt module requires a X11 environment.\n" "Please either run melt from an X session or use a fake X server like xvfb:\n" "xvfb-run -a melt (...)\n"); return false; } #endif if (!mlt_properties_get(mlt_global_properties(), "qt_argv")) mlt_properties_set(mlt_global_properties(), "qt_argv", "MLT"); static int argc = 1; static char *argv[] = {mlt_properties_get(mlt_global_properties(), "qt_argv")}; new QApplication(argc, argv); const char *localename = mlt_properties_get_lcnumeric(MLT_SERVICE_PROPERTIES(service)); QLocale::setDefault(QLocale(localename)); } return true; } extern "C" { static int get_image(mlt_frame frame, uint8_t **buffer, mlt_image_format *format, int *width, int *height, int writable) { auto producer = static_cast(mlt_frame_pop_service(frame)); auto glax = static_cast(producer->child); if (mlt_properties_get_int(glax->properties(), "refresh")) { mlt_properties_clear(glax->properties(), "refresh"); glax->open(mlt_properties_get(glax->properties(), "resource")); if (glax->duration() > mlt_properties_get_int(glax->properties(), "length")) { mlt_properties_set_int(glax->properties(), "length", glax->duration()); } } return glax->getImage(frame, buffer, format, width, height, writable); } static int get_frame(mlt_producer producer, mlt_frame_ptr frame, int index) { *frame = mlt_frame_init(MLT_PRODUCER_SERVICE(producer)); mlt_properties frame_properties = MLT_FRAME_PROPERTIES(*frame); // Set frame properties mlt_properties_set_int(frame_properties, "progressive", 1); // Inform framework that this producer creates rgba frames by default mlt_properties_set_int(frame_properties, "format", mlt_image_rgba); double force_ratio = mlt_properties_get_double(MLT_PRODUCER_PROPERTIES(producer), "force_aspect_ratio"); if (force_ratio > 0.0) mlt_properties_set_double(frame_properties, "aspect_ratio", force_ratio); else mlt_properties_set_double(frame_properties, "aspect_ratio", 1.0); mlt_frame_set_position(*frame, mlt_producer_position(producer)); mlt_frame_push_service(*frame, producer); mlt_frame_push_get_image(*frame, get_image); mlt_producer_prepare_next(producer); return 0; } static void producer_close(mlt_producer producer) { delete static_cast(producer->child); producer->close = nullptr; mlt_producer_close(producer); } mlt_producer producer_glaxnimate_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { // Allocate the producer Glaxnimate *glax = new Glaxnimate(); mlt_producer producer = (mlt_producer) calloc(1, sizeof(*producer)); if (!glax || mlt_producer_init(producer, glax) || !createQApplicationIfNeeded(MLT_PRODUCER_SERVICE(producer))) { mlt_producer_close(producer); return NULL; } // If allocated and initializes if (glax->open(arg)) { glax->setProducer(producer); glax->m_profile = profile; producer->close = (mlt_destructor) producer_close; producer->get_frame = get_frame; auto properties = glax->properties(); mlt_properties_set(properties, "resource", arg); mlt_properties_set(properties, "background", "#00000000"); mlt_properties_set_int(properties, "aspect_ratio", 1); mlt_properties_set_int(properties, "progressive", 1); mlt_properties_set_int(properties, "seekable", 1); mlt_properties_set_int(properties, "meta.media.width", glax->size().width()); mlt_properties_set_int(properties, "meta.media.height", glax->size().height()); mlt_properties_set_int(properties, "meta.media.sample_aspect_num", 1); mlt_properties_set_int(properties, "meta.media.sample_aspect_den", 1); mlt_properties_set_double(properties, "meta.media.frame_rate", glax->fps()); mlt_properties_set_int(properties, "out", glax->duration() - 1); mlt_properties_set_int(properties, "length", glax->duration()); mlt_properties_set_int(properties, "first_frame", glax->firstFrame()); mlt_properties_set(properties, "eof", "loop"); } return producer; } static mlt_properties metadata(mlt_service_type type, const char *id, void *data) { char file[PATH_MAX]; const char *service_type = NULL; switch (type) { case mlt_service_producer_type: service_type = "producer"; break; default: return NULL; } snprintf(file, PATH_MAX, #if QT_VERSION_MAJOR < 6 "%s/glaxnimate/%s_%s.yml", #else "%s/glaxnimate-qt6/%s_%s.yml", #endif mlt_environment("MLT_DATA"), service_type, id); return mlt_properties_parse_yaml(file); } MLT_REPOSITORY { MLT_REGISTER(mlt_service_producer_type, "glaxnimate", producer_glaxnimate_init); MLT_REGISTER_METADATA(mlt_service_producer_type, "glaxnimate", metadata, NULL); } } // extern C mlt-7.22.0/src/modules/glaxnimate/producer_glaxnimate.yml000664 000000 000000 00000002105 14531534050 023523 0ustar00rootroot000000 000000 schema_version: 7.0 type: producer identifier: glaxnimate title: Glaxnimate Animation version: 1 copyright: Meltytech, LLC license: GPLv3 language: en tags: - Video description: Reads a variety of 2D animations parameters: - identifier: resource argument: yes title: File type: string required: yes mutable: no - identifier: background title: Background color type: color mutable: yes default: '#00000000' - identifier: refresh title: Refresh type: boolean description: > Set this to reload the animation from the resource the next an image is requested. This property is cleared when it has been refreshed. mutable: yes - identifier: meta.media..frame_rate title: Animation frame rate type: float readonly: yes - identifier: first_frame title: First frame number type: integer description: > This returns the first frame number in the animation file as they do not always start with 0. This value is converted to the associated MLT profile frame rate. readonly: yes mlt-7.22.0/src/modules/jackrack/000775 000000 000000 00000000000 14531534050 016366 5ustar00rootroot000000 000000 mlt-7.22.0/src/modules/jackrack/CMakeLists.txt000664 000000 000000 00000003150 14531534050 021125 0ustar00rootroot000000 000000 add_library(mltjackrack MODULE factory.c) file(GLOB YML "*.yml") add_custom_target(Other_jackrack_Files SOURCES ${YML} ) target_compile_options(mltjackrack PRIVATE ${MLT_COMPILE_OPTIONS}) target_link_libraries(mltjackrack PRIVATE mlt Threads::Threads) if(TARGET JACK::JACK) target_sources(mltjackrack PRIVATE consumer_jack.c) target_link_libraries(mltjackrack PRIVATE JACK::JACK) target_compile_definitions(mltjackrack PRIVATE WITH_JACK) install(FILES consumer_jack.yml DESTINATION ${MLT_INSTALL_DATA_DIR}/jackrack) endif() if(GPL AND TARGET PkgConfig::xml AND TARGET PkgConfig::glib AND ladspa_h_FOUND) target_sources(mltjackrack PRIVATE jack_rack.c jack_rack.h lock_free_fifo.c lock_free_fifo.h plugin.c plugin.h plugin_desc.c plugin_desc.h plugin_mgr.c plugin_mgr.h plugin_settings.c plugin_settings.h process.c process.h producer_ladspa.c filter_ladspa.c ) target_link_libraries(mltjackrack PRIVATE ${CMAKE_DL_LIBS} m PkgConfig::xml PkgConfig::glib) target_compile_definitions(mltjackrack PRIVATE GPL) if(RELOCATABLE) target_compile_definitions(mltjackrack PRIVATE RELOCATABLE) endif() install(FILES filter_jack.yml filter_ladspa.yml producer_ladspa.yml DESTINATION ${MLT_INSTALL_DATA_DIR}/jackrack) if(TARGET JACK::JACK) target_sources(mltjackrack PRIVATE filter_jackrack.c) install(FILES filter_jackrack.yml DESTINATION ${MLT_INSTALL_DATA_DIR}/jackrack) endif() endif() set_target_properties(mltjackrack PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${MLT_MODULE_OUTPUT_DIRECTORY}") install(TARGETS mltjackrack LIBRARY DESTINATION ${MLT_INSTALL_MODULE_DIR}) mlt-7.22.0/src/modules/jackrack/blacklist.txt000664 000000 000000 00000000014 14531534050 021072 0ustar00rootroot000000 000000 dssi-vst.so mlt-7.22.0/src/modules/jackrack/consumer_jack.c000664 000000 000000 00000047206 14531534050 021366 0ustar00rootroot000000 000000 /* * consumer_jack.c -- a JACK audio consumer * Copyright (C) 2011-2021 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include #include #include #define BUFFER_LEN (204800 * 6) pthread_mutex_t g_activate_mutex = PTHREAD_MUTEX_INITIALIZER; /** This classes definition. */ typedef struct consumer_jack_s *consumer_jack; struct consumer_jack_s { struct mlt_consumer_s parent; jack_client_t *jack; mlt_deque queue; pthread_t thread; int joined; int running; pthread_mutex_t video_mutex; pthread_cond_t video_cond; int playing; pthread_cond_t refresh_cond; pthread_mutex_t refresh_mutex; int refresh_count; int counter; jack_ringbuffer_t **ringbuffers; jack_port_t **ports; }; /** Forward references to static functions. */ static int consumer_start(mlt_consumer parent); static int consumer_stop(mlt_consumer parent); static int consumer_is_stopped(mlt_consumer parent); static void consumer_close(mlt_consumer parent); static void *consumer_thread(void *); static void consumer_refresh_cb(mlt_consumer sdl, mlt_consumer parent, mlt_event_data); static int jack_process(jack_nframes_t frames, void *data); /** Constructor */ mlt_consumer consumer_jack_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { // Create the consumer object consumer_jack self = calloc(1, sizeof(struct consumer_jack_s)); // If no malloc'd and consumer init ok if (self != NULL && mlt_consumer_init(&self->parent, self, profile) == 0) { char name[14]; snprintf(name, sizeof(name), "mlt%d", getpid()); if ((self->jack = jack_client_open(name, JackNullOption, NULL))) { jack_set_process_callback(self->jack, jack_process, self); // Create the queue self->queue = mlt_deque_init(); // Get the parent consumer object mlt_consumer parent = &self->parent; // We have stuff to clean up, so override the close method parent->close = consumer_close; // get a handle on properties mlt_service service = MLT_CONSUMER_SERVICE(parent); mlt_properties properties = MLT_SERVICE_PROPERTIES(service); // This is the initialisation of the consumer pthread_mutex_init(&self->video_mutex, NULL); pthread_cond_init(&self->video_cond, NULL); // Default scaler (for now we'll use nearest) mlt_properties_set(properties, "rescale", "nearest"); mlt_properties_set(properties, "consumer.deinterlacer", "onefield"); // Default buffer for low latency mlt_properties_set_int(properties, "buffer", 1); // Set frequency from JACK mlt_properties_set_int(properties, "frequency", (int) jack_get_sample_rate(self->jack)); // Set default volume mlt_properties_set_double(properties, "volume", 1.0); // Ensure we don't join on a non-running object self->joined = 1; // Allow thread to be started/stopped parent->start = consumer_start; parent->stop = consumer_stop; parent->is_stopped = consumer_is_stopped; // Initialize the refresh handler pthread_cond_init(&self->refresh_cond, NULL); pthread_mutex_init(&self->refresh_mutex, NULL); mlt_events_listen(MLT_CONSUMER_PROPERTIES(parent), self, "property-changed", (mlt_listener) consumer_refresh_cb); // Return the consumer produced return parent; } } // malloc or consumer init failed free(self); // Indicate failure return NULL; } static void consumer_refresh_cb(mlt_consumer sdl, mlt_consumer parent, mlt_event_data event_data) { const char *name = mlt_event_data_to_string(event_data); if (!strcmp(name, "refresh")) { consumer_jack self = parent->child; pthread_mutex_lock(&self->refresh_mutex); self->refresh_count = self->refresh_count <= 0 ? 1 : self->refresh_count + 1; pthread_cond_broadcast(&self->refresh_cond); pthread_mutex_unlock(&self->refresh_mutex); } } static int consumer_start(mlt_consumer parent) { consumer_jack self = parent->child; if (!self->running) { consumer_stop(parent); self->running = 1; self->joined = 0; pthread_create(&self->thread, NULL, consumer_thread, self); } return 0; } static int consumer_stop(mlt_consumer parent) { // Get the actual object consumer_jack self = parent->child; if (self->running && !self->joined) { // Kill the thread and clean up self->joined = 1; self->running = 0; // Unlatch the consumer thread pthread_mutex_lock(&self->refresh_mutex); pthread_cond_broadcast(&self->refresh_cond); pthread_mutex_unlock(&self->refresh_mutex); // Cleanup the main thread #ifndef _WIN32 if (self->thread) #endif pthread_join(self->thread, NULL); // Unlatch the video thread pthread_mutex_lock(&self->video_mutex); pthread_cond_broadcast(&self->video_cond); pthread_mutex_unlock(&self->video_mutex); // Cleanup JACK if (self->playing) jack_deactivate(self->jack); if (self->ringbuffers) { int n = mlt_properties_get_int(MLT_CONSUMER_PROPERTIES(parent), "channels"); while (n--) { jack_ringbuffer_free(self->ringbuffers[n]); jack_port_unregister(self->jack, self->ports[n]); } mlt_pool_release(self->ringbuffers); } self->ringbuffers = NULL; if (self->ports) mlt_pool_release(self->ports); self->ports = NULL; } return 0; } static int consumer_is_stopped(mlt_consumer parent) { consumer_jack self = parent->child; return !self->running; } static int jack_process(jack_nframes_t frames, void *data) { int error = 0; consumer_jack self = (consumer_jack) data; mlt_properties properties = MLT_CONSUMER_PROPERTIES(&self->parent); int channels = mlt_properties_get_int(properties, "channels"); int i; if (!self->ringbuffers) return 1; for (i = 0; i < channels; i++) { size_t jack_size = (frames * sizeof(float)); size_t ring_size = jack_ringbuffer_read_space(self->ringbuffers[i]); char *dest = jack_port_get_buffer(self->ports[i], frames); jack_ringbuffer_read(self->ringbuffers[i], dest, ring_size < jack_size ? ring_size : jack_size); if (ring_size < jack_size) memset(dest + ring_size, 0, jack_size - ring_size); } return error; } static void initialise_jack_ports(consumer_jack self) { int i; char mlt_name[20], con_name[30]; mlt_properties properties = MLT_CONSUMER_PROPERTIES(&self->parent); const char **ports = NULL; // Propagate these for the Jack processing callback int channels = mlt_properties_get_int(properties, "channels"); // Allocate buffers and ports self->ringbuffers = mlt_pool_alloc(sizeof(jack_ringbuffer_t *) * channels); self->ports = mlt_pool_alloc(sizeof(jack_port_t *) * channels); // Start Jack processing - required before registering ports pthread_mutex_lock(&g_activate_mutex); jack_activate(self->jack); pthread_mutex_unlock(&g_activate_mutex); self->playing = 1; // Register Jack ports for (i = 0; i < channels; i++) { self->ringbuffers[i] = jack_ringbuffer_create(BUFFER_LEN * sizeof(float)); snprintf(mlt_name, sizeof(mlt_name), "out_%d", i + 1); self->ports[i] = jack_port_register(self->jack, mlt_name, JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput | JackPortIsTerminal, 0); } // Establish connections for (i = 0; i < channels; i++) { snprintf(mlt_name, sizeof(mlt_name), "%s", jack_port_name(self->ports[i])); if (mlt_properties_get(properties, con_name)) snprintf(con_name, sizeof(con_name), "%s", mlt_properties_get(properties, con_name)); else { if (!ports) ports = jack_get_ports(self->jack, NULL, NULL, JackPortIsPhysical | JackPortIsInput); if (ports) strncpy(con_name, ports[i], sizeof(con_name)); else snprintf(con_name, sizeof(con_name), "system:playback_%d", i + 1); con_name[sizeof(con_name) - 1] = '\0'; } mlt_log_verbose(NULL, "JACK connect %s to %s\n", mlt_name, con_name); jack_connect(self->jack, mlt_name, con_name); } if (ports) jack_free(ports); } static int consumer_play_audio(consumer_jack self, mlt_frame frame, int init_audio, int *duration) { // Get the properties of this consumer mlt_properties properties = MLT_CONSUMER_PROPERTIES(&self->parent); mlt_audio_format afmt = mlt_audio_float; // Set the preferred params of the test card signal double speed = mlt_properties_get_double(MLT_FRAME_PROPERTIES(frame), "_speed"); int channels = mlt_properties_get_int(properties, "channels"); int frequency = mlt_properties_get_int(properties, "frequency"); int scrub = mlt_properties_get_int(properties, "scrub_audio"); int samples = mlt_audio_calculate_frame_samples(mlt_properties_get_double(properties, "fps"), frequency, self->counter++); float *buffer; mlt_frame_get_audio(frame, (void **) &buffer, &afmt, &frequency, &channels, &samples); *duration = ((samples * 1000) / frequency); if (mlt_properties_get_int(properties, "audio_off")) { init_audio = 1; return init_audio; } if (init_audio == 1) { self->playing = 0; initialise_jack_ports(self); init_audio = 0; } if (init_audio == 0 && (speed == 1.0 || speed == 0.0)) { int i; size_t mlt_size = samples * sizeof(float); float volume = mlt_properties_get_double(properties, "volume"); if (!scrub && speed == 0.0) volume = 0.0; if (volume != 1.0) { float *p = buffer; i = samples * channels + 1; while (--i) *p++ *= volume; } // Write into output ringbuffer for (i = 0; i < channels; i++) { size_t ring_size = jack_ringbuffer_write_space(self->ringbuffers[i]); if (ring_size >= mlt_size) jack_ringbuffer_write(self->ringbuffers[i], (char *) (buffer + i * samples), mlt_size); } } return init_audio; } static int consumer_play_video(consumer_jack self, mlt_frame frame) { // Get the properties of this consumer mlt_properties properties = MLT_CONSUMER_PROPERTIES(&self->parent); if (self->running && !mlt_consumer_is_stopped(&self->parent)) { mlt_events_fire(properties, "consumer-frame-show", mlt_event_data_from_frame(frame)); } return 0; } static void *video_thread(void *arg) { // Identify the arg consumer_jack self = arg; // Obtain time of thread start struct timeval now; int64_t start = 0; int64_t elapsed = 0; struct timespec tm; mlt_frame next = NULL; mlt_properties properties = NULL; double speed = 0; // Get real time flag int real_time = mlt_properties_get_int(MLT_CONSUMER_PROPERTIES(&self->parent), "real_time"); // Get the current time gettimeofday(&now, NULL); // Determine start time start = (int64_t) now.tv_sec * 1000000 + now.tv_usec; while (self->running) { // Pop the next frame pthread_mutex_lock(&self->video_mutex); next = mlt_deque_pop_front(self->queue); while (next == NULL && self->running) { pthread_cond_wait(&self->video_cond, &self->video_mutex); next = mlt_deque_pop_front(self->queue); } pthread_mutex_unlock(&self->video_mutex); if (!self->running || next == NULL) break; // Get the properties properties = MLT_FRAME_PROPERTIES(next); // Get the speed of the frame speed = mlt_properties_get_double(properties, "_speed"); // Get the current time gettimeofday(&now, NULL); // Get the elapsed time elapsed = ((int64_t) now.tv_sec * 1000000 + now.tv_usec) - start; // See if we have to delay the display of the current frame if (mlt_properties_get_int(properties, "rendered") == 1 && self->running) { // Obtain the scheduled playout time int64_t scheduled = mlt_properties_get_int(properties, "playtime"); // Determine the difference between the elapsed time and the scheduled playout time int64_t difference = scheduled - elapsed; // Smooth playback a bit if (real_time && (difference > 20000 && speed == 1.0)) { tm.tv_sec = difference / 1000000; tm.tv_nsec = (difference % 1000000) * 500; nanosleep(&tm, NULL); } // Show current frame if not too old if (!real_time || (difference > -10000 || speed != 1.0 || mlt_deque_count(self->queue) < 2)) consumer_play_video(self, next); // If the queue is empty, recalculate start to allow build up again if (real_time && (mlt_deque_count(self->queue) == 0 && speed == 1.0)) { gettimeofday(&now, NULL); start = ((int64_t) now.tv_sec * 1000000 + now.tv_usec) - scheduled + 20000; } } // This frame can now be closed mlt_frame_close(next); next = NULL; } if (next != NULL) mlt_frame_close(next); mlt_consumer_stopped(&self->parent); return NULL; } /** Threaded wrapper for pipe. */ static void *consumer_thread(void *arg) { // Identify the arg consumer_jack self = arg; // Get the consumer mlt_consumer consumer = &self->parent; // Get the properties mlt_properties consumer_props = MLT_CONSUMER_PROPERTIES(consumer); // Video thread pthread_t thread; // internal initialization int init_audio = 1; int init_video = 1; mlt_frame frame = NULL; mlt_properties properties = NULL; int duration = 0; int64_t playtime = 0; struct timespec tm = {0, 100000}; // int last_position = -1; pthread_mutex_lock(&self->refresh_mutex); self->refresh_count = 0; pthread_mutex_unlock(&self->refresh_mutex); // Loop until told not to while (self->running) { // Get a frame from the attached producer frame = mlt_consumer_rt_frame(consumer); // Ensure that we have a frame if (frame) { // Get the frame properties properties = MLT_FRAME_PROPERTIES(frame); // Get the speed of the frame double speed = mlt_properties_get_double(properties, "_speed"); // Get refresh request for the current frame int refresh = mlt_properties_get_int(consumer_props, "refresh"); // Clear refresh mlt_events_block(consumer_props, consumer_props); mlt_properties_set_int(consumer_props, "refresh", 0); mlt_events_unblock(consumer_props, consumer_props); // Play audio init_audio = consumer_play_audio(self, frame, init_audio, &duration); // Determine the start time now if (self->playing && init_video) { // Create the video thread pthread_create(&thread, NULL, video_thread, self); // Video doesn't need to be initialised any more init_video = 0; } // Set playtime for this frame mlt_properties_set_int(properties, "playtime", playtime); while (self->running && speed != 0 && mlt_deque_count(self->queue) > 15) nanosleep(&tm, NULL); // Push this frame to the back of the video queue if (self->running && speed) { pthread_mutex_lock(&self->video_mutex); mlt_deque_push_back(self->queue, frame); pthread_cond_broadcast(&self->video_cond); pthread_mutex_unlock(&self->video_mutex); // Calculate the next playtime playtime += (duration * 1000); } else if (self->running) { pthread_mutex_lock(&self->refresh_mutex); if (refresh == 0 && self->refresh_count <= 0) { consumer_play_video(self, frame); pthread_cond_wait(&self->refresh_cond, &self->refresh_mutex); } mlt_frame_close(frame); self->refresh_count--; pthread_mutex_unlock(&self->refresh_mutex); } else { mlt_frame_close(frame); frame = NULL; } // Optimisation to reduce latency if (frame && speed == 1.0) { // TODO: disabled due to misbehavior on parallel-consumer // if ( last_position != -1 && last_position + 1 != mlt_frame_get_position( frame ) ) // mlt_consumer_purge( consumer ); // last_position = mlt_frame_get_position( frame ); } else if (speed == 0.0) { mlt_consumer_purge(consumer); // last_position = -1; } } } // Kill the video thread if (init_video == 0) { pthread_mutex_lock(&self->video_mutex); pthread_cond_broadcast(&self->video_cond); pthread_mutex_unlock(&self->video_mutex); pthread_join(thread, NULL); } while (mlt_deque_count(self->queue)) mlt_frame_close(mlt_deque_pop_back(self->queue)); return NULL; } /** Callback to allow override of the close method. */ static void consumer_close(mlt_consumer parent) { // Get the actual object consumer_jack self = parent->child; // Stop the consumer mlt_consumer_stop(parent); // Now clean up the rest mlt_consumer_close(parent); // Close the queue mlt_deque_close(self->queue); // Destroy mutexes pthread_mutex_destroy(&self->video_mutex); pthread_cond_destroy(&self->video_cond); pthread_mutex_destroy(&self->refresh_mutex); pthread_cond_destroy(&self->refresh_cond); // Disconnect from JACK jack_client_close(self->jack); // Finally deallocate self free(self); } mlt-7.22.0/src/modules/jackrack/consumer_jack.yml000664 000000 000000 00000002066 14531534050 021740 0ustar00rootroot000000 000000 schema_version: 0.1 type: consumer identifier: jack title: JACK version: 1 copyright: Meltytech, LLC creator: Dan Dennedy license: LGPLv2.1 language: en tags: - Audio parameters: - identifier: channels title: Channels type: integer minimum: 1 default: 2 - identifier: out_1 title: Send L type: string - identifier: out_2 title: Send R type: string - identifier: volume title: Volume type: float minimum: 0.0 default: 1.0 - identifier: refresh description: > Applications should set this to update the video frame when paused. type: integer minimum: 0 maximum: 1 - identifier: audio_off title: Audio off type: integer description: If 1, disable audio output mutable: yes minimum: 0 maximum: 1 default: 0 widget: checkbox - identifier: scrub_audio title: Audio scrubbing type: integer description: If enabled, sound is played even when the speed is not normal. mutable: yes minimum: 0 maximum: 1 default: 0 widget: checkbox mlt-7.22.0/src/modules/jackrack/factory.c000664 000000 000000 00000025254 14531534050 020211 0ustar00rootroot000000 000000 /* * factory.c -- the factory method interfaces * Copyright (C) 2003-2022 Meltytech, LLC * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include extern mlt_consumer consumer_jack_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); #ifdef GPL #include "plugin_mgr.h" #include #ifdef WITH_JACK extern mlt_filter filter_jackrack_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); #endif extern mlt_filter filter_ladspa_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_producer producer_ladspa_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); plugin_mgr_t *g_jackrack_plugin_mgr = NULL; static void add_port_to_metadata(mlt_properties p, plugin_desc_t *desc, int j) { LADSPA_Data sample_rate = 48000; LADSPA_PortRangeHintDescriptor hint_descriptor = desc->port_range_hints[j].HintDescriptor; mlt_properties_set(p, "title", desc->port_names[j]); if (LADSPA_IS_HINT_INTEGER(hint_descriptor)) { mlt_properties_set(p, "type", "integer"); mlt_properties_set_int(p, "default", plugin_desc_get_default_control_value(desc, j, sample_rate)); } else if (LADSPA_IS_HINT_TOGGLED(hint_descriptor)) { mlt_properties_set(p, "type", "boolean"); mlt_properties_set_int(p, "default", plugin_desc_get_default_control_value(desc, j, sample_rate)); } else { mlt_properties_set(p, "type", "float"); mlt_properties_set_double(p, "default", plugin_desc_get_default_control_value(desc, j, sample_rate)); } /* set upper and lower, possibly adjusted to the sample rate */ if (LADSPA_IS_HINT_BOUNDED_BELOW(hint_descriptor)) { LADSPA_Data lower = desc->port_range_hints[j].LowerBound; if (LADSPA_IS_HINT_SAMPLE_RATE(hint_descriptor)) lower *= sample_rate; if (LADSPA_IS_HINT_LOGARITHMIC(hint_descriptor)) { if (lower < FLT_EPSILON) lower = FLT_EPSILON; } mlt_properties_set_double(p, "minimum", lower); } if (LADSPA_IS_HINT_BOUNDED_ABOVE(hint_descriptor)) { LADSPA_Data upper = desc->port_range_hints[j].UpperBound; if (LADSPA_IS_HINT_SAMPLE_RATE(hint_descriptor)) upper *= sample_rate; mlt_properties_set_double(p, "maximum", upper); } if (LADSPA_IS_HINT_LOGARITHMIC(hint_descriptor)) mlt_properties_set(p, "scale", "log"); mlt_properties_set(p, "mutable", "yes"); mlt_properties_set(p, "animation", "yes"); } #endif static mlt_properties metadata(mlt_service_type type, const char *id, char *data) { char file[PATH_MAX]; if (type == mlt_service_filter_type) { snprintf(file, PATH_MAX, "%s/jackrack/%s", mlt_environment("MLT_DATA"), strncmp(id, "ladspa.", 7) ? data : "filter_ladspa.yml"); } else { snprintf(file, PATH_MAX, "%s/jackrack/%s", mlt_environment("MLT_DATA"), strncmp(id, "ladspa.", 7) ? data : "producer_ladspa.yml"); } mlt_properties result = mlt_properties_parse_yaml(file); #ifdef GPL if (!strncmp(id, "ladspa.", 7)) { // Annotate the yaml properties with ladspa control port info. plugin_desc_t *desc = plugin_mgr_get_any_desc(g_jackrack_plugin_mgr, strtol(id + 7, NULL, 10)); if (desc) { mlt_properties params = mlt_properties_new(); mlt_properties p; char key[20]; int i; mlt_properties_set(result, "identifier", id); mlt_properties_set(result, "title", desc->name); mlt_properties_set(result, "creator", desc->maker ? desc->maker : "unknown"); mlt_properties_set(result, "description", "LADSPA plugin"); mlt_properties_set_data(result, "parameters", params, 0, (mlt_destructor) mlt_properties_close, NULL); for (i = 0; i < desc->control_port_count; i++) { int j = desc->control_port_indicies[i]; p = mlt_properties_new(); snprintf(key, sizeof(key), "%d", mlt_properties_count(params)); mlt_properties_set_data(params, key, p, 0, (mlt_destructor) mlt_properties_close, NULL); snprintf(key, sizeof(key), "%d", j); mlt_properties_set(p, "identifier", key); add_port_to_metadata(p, desc, j); mlt_properties_set(p, "mutable", "yes"); } for (i = 0; i < desc->status_port_count; i++) { int j = desc->status_port_indicies[i]; p = mlt_properties_new(); snprintf(key, sizeof(key), "%d", mlt_properties_count(params)); mlt_properties_set_data(params, key, p, 0, (mlt_destructor) mlt_properties_close, NULL); snprintf(key, sizeof(key), "%d[*]", j); mlt_properties_set(p, "identifier", key); add_port_to_metadata(p, desc, j); mlt_properties_set(p, "readonly", "yes"); } p = mlt_properties_new(); snprintf(key, sizeof(key), "%d", mlt_properties_count(params)); mlt_properties_set_data(params, key, p, 0, (mlt_destructor) mlt_properties_close, NULL); mlt_properties_set(p, "identifier", "instances"); mlt_properties_set(p, "title", "Instances"); mlt_properties_set(p, "description", "The number of instances of the plugin that are in use.\n" "MLT will create the number of plugins that are required " "to support the number of audio channels.\n" "Status parameters (readonly) are provided for each instance " "and are accessed by specifying the instance number after the " "identifier (starting at zero).\n" "e.g. 9[0] provides the value of status 9 for the first instance."); mlt_properties_set(p, "type", "integer"); mlt_properties_set(p, "readonly", "yes"); if (type == mlt_service_filter_type) { p = mlt_properties_new(); snprintf(key, sizeof(key), "%d", mlt_properties_count(params)); mlt_properties_set_data(params, key, p, 0, (mlt_destructor) mlt_properties_close, NULL); mlt_properties_set(p, "identifier", "wetness"); mlt_properties_set(p, "title", "Wet/Dry"); mlt_properties_set(p, "type", "float"); mlt_properties_set_double(p, "default", 1); mlt_properties_set_double(p, "minimum", 0); mlt_properties_set_double(p, "maximum", 1); mlt_properties_set(p, "mutable", "yes"); mlt_properties_set(p, "animation", "yes"); } } } #endif return result; } MLT_REPOSITORY { #ifdef GPL GSList *list; g_jackrack_plugin_mgr = plugin_mgr_new(); for (list = g_jackrack_plugin_mgr->all_plugins; list; list = g_slist_next(list)) { plugin_desc_t *desc = (plugin_desc_t *) list->data; char *s = malloc(strlen("ladpsa.") + 21); sprintf(s, "ladspa.%lu", desc->id); if (desc->has_input) { MLT_REGISTER(mlt_service_filter_type, s, filter_ladspa_init); MLT_REGISTER_METADATA(mlt_service_filter_type, s, metadata, NULL); } else { MLT_REGISTER(mlt_service_producer_type, s, producer_ladspa_init); MLT_REGISTER_METADATA(mlt_service_producer_type, s, metadata, NULL); } free(s); } mlt_factory_register_for_clean_up(g_jackrack_plugin_mgr, (mlt_destructor) plugin_mgr_destroy); #ifdef WITH_JACK MLT_REGISTER(mlt_service_filter_type, "jack", filter_jackrack_init); MLT_REGISTER_METADATA(mlt_service_filter_type, "jack", metadata, "filter_jack.yml"); MLT_REGISTER(mlt_service_filter_type, "jackrack", filter_jackrack_init); MLT_REGISTER_METADATA(mlt_service_filter_type, "jackrack", metadata, "filter_jackrack.yml"); #endif MLT_REGISTER(mlt_service_filter_type, "ladspa", filter_ladspa_init); MLT_REGISTER_METADATA(mlt_service_filter_type, "ladspa", metadata, "filter_ladspa.yml"); #endif #ifdef WITH_JACK MLT_REGISTER(mlt_service_consumer_type, "jack", consumer_jack_init); MLT_REGISTER_METADATA(mlt_service_consumer_type, "jack", metadata, "consumer_jack.yml"); #endif } mlt-7.22.0/src/modules/jackrack/filter_jack.yml000664 000000 000000 00000003267 14531534050 021376 0ustar00rootroot000000 000000 schema_version: 0.3 type: filter identifier: jack title: JACK version: 1 copyright: Copyright (C) 2004-2018 Meltytech, LLC license: GPLv2 language: en url: http://www.ladspa.org/ creator: Dan Dennedy tags: - Audio description: Process audio using JACK. notes: > This can be used to receive audio from JACK by connecting only input ports. It can be used to output audio to JACK by connecting only the output ports. Or, you can use it as a filter with something like JACK Rack by connecting both output and input ports to send and receive. You can configure as many channels as you need and repeat the in_1/out_1 pattern for as many channels as you have configured. If you are using a MLT consumer that uses ALSA, then you should start jackd with the dummy driver, e.g.: jackd -ddummy -r48000 -p2048. bugs: - > MLT cannot automatically adapt to the sample rate at which JACK is configured. Please make sure they are configured the same. - Does not automatically reconfigure to the number of channels requested by consumer. - Some effects have a temporal side-effect that may not work well. parameters: - identifier: client_name title: JACK client name type: string argument: yes required: yes description: > Creates a JACK client with the specified name with input and output ports. The name must be 60 characters or less. - identifier: channels title: Channels type: integer minimum: 1 default: 2 - identifier: in_1 title: Receive L type: string - identifier: in_2 title: Receive R type: string - identifier: out_1 title: Send L type: string - identifier: out_2 title: Send R type: string mlt-7.22.0/src/modules/jackrack/filter_jackrack.c000664 000000 000000 00000053441 14531534050 021657 0ustar00rootroot000000 000000 /* * filter_jackrack.c -- filter audio through Jack and/or LADSPA plugins * Copyright (C) 2004-2021 Meltytech, LLC * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include #include "jack_rack.h" extern pthread_mutex_t g_activate_mutex; #define BUFFER_LEN 204800 * 6 #define JACKSTATE(x) \ (x == JackTransportStopped ? "stopped" \ : x == JackTransportStarting ? "starting" \ : x == JackTransportRolling ? "rolling" \ : "unknown") static int jack_sync(jack_transport_state_t state, jack_position_t *jack_pos, void *arg) { mlt_filter filter = (mlt_filter) arg; mlt_properties properties = MLT_FILTER_PROPERTIES(filter); mlt_profile profile = mlt_service_profile(MLT_FILTER_SERVICE(filter)); mlt_position position = mlt_profile_fps(profile) * jack_pos->frame / jack_pos->frame_rate + 0.5; int result = 1; mlt_log_debug(MLT_FILTER_SERVICE(filter), "%s frame %u rate %u pos %d last_pos %d\n", JACKSTATE(state), jack_pos->frame, jack_pos->frame_rate, position, mlt_properties_get_position(properties, "_last_pos")); if (state == JackTransportStopped) { mlt_events_fire(properties, "jack-stopped", mlt_event_data_from_int(position)); mlt_properties_set_int(properties, "_sync_guard", 0); } else if (state == JackTransportStarting) { result = 0; if (!mlt_properties_get_int(properties, "_sync_guard")) { mlt_properties_set_int(properties, "_sync_guard", 1); mlt_events_fire(properties, "jack-started", mlt_event_data_from_int(position)); } else if (position >= mlt_properties_get_position(properties, "_last_pos") - 2) { mlt_properties_set_int(properties, "_sync_guard", 0); result = 1; } } else { mlt_properties_set_int(properties, "_sync_guard", 0); } return result; } static void on_jack_start(mlt_properties owner, mlt_properties properties) { mlt_log_verbose(NULL, "%s\n", __FUNCTION__); jack_client_t *jack_client = mlt_properties_get_data(properties, "jack_client", NULL); jack_transport_start(jack_client); } static void on_jack_stop(mlt_properties owner, mlt_properties properties) { mlt_log_verbose(NULL, "%s\n", __FUNCTION__); jack_client_t *jack_client = mlt_properties_get_data(properties, "jack_client", NULL); jack_transport_stop(jack_client); } static void on_jack_seek(mlt_properties owner, mlt_filter filter, mlt_event_data event_data) { mlt_position position = mlt_event_data_to_int(event_data); mlt_properties properties = MLT_FILTER_PROPERTIES(filter); mlt_log_verbose(MLT_FILTER_SERVICE(filter), "%s: %d\n", __FUNCTION__, position); mlt_properties_set_int(properties, "_sync_guard", 1); mlt_profile profile = mlt_service_profile(MLT_FILTER_SERVICE(filter)); jack_client_t *jack_client = mlt_properties_get_data(properties, "jack_client", NULL); jack_nframes_t jack_frame = jack_get_sample_rate(jack_client); jack_frame *= position / mlt_profile_fps(profile); jack_transport_locate(jack_client, jack_frame); } static void initialise_jack_ports(mlt_properties properties) { int i; char mlt_name[67], rack_name[30]; jack_port_t **port = NULL; jack_client_t *jack_client = mlt_properties_get_data(properties, "jack_client", NULL); jack_nframes_t jack_buffer_size = jack_get_buffer_size(jack_client); // Propagate these for the Jack processing callback int channels = mlt_properties_get_int(properties, "channels"); // Start JackRack if (mlt_properties_get(properties, "src")) { snprintf(rack_name, sizeof(rack_name), "jackrack%d", getpid()); jack_rack_t *jackrack = jack_rack_new(rack_name, mlt_properties_get_int(properties, "channels")); jack_rack_open_file(jackrack, mlt_properties_get(properties, "src")); mlt_properties_set_data(properties, "jackrack", jackrack, 0, (mlt_destructor) jack_rack_destroy, NULL); mlt_properties_set(properties, "_rack_client_name", rack_name); } else { // We have to set this to something to prevent re-initialization. mlt_properties_set_data(properties, "jackrack", jack_client, 0, NULL, NULL); } // Allocate buffers and ports jack_ringbuffer_t **output_buffers = mlt_pool_alloc(sizeof(jack_ringbuffer_t *) * channels); jack_ringbuffer_t **input_buffers = mlt_pool_alloc(sizeof(jack_ringbuffer_t *) * channels); jack_port_t **jack_output_ports = mlt_pool_alloc(sizeof(jack_port_t *) * channels); jack_port_t **jack_input_ports = mlt_pool_alloc(sizeof(jack_port_t *) * channels); float **jack_output_buffers = mlt_pool_alloc(sizeof(float *) * jack_buffer_size); float **jack_input_buffers = mlt_pool_alloc(sizeof(float *) * jack_buffer_size); // Set properties - released inside filter_close mlt_properties_set_data(properties, "output_buffers", output_buffers, sizeof(jack_ringbuffer_t *) * channels, mlt_pool_release, NULL); mlt_properties_set_data(properties, "input_buffers", input_buffers, sizeof(jack_ringbuffer_t *) * channels, mlt_pool_release, NULL); mlt_properties_set_data(properties, "jack_output_ports", jack_output_ports, sizeof(jack_port_t *) * channels, mlt_pool_release, NULL); mlt_properties_set_data(properties, "jack_input_ports", jack_input_ports, sizeof(jack_port_t *) * channels, mlt_pool_release, NULL); mlt_properties_set_data(properties, "jack_output_buffers", jack_output_buffers, sizeof(float *) * channels, mlt_pool_release, NULL); mlt_properties_set_data(properties, "jack_input_buffers", jack_input_buffers, sizeof(float *) * channels, mlt_pool_release, NULL); // Register Jack ports for (i = 0; i < channels; i++) { int in; output_buffers[i] = jack_ringbuffer_create(BUFFER_LEN * sizeof(float)); input_buffers[i] = jack_ringbuffer_create(BUFFER_LEN * sizeof(float)); snprintf(mlt_name, sizeof(mlt_name), "obuf%d", i); mlt_properties_set_data(properties, mlt_name, output_buffers[i], BUFFER_LEN * sizeof(float), (mlt_destructor) jack_ringbuffer_free, NULL); snprintf(mlt_name, sizeof(mlt_name), "ibuf%d", i); mlt_properties_set_data(properties, mlt_name, input_buffers[i], BUFFER_LEN * sizeof(float), (mlt_destructor) jack_ringbuffer_free, NULL); for (in = 0; in < 2; in++) { snprintf(mlt_name, sizeof(mlt_name), "%s_%d", in ? "in" : "out", i + 1); port = (in ? &jack_input_ports[i] : &jack_output_ports[i]); *port = jack_port_register(jack_client, mlt_name, JACK_DEFAULT_AUDIO_TYPE, (in ? JackPortIsInput : JackPortIsOutput) | JackPortIsTerminal, 0); } } // Start Jack processing pthread_mutex_lock(&g_activate_mutex); jack_activate(jack_client); pthread_mutex_unlock(&g_activate_mutex); // Establish connections for (i = 0; i < channels; i++) { int in; for (in = 0; in < 2; in++) { port = (in ? &jack_input_ports[i] : &jack_output_ports[i]); snprintf(mlt_name, sizeof(mlt_name), "%s", jack_port_name(*port)); snprintf(rack_name, sizeof(rack_name), "%s_%d", in ? "in" : "out", i + 1); if (mlt_properties_get(properties, "_rack_client_name")) snprintf(rack_name, sizeof(rack_name), "%s:%s_%d", mlt_properties_get(properties, "_rack_client_name"), in ? "out" : "in", i + 1); else if (mlt_properties_get(properties, rack_name)) snprintf(rack_name, sizeof(rack_name), "%s", mlt_properties_get(properties, rack_name)); else snprintf(rack_name, sizeof(rack_name), "%s:%s_%d", mlt_properties_get(properties, "client_name"), in ? "out" : "in", i + 1); if (in) { mlt_log_verbose(NULL, "JACK connect %s to %s\n", rack_name, mlt_name); jack_connect(jack_client, rack_name, mlt_name); } else { mlt_log_verbose(NULL, "JACK connect %s to %s\n", mlt_name, rack_name); jack_connect(jack_client, mlt_name, rack_name); } } } } static int jack_process(jack_nframes_t frames, void *data) { mlt_filter filter = (mlt_filter) data; mlt_properties properties = MLT_FILTER_PROPERTIES(filter); int channels = mlt_properties_get_int(properties, "channels"); int frame_size = mlt_properties_get_int(properties, "_samples") * sizeof(float); int sync = mlt_properties_get_int(properties, "_sync"); int err = 0; int i; static int total_size = 0; jack_ringbuffer_t **output_buffers = mlt_properties_get_data(properties, "output_buffers", NULL); if (output_buffers == NULL) return 0; jack_ringbuffer_t **input_buffers = mlt_properties_get_data(properties, "input_buffers", NULL); jack_port_t **jack_output_ports = mlt_properties_get_data(properties, "jack_output_ports", NULL); jack_port_t **jack_input_ports = mlt_properties_get_data(properties, "jack_input_ports", NULL); float **jack_output_buffers = mlt_properties_get_data(properties, "jack_output_buffers", NULL); float **jack_input_buffers = mlt_properties_get_data(properties, "jack_input_buffers", NULL); pthread_mutex_t *output_lock = mlt_properties_get_data(properties, "output_lock", NULL); pthread_cond_t *output_ready = mlt_properties_get_data(properties, "output_ready", NULL); for (i = 0; i < channels; i++) { size_t jack_size = (frames * sizeof(float)); size_t ring_size; // Send audio through out port jack_output_buffers[i] = jack_port_get_buffer(jack_output_ports[i], frames); if (!jack_output_buffers[i]) { mlt_log_error(MLT_FILTER_SERVICE(filter), "no buffer for output port %d\n", i); err = 1; break; } ring_size = jack_ringbuffer_read_space(output_buffers[i]); jack_ringbuffer_read(output_buffers[i], (char *) jack_output_buffers[i], ring_size < jack_size ? ring_size : jack_size); if (ring_size < jack_size) memset(&jack_output_buffers[i][ring_size], 0, jack_size - ring_size); // Return audio through in port jack_input_buffers[i] = jack_port_get_buffer(jack_input_ports[i], frames); if (!jack_input_buffers[i]) { mlt_log_error(MLT_FILTER_SERVICE(filter), "no buffer for input port %d\n", i); err = 1; break; } // Do not start returning audio until we have sent first mlt frame if (sync && i == 0 && frame_size > 0) total_size += ring_size; mlt_log_debug(MLT_FILTER_SERVICE(filter), "sync %d frame_size %d ring_size %zu jack_size %zu\n", sync, frame_size, ring_size, jack_size); if (!sync || (frame_size > 0 && total_size >= frame_size)) { ring_size = jack_ringbuffer_write_space(input_buffers[i]); jack_ringbuffer_write(input_buffers[i], (char *) jack_input_buffers[i], ring_size < jack_size ? ring_size : jack_size); if (sync) { // Tell mlt that audio is available pthread_mutex_lock(output_lock); pthread_cond_signal(output_ready); pthread_mutex_unlock(output_lock); // Clear sync phase mlt_properties_set_int(properties, "_sync", 0); } } } // Often jackd does not send the stopped event through the JackSyncCallback jack_client_t *jack_client = mlt_properties_get_data(properties, "jack_client", NULL); jack_position_t jack_pos; jack_transport_state_t state = jack_transport_query(jack_client, &jack_pos); int transport_state = mlt_properties_get_int(properties, "_transport_state"); if (state != transport_state) { mlt_properties_set_int(properties, "_transport_state", state); if (state == JackTransportStopped) jack_sync(state, &jack_pos, filter); } return err; } /** Get the audio. */ static int jackrack_get_audio(mlt_frame frame, void **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples) { // Get the filter service mlt_filter filter = mlt_frame_pop_audio(frame); // Get the filter properties mlt_properties filter_properties = MLT_FILTER_PROPERTIES(filter); int jack_frequency = mlt_properties_get_int(filter_properties, "_sample_rate"); // Get the producer's audio *format = mlt_audio_float; mlt_frame_get_audio(frame, buffer, format, &jack_frequency, channels, samples); // TODO: Deal with sample rate differences if (*frequency != jack_frequency) mlt_log_error(MLT_FILTER_SERVICE(filter), "mismatching frequencies JACK = %d actual = %d\n", jack_frequency, *frequency); *frequency = jack_frequency; // Initialise Jack ports and connections if needed if (mlt_properties_get_int(filter_properties, "_samples") == 0) mlt_properties_set_int(filter_properties, "_samples", *samples); // Get the filter-specific properties jack_ringbuffer_t **output_buffers = mlt_properties_get_data(filter_properties, "output_buffers", NULL); jack_ringbuffer_t **input_buffers = mlt_properties_get_data(filter_properties, "input_buffers", NULL); // pthread_mutex_t *output_lock = mlt_properties_get_data( filter_properties, "output_lock", NULL ); // pthread_cond_t *output_ready = mlt_properties_get_data( filter_properties, "output_ready", NULL ); // Process the audio float *q = (float *) *buffer; size_t size = *samples * sizeof(float); int j; // struct timespec tm = { 0, 0 }; // Write into output ringbuffer for (j = 0; j < *channels; j++) { if (jack_ringbuffer_write_space(output_buffers[j]) >= size) jack_ringbuffer_write(output_buffers[j], (char *) (q + j * *samples), size); } // Synchronization phase - wait for signal from Jack process while (jack_ringbuffer_read_space(input_buffers[*channels - 1]) < size) ; //pthread_cond_wait( output_ready, output_lock ); // Read from input ringbuffer for (j = 0; j < *channels; j++, q++) { if (jack_ringbuffer_read_space(input_buffers[j]) >= size) jack_ringbuffer_read(input_buffers[j], (char *) (q + j * *samples), size); } // help jack_sync() indicate when we are rolling mlt_position pos = mlt_frame_get_position(frame); mlt_properties_set_position(filter_properties, "_last_pos", pos); return 0; } /** Filter processing. */ static mlt_frame filter_process(mlt_filter this, mlt_frame frame) { { mlt_properties properties = MLT_FILTER_PROPERTIES(this); mlt_frame_push_audio(frame, this); mlt_frame_push_audio(frame, jackrack_get_audio); if (!mlt_properties_get_data(properties, "jackrack", NULL)) initialise_jack_ports(properties); } return frame; } static void filter_close(mlt_filter this) { mlt_properties properties = MLT_FILTER_PROPERTIES(this); jack_client_t *jack_client = mlt_properties_get_data(properties, "jack_client", NULL); jack_deactivate(jack_client); jack_client_close(jack_client); this->parent.close = NULL; mlt_service_close(&this->parent); } /** Constructor for the filter. */ mlt_filter filter_jackrack_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter this = mlt_filter_new(); if (this != NULL) { char name[61]; char *jack_client_name; const char *src; jack_status_t status = 0; if (id && arg && !strcmp(id, "jack")) { snprintf(name, sizeof(name), "%s", arg); src = NULL; } else { snprintf(name, sizeof(name), "mlt%d", getpid()); src = arg; } jack_client_t *jack_client = jack_client_open(arg, JackNullOption, &status, NULL); if (jack_client) { if (status & JackNameNotUnique) { jack_client_name = jack_get_client_name(jack_client); strcpy(name, jack_client_name); } mlt_properties properties = MLT_FILTER_PROPERTIES(this); pthread_mutex_t *output_lock = mlt_pool_alloc(sizeof(pthread_mutex_t)); pthread_cond_t *output_ready = mlt_pool_alloc(sizeof(pthread_cond_t)); jack_set_process_callback(jack_client, jack_process, this); jack_set_sync_callback(jack_client, jack_sync, this); jack_set_sync_timeout(jack_client, 5000000); //TODO: jack_on_shutdown( jack_client, jack_shutdown_cb, this ); this->process = filter_process; this->close = filter_close; pthread_mutex_init(output_lock, NULL); pthread_cond_init(output_ready, NULL); mlt_properties_set(properties, "src", src); mlt_properties_set(properties, "client_name", name); mlt_properties_set_data(properties, "jack_client", jack_client, 0, NULL, NULL); mlt_properties_set_int(properties, "_sample_rate", jack_get_sample_rate(jack_client)); mlt_properties_set_data(properties, "output_lock", output_lock, 0, mlt_pool_release, NULL); mlt_properties_set_data(properties, "output_ready", output_ready, 0, mlt_pool_release, NULL); mlt_properties_set_int(properties, "_sync", 1); mlt_properties_set_int(properties, "channels", 2); mlt_events_register(properties, "jack-started"); mlt_events_register(properties, "jack-stopped"); mlt_events_register(properties, "jack-start"); mlt_events_register(properties, "jack-stop"); mlt_events_register(properties, "jack-seek"); mlt_events_listen(properties, properties, "jack-start", (mlt_listener) on_jack_start); mlt_events_listen(properties, properties, "jack-stop", (mlt_listener) on_jack_stop); mlt_events_listen(properties, this, "jack-seek", (mlt_listener) on_jack_seek); mlt_properties_set_position(properties, "_jack_seek", -1); } else { mlt_log_error(NULL, "Failed to connect to JACK server\n"); mlt_filter_close(this); this = NULL; } } return this; } mlt-7.22.0/src/modules/jackrack/filter_jackrack.yml000664 000000 000000 00000003553 14531534050 022235 0ustar00rootroot000000 000000 schema_version: 0.3 type: filter identifier: jackrack title: JACK version: 1 copyright: Copyright (C) 2004-2018 Meltytech, LLC license: GPLv2 language: en url: http://www.ladspa.org/ creator: Dan Dennedy tags: - Audio description: Process audio using JACK. notes: > This can be used to receive audio from JACK by connecting only input ports. It can be used to output audio to JACK by connecting only the output ports. Or, you can use it as a filter with something like JACK Rack by connecting both output and input ports to send and receive. You can configure as many channels as you need and repeat the in_1/out_1 pattern for as many channels as you have configured. If you are using a MLT consumer that uses ALSA, then you should start jackd with the dummy driver, e.g.: jackd -ddummy -r48000 -p2048. The MLT JACK client name uses the format: mlt{pid}. bugs: - > MLT cannot automatically adapt to the sample rate at which JACK is configured. Please make sure they are configured the same. - Does not automatically reconfigure to the number of channels requested by consumer. - Some effects have a temporal side-effect that may not work well. parameters: - identifier: src title: JACK Rack file type: string argument: yes description: > Creates JACK ports and runs a JACK Rack project to process audio through a stack of LADSPA filters. - identifier: client_name title: JACK client name type: string argument: yes readonly: yes description: The generated name of the JACK client. - identifier: channels title: Channels type: integer minimum: 1 default: 2 - identifier: in_1 title: Receive L type: string - identifier: in_2 title: Receive R type: string - identifier: out_1 title: Send L type: string - identifier: out_2 title: Send R type: string mlt-7.22.0/src/modules/jackrack/filter_ladspa.c000664 000000 000000 00000024246 14531534050 021353 0ustar00rootroot000000 000000 /* * filter_ladspa.c -- filter audio through LADSPA plugins * Copyright (C) 2004-2018 Meltytech, LLC * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include #include "jack_rack.h" #define BUFFER_LEN (10000) #define MAX_SAMPLE_COUNT (4096) static jack_rack_t *initialise_jack_rack(mlt_properties properties, int channels) { jack_rack_t *jackrack = NULL; char *resource = mlt_properties_get(properties, "resource"); if (!resource && mlt_properties_get(properties, "src")) resource = mlt_properties_get(properties, "src"); // Start JackRack if (resource || mlt_properties_get_int64(properties, "_pluginid")) { // Create JackRack without Jack client name so that it only uses LADSPA jackrack = jack_rack_new(NULL, channels); mlt_properties_set_data(properties, "jackrack", jackrack, 0, (mlt_destructor) jack_rack_destroy, NULL); if (resource) // Load JACK Rack XML file jack_rack_open_file(jackrack, resource); else if (mlt_properties_get_int64(properties, "_pluginid")) { // Load one LADSPA plugin by its UniqueID unsigned long id = mlt_properties_get_int64(properties, "_pluginid"); plugin_desc_t *desc = plugin_mgr_get_any_desc(jackrack->plugin_mgr, id); plugin_t *plugin; if (desc && (plugin = jack_rack_instantiate_plugin(jackrack, desc))) { plugin->enabled = TRUE; process_add_plugin(jackrack->procinfo, plugin); mlt_properties_set_int(properties, "instances", plugin->copies); } else { mlt_log_error(properties, "failed to load plugin %lu\n", id); return jackrack; } if (plugin && plugin->desc && plugin->copies == 0) { // Calculate the number of channels that will work with this plugin int request_channels = plugin->desc->channels; while (request_channels < channels) request_channels += plugin->desc->channels; if (request_channels != channels) { // Try to load again with a compatible number of channels. mlt_log_warning( properties, "Not compatible with %d channels. Requesting %d channels instead.\n", channels, request_channels); jackrack = initialise_jack_rack(properties, request_channels); } else { mlt_log_error(properties, "Invalid plugin configuration: %lu\n", id); return jackrack; } } if (plugin && plugin->desc && plugin->copies) mlt_log_debug(properties, "Plugin Initialized. Channels: %lu\tCopies: %d\tTotal: %lu\n", plugin->desc->channels, plugin->copies, jackrack->channels); } } return jackrack; } /** Get the audio. */ static int ladspa_get_audio(mlt_frame frame, void **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples) { int error = 0; // Get the filter service mlt_filter filter = mlt_frame_pop_audio(frame); // Get the filter properties mlt_properties filter_properties = MLT_FILTER_PROPERTIES(filter); // Check if the channel configuration has changed int prev_channels = mlt_properties_get_int(filter_properties, "_prev_channels"); if (prev_channels != *channels) { if (prev_channels) { mlt_log_info(MLT_FILTER_SERVICE(filter), "Channel configuration changed. Old: %d New: %d.\n", prev_channels, *channels); mlt_properties_set_data(filter_properties, "jackrack", NULL, 0, (mlt_destructor) NULL, NULL); } mlt_properties_set_int(filter_properties, "_prev_channels", *channels); } // Initialise LADSPA if needed jack_rack_t *jackrack = mlt_properties_get_data(filter_properties, "jackrack", NULL); if (jackrack == NULL) { sample_rate = *frequency; // global inside jack_rack jackrack = initialise_jack_rack(filter_properties, *channels); } if (jackrack && jackrack->procinfo && jackrack->procinfo->chain && mlt_properties_get_int64(filter_properties, "_pluginid")) { plugin_t *plugin = jackrack->procinfo->chain; LADSPA_Data value; int i, c; mlt_position position = mlt_filter_get_position(filter, frame); mlt_position length = mlt_filter_get_length2(filter, frame); // Get the producer's audio *format = mlt_audio_float; mlt_frame_get_audio(frame, buffer, format, frequency, channels, samples); // Resize the buffer if necessary. if (*channels < jackrack->channels) { // Add extra channels to satisfy the plugin. // Extra channels in the buffer will be ignored by downstream services. int old_size = mlt_audio_format_size(*format, *samples, *channels); int new_size = mlt_audio_format_size(*format, *samples, jackrack->channels); uint8_t *new_buffer = mlt_pool_alloc(new_size); memcpy(new_buffer, *buffer, old_size); // Put silence in extra channels. memset(new_buffer + old_size, 0, new_size - old_size); mlt_frame_set_audio(frame, new_buffer, *format, new_size, mlt_pool_release); *buffer = new_buffer; } for (i = 0; i < plugin->desc->control_port_count; i++) { // Apply the control port values char key[20]; value = plugin_desc_get_default_control_value(plugin->desc, i, sample_rate); snprintf(key, sizeof(key), "%d", i); if (mlt_properties_get(filter_properties, key)) value = mlt_properties_anim_get_double(filter_properties, key, position, length); for (c = 0; c < plugin->copies; c++) plugin->holders[c].control_memory[i] = value; } plugin->wet_dry_enabled = mlt_properties_get(filter_properties, "wetness") != NULL; if (plugin->wet_dry_enabled) { value = mlt_properties_anim_get_double(filter_properties, "wetness", position, length); for (c = 0; c < jackrack->channels; c++) plugin->wet_dry_values[c] = value; } // Configure the buffers LADSPA_Data **input_buffers = mlt_pool_alloc(sizeof(LADSPA_Data *) * jackrack->channels); LADSPA_Data **output_buffers = mlt_pool_alloc(sizeof(LADSPA_Data *) * jackrack->channels); // Some plugins crash with too many frames (samples). // So, feed the plugin with N samples per loop iteration. int samples_offset = 0; int sample_count = MIN(*samples, MAX_SAMPLE_COUNT); for (i = 0; samples_offset < *samples; i++) { int j = 0; for (; j < jackrack->channels; j++) output_buffers[j] = input_buffers[j] = (LADSPA_Data *) *buffer + j * (*samples) + samples_offset; sample_count = MIN(*samples - samples_offset, MAX_SAMPLE_COUNT); // Do LADSPA processing error = process_ladspa(jackrack->procinfo, sample_count, input_buffers, output_buffers); samples_offset += MAX_SAMPLE_COUNT; } mlt_pool_release(input_buffers); mlt_pool_release(output_buffers); // read the status port values for (i = 0; i < plugin->desc->status_port_count; i++) { char key[20]; int p = plugin->desc->status_port_indicies[i]; for (c = 0; c < plugin->copies; c++) { snprintf(key, sizeof(key), "%d[%d]", p, c); value = plugin->holders[c].status_memory[i]; mlt_properties_set_double(filter_properties, key, value); } } } else { // Nothing to do. error = mlt_frame_get_audio(frame, buffer, format, frequency, channels, samples); } return error; } /** Filter processing. */ static mlt_frame filter_process(mlt_filter this, mlt_frame frame) { if (mlt_frame_is_test_audio(frame) == 0) { mlt_frame_push_audio(frame, this); mlt_frame_push_audio(frame, ladspa_get_audio); } return frame; } /** Constructor for the filter. */ mlt_filter filter_ladspa_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter this = mlt_filter_new(); if (this != NULL) { mlt_properties properties = MLT_FILTER_PROPERTIES(this); this->process = filter_process; mlt_properties_set(properties, "resource", arg); if (!strncmp(id, "ladspa.", 7)) mlt_properties_set(properties, "_pluginid", id + 7); } return this; } mlt-7.22.0/src/modules/jackrack/filter_ladspa.yml000664 000000 000000 00000001151 14531534050 021720 0ustar00rootroot000000 000000 schema_version: 7.0 type: filter identifier: ladspa title: LADSPA version: 1 license: GPLv2 language: en url: http://www.ladspa.org/ creator: Dan Dennedy tags: - Audio description: Process audio using LADSPA plugins. notes: > Automatically adapts to the number of channels and sampling rate of the consumer. bugs: - Some effects have a temporal side-effect that may not work well. parameters: - identifier: resource argument: yes title: JACK Rack XML file type: string description: > Runs a JACK Rack project to process audio through a stack of LADSPA filters without using JACK. mlt-7.22.0/src/modules/jackrack/jack_rack.c000664 000000 000000 00000026661 14531534050 020455 0ustar00rootroot000000 000000 /* * JACK Rack * * Original: * Copyright (C) Robert Ham 2002, 2003 (node@users.sourceforge.net) * * Modification for MLT: * Copyright (C) 2004-2014 Meltytech, LLC * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include #include #include #include #include #include #include #include "jack_rack.h" #include "lock_free_fifo.h" #include "plugin_settings.h" #include "framework/mlt_log.h" #ifndef _ #define _(x) x #endif #define _x (const xmlChar*) #define _s (const char*) extern plugin_mgr_t *g_jackrack_plugin_mgr; jack_rack_t * jack_rack_new (const char * client_name, unsigned long channels) { jack_rack_t *rack; rack = g_malloc (sizeof (jack_rack_t)); rack->saved_plugins = NULL; rack->channels = channels; rack->procinfo = process_info_new (client_name, channels, FALSE, FALSE); if (!rack->procinfo) { g_free (rack); return NULL; } rack->plugin_mgr = g_jackrack_plugin_mgr; plugin_mgr_set_plugins (rack->plugin_mgr, channels); return rack; } void jack_rack_destroy (jack_rack_t * jack_rack) { process_quit (jack_rack->procinfo); // plugin_mgr is shared and global now, so we do not destroy it with each instance // plugin_mgr_destroy (jack_rack->plugin_mgr); process_info_destroy (jack_rack->procinfo); g_slist_free (jack_rack->saved_plugins); g_free (jack_rack); } plugin_t * jack_rack_instantiate_plugin (jack_rack_t * jack_rack, plugin_desc_t * desc) { plugin_t * plugin; /* check whether or not the plugin is RT capable and confirm with the user if it isn't */ if (!LADSPA_IS_HARD_RT_CAPABLE(desc->properties)) { mlt_log_info( NULL, "Plugin not RT capable. The plugin '%s' does not describe itself as being capable of real-time operation. You may experience drop outs or jack may even kick us out if you use it.\n", desc->name); } /* create the plugin */ plugin = plugin_new (desc, jack_rack); if (!plugin) { mlt_log_error( NULL, "Error loading file plugin '%s' from file '%s'\n", desc->name, desc->object_file); } return plugin; } void jack_rack_add_saved_plugin (jack_rack_t * jack_rack, saved_plugin_t * saved_plugin) { plugin_t * plugin = jack_rack_instantiate_plugin (jack_rack, saved_plugin->settings->desc); if (!plugin) { mlt_log_warning( NULL, "%s: could not instantiate object file '%s'\n", __FUNCTION__, saved_plugin->settings->desc->object_file); return; } jack_rack->saved_plugins = g_slist_append (jack_rack->saved_plugins, saved_plugin); process_add_plugin (jack_rack->procinfo, plugin); jack_rack_add_plugin (jack_rack, plugin); } void jack_rack_add_plugin (jack_rack_t * jack_rack, plugin_t * plugin) { saved_plugin_t * saved_plugin = NULL; GSList * list; unsigned long control, channel; LADSPA_Data value; guint copy; /* see if there's any saved settings that match the plugin id */ for (list = jack_rack->saved_plugins; list; list = g_slist_next (list)) { saved_plugin = list->data; if (saved_plugin->settings->desc->id == plugin->desc->id) { /* process the settings! */ jack_rack->saved_plugins = g_slist_remove (jack_rack->saved_plugins, saved_plugin); break; } saved_plugin = NULL; } if ( !saved_plugin ) return; /* initialize plugin parameters */ plugin->enabled = settings_get_enabled (saved_plugin->settings); plugin->wet_dry_enabled = settings_get_wet_dry_enabled (saved_plugin->settings); for (control = 0; control < saved_plugin->settings->desc->control_port_count; control++) for (copy = 0; copy < plugin->copies; copy++) { value = settings_get_control_value (saved_plugin->settings, copy, control); plugin->holders[copy].control_memory[control] = value; //mlt_log_debug( NULL, "setting control value %s (%d) = %f\n", saved_plugin->settings->desc->port_names[control], copy, value); // lff_write (plugin->holders[copy].ui_control_fifos + control, &value); } if (plugin->wet_dry_enabled) for (channel = 0; channel < jack_rack->channels; channel++) { value = settings_get_wet_dry_value (saved_plugin->settings, channel); plugin->wet_dry_values[channel] = value; //mlt_log_debug( NULL, "setting wet/dry value %d = %f\n", channel, value); // lff_write (plugin->wet_dry_fifos + channel, &value); } } static void saved_rack_parse_plugin (jack_rack_t * jack_rack, saved_rack_t * saved_rack, saved_plugin_t * saved_plugin, const char * filename, xmlNodePtr plugin) { plugin_desc_t * desc; settings_t * settings = NULL; xmlNodePtr node; xmlNodePtr sub_node; xmlChar *content; unsigned long num; unsigned long control = 0; #ifdef _WIN32 xmlFreeFunc xmlFree = NULL; xmlMemGet( &xmlFree, NULL, NULL, NULL); #endif for (node = plugin->children; node; node = node->next) { if (xmlStrcmp (node->name, _x("id")) == 0) { content = xmlNodeGetContent (node); num = strtoul (_s(content), NULL, 10); xmlFree (content); desc = plugin_mgr_get_any_desc (jack_rack->plugin_mgr, num); if (!desc) { mlt_log_verbose( NULL, _("The file '%s' contains an unknown plugin with ID '%ld'; skipping\n"), filename, num); return; } settings = settings_new (desc, saved_rack->channels, saved_rack->sample_rate); } else if (xmlStrcmp (node->name, _x("enabled")) == 0) { content = xmlNodeGetContent (node); settings_set_enabled (settings, xmlStrcmp (content, _x("true")) == 0 ? TRUE : FALSE); xmlFree (content); } else if (xmlStrcmp (node->name, _x("wet_dry_enabled")) == 0) { content = xmlNodeGetContent (node); settings_set_wet_dry_enabled (settings, xmlStrcmp (content, _x("true")) == 0 ? TRUE : FALSE); xmlFree (content); } else if (xmlStrcmp (node->name, _x("wet_dry_locked")) == 0) { content = xmlNodeGetContent (node); settings_set_wet_dry_locked (settings, xmlStrcmp (content, _x("true")) == 0 ? TRUE : FALSE); xmlFree (content); } else if (xmlStrcmp (node->name, _x("wet_dry_values")) == 0) { unsigned long channel = 0; for (sub_node = node->children; sub_node; sub_node = sub_node->next) { if (xmlStrcmp (sub_node->name, _x("value")) == 0) { content = xmlNodeGetContent (sub_node); settings_set_wet_dry_value (settings, channel, strtod (_s(content), NULL)); xmlFree (content); channel++; } } } else if (xmlStrcmp (node->name, _x("lockall")) == 0) { content = xmlNodeGetContent (node); settings_set_lock_all (settings, xmlStrcmp (content, _x("true")) == 0 ? TRUE : FALSE); xmlFree (content); } else if (xmlStrcmp (node->name, _x("controlrow")) == 0) { gint copy = 0; for (sub_node = node->children; sub_node; sub_node = sub_node->next) { if (xmlStrcmp (sub_node->name, _x("lock")) == 0) { content = xmlNodeGetContent (sub_node); settings_set_lock (settings, control, xmlStrcmp (content, _x("true")) == 0 ? TRUE : FALSE); xmlFree (content); } else if (xmlStrcmp (sub_node->name, _x("value")) == 0) { content = xmlNodeGetContent (sub_node); settings_set_control_value (settings, copy, control, strtod (_s(content), NULL)); xmlFree (content); copy++; } } control++; } } if (settings) saved_plugin->settings = settings; } static void saved_rack_parse_jackrack (jack_rack_t * jack_rack, saved_rack_t * saved_rack, const char * filename, xmlNodePtr jackrack) { xmlNodePtr node; xmlChar *content; saved_plugin_t * saved_plugin; #ifdef _WIN32 xmlFreeFunc xmlFree = NULL; xmlMemGet( &xmlFree, NULL, NULL, NULL); #endif for (node = jackrack->children; node; node = node->next) { if (xmlStrcmp (node->name, _x("channels")) == 0) { content = xmlNodeGetContent (node); saved_rack->channels = strtoul (_s(content), NULL, 10); xmlFree (content); } else if (xmlStrcmp (node->name, _x("samplerate")) == 0) { content = xmlNodeGetContent (node); saved_rack->sample_rate = strtoul (_s(content), NULL, 10); xmlFree (content); } else if (xmlStrcmp (node->name, _x("plugin")) == 0) { saved_plugin = g_malloc0 (sizeof (saved_plugin_t)); saved_rack->plugins = g_slist_append (saved_rack->plugins, saved_plugin); saved_rack_parse_plugin (jack_rack, saved_rack, saved_plugin, filename, node); } } } static saved_rack_t * saved_rack_new (jack_rack_t * jack_rack, const char * filename, xmlDocPtr doc) { xmlNodePtr node; saved_rack_t *saved_rack; /* create the saved rack */ saved_rack = g_malloc (sizeof (saved_rack_t)); saved_rack->plugins = NULL; saved_rack->sample_rate = 48000; saved_rack->channels = 2; for (node = doc->children; node; node = node->next) { if (xmlStrcmp (node->name, _x("jackrack")) == 0) saved_rack_parse_jackrack (jack_rack, saved_rack, filename, node); } return saved_rack; } static void saved_rack_destroy (saved_rack_t * saved_rack) { GSList * list; for (list = saved_rack->plugins; list; list = g_slist_next (list)) settings_destroy (((saved_plugin_t *) list->data)->settings); g_slist_free (saved_rack->plugins); g_free (saved_rack); } int jack_rack_open_file (jack_rack_t * jack_rack, const char * filename) { xmlDocPtr doc; saved_rack_t * saved_rack; GSList * list; saved_plugin_t * saved_plugin; doc = xmlParseFile (filename); if (!doc) { mlt_log_error( NULL, _("Could not parse file '%s'\n"), filename); return 1; } if (xmlStrcmp ( ((xmlDtdPtr)doc->children)->name, _x("jackrack")) != 0) { mlt_log_error( NULL, _("The file '%s' is not a JACK Rack settings file\n"), filename); return 1; } saved_rack = saved_rack_new (jack_rack, filename, doc); xmlFreeDoc (doc); if (!saved_rack) return 1; for (list = saved_rack->plugins; list; list = g_slist_next (list)) { saved_plugin = list->data; settings_set_sample_rate (saved_plugin->settings, sample_rate); jack_rack_add_saved_plugin (jack_rack, saved_plugin); } saved_rack_destroy (saved_rack); return 0; } /* EOF */ mlt-7.22.0/src/modules/jackrack/jack_rack.h000664 000000 000000 00000003741 14531534050 020454 0ustar00rootroot000000 000000 /* * JACK Rack * * Original: * Copyright (C) Robert Ham 2002, 2003 (node@users.sourceforge.net) * * Modification for MLT: * Copyright (C) 2004-2014 Meltytech, LLC * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #ifndef __JR_JACK_RACK_H__ #define __JR_JACK_RACK_H__ #include #include #include "plugin.h" #include "plugin_mgr.h" #include "plugin_settings.h" #include "process.h" typedef struct _saved_plugin saved_plugin_t; struct _saved_plugin { settings_t *settings; }; typedef struct _saved_rack saved_rack_t; struct _saved_rack { unsigned long channels; jack_nframes_t sample_rate; GSList * plugins; }; typedef struct _jack_rack jack_rack_t; struct _jack_rack { plugin_mgr_t * plugin_mgr; process_info_t * procinfo; unsigned long channels; GSList * saved_plugins; }; jack_rack_t * jack_rack_new (const char * client_name, unsigned long channels); void jack_rack_destroy (jack_rack_t * jack_rack); int jack_rack_open_file (jack_rack_t * jack_rack, const char * filename); void jack_rack_add_plugin (jack_rack_t * jack_rack, plugin_t * plugin); void jack_rack_add_saved_plugin (jack_rack_t * jack_rack, struct _saved_plugin * saved_plugin); plugin_t * jack_rack_instantiate_plugin (jack_rack_t * jack_rack, plugin_desc_t * desc); #endif /* __JR_JACK_RACK_H__ */ mlt-7.22.0/src/modules/jackrack/lock_free_fifo.c000664 000000 000000 00000005753 14531534050 021500 0ustar00rootroot000000 000000 /* * JACK Rack * * Original: * Copyright (C) Robert Ham 2002, 2003 (node@users.sourceforge.net) * * Modification for MLT: * Copyright (C) 2004-2014 Meltytech, LLC * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include #include #include #include #include "lock_free_fifo.h" /** initialise a lock free fifo */ void lff_init (lff_t * lff, unsigned int size, size_t object_size) { lff->size = size; lff->object_size = object_size; lff->read_index = 0; lff->write_index = 0; lff->data = g_malloc (object_size * size); } lff_t * lff_new (unsigned int size, size_t object_size) { lff_t * lff; lff = g_malloc (sizeof (lff_t)); lff_init (lff, size, object_size); return lff; } void lff_free (lff_t * lff) { g_free (lff->data); } void lff_destroy (lff_t * lff) { lff_free (lff); g_free (lff); } /** read an element from the fifo into data. returns 0 on success, non-zero if there were no elements to read */ int lff_read (lff_t * lff, void * data) { if (lff->read_index == lff->write_index) { return -1; } else { memcpy (data, ((char *)lff->data) + (lff->read_index * lff->object_size), lff->object_size); lff->read_index++; if (lff->read_index >= lff->size) { lff->read_index = 0; } return 0; } } /** write an element from data to the fifo. returns 0 on success, non-zero if there was no space */ int lff_write (lff_t * lff, void * data) { static unsigned int ri; /* got to read read_index only once for safety */ ri = lff->read_index; /* lots of logic for when we're allowed to write to the fifo which basically boils down to "don't write if we're one element behind the read index" */ if ((ri > lff->write_index && ri - lff->write_index > 1) || (lff->write_index >= ri && lff->write_index != lff->size - 1) || (lff->write_index >= ri && lff->write_index == lff->size - 1 && ri != 0)) { /* if ((ri > lff->write_index && ri - lff->write_index > 1) || (lff->write_index >= ri && (lff->write_index != lff->size - 1 || ri != 0))) { */ memcpy (((char *)lff->data) + (lff->write_index * lff->object_size), data, lff->object_size); /* FIXME: is this safe? */ lff->write_index++; if (lff->write_index >= lff->size) { lff->write_index = 0; } return 0; } else { return -1; } } mlt-7.22.0/src/modules/jackrack/lock_free_fifo.h000664 000000 000000 00000003267 14531534050 021503 0ustar00rootroot000000 000000 /* * JACK Rack * * Original: * Copyright (C) Robert Ham 2002, 2003 (node@users.sourceforge.net) * * Modification for MLT: * Copyright (C) 2004-2014 Meltytech, LLC * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #ifndef __JLH_LOCK_FREE_FIFO_H__ #define __JLH_LOCK_FREE_FIFO_H__ /** lock free fifo ring buffer structure */ typedef struct lock_free_fifo { /** Size of the ringbuffer (in elements) */ unsigned int size; /** the memory containing the ringbuffer */ void * data; /** the size of an element */ size_t object_size; /** the current position of the reader */ unsigned int read_index; /** the current position of the writer */ unsigned int write_index; } lff_t; void lff_init (lff_t * lff, unsigned int size, size_t object_size); void lff_free (lff_t * lff); lff_t * lff_new (unsigned int size, size_t object_size); void lff_destroy (lff_t * lock_free_fifo); int lff_read (lff_t * lock_free_fifo, void * data); int lff_write (lff_t * lock_free_fifo, void * data); #endif /* __JLH_LOCK_FREE_FIFO_H__ */ mlt-7.22.0/src/modules/jackrack/plugin.c000664 000000 000000 00000037314 14531534050 020040 0ustar00rootroot000000 000000 /* * JACK Rack * * Original: * Copyright (C) Robert Ham 2002, 2003 (node@users.sourceforge.net) * * Modification for MLT: * Copyright (C) 2004-2021 Meltytech, LLC * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include #include #include #include #include #include #include "plugin.h" #include "jack_rack.h" #include "process.h" #include "framework/mlt_log.h" #define CONTROL_FIFO_SIZE 128 #ifdef WITH_JACK /* swap over the jack ports in two plugins */ static void plugin_swap_aux_ports (plugin_t * plugin, plugin_t * other) { guint copy; jack_port_t ** aux_ports_tmp; for (copy = 0; copy < plugin->copies; copy++) { aux_ports_tmp = other->holders[copy].aux_ports; other->holders[copy].aux_ports = plugin->holders[copy].aux_ports; plugin->holders[copy].aux_ports = aux_ports_tmp; } } #endif /** connect up the ladspa instance's input buffers to the previous plugin's audio memory. make sure to check that plugin->prev exists. */ void plugin_connect_input_ports (plugin_t * plugin, LADSPA_Data ** inputs) { gint copy; unsigned long channel; unsigned long rack_channel; if (!plugin || !inputs) return; rack_channel = 0; for (copy = 0; copy < plugin->copies; copy++) { for (channel = 0; channel < plugin->desc->channels; channel++) { plugin->descriptor-> connect_port (plugin->holders[copy].instance, plugin->desc->audio_input_port_indicies[channel], inputs[rack_channel]); rack_channel++; } } plugin->audio_input_memory = inputs; } /** connect up a plugin's output ports to its own audio_output_memory output memory */ void plugin_connect_output_ports (plugin_t * plugin) { gint copy; unsigned long channel; unsigned long rack_channel = 0; if (!plugin) return; for (copy = 0; copy < plugin->copies; copy++) { for (channel = 0; channel < plugin->desc->channels; channel++) { plugin->descriptor-> connect_port (plugin->holders[copy].instance, plugin->desc->audio_output_port_indicies[channel], plugin->audio_output_memory[rack_channel]); rack_channel++; } } } void process_add_plugin (process_info_t * procinfo, plugin_t * plugin) { /* sort out list pointers */ plugin->next = NULL; plugin->prev = procinfo->chain_end; if (procinfo->chain_end) procinfo->chain_end->next = plugin; else procinfo->chain = plugin; procinfo->chain_end = plugin; } /** remove a plugin from the chain */ plugin_t * process_remove_plugin (process_info_t * procinfo, plugin_t *plugin) { /* sort out chain pointers */ if (plugin->prev) plugin->prev->next = plugin->next; else procinfo->chain = plugin->next; if (plugin->next) plugin->next->prev = plugin->prev; else procinfo->chain_end = plugin->prev; #ifdef WITH_JACK /* sort out the aux ports */ if (procinfo->jack_client && plugin->desc->aux_channels > 0) { plugin_t * other; for (other = plugin->next; other; other = other->next) if (other->desc->id == plugin->desc->id) plugin_swap_aux_ports (plugin, other); } #endif return plugin; } /** enable/disable a plugin */ void process_ablise_plugin (process_info_t * procinfo, plugin_t *plugin, gboolean enable) { plugin->enabled = enable; } /** enable/disable a plugin */ void process_ablise_plugin_wet_dry (process_info_t * procinfo, plugin_t *plugin, gboolean enable) { plugin->wet_dry_enabled = enable; } /** move a plugin up or down one place in the chain */ void process_move_plugin (process_info_t * procinfo, plugin_t *plugin, gint up) { /* other plugins in the chain */ plugin_t *pp = NULL, *p, *n, *nn = NULL; /* note that we should never receive an illogical move request ie, there will always be at least 1 plugin before for an up request or 1 plugin after for a down request */ /* these are pointers to the plugins surrounding the specified one: { pp, p, plugin, n, nn } which makes things much clearer than tptr, tptr2 etc */ p = plugin->prev; if (p) pp = p->prev; n = plugin->next; if (n) nn = n->next; if (up) { if (!p) return; if (pp) pp->next = plugin; else procinfo->chain = plugin; p->next = n; p->prev = plugin; plugin->prev = pp; plugin->next = p; if (n) n->prev = p; else procinfo->chain_end = p; } else { if (!n) return; if (p) p->next = n; else procinfo->chain = n; n->prev = p; n->next = plugin; plugin->prev = n; plugin->next = nn; if (nn) nn->prev = plugin; else procinfo->chain_end = plugin; } #ifdef WITH_JACK if (procinfo->jack_client && plugin->desc->aux_channels > 0) { plugin_t * other; other = up ? plugin->next : plugin->prev; /* swap around the jack ports */ if (other->desc->id == plugin->desc->id) plugin_swap_aux_ports (plugin, other); } #endif } /** exchange an existing plugin for a newly created one */ plugin_t * process_change_plugin (process_info_t * procinfo, plugin_t *plugin, plugin_t * new_plugin) { new_plugin->next = plugin->next; new_plugin->prev = plugin->prev; if (plugin->prev) plugin->prev->next = new_plugin; else procinfo->chain = new_plugin; if (plugin->next) plugin->next->prev = new_plugin; else procinfo->chain_end = new_plugin; #ifdef WITH_JACK /* sort out the aux ports */ if (procinfo->jack_client && plugin->desc->aux_channels > 0) { plugin_t * other; for (other = plugin->next; other; other = other->next) if (other->desc->id == plugin->desc->id) plugin_swap_aux_ports (plugin, other); } #endif return plugin; } /****************************************** ************* non RT stuff *************** ******************************************/ static int plugin_open_plugin (plugin_desc_t * desc, void ** dl_handle_ptr, const LADSPA_Descriptor ** descriptor_ptr) { void * dl_handle; const char * dlerr; LADSPA_Descriptor_Function get_descriptor; /* clear the error report */ dlerror (); /* open the object file */ dl_handle = dlopen (desc->object_file, RTLD_NOW); dlerr = dlerror (); if (!dl_handle || dlerr) { if (!dlerr) dlerr = "unknown error"; mlt_log_warning( NULL, "%s: error opening shared object file '%s': %s\n", __FUNCTION__, desc->object_file, dlerr); return 1; } /* get the get_descriptor function */ get_descriptor = (LADSPA_Descriptor_Function) dlsym (dl_handle, "ladspa_descriptor"); dlerr = dlerror(); if (dlerr) { if (!dlerr) dlerr = "unknown error"; mlt_log_warning( NULL, "%s: error finding descriptor symbol in object file '%s': %s\n", __FUNCTION__, desc->object_file, dlerr); dlclose (dl_handle); return 1; } #ifdef __APPLE__ if (!get_descriptor (desc->index)) { void (*constructor)(void) = dlsym (dl_handle, "_init"); if (constructor) constructor(); } #endif *descriptor_ptr = get_descriptor (desc->index); if (!*descriptor_ptr) { mlt_log_warning( NULL, "%s: error finding index %lu in object file '%s'\n", __FUNCTION__, desc->index, desc->object_file); dlclose (dl_handle); return 1; } *dl_handle_ptr = dl_handle; return 0; } static int plugin_instantiate (const LADSPA_Descriptor * descriptor, unsigned long plugin_index, gint copies, LADSPA_Handle * instances) { gint i; for (i = 0; i < copies; i++) { instances[i] = descriptor->instantiate (descriptor, sample_rate); if (!instances[i]) { unsigned long d; for (d = 0; d < i; d++) descriptor->cleanup (instances[d]); return 1; } } return 0; } #ifdef WITH_JACK static void plugin_create_aux_ports (plugin_t * plugin, guint copy, jack_rack_t * jack_rack) { plugin_desc_t * desc; // plugin_slot_t * slot; unsigned long aux_channel = 1; unsigned long plugin_index = 1; unsigned long i; char port_name[64]; char * plugin_name; char * ptr; // GList * list; ladspa_holder_t * holder; desc = plugin->desc; holder = plugin->holders + copy; holder->aux_ports = g_malloc (sizeof (jack_port_t *) * desc->aux_channels); /* make the plugin name jack worthy */ ptr = plugin_name = g_strndup (plugin->desc->name, 7); while (*ptr != '\0') { if (*ptr == ' ') *ptr = '_'; else *ptr = tolower (*ptr); ptr++; } /* for (list = jack_rack->slots; list; list = g_list_next (list)) { slot = (plugin_slot_t *) list->data; if (slot->plugin->desc->id == plugin->desc->id) plugin_index++; } */ for (i = 0; i < desc->aux_channels; i++, aux_channel++) { sprintf (port_name, "%s_%ld-%d_%c%ld", plugin_name, plugin_index, copy + 1, desc->aux_are_input ? 'i' : 'o', aux_channel); holder->aux_ports[i] = jack_port_register (jack_rack->procinfo->jack_client, port_name, JACK_DEFAULT_AUDIO_TYPE, desc->aux_are_input ? JackPortIsInput : JackPortIsOutput, 0); if (!holder->aux_ports[i]) { mlt_log_panic( NULL, "Could not register jack port '%s'; aborting\n", port_name); } } g_free (plugin_name); } #endif static void plugin_init_holder (plugin_t * plugin, guint copy, LADSPA_Handle instance, jack_rack_t * jack_rack) { unsigned long i; plugin_desc_t * desc; ladspa_holder_t * holder; desc = plugin->desc; holder = plugin->holders + copy; holder->instance = instance; if (desc->control_port_count > 0) { holder->ui_control_fifos = g_malloc (sizeof (lff_t) * desc->control_port_count); holder->control_memory = g_malloc (sizeof (LADSPA_Data) * desc->control_port_count); } else { holder->ui_control_fifos = NULL; holder->control_memory = NULL; } for (i = 0; i < desc->control_port_count; i++) { lff_init (holder->ui_control_fifos + i, CONTROL_FIFO_SIZE, sizeof (LADSPA_Data)); holder->control_memory[i] = plugin_desc_get_default_control_value (desc, desc->control_port_indicies[i], sample_rate); plugin->descriptor-> connect_port (instance, desc->control_port_indicies[i], holder->control_memory + i); } if (desc->status_port_count > 0) { holder->status_memory = g_malloc (sizeof (LADSPA_Data) * desc->status_port_count); } else { holder->status_memory = NULL; } for (i = 0; i < desc->status_port_count; i++) { plugin->descriptor-> connect_port (instance, desc->status_port_indicies[i], holder->status_memory + i); } #ifdef WITH_JACK if (jack_rack->procinfo->jack_client && plugin->desc->aux_channels > 0) plugin_create_aux_ports (plugin, copy, jack_rack); #endif if (plugin->descriptor->activate) plugin->descriptor->activate (instance); } plugin_t * plugin_new (plugin_desc_t * desc, jack_rack_t * jack_rack) { void * dl_handle; const LADSPA_Descriptor * descriptor; LADSPA_Handle * instances; gint copies; unsigned long i; int err; plugin_t * plugin; /* open the plugin */ err = plugin_open_plugin (desc, &dl_handle, &descriptor); if (err) return NULL; /* create the instances */ copies = plugin_desc_get_copies (desc, jack_rack->channels); instances = g_malloc (sizeof (LADSPA_Handle) * copies); err = plugin_instantiate (descriptor, desc->index, copies, instances); if (err) { g_free (instances); dlclose (dl_handle); return NULL; } plugin = g_malloc (sizeof (plugin_t)); plugin->descriptor = descriptor; plugin->dl_handle = dl_handle; plugin->desc = desc; plugin->copies = copies; plugin->enabled = FALSE; plugin->next = NULL; plugin->prev = NULL; plugin->wet_dry_enabled = FALSE; plugin->jack_rack = jack_rack; /* create audio memory and wet/dry stuff */ plugin->audio_output_memory = g_malloc (sizeof (LADSPA_Data *) * jack_rack->channels); plugin->wet_dry_fifos = g_malloc (sizeof (lff_t) * jack_rack->channels); plugin->wet_dry_values = g_malloc (sizeof (LADSPA_Data) * jack_rack->channels); for (i = 0; i < jack_rack->channels; i++) { plugin->audio_output_memory[i] = g_malloc (sizeof (LADSPA_Data) * buffer_size); lff_init (plugin->wet_dry_fifos + i, CONTROL_FIFO_SIZE, sizeof (LADSPA_Data)); plugin->wet_dry_values[i] = 1.0; } /* create holders and fill them out */ plugin->holders = g_malloc (sizeof (ladspa_holder_t) * copies); for (i = 0; i < copies; i++) plugin_init_holder (plugin, i, instances[i], jack_rack); return plugin; } void plugin_destroy (plugin_t * plugin) { unsigned long i, j; int err; /* destroy holders */ for (i = 0; i < plugin->copies; i++) { if (plugin->descriptor->deactivate) plugin->descriptor->deactivate (plugin->holders[i].instance); /* if (plugin->descriptor->cleanup) plugin->descriptor->cleanup (plugin->holders[i].instance); */ if (plugin->desc->control_port_count > 0) { for (j = 0; j < plugin->desc->control_port_count; j++) { lff_free (plugin->holders[i].ui_control_fifos + j); } g_free (plugin->holders[i].ui_control_fifos); g_free (plugin->holders[i].control_memory); } if (plugin->desc->status_port_count > 0) { g_free (plugin->holders[i].status_memory); } #ifdef WITH_JACK /* aux ports */ if (plugin->jack_rack->procinfo->jack_client && plugin->desc->aux_channels > 0) { for (j = 0; j < plugin->desc->aux_channels; j++) { err = jack_port_unregister (plugin->jack_rack->procinfo->jack_client, plugin->holders[i].aux_ports[j]); if (err) mlt_log_warning( NULL, "%s: could not unregister jack port\n", __FUNCTION__); } g_free (plugin->holders[i].aux_ports); } #endif } g_free (plugin->holders); for (i = 0; i < plugin->jack_rack->channels; i++) { g_free (plugin->audio_output_memory[i]); lff_free (plugin->wet_dry_fifos + i); } g_free (plugin->audio_output_memory); g_free (plugin->wet_dry_fifos); g_free (plugin->wet_dry_values); err = dlclose (plugin->dl_handle); if (err) { mlt_log_warning( NULL, "%s: error closing shared object '%s': %s\n", __FUNCTION__, plugin->desc->object_file, dlerror ()); } g_free (plugin); } /* EOF */ mlt-7.22.0/src/modules/jackrack/plugin.h000664 000000 000000 00000005521 14531534050 020040 0ustar00rootroot000000 000000 /* * JACK Rack * * Original: * Copyright (C) Robert Ham 2002, 2003 (node@users.sourceforge.net) * * Modification for MLT: * Copyright (C) 2004-2021 Meltytech, LLC * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #ifndef __JR_PLUGIN_H__ #define __JR_PLUGIN_H__ #include #include #include #ifdef WITH_JACK #include #endif #include "process.h" #include "plugin_desc.h" typedef struct _ladspa_holder ladspa_holder_t; typedef struct _plugin plugin_t; struct _ladspa_holder { LADSPA_Handle instance; lff_t * ui_control_fifos; LADSPA_Data * control_memory; LADSPA_Data * status_memory; #ifdef WITH_JACK jack_port_t ** aux_ports; #endif }; struct _plugin { plugin_desc_t * desc; gint enabled; gint copies; ladspa_holder_t * holders; LADSPA_Data ** audio_input_memory; LADSPA_Data ** audio_output_memory; gboolean wet_dry_enabled; /* 1.0 = all wet, 0.0 = all dry, 0.5 = 50% wet/50% dry */ LADSPA_Data * wet_dry_values; lff_t * wet_dry_fifos; plugin_t * next; plugin_t * prev; const LADSPA_Descriptor * descriptor; void * dl_handle; struct _jack_rack * jack_rack; }; void process_add_plugin (process_info_t *, plugin_t *plugin); plugin_t * process_remove_plugin (process_info_t *, plugin_t *plugin); void process_ablise_plugin (process_info_t *, plugin_t *plugin, gboolean able); void process_ablise_plugin_wet_dry (process_info_t *, plugin_t *plugin, gboolean enable); void process_move_plugin (process_info_t *, plugin_t *plugin, gint up); plugin_t * process_change_plugin (process_info_t *, plugin_t *plugin, plugin_t * new_plugin); struct _jack_rack; struct _ui; plugin_t * plugin_new (plugin_desc_t * plugin_desc, struct _jack_rack * jack_rack); void plugin_destroy (plugin_t * plugin); void plugin_connect_input_ports (plugin_t * plugin, LADSPA_Data ** inputs); void plugin_connect_output_ports (plugin_t * plugin); #endif /* __JR_PLUGIN_H__ */ mlt-7.22.0/src/modules/jackrack/plugin_desc.c000664 000000 000000 00000031353 14531534050 021033 0ustar00rootroot000000 000000 /* * JACK Rack * * Original: * Copyright (C) Robert Ham 2002, 2003 (node@users.sourceforge.net) * * Modification for MLT: * Copyright (C) 2004-2014 Meltytech, LLC * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include #include #include #include "plugin_desc.h" #include "plugin.h" #define set_string_property(property, value) \ \ if (property) \ g_free (property); \ \ if (value) \ (property) = g_strdup (value); \ else \ (property) = NULL; void plugin_desc_set_ports (plugin_desc_t * pd, unsigned long port_count, const LADSPA_PortDescriptor * port_descriptors, const LADSPA_PortRangeHint * port_range_hints, const char * const * port_names); static void plugin_desc_init (plugin_desc_t * pd) { pd->object_file = NULL; pd->id = 0; pd->name = NULL; pd->maker = NULL; pd->properties = 0; pd->channels = 0; pd->port_count = 0; pd->port_descriptors = NULL; pd->port_range_hints = NULL; pd->audio_input_port_indicies = NULL; pd->audio_output_port_indicies = NULL; pd->audio_aux_port_indicies = NULL; pd->control_port_count = 0; pd->control_port_indicies = NULL; pd->status_port_count = 0; pd->status_port_indicies = NULL; pd->aux_channels = 0; pd->aux_are_input = TRUE; pd->has_input = TRUE; } static void plugin_desc_free_ports (plugin_desc_t * pd) { if (pd->port_count) { g_free (pd->port_descriptors); g_free (pd->port_range_hints); g_free (pd->audio_input_port_indicies); g_free (pd->audio_output_port_indicies); g_free (pd->port_names); g_free (pd->control_port_indicies); g_free (pd->status_port_indicies); g_free (pd->audio_aux_port_indicies); pd->port_descriptors = NULL; pd->port_range_hints = NULL; pd->audio_input_port_indicies = NULL; pd->audio_output_port_indicies = NULL; pd->port_names = NULL; pd->control_port_indicies = NULL; pd->status_port_indicies = NULL; pd->audio_aux_port_indicies = NULL; pd->port_count = 0; } } static void plugin_desc_free (plugin_desc_t * pd) { plugin_desc_set_object_file (pd, NULL); plugin_desc_set_name (pd, NULL); plugin_desc_set_maker (pd, NULL); plugin_desc_free_ports (pd); } plugin_desc_t * plugin_desc_new () { plugin_desc_t * pd; pd = g_malloc (sizeof (plugin_desc_t)); plugin_desc_init (pd); return pd; } plugin_desc_t * plugin_desc_new_with_descriptor (const char * object_file, unsigned long index, const LADSPA_Descriptor * descriptor) { plugin_desc_t * pd; pd = plugin_desc_new (); plugin_desc_set_object_file (pd, object_file); plugin_desc_set_index (pd, index); plugin_desc_set_id (pd, descriptor->UniqueID); plugin_desc_set_name (pd, descriptor->Name); plugin_desc_set_maker (pd, descriptor->Maker); plugin_desc_set_properties (pd, descriptor->Properties); plugin_desc_set_ports (pd, descriptor->PortCount, descriptor->PortDescriptors, descriptor->PortRangeHints, descriptor->PortNames); pd->rt = LADSPA_IS_HARD_RT_CAPABLE(pd->properties) ? TRUE : FALSE; return pd; } void plugin_desc_destroy (plugin_desc_t * pd) { plugin_desc_free (pd); g_free (pd); } void plugin_desc_set_object_file (plugin_desc_t * pd, const char * object_file) { set_string_property (pd->object_file, object_file); } void plugin_desc_set_index (plugin_desc_t * pd, unsigned long index) { pd->index = index; } void plugin_desc_set_id (plugin_desc_t * pd, unsigned long id) { pd->id = id; } void plugin_desc_set_name (plugin_desc_t * pd, const char * name) { set_string_property (pd->name, name); } void plugin_desc_set_maker (plugin_desc_t * pd, const char * maker) { set_string_property (pd->maker, maker); } void plugin_desc_set_properties (plugin_desc_t * pd, LADSPA_Properties properties) { pd->properties = properties; } static void plugin_desc_add_audio_port_index (unsigned long ** indices, unsigned long * current_port_count, unsigned long index) { (*current_port_count)++; if (*current_port_count == 0) *indices = g_malloc (sizeof (unsigned long) * *current_port_count); else *indices = g_realloc (*indices, sizeof (unsigned long) * *current_port_count); (*indices)[*current_port_count - 1] = index; } static void plugin_desc_set_port_counts (plugin_desc_t * pd) { unsigned long i; unsigned long icount = 0; unsigned long ocount = 0; for (i = 0; i < pd->port_count; i++) { if (LADSPA_IS_PORT_AUDIO (pd->port_descriptors[i])) { if (LADSPA_IS_PORT_INPUT (pd->port_descriptors[i])) plugin_desc_add_audio_port_index (&pd->audio_input_port_indicies, &icount, i); else plugin_desc_add_audio_port_index (&pd->audio_output_port_indicies, &ocount, i); } else { if (LADSPA_IS_PORT_OUTPUT (pd->port_descriptors[i])) { pd->status_port_count++; if (pd->status_port_count == 0) pd->status_port_indicies = g_malloc (sizeof (unsigned long) * pd->status_port_count); else pd->status_port_indicies = g_realloc (pd->status_port_indicies, sizeof (unsigned long) * pd->status_port_count); pd->status_port_indicies[pd->status_port_count - 1] = i; } else { pd->control_port_count++; if (pd->control_port_count == 0) pd->control_port_indicies = g_malloc (sizeof (unsigned long) * pd->control_port_count); else pd->control_port_indicies = g_realloc (pd->control_port_indicies, sizeof (unsigned long) * pd->control_port_count); pd->control_port_indicies[pd->control_port_count - 1] = i; } } } if (icount == ocount) pd->channels = icount; else if( icount == 0 ) { pd->channels = ocount; pd->has_input = FALSE; } else { /* deal with auxiliary ports */ unsigned long ** port_indicies; unsigned long port_count; unsigned long i, j; if (icount > ocount) { pd->channels = ocount; pd->aux_channels = icount - ocount; pd->aux_are_input = TRUE; port_indicies = &pd->audio_input_port_indicies; port_count = icount; } else { pd->channels = icount; pd->aux_channels = ocount - icount; pd->aux_are_input = FALSE; port_indicies = &pd->audio_output_port_indicies; port_count = ocount; } /* allocate indices */ pd->audio_aux_port_indicies = g_malloc (sizeof (unsigned long) * pd->aux_channels); /* copy indices */ for (i = pd->channels, j = 0; i < port_count; i++, j++) pd->audio_aux_port_indicies[j] = (*port_indicies)[i]; /* shrink the main indices to only have channels indices */ *port_indicies = g_realloc (*port_indicies, sizeof (unsigned long) * pd->channels); } } void plugin_desc_set_ports (plugin_desc_t * pd, unsigned long port_count, const LADSPA_PortDescriptor * port_descriptors, const LADSPA_PortRangeHint * port_range_hints, const char * const * port_names) { unsigned long i; plugin_desc_free_ports (pd); if (!port_count) return; pd->port_count = port_count; pd->port_descriptors = g_malloc (sizeof (LADSPA_PortDescriptor) * port_count); pd->port_range_hints = g_malloc (sizeof (LADSPA_PortRangeHint) * port_count); pd->port_names = g_malloc (sizeof (char *) * port_count); memcpy (pd->port_descriptors, port_descriptors, sizeof (LADSPA_PortDescriptor) * port_count); memcpy (pd->port_range_hints, port_range_hints, sizeof (LADSPA_PortRangeHint) * port_count); for (i = 0; i < port_count; i++) pd->port_names[i] = g_strdup (port_names[i]); plugin_desc_set_port_counts (pd); } LADSPA_Data plugin_desc_get_default_control_value (plugin_desc_t * pd, unsigned long port_index, guint32 sample_rate) { LADSPA_Data upper, lower; LADSPA_PortRangeHintDescriptor hint_descriptor; hint_descriptor = pd->port_range_hints[port_index].HintDescriptor; /* set upper and lower, possibly adjusted to the sample rate */ if (LADSPA_IS_HINT_SAMPLE_RATE(hint_descriptor)) { upper = pd->port_range_hints[port_index].UpperBound * (LADSPA_Data) sample_rate; lower = pd->port_range_hints[port_index].LowerBound * (LADSPA_Data) sample_rate; } else { upper = pd->port_range_hints[port_index].UpperBound; lower = pd->port_range_hints[port_index].LowerBound; } if (LADSPA_IS_HINT_LOGARITHMIC(hint_descriptor)) { if (lower < FLT_EPSILON) lower = FLT_EPSILON; } if (LADSPA_IS_HINT_HAS_DEFAULT(hint_descriptor)) { if (LADSPA_IS_HINT_DEFAULT_MINIMUM(hint_descriptor)) { return lower; } else if (LADSPA_IS_HINT_DEFAULT_LOW(hint_descriptor)) { if (LADSPA_IS_HINT_LOGARITHMIC(hint_descriptor)) { return exp(log(lower) * 0.75 + log(upper) * 0.25); } else { return lower * 0.75 + upper * 0.25; } } else if (LADSPA_IS_HINT_DEFAULT_MIDDLE(hint_descriptor)) { if (LADSPA_IS_HINT_LOGARITHMIC(hint_descriptor)) { return exp(log(lower) * 0.5 + log(upper) * 0.5); } else { return lower * 0.5 + upper * 0.5; } } else if (LADSPA_IS_HINT_DEFAULT_HIGH(hint_descriptor)) { if (LADSPA_IS_HINT_LOGARITHMIC(hint_descriptor)) { return exp(log(lower) * 0.25 + log(upper) * 0.75); } else { return lower * 0.25 + upper * 0.75; } } else if (LADSPA_IS_HINT_DEFAULT_MAXIMUM(hint_descriptor)) { return upper; } else if (LADSPA_IS_HINT_DEFAULT_0(hint_descriptor)) { return 0.0; } else if (LADSPA_IS_HINT_DEFAULT_1(hint_descriptor)) { if (LADSPA_IS_HINT_SAMPLE_RATE(hint_descriptor)) { return (LADSPA_Data) sample_rate; } else { return 1.0; } } else if (LADSPA_IS_HINT_DEFAULT_100(hint_descriptor)) { if (LADSPA_IS_HINT_SAMPLE_RATE(hint_descriptor)) { return 100.0 * (LADSPA_Data) sample_rate; } else { return 100.0; } } else if (LADSPA_IS_HINT_DEFAULT_440(hint_descriptor)) { if (LADSPA_IS_HINT_SAMPLE_RATE(hint_descriptor)) { return 440.0 * (LADSPA_Data) sample_rate; } else { return 440.0; } } } else { /* try and find a reasonable default */ if (LADSPA_IS_HINT_BOUNDED_BELOW(hint_descriptor)) { return lower; } else if (LADSPA_IS_HINT_BOUNDED_ABOVE(hint_descriptor)) { return upper; } } return 0.0; } LADSPA_Data plugin_desc_change_control_value (plugin_desc_t * pd, unsigned long control_index, LADSPA_Data value, guint32 old_sample_rate, guint32 new_sample_rate) { if (LADSPA_IS_HINT_SAMPLE_RATE (pd->port_range_hints[control_index].HintDescriptor)) { LADSPA_Data old_sr, new_sr; old_sr = (LADSPA_Data) old_sample_rate; new_sr = (LADSPA_Data) new_sample_rate; value /= old_sr; value *= new_sr; } return value; } gint plugin_desc_get_copies (plugin_desc_t * pd, unsigned long rack_channels) { gint copies = 1; if (pd->channels > rack_channels) return 0; while (pd->channels * copies < rack_channels) copies++; if (pd->channels * copies > rack_channels) return 0; return copies; } /* EOF */ mlt-7.22.0/src/modules/jackrack/plugin_desc.h000664 000000 000000 00000006155 14531534050 021042 0ustar00rootroot000000 000000 /* * JACK Rack * * Original: * Copyright (C) Robert Ham 2002, 2003 (node@users.sourceforge.net) * * Modification for MLT: * Copyright (C) 2004-2014 Meltytech, LLC * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #ifndef __JR_PLUGIN_DESC_H__ #define __JR_PLUGIN_DESC_H__ #include #include typedef struct _plugin_desc plugin_desc_t; struct _plugin_desc { char * object_file; unsigned long index; unsigned long id; char * name; char * maker; LADSPA_Properties properties; gboolean rt; unsigned long channels; gboolean aux_are_input; unsigned long aux_channels; unsigned long port_count; LADSPA_PortDescriptor * port_descriptors; LADSPA_PortRangeHint * port_range_hints; char ** port_names; unsigned long * audio_input_port_indicies; unsigned long * audio_output_port_indicies; unsigned long * audio_aux_port_indicies; unsigned long control_port_count; unsigned long * control_port_indicies; unsigned long status_port_count; unsigned long * status_port_indicies; gboolean has_input; }; plugin_desc_t * plugin_desc_new (); plugin_desc_t * plugin_desc_new_with_descriptor (const char * object_file, unsigned long index, const LADSPA_Descriptor * descriptor); void plugin_desc_destroy (plugin_desc_t * pd); void plugin_desc_set_object_file (plugin_desc_t * pd, const char * object_file); void plugin_desc_set_index (plugin_desc_t * pd, unsigned long index); void plugin_desc_set_id (plugin_desc_t * pd, unsigned long id); void plugin_desc_set_name (plugin_desc_t * pd, const char * name); void plugin_desc_set_maker (plugin_desc_t * pd, const char * maker); void plugin_desc_set_properties (plugin_desc_t * pd, LADSPA_Properties properties); struct _plugin * plugin_desc_instantiate (plugin_desc_t * pd); LADSPA_Data plugin_desc_get_default_control_value (plugin_desc_t * pd, unsigned long port_index, guint32 sample_rate); LADSPA_Data plugin_desc_change_control_value (plugin_desc_t *, unsigned long, LADSPA_Data, guint32, guint32); gint plugin_desc_get_copies (plugin_desc_t * pd, unsigned long rack_channels); #endif /* __JR_PLUGIN_DESC_H__ */ mlt-7.22.0/src/modules/jackrack/plugin_mgr.c000664 000000 000000 00000022205 14531534050 020676 0ustar00rootroot000000 000000 /* * JACK Rack * * Original: * Copyright (C) Robert Ham 2002, 2003 (node@users.sourceforge.net) * * Modifications for MLT: * Copyright (C) 2004-2016 Meltytech, LLC * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "plugin_mgr.h" #include "plugin_desc.h" #include "framework/mlt_log.h" #include "framework/mlt_factory.h" static gboolean plugin_is_valid (const LADSPA_Descriptor * descriptor) { unsigned long i; unsigned long icount = 0; unsigned long ocount = 0; for (i = 0; i < descriptor->PortCount; i++) { if (!LADSPA_IS_PORT_AUDIO (descriptor->PortDescriptors[i])) continue; if (LADSPA_IS_PORT_INPUT (descriptor->PortDescriptors[i])) icount++; else ocount++; } if (ocount == 0) return FALSE; return TRUE; } static void plugin_mgr_get_object_file_plugins (plugin_mgr_t * plugin_mgr, const char * filename) { const char * dlerr; void * dl_handle; LADSPA_Descriptor_Function get_descriptor; const LADSPA_Descriptor * descriptor; unsigned long plugin_index; plugin_desc_t * desc, * other_desc = NULL; GSList * list; gboolean exists; int err; /* open the object file */ dl_handle = dlopen (filename, RTLD_LAZY); if (!dl_handle) { mlt_log_info( NULL, "%s: error opening shared object file '%s': %s\n", __FUNCTION__, filename, dlerror()); return; } /* get the get_descriptor function */ dlerror (); /* clear the error report */ get_descriptor = (LADSPA_Descriptor_Function) dlsym (dl_handle, "ladspa_descriptor"); dlerr = dlerror(); if (dlerr) { mlt_log_info( NULL, "%s: error finding ladspa_descriptor symbol in object file '%s': %s\n", __FUNCTION__, filename, dlerr); dlclose (dl_handle); return; } #ifdef __APPLE__ if (!get_descriptor (0)) { void (*constructor)(void) = dlsym (dl_handle, "_init"); if (constructor) constructor(); } #endif plugin_index = 0; while ( (descriptor = get_descriptor (plugin_index)) ) { if (!plugin_is_valid (descriptor)) { plugin_index++; continue; } /* check it doesn't already exist */ exists = FALSE; for (list = plugin_mgr->all_plugins; list; list = g_slist_next (list)) { other_desc = (plugin_desc_t *) list->data; if (other_desc->id == descriptor->UniqueID) { exists = TRUE; break; } } if (exists) { mlt_log_info( NULL, "Plugin %ld exists in both '%s' and '%s'; using version in '%s'\n", descriptor->UniqueID, other_desc->object_file, filename, other_desc->object_file); plugin_index++; continue; } desc = plugin_desc_new_with_descriptor (filename, plugin_index, descriptor); plugin_mgr->all_plugins = g_slist_append (plugin_mgr->all_plugins, desc); plugin_index++; plugin_mgr->plugin_count++; /* print in the splash screen */ /* mlt_log_verbose( NULL, "Loaded plugin '%s'\n", desc->name); */ } err = dlclose (dl_handle); if (err) { mlt_log_warning( NULL, "%s: error closing object file '%s': %s\n", __FUNCTION__, filename, dlerror ()); } } static void plugin_mgr_get_dir_plugins (plugin_mgr_t * plugin_mgr, const char * dir) { DIR * dir_stream; struct dirent * dir_entry; char * file_name; int err; size_t dirlen; dir_stream = opendir (dir); if (!dir_stream) { /* mlt_log_warning( NULL, "%s: error opening directory '%s': %s\n", __FUNCTION__, dir, strerror (errno)); */ return; } dirlen = strlen (dir); while ( (dir_entry = readdir (dir_stream)) ) { struct stat info; if (strcmp (dir_entry->d_name, ".") == 0 || mlt_properties_get (plugin_mgr->blacklist, dir_entry->d_name) || strcmp (dir_entry->d_name, "..") == 0) continue; file_name = g_malloc (dirlen + 1 + strlen (dir_entry->d_name) + 1); strcpy (file_name, dir); if (file_name[dirlen - 1] == '/') strcpy (file_name + dirlen, dir_entry->d_name); else { file_name[dirlen] = '/'; strcpy (file_name + dirlen + 1, dir_entry->d_name); } stat (file_name, &info); if (S_ISDIR (info.st_mode)) plugin_mgr_get_dir_plugins (plugin_mgr, file_name); else { char * ext = strrchr(file_name, '.'); if (ext && (strcmp(ext, ".so") == 0 || strcmp(ext, ".dll") == 0 || strcmp(ext, ".dylib") == 0)) { plugin_mgr_get_object_file_plugins (plugin_mgr, file_name); } } g_free (file_name); } err = closedir (dir_stream); if (err) mlt_log_warning( NULL, "%s: error closing directory '%s': %s\n", __FUNCTION__, dir, strerror (errno)); } static void plugin_mgr_get_path_plugins (plugin_mgr_t * plugin_mgr) { char * ladspa_path, * dir; ladspa_path = g_strdup (getenv ("LADSPA_PATH")); #ifdef _WIN32 if (!ladspa_path) { ladspa_path = malloc (strlen (mlt_environment("MLT_APPDIR")) + strlen ("\\lib\\ladspa") + 1); strcpy (ladspa_path, mlt_environment("MLT_APPDIR")); strcat (ladspa_path, "\\lib\\ladspa"); } #elif defined(RELOCATABLE) # ifdef __APPLE__ # define LADSPA_SUBDIR "/PlugIns/ladspa" # else # define LADSPA_SUBDIR "/lib/ladspa" # endif { ladspa_path = malloc( strlen (mlt_environment ("MLT_APPDIR")) + strlen (LADSPA_SUBDIR) + 1 ); strcpy (ladspa_path, mlt_environment ("MLT_APPDIR")); strcat (ladspa_path, LADSPA_SUBDIR ); } #else if (!ladspa_path) ladspa_path = g_strdup ("/usr/local/lib/ladspa:/usr/lib/ladspa:/usr/lib64/ladspa"); #endif for (dir = strtok (ladspa_path, MLT_DIRLIST_DELIMITER); dir; dir = strtok (NULL, MLT_DIRLIST_DELIMITER)) plugin_mgr_get_dir_plugins (plugin_mgr, dir); g_free (ladspa_path); } static gint plugin_mgr_sort (gconstpointer a, gconstpointer b) { const plugin_desc_t * da; const plugin_desc_t * db; da = (const plugin_desc_t *) a; db = (const plugin_desc_t *) b; return strcasecmp (da->name, db->name); } plugin_mgr_t * plugin_mgr_new () { plugin_mgr_t * pm; char dirname[PATH_MAX]; pm = g_malloc (sizeof (plugin_mgr_t)); pm->all_plugins = NULL; pm->plugins = NULL; pm->plugin_count = 0; snprintf (dirname, PATH_MAX, "%s/jackrack/blacklist.txt", mlt_environment ("MLT_DATA")); pm->blacklist = mlt_properties_load (dirname); plugin_mgr_get_path_plugins (pm); if (!pm->all_plugins) mlt_log_warning( NULL, "No LADSPA plugins were found!\n\nCheck your LADSPA_PATH environment variable.\n"); else pm->all_plugins = g_slist_sort (pm->all_plugins, plugin_mgr_sort); return pm; } void plugin_mgr_destroy (plugin_mgr_t * plugin_mgr) { GSList * list; for (list = plugin_mgr->all_plugins; list; list = g_slist_next (list)) plugin_desc_destroy ((plugin_desc_t *) list->data); g_slist_free (plugin_mgr->plugins); g_slist_free (plugin_mgr->all_plugins); mlt_properties_close(plugin_mgr->blacklist); free (plugin_mgr); } void plugin_mgr_set_plugins (plugin_mgr_t * plugin_mgr, unsigned long rack_channels) { GSList * list; plugin_desc_t * desc; /* clear the current plugins */ g_slist_free (plugin_mgr->plugins); plugin_mgr->plugins = NULL; for (list = plugin_mgr->all_plugins; list; list = g_slist_next (list)) { desc = (plugin_desc_t *) list->data; if (plugin_desc_get_copies (desc, rack_channels) != 0) plugin_mgr->plugins = g_slist_append (plugin_mgr->plugins, desc); } } static plugin_desc_t * plugin_mgr_find_desc (plugin_mgr_t * plugin_mgr, GSList * plugins, unsigned long id) { GSList * list; plugin_desc_t * desc; for (list = plugins; list; list = g_slist_next (list)) { desc = (plugin_desc_t *) list->data; if (desc->id == id) return desc; } return NULL; } plugin_desc_t * plugin_mgr_get_desc (plugin_mgr_t * plugin_mgr, unsigned long id) { return plugin_mgr_find_desc (plugin_mgr, plugin_mgr->plugins, id); } plugin_desc_t * plugin_mgr_get_any_desc (plugin_mgr_t * plugin_mgr, unsigned long id) { return plugin_mgr_find_desc (plugin_mgr, plugin_mgr->all_plugins, id); } /* EOF */ mlt-7.22.0/src/modules/jackrack/plugin_mgr.h000664 000000 000000 00000003077 14531534050 020711 0ustar00rootroot000000 000000 /* * JACK Rack * * Original: * Copyright (C) Robert Ham 2002, 2003 (node@users.sourceforge.net) * * Modification for MLT: * Copyright (C) 2004-2014 Meltytech, LLC * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #ifndef __JR_PLUGIN_MANAGER_H__ #define __JR_PLUGIN_MANAGER_H__ #include #include "plugin_desc.h" #include "framework/mlt_properties.h" typedef struct _plugin_mgr plugin_mgr_t; struct _plugin_mgr { GSList * all_plugins; GSList * plugins; unsigned long plugin_count; mlt_properties blacklist; }; struct _ui; plugin_mgr_t * plugin_mgr_new (); void plugin_mgr_destroy (plugin_mgr_t * plugin_mgr); void plugin_mgr_set_plugins (plugin_mgr_t * plugin_mgr, unsigned long rack_channels); plugin_desc_t * plugin_mgr_get_desc (plugin_mgr_t * plugin_mgr, unsigned long id); plugin_desc_t * plugin_mgr_get_any_desc (plugin_mgr_t * plugin_mgr, unsigned long id); #endif /* __JR_PLUGIN_MANAGER_H__ */ mlt-7.22.0/src/modules/jackrack/plugin_settings.c000664 000000 000000 00000024441 14531534050 021755 0ustar00rootroot000000 000000 /* * JACK Rack * * Original: * Copyright (C) Robert Ham 2002, 2003 (node@users.sourceforge.net) * * Modification for MLT: * Copyright (C) 2004-2014 Meltytech, LLC * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #include #include "plugin_settings.h" static void settings_set_to_default (settings_t * settings, guint32 sample_rate) { unsigned long control; guint copy; LADSPA_Data value; for (control = 0; control < settings->desc->control_port_count; control++) { value = plugin_desc_get_default_control_value (settings->desc, control, sample_rate); for (copy = 0; copy < settings->copies; copy++) { settings->control_values[copy][control] = value; } settings->locks[control] = TRUE; } } settings_t * settings_new (plugin_desc_t * desc, unsigned long channels, guint32 sample_rate) { settings_t * settings; unsigned long channel; guint copies; settings = g_malloc (sizeof (settings_t)); copies = plugin_desc_get_copies (desc, channels); settings->sample_rate = sample_rate; settings->desc = desc; settings->copies = copies; settings->channels = channels; settings->lock_all = TRUE; settings->enabled = FALSE; settings->locks = NULL; settings->control_values = NULL; settings->wet_dry_enabled = FALSE; settings->wet_dry_locked = TRUE; /* control settings */ if (desc->control_port_count > 0) { guint copy; settings->locks = g_malloc (sizeof (gboolean) * desc->control_port_count); settings->control_values = g_malloc (sizeof (LADSPA_Data *) * copies); for (copy = 0; copy < copies; copy++) { settings->control_values[copy] = g_malloc (sizeof (LADSPA_Data) * desc->control_port_count); } settings_set_to_default (settings, sample_rate); } /* wet/dry settings */ settings->wet_dry_values = g_malloc (sizeof (LADSPA_Data) * channels); for (channel = 0; channel < channels; channel++) settings->wet_dry_values[channel] = 1.0; return settings; } settings_t * settings_dup (settings_t * other) { settings_t * settings; plugin_desc_t * desc; unsigned long channel; settings = g_malloc (sizeof (settings_t)); settings->sample_rate = other->sample_rate; settings->desc = other->desc; settings->copies = settings_get_copies (other); settings->channels = settings_get_channels (other); settings->wet_dry_enabled = settings_get_wet_dry_enabled (other); settings->wet_dry_locked = settings_get_wet_dry_locked (other); settings->lock_all = settings_get_lock_all (other); settings->enabled = settings_get_enabled (other); settings->locks = NULL; settings->control_values = NULL; desc = other->desc; if (desc->control_port_count > 0) { guint copy; unsigned long control; settings->locks = g_malloc (sizeof (gboolean) * desc->control_port_count); for (control = 0; control < desc->control_port_count; control++) settings_set_lock (settings, control, settings_get_lock (other, control)); settings->control_values = g_malloc (sizeof (LADSPA_Data *) * settings->copies); for (copy = 0; copy < settings->copies; copy++) { settings->control_values[copy] = g_malloc (sizeof (LADSPA_Data) * desc->control_port_count); for (control = 0; control < desc->control_port_count; control++) { settings->control_values[copy][control] = settings_get_control_value (other, copy, control); } } } settings->wet_dry_values = g_malloc (sizeof (LADSPA_Data) * settings->channels); for (channel = 0; channel < settings->channels; channel++) settings->wet_dry_values[channel] = settings_get_wet_dry_value (other, channel); return settings; } void settings_destroy (settings_t * settings) { if (settings->desc->control_port_count > 0) { guint i; for (i = 0; i < settings->copies; i++) g_free (settings->control_values[i]); g_free (settings->control_values); g_free (settings->locks); } g_free (settings->wet_dry_values); g_free (settings); } static void settings_set_copies (settings_t * settings, guint copies) { guint copy; guint last_copy; unsigned long control; if (copies <= settings->copies) return; last_copy = settings->copies - 1; settings->control_values = g_realloc (settings->control_values, sizeof (LADSPA_Data *) * copies); /* copy over the last settings to the new copies */ for (copy = settings->copies; copy < copies; copy++) { for (control = 0; control < settings->desc->control_port_count; control++) { settings->control_values[copy][control] = settings->control_values[last_copy][control]; } } settings->copies = copies; } static void settings_set_channels (settings_t * settings, unsigned long channels) { unsigned long channel; LADSPA_Data last_value; if (channels <= settings->channels) return; settings->wet_dry_values = g_realloc (settings->wet_dry_values, sizeof (LADSPA_Data) * channels); last_value = settings->wet_dry_values[settings->channels - 1]; for (channel = settings->channels; channel < channels; channel++) settings->wet_dry_values[channel] = last_value; settings->channels = channels; } void settings_set_sample_rate (settings_t * settings, guint32 sample_rate) { LADSPA_Data old_sample_rate; LADSPA_Data new_sample_rate; g_return_if_fail (settings != NULL); if (settings->sample_rate == sample_rate) return; if (settings->desc->control_port_count > 0) { unsigned long control; guint copy; new_sample_rate = (LADSPA_Data) sample_rate; old_sample_rate = (LADSPA_Data) settings->sample_rate; for (control = 0; control < settings->desc->control_port_count; control++) { for (copy = 0; copy < settings->copies; copy++) { if (LADSPA_IS_HINT_SAMPLE_RATE (settings->desc->port_range_hints[control].HintDescriptor)) { settings->control_values[copy][control] = (settings->control_values[copy][control] / old_sample_rate) * new_sample_rate; } } } } settings->sample_rate = sample_rate; } void settings_set_control_value (settings_t * settings, guint copy, unsigned long control_index, LADSPA_Data value) { g_return_if_fail (settings != NULL); g_return_if_fail (control_index < settings->desc->control_port_count); if (copy >= settings->copies) settings_set_copies (settings, copy + 1); settings->control_values[copy][control_index] = value; } void settings_set_lock (settings_t * settings, unsigned long control_index, gboolean locked) { g_return_if_fail (settings != NULL); g_return_if_fail (control_index < settings->desc->control_port_count); settings->locks[control_index] = locked; } void settings_set_lock_all (settings_t * settings, gboolean lock_all) { g_return_if_fail (settings != NULL); settings->lock_all = lock_all; } void settings_set_enabled (settings_t * settings, gboolean enabled) { g_return_if_fail (settings != NULL); settings->enabled = enabled; } void settings_set_wet_dry_enabled (settings_t * settings, gboolean enabled) { g_return_if_fail (settings != NULL); settings->wet_dry_enabled = enabled; } void settings_set_wet_dry_locked (settings_t * settings, gboolean locked) { g_return_if_fail (settings != NULL); settings->wet_dry_locked = locked; } void settings_set_wet_dry_value (settings_t * settings, unsigned long channel, LADSPA_Data value) { g_return_if_fail (settings != NULL); if (channel >= settings->channels) settings_set_channels (settings, channel + 1); settings->wet_dry_values[channel] = value; } LADSPA_Data settings_get_control_value (settings_t * settings, guint copy, unsigned long control_index) { g_return_val_if_fail (settings != NULL, NAN); g_return_val_if_fail (control_index < settings->desc->control_port_count, NAN); if (copy >= settings->copies) settings_set_copies (settings, copy - 1); return settings->control_values[copy][control_index]; } gboolean settings_get_lock (const settings_t * settings, unsigned long control_index) { g_return_val_if_fail (settings != NULL, FALSE); return settings->locks[control_index]; } gboolean settings_get_lock_all (const settings_t * settings) { g_return_val_if_fail (settings != NULL, FALSE); return settings->lock_all; } gboolean settings_get_enabled (const settings_t * settings) { g_return_val_if_fail (settings != NULL, FALSE); return settings->enabled; } guint settings_get_copies (const settings_t * settings) { g_return_val_if_fail (settings != NULL, 0); return settings->copies; } unsigned long settings_get_channels (const settings_t * settings) { g_return_val_if_fail (settings != NULL, 0); return settings->channels; } gboolean settings_get_wet_dry_enabled (const settings_t * settings) { g_return_val_if_fail (settings != NULL, FALSE); return settings->wet_dry_enabled; } gboolean settings_get_wet_dry_locked (const settings_t * settings) { g_return_val_if_fail (settings != NULL, FALSE); return settings->wet_dry_locked; } LADSPA_Data settings_get_wet_dry_value (settings_t * settings, unsigned long channel) { g_return_val_if_fail (settings != NULL, NAN); if (channel >= settings->channels) settings_set_channels (settings, channel + 1); return settings->wet_dry_values[channel]; } /* EOF */ mlt-7.22.0/src/modules/jackrack/plugin_settings.h000664 000000 000000 00000006071 14531534050 021761 0ustar00rootroot000000 000000 /* * JACK Rack * * Original: * Copyright (C) Robert Ham 2002, 2003 (node@users.sourceforge.net) * * Modification for MLT: * Copyright (C) 2004-2014 Meltytech, LLC * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #ifndef __JR_PLUGIN_SETTINGS_H__ #define __JR_PLUGIN_SETTINGS_H__ #include #include #include "plugin_mgr.h" #include "plugin_desc.h" typedef struct _settings settings_t; struct _settings { guint32 sample_rate; plugin_desc_t * desc; guint copies; LADSPA_Data ** control_values; gboolean * locks; gboolean lock_all; gboolean enabled; unsigned long channels; gboolean wet_dry_enabled; gboolean wet_dry_locked; LADSPA_Data * wet_dry_values; }; settings_t * settings_new (plugin_desc_t * desc, unsigned long channels, guint32 sample_rate); settings_t * settings_dup (settings_t * settings); void settings_destroy (settings_t * settings); void settings_set_control_value (settings_t * settings, guint copy, unsigned long control_index, LADSPA_Data value); void settings_set_lock (settings_t * settings, unsigned long control_index, gboolean locked); void settings_set_lock_all (settings_t * settings, gboolean lock_all); void settings_set_enabled (settings_t * settings, gboolean enabled); void settings_set_wet_dry_enabled (settings_t * settings, gboolean enabled); void settings_set_wet_dry_locked (settings_t * settings, gboolean locked); void settings_set_wet_dry_value (settings_t * settings, unsigned long channel, LADSPA_Data value); LADSPA_Data settings_get_control_value (settings_t * settings, guint copy, unsigned long control_index); gboolean settings_get_lock (const settings_t * settings, unsigned long control_index); gboolean settings_get_lock_all (const settings_t * settings); gboolean settings_get_enabled (const settings_t * settings); guint settings_get_copies (const settings_t * settings); unsigned long settings_get_channels (const settings_t * settings); gboolean settings_get_wet_dry_enabled (const settings_t * settings); gboolean settings_get_wet_dry_locked (const settings_t * settings); LADSPA_Data settings_get_wet_dry_value (settings_t * settings, unsigned long channel); void settings_set_sample_rate (settings_t * settings, guint32 sample_rate); #endif /* __JR_PLUGIN_SETTINGS_H__ */ mlt-7.22.0/src/modules/jackrack/process.c000664 000000 000000 00000045210 14531534050 020212 0ustar00rootroot000000 000000 /* * JACK Rack * * Original: * Copyright (C) Robert Ham 2002, 2003 (node@users.sourceforge.net) * * Modification for MLT: * Copyright (C) 2004-2021 Meltytech, LLC * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include #ifdef WITH_JACK #include #endif #include #include #include #include #include #include #include #include "process.h" #include "lock_free_fifo.h" #include "plugin.h" #include "jack_rack.h" #include "framework/mlt_log.h" #ifndef _ #define _(x) x #endif extern pthread_mutex_t g_activate_mutex; #define USEC_PER_SEC 1000000 #define MSEC_PER_SEC 1000 #define TIME_RUN_SKIP_COUNT 5 #define MAX_BUFFER_SIZE 4096 jack_nframes_t sample_rate; jack_nframes_t buffer_size; #ifdef WITH_JACK static void jack_shutdown_cb (void * data) { process_info_t * procinfo = data; procinfo->quit = TRUE; } #endif /** process messages for plugins' control ports */ void process_control_port_messages (process_info_t * procinfo) { plugin_t * plugin; unsigned long control; unsigned long channel; gint copy; if (!procinfo->chain) return; for (plugin = procinfo->chain; plugin; plugin = plugin->next) { if (plugin->desc->control_port_count > 0) for (control = 0; control < plugin->desc->control_port_count; control++) for (copy = 0; copy < plugin->copies; copy++) { while (lff_read (plugin->holders[copy].ui_control_fifos + control, plugin->holders[copy].control_memory + control) == 0); } if (plugin->wet_dry_enabled) for (channel = 0; channel < procinfo->channels; channel++) { while (lff_read (plugin->wet_dry_fifos + channel, plugin->wet_dry_values + channel) == 0); } } } #ifdef WITH_JACK static int get_jack_buffers (process_info_t * procinfo, jack_nframes_t frames) { unsigned long channel; for (channel = 0; channel < procinfo->channels; channel++) { procinfo->jack_input_buffers[channel] = jack_port_get_buffer (procinfo->jack_input_ports[channel], frames); if (!procinfo->jack_input_buffers[channel]) { mlt_log_verbose( NULL, "%s: no jack buffer for input port %ld\n", __FUNCTION__, channel); return 1; } procinfo->jack_output_buffers[channel] = jack_port_get_buffer (procinfo->jack_output_ports[channel], frames); if (!procinfo->jack_output_buffers[channel]) { mlt_log_verbose( NULL, "%s: no jack buffer for output port %ld\n", __FUNCTION__, channel); return 1; } } return 0; } #endif plugin_t * get_first_enabled_plugin (process_info_t * procinfo) { plugin_t * first_enabled; if (!procinfo->chain) return NULL; for (first_enabled = procinfo->chain; first_enabled; first_enabled = first_enabled->next) { if (first_enabled->enabled) return first_enabled; } return NULL; } plugin_t * get_last_enabled_plugin (process_info_t * procinfo) { plugin_t * last_enabled; if (!procinfo->chain) return NULL; for (last_enabled = procinfo->chain_end; last_enabled; last_enabled = last_enabled->prev) { if (last_enabled->enabled) return last_enabled; } return NULL; } void connect_chain (process_info_t * procinfo, jack_nframes_t frames) { plugin_t * first_enabled, * last_enabled, * plugin; gint copy; unsigned long channel; if (!procinfo->chain) return; first_enabled = get_first_enabled_plugin (procinfo); if (!first_enabled) return; last_enabled = get_last_enabled_plugin (procinfo); /* sort out the aux ports */ plugin = first_enabled; do { if (plugin->desc->aux_channels > 0 && plugin->enabled) { #ifdef WITH_JACK if (procinfo->jack_client) { for (copy = 0; copy < plugin->copies; copy++) for (channel = 0; channel < plugin->desc->aux_channels; channel++) plugin->descriptor-> connect_port (plugin->holders[copy].instance, plugin->desc->audio_aux_port_indicies[channel], jack_port_get_buffer (plugin->holders[copy].aux_ports[channel], frames)); } else #endif { for (copy = 0; copy < frames; copy++) procinfo->silent_buffer[copy] = 0.0; for (copy = 0; copy < plugin->copies; copy++) for (channel = 0; channel < plugin->desc->aux_channels; channel++) plugin->descriptor-> connect_port (plugin->holders[copy].instance, plugin->desc->audio_aux_port_indicies[channel], procinfo->silent_buffer); } } } while ( (plugin != last_enabled) && (plugin = plugin->next) ); /* ensure that all the of the enabled plugins are connected to their memory */ plugin_connect_output_ports (first_enabled); if (first_enabled != last_enabled) { plugin_connect_input_ports (last_enabled, last_enabled->prev->audio_output_memory); for (plugin = first_enabled->next; plugin; plugin = plugin->next) { if (plugin->enabled) { plugin_connect_input_ports (plugin, plugin->prev->audio_output_memory); plugin_connect_output_ports (plugin); } } } /* input buffers for first plugin */ if( plugin->desc->has_input ) plugin_connect_input_ports (first_enabled, procinfo->jack_input_buffers); } void process_chain (process_info_t * procinfo, jack_nframes_t frames) { plugin_t * first_enabled; plugin_t * last_enabled = NULL; plugin_t * plugin; unsigned long channel; unsigned long i; #ifdef WITH_JACK if (procinfo->jack_client) { LADSPA_Data zero_signal[frames]; guint copy; /* set the zero signal to zero */ for (channel = 0; channel < frames; channel++) zero_signal[channel] = 0.0; /* possibly set aux output channels to zero if they're not enabled */ for (plugin = procinfo->chain; plugin; plugin = plugin->next) if (!plugin->enabled && plugin->desc->aux_channels > 0 && !plugin->desc->aux_are_input) for (copy = 0; copy < plugin->copies; copy++) for (channel = 0; channel < plugin->desc->aux_channels; channel++) memcpy (jack_port_get_buffer (plugin->holders[copy].aux_ports[channel], frames), zero_signal, sizeof (LADSPA_Data) * frames); } #endif first_enabled = get_first_enabled_plugin (procinfo); /* no chain; just copy input to output */ if (!procinfo->chain || !first_enabled) { unsigned long channel; for (channel = 0; channel < procinfo->channels; channel++) { memcpy (procinfo->jack_output_buffers[channel], procinfo->jack_input_buffers[channel], sizeof(LADSPA_Data) * frames); } return; } /* all past here is guaranteed to have at least 1 enabled plugin */ last_enabled = get_last_enabled_plugin (procinfo); for (plugin = first_enabled; plugin; plugin = plugin->next) { if (plugin->enabled) { for (i = 0; i < plugin->copies; i++) plugin->descriptor->run (plugin->holders[i].instance, frames); if (plugin->wet_dry_enabled) for (channel = 0; channel < procinfo->channels; channel++) for (i = 0; i < frames; i++) { plugin->audio_output_memory[channel][i] *= plugin->wet_dry_values[channel]; plugin->audio_output_memory[channel][i] += plugin->audio_input_memory[channel][i] * (1.0 - plugin->wet_dry_values[channel]); } if (plugin == last_enabled) break; } else { /* copy the data through */ for (i = 0; i < procinfo->channels; i++) memcpy (plugin->audio_output_memory[i], plugin->prev->audio_output_memory[i], sizeof(LADSPA_Data) * frames); } } /* copy the last enabled data to the jack ports */ for (i = 0; i < procinfo->channels; i++) memcpy (procinfo->jack_output_buffers[i], last_enabled->audio_output_memory[i], sizeof(LADSPA_Data) * frames); } int process_ladspa (process_info_t * procinfo, jack_nframes_t frames, LADSPA_Data ** inputs, LADSPA_Data ** outputs) { unsigned long channel; if (!procinfo) { mlt_log_error( NULL, "%s: no process_info from jack!\n", __FUNCTION__); return 1; } if (procinfo->quit == TRUE) return 1; process_control_port_messages (procinfo); for (channel = 0; channel < procinfo->channels; channel++) { if(get_first_enabled_plugin (procinfo)->desc->has_input) { procinfo->jack_input_buffers[channel] = inputs[channel]; if (!procinfo->jack_input_buffers[channel]) { mlt_log_verbose( NULL, "%s: no jack buffer for input port %ld\n", __FUNCTION__, channel); return 1; } } procinfo->jack_output_buffers[channel] = outputs[channel]; if (!procinfo->jack_output_buffers[channel]) { mlt_log_verbose( NULL, "%s: no jack buffer for output port %ld\n", __FUNCTION__, channel); return 1; } } connect_chain (procinfo, frames); process_chain (procinfo, frames); return 0; } #ifdef WITH_JACK int process_jack (jack_nframes_t frames, void * data) { int err; process_info_t * procinfo; procinfo = (process_info_t *) data; if (!procinfo) { mlt_log_error( NULL, "%s: no process_info from jack!\n", __FUNCTION__); return 1; } if (procinfo->port_count == 0) return 0; if (procinfo->quit == TRUE) return 1; process_control_port_messages (procinfo); err = get_jack_buffers (procinfo, frames); if (err) { mlt_log_warning( NULL, "%s: failed to get jack ports, not processing\n", __FUNCTION__); return 0; } connect_chain (procinfo, frames); process_chain (procinfo, frames); return 0; } /******************************************* ************** non RT stuff *************** *******************************************/ static int process_info_connect_jack (process_info_t * procinfo) { mlt_log_info( NULL, _("Connecting to JACK server with client name '%s'\n"), procinfo->jack_client_name); procinfo->jack_client = jack_client_open (procinfo->jack_client_name, JackNullOption, NULL); if (!procinfo->jack_client) { mlt_log_warning( NULL, "%s: could not create jack client; is the jackd server running?\n", __FUNCTION__); return 1; } mlt_log_verbose( NULL, _("Connected to JACK server\n")); jack_set_process_callback (procinfo->jack_client, process_jack, procinfo); jack_on_shutdown (procinfo->jack_client, jack_shutdown_cb, procinfo); return 0; } static void process_info_connect_port (process_info_t * procinfo, gshort in, unsigned long port_index, const char * port_name) { const char ** jack_ports; unsigned long jack_port_index; int err; char * full_port_name; jack_ports = jack_get_ports (procinfo->jack_client, NULL, NULL, JackPortIsPhysical | (in ? JackPortIsOutput : JackPortIsInput)); if (!jack_ports) return; for (jack_port_index = 0; jack_ports[jack_port_index] && jack_port_index <= port_index; jack_port_index++) { if (jack_port_index != port_index) continue; full_port_name = g_strdup_printf ("%s:%s", procinfo->jack_client_name, port_name); mlt_log_debug( NULL, _("Connecting ports '%s' and '%s'\n"), full_port_name, jack_ports[jack_port_index]); err = jack_connect (procinfo->jack_client, in ? jack_ports[jack_port_index] : full_port_name, in ? full_port_name : jack_ports[jack_port_index]); if (err) mlt_log_warning( NULL, "%s: error connecting ports '%s' and '%s'\n", __FUNCTION__, full_port_name, jack_ports[jack_port_index]); else mlt_log_info( NULL, _("Connected ports '%s' and '%s'\n"), full_port_name, jack_ports[jack_port_index]); free (full_port_name); } free (jack_ports); } static int process_info_set_port_count (process_info_t * procinfo, unsigned long port_count, gboolean connect_inputs, gboolean connect_outputs) { unsigned long i; char * port_name; jack_port_t ** port_ptr; gshort in; if (procinfo->port_count >= port_count) return -1; if (procinfo->port_count == 0) { procinfo->jack_input_ports = g_malloc (sizeof (jack_port_t *) * port_count); procinfo->jack_output_ports = g_malloc (sizeof (jack_port_t *) * port_count); procinfo->jack_input_buffers = g_malloc (sizeof (LADSPA_Data *) * port_count); procinfo->jack_output_buffers = g_malloc (sizeof (LADSPA_Data *) * port_count); } else { procinfo->jack_input_ports = g_realloc (procinfo->jack_input_ports, sizeof (jack_port_t *) * port_count); procinfo->jack_output_ports = g_realloc (procinfo->jack_output_ports, sizeof (jack_port_t *) * port_count); procinfo->jack_input_buffers = g_realloc (procinfo->jack_input_buffers, sizeof (LADSPA_Data *) * port_count); procinfo->jack_output_buffers = g_realloc (procinfo->jack_output_buffers, sizeof (LADSPA_Data *) * port_count); } for (i = procinfo->port_count; i < port_count; i++) { for (in = 0; in < 2; in++) { port_name = g_strdup_printf ("%s_%ld", in ? "in" : "out", i + 1); //mlt_log_debug( NULL, _("Creating %s port %s\n"), in ? "input" : "output", port_name); port_ptr = (in ? &procinfo->jack_input_ports[i] : &procinfo->jack_output_ports[i]); *port_ptr = jack_port_register (procinfo->jack_client, port_name, JACK_DEFAULT_AUDIO_TYPE, in ? JackPortIsInput : JackPortIsOutput, 0); if (!*port_ptr) { mlt_log_error( NULL, "%s: could not register port '%s'; aborting\n", __FUNCTION__, port_name); return 1; } //mlt_log_debug( NULL, _("Created %s port %s\n"), in ? "input" : "output", port_name); if ((in && connect_inputs) || (!in && connect_outputs)) process_info_connect_port (procinfo, in, i, port_name); g_free (port_name); } } procinfo->port_count = port_count; return 0; } #endif void process_info_set_channels (process_info_t * procinfo, unsigned long channels, gboolean connect_inputs, gboolean connect_outputs) { #ifdef WITH_JACK process_info_set_port_count (procinfo, channels, connect_inputs, connect_outputs); #endif procinfo->channels = channels; } process_info_t * process_info_new (const char * client_name, unsigned long rack_channels, gboolean connect_inputs, gboolean connect_outputs) { process_info_t * procinfo; char * jack_client_name; int err; procinfo = g_malloc (sizeof (process_info_t)); procinfo->chain = NULL; procinfo->chain_end = NULL; #ifdef WITH_JACK procinfo->jack_client = NULL; procinfo->port_count = 0; procinfo->jack_input_ports = NULL; procinfo->jack_output_ports = NULL; #endif procinfo->channels = rack_channels; procinfo->quit = FALSE; if ( client_name == NULL ) { sample_rate = 48000; // should be set externally before calling process_ladspa buffer_size = MAX_BUFFER_SIZE; procinfo->silent_buffer = g_malloc (sizeof (LADSPA_Data) * buffer_size ); procinfo->jack_input_buffers = g_malloc (sizeof (LADSPA_Data *) * rack_channels); procinfo->jack_output_buffers = g_malloc (sizeof (LADSPA_Data *) * rack_channels); return procinfo; } /* sort out the client name */ procinfo->jack_client_name = jack_client_name = strdup (client_name); for (err = 0; jack_client_name[err] != '\0'; err++) { if (jack_client_name[err] == ' ') jack_client_name[err] = '_'; else if (!isalnum (jack_client_name[err])) { /* shift all the chars up one (to remove the non-alphanumeric char) */ int i; for (i = err; jack_client_name[i] != '\0'; i++) jack_client_name[i] = jack_client_name[i + 1]; } else if (isupper (jack_client_name[err])) jack_client_name[err] = tolower (jack_client_name[err]); } #ifdef WITH_JACK err = process_info_connect_jack (procinfo); if (err) { /* g_free (procinfo); */ return NULL; /* abort (); */ } sample_rate = jack_get_sample_rate (procinfo->jack_client); buffer_size = jack_get_sample_rate (procinfo->jack_client); jack_set_process_callback (procinfo->jack_client, process_jack, procinfo); pthread_mutex_lock( &g_activate_mutex ); jack_on_shutdown (procinfo->jack_client, jack_shutdown_cb, procinfo); pthread_mutex_unlock( &g_activate_mutex ); jack_activate (procinfo->jack_client); err = process_info_set_port_count (procinfo, rack_channels, connect_inputs, connect_outputs); if (err) return NULL; #endif return procinfo; } void process_info_destroy (process_info_t * procinfo) { #ifdef WITH_JACK if (procinfo->jack_client) { jack_deactivate (procinfo->jack_client); jack_client_close (procinfo->jack_client); } g_free (procinfo->jack_input_ports); g_free (procinfo->jack_output_ports); #endif g_free (procinfo->jack_input_buffers); g_free (procinfo->jack_output_buffers); g_free (procinfo->silent_buffer); g_free (procinfo); } void process_quit (process_info_t * procinfo) { procinfo->quit = TRUE; } mlt-7.22.0/src/modules/jackrack/process.h000664 000000 000000 00000004566 14531534050 020230 0ustar00rootroot000000 000000 /* * JACK Rack * * Original: * Copyright (C) Robert Ham 2002, 2003 (node@users.sourceforge.net) * * Modification for MLT: * Copyright (C) 2004-2021 Meltytech, LLC * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #ifndef __JLH_PROCESS_H__ #define __JLH_PROCESS_H__ #include #ifdef WITH_JACK #include #endif #include #include "lock_free_fifo.h" typedef struct _process_info process_info_t; /** this is what gets passed to the process() callback and contains all the data the process callback will need */ struct _process_info { /** the plugin instance chain */ struct _plugin * chain; struct _plugin * chain_end; #ifdef WITH_JACK jack_client_t * jack_client; unsigned long port_count; jack_port_t ** jack_input_ports; jack_port_t ** jack_output_ports; #endif unsigned long channels; LADSPA_Data ** jack_input_buffers; LADSPA_Data ** jack_output_buffers; LADSPA_Data * silent_buffer; char * jack_client_name; int quit; }; #ifndef WITH_JACK typedef guint32 jack_nframes_t; #endif extern jack_nframes_t sample_rate; extern jack_nframes_t buffer_size; process_info_t * process_info_new (const char * client_name, unsigned long rack_channels, gboolean connect_inputs, gboolean connect_outputs); void process_info_destroy (process_info_t * procinfo); void process_info_set_channels (process_info_t * procinfo, unsigned long channels, gboolean connect_inputs, gboolean connect_outputs); int process_ladspa (process_info_t * procinfo, jack_nframes_t frames, LADSPA_Data ** inputs, LADSPA_Data ** outputs); #ifdef WITH_JACK int process_jack (jack_nframes_t frames, void * data); #endif void process_quit (process_info_t * procinfo); #endif /* __JLH_PROCESS_H__ */ mlt-7.22.0/src/modules/jackrack/producer_ladspa.c000664 000000 000000 00000020035 14531534050 021701 0ustar00rootroot000000 000000 /* * producer_ladspa.c -- LADSPA plugin producer * Copyright (C) 2013-2014 Meltytech, LLC * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #include #include "jack_rack.h" #define BUFFER_LEN 10000 /** One-time initialization of jack rack. */ static jack_rack_t *initialise_jack_rack(mlt_properties properties, int channels) { jack_rack_t *jackrack = NULL; unsigned long plugin_id = mlt_properties_get_int64(properties, "_pluginid"); // Start JackRack if (plugin_id) { // Create JackRack without Jack client name so that it only uses LADSPA jackrack = jack_rack_new(NULL, channels); mlt_properties_set_data(properties, "_jackrack", jackrack, 0, (mlt_destructor) jack_rack_destroy, NULL); // Load one LADSPA plugin by its UniqueID plugin_desc_t *desc = plugin_mgr_get_any_desc(jackrack->plugin_mgr, plugin_id); plugin_t *plugin; if (desc && (plugin = jack_rack_instantiate_plugin(jackrack, desc))) { plugin->enabled = TRUE; plugin->wet_dry_enabled = FALSE; process_add_plugin(jackrack->procinfo, plugin); mlt_properties_set_int(properties, "instances", plugin->copies); } else { mlt_log_error(properties, "failed to load plugin %lu\n", plugin_id); } } return jackrack; } static int producer_get_audio(mlt_frame frame, void **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples) { // Get the producer service mlt_producer producer = mlt_properties_get_data(MLT_FRAME_PROPERTIES(frame), "_producer_ladspa", NULL); mlt_properties producer_properties = MLT_PRODUCER_PROPERTIES(producer); int size = 0; LADSPA_Data **output_buffers = NULL; int i = 0; // Initialize LADSPA if needed jack_rack_t *jackrack = mlt_properties_get_data(producer_properties, "_jackrack", NULL); if (!jackrack) { sample_rate = *frequency; // global inside jack_rack jackrack = initialise_jack_rack(producer_properties, *channels); } if (jackrack) { // Correct the returns if necessary *samples = *samples <= 0 ? 1920 : *samples; *channels = *channels <= 0 ? 2 : *channels; *frequency = *frequency <= 0 ? 48000 : *frequency; *format = mlt_audio_float; if (jackrack->procinfo && jackrack->procinfo->chain) { plugin_t *plugin = jackrack->procinfo->chain; LADSPA_Data value; int index, c; mlt_position position = mlt_frame_get_position(frame); mlt_position length = mlt_producer_get_length(producer); for (index = 0; index < plugin->desc->control_port_count; index++) { // Apply the control port values char key[20]; value = plugin_desc_get_default_control_value(plugin->desc, index, sample_rate); snprintf(key, sizeof(key), "%d", index); if (mlt_properties_get(producer_properties, key)) value = mlt_properties_anim_get_double(producer_properties, key, position, length); for (c = 0; c < plugin->copies; c++) plugin->holders[c].control_memory[index] = value; } } // Calculate the size of the buffer size = *samples * *channels * sizeof(float); // Allocate the buffer *buffer = mlt_pool_alloc(size); // Initialize the LADSPA output buffer. output_buffers = mlt_pool_alloc(sizeof(LADSPA_Data *) * *channels); for (i = 0; i < *channels; i++) { output_buffers[i] = (LADSPA_Data *) *buffer + i * *samples; } // Do LADSPA processing process_ladspa(jackrack->procinfo, *samples, NULL, output_buffers); mlt_pool_release(output_buffers); // Set the buffer for destruction mlt_frame_set_audio(frame, *buffer, *format, size, mlt_pool_release); if (jackrack && jackrack->procinfo && jackrack->procinfo->chain && mlt_properties_get_int64(producer_properties, "_pluginid")) { plugin_t *plugin = jackrack->procinfo->chain; LADSPA_Data value; int i, c; for (i = 0; i < plugin->desc->status_port_count; i++) { // read the status port values char key[20]; int p = plugin->desc->status_port_indicies[i]; for (c = 0; c < plugin->copies; c++) { snprintf(key, sizeof(key), "%d[%d]", p, c); value = plugin->holders[c].status_memory[i]; mlt_properties_set_double(producer_properties, key, value); } } } } return 0; } static int producer_get_frame(mlt_producer producer, mlt_frame_ptr frame, int index) { // Generate a frame *frame = mlt_frame_init(MLT_PRODUCER_SERVICE(producer)); // Check that we created a frame and initialize it if (*frame != NULL) { // Obtain properties of frame mlt_properties frame_properties = MLT_FRAME_PROPERTIES(*frame); // Update timecode on the frame we're creating mlt_frame_set_position(*frame, mlt_producer_position(producer)); // Save the producer to be used in get_audio mlt_properties_set_data(frame_properties, "_producer_ladspa", producer, 0, NULL, NULL); // Push the get_audio method mlt_frame_push_audio(*frame, producer_get_audio); } // Calculate the next time code mlt_producer_prepare_next(producer); return 0; } /** Destructor for the producer. */ static void producer_close(mlt_producer producer) { producer->close = NULL; mlt_producer_close(producer); free(producer); } /** Constructor for the producer. */ mlt_producer producer_ladspa_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { // Create a new producer object mlt_producer producer = mlt_producer_new(profile); if (producer != NULL) { mlt_properties properties = MLT_PRODUCER_PROPERTIES(producer); producer->get_frame = producer_get_frame; producer->close = (mlt_destructor) producer_close; // Save the plugin ID. if (!strncmp(id, "ladspa.", 7)) { mlt_properties_set(properties, "_pluginid", id + 7); } // Make sure the plugin ID is valid. unsigned long plugin_id = mlt_properties_get_int64(properties, "_pluginid"); if (plugin_id < 1000 || plugin_id > 0x00FFFFFF) { producer_close(producer); producer = NULL; } } return producer; } mlt-7.22.0/src/modules/jackrack/producer_ladspa.yml000664 000000 000000 00000000413 14531534050 022256 0ustar00rootroot000000 000000 schema_version: 7.0 type: producer identifier: ladspa title: LADSPA version: 1 license: GPLv2 language: en tags: - Audio description: Generate audio using LADSPA plugins. notes: > Automatically adapts to the number of channels and sampling rate of the consumer. mlt-7.22.0/src/modules/kdenlive/000775 000000 000000 00000000000 14531534050 016416 5ustar00rootroot000000 000000 mlt-7.22.0/src/modules/kdenlive/CMakeLists.txt000664 000000 000000 00000001216 14531534050 021156 0ustar00rootroot000000 000000 add_library(mltkdenlive MODULE factory.c filter_boxblur.c filter_freeze.c filter_wave.c producer_framebuffer.c ) file(GLOB YML "*.yml") add_custom_target(Other_kdenlive_Files SOURCES ${YML} ) target_compile_options(mltkdenlive PRIVATE ${MLT_COMPILE_OPTIONS}) target_link_libraries(mltkdenlive PRIVATE mlt m) set_target_properties(mltkdenlive PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${MLT_MODULE_OUTPUT_DIRECTORY}") install(TARGETS mltkdenlive LIBRARY DESTINATION ${MLT_INSTALL_MODULE_DIR}) install(FILES filter_boxblur.yml filter_freeze.yml filter_wave.yml producer_framebuffer.yml DESTINATION ${MLT_INSTALL_DATA_DIR}/kdenlive ) mlt-7.22.0/src/modules/kdenlive/factory.c000664 000000 000000 00000005452 14531534050 020237 0ustar00rootroot000000 000000 /* * factory.c -- the factory method interfaces * Copyright (C) 2007 Jean-Baptiste Mardelle * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include extern mlt_filter filter_boxblur_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_freeze_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_wave_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_producer producer_framebuffer_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); static mlt_properties metadata(mlt_service_type type, const char *id, void *data) { char file[PATH_MAX]; snprintf(file, PATH_MAX, "%s/kdenlive/%s", mlt_environment("MLT_DATA"), (char *) data); return mlt_properties_parse_yaml(file); } MLT_REPOSITORY { MLT_REGISTER(mlt_service_filter_type, "boxblur", filter_boxblur_init); MLT_REGISTER(mlt_service_filter_type, "freeze", filter_freeze_init); MLT_REGISTER(mlt_service_filter_type, "wave", filter_wave_init); MLT_REGISTER(mlt_service_producer_type, "framebuffer", producer_framebuffer_init); MLT_REGISTER_METADATA(mlt_service_filter_type, "boxblur", metadata, "filter_boxblur.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "freeze", metadata, "filter_freeze.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "wave", metadata, "filter_wave.yml"); MLT_REGISTER_METADATA(mlt_service_producer_type, "framebuffer", metadata, "producer_framebuffer.yml"); } mlt-7.22.0/src/modules/kdenlive/filter_boxblur.c000664 000000 000000 00000016122 14531534050 021606 0ustar00rootroot000000 000000 /* * filter_boxblur.c -- blur filter * Copyright (C) ?-2007 Leny Grisel * Copyright (C) 2007 Jean-Baptiste Mardelle * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include static void PreCompute(uint8_t *image, int32_t *rgba, int width, int height) { register int x, y, z; int32_t pts[4]; for (y = 0; y < height; y++) { for (x = 0; x < width; x++) { pts[0] = image[0]; pts[1] = image[1]; pts[2] = image[2]; pts[3] = image[3]; for (z = 0; z < 4; z++) { if (x > 0) pts[z] += rgba[-4]; if (y > 0) pts[z] += rgba[width * -4]; if (x > 0 && y > 0) pts[z] -= rgba[(width + 1) * -4]; *rgba++ = pts[z]; } image += 4; } } } static inline int32_t GetRGBA(int32_t *rgba, unsigned int w, unsigned int h, unsigned int x, int offsetx, unsigned int y, int offsety, unsigned int z) { int xtheo = x + offsetx; int ytheo = y + offsety; return rgba[4 * (CLAMP(xtheo, 0, w - 1) + CLAMP(ytheo, 0, h - 1) * w) + z]; } static void DoBoxBlur(uint8_t *image, int32_t *rgba, unsigned int width, unsigned int height, unsigned int boxw, unsigned int boxh) { register int x, y; float mul = 1.f / ((boxw * 2) * (boxh * 2)); for (y = 0; y < height; y++) { for (x = 0; x < width; x++) { *image++ = (GetRGBA(rgba, width, height, x, +boxw, y, +boxh, 0) + GetRGBA(rgba, width, height, x, -boxw, y, -boxh, 0) - GetRGBA(rgba, width, height, x, -boxw, y, +boxh, 0) - GetRGBA(rgba, width, height, x, +boxw, y, -boxh, 0)) * mul; *image++ = (GetRGBA(rgba, width, height, x, +boxw, y, +boxh, 1) + GetRGBA(rgba, width, height, x, -boxw, y, -boxh, 1) - GetRGBA(rgba, width, height, x, -boxw, y, +boxh, 1) - GetRGBA(rgba, width, height, x, +boxw, y, -boxh, 1)) * mul; *image++ = (GetRGBA(rgba, width, height, x, +boxw, y, +boxh, 2) + GetRGBA(rgba, width, height, x, -boxw, y, -boxh, 2) - GetRGBA(rgba, width, height, x, -boxw, y, +boxh, 2) - GetRGBA(rgba, width, height, x, +boxw, y, -boxh, 2)) * mul; *image++ = (GetRGBA(rgba, width, height, x, +boxw, y, +boxh, 3) + GetRGBA(rgba, width, height, x, -boxw, y, -boxh, 3) - GetRGBA(rgba, width, height, x, -boxw, y, +boxh, 3) - GetRGBA(rgba, width, height, x, +boxw, y, -boxh, 3)) * mul; } } } static int filter_get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { int error = 0; mlt_filter filter = (mlt_filter) mlt_frame_pop_service(frame); mlt_properties properties = MLT_FILTER_PROPERTIES(filter); unsigned int boxw = 0; unsigned int boxh = 0; mlt_position position = mlt_filter_get_position(filter, frame); mlt_position length = mlt_filter_get_length2(filter, frame); double hori = mlt_properties_anim_get_double(properties, "hori", position, length); double vert = mlt_properties_anim_get_double(properties, "vert", position, length); // Get blur factor double factor = mlt_properties_get_int(properties, "start"); if (mlt_properties_get(properties, "end")) { double end = (double) mlt_properties_get_int(properties, "end"); factor += (end - factor) * mlt_filter_get_progress(filter, frame); } // If animated property "blur" is set, use its value. char *blur_property = mlt_properties_get(properties, "blur"); if (blur_property) { factor = mlt_properties_anim_get_double(properties, "blur", position, length); } boxw = (unsigned int) (factor * hori); boxh = (unsigned int) (factor * vert); if (boxw == 0 && boxh == 0) { // Don't do anything error = mlt_frame_get_image(frame, image, format, width, height, writable); } else { // Get the image *format = mlt_image_rgba; int error = mlt_frame_get_image(frame, image, format, width, height, 1); // Only process if we have no error and a valid colour space if (error == 0) { mlt_profile profile = mlt_service_profile(MLT_FILTER_SERVICE(filter)); boxw *= mlt_profile_scale_width(profile, *width); boxh *= mlt_profile_scale_height(profile, *height); if (boxw || boxh) { int size = mlt_image_format_size(*format, *width, *height, NULL); int32_t *rgba = mlt_pool_alloc(4 * size); PreCompute(*image, rgba, *width, *height); DoBoxBlur(*image, rgba, *width, *height, MAX(1, boxw), MAX(1, boxh)); mlt_pool_release(rgba); } } } return error; } /** Filter processing. */ static mlt_frame filter_process(mlt_filter filter, mlt_frame frame) { mlt_frame_push_service(frame, filter); mlt_frame_push_get_image(frame, filter_get_image); return frame; } /** Constructor for the filter. */ mlt_filter filter_boxblur_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = mlt_filter_new(); if (filter != NULL) { filter->process = filter_process; mlt_properties_set(MLT_FILTER_PROPERTIES(filter), "start", arg == NULL ? "2" : arg); mlt_properties_set(MLT_FILTER_PROPERTIES(filter), "hori", "1"); mlt_properties_set(MLT_FILTER_PROPERTIES(filter), "vert", "1"); mlt_properties_set(MLT_FILTER_PROPERTIES(filter), "blur", NULL); } return filter; } mlt-7.22.0/src/modules/kdenlive/filter_boxblur.yml000664 000000 000000 00000003105 14531534050 022162 0ustar00rootroot000000 000000 schema_version: 7.0 type: filter identifier: boxblur title: Box Blur (*DEPRECATED*) version: 3 copyright: Leny Grisel, Jean-Baptiste Mardelle creator: Leny Grisel, Jean-Baptiste Mardelle license: LGPLv2.1 language: en notes: > This filter is deprecated and will eventually be removed. Use the box_blur filter instead. tags: - Video parameters: - identifier: start (*DEPRECATED*) title: Size argument: yes type: integer description: > If an end size is provided, then this is the starting size, and size is interpolated over the duration of the filter from start to end size. If the keyframable property "blur" is provided, then this is ignored, and "blur" is used instead. This parameter also affects the size both horizontally and vertically simultaneously, in amounts proportional to the horizontal size and vertical size parameters. unit: pixels mutable: yes default: 2 minimum: 1 - identifier: end (*DEPRECATED*) title: End size type: integer unit: pixels mutable: yes minimum: 1 - identifier: blur title: Size type: integer description: > If this value is set the start and end parameters are ignored. unit: pixels mutable: yes animation: yes minimum: 1 - identifier: hori title: Horizontal size type: integer unit: pixels mutable: yes animation: yes minimum: 0 default: 1 - identifier: vert title: Vertical size type: integer unit: pixels mutable: yes animation: yes minimum: 0 default: 1 mlt-7.22.0/src/modules/kdenlive/filter_freeze.c000664 000000 000000 00000014127 14531534050 021414 0ustar00rootroot000000 000000 /* * filter_freeze.c -- simple frame freezing filter * Copyright (C) 2007 Jean-Baptiste Mardelle * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include #include static int filter_get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { // Get the image mlt_filter filter = mlt_frame_pop_service(frame); mlt_properties properties = MLT_FILTER_PROPERTIES(filter); mlt_properties props = MLT_FRAME_PROPERTIES(frame); mlt_frame freeze_frame = NULL; ; int freeze_before = mlt_properties_get_int(properties, "freeze_before"); int freeze_after = mlt_properties_get_int(properties, "freeze_after"); mlt_position pos = mlt_properties_get_position(properties, "frame") + mlt_producer_get_in(mlt_frame_get_original_producer(frame)); mlt_position currentpos = mlt_filter_get_position(filter, frame); int do_freeze = 0; if (freeze_before == 0 && freeze_after == 0) { do_freeze = 1; } else if (freeze_before != 0 && pos > currentpos) { do_freeze = 1; } else if (freeze_after != 0 && pos < currentpos) { do_freeze = 1; } if (do_freeze == 1) { mlt_service_lock(MLT_FILTER_SERVICE(filter)); freeze_frame = mlt_properties_get_data(properties, "freeze_frame", NULL); if (!freeze_frame || mlt_properties_get_position(properties, "_frame") != pos) { // freeze_frame has not been fetched yet or is not useful, so fetch it and cache it. // get parent producer mlt_producer producer = mlt_producer_cut_parent(mlt_frame_get_original_producer(frame)); mlt_producer_seek(producer, pos); // Get the frame mlt_service_get_frame(mlt_producer_service(producer), &freeze_frame, 0); mlt_properties freeze_properties = MLT_FRAME_PROPERTIES(freeze_frame); mlt_properties_set(freeze_properties, "consumer.rescale", mlt_properties_get(props, "consumer.rescale")); mlt_properties_set_double(freeze_properties, "aspect_ratio", mlt_frame_get_aspect_ratio(frame)); mlt_properties_set_int(freeze_properties, "progressive", mlt_properties_get_int(props, "progressive")); mlt_properties_set_int(freeze_properties, "consumer.progressive", mlt_properties_get_int(props, "consumer.progressive") || mlt_properties_get_int(properties, "deinterlace")); mlt_properties_set_data(properties, "freeze_frame", freeze_frame, 0, (mlt_destructor) mlt_frame_close, NULL); mlt_properties_set_position(properties, "_frame", pos); } // Get frozen image uint8_t *buffer = NULL; int error = mlt_frame_get_image(freeze_frame, &buffer, format, width, height, 1); mlt_service_unlock(MLT_FILTER_SERVICE(filter)); // Copy it to current frame int size = mlt_image_format_size(*format, *width, *height, NULL); uint8_t *image_copy = mlt_pool_alloc(size); memcpy(image_copy, buffer, size); *image = image_copy; mlt_frame_set_image(frame, *image, size, mlt_pool_release); uint8_t *alpha_buffer = mlt_frame_get_alpha(freeze_frame); if (alpha_buffer) { int alphasize = *width * *height; uint8_t *alpha_copy = mlt_pool_alloc(alphasize); memcpy(alpha_copy, alpha_buffer, alphasize); mlt_frame_set_alpha(frame, alpha_copy, alphasize, mlt_pool_release); } return error; } int error = mlt_frame_get_image(frame, image, format, width, height, 1); return error; } /** Filter processing. */ static mlt_frame filter_process(mlt_filter filter, mlt_frame frame) { // Push the filter on to the stack mlt_frame_push_service(frame, filter); // Push the frame filter mlt_frame_push_get_image(frame, filter_get_image); return frame; } /** Constructor for the filter. */ mlt_filter filter_freeze_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = mlt_filter_new(); if (filter != NULL) { filter->process = filter_process; // Set the frame which will be chosen for freeze mlt_properties_set(MLT_FILTER_PROPERTIES(filter), "frame", "0"); // If freeze_after = 1, only frames after the "frame" value will be frozen mlt_properties_set(MLT_FILTER_PROPERTIES(filter), "freeze_after", "0"); // If freeze_before = 1, only frames before the "frame" value will be frozen mlt_properties_set(MLT_FILTER_PROPERTIES(filter), "freeze_before", "0"); } return filter; } mlt-7.22.0/src/modules/kdenlive/filter_freeze.yml000664 000000 000000 00000001247 14531534050 021772 0ustar00rootroot000000 000000 schema_version: 0.3 type: filter identifier: freeze title: Freeze frame version: 1 copyright: Jean-Baptiste Mardelle creator: Jean-Baptiste Mardelle license: LGPLv2.1 language: en tags: - Video parameters: - identifier: frame title: Frame type: time description: The time of the frame to freeze default: 0 mutable: true - identifier: freeze_after title: Freeze After type: boolean description: Whether to only freeze the frames after default: false mutable: true - identifier: freeze_before title: Freeze Before type: boolean description: Whether to only freeze the frames before default: false mutable: true mlt-7.22.0/src/modules/kdenlive/filter_wave.c000664 000000 000000 00000014553 14531534050 021101 0ustar00rootroot000000 000000 /* * wave.c -- wave filter * Copyright (C) ?-2007 Leny Grisel * Copyright (C) 2007 Jean-Baptiste Mardelle * Copyright (c) 2022 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include #include // this is a utility function used by DoWave below static uint8_t getPoint(uint8_t *src, int w, int h, int x, int y, int z) { if (x < 0) x += -((-x) % w) + w; else if (x >= w) x = x % w; if (y < 0) y += -((-y) % h) + h; else if (y >= h) y = y % h; return src[CLAMP(x + y * w, 0, w * h - 1) * 4 + z]; } typedef struct { uint8_t *src; int src_w; int src_h; uint8_t *dst; mlt_position position; int speed; int factor; int deformX; int deformY; } slice_desc; // the main meat of the algorithm lies here static int do_wave_slice_proc(int id, int index, int jobs, void *data) { (void) id; // unused slice_desc *d = (slice_desc *) data; int slice_line_start, slice_height = mlt_slices_size_slice(jobs, index, d->src_h, &slice_line_start); int slice_line_end = slice_line_start + slice_height; register int x, y; int decalY, decalX, z; float amplitude, phase, pulsation; register int uneven = d->src_w % 2; int w = (d->src_w - uneven) / 2; amplitude = d->factor; pulsation = 0.5 / d->factor; // smaller means bigger period phase = d->position * pulsation * d->speed / 10; // smaller means longer uint8_t *dst = d->dst + (slice_line_start * d->src_w * 2); for (y = slice_line_start; y < slice_line_end; y++) { decalX = d->deformX ? sin(pulsation * y + phase) * amplitude : 0; for (x = 0; x < w; x++) { decalY = d->deformY ? sin(pulsation * x * 2 + phase) * amplitude : 0; for (z = 0; z < 4; z++) *dst++ = getPoint(d->src, w, d->src_h, (x + decalX), (y + decalY), z); } if (uneven) { decalY = sin(pulsation * x * 2 + phase) * amplitude; for (z = 0; z < 2; z++) *dst++ = getPoint(d->src, w, d->src_h, (x + decalX), (y + decalY), z); } } return 0; } static int filter_get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { mlt_filter filter = (mlt_filter) mlt_frame_pop_service(frame); mlt_properties properties = MLT_FILTER_PROPERTIES(filter); mlt_position position = mlt_frame_get_position(frame); // Get the image *format = mlt_image_yuv422; int error = mlt_frame_get_image(frame, image, format, width, height, 0); // Only process if we have no error and a valid colour space if (error == 0) { double factor = mlt_properties_get_double(properties, "start"); mlt_position f_pos = mlt_filter_get_position(filter, frame); mlt_position f_len = mlt_filter_get_length2(filter, frame); int speed = mlt_properties_anim_get_int(properties, "speed", f_pos, f_len); int deformX = mlt_properties_anim_get_int(properties, "deformX", f_pos, f_len); int deformY = mlt_properties_anim_get_int(properties, "deformY", f_pos, f_len); if (mlt_properties_get(properties, "end")) { // Determine the time position of this frame in the transition duration double end = fabs(mlt_properties_get_double(MLT_FILTER_PROPERTIES(filter), "end")); factor += (end - factor) * mlt_filter_get_progress(filter, frame); } // If animated property "wave" is set, use its value. char *wave_property = mlt_properties_get(properties, "wave"); if (wave_property) { factor = mlt_properties_anim_get_double(properties, "wave", f_pos, f_len); } mlt_profile profile = mlt_service_profile(MLT_FILTER_SERVICE(filter)); factor *= mlt_profile_scale_width(profile, *width); if (factor > 0.0) { int image_size = *width * (*height) * 2; uint8_t *dst = mlt_pool_alloc(image_size); slice_desc desc; desc.src = *image; desc.src_w = *width; desc.src_h = *height; desc.dst = dst; desc.position = position; desc.speed = speed; desc.factor = factor; desc.deformX = deformX; desc.deformY = deformY; mlt_slices_run_normal(0, do_wave_slice_proc, &desc); *image = dst; mlt_frame_set_image(frame, *image, image_size, mlt_pool_release); } } return error; } /** Filter processing. */ static mlt_frame filter_process(mlt_filter filter, mlt_frame frame) { mlt_frame_push_service(frame, filter); mlt_frame_push_get_image(frame, filter_get_image); return frame; } /** Constructor for the filter. */ mlt_filter filter_wave_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = mlt_filter_new(); if (filter) { filter->process = filter_process; mlt_properties_set(MLT_FILTER_PROPERTIES(filter), "start", arg == NULL ? "10" : arg); mlt_properties_set(MLT_FILTER_PROPERTIES(filter), "speed", "5"); mlt_properties_set(MLT_FILTER_PROPERTIES(filter), "deformX", "1"); mlt_properties_set(MLT_FILTER_PROPERTIES(filter), "deformY", "1"); mlt_properties_set(MLT_FILTER_PROPERTIES(filter), "wave", NULL); } return filter; } mlt-7.22.0/src/modules/kdenlive/filter_wave.yml000664 000000 000000 00000002615 14531534050 021454 0ustar00rootroot000000 000000 schema_version: 7.0 type: filter identifier: wave title: Wave version: 2 copyright: Leny Grisel, Jean-Baptiste Mardelle creator: Leny Grisel, Jean-Baptiste Mardelle license: LGPLv2.1 language: en tags: - Video parameters: - identifier: start (*DEPRECATED*) title: Amplitude argument: yes type: integer description: > If an end amplitude is provided, then this is the starting amplitude, and amplitude is interpolated over the duration of the filter from start to end amplitude. If the keyframable property "wave" is provided, then this is ignored, and "wave" is used instead. This parameter also affects the pulsation period and the phase length. mutable: yes default: 10 minimum: 1 - identifier: end (*DEPRECATED*) title: End amplitude type: integer mutable: yes minimum: 1 - identifier: wave title: Amplitude type: integer description: > If this value is set the start and end parameters are ignored. mutable: yes animation: yes minimum: 1 - identifier: speed title: Speed type: integer mutable: yes animation: yes default: 5 - identifier: deformX title: Deform horizontally? type: boolean mutable: yes animation: yes default: 1 - identifier: deformY title: Deform vertically? type: boolean mutable: yes animation: yes default: 1 mlt-7.22.0/src/modules/kdenlive/producer_framebuffer.c000664 000000 000000 00000040000 14531534050 022743 0ustar00rootroot000000 000000 /* * producer_framebuffer.c -- create subspeed frames * Copyright (C) 2007 Jean-Baptiste Mardelle * Copyright (C) 2022 Meltytech, LLC * Author: Jean-Baptiste Mardelle, based on the code of motion_est by Zachary Drew * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include // Forward references. static int producer_get_frame(mlt_producer producer, mlt_frame_ptr frame, int index); /** Image stack(able) method */ static int framebuffer_get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { // Get the filter object and properties mlt_producer producer = mlt_frame_pop_service(frame); int index = mlt_frame_pop_service_int(frame); mlt_properties properties = MLT_PRODUCER_PROPERTIES(producer); mlt_service_lock(MLT_PRODUCER_SERVICE(producer)); // Frame properties objects mlt_properties frame_properties = MLT_FRAME_PROPERTIES(frame); mlt_frame first_frame = mlt_properties_get_data(properties, "first_frame", NULL); // Get producer parameters int strobe = mlt_properties_get_int(properties, "strobe"); int freeze = mlt_properties_get_int(properties, "freeze"); int freeze_after = mlt_properties_get_int(properties, "freeze_after"); int freeze_before = mlt_properties_get_int(properties, "freeze_before"); int in = mlt_properties_get_position(properties, "in"); // Determine the position mlt_position first_position = (first_frame != NULL) ? mlt_frame_get_position(first_frame) : -1; mlt_position need_first = freeze; if (!freeze || freeze_after || freeze_before) { double prod_speed = mlt_properties_get_double(properties, "_speed"); double actual_position = prod_speed * (in + mlt_producer_position(producer)); if (mlt_properties_get_int(properties, "reverse")) actual_position = mlt_producer_get_playtime(producer) - actual_position; if (strobe < 2) { need_first = floor(actual_position); } else { // Strobe effect wanted, calculate frame position need_first = floor(actual_position); need_first -= MLT_POSITION_MOD(need_first, strobe); } if (freeze) { if (freeze_after && need_first > freeze) need_first = freeze; else if (freeze_before && need_first < freeze) need_first = freeze; } } if (*format == mlt_image_none) { // set format to the original's producer format *format = (mlt_image_format) mlt_properties_get_int(properties, "_original_format"); } // Determine output buffer size *width = mlt_properties_get_int(frame_properties, "width"); *height = mlt_properties_get_int(frame_properties, "height"); int size = mlt_image_format_size(*format, *width, *height, NULL); // Get output buffer int buffersize = 0; int alphasize = *width * *height; uint8_t *output = mlt_properties_get_data(properties, "output_buffer", &buffersize); uint8_t *output_alpha = mlt_properties_get_data(properties, "output_alpha", NULL); if (buffersize == 0 || buffersize != size) { // invalidate cached frame first_position = -1; } if (need_first != first_position) { // invalidate cached frame first_position = -1; // Bust the cached frame mlt_properties_set_data(properties, "first_frame", NULL, 0, NULL, NULL); first_frame = NULL; } if (output && first_position != -1) { // Using the cached frame uint8_t *image_copy = mlt_pool_alloc(size); memcpy(image_copy, output, size); uint8_t *alpha_copy = mlt_pool_alloc(alphasize); memcpy(alpha_copy, output_alpha, alphasize); // Set the output image *image = image_copy; mlt_frame_set_image(frame, image_copy, size, mlt_pool_release); mlt_frame_set_alpha(frame, alpha_copy, alphasize, mlt_pool_release); *width = mlt_properties_get_int(properties, "_output_width"); *height = mlt_properties_get_int(properties, "_output_height"); *format = mlt_properties_get_int(properties, "_output_format"); mlt_service_unlock(MLT_PRODUCER_SERVICE(producer)); return 0; } // Get the cached frame if (first_frame == NULL) { // Get the frame to cache from the real producer mlt_producer real_producer = mlt_properties_get_data(properties, "producer", NULL); // Seek the producer to the correct place mlt_producer_seek(real_producer, need_first); // Get the frame mlt_service_get_frame(MLT_PRODUCER_SERVICE(real_producer), &first_frame, index); // Cache the frame mlt_properties_set_data(properties, "first_frame", first_frame, 0, (mlt_destructor) mlt_frame_close, NULL); } mlt_properties first_frame_properties = MLT_FRAME_PROPERTIES(first_frame); // Which frames are buffered? uint8_t *first_image = mlt_properties_get_data(first_frame_properties, "image", NULL); uint8_t *first_alpha = mlt_frame_get_alpha(first_frame); if (!first_image) { mlt_properties_set(first_frame_properties, "consumer.rescale", mlt_properties_get(frame_properties, "consumer.rescale")); int error = mlt_frame_get_image(first_frame, &first_image, format, width, height, writable); if (error != 0) { mlt_log_warning(MLT_PRODUCER_SERVICE(producer), "first_image == NULL get image died\n"); mlt_properties_set_data(properties, "first_frame", NULL, 0, NULL, NULL); mlt_service_unlock(MLT_PRODUCER_SERVICE(producer)); return error; } output = mlt_pool_alloc(size); memcpy(output, first_image, size); // Let someone else clean up mlt_properties_set_data(properties, "output_buffer", output, size, mlt_pool_release, NULL); mlt_properties_set_int(properties, "_output_width", *width); mlt_properties_set_int(properties, "_output_height", *height); mlt_properties_set_int(properties, "_output_format", *format); } if (!first_alpha) { alphasize = *width * *height; first_alpha = mlt_frame_get_alpha(first_frame); if (!first_alpha) { first_alpha = mlt_pool_alloc(alphasize); memset(first_alpha, 255, alphasize); mlt_frame_set_alpha(first_frame, first_alpha, alphasize, mlt_pool_release); } output_alpha = mlt_pool_alloc(alphasize); memcpy(output_alpha, first_alpha, alphasize); mlt_properties_set_data(properties, "output_alpha", output_alpha, alphasize, mlt_pool_release, NULL); } mlt_service_unlock(MLT_PRODUCER_SERVICE(producer)); // Create a copy uint8_t *image_copy = mlt_pool_alloc(size); memcpy(image_copy, first_image, size); uint8_t *alpha_copy = mlt_pool_alloc(alphasize); memcpy(alpha_copy, first_alpha, alphasize); // Set the output image *image = image_copy; mlt_frame_set_image(frame, image_copy, size, mlt_pool_release); mlt_frame_set_alpha(frame, alpha_copy, alphasize, mlt_pool_release); return 0; } static int producer_get_frame(mlt_producer producer, mlt_frame_ptr frame, int index) { if (frame) { // Construct a new frame *frame = mlt_frame_init(MLT_PRODUCER_SERVICE(producer)); // Stack the producer and producer's get image mlt_frame_push_service_int(*frame, index); mlt_frame_push_service(*frame, producer); mlt_frame_push_service(*frame, framebuffer_get_image); mlt_properties properties = MLT_PRODUCER_PROPERTIES(producer); mlt_properties frame_properties = MLT_FRAME_PROPERTIES(*frame); // Get frame from the real producer mlt_frame first_frame = mlt_properties_get_data(properties, "first_frame", NULL); if (first_frame == NULL) { // Get the frame to cache from the real producer mlt_producer real_producer = mlt_properties_get_data(properties, "producer", NULL); // Get the producer speed double prod_speed = mlt_properties_get_double(properties, "_speed"); // Seek the producer to the correct place mlt_producer_seek(real_producer, mlt_producer_position(producer) * prod_speed); // Get the frame mlt_service_get_frame(MLT_PRODUCER_SERVICE(real_producer), &first_frame, index); // Cache the frame mlt_properties_set_data(properties, "first_frame", first_frame, 0, (mlt_destructor) mlt_frame_close, NULL); // Find the original producer's format int width = 0; int height = 0; mlt_image_format format = mlt_image_none; uint8_t *image = NULL; int error = mlt_frame_get_image(first_frame, &image, &format, &width, &height, 0); if (!error) { // cache the original producer's pixel format mlt_properties_set_int(properties, "_original_format", (int) format); // Inform framework of the default frame format for this producer mlt_properties_set_int(frame_properties, "format", format); } } mlt_properties_inherit(frame_properties, MLT_FRAME_PROPERTIES(first_frame)); double force_aspect_ratio = mlt_properties_get_double(properties, "force_aspect_ratio"); if (force_aspect_ratio <= 0.0) force_aspect_ratio = mlt_properties_get_double(properties, "aspect_ratio"); mlt_properties_set_double(frame_properties, "aspect_ratio", force_aspect_ratio); // Give the returned frame temporal identity mlt_frame_set_position(*frame, mlt_producer_position(producer)); mlt_properties_set_int(frame_properties, "meta.media.width", mlt_properties_get_int(properties, "width")); mlt_properties_set_int(frame_properties, "meta.media.height", mlt_properties_get_int(properties, "height")); mlt_properties_pass_list(frame_properties, properties, "width, height"); } return 0; } mlt_producer producer_framebuffer_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { if (!arg) return NULL; mlt_producer producer = NULL; producer = calloc(1, sizeof(struct mlt_producer_s)); if (!producer) return NULL; if (mlt_producer_init(producer, NULL)) { free(producer); return NULL; } // Wrap loader mlt_producer real_producer; // Check if a speed was specified. /** * Speed must be appended to the filename with '?'. To play your video at 50%: melt framebuffer:my_video.mpg?0.5 * Stroboscope effect can be obtained by adding a stobe=x parameter, where x is the number of frames that will be ignored. * You can play the movie backwards by adding reverse=1 * You can freeze the clip at a determined position by adding freeze=frame_pos add freeze_after=1 to freeze only paste position or freeze_before to freeze before it **/ double speed = 0.0; char *props = strdup(arg); char *ptr = strrchr(props, '?'); if (ptr) { speed = atof(ptr + 1); if (speed != 0.0) // If speed was valid, then strip it and the delimiter. // Otherwise, an invalid speed probably means this '?' was not a delimiter. *ptr = '\0'; } real_producer = mlt_factory_producer(profile, "abnormal", props); free(props); if (speed == 0.0) speed = 1.0; if (producer != NULL && real_producer != NULL) { // Get the properties of this producer mlt_properties properties = MLT_PRODUCER_PROPERTIES(producer); mlt_properties_set(properties, "resource", arg); // Store the producer and filter mlt_properties_set_data(properties, "producer", real_producer, 0, (mlt_destructor) mlt_producer_close, NULL); // Grab some stuff from the real_producer mlt_properties_pass_list(properties, MLT_PRODUCER_PROPERTIES(real_producer), "progressive, length, width, height, aspect_ratio"); if (speed < 0) { speed = -speed; mlt_properties_set_int(properties, "reverse", 1); } if (speed != 1.0) { double real_length = ((double) mlt_producer_get_length(real_producer)) / speed; mlt_properties_set_position(properties, "length", real_length); mlt_properties real_properties = MLT_PRODUCER_PROPERTIES(real_producer); const char *service = mlt_properties_get(real_properties, "mlt_service"); if (service && !strcmp(service, "avformat")) { int n = mlt_properties_count(real_properties); int i; for (i = 0; i < n; i++) { if (strstr(mlt_properties_get_name(real_properties, i), "stream.frame_rate")) { double source_fps = mlt_properties_get_double(real_properties, mlt_properties_get_name(real_properties, i)); if (source_fps > mlt_profile_fps(profile)) { mlt_properties_set_double(real_properties, "force_fps", source_fps * speed); mlt_properties_set_position(real_properties, "length", real_length); mlt_properties_set_position(real_properties, "out", real_length - 1); speed = 1.0; } break; } } } } mlt_properties_set_position(properties, "out", mlt_producer_get_length(producer) - 1); // Since we control the seeking, prevent it from seeking on its own mlt_producer_set_speed(real_producer, 0); mlt_producer_set_speed(producer, speed); // Override the get_frame method producer->get_frame = producer_get_frame; } else { if (producer) mlt_producer_close(producer); if (real_producer) mlt_producer_close(real_producer); producer = NULL; } return producer; } mlt-7.22.0/src/modules/kdenlive/producer_framebuffer.yml000664 000000 000000 00000000304 14531534050 023325 0ustar00rootroot000000 000000 schema_version: 0.1 type: producer identifier: framebuffer title: Speed version: 1 copyright: Jean-Baptiste Mardelle creator: Jean-Baptiste Mardelle license: LGPLv2.1 language: en tags: - Video mlt-7.22.0/src/modules/movit/000775 000000 000000 00000000000 14531534050 015753 5ustar00rootroot000000 000000 mlt-7.22.0/src/modules/movit/CMakeLists.txt000664 000000 000000 00000003757 14531534050 020527 0ustar00rootroot000000 000000 add_library(mltmovit MODULE factory.c filter_glsl_manager.cpp filter_glsl_manager.h filter_movit_blur.cpp filter_movit_convert.cpp filter_movit_crop.cpp filter_movit_deconvolution_sharpen.cpp filter_movit_diffusion.cpp filter_movit_flip.cpp filter_movit_glow.cpp filter_movit_lift_gamma_gain.cpp filter_movit_mirror.cpp filter_movit_opacity.cpp filter_movit_rect.cpp filter_movit_resample.cpp filter_movit_resize.cpp filter_movit_saturation.cpp filter_movit_vignette.cpp filter_movit_white_balance.cpp mlt_movit_input.cpp mlt_movit_input.h optional_effect.h transition_movit_luma.cpp transition_movit_mix.cpp transition_movit_overlay.cpp ) file(GLOB YML "*.yml") add_custom_target(Other_movit_Files SOURCES ${YML} ) target_compile_options(mltmovit PRIVATE ${MLT_COMPILE_OPTIONS}) if(RELOCATABLE) target_compile_definitions(mltmovit PRIVATE RELOCATABLE) endif() target_link_libraries(mltmovit PRIVATE m Threads::Threads mlt mlt++ OpenGL::GL PkgConfig::movit) if(UNIX AND NOT APPLE) target_sources(mltmovit PRIVATE consumer_xgl.c) target_link_libraries(mltmovit PRIVATE X11::X11) endif() pkg_get_variable(SHADERDIR movit shaderdir) target_compile_definitions(mltmovit PRIVATE SHADERDIR="${SHADERDIR}") set_target_properties(mltmovit PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${MLT_MODULE_OUTPUT_DIRECTORY}") install(TARGETS mltmovit LIBRARY DESTINATION ${MLT_INSTALL_MODULE_DIR}) install(FILES filter_movit_blur.yml filter_movit_convert.yml filter_movit_crop.yml filter_movit_deconvolution_sharpen.yml filter_movit_diffusion.yml filter_movit_flip.yml filter_movit_glow.yml filter_movit_lift_gamma_gain.yml filter_movit_mirror.yml filter_movit_opacity.yml filter_movit_rect.yml filter_movit_resample.yml filter_movit_resize.yml filter_movit_saturation.yml filter_movit_vignette.yml filter_movit_white_balance.yml transition_movit_luma.yml transition_movit_mix.yml transition_movit_overlay.yml DESTINATION ${MLT_INSTALL_DATA_DIR}/movit ) mlt-7.22.0/src/modules/movit/consumer_xgl.c000664 000000 000000 00000047444 14531534050 020641 0ustar00rootroot000000 000000 /* * consumer_xgl.c * Copyright (C) 2012 Christophe Thommeret * Author: Christophe Thommeret * Based on Nehe's GLX port by Mihael.Vrbanec@stud.uni-karlsruhe.de * http://nehe.gamedev.net/ * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #define GL_GLEXT_PROTOTYPES #include #include #include #include #include #include #include #include #include #include #include #define STARTWIDTH 1280 #define STARTHEIGHT 720 extern int XInitThreads(); typedef struct consumer_xgl_s *consumer_xgl; struct consumer_xgl_s { struct mlt_consumer_s parent; mlt_properties properties; mlt_deque queue; pthread_t thread; int joined; int running; int playing; int xgl_started; }; typedef struct { pthread_t thread; int running; } thread_video; typedef struct { int width; int height; double aspect_ratio; GLuint texture; pthread_mutex_t mutex; int new; mlt_frame mlt_frame_ref; } frame_new; typedef struct { int width; int height; GLuint fbo; GLuint texture; } fbo; typedef struct { Display *dpy; int screen; Window win; GLXContext ctx; } HiddenContext; typedef struct { Display *dpy; int screen; Window win; GLXContext ctx; XSetWindowAttributes attr; int x, y; unsigned int width, height; unsigned int depth; } GLWindow; static GLWindow GLWin; static HiddenContext hiddenctx; static frame_new new_frame; static fbo fb; static thread_video vthread; static consumer_xgl xgl; static mlt_filter glsl_manager; static void *video_thread(void *arg); static void update() { int _width = GLWin.width; int _height = GLWin.height; GLfloat left, right, top, bottom; GLfloat war = (GLfloat) _width / (GLfloat) _height; if (war < new_frame.aspect_ratio) { left = -1.0; right = 1.0; top = war / new_frame.aspect_ratio; bottom = -war / new_frame.aspect_ratio; } else { top = 1.0; bottom = -1.0; left = -new_frame.aspect_ratio / war; right = new_frame.aspect_ratio / war; } glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glLoadIdentity(); glPushMatrix(); glTranslatef(_width / 2, _height / 2, 0); glScalef(_width / 2, _height / 2, 1.0); glBindTexture(GL_TEXTURE_2D, fb.texture); glBegin(GL_QUADS); glTexCoord2f(0.0f, 0.0f); glVertex2f(left, top); glTexCoord2f(0.0f, 1.0f); glVertex2f(left, bottom); glTexCoord2f(1.0f, 1.0f); glVertex2f(right, bottom); glTexCoord2f(1.0f, 0.0f); glVertex2f(right, top); glEnd(); glPopMatrix(); glXSwapBuffers(GLWin.dpy, GLWin.win); if (!vthread.running) { pthread_create(&vthread.thread, NULL, video_thread, NULL); vthread.running = 1; } } static void show_frame() { if ((fb.width != new_frame.width) || (fb.height != new_frame.height)) { glDeleteFramebuffers(1, &fb.fbo); glDeleteTextures(1, &fb.texture); fb.fbo = 0; fb.width = new_frame.width; fb.height = new_frame.height; glGenFramebuffers(1, &fb.fbo); glGenTextures(1, &fb.texture); glBindTexture(GL_TEXTURE_2D, fb.texture); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, fb.width, fb.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glBindFramebuffer(GL_FRAMEBUFFER, fb.fbo); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fb.texture, 0); glBindFramebuffer(GL_FRAMEBUFFER, 0); } glPushAttrib(GL_VIEWPORT_BIT); glMatrixMode(GL_PROJECTION); glPushMatrix(); glBindFramebuffer(GL_FRAMEBUFFER, fb.fbo); glViewport(0, 0, new_frame.width, new_frame.height); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(0.0, new_frame.width, 0.0, new_frame.height, -1.0, 1.0); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, new_frame.texture); glBegin(GL_QUADS); glTexCoord2f(0.0f, 0.0f); glVertex2f(0.0f, 0.0f); glTexCoord2f(0.0f, 1.0f); glVertex2f(0.0f, new_frame.height); glTexCoord2f(1.0f, 1.0f); glVertex2f(new_frame.width, new_frame.height); glTexCoord2f(1.0f, 0.0f); glVertex2f(new_frame.width, 0.0f); glEnd(); glBindFramebuffer(GL_FRAMEBUFFER, 0); mlt_events_fire(MLT_CONSUMER_PROPERTIES(&xgl->parent), "consumer-frame-show", mlt_event_data_from_frame(new_frame.mlt_frame_ref)); mlt_frame_close(new_frame.mlt_frame_ref); new_frame.mlt_frame_ref = NULL; glMatrixMode(GL_PROJECTION); glPopMatrix(); glMatrixMode(GL_MODELVIEW); glPopAttrib(); update(); new_frame.new = 0; } void *video_thread(void *arg) { mlt_frame next = NULL; mlt_consumer consumer = &xgl->parent; mlt_properties consumer_props = MLT_CONSUMER_PROPERTIES(consumer); struct timeval start, end; double duration = 0; gettimeofday(&start, NULL); while (vthread.running) { // Get a frame from the attached producer next = mlt_consumer_rt_frame(consumer); if (!mlt_properties_get_int(MLT_FILTER_PROPERTIES(glsl_manager), "glsl_supported")) { mlt_log_error(MLT_CONSUMER_SERVICE(consumer), "OpenGL Shading Language is not supported on this machine.\n"); xgl->running = 0; break; } // Ensure that we have a frame if (next) { mlt_properties properties = MLT_FRAME_PROPERTIES(next); if (mlt_properties_get_int(properties, "rendered") == 1) { // Get the image, width and height mlt_image_format vfmt = mlt_image_opengl_texture; int width = 0, height = 0; GLuint *image = 0; int error = mlt_frame_get_image(next, (uint8_t **) &image, &vfmt, &width, &height, 0); if (!error && image && width > 0 && height > 0 && !new_frame.new) { new_frame.width = width; new_frame.height = height; new_frame.texture = *image; new_frame.mlt_frame_ref = next; new_frame.aspect_ratio = ((double) width / (double) height) * mlt_properties_get_double(properties, "aspect_ratio"); new_frame.new = 1; int loop = 200; while (new_frame.new && --loop) usleep(500); } else { mlt_frame_close(next); } new_frame.new = 0; gettimeofday(&end, NULL); duration = 1000000.0 / mlt_properties_get_double(consumer_props, "fps"); duration -= (end.tv_sec * 1000000 + end.tv_usec) - (start.tv_sec * 1000000 + start.tv_usec); if (duration > 0) usleep((int) duration); gettimeofday(&start, NULL); } else { mlt_frame_close(next); static int dropped = 0; mlt_log_info(MLT_CONSUMER_SERVICE(consumer), "dropped video frame %d\n", ++dropped); } } else usleep(1000); } mlt_consumer_stopped(consumer); return NULL; } static void resizeGLScene() { glXMakeCurrent(GLWin.dpy, GLWin.win, GLWin.ctx); if (GLWin.height == 0) GLWin.height = 1; if (GLWin.width == 0) GLWin.width = 1; glViewport(0, 0, GLWin.width, GLWin.height); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(0.0, GLWin.width, 0.0, GLWin.height, -1.0, 1.0); glMatrixMode(GL_MODELVIEW); update(); } static void initGL(void) { glXMakeCurrent(GLWin.dpy, GLWin.win, GLWin.ctx); glClearColor(0.0f, 0.0f, 0.0f, 0.0f); glClearDepth(1.0f); glDepthFunc(GL_LEQUAL); glEnable(GL_DEPTH_TEST); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnable(GL_BLEND); glShadeModel(GL_SMOOTH); glEnable(GL_TEXTURE_2D); glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); typedef int (*GLXSWAPINTERVALSGI)(int); GLXSWAPINTERVALSGI mglXSwapInterval = (GLXSWAPINTERVALSGI) glXGetProcAddressARB( (const GLubyte *) "glXSwapIntervalSGI"); if (mglXSwapInterval) mglXSwapInterval(1); fb.fbo = 0; fb.width = STARTWIDTH; fb.height = STARTHEIGHT; glGenFramebuffers(1, &fb.fbo); glGenTextures(1, &fb.texture); glBindTexture(GL_TEXTURE_2D, fb.texture); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, fb.width, fb.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glBindFramebuffer(GL_FRAMEBUFFER, fb.fbo); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fb.texture, 0); glBindFramebuffer(GL_FRAMEBUFFER, 0); resizeGLScene(); } static void createGLWindow() { const char *title = "OpenGL consumer"; int width = STARTWIDTH; int height = STARTHEIGHT; int attrListSgl[] = {GLX_RGBA, GLX_RED_SIZE, 8, GLX_GREEN_SIZE, 8, GLX_BLUE_SIZE, 8, GLX_DEPTH_SIZE, 16, None}; int attrListDbl[] = {GLX_RGBA, GLX_DOUBLEBUFFER, GLX_RED_SIZE, 8, GLX_GREEN_SIZE, 8, GLX_BLUE_SIZE, 8, GLX_DEPTH_SIZE, 16, None}; XVisualInfo *vi; Colormap cmap; Atom wmDelete; Window winDummy; unsigned int borderDummy; GLWin.dpy = XOpenDisplay(0); GLWin.screen = DefaultScreen(GLWin.dpy); vi = glXChooseVisual(GLWin.dpy, GLWin.screen, attrListDbl); if (!vi) vi = glXChooseVisual(GLWin.dpy, GLWin.screen, attrListSgl); GLWin.ctx = glXCreateContext(GLWin.dpy, vi, 0, GL_TRUE); cmap = XCreateColormap(GLWin.dpy, RootWindow(GLWin.dpy, vi->screen), vi->visual, AllocNone); GLWin.attr.colormap = cmap; GLWin.attr.border_pixel = 0; GLWin.attr.event_mask = ExposureMask | KeyPressMask | ButtonPressMask | StructureNotifyMask; GLWin.win = XCreateWindow(GLWin.dpy, RootWindow(GLWin.dpy, vi->screen), 0, 0, width, height, 0, vi->depth, InputOutput, vi->visual, CWBorderPixel | CWColormap | CWEventMask, &GLWin.attr); wmDelete = XInternAtom(GLWin.dpy, "WM_DELETE_WINDOW", True); XSetWMProtocols(GLWin.dpy, GLWin.win, &wmDelete, 1); XSetStandardProperties(GLWin.dpy, GLWin.win, title, title, None, NULL, 0, NULL); XMapRaised(GLWin.dpy, GLWin.win); glXMakeCurrent(GLWin.dpy, GLWin.win, GLWin.ctx); XGetGeometry(GLWin.dpy, GLWin.win, &winDummy, &GLWin.x, &GLWin.y, &GLWin.width, &GLWin.height, &borderDummy, &GLWin.depth); // Verify GLSL works on this machine hiddenctx.ctx = glXCreateContext(GLWin.dpy, vi, GLWin.ctx, GL_TRUE); if (hiddenctx.ctx) { hiddenctx.dpy = GLWin.dpy; hiddenctx.screen = GLWin.screen; hiddenctx.win = RootWindow(hiddenctx.dpy, hiddenctx.screen); } initGL(); } static void killGLWindow() { if (GLWin.ctx) { if (!glXMakeCurrent(GLWin.dpy, None, NULL)) { printf("Error releasing drawing context : killGLWindow\n"); } glXDestroyContext(GLWin.dpy, GLWin.ctx); GLWin.ctx = NULL; } if (hiddenctx.ctx) glXDestroyContext(hiddenctx.dpy, hiddenctx.ctx); XCloseDisplay(GLWin.dpy); } static void run() { XEvent event; while (xgl->running) { while (XPending(GLWin.dpy) > 0) { XNextEvent(GLWin.dpy, &event); switch (event.type) { case Expose: if (event.xexpose.count != 0) break; break; case ConfigureNotify: if ((event.xconfigure.width != GLWin.width) || (event.xconfigure.height != GLWin.height)) { GLWin.width = event.xconfigure.width; GLWin.height = event.xconfigure.height; resizeGLScene(); } break; case KeyPress: switch (XLookupKeysym(&event.xkey, 0)) { case XK_Escape: xgl->running = 0; break; default: { mlt_producer producer = mlt_properties_get_data(xgl->properties, "transport_producer", NULL); char keyboard[2] = " "; void (*callback)(mlt_producer, char *) = mlt_properties_get_data(xgl->properties, "transport_callback", NULL); if (callback != NULL && producer != NULL) { keyboard[0] = (char) XLookupKeysym(&event.xkey, 0); callback(producer, keyboard); } break; } } break; case ClientMessage: if (*XGetAtomName(GLWin.dpy, event.xclient.message_type) == *"WM_PROTOCOLS") xgl->running = 0; break; default: break; } } if (new_frame.new) show_frame(); else usleep(1000); } } void start_xgl(consumer_xgl consumer) { xgl = consumer; pthread_mutex_init(&new_frame.mutex, NULL); new_frame.aspect_ratio = 16.0 / 9.0; new_frame.new = 0; new_frame.width = STARTWIDTH; new_frame.height = STARTHEIGHT; new_frame.mlt_frame_ref = NULL; vthread.running = 0; xgl->xgl_started = 1; createGLWindow(); run(); if (vthread.running) { vthread.running = 0; pthread_join(vthread.thread, NULL); } xgl->running = 0; } static void on_consumer_thread_started(mlt_properties owner, HiddenContext *context) { // Initialize this thread's OpenGL state glXMakeCurrent(context->dpy, context->win, context->ctx); mlt_events_fire(MLT_FILTER_PROPERTIES(glsl_manager), "init glsl", mlt_event_data_none()); } /** Forward references to static functions. */ static int consumer_start(mlt_consumer parent); static int consumer_stop(mlt_consumer parent); static int consumer_is_stopped(mlt_consumer parent); static void consumer_close(mlt_consumer parent); static void *consumer_thread(void *); /** This is what will be called by the factory - anything can be passed in via the argument, but keep it simple. */ mlt_consumer consumer_xgl_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { // Create the consumer object consumer_xgl this = calloc(sizeof(struct consumer_xgl_s), 1); // If no malloc'd and consumer init ok if (this != NULL && mlt_consumer_init(&this->parent, this, profile) == 0) { // Create the queue this->queue = mlt_deque_init(); // Get the parent consumer object mlt_consumer parent = &this->parent; // We have stuff to clean up, so override the close method parent->close = consumer_close; // get a handle on properties mlt_service service = MLT_CONSUMER_SERVICE(parent); this->properties = MLT_SERVICE_PROPERTIES(service); // Default scaler mlt_properties_set(this->properties, "rescale", "bilinear"); mlt_properties_set(this->properties, "consumer.deinterlacer", "onefield"); // default image format mlt_properties_set(this->properties, "mlt_image_format", "glsl"); // Default buffer for low latency mlt_properties_set_int(this->properties, "buffer", 1); // Ensure we don't join on a non-running object this->joined = 1; this->xgl_started = 0; // Allow thread to be started/stopped parent->start = consumer_start; parent->stop = consumer_stop; parent->is_stopped = consumer_is_stopped; // "init glsl" is required to instantiate glsl filters. glsl_manager = mlt_factory_filter(profile, "glsl.manager", NULL); if (glsl_manager) { mlt_events_listen(this->properties, &hiddenctx, "consumer-thread-started", (mlt_listener) on_consumer_thread_started); } else { mlt_consumer_close(parent); parent = NULL; } // Return the consumer produced return parent; } // malloc or consumer init failed free(this); // Indicate failure return NULL; } int consumer_start(mlt_consumer parent) { consumer_xgl this = parent->child; if (!this->running) { consumer_stop(parent); this->running = 1; this->joined = 0; pthread_create(&this->thread, NULL, consumer_thread, this); } return 0; } int consumer_stop(mlt_consumer parent) { // Get the actual object consumer_xgl this = parent->child; if (this->running && this->joined == 0) { // Kill the thread and clean up this->joined = 1; this->running = 0; if (this->thread) pthread_join(this->thread, NULL); } return 0; } int consumer_is_stopped(mlt_consumer parent) { consumer_xgl this = parent->child; return !this->running; } static void *consumer_thread(void *arg) { // Identify the arg consumer_xgl this = arg; XInitThreads(); start_xgl(this); return NULL; } /** Callback to allow override of the close method. */ static void consumer_close(mlt_consumer parent) { // Get the actual object consumer_xgl this = parent->child; // Stop the consumer ///mlt_consumer_stop( parent ); mlt_filter_close(glsl_manager); // Now clean up the rest mlt_consumer_close(parent); // Close the queue mlt_deque_close(this->queue); if (this->xgl_started) killGLWindow(); // Finally clean up this free(this); } mlt-7.22.0/src/modules/movit/consumer_xgl.yml000664 000000 000000 00000000543 14531534050 021205 0ustar00rootroot000000 000000 schema_version: 7.0 type: consumer identifier: xgl title: OpenGL Video (*DEPRECATED*) version: 1 copyright: Christophe Thommeret license: LGPLv2.1 language: en tags: - Video description: Display the video only in a X11 window using OpenGL. notes: > The window is a fixed size 1280x720 regardless the profile or consumer width and height properties. mlt-7.22.0/src/modules/movit/factory.c000664 000000 000000 00000030226 14531534050 017571 0ustar00rootroot000000 000000 /* * Copyright (C) 2013-2022 Dan Dennedy * factory.c -- the factory method interfaces * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include extern mlt_consumer consumer_xgl_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_glsl_manager_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_movit_blur_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_movit_convert_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_movit_crop_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_deconvolution_sharpen_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_movit_diffusion_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_movit_flip_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_movit_glow_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_lift_gamma_gain_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_movit_mirror_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_movit_opacity_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_movit_rect_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_movit_resample_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_movit_resize_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_movit_saturation_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_movit_vignette_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_white_balance_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_transition transition_movit_luma_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_transition transition_movit_mix_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_transition transition_movit_overlay_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); static mlt_properties metadata(mlt_service_type type, const char *id, void *data) { char file[PATH_MAX]; snprintf(file, PATH_MAX, "%s/movit/%s", mlt_environment("MLT_DATA"), (char *) data); return mlt_properties_parse_yaml(file); } MLT_REPOSITORY { #if !defined(__APPLE__) && !defined(_WIN32) MLT_REGISTER(mlt_service_consumer_type, "xgl", consumer_xgl_init); MLT_REGISTER_METADATA(mlt_service_consumer_type, "xgl", metadata, "consumer_xgl.yml"); #endif MLT_REGISTER(mlt_service_filter_type, "glsl.manager", filter_glsl_manager_init); MLT_REGISTER(mlt_service_filter_type, "movit.blur", filter_movit_blur_init); MLT_REGISTER(mlt_service_filter_type, "movit.convert", filter_movit_convert_init); MLT_REGISTER(mlt_service_filter_type, "movit.crop", filter_movit_crop_init); MLT_REGISTER(mlt_service_filter_type, "movit.diffusion", filter_movit_diffusion_init); MLT_REGISTER(mlt_service_filter_type, "movit.flip", filter_movit_flip_init); MLT_REGISTER(mlt_service_filter_type, "movit.glow", filter_movit_glow_init); MLT_REGISTER(mlt_service_filter_type, "movit.lift_gamma_gain", filter_lift_gamma_gain_init); MLT_REGISTER(mlt_service_filter_type, "movit.mirror", filter_movit_mirror_init); MLT_REGISTER(mlt_service_filter_type, "movit.opacity", filter_movit_opacity_init); MLT_REGISTER(mlt_service_filter_type, "movit.rect", filter_movit_rect_init); MLT_REGISTER(mlt_service_filter_type, "movit.resample", filter_movit_resample_init); MLT_REGISTER(mlt_service_filter_type, "movit.resize", filter_movit_resize_init); MLT_REGISTER(mlt_service_filter_type, "movit.saturation", filter_movit_saturation_init); MLT_REGISTER(mlt_service_filter_type, "movit.sharpen", filter_deconvolution_sharpen_init); MLT_REGISTER(mlt_service_filter_type, "movit.vignette", filter_movit_vignette_init); MLT_REGISTER(mlt_service_filter_type, "movit.white_balance", filter_white_balance_init); MLT_REGISTER(mlt_service_link_type, "movit.convert", mlt_link_filter_init); MLT_REGISTER(mlt_service_link_type, "movit.crop", mlt_link_filter_init); MLT_REGISTER(mlt_service_link_type, "movit.resample", mlt_link_filter_init); MLT_REGISTER(mlt_service_link_type, "movit.resize", mlt_link_filter_init); MLT_REGISTER(mlt_service_transition_type, "movit.luma_mix", transition_movit_luma_init); MLT_REGISTER(mlt_service_transition_type, "movit.mix", transition_movit_mix_init); MLT_REGISTER(mlt_service_transition_type, "movit.overlay", transition_movit_overlay_init); MLT_REGISTER_METADATA(mlt_service_filter_type, "glsl.manager", metadata, "filter_glsl_manager.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "movit.blur", metadata, "filter_movit_blur.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "movit.convert", metadata, "filter_movit_convert.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "movit.crop", metadata, "filter_movit_crop.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "movit.diffusion", metadata, "filter_movit_diffusion.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "movit.flip", metadata, "filter_movit_flip.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "movit.glow", metadata, "filter_movit_glow.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "movit.lift_gamma_gain", metadata, "filter_movit_lift_gamma_gain.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "movit.mirror", metadata, "filter_movit_mirror.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "movit.opacity", metadata, "filter_movit_opacity.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "movit.rect", metadata, "filter_movit_rect.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "movit.resample", metadata, "filter_movit_resample.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "movit.resize", metadata, "filter_movit_resize.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "movit.saturation", metadata, "filter_movit_saturation.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "movit.sharpen", metadata, "filter_movit_deconvolution_sharpen.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "movit.vignette", metadata, "filter_movit_vignette.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "movit.white_balance", metadata, "filter_movit_white_balance.yml"); MLT_REGISTER_METADATA(mlt_service_link_type, "movit.convert", mlt_link_filter_metadata, NULL); MLT_REGISTER_METADATA(mlt_service_link_type, "movit.crop", mlt_link_filter_metadata, NULL); MLT_REGISTER_METADATA(mlt_service_link_type, "movit.resample", mlt_link_filter_metadata, NULL); MLT_REGISTER_METADATA(mlt_service_link_type, "movit.resize", mlt_link_filter_metadata, NULL); MLT_REGISTER_METADATA(mlt_service_transition_type, "movit.luma_mix", metadata, "transition_movit_luma.yml"); MLT_REGISTER_METADATA(mlt_service_transition_type, "movit.mix", metadata, "transition_movit_mix.yml"); MLT_REGISTER_METADATA(mlt_service_transition_type, "movit.overlay", metadata, "transition_movit_overlay.yml"); } mlt-7.22.0/src/modules/movit/filter_glsl_manager.cpp000664 000000 000000 00000054705 14531534050 022472 0ustar00rootroot000000 000000 /* * filter_glsl_manager.cpp * Copyright (C) 2011-2012 Christophe Thommeret * Copyright (C) 2013-2023 Dan Dennedy * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "filter_glsl_manager.h" #include "mlt_flip_effect.h" #include "mlt_movit_input.h" #include #include #include #include #include #include #include #include extern "C" { #include } #if defined(__APPLE__) #include #elif defined(_WIN32) #include #include #else #include #endif // Texture pool may cause frames to appear out-of-order with NVIDIA // threaded optimizations. #define USE_TEXTURE_POOL 1 using namespace movit; void dec_ref_and_delete(GlslManager *p) { if (p->dec_ref() == 0) { delete p; } } GlslManager::GlslManager() : Mlt::Filter(mlt_filter_new()) , resource_pool(new ResourcePool()) , pbo(0) , initEvent(0) , closeEvent(0) , prev_sync(NULL) { mlt_filter filter = get_filter(); if (filter) { // Set the mlt_filter child in case we choose to override virtual functions. filter->child = this; add_ref(mlt_global_properties()); mlt_events_register(get_properties(), "init glsl"); mlt_events_register(get_properties(), "close glsl"); initEvent = listen("init glsl", this, (mlt_listener) GlslManager::onInit); closeEvent = listen("close glsl", this, (mlt_listener) GlslManager::onClose); } } GlslManager::~GlslManager() { mlt_log_debug(get_service(), "%s\n", __FUNCTION__); cleanupContext(); // XXX If there is still a frame holding a reference to a texture after this // destructor is called, then it will crash in release_texture(). // while (texture_list.peek_back()) // delete (glsl_texture) texture_list.pop_back(); delete initEvent; delete closeEvent; if (prev_sync != NULL) { glDeleteSync(prev_sync); } while (syncs_to_delete.count() > 0) { GLsync sync = (GLsync) syncs_to_delete.pop_front(); glDeleteSync(sync); } delete resource_pool; } void GlslManager::add_ref(mlt_properties properties) { inc_ref(); mlt_properties_set_data(properties, "glslManager", this, 0, (mlt_destructor) dec_ref_and_delete, NULL); } GlslManager *GlslManager::get_instance() { return (GlslManager *) mlt_properties_get_data(mlt_global_properties(), "glslManager", 0); } glsl_texture GlslManager::get_texture(int width, int height, GLint internal_format) { if (width < 1 || height < 1) { return NULL; } #if USE_TEXTURE_POOL lock(); for (int i = 0; i < texture_list.count(); ++i) { glsl_texture tex = (glsl_texture) texture_list.peek(i); if (!tex->used && (tex->width == width) && (tex->height == height) && (tex->internal_format == internal_format)) { glBindTexture(GL_TEXTURE_2D, tex->texture); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glBindTexture(GL_TEXTURE_2D, 0); tex->used = 1; unlock(); return tex; } } unlock(); #endif GLuint tex = 0; glGenTextures(1, &tex); if (!tex) { return NULL; } glsl_texture gtex = new glsl_texture_s; if (!gtex) { glDeleteTextures(1, &tex); return NULL; } glBindTexture(GL_TEXTURE_2D, tex); glTexImage2D(GL_TEXTURE_2D, 0, internal_format, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glBindTexture(GL_TEXTURE_2D, 0); gtex->texture = tex; gtex->width = width; gtex->height = height; gtex->internal_format = internal_format; gtex->used = 1; #if USE_TEXTURE_POOL lock(); texture_list.push_back(gtex); unlock(); #endif return gtex; } void GlslManager::release_texture(glsl_texture texture) { #if USE_TEXTURE_POOL texture->used = 0; #else GlslManager *g = GlslManager::get_instance(); g->lock(); g->texture_list.push_back(texture); g->unlock(); #endif } void GlslManager::delete_sync(GLsync sync) { // We do not know which thread we are called from, and we can only // delete this if we are in one with a valid OpenGL context. // Thus, store it for later deletion in render_frame_texture(). GlslManager *g = GlslManager::get_instance(); g->lock(); g->syncs_to_delete.push_back(sync); g->unlock(); } glsl_pbo GlslManager::get_pbo(int size) { lock(); if (!pbo) { GLuint pb = 0; glGenBuffers(1, &pb); if (!pb) { unlock(); return NULL; } pbo = new glsl_pbo_s; if (!pbo) { glDeleteBuffers(1, &pb); unlock(); return NULL; } pbo->pbo = pb; pbo->size = 0; } if (size > pbo->size) { glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, pbo->pbo); glBufferData(GL_PIXEL_UNPACK_BUFFER_ARB, size, NULL, GL_STREAM_DRAW); glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, 0); pbo->size = size; } unlock(); return pbo; } void GlslManager::cleanupContext() { lock(); while (texture_list.peek_back()) { glsl_texture texture = (glsl_texture) texture_list.peek_back(); glDeleteTextures(1, &texture->texture); delete texture; texture_list.pop_back(); } if (pbo) { glDeleteBuffers(1, &pbo->pbo); delete pbo; pbo = 0; } unlock(); } void GlslManager::onInit(mlt_properties owner, GlslManager *filter, mlt_event_data) { mlt_log_debug(filter->get_service(), "%s\n", __FUNCTION__); #ifdef _WIN32 std::string path = std::string(mlt_environment("MLT_APPDIR")).append("\\share\\movit"); #elif defined(RELOCATABLE) #ifdef __APPLE__ std::string path = std::string(mlt_environment("MLT_APPDIR")).append("/Resources/movit"); #else std::string path = std::string(mlt_environment("MLT_APPDIR")).append("/share/movit"); #endif #else std::string path = std::string(getenv("MLT_MOVIT_PATH") ? getenv("MLT_MOVIT_PATH") : SHADERDIR); #endif bool success = init_movit(path, mlt_log_get_level() == MLT_LOG_DEBUG ? MOVIT_DEBUG_ON : MOVIT_DEBUG_OFF); filter->set("glsl_supported", success); } void GlslManager::onClose(mlt_properties owner, GlslManager *filter, mlt_event_data) { filter->cleanupContext(); } void GlslManager::onServiceChanged(mlt_properties owner, mlt_service aservice) { Mlt::Service service(aservice); service.lock(); service.set("movit chain", NULL, 0); service.unlock(); } void GlslManager::onPropertyChanged(mlt_properties owner, mlt_service service, const char *property) { if (property && std::string(property) == "disable") onServiceChanged(owner, service); } extern "C" { mlt_filter filter_glsl_manager_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { GlslManager *g = GlslManager::get_instance(); if (g) g->inc_ref(); else g = new GlslManager(); return g->get_filter(); } } // extern "C" static void deleteChain(GlslChain *chain) { // The Input* is owned by the EffectChain, but the MltInput* is not. // Thus, we have to delete it here. for (std::map::iterator input_it = chain->inputs.begin(); input_it != chain->inputs.end(); ++input_it) { delete input_it->second; } delete chain->effect_chain; delete chain; } void *GlslManager::get_frame_specific_data(mlt_service service, mlt_frame frame, const char *key, int *length) { const char *unique_id = mlt_properties_get(MLT_SERVICE_PROPERTIES(service), "_unique_id"); char buf[256]; snprintf(buf, sizeof(buf), "%s_%s", key, unique_id); return mlt_properties_get_data(MLT_FRAME_PROPERTIES(frame), buf, length); } int GlslManager::set_frame_specific_data(mlt_service service, mlt_frame frame, const char *key, void *value, int length, mlt_destructor destroy, mlt_serialiser serialise) { const char *unique_id = mlt_properties_get(MLT_SERVICE_PROPERTIES(service), "_unique_id"); char buf[256]; snprintf(buf, sizeof(buf), "%s_%s", key, unique_id); return mlt_properties_set_data(MLT_FRAME_PROPERTIES(frame), buf, value, length, destroy, serialise); } void GlslManager::set_chain(mlt_service service, GlslChain *chain) { mlt_properties_set_data(MLT_SERVICE_PROPERTIES(service), "_movit chain", chain, 0, (mlt_destructor) deleteChain, NULL); } GlslChain *GlslManager::get_chain(mlt_service service) { return ( GlslChain *) mlt_properties_get_data(MLT_SERVICE_PROPERTIES(service), "_movit chain", NULL); } Effect *GlslManager::get_effect(mlt_service service, mlt_frame frame) { return (Effect *) get_frame_specific_data(service, frame, "_movit effect", NULL); } Effect *GlslManager::set_effect(mlt_service service, mlt_frame frame, Effect *effect) { set_frame_specific_data(service, frame, "_movit effect", effect, 0, NULL, NULL); return effect; } MltInput *GlslManager::get_input(mlt_producer producer, mlt_frame frame) { return (MltInput *) get_frame_specific_data(MLT_PRODUCER_SERVICE(producer), frame, "_movit input", NULL); } MltInput *GlslManager::set_input(mlt_producer producer, mlt_frame frame, MltInput *input) { set_frame_specific_data(MLT_PRODUCER_SERVICE(producer), frame, "_movit input", input, 0, NULL, NULL); return input; } uint8_t *GlslManager::get_input_pixel_pointer(mlt_producer producer, mlt_frame frame) { return (uint8_t *) get_frame_specific_data(MLT_PRODUCER_SERVICE(producer), frame, "_movit input pp", NULL); } uint8_t *GlslManager::set_input_pixel_pointer(mlt_producer producer, mlt_frame frame, uint8_t *image) { set_frame_specific_data(MLT_PRODUCER_SERVICE(producer), frame, "_movit input pp", image, 0, NULL, NULL); return image; } mlt_service GlslManager::get_effect_input(mlt_service service, mlt_frame frame) { return (mlt_service) get_frame_specific_data(service, frame, "_movit effect input", NULL); } void GlslManager::set_effect_input(mlt_service service, mlt_frame frame, mlt_service input_service) { set_frame_specific_data(service, frame, "_movit effect input", input_service, 0, NULL, NULL); } void GlslManager::get_effect_secondary_input(mlt_service service, mlt_frame frame, mlt_service *input_service, mlt_frame *input_frame) { *input_service = (mlt_service) get_frame_specific_data(service, frame, "_movit effect secondary input", NULL); *input_frame = (mlt_frame) get_frame_specific_data(service, frame, "_movit effect secondary input frame", NULL); } void GlslManager::set_effect_secondary_input(mlt_service service, mlt_frame frame, mlt_service input_service, mlt_frame input_frame) { set_frame_specific_data(service, frame, "_movit effect secondary input", input_service, 0, NULL, NULL); set_frame_specific_data(service, frame, "_movit effect secondary input frame", input_frame, 0, NULL, NULL); } void GlslManager::get_effect_third_input(mlt_service service, mlt_frame frame, mlt_service *input_service, mlt_frame *input_frame) { *input_service = (mlt_service) get_frame_specific_data(service, frame, "_movit effect third input", NULL); *input_frame = (mlt_frame) get_frame_specific_data(service, frame, "_movit effect third input frame", NULL); } void GlslManager::set_effect_third_input(mlt_service service, mlt_frame frame, mlt_service input_service, mlt_frame input_frame) { set_frame_specific_data(service, frame, "_movit effect third input", input_service, 0, NULL, NULL); set_frame_specific_data(service, frame, "_movit effect third input frame", input_frame, 0, NULL, NULL); } int GlslManager::render_frame_texture( EffectChain *chain, mlt_frame frame, int width, int height, uint8_t **image) { if (width < 1 || height < 1) { return 1; } glsl_texture texture = get_texture(width, height, GL_RGBA8); if (!texture) { return 1; } GLuint fbo; glGenFramebuffers(1, &fbo); check_error(); glBindFramebuffer(GL_FRAMEBUFFER, fbo); check_error(); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture->texture, 0); check_error(); glBindFramebuffer(GL_FRAMEBUFFER, 0); check_error(); lock(); while (syncs_to_delete.count() > 0) { GLsync sync = (GLsync) syncs_to_delete.pop_front(); glDeleteSync(sync); } #if !USE_TEXTURE_POOL while (texture_list.count() > 0) { glsl_texture texture = (glsl_texture) texture_list.pop_back(); glDeleteTextures(1, &texture->texture); delete texture; } #endif unlock(); // Make sure we never have more than one frame pending at any time. // This ensures we do not swamp the GPU with so much work // that we cannot actually display the frames we generate. if (prev_sync != NULL) { glFlush(); glClientWaitSync(prev_sync, 0, GL_TIMEOUT_IGNORED); glDeleteSync(prev_sync); } chain->render_to_fbo(fbo, width, height); prev_sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); GLsync sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); check_error(); glBindFramebuffer(GL_FRAMEBUFFER, 0); check_error(); glDeleteFramebuffers(1, &fbo); check_error(); *image = (uint8_t *) &texture->texture; mlt_frame_set_image(frame, *image, 0, NULL); mlt_properties_set_data(MLT_FRAME_PROPERTIES(frame), "movit.convert.texture", texture, 0, (mlt_destructor) GlslManager::release_texture, NULL); mlt_properties_set_data(MLT_FRAME_PROPERTIES(frame), "movit.convert.fence", sync, 0, (mlt_destructor) GlslManager::delete_sync, NULL); return 0; } int GlslManager::render_frame_rgba( EffectChain *chain, mlt_frame frame, int width, int height, uint8_t **image) { if (width < 1 || height < 1) { return 1; } glsl_texture texture = get_texture(width, height, GL_RGBA8); if (!texture) { return 1; } // Use a PBO to hold the data we read back with glReadPixels(). // (Intel/DRI goes into a slow path if we don't read to PBO.) int img_size = width * height * 4; glsl_pbo pbo = get_pbo(img_size); if (!pbo) { release_texture(texture); return 1; } // Set the FBO GLuint fbo; glGenFramebuffers(1, &fbo); check_error(); glBindFramebuffer(GL_FRAMEBUFFER, fbo); check_error(); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture->texture, 0); check_error(); glBindFramebuffer(GL_FRAMEBUFFER, 0); check_error(); chain->render_to_fbo(fbo, width, height); // Read FBO into PBO glBindFramebuffer(GL_FRAMEBUFFER, fbo); check_error(); glBindBuffer(GL_PIXEL_PACK_BUFFER_ARB, pbo->pbo); check_error(); glBufferData(GL_PIXEL_PACK_BUFFER_ARB, img_size, NULL, GL_STREAM_READ); check_error(); glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, BUFFER_OFFSET(0)); check_error(); // Copy from PBO uint8_t *buf = (uint8_t *) glMapBuffer(GL_PIXEL_PACK_BUFFER_ARB, GL_READ_ONLY); check_error(); *image = (uint8_t *) mlt_pool_alloc(img_size); mlt_frame_set_image(frame, *image, img_size, mlt_pool_release); memcpy(*image, buf, img_size); // Release PBO and FBO glUnmapBuffer(GL_PIXEL_PACK_BUFFER_ARB); check_error(); glBindBuffer(GL_PIXEL_PACK_BUFFER_ARB, 0); check_error(); glBindFramebuffer(GL_FRAMEBUFFER, 0); check_error(); glBindTexture(GL_TEXTURE_2D, 0); check_error(); mlt_properties_set_data(MLT_FRAME_PROPERTIES(frame), "movit.convert.texture", texture, 0, (mlt_destructor) GlslManager::release_texture, NULL); glDeleteFramebuffers(1, &fbo); check_error(); return 0; } int GlslManager::render_frame_ycbcr( EffectChain *chain, mlt_frame frame, int width, int height, uint8_t **image) { if (width < 1 || height < 1) { return 1; } glsl_texture texture = get_texture(width, height, GL_RGBA16); if (!texture) { return 1; } // Use a PBO to hold the data we read back with glReadPixels(). // (Intel/DRI goes into a slow path if we don't read to PBO.) int img_size = width * height * 4 * sizeof(uint16_t); glsl_pbo pbo = get_pbo(img_size); if (!pbo) { release_texture(texture); return 1; } // Set the FBO GLuint fbo; glGenFramebuffers(1, &fbo); check_error(); glBindFramebuffer(GL_FRAMEBUFFER, fbo); check_error(); glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture->texture, 0); check_error(); glBindFramebuffer(GL_FRAMEBUFFER, 0); check_error(); chain->render_to_fbo(fbo, width, height); // Read FBO into PBO glBindFramebuffer(GL_FRAMEBUFFER, fbo); check_error(); glBindBuffer(GL_PIXEL_PACK_BUFFER_ARB, pbo->pbo); check_error(); glBufferData(GL_PIXEL_PACK_BUFFER_ARB, img_size, NULL, GL_STREAM_READ); check_error(); glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_SHORT, BUFFER_OFFSET(0)); check_error(); // Copy from PBO uint16_t *buf = (uint16_t *) glMapBuffer(GL_PIXEL_PACK_BUFFER_ARB, GL_READ_ONLY); check_error(); int mlt_size = mlt_image_format_size(mlt_image_yuv444p10, width, height, nullptr); *image = (uint8_t *) mlt_pool_alloc(mlt_size); mlt_frame_set_image(frame, *image, mlt_size, mlt_pool_release); uint8_t *planes[4]; int strides[4]; mlt_image_format_planes(mlt_image_yuv444p10, width, height, *image, planes, strides); uint16_t **p = (uint16_t **) planes; for (int i = 0; i < width * height; ++i) { p[0][i] = buf[4 * i + 0]; p[1][i] = buf[4 * i + 1]; p[2][i] = buf[4 * i + 2]; } // Release PBO and FBO glUnmapBuffer(GL_PIXEL_PACK_BUFFER_ARB); check_error(); glBindBuffer(GL_PIXEL_PACK_BUFFER_ARB, 0); check_error(); glBindFramebuffer(GL_FRAMEBUFFER, 0); check_error(); glBindTexture(GL_TEXTURE_2D, 0); check_error(); mlt_properties_set_data(MLT_FRAME_PROPERTIES(frame), "movit.convert.texture", texture, 0, (mlt_destructor) GlslManager::release_texture, NULL); glDeleteFramebuffers(1, &fbo); check_error(); return 0; } void GlslManager::lock_service(mlt_frame frame) { Mlt::Producer producer(mlt_producer_cut_parent(mlt_frame_get_original_producer(frame))); producer.lock(); } void GlslManager::unlock_service(mlt_frame frame) { Mlt::Producer producer(mlt_producer_cut_parent(mlt_frame_get_original_producer(frame))); producer.unlock(); } mlt-7.22.0/src/modules/movit/filter_glsl_manager.h000664 000000 000000 00000012635 14531534050 022133 0ustar00rootroot000000 000000 /* * filter_glsl_manager.h * Copyright (C) 2013-2023 Dan Dennedy * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef GLSL_MANAGER_H #define GLSL_MANAGER_H // Include a random Movit header file to get in GL.h, without including it // ourselves (which might interfere with whatever OpenGL extension library // Movit has chosen to use). #include #include #include #include #include #define MAXLISTCOUNT 1024 typedef struct glsl_list_s *glsl_list; struct glsl_list_s { void *items[MAXLISTCOUNT]; int count; int (*add)(glsl_list, void *); void *(*at)(glsl_list, int); void *(*take_at)(glsl_list, int); void *(*take)(glsl_list, void *); }; struct glsl_texture_s { int used; GLuint texture; int width; int height; GLint internal_format; }; typedef struct glsl_texture_s *glsl_texture; struct glsl_pbo_s { int size; GLuint pbo; }; typedef struct glsl_pbo_s *glsl_pbo; namespace movit { class Effect; class EffectChain; class ResourcePool; } // namespace movit class MltInput; struct GlslChain { movit::EffectChain *effect_chain; // All MltInputs in the effect chain. These are not owned by the // EffectChain (although the contained Input* is). std::map inputs; // All services owned by the effect chain and their associated Movit effect. std::map effects; // For each effect in the Movit graph, a unique identifier for the service // and whether it's disabled or not, using post-order traversal. // We need to generate the chain if and only if this has changed. std::string fingerprint; }; class GlslManager : public Mlt::Filter { public: GlslManager(); ~GlslManager(); void add_ref(mlt_properties properties); static GlslManager *get_instance(); glsl_texture get_texture(int width, int height, GLint internal_format); static void release_texture(glsl_texture); static void delete_sync(GLsync sync); glsl_pbo get_pbo(int size); void cleanupContext(); movit::ResourcePool *get_resource_pool() { return resource_pool; } static void set_chain(mlt_service, GlslChain *); static GlslChain *get_chain(mlt_service); static movit::Effect *get_effect(mlt_service, mlt_frame); static movit::Effect *set_effect(mlt_service, mlt_frame, movit::Effect *); static MltInput *get_input(mlt_producer, mlt_frame); static MltInput *set_input(mlt_producer, mlt_frame, MltInput *); static uint8_t *get_input_pixel_pointer(mlt_producer, mlt_frame); static uint8_t *set_input_pixel_pointer(mlt_producer, mlt_frame, uint8_t *); static mlt_service get_effect_input(mlt_service, mlt_frame); static void set_effect_input(mlt_service, mlt_frame, mlt_service); static void get_effect_secondary_input(mlt_service, mlt_frame, mlt_service *, mlt_frame *); static void set_effect_secondary_input(mlt_service, mlt_frame, mlt_service, mlt_frame); static void get_effect_third_input(mlt_service, mlt_frame, mlt_service *, mlt_frame *); static void set_effect_third_input(mlt_service, mlt_frame, mlt_service, mlt_frame); int render_frame_texture(movit::EffectChain *, mlt_frame, int width, int height, uint8_t **image); int render_frame_rgba(movit::EffectChain *, mlt_frame, int width, int height, uint8_t **image); int render_frame_ycbcr(movit::EffectChain *, mlt_frame, int width, int height, uint8_t **image); static void lock_service(mlt_frame frame); static void unlock_service(mlt_frame frame); private: static void *get_frame_specific_data(mlt_service service, mlt_frame frame, const char *key, int *length); static int set_frame_specific_data(mlt_service service, mlt_frame frame, const char *key, void *value, int length, mlt_destructor destroy, mlt_serialiser serialise); static void onInit(mlt_properties owner, GlslManager *filter, mlt_event_data); static void onClose(mlt_properties owner, GlslManager *filter, mlt_event_data); static void onServiceChanged(mlt_properties owner, mlt_service service); static void onPropertyChanged(mlt_properties owner, mlt_service service, const char *property); movit::ResourcePool *resource_pool; Mlt::Deque texture_list; Mlt::Deque syncs_to_delete; glsl_pbo pbo; Mlt::Event *initEvent; Mlt::Event *closeEvent; GLsync prev_sync; }; #endif // GLSL_MANAGER_H mlt-7.22.0/src/modules/movit/filter_glsl_manager.yml000664 000000 000000 00000000515 14531534050 022477 0ustar00rootroot000000 000000 schema_version: 7.0 type: filter identifier: glsl.manager title: GLSL Manager version: 1 copyright: Dan Dennedy creator: Steinar H. Gunderson license: GPLv2 language: en description: > This is a special filter to manage OpenGL and Movit. See https://mltframework.org/docs/opengl/ for more information. tags: - Video - Hidden mlt-7.22.0/src/modules/movit/filter_movit_blur.cpp000664 000000 000000 00000006072 14531534050 022213 0ustar00rootroot000000 000000 /* * filter_movit_blur.cpp * Copyright (C) 2013-2020 Dan Dennedy * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include "filter_glsl_manager.h" #include using namespace movit; static int get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { mlt_filter filter = (mlt_filter) mlt_frame_pop_service(frame); mlt_properties properties = MLT_FILTER_PROPERTIES(filter); GlslManager::get_instance()->lock_service(frame); double radius = mlt_properties_anim_get_double(properties, "radius", mlt_filter_get_position(filter, frame), mlt_filter_get_length2(filter, frame)); mlt_profile profile = mlt_service_profile(MLT_FILTER_SERVICE(filter)); GlslManager::get_instance()->unlock_service(frame); *format = mlt_image_movit; int error = mlt_frame_get_image(frame, image, format, width, height, writable); if (*width < 1 || *height < 1) { return error; } radius *= mlt_profile_scale_width(profile, *width); mlt_properties_set_double(properties, "_movit.parms.float.radius", radius); GlslManager::set_effect_input(MLT_FILTER_SERVICE(filter), frame, (mlt_service) *image); GlslManager::set_effect(MLT_FILTER_SERVICE(filter), frame, new BlurEffect); *image = (uint8_t *) MLT_FILTER_SERVICE(filter); return error; } static mlt_frame process(mlt_filter filter, mlt_frame frame) { mlt_frame_push_service(frame, filter); mlt_frame_push_get_image(frame, get_image); return frame; } extern "C" { mlt_filter filter_movit_blur_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = NULL; GlslManager *glsl = GlslManager::get_instance(); if (glsl && (filter = mlt_filter_new())) { mlt_properties properties = MLT_FILTER_PROPERTIES(filter); glsl->add_ref(properties); mlt_properties_set_double(properties, "radius", 3); filter->process = process; } return filter; } } mlt-7.22.0/src/modules/movit/filter_movit_blur.yml000664 000000 000000 00000001020 14531534050 022216 0ustar00rootroot000000 000000 schema_version: 7.0 type: filter identifier: movit.blur title: Blur (GLSL) version: 1 copyright: Dan Dennedy creator: Steinar H. Gunderson license: GPLv2 language: en tags: - Video description: > A separable 2D blur implemented by a combination of mipmap filtering and convolution (essentially giving a convolution with a piecewise linear approximation to the true impulse response). parameters: - identifier: radius title: Radius type: float minimum: 0 default: 3 mutable: yes animation: yes mlt-7.22.0/src/modules/movit/filter_movit_convert.cpp000664 000000 000000 00000101770 14531534050 022730 0ustar00rootroot000000 000000 /* * filter_movit_convert.cpp * Copyright (C) 2013-2023 Dan Dennedy * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include "filter_glsl_manager.h" #include "mlt_flip_effect.h" #include "mlt_movit_input.h" #include #include #include using namespace movit; static void set_movit_parameters(GlslChain *chain, mlt_service service, mlt_frame frame); static void yuv422_to_yuv422p(uint8_t *yuv422, uint8_t *yuv422p, int width, int height) { uint8_t *Y = yuv422p; uint8_t *U = Y + width * height; uint8_t *V = U + width * height / 2; int n = width * height / 2 + 1; while (--n) { *Y++ = *yuv422++; *U++ = *yuv422++; *Y++ = *yuv422++; *V++ = *yuv422++; } } static int convert_on_cpu(mlt_frame frame, uint8_t **image, mlt_image_format *format, mlt_image_format output_format) { int error = 0; mlt_filter cpu_csc = (mlt_filter) mlt_properties_get_data(MLT_FRAME_PROPERTIES(frame), "_movit cpu_convert", NULL); if (cpu_csc) { int (*save_fp)(mlt_frame self, uint8_t * *image, mlt_image_format * input, mlt_image_format output) = frame->convert_image; frame->convert_image = NULL; mlt_filter_process(cpu_csc, frame); error = frame->convert_image(frame, image, format, output_format); frame->convert_image = save_fp; } else { error = 1; } return error; } static void delete_chain(EffectChain *chain) { delete chain; } // Copied from libavcodec, but we can not add that as a dependency to this module // simply for this. enum AVColorTransferCharacteristic { AVCOL_TRC_BT709 = 1, ///< also ITU-R BT1361 AVCOL_TRC_UNSPECIFIED = 2, AVCOL_TRC_GAMMA22 = 4, ///< also ITU-R BT470M / ITU-R BT1700 625 PAL & SECAM AVCOL_TRC_GAMMA28 = 5, ///< also ITU-R BT470BG AVCOL_TRC_SMPTE170M = 6, ///< also ITU-R BT601-6 525 or 625 / ITU-R BT1358 525 or 625 / ITU-R BT1700 NTSC AVCOL_TRC_SMPTE240M = 7, AVCOL_TRC_LINEAR = 8, ///< "Linear transfer characteristics" AVCOL_TRC_LOG = 9, ///< "Logarithmic transfer characteristic (100:1 range)" AVCOL_TRC_LOG_SQRT = 10, ///< "Logarithmic transfer characteristic (100 * Sqrt( 10 ) : 1 range)" AVCOL_TRC_IEC61966_2_4 = 11, ///< IEC 61966-2-4 AVCOL_TRC_BT1361_ECG = 12, ///< ITU-R BT1361 Extended Colour Gamut AVCOL_TRC_IEC61966_2_1 = 13, ///< IEC 61966-2-1 (sRGB or sYCC) AVCOL_TRC_BT2020_10 = 14, ///< ITU-R BT2020 for 10 bit system AVCOL_TRC_BT2020_12 = 15, ///< ITU-R BT2020 for 12 bit system AVCOL_TRC_NB, ///< Not part of ABI }; // Get the gamma from the frame "color_trc" property as set by producer or this filter. static GammaCurve getGammaCurve(int color_trc) { switch (color_trc) { case AVCOL_TRC_LINEAR: return GAMMA_LINEAR; case AVCOL_TRC_GAMMA22: case AVCOL_TRC_IEC61966_2_1: return GAMMA_sRGB; case AVCOL_TRC_BT2020_10: return GAMMA_REC_2020_10_BIT; case AVCOL_TRC_BT2020_12: return GAMMA_REC_2020_12_BIT; default: return GAMMA_REC_709; } } // Get the gamma from the consumer's "color_trc" property. // Also, update the frame's color_trc property with the selection. static GammaCurve getGammaCurve(mlt_properties properties) { const char *color_trc = mlt_properties_get(properties, "consumer.color_trc"); if (color_trc) { // If specified with enum or int. int n = mlt_properties_get_int(properties, "consumer.color_trc"); switch (n) { case AVCOL_TRC_BT709: case AVCOL_TRC_SMPTE170M: mlt_properties_set_int(properties, "color_trc", n); return GAMMA_REC_709; case AVCOL_TRC_LINEAR: mlt_properties_set_int(properties, "color_trc", n); return GAMMA_LINEAR; case AVCOL_TRC_BT2020_10: mlt_properties_set_int(properties, "color_trc", n); return GAMMA_REC_2020_10_BIT; case AVCOL_TRC_BT2020_12: mlt_properties_set_int(properties, "color_trc", n); return GAMMA_REC_2020_12_BIT; default: // If specified by string. if (!strcmp(color_trc, "bt709")) { mlt_properties_set_int(properties, "color_trc", AVCOL_TRC_BT709); return GAMMA_REC_709; } else if (!strcmp(color_trc, "smpte170m")) { mlt_properties_set_int(properties, "color_trc", AVCOL_TRC_SMPTE170M); return GAMMA_REC_709; } else if (!strcmp(color_trc, "linear")) { mlt_properties_set_int(properties, "color_trc", AVCOL_TRC_LINEAR); return GAMMA_LINEAR; } else if (!strcmp(color_trc, "bt2020_10bit")) { mlt_properties_set_int(properties, "color_trc", AVCOL_TRC_BT2020_10); return GAMMA_REC_2020_10_BIT; } else if (!strcmp(color_trc, "bt2020_12bit")) { mlt_properties_set_int(properties, "color_trc", AVCOL_TRC_BT2020_12); return GAMMA_REC_2020_12_BIT; } break; } } return GAMMA_sRGB; } static void get_format_from_properties(mlt_properties properties, ImageFormat *image_format, YCbCrFormat *ycbcr_format) { switch (mlt_properties_get_int(properties, "colorspace")) { case 601: ycbcr_format->luma_coefficients = YCBCR_REC_601; break; case 709: default: ycbcr_format->luma_coefficients = YCBCR_REC_709; break; } if (image_format) { switch (mlt_properties_get_int(properties, "color_primaries")) { case 601625: image_format->color_space = COLORSPACE_REC_601_625; break; case 601525: image_format->color_space = COLORSPACE_REC_601_525; break; case 709: default: image_format->color_space = COLORSPACE_REC_709; break; } image_format->gamma_curve = getGammaCurve(mlt_properties_get_int(properties, "color_trc")); } if (mlt_properties_get_int(properties, "force_full_luma")) { ycbcr_format->full_range = true; } else { ycbcr_format->full_range = (mlt_properties_get_int(properties, "full_range") == 1); } // TODO: make new frame properties set by producers ycbcr_format->cb_x_position = ycbcr_format->cr_x_position = 0.0f; ycbcr_format->cb_y_position = ycbcr_format->cr_y_position = 0.5f; } static void build_fingerprint(mlt_service service, mlt_frame frame, std::string *fingerprint) { if (service == (mlt_service) -1) { fingerprint->append("input"); return; } mlt_service input_a = GlslManager::get_effect_input(service, frame); fingerprint->push_back('('); build_fingerprint(input_a, frame, fingerprint); fingerprint->push_back(')'); mlt_frame frame_b; mlt_service input_b; GlslManager::get_effect_secondary_input(service, frame, &input_b, &frame_b); if (input_b) { fingerprint->push_back('('); build_fingerprint(input_b, frame_b, fingerprint); fingerprint->push_back(')'); } GlslManager::get_effect_third_input(service, frame, &input_b, &frame_b); if (input_b) { fingerprint->push_back('('); build_fingerprint(input_b, frame_b, fingerprint); fingerprint->push_back(')'); } fingerprint->push_back('('); fingerprint->append(mlt_properties_get(MLT_SERVICE_PROPERTIES(service), "_unique_id")); const char *effect_fingerprint = mlt_properties_get(MLT_SERVICE_PROPERTIES(service), "_movit fingerprint"); if (effect_fingerprint) { fingerprint->push_back('['); fingerprint->append(effect_fingerprint); fingerprint->push_back(']'); } bool disable = mlt_properties_get_int(MLT_SERVICE_PROPERTIES(service), "_movit.parms.int.disable"); if (disable) { fingerprint->push_back('d'); } fingerprint->push_back(')'); } static Effect *build_movit_chain(mlt_service service, mlt_frame frame, GlslChain *chain) { if (service == (mlt_service) -1) { mlt_producer producer = mlt_producer_cut_parent(mlt_frame_get_original_producer(frame)); MltInput *input = GlslManager::get_input(producer, frame); GlslManager::set_input(producer, frame, NULL); chain->effect_chain->add_input(input->get_input()); chain->inputs.insert(std::make_pair(producer, input)); return input->get_input(); } Effect *effect = GlslManager::get_effect(service, frame); assert(effect); GlslManager::set_effect(service, frame, NULL); mlt_service input_a = GlslManager::get_effect_input(service, frame); mlt_service input_b, input_c; mlt_frame frame_b, frame_c; GlslManager::get_effect_secondary_input(service, frame, &input_b, &frame_b); GlslManager::get_effect_third_input(service, frame, &input_c, &frame_c); Effect *effect_a = build_movit_chain(input_a, frame, chain); if (input_c && input_b) { Effect *effect_b = build_movit_chain(input_b, frame_b, chain); Effect *effect_c = build_movit_chain(input_c, frame_c, chain); chain->effect_chain->add_effect(effect, effect_a, effect_b, effect_c); } else if (input_b) { Effect *effect_b = build_movit_chain(input_b, frame_b, chain); chain->effect_chain->add_effect(effect, effect_a, effect_b); } else { chain->effect_chain->add_effect(effect, effect_a); } chain->effects.insert(std::make_pair(service, effect)); return effect; } static void dispose_movit_effects(mlt_service service, mlt_frame frame) { if (service == (mlt_service) -1) { mlt_producer producer = mlt_producer_cut_parent(mlt_frame_get_original_producer(frame)); delete GlslManager::get_input(producer, frame); GlslManager::set_input(producer, frame, NULL); return; } delete GlslManager::get_effect(service, frame); GlslManager::set_effect(service, frame, NULL); mlt_service input_a = GlslManager::get_effect_input(service, frame); mlt_service input_b; mlt_frame frame_b; GlslManager::get_effect_secondary_input(service, frame, &input_b, &frame_b); dispose_movit_effects(input_a, frame); if (input_b) { dispose_movit_effects(input_b, frame_b); } GlslManager::get_effect_third_input(service, frame, &input_b, &frame_b); if (input_b) { dispose_movit_effects(input_b, frame_b); } } static void finalize_movit_chain(mlt_service leaf_service, mlt_frame frame, mlt_image_format format) { GlslChain *chain = GlslManager::get_chain(leaf_service); std::string new_fingerprint; build_fingerprint(leaf_service, frame, &new_fingerprint); // Build the chain if needed. if (!chain || new_fingerprint != chain->fingerprint) { mlt_log_debug(leaf_service, "=== CREATING NEW CHAIN (old chain=%p, leaf=%p, fingerprint=%s) ===\n", chain, leaf_service, new_fingerprint.c_str()); mlt_profile profile = mlt_service_profile(leaf_service); chain = new GlslChain; chain->effect_chain = new EffectChain(profile->display_aspect_num, profile->display_aspect_den, GlslManager::get_instance()->get_resource_pool()); chain->fingerprint = new_fingerprint; build_movit_chain(leaf_service, frame, chain); set_movit_parameters(chain, leaf_service, frame); chain->effect_chain->add_effect(new Mlt::VerticalFlip); ImageFormat output_format; output_format.color_space = COLORSPACE_sRGB; output_format.gamma_curve = getGammaCurve(MLT_FRAME_PROPERTIES(frame)); if (format == mlt_image_yuv444p10 || format == mlt_image_yuv420p10) { YCbCrFormat ycbcr_format = {}; get_format_from_properties(MLT_FRAME_PROPERTIES(frame), nullptr, &ycbcr_format); ycbcr_format.num_levels = 1024; ycbcr_format.chroma_subsampling_x = ycbcr_format.chroma_subsampling_y = 1; chain->effect_chain->add_ycbcr_output(output_format, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED, ycbcr_format, movit::YCBCR_OUTPUT_INTERLEAVED, GL_UNSIGNED_SHORT); chain->effect_chain->set_dither_bits(16); } else { chain->effect_chain->add_output(output_format, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED); chain->effect_chain->set_dither_bits(8); } chain->effect_chain->finalize(); GlslManager::set_chain(leaf_service, chain); } else { // Delete all the created Effect instances to avoid memory leaks. dispose_movit_effects(leaf_service, frame); } } static void set_movit_parameters(GlslChain *chain, mlt_service service, mlt_frame frame) { if (service == (mlt_service) -1) { mlt_producer producer = mlt_producer_cut_parent(mlt_frame_get_original_producer(frame)); MltInput *input = chain->inputs[producer]; if (input) input->set_pixel_data(GlslManager::get_input_pixel_pointer(producer, frame)); return; } Effect *effect = chain->effects[service]; mlt_service input_a = GlslManager::get_effect_input(service, frame); set_movit_parameters(chain, input_a, frame); mlt_service input_b; mlt_frame frame_b; GlslManager::get_effect_secondary_input(service, frame, &input_b, &frame_b); if (input_b) { set_movit_parameters(chain, input_b, frame_b); } GlslManager::get_effect_third_input(service, frame, &input_b, &frame_b); if (input_b) { set_movit_parameters(chain, input_b, frame_b); } mlt_properties properties = MLT_SERVICE_PROPERTIES(service); int count = mlt_properties_count(properties); for (int i = 0; i < count; ++i) { const char *name = mlt_properties_get_name(properties, i); if (strncmp(name, "_movit.parms.float.", strlen("_movit.parms.float.")) == 0 && mlt_properties_get_value(properties, i)) { effect->set_float(name + strlen("_movit.parms.float."), mlt_properties_get_double(properties, name)); } if (strncmp(name, "_movit.parms.int.", strlen("_movit.parms.int.")) == 0 && mlt_properties_get_value(properties, i)) { effect->set_int(name + strlen("_movit.parms.int."), mlt_properties_get_int(properties, name)); } if (strncmp(name, "_movit.parms.vec3.", strlen("_movit.parms.vec3.")) == 0 && strcmp(name + strlen(name) - 3, "[0]") == 0 && mlt_properties_get_value(properties, i)) { float val[3]; char *name_copy = strdup(name); char *index_char = name_copy + strlen(name_copy) - 2; val[0] = mlt_properties_get_double(properties, name_copy); *index_char = '1'; val[1] = mlt_properties_get_double(properties, name_copy); *index_char = '2'; val[2] = mlt_properties_get_double(properties, name_copy); index_char[-1] = '\0'; effect->set_vec3(name_copy + strlen("_movit.parms.vec3."), val); free(name_copy); } if (strncmp(name, "_movit.parms.vec4.", strlen("_movit.parms.vec4.")) == 0 && strcmp(name + strlen(name) - 3, "[0]") == 0 && mlt_properties_get_value(properties, i)) { float val[4]; char *name_copy = strdup(name); char *index_char = name_copy + strlen(name_copy) - 2; val[0] = mlt_properties_get_double(properties, name_copy); *index_char = '1'; val[1] = mlt_properties_get_double(properties, name_copy); *index_char = '2'; val[2] = mlt_properties_get_double(properties, name_copy); *index_char = '3'; val[3] = mlt_properties_get_double(properties, name_copy); index_char[-1] = '\0'; effect->set_vec4(name_copy + strlen("_movit.parms.vec4."), val); free(name_copy); } } } static void dispose_pixel_pointers(GlslChain *chain, mlt_service service, mlt_frame frame) { if (service == (mlt_service) -1) { mlt_producer producer = mlt_producer_cut_parent(mlt_frame_get_original_producer(frame)); MltInput *input = chain->inputs[producer]; if (input) input->invalidate_pixel_data(); mlt_pool_release(GlslManager::get_input_pixel_pointer(producer, frame)); return; } mlt_service input_a = GlslManager::get_effect_input(service, frame); dispose_pixel_pointers(chain, input_a, frame); mlt_service input_b; mlt_frame frame_b; GlslManager::get_effect_secondary_input(service, frame, &input_b, &frame_b); if (input_b) { dispose_pixel_pointers(chain, input_b, frame_b); } GlslManager::get_effect_third_input(service, frame, &input_b, &frame_b); if (input_b) { dispose_pixel_pointers(chain, input_b, frame_b); } } static int movit_render(EffectChain *chain, mlt_frame frame, mlt_image_format *format, mlt_image_format output_format, int width, int height, uint8_t **image) { if (width < 1 || height < 1) { mlt_log_error(NULL, "Invalid frame size for movit_render: %dx%d.\n", width, height); return 1; } GlslManager *glsl = GlslManager::get_instance(); int error; if (output_format == mlt_image_opengl_texture) { error = glsl->render_frame_texture(chain, frame, width, height, image); } else if (output_format == mlt_image_yuv444p10 || output_format == mlt_image_yuv420p10) { error = glsl->render_frame_ycbcr(chain, frame, width, height, image); if (!error && output_format != mlt_image_yuv444p10) { *format = mlt_image_yuv444p10; error = convert_on_cpu(frame, image, format, output_format); } } else { error = glsl->render_frame_rgba(chain, frame, width, height, image); if (!error && output_format != mlt_image_rgba) { *format = mlt_image_rgba; error = convert_on_cpu(frame, image, format, output_format); } } return error; } // Create an MltInput for an image with the given format and dimensions. static MltInput *create_input(mlt_properties properties, mlt_image_format format, int aspect_width, int aspect_height, int width, int height) { if (width < 1 || height < 1) { mlt_log_error(NULL, "Invalid frame size for create_input: %dx%d.\n", width, height); return nullptr; } MltInput *input = new MltInput(format); if (format == mlt_image_rgba) { // TODO: Get the color space if available. input->useFlatInput(FORMAT_RGBA_POSTMULTIPLIED_ALPHA, width, height); } else if (format == mlt_image_rgb) { // TODO: Get the color space if available. input->useFlatInput(FORMAT_RGB, width, height); } else if (format == mlt_image_yuv420p) { ImageFormat image_format = {}; YCbCrFormat ycbcr_format = {}; get_format_from_properties(properties, &image_format, &ycbcr_format); ycbcr_format.chroma_subsampling_x = ycbcr_format.chroma_subsampling_y = 2; input->useYCbCrInput(image_format, ycbcr_format, width, height); } else if (format == mlt_image_yuv422) { ImageFormat image_format = {}; YCbCrFormat ycbcr_format = {}; get_format_from_properties(properties, &image_format, &ycbcr_format); ycbcr_format.chroma_subsampling_x = 2; ycbcr_format.chroma_subsampling_y = 1; input->useYCbCrInput(image_format, ycbcr_format, width, height); } else if (format == mlt_image_yuv420p10) { ImageFormat image_format = {}; YCbCrFormat ycbcr_format = {}; get_format_from_properties(properties, &image_format, &ycbcr_format); ycbcr_format.chroma_subsampling_x = 2; ycbcr_format.chroma_subsampling_y = 2; ycbcr_format.num_levels = 1024; input->useYCbCrInput(image_format, ycbcr_format, width, height); } else if (format == mlt_image_yuv444p10) { ImageFormat image_format = {}; YCbCrFormat ycbcr_format = {}; get_format_from_properties(properties, &image_format, &ycbcr_format); ycbcr_format.chroma_subsampling_x = 1; ycbcr_format.chroma_subsampling_y = 1; ycbcr_format.num_levels = 1024; input->useYCbCrInput(image_format, ycbcr_format, width, height); } return input; } // Make a copy of the given image (allocated using mlt_pool_alloc) suitable // to pass as pixel pointer to an MltInput (created using create_input // with the same parameters), and return that pointer. static uint8_t *make_input_copy(mlt_image_format format, uint8_t *image, int width, int height) { if (width < 1 || height < 1) { mlt_log_error(NULL, "Invalid frame size for make_input_copy: %dx%d.\n", width, height); return NULL; } int img_size = mlt_image_format_size(format, width, height, NULL); uint8_t *img_copy = (uint8_t *) mlt_pool_alloc(img_size); if (format == mlt_image_yuv422) { yuv422_to_yuv422p(image, img_copy, width, height); } else { memcpy(img_copy, image, img_size); } return img_copy; } static int convert_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, mlt_image_format output_format) { // Nothing to do! if (*format == output_format) return 0; mlt_properties properties = MLT_FRAME_PROPERTIES(frame); mlt_log_debug(NULL, "filter_movit_convert: %s -> %s (%d)\n", mlt_image_format_name(*format), mlt_image_format_name(output_format), mlt_frame_get_position(frame)); // Use CPU if glsl not initialized or not supported. GlslManager *glsl = GlslManager::get_instance(); if (!glsl || !glsl->get_int("glsl_supported")) return convert_on_cpu(frame, image, format, output_format); // Do non-GL image conversions on a CPU-based image converter. if (*format != mlt_image_movit && output_format != mlt_image_movit && output_format != mlt_image_opengl_texture) return convert_on_cpu(frame, image, format, output_format); int error = 0; int width = mlt_properties_get_int(properties, "width"); int height = mlt_properties_get_int(properties, "height"); if (width < 1 || height < 1) { mlt_log_error(NULL, "Invalid frame size for convert_image %dx%d.\n", width, height); return 1; } GlslManager::get_instance()->lock_service(frame); // If we're at the beginning of a series of Movit effects, store the input // sent into the chain. if (output_format == mlt_image_movit) { if (*format != mlt_image_rgba && mlt_frame_get_alpha(frame)) { if (!convert_on_cpu(frame, image, format, mlt_image_rgba)) { *format = mlt_image_rgba; } } mlt_producer producer = mlt_producer_cut_parent(mlt_frame_get_original_producer(frame)); mlt_profile profile = mlt_service_profile(MLT_PRODUCER_SERVICE(producer)); MltInput *input = create_input(properties, *format, profile->width, profile->height, width, height); if (!input) { mlt_log_error(nullptr, "filter movit.convert: create_input failed\n"); return 1; } GlslManager::set_input(producer, frame, input); uint8_t *img_copy = make_input_copy(*format, *image, width, height); if (!img_copy) { mlt_log_error(nullptr, "filter movit.convert: make_input_copy failed\n"); delete input; return 1; } GlslManager::set_input_pixel_pointer(producer, frame, img_copy); *image = (uint8_t *) -1; mlt_frame_set_image(frame, *image, 0, NULL); } // If we're at the _end_ of a series of Movit effects, render the chain. if (*format == mlt_image_movit) { mlt_service leaf_service = (mlt_service) *image; if (leaf_service == (mlt_service) -1) { // Something on the way requested conversion to mlt_glsl, // but never added an effect. Don't build a Movit chain; // just do the conversion and we're done. mlt_producer producer = mlt_producer_cut_parent(mlt_frame_get_original_producer(frame)); MltInput *input = GlslManager::get_input(producer, frame); *image = GlslManager::get_input_pixel_pointer(producer, frame); *format = input->get_format(); delete input; GlslManager::get_instance()->unlock_service(frame); return convert_on_cpu(frame, image, format, output_format); } // Construct the chain unless we already have a good one. finalize_movit_chain(leaf_service, frame, output_format); // Set per-frame parameters now that we know which Effect instances to set them on. // (finalize_movit_chain may already have done this, though, but twice doesn't hurt.) GlslChain *chain = GlslManager::get_chain(leaf_service); set_movit_parameters(chain, leaf_service, frame); error = movit_render(chain->effect_chain, frame, format, output_format, width, height, image); dispose_pixel_pointers(chain, leaf_service, frame); } // If we've been asked to render some frame directly to a texture (without any // effects in-between), we create a new mini-chain to do so. if (*format != mlt_image_movit && output_format == mlt_image_opengl_texture) { // We might already have a texture from a previous conversion from mlt_image_movit. glsl_texture texture = (glsl_texture) mlt_properties_get_data(properties, "movit.convert.texture", NULL); // XXX: requires a special property set on the frame by the app for now // because we do not have reliable way to clear the texture property // when a downstream filter has changed image. if (texture && mlt_properties_get_int(properties, "movit.convert.use_texture")) { *image = (uint8_t *) &texture->texture; mlt_frame_set_image(frame, *image, 0, NULL); } else { // Use a separate chain to convert image in RAM to OpenGL texture. // Use cached chain if available and compatible. Mlt::Producer producer(mlt_producer_cut_parent(mlt_frame_get_original_producer(frame))); EffectChain *chain = (EffectChain *) producer.get_data("movit.convert.chain"); MltInput *input = (MltInput *) producer.get_data("movit.convert.input"); int w = producer.get_int("movit.convert.width"); int h = producer.get_int("movit.convert.height"); mlt_image_format f = (mlt_image_format) producer.get_int("movit.convert.format"); if (!chain || !input || width != w || height != h || *format != f) { chain = new EffectChain(width, height, GlslManager::get_instance()->get_resource_pool()); input = create_input(properties, *format, width, height, width, height); if (!input) { delete chain; return 1; } chain->add_input(input->get_input()); chain->add_effect(new Mlt::VerticalFlip()); ImageFormat movit_output_format; movit_output_format.color_space = COLORSPACE_sRGB; movit_output_format.gamma_curve = getGammaCurve(properties); chain->add_output(movit_output_format, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED); chain->set_dither_bits(8); chain->finalize(); producer.set("movit.convert.chain", chain, 0, (mlt_destructor) delete_chain); producer.set("movit.convert.input", input, 0, NULL); producer.set("movit.convert.width", width); producer.set("movit.convert.height", height); producer.set("movit.convert.format", *format); } if (*format == mlt_image_yuv422) { // We need to convert to planar, which make_input_copy() will do for us. uint8_t *planar = make_input_copy(*format, *image, width, height); if (!planar) { return 1; } input->set_pixel_data(planar); error = movit_render(chain, frame, format, output_format, width, height, image); mlt_pool_release(planar); } else { input->set_pixel_data(*image); error = movit_render(chain, frame, format, output_format, width, height, image); } } } GlslManager::get_instance()->unlock_service(frame); mlt_properties_set_int(properties, "format", output_format); *format = output_format; return error; } static mlt_frame process(mlt_filter filter, mlt_frame frame) { // Set a default colorspace on the frame if not yet set by the producer. // The producer may still change it during get_image. // This way we do not have to modify each producer to set a valid colorspace. mlt_properties properties = MLT_FRAME_PROPERTIES(frame); if (mlt_properties_get_int(properties, "colorspace") <= 0) mlt_properties_set_int(properties, "colorspace", mlt_service_profile(MLT_FILTER_SERVICE(filter))->colorspace); frame->convert_image = convert_image; mlt_filter cpu_csc = (mlt_filter) mlt_properties_get_data(MLT_FILTER_PROPERTIES(filter), "cpu_convert", NULL); mlt_properties_inc_ref(MLT_FILTER_PROPERTIES(cpu_csc)); mlt_properties_set_data(properties, "_movit cpu_convert", cpu_csc, 0, (mlt_destructor) mlt_filter_close, NULL); return frame; } static mlt_filter create_filter(mlt_profile profile, const char *effect) { mlt_filter filter; char *id = strdup(effect); char *arg = strchr(id, ':'); if (arg != NULL) *arg++ = '\0'; filter = mlt_factory_filter(profile, id, arg); if (filter) mlt_properties_set_int(MLT_FILTER_PROPERTIES(filter), "_loader", 1); free(id); return filter; } extern "C" { mlt_filter filter_movit_convert_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = NULL; GlslManager *glsl = GlslManager::get_instance(); if (glsl && (filter = mlt_filter_new())) { mlt_properties properties = MLT_FILTER_PROPERTIES(filter); glsl->add_ref(properties); mlt_filter cpu_csc = create_filter(profile, "avcolor_space"); if (!cpu_csc) cpu_csc = create_filter(profile, "imageconvert"); if (cpu_csc) mlt_properties_set_data(MLT_FILTER_PROPERTIES(filter), "cpu_convert", cpu_csc, 0, (mlt_destructor) mlt_filter_close, NULL); filter->process = process; } return filter; } } mlt-7.22.0/src/modules/movit/filter_movit_convert.yml000664 000000 000000 00000000746 14531534050 022750 0ustar00rootroot000000 000000 schema_version: 7.0 type: filter identifier: movit.convert title: Image Converter (GLSL) version: 1 copyright: Dan Dennedy creator: Steinar H. Gunderson license: GPLv2 language: en description: Remove pixels from the edges of the video. tags: - Video - Hidden description: Converts the colorspace and pixel format. notes: > This is not intended to be created directly. Rather, the loader producer loads it if it is available to set the convert_image function pointer on frames. mlt-7.22.0/src/modules/movit/filter_movit_crop.cpp000664 000000 000000 00000013316 14531534050 022211 0ustar00rootroot000000 000000 /* * filter_movit_crop.cpp * Copyright (C) 2013-2023 Dan Dennedy * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include "filter_glsl_manager.h" #include "optional_effect.h" #include using namespace movit; static int get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { int error = 0; mlt_properties properties = MLT_FRAME_PROPERTIES(frame); mlt_filter filter = (mlt_filter) mlt_frame_pop_service(frame); mlt_profile profile = mlt_service_profile(MLT_FILTER_SERVICE(filter)); mlt_image_format requested_format = *format; // Correct width/height if necessary *width = mlt_properties_get_int(properties, "crop.original_width"); *height = mlt_properties_get_int(properties, "crop.original_height"); if (*width < 1 || *height < 1) { *width = mlt_properties_get_int(properties, "meta.media.width"); *height = mlt_properties_get_int(properties, "meta.media.height"); } if (*width < 1 || *height < 1) { *width = profile->width; *height = profile->height; } if (*width < 1 || *height < 1) { mlt_log_error(MLT_FILTER_SERVICE(filter), "Invalid size for get_image: %dx%d", *width, *height); return error; } mlt_properties_set_int(properties, "rescale_width", *width); mlt_properties_set_int(properties, "rescale_height", *height); // Get the image as requested // *format = (mlt_image_format) mlt_properties_get_int( MLT_PRODUCER_PROPERTIES(producer), "_movit image_format" ); // This is needed to prevent conversion to mlt_image_movit after producer, // deinterlace, or fieldorder, The latter two can force output of // an image after it had already been converted to glsl. *format = mlt_image_none; error = mlt_frame_get_image(frame, image, format, width, height, writable); // Skip processing if requested. if (requested_format == mlt_image_none) return error; if (!error && *format != mlt_image_movit && frame->convert_image) { // Pin the requested format to the first one returned. // mlt_properties_set_int( MLT_PRODUCER_PROPERTIES(producer), "_movit image_format", *format ); error = frame->convert_image(frame, image, format, mlt_image_movit); } if (!error) { double left = mlt_properties_get_double(properties, "crop.left"); double right = mlt_properties_get_double(properties, "crop.right"); double top = mlt_properties_get_double(properties, "crop.top"); double bottom = mlt_properties_get_double(properties, "crop.bottom"); int owidth = *width - left - right; int oheight = *height - top - bottom; owidth = owidth < 1 ? 1 : owidth; oheight = oheight < 1 ? 1 : oheight; mlt_log_debug(MLT_FILTER_SERVICE(filter), "%dx%d -> %dx%d\n", *width, *height, owidth, oheight); mlt_properties properties = MLT_FILTER_PROPERTIES(filter); GlslManager::get_instance()->lock_service(frame); mlt_properties_set_int(properties, "_movit.parms.int.width", owidth); mlt_properties_set_int(properties, "_movit.parms.int.height", oheight); mlt_properties_set_double(properties, "_movit.parms.float.left", -left); mlt_properties_set_double(properties, "_movit.parms.float.top", -top); bool disable = (*width == owidth && *height == oheight); mlt_properties_set_int(properties, "_movit.parms.int.disable", disable); GlslManager::get_instance()->unlock_service(frame); } GlslManager::set_effect_input(MLT_FILTER_SERVICE(filter), frame, (mlt_service) *image); Effect *effect = GlslManager::set_effect(MLT_FILTER_SERVICE(filter), frame, new OptionalEffect); *image = (uint8_t *) MLT_FILTER_SERVICE(filter); RGBATuple border_color(0.0f, 0.0f, 0.0f, 1.0f); effect->set_vec4("border_color", (float *) &border_color); return error; } static mlt_frame process(mlt_filter filter, mlt_frame frame) { mlt_frame_push_service(frame, filter); mlt_frame_push_get_image(frame, get_image); return frame; } extern "C" mlt_filter filter_movit_crop_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = NULL; GlslManager *glsl = GlslManager::get_instance(); if (glsl && (filter = mlt_filter_new())) { mlt_properties properties = MLT_FILTER_PROPERTIES(filter); glsl->add_ref(properties); filter->process = process; } return filter; } mlt-7.22.0/src/modules/movit/filter_movit_crop.yml000664 000000 000000 00000001043 14531534050 022222 0ustar00rootroot000000 000000 schema_version: 7.0 type: filter identifier: movit.crop title: Crop (GLSL) version: 1 copyright: Dan Dennedy creator: Steinar H. Gunderson license: GPLv2 language: en description: Remove pixels from the edges of the video. tags: - Video notes: > This filter is meant to be included as a normalizing filter attached automatically by the loader so that cropping occurs early in the processing stack and can request the full resolution of the source image. Then, the core crop filter can be use to set the parameters of the crop operation. mlt-7.22.0/src/modules/movit/filter_movit_deconvolution_sharpen.cpp000664 000000 000000 00000011521 14531534050 025652 0ustar00rootroot000000 000000 /* * filter_deconvolution_sharpen.cpp * Copyright (C) 2013-2020 Dan Dennedy * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include "filter_glsl_manager.h" #include using namespace movit; static int get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { mlt_filter filter = (mlt_filter) mlt_frame_pop_service(frame); mlt_properties properties = MLT_FILTER_PROPERTIES(filter); GlslManager::get_instance()->lock_service(frame); mlt_position position = mlt_filter_get_position(filter, frame); mlt_position length = mlt_filter_get_length2(filter, frame); int matrix_size = mlt_properties_anim_get_int(properties, "matrix_size", position, length); double circle_radius = mlt_properties_anim_get_double(properties, "circle_radius", position, length); double gaussian_radius = mlt_properties_anim_get_double(properties, "gaussian_radius", position, length); double scale = mlt_profile_scale_width(mlt_service_profile(MLT_FILTER_SERVICE(filter)), *width); circle_radius *= scale; gaussian_radius *= scale; mlt_properties_set_int(properties, "_movit.parms.int.matrix_size", matrix_size); mlt_properties_set_double(properties, "_movit.parms.float.circle_radius", circle_radius); mlt_properties_set_double(properties, "_movit.parms.float.gaussian_radius", gaussian_radius); mlt_properties_set_double(properties, "_movit.parms.float.correlation", mlt_properties_anim_get_double(properties, "correlation", position, length)); mlt_properties_set_double(properties, "_movit.parms.float.noise", mlt_properties_anim_get_double(properties, "noise", position, length)); // DeconvolutionSharpenEffect compiles the matrix size into the shader, // so we need to regenerate the chain if this changes. char fingerprint[256]; snprintf(fingerprint, sizeof(fingerprint), "s=%d", matrix_size); mlt_properties_set(properties, "_movit fingerprint", fingerprint); GlslManager::get_instance()->unlock_service(frame); *format = mlt_image_movit; int error = mlt_frame_get_image(frame, image, format, width, height, writable); if (*width < 1 || *height < 1) { mlt_log_error(MLT_FILTER_SERVICE(filter), "Invalid size for get_image: %dx%d", *width, *height); return error; } GlslManager::set_effect_input(MLT_FILTER_SERVICE(filter), frame, (mlt_service) *image); GlslManager::set_effect(MLT_FILTER_SERVICE(filter), frame, new DeconvolutionSharpenEffect); *image = (uint8_t *) MLT_FILTER_SERVICE(filter); return error; } static mlt_frame process(mlt_filter filter, mlt_frame frame) { mlt_frame_push_service(frame, filter); mlt_frame_push_get_image(frame, get_image); return frame; } extern "C" { mlt_filter filter_deconvolution_sharpen_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = NULL; GlslManager *glsl = GlslManager::get_instance(); if (glsl && (filter = mlt_filter_new())) { mlt_properties properties = MLT_FILTER_PROPERTIES(filter); glsl->add_ref(properties); mlt_properties_set_int(properties, "matrix_size", 5); mlt_properties_set_double(properties, "circle_radius", 2.0); mlt_properties_set_double(properties, "gaussian_radius", 0.0); mlt_properties_set_double(properties, "correlation", 0.95); mlt_properties_set_double(properties, "noise", 0.01); filter->process = process; } return filter; } } mlt-7.22.0/src/modules/movit/filter_movit_deconvolution_sharpen.yml000664 000000 000000 00000002732 14531534050 025675 0ustar00rootroot000000 000000 schema_version: 7.0 type: filter identifier: movit.sharpen title: Deconvolution Sharpen (GLSL) version: 1 copyright: Dan Dennedy creator: Steinar H. Gunderson license: GPLv2 language: en tags: - Video description: > Deconvolution Sharpen is a filter that sharpens by way of deconvolution (i.e., trying to reverse the blur kernel, as opposed to just boosting high frequencies), more specifically by FIR Wiener filters. It is the same algorithm as used by the (now largely abandoned) Refocus plug-in for GIMP, and I suspect the same as in Photoshop's “Smart Sharpen” filter. The effect gives generally better results than unsharp masking, but can be very GPU intensive, and requires a fair bit of tweaking to get good results without ringing and/or excessive noise. parameters: - identifier: matrix_size title: Matrix Size type: integer minimum: 0 maximum: 25 default: 5 mutable: yes animation: yes - identifier: circle_radius title: Circle Radius type: float minimum: 0 default: 2 mutable: yes animation: yes - identifier: gaussian_radius title: Gaussian Radius type: float minimum: 0 default: 0 mutable: yes animation: yes - identifier: correlation title: Correlation type: float minimum: 0 default: 0.95 mutable: yes animation: yes - identifier: noise title: Noise Level type: float minimum: 0 default: 0.01 mutable: yes animation: yes mlt-7.22.0/src/modules/movit/filter_movit_diffusion.cpp000664 000000 000000 00000006542 14531534050 023237 0ustar00rootroot000000 000000 /* * filter_movit_diffusion.cpp * Copyright (C) 2013 Dan Dennedy * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include "filter_glsl_manager.h" #include using namespace movit; static int get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { mlt_filter filter = (mlt_filter) mlt_frame_pop_service(frame); mlt_properties properties = MLT_FILTER_PROPERTIES(filter); GlslManager::get_instance()->lock_service(frame); mlt_position position = mlt_filter_get_position(filter, frame); mlt_position length = mlt_filter_get_length2(filter, frame); mlt_properties_set_double(properties, "_movit.parms.float.radius", mlt_properties_anim_get_double(properties, "radius", position, length)); mlt_properties_set_double(properties, "_movit.parms.float.blurred_mix_amount", mlt_properties_anim_get_double(properties, "mix", position, length)); GlslManager::get_instance()->unlock_service(frame); *format = mlt_image_movit; int error = mlt_frame_get_image(frame, image, format, width, height, writable); if (*width < 1 || *height < 1) { mlt_log_error(MLT_FILTER_SERVICE(filter), "Invalid size for get_image: %dx%d", *width, *height); return error; } GlslManager::set_effect_input(MLT_FILTER_SERVICE(filter), frame, (mlt_service) *image); GlslManager::set_effect(MLT_FILTER_SERVICE(filter), frame, new DiffusionEffect); *image = (uint8_t *) MLT_FILTER_SERVICE(filter); return error; } static mlt_frame process(mlt_filter filter, mlt_frame frame) { mlt_frame_push_service(frame, filter); mlt_frame_push_get_image(frame, get_image); return frame; } extern "C" { mlt_filter filter_movit_diffusion_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = NULL; GlslManager *glsl = GlslManager::get_instance(); if (glsl && (filter = mlt_filter_new())) { mlt_properties properties = MLT_FILTER_PROPERTIES(filter); glsl->add_ref(properties); mlt_properties_set_double(properties, "radius", 3.0); mlt_properties_set_double(properties, "mix", 0.3); filter->process = process; } return filter; } } mlt-7.22.0/src/modules/movit/filter_movit_diffusion.yml000664 000000 000000 00000002067 14531534050 023254 0ustar00rootroot000000 000000 schema_version: 7.0 type: filter identifier: movit.diffusion title: Diffusion (GLSL) version: 1 copyright: Dan Dennedy creator: Steinar H. Gunderson license: GPLv2 language: en tags: - Video description: > There are many different effects that go under the name of "diffusion", seemingly all of the inspired by the effect you get when you put a diffusion filter in front of your camera lens. The effect most people want is a general flattening/smoothing of the light, and reduction of fine detail (most notably, blemishes in people's skin), without ruining edges, which a regular blur would do. We do a relatively simple version, sometimes known as "white diffusion", where we first blur the picture, and then overlay it on the original using the original as a matte. parameters: - identifier: radius title: Blur Radius type: float minimum: 0.0 default: 3.0 mutable: yes animation: yes - identifier: mix title: Blurriness type: float minimum: 0.0 maximum: 1.0 default: 0.3 mutable: yes animation: yes mlt-7.22.0/src/modules/movit/filter_movit_flip.cpp000664 000000 000000 00000005024 14531534050 022175 0ustar00rootroot000000 000000 /* * filter_movit_mirror.cpp * Copyright (C) 2019 Dan Dennedy * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include "filter_glsl_manager.h" #include "mlt_flip_effect.h" #include using namespace movit; static int get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { mlt_filter filter = (mlt_filter) mlt_frame_pop_service(frame); *format = mlt_image_movit; int error = mlt_frame_get_image(frame, image, format, width, height, writable); if (*width < 1 || *height < 1) { mlt_log_error(MLT_FILTER_SERVICE(filter), "Invalid size for get_image: %dx%d", *width, *height); return error; } GlslManager::set_effect_input(MLT_FILTER_SERVICE(filter), frame, (mlt_service) *image); GlslManager::set_effect(MLT_FILTER_SERVICE(filter), frame, new Mlt::VerticalFlip); *image = (uint8_t *) MLT_FILTER_SERVICE(filter); return error; } static mlt_frame process(mlt_filter filter, mlt_frame frame) { mlt_frame_push_service(frame, filter); mlt_frame_push_get_image(frame, get_image); return frame; } extern "C" { mlt_filter filter_movit_flip_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = NULL; GlslManager *glsl = GlslManager::get_instance(); if (glsl && (filter = mlt_filter_new())) { mlt_properties properties = MLT_FILTER_PROPERTIES(filter); glsl->add_ref(properties); filter->process = process; } return filter; } } mlt-7.22.0/src/modules/movit/filter_movit_flip.yml000664 000000 000000 00000000334 14531534050 022213 0ustar00rootroot000000 000000 schema_version: 0.1 type: filter identifier: movit.flip title: Flip (GLSL) version: 1 copyright: Dan Dennedy creator: Steinar H. Gunderson license: GPLv2 language: en description: A simple vertical flip. tags: - Video mlt-7.22.0/src/modules/movit/filter_movit_glow.cpp000664 000000 000000 00000010045 14531534050 022212 0ustar00rootroot000000 000000 /* * filter_movit_glow.cpp * Copyright (C) 2013-2020 Dan Dennedy * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include "filter_glsl_manager.h" #include using namespace movit; static int get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { mlt_filter filter = (mlt_filter) mlt_frame_pop_service(frame); mlt_properties properties = MLT_FILTER_PROPERTIES(filter); mlt_position position = mlt_filter_get_position(filter, frame); mlt_position length = mlt_filter_get_length2(filter, frame); GlslManager::get_instance()->lock_service(frame); double radius = mlt_properties_anim_get_double(properties, "radius", position, length); radius *= mlt_profile_scale_width(mlt_service_profile(MLT_FILTER_SERVICE(filter)), *width); mlt_properties_set_double(properties, "_movit.parms.float.radius", radius); mlt_properties_set_double(properties, "_movit.parms.float.blurred_mix_amount", mlt_properties_anim_get_double(properties, "blur_mix", position, length)); mlt_properties_set_double(properties, "_movit.parms.float.highlight_cutoff", mlt_properties_anim_get_double(properties, "highlight_cutoff", position, length)); GlslManager::get_instance()->unlock_service(frame); *format = mlt_image_movit; int error = mlt_frame_get_image(frame, image, format, width, height, writable); if (*width < 1 || *height < 1) { mlt_log_error(MLT_FILTER_SERVICE(filter), "Invalid size for get_image: %dx%d", *width, *height); return error; } GlslManager::set_effect_input(MLT_FILTER_SERVICE(filter), frame, (mlt_service) *image); GlslManager::set_effect(MLT_FILTER_SERVICE(filter), frame, new GlowEffect); *image = (uint8_t *) MLT_FILTER_SERVICE(filter); return error; } static mlt_frame process(mlt_filter filter, mlt_frame frame) { mlt_frame_push_service(frame, filter); mlt_frame_push_get_image(frame, get_image); return frame; } extern "C" { mlt_filter filter_movit_glow_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = NULL; GlslManager *glsl = GlslManager::get_instance(); if (glsl && (filter = mlt_filter_new())) { mlt_properties properties = MLT_FILTER_PROPERTIES(filter); glsl->add_ref(properties); mlt_properties_set_double(properties, "radius", 20.0); mlt_properties_set_double(properties, "blur_mix", 1.0); mlt_properties_set_double(properties, "highlight_cutoff", 0.2); filter->process = process; } return filter; } } mlt-7.22.0/src/modules/movit/filter_movit_glow.yml000664 000000 000000 00000001450 14531534050 022231 0ustar00rootroot000000 000000 schema_version: 7.0 type: filter identifier: movit.glow title: Glow (GLSL) version: 1 copyright: Dan Dennedy creator: Steinar H. Gunderson license: GPLv2 language: en tags: - Video description: > Cut out the highlights of the image (everything above a certain threshold), blur them, and overlay them onto the original image. parameters: - identifier: radius title: Radius type: float minimum: 0.0 default: 20.0 mutable: yes animation: yes - identifier: blur_mix title: Highlight Blurriness type: float minimum: 0.0 maximum: 1.0 default: 1.0 mutable: yes animation: yes - identifier: highlight_cutoff title: Highlight Cutoff Threshold type: float minimum: 0.0 maximum: 1.0 default: 0.2 mutable: yes animation: yes mlt-7.22.0/src/modules/movit/filter_movit_lift_gamma_gain.cpp000664 000000 000000 00000013326 14531534050 024345 0ustar00rootroot000000 000000 /* * filter_lift_gamma_gain.cpp * Copyright (C) 2013 Dan Dennedy * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include "filter_glsl_manager.h" #include using namespace movit; static int get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { mlt_filter filter = (mlt_filter) mlt_frame_pop_service(frame); mlt_properties properties = MLT_FILTER_PROPERTIES(filter); GlslManager::get_instance()->lock_service(frame); mlt_position position = mlt_filter_get_position(filter, frame); mlt_position length = mlt_filter_get_length2(filter, frame); mlt_properties_set_double(properties, "_movit.parms.vec3.lift[0]", mlt_properties_anim_get_double(properties, "lift_r", position, length)); mlt_properties_set_double(properties, "_movit.parms.vec3.lift[1]", mlt_properties_anim_get_double(properties, "lift_g", position, length)); mlt_properties_set_double(properties, "_movit.parms.vec3.lift[2]", mlt_properties_anim_get_double(properties, "lift_b", position, length)); mlt_properties_set_double(properties, "_movit.parms.vec3.gamma[0]", mlt_properties_anim_get_double(properties, "gamma_r", position, length)); mlt_properties_set_double(properties, "_movit.parms.vec3.gamma[1]", mlt_properties_anim_get_double(properties, "gamma_g", position, length)); mlt_properties_set_double(properties, "_movit.parms.vec3.gamma[2]", mlt_properties_anim_get_double(properties, "gamma_b", position, length)); mlt_properties_set_double(properties, "_movit.parms.vec3.gain[0]", mlt_properties_anim_get_double(properties, "gain_r", position, length)); mlt_properties_set_double(properties, "_movit.parms.vec3.gain[1]", mlt_properties_anim_get_double(properties, "gain_g", position, length)); mlt_properties_set_double(properties, "_movit.parms.vec3.gain[2]", mlt_properties_anim_get_double(properties, "gain_b", position, length)); GlslManager::get_instance()->unlock_service(frame); *format = mlt_image_movit; int error = mlt_frame_get_image(frame, image, format, width, height, writable); if (*width < 1 || *height < 1) { mlt_log_error(MLT_FILTER_SERVICE(filter), "Invalid size for get_image: %dx%d", *width, *height); return 1; } GlslManager::set_effect_input(MLT_FILTER_SERVICE(filter), frame, (mlt_service) *image); GlslManager::set_effect(MLT_FILTER_SERVICE(filter), frame, new LiftGammaGainEffect); *image = (uint8_t *) MLT_FILTER_SERVICE(filter); return error; } static mlt_frame process(mlt_filter filter, mlt_frame frame) { mlt_frame_push_service(frame, filter); mlt_frame_push_get_image(frame, get_image); return frame; } extern "C" { mlt_filter filter_lift_gamma_gain_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = NULL; GlslManager *glsl = GlslManager::get_instance(); if (glsl && (filter = mlt_filter_new())) { mlt_properties properties = MLT_FILTER_PROPERTIES(filter); glsl->add_ref(properties); mlt_properties_set_double(properties, "lift_r", 0.0); mlt_properties_set_double(properties, "lift_g", 0.0); mlt_properties_set_double(properties, "lift_b", 0.0); mlt_properties_set_double(properties, "gamma_r", 1.0); mlt_properties_set_double(properties, "gamma_g", 1.0); mlt_properties_set_double(properties, "gamma_b", 1.0); mlt_properties_set_double(properties, "gain_r", 1.0); mlt_properties_set_double(properties, "gain_g", 1.0); mlt_properties_set_double(properties, "gain_b", 1.0); filter->process = process; } return filter; } } mlt-7.22.0/src/modules/movit/filter_movit_lift_gamma_gain.yml000664 000000 000000 00000004033 14531534050 024357 0ustar00rootroot000000 000000 schema_version: 0.1 type: filter identifier: movit.lift_gamma_gain title: Lift, Gamma, and Gain (GLSL) version: 1 copyright: Dan Dennedy creator: Steinar H. Gunderson license: GPLv2 language: en tags: - Video description: > A simple lift/gamma/gain effect, used for color grading. notes: > Very roughly speaking, lift=shadows, gamma=midtones and gain=highlights, although all parameters affect the entire curve. Mathematically speaking, it is a bit unusual to look at gamma as a color, but it works pretty well in practice. The classic formula is: output = (gain * (x + lift * (1-x)))^(1/gamma). The lift is actually a case where we actually would _not_ want linear light; since black by definition becomes equal to the lift color, we want lift to be pretty close to black, but in linear light that means lift affects the rest of the curve relatively little. Thus, we actually convert to gamma 2.2 before lift, and then back again afterwards. (Gain and gamma are, up to constants, commutative with the de-gamma operation.) parameters: - identifier: lift_r title: Lift Red type: float minimum: 0.0 default: 0.0 mutable: yes - identifier: lift_g title: Lift Green type: float minimum: 0.0 default: 0.0 mutable: yes - identifier: lift_b title: Lift Blue type: float minimum: 0.0 default: 0.0 mutable: yes - identifier: gamma_r title: Gamma Red type: float minimum: 0.0 default: 1.0 mutable: yes - identifier: gamma_g title: Gamma Green type: float minimum: 0.0 default: 1.0 mutable: yes - identifier: gamma_b title: Gamma Blue type: float minimum: 0.0 default: 1.0 mutable: yes - identifier: gain_r title: Gain Red type: float minimum: 0.0 default: 1.0 mutable: yes - identifier: gain_g title: Gain Green type: float minimum: 0.0 default: 1.0 mutable: yes - identifier: gain_b title: Gain Blue type: float minimum: 0.0 default: 1.0 mutable: yes mlt-7.22.0/src/modules/movit/filter_movit_mirror.cpp000664 000000 000000 00000005001 14531534050 022550 0ustar00rootroot000000 000000 /* * filter_movit_mirror.cpp * Copyright (C) 2013 Dan Dennedy * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include "filter_glsl_manager.h" #include using namespace movit; static int get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { mlt_filter filter = (mlt_filter) mlt_frame_pop_service(frame); *format = mlt_image_movit; int error = mlt_frame_get_image(frame, image, format, width, height, writable); if (*width < 1 || *height < 1) { mlt_log_error(MLT_FILTER_SERVICE(filter), "Invalid size for get_image: %dx%d", *width, *height); return error; } GlslManager::set_effect_input(MLT_FILTER_SERVICE(filter), frame, (mlt_service) *image); GlslManager::set_effect(MLT_FILTER_SERVICE(filter), frame, new MirrorEffect); *image = (uint8_t *) MLT_FILTER_SERVICE(filter); return error; } static mlt_frame process(mlt_filter filter, mlt_frame frame) { mlt_frame_push_service(frame, filter); mlt_frame_push_get_image(frame, get_image); return frame; } extern "C" { mlt_filter filter_movit_mirror_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = NULL; GlslManager *glsl = GlslManager::get_instance(); if (glsl && (filter = mlt_filter_new())) { mlt_properties properties = MLT_FILTER_PROPERTIES(filter); glsl->add_ref(properties); filter->process = process; } return filter; } } mlt-7.22.0/src/modules/movit/filter_movit_mirror.yml000664 000000 000000 00000000347 14531534050 022577 0ustar00rootroot000000 000000 schema_version: 0.1 type: filter identifier: movit.mirror title: Mirror (GLSL) version: 1 copyright: Dan Dennedy creator: Steinar H. Gunderson license: GPLv2 language: en description: A simple horizontal mirroring. tags: - Video mlt-7.22.0/src/modules/movit/filter_movit_opacity.cpp000664 000000 000000 00000007057 14531534050 022723 0ustar00rootroot000000 000000 /* * filter_movit_opacity.cpp * Copyright (C) 2013 Dan Dennedy * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include "filter_glsl_manager.h" #include using namespace movit; static int get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { mlt_filter filter = (mlt_filter) mlt_frame_pop_service(frame); mlt_properties properties = MLT_FILTER_PROPERTIES(filter); GlslManager::get_instance()->lock_service(frame); mlt_position position = mlt_filter_get_position(filter, frame); mlt_position length = mlt_filter_get_length2(filter, frame); double opacity = mlt_properties_anim_get_double(properties, "opacity", position, length); double alpha = mlt_properties_anim_get_double(properties, "alpha", position, length); mlt_properties_set_double(properties, "_movit.parms.vec4.factor[0]", opacity); mlt_properties_set_double(properties, "_movit.parms.vec4.factor[1]", opacity); mlt_properties_set_double(properties, "_movit.parms.vec4.factor[2]", opacity); mlt_properties_set_double(properties, "_movit.parms.vec4.factor[3]", alpha >= 0 ? alpha : opacity); GlslManager::get_instance()->unlock_service(frame); *format = mlt_image_movit; int error = mlt_frame_get_image(frame, image, format, width, height, writable); if (*width < 1 || *height < 1) { mlt_log_error(MLT_FILTER_SERVICE(filter), "Invalid size for get_image: %dx%d", *width, *height); return error; } GlslManager::set_effect_input(MLT_FILTER_SERVICE(filter), frame, (mlt_service) *image); GlslManager::set_effect(MLT_FILTER_SERVICE(filter), frame, new MultiplyEffect); *image = (uint8_t *) MLT_FILTER_SERVICE(filter); return error; } static mlt_frame process(mlt_filter filter, mlt_frame frame) { mlt_frame_push_service(frame, filter); mlt_frame_push_get_image(frame, get_image); return frame; } extern "C" mlt_filter filter_movit_opacity_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = NULL; GlslManager *glsl = GlslManager::get_instance(); if (glsl && (filter = mlt_filter_new())) { mlt_properties properties = MLT_FILTER_PROPERTIES(filter); glsl->add_ref(properties); mlt_properties_set(properties, "opacity", arg ? arg : "1"); mlt_properties_set_double(properties, "alpha", -1.0); filter->process = process; } return filter; } mlt-7.22.0/src/modules/movit/filter_movit_opacity.yml000664 000000 000000 00000002350 14531534050 022731 0ustar00rootroot000000 000000 schema_version: 7.0 type: filter identifier: movit.opacity title: Opacity (GLSL) version: 1 copyright: Dan Dennedy creator: Steinar H. Gunderson license: GPLv2 language: en tags: - Video description: Adjust the opacity of an image through the alpha channel. notes: > When used in some transitions this will cause this video to be mixed with the other video. If the video this is applied to already has an alpha channel, then this preserves that by multiplying the opacity level with the alpha channel. This filter is especially handy when used in conjunction with movit.overlay. You can also use this to fade a clip from and to black. parameters: - identifier: opacity title: Opacity type: float minimum: 0 maximum: 1 default: 1 mutable: yes animation: yes - identifier: alpha title: Alpha description: > When this is less than zero, the alpha component of the Movit multiply effect follows opacity. Otherwise, you can set this to another value to control the alpha component independently. If you set this to 1, then the opacity parameter can be used to fade to and from black. type: float minimum: -1 maximum: 1 default: -1 mutable: yes animation: yes mlt-7.22.0/src/modules/movit/filter_movit_rect.cpp000664 000000 000000 00000005072 14531534050 022203 0ustar00rootroot000000 000000 /* * filter_movit_rect.cpp * Copyright (C) 2013 Dan Dennedy * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "filter_glsl_manager.h" #include using namespace movit; static mlt_frame process(mlt_filter filter, mlt_frame frame) { // Drive the resize and resample filters on the b_frame for these composite parameters mlt_properties properties = MLT_FILTER_PROPERTIES(filter); mlt_properties frame_props = MLT_FRAME_PROPERTIES(frame); mlt_position position = mlt_filter_get_position(filter, frame); mlt_position length = mlt_filter_get_length2(filter, frame); auto rect = mlt_properties_anim_get_rect(properties, "rect", position, length); mlt_properties_set_rect(frame_props, "resize.rect", rect); mlt_properties_set(frame_props, "resize.fill", mlt_properties_get(properties, "fill")); mlt_properties_set(frame_props, "distort", mlt_properties_get(properties, "distort")); mlt_properties_set(frame_props, "resize.halign", mlt_properties_get(properties, "halign")); mlt_properties_set(frame_props, "resize.valign", mlt_properties_get(properties, "valign")); return frame; } extern "C" mlt_filter filter_movit_rect_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = NULL; GlslManager *glsl = GlslManager::get_instance(); if (glsl && (filter = mlt_filter_new())) { mlt_properties properties = MLT_FILTER_PROPERTIES(filter); glsl->add_ref(properties); mlt_properties_set(MLT_FILTER_PROPERTIES(filter), "rect", arg); mlt_properties_set_int(MLT_FILTER_PROPERTIES(filter), "fill", 1); mlt_properties_set_int(MLT_FILTER_PROPERTIES(filter), "distort", 0); filter->process = process; } return filter; } mlt-7.22.0/src/modules/movit/filter_movit_rect.yml000664 000000 000000 00000003073 14531534050 022221 0ustar00rootroot000000 000000 schema_version: 7.0 type: filter identifier: movit.rect title: Position and Size (GLSL) version: 1 copyright: Dan Dennedy creator: Steinar H. Gunderson license: GPLv2 language: en tags: - Video description: > Change the coordinates and scale to fit within a rectangle. parameters: - identifier: rect title: Rectangle type: rect description: Rectangle specifying the position of the top, left corner and size. mutable: yes animation: yes - identifier: distort title: Ignore aspect ratio description: > Determines whether the image aspect ratio will be distorted while scaling to completely fill the geometry rectangle. type: boolean default: 0 mutable: yes widget: checkbox - identifier: fill title: Upscale to Fill description: > Determines whether the image will be scaled up to fill the rectangle or whether the size will be constrained to 100% of the profile resolution. type: integer default: 1 minimum: 0 maximum: 1 mutable: yes widget: checkbox - identifier: halign title: Horizontal alignment description: > Set the horizontal alignment within the geometry rectangle. type: string default: left values: - left - center - right mutable: yes widget: combo - identifier: valign title: Vertical alignment description: > Set the vertical alignment within the geometry rectangle. type: string default: top values: - top - middle - bottom mutable: yes widget: combo mlt-7.22.0/src/modules/movit/filter_movit_resample.cpp000664 000000 000000 00000011444 14531534050 023056 0ustar00rootroot000000 000000 /* * filter_movit_resample.cpp * Copyright (C) 2013 Dan Dennedy * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include "filter_glsl_manager.h" #include "optional_effect.h" #include using namespace movit; static int get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { mlt_properties properties = MLT_FRAME_PROPERTIES(frame); mlt_filter filter = (mlt_filter) mlt_frame_pop_service(frame); mlt_properties filter_properties = MLT_FILTER_PROPERTIES(filter); mlt_profile profile = mlt_service_profile(MLT_FILTER_SERVICE(filter)); // Correct width/height if necessary if (*width < 0 || *height < 1) { *width = profile->width; *height = profile->height; } int iwidth = *width; int iheight = *height; double factor = mlt_properties_get_double(filter_properties, "factor"); factor = factor > 0 ? factor : 1.0; int owidth = *width * factor; int oheight = *height * factor; // If meta.media.width/height exist, we want that as minimum information if (mlt_properties_get_int(properties, "meta.media.width")) { iwidth = mlt_properties_get_int(properties, "meta.media.width"); iheight = mlt_properties_get_int(properties, "meta.media.height"); } mlt_properties_set_int(properties, "rescale_width", *width); mlt_properties_set_int(properties, "rescale_height", *height); // Deinterlace if height is changing to prevent fields mixing on interpolation if (iheight != oheight) mlt_properties_set_int(properties, "consumer.progressive", 1); GlslManager::get_instance()->lock_service(frame); mlt_properties_set_int(filter_properties, "_movit.parms.int.width", owidth); mlt_properties_set_int(filter_properties, "_movit.parms.int.height", oheight); bool disable = (iwidth == owidth && iheight == oheight); mlt_properties_set_int(filter_properties, "_movit.parms.int.disable", disable); *width = owidth; *height = oheight; GlslManager::get_instance()->unlock_service(frame); // Get the image as requested if (*format != mlt_image_none) *format = mlt_image_movit; int error = mlt_frame_get_image(frame, image, format, &iwidth, &iheight, writable); if (*width < 1 || *height < 1 || iwidth < 1 || iheight < 1 || owidth < 1 || oheight < 1) { mlt_log_error(MLT_FILTER_SERVICE(filter), "Invalid size for get_image: %dx%d, in: %dx%d, out: %dx%d", *width, *height, iwidth, iheight, owidth, oheight); return error; } GlslManager::set_effect_input(MLT_FILTER_SERVICE(filter), frame, (mlt_service) *image); Effect *effect = GlslManager::set_effect(MLT_FILTER_SERVICE(filter), frame, new OptionalEffect); // This needs to be something else than 0x0 at chain finalization time. bool ok = effect->set_int("width", owidth); ok |= effect->set_int("height", oheight); assert(ok); *image = (uint8_t *) MLT_FILTER_SERVICE(filter); return error; } static mlt_frame process(mlt_filter filter, mlt_frame frame) { mlt_frame_push_service(frame, filter); mlt_frame_push_get_image(frame, get_image); return frame; } extern "C" mlt_filter filter_movit_resample_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = NULL; GlslManager *glsl = GlslManager::get_instance(); if (glsl && (filter = mlt_filter_new())) { mlt_properties properties = MLT_FILTER_PROPERTIES(filter); glsl->add_ref(properties); filter->process = process; } return filter; } mlt-7.22.0/src/modules/movit/filter_movit_resample.yml000664 000000 000000 00000000701 14531534050 023067 0ustar00rootroot000000 000000 schema_version: 7.0 type: filter identifier: movit.resample title: Image Scaler (GLSL) version: 1 copyright: Dan Dennedy creator: Steinar H. Gunderson license: GPLv2 language: en description: Change the resolution of a video or image. tags: - Video - Hidden notes: > This is not intended to be created directly. Rather, the rescale filter loads it if it is available to normalize video and image inputs to the consumer/profile resolution. mlt-7.22.0/src/modules/movit/filter_movit_resize.cpp000664 000000 000000 00000021320 14531534050 022541 0ustar00rootroot000000 000000 /* * filter_movit_resize.cpp * Copyright (C) 2013-2020 Dan Dennedy * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include "filter_glsl_manager.h" #include "optional_effect.h" #include #include using namespace movit; static float alignment_parse(char *align) { int ret = 0.0f; if (align == NULL) ; else if (isdigit(align[0])) ret = atoi(align); else if (align[0] == 'c' || align[0] == 'm') ret = 1.0f; else if (align[0] == 'r' || align[0] == 'b') ret = 2.0f; return ret; } static int get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { int error = 0; mlt_properties properties = MLT_FRAME_PROPERTIES(frame); mlt_filter filter = (mlt_filter) mlt_frame_pop_service(frame); mlt_profile profile = mlt_service_profile(MLT_FILTER_SERVICE(filter)); // Retrieve the aspect ratio double aspect_ratio = mlt_frame_get_aspect_ratio(frame); double consumer_aspect = mlt_profile_sar(profile); // Correct Width/height if necessary if (*width < 1 || *height < 1) { *width = profile->width; *height = profile->height; } // Assign requested width/height from our subordinate int owidth = *width; int oheight = *height; // Use a mlt_rect to compute position and size mlt_rect rect; rect.x = rect.y = 0.0; rect.w = rect.h = 1.0; if (mlt_properties_get(properties, "resize.rect")) { mlt_position position = mlt_filter_get_position(filter, frame); mlt_position length = mlt_filter_get_length2(filter, frame); rect = mlt_properties_anim_get_rect(properties, "resize.rect", position, length); if (strchr(mlt_properties_get(properties, "resize.rect"), '%')) { rect.x *= profile->width; rect.w *= profile->width; rect.y *= profile->height; rect.h *= profile->height; } double scale = mlt_profile_scale_width(profile, *width); rect.x *= scale; rect.w *= scale; scale = mlt_profile_scale_height(profile, *height); rect.y *= scale; rect.h *= scale; if (!mlt_properties_get_int(properties, "resize.fill")) { int x = mlt_properties_get_int(properties, "meta.media.width"); owidth = lrintf(rect.w > x ? x : rect.w); x = mlt_properties_get_int(properties, "meta.media.height"); oheight = lrintf(rect.h > x ? x : rect.h); } else { owidth = lrintf(rect.w); oheight = lrintf(rect.h); } } // Check for the special case - no aspect ratio means no problem :-) if (aspect_ratio == 0.0) aspect_ratio = consumer_aspect; // Reset the aspect ratio mlt_properties_set_double(properties, "aspect_ratio", aspect_ratio); // Skip processing if requested. char *rescale = mlt_properties_get(properties, "consumer.rescale"); if (*format == mlt_image_none || (rescale && !strcmp(rescale, "none"))) return mlt_frame_get_image(frame, image, format, width, height, writable); if (mlt_properties_get_int(properties, "distort") == 0) { // Normalize the input and out display aspect int normalized_width = profile->width; int normalized_height = profile->height; int real_width = mlt_properties_get_int(properties, "meta.media.width"); int real_height = mlt_properties_get_int(properties, "meta.media.height"); if (real_width == 0) real_width = mlt_properties_get_int(properties, "width"); if (real_height == 0) real_height = mlt_properties_get_int(properties, "height"); double input_ar = aspect_ratio * real_width / real_height; double output_ar = consumer_aspect * owidth / oheight; // Optimised for the input_ar > output_ar case (e.g. widescreen on standard) int scaled_width = lrint((input_ar * normalized_width) / output_ar); int scaled_height = normalized_height; // Now ensure that our images fit in the output frame if (scaled_width > normalized_width) { scaled_width = normalized_width; scaled_height = lrint((output_ar * normalized_height) / input_ar); } // Now calculate the actual image size that we want owidth = lrint(scaled_width * owidth / normalized_width); oheight = lrint(scaled_height * oheight / normalized_height); mlt_log_debug(MLT_FILTER_SERVICE(filter), "real %dx%d normalized %dx%d output %dx%d sar %f in-dar %f out-dar %f\n", real_width, real_height, normalized_width, normalized_height, owidth, oheight, aspect_ratio, input_ar, output_ar); // Tell frame we have conformed the aspect to the consumer mlt_frame_set_aspect_ratio(frame, consumer_aspect); } mlt_properties_set_int(properties, "distort", 0); // Now get the image *format = mlt_image_movit; error = mlt_frame_get_image(frame, image, format, &owidth, &oheight, writable); // Offset the position according to alignment if (mlt_properties_get(properties, "resize.rect")) { // default top left if rect supplied float w = float(rect.w - owidth); float h = float(rect.h - oheight); rect.x += w * alignment_parse(mlt_properties_get(properties, "resize.halign")) / 2.0f; rect.y += h * alignment_parse(mlt_properties_get(properties, "resize.valign")) / 2.0f; } else { // default center if no rect float w = float(*width - owidth); float h = float(*height - oheight); rect.x = w * 0.5f; rect.y = h * 0.5f; } if (*width < 1 || *height < 1) { mlt_log_error(MLT_FILTER_SERVICE(filter), "Invalid size for get_image: %dx%d", *width, *height); return error; } if (!error) { mlt_properties filter_properties = MLT_FILTER_PROPERTIES(filter); GlslManager::get_instance()->lock_service(frame); mlt_properties_set_int(filter_properties, "_movit.parms.int.width", *width); mlt_properties_set_int(filter_properties, "_movit.parms.int.height", *height); mlt_properties_set_double(filter_properties, "_movit.parms.float.left", rect.x); mlt_properties_set_double(filter_properties, "_movit.parms.float.top", rect.y); bool disable = (*width == owidth && *height == oheight && rect.x == 0 && rect.y == 0); mlt_properties_set_int(filter_properties, "_movit.parms.int.disable", disable); GlslManager::get_instance()->unlock_service(frame); GlslManager::set_effect_input(MLT_FILTER_SERVICE(filter), frame, (mlt_service) *image); GlslManager::set_effect(MLT_FILTER_SERVICE(filter), frame, new OptionalEffect); *image = (uint8_t *) MLT_FILTER_SERVICE(filter); return error; } return error; } static mlt_frame process(mlt_filter filter, mlt_frame frame) { mlt_frame_push_service(frame, filter); mlt_frame_push_get_image(frame, get_image); return frame; } extern "C" mlt_filter filter_movit_resize_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = NULL; GlslManager *glsl = GlslManager::get_instance(); if (glsl && (filter = mlt_filter_new())) { mlt_properties properties = MLT_FILTER_PROPERTIES(filter); glsl->add_ref(properties); filter->process = process; } return filter; } mlt-7.22.0/src/modules/movit/filter_movit_resize.yml000664 000000 000000 00000001051 14531534050 022557 0ustar00rootroot000000 000000 schema_version: 7.0 type: filter identifier: movit.resize title: Image Padding (GLSL) version: 1 copyright: Dan Dennedy creator: Steinar H. Gunderson license: GPLv2 language: en description: Pad an image with black to fulfill the requested image size. tags: - Video - Hidden notes: > Normally resize is used to pad the producer's output to what the consumer has requested after an upstream filter first scales the image to maximise usage of the image area. This filter is automatically invoked by the loader as part of image normalization. mlt-7.22.0/src/modules/movit/filter_movit_saturation.cpp000664 000000 000000 00000006445 14531534050 023444 0ustar00rootroot000000 000000 /* * filter_movit_saturation.cpp * Copyright (C) 2013 Dan Dennedy * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include "filter_glsl_manager.h" #include using namespace movit; static int get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { mlt_filter filter = (mlt_filter) mlt_frame_pop_service(frame); mlt_properties properties = MLT_FILTER_PROPERTIES(filter); GlslManager::get_instance()->lock_service(frame); mlt_position position = mlt_filter_get_position(filter, frame); mlt_position length = mlt_filter_get_length2(filter, frame); mlt_properties_set_double(properties, "_movit.parms.float.saturation", mlt_properties_anim_get_double(properties, "saturation", position, length)); GlslManager::get_instance()->unlock_service(frame); *format = mlt_image_movit; int error = mlt_frame_get_image(frame, image, format, width, height, writable); if (*width < 1 || *height < 1) { mlt_log_error(MLT_FILTER_SERVICE(filter), "Invalid size for get_image: %dx%d", *width, *height); return error; } GlslManager::set_effect_input(MLT_FILTER_SERVICE(filter), frame, (mlt_service) *image); GlslManager::set_effect(MLT_FILTER_SERVICE(filter), frame, new SaturationEffect()); *image = (uint8_t *) MLT_FILTER_SERVICE(filter); return error; } static mlt_frame process(mlt_filter filter, mlt_frame frame) { mlt_frame_push_service(frame, filter); mlt_frame_push_get_image(frame, get_image); return frame; } extern "C" { mlt_filter filter_movit_saturation_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = NULL; GlslManager *glsl = GlslManager::get_instance(); if (glsl && (filter = mlt_filter_new())) { mlt_properties properties = MLT_FILTER_PROPERTIES(filter); glsl->add_ref(properties); mlt_properties_set(properties, "saturation", arg ? arg : "1.0"); filter->process = process; } return filter; } } mlt-7.22.0/src/modules/movit/filter_movit_saturation.yml000664 000000 000000 00000001237 14531534050 023455 0ustar00rootroot000000 000000 schema_version: 7.0 type: filter identifier: movit.saturation title: Saturation (GLSL) version: 1 copyright: Dan Dennedy creator: Steinar H. Gunderson license: GPLv2 language: en tags: - Video description: > A simple desaturation/saturation effect. We use the Rec. 709 definition of luminance (in linear light, of course) and linearly interpolate between that (saturation=0) and the original signal (saturation=1). Extrapolating that curve further (ie., saturation > 1) gives us increased saturation if so desired. parameters: - identifier: saturation title: Saturation type: float minimum: 0 default: 1 mutable: yes animation: yes mlt-7.22.0/src/modules/movit/filter_movit_vignette.cpp000664 000000 000000 00000007106 14531534050 023073 0ustar00rootroot000000 000000 /* * filter_movit_vignette.cpp * Copyright (C) 2013 Dan Dennedy * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include "filter_glsl_manager.h" #include using namespace movit; static int get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { mlt_filter filter = (mlt_filter) mlt_frame_pop_service(frame); mlt_properties properties = MLT_FILTER_PROPERTIES(filter); GlslManager::get_instance()->lock_service(frame); mlt_position position = mlt_filter_get_position(filter, frame); mlt_position length = mlt_filter_get_length2(filter, frame); mlt_properties_set_double(properties, "_movit.parms.float.radius", mlt_properties_anim_get_double(properties, "radius", position, length)); mlt_properties_set_double(properties, "_movit.parms.float.inner_radius", mlt_properties_anim_get_double(properties, "inner_radius", position, length)); GlslManager::get_instance()->unlock_service(frame); *format = mlt_image_movit; int error = mlt_frame_get_image(frame, image, format, width, height, writable); if (*width < 1 || *height < 1) { mlt_log_error(MLT_FILTER_SERVICE(filter), "Invalid size for get_image: %dx%d", *width, *height); return error; } GlslManager::set_effect_input(MLT_FILTER_SERVICE(filter), frame, (mlt_service) *image); GlslManager::set_effect(MLT_FILTER_SERVICE(filter), frame, new VignetteEffect()); *image = (uint8_t *) MLT_FILTER_SERVICE(filter); return error; } static mlt_frame process(mlt_filter filter, mlt_frame frame) { mlt_frame_push_service(frame, filter); mlt_frame_push_get_image(frame, get_image); return frame; } extern "C" { mlt_filter filter_movit_vignette_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = NULL; GlslManager *glsl = GlslManager::get_instance(); if (glsl && (filter = mlt_filter_new())) { mlt_properties properties = MLT_FILTER_PROPERTIES(filter); glsl->add_ref(properties); filter->process = process; mlt_properties_set_double(MLT_FILTER_PROPERTIES(filter), "radius", 0.3); mlt_properties_set_double(MLT_FILTER_PROPERTIES(filter), "inner_radius", 0.3); } return filter; } } mlt-7.22.0/src/modules/movit/filter_movit_vignette.yml000664 000000 000000 00000001221 14531534050 023102 0ustar00rootroot000000 000000 schema_version: 7.0 type: filter identifier: movit.vignette title: Vignette (GLSL) version: 1 copyright: Dan Dennedy creator: Steinar H. Gunderson license: GPLv2 language: en tags: - Video description: > A circular vignette, falling off as cos² of the distance from the center (the classic formula for approximating a real lens). parameters: - identifier: radius title: Outer Radius type: float minimum: 0.0 maximum: 1.0 default: 0.3 mutable: yes animation: yes - identifier: inner_radius title: Inner Radius type: float minimum: 0.0 maximum: 1.0 default: 0.3 mutable: yes animation: yes mlt-7.22.0/src/modules/movit/filter_movit_white_balance.cpp000664 000000 000000 00000010034 14531534050 024025 0ustar00rootroot000000 000000 /* * filter_white_balance.cpp * Copyright (C) 2013 Dan Dennedy * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include "filter_glsl_manager.h" #include using namespace movit; static double srgb8_to_linear(int c) { double x = c / 255.0f; if (x < 0.04045f) { return (1.0 / 12.92f) * x; } else { return pow((x + 0.055) * (1.0 / 1.055f), 2.4); } } static int get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { mlt_filter filter = (mlt_filter) mlt_frame_pop_service(frame); mlt_properties properties = MLT_FILTER_PROPERTIES(filter); GlslManager::get_instance()->lock_service(frame); mlt_position position = mlt_filter_get_position(filter, frame); mlt_position length = mlt_filter_get_length2(filter, frame); int color_int = mlt_properties_anim_get_int(properties, "neutral_color", position, length); RGBTriplet color(srgb8_to_linear((color_int >> 24) & 0xff), srgb8_to_linear((color_int >> 16) & 0xff), srgb8_to_linear((color_int >> 8) & 0xff)); mlt_properties_set_double(properties, "_movit.parms.vec3.neutral_color[0]", color.r); mlt_properties_set_double(properties, "_movit.parms.vec3.neutral_color[1]", color.g); mlt_properties_set_double(properties, "_movit.parms.vec3.neutral_color[2]", color.b); double output_color_temperature = mlt_properties_anim_get_double(properties, "color_temperature", position, length); mlt_properties_set_double(properties, "_movit.parms.float.output_color_temperature", output_color_temperature); GlslManager::get_instance()->unlock_service(frame); *format = mlt_image_movit; int error = mlt_frame_get_image(frame, image, format, width, height, writable); if (*width < 1 || *height < 1) { mlt_log_error(MLT_FILTER_SERVICE(filter), "Invalid size for get_image: %dx%d", *width, *height); return error; } GlslManager::set_effect_input(MLT_FILTER_SERVICE(filter), frame, (mlt_service) *image); GlslManager::set_effect(MLT_FILTER_SERVICE(filter), frame, new WhiteBalanceEffect); *image = (uint8_t *) MLT_FILTER_SERVICE(filter); return error; } static mlt_frame process(mlt_filter filter, mlt_frame frame) { mlt_frame_push_service(frame, filter); mlt_frame_push_get_image(frame, get_image); return frame; } extern "C" { mlt_filter filter_white_balance_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = NULL; GlslManager *glsl = GlslManager::get_instance(); if (glsl && (filter = mlt_filter_new())) { mlt_properties properties = MLT_FILTER_PROPERTIES(filter); glsl->add_ref(properties); mlt_properties_set(properties, "neutral_color", arg ? arg : "#7f7f7f"); mlt_properties_set_double(properties, "color_temperature", 6500.0); filter->process = process; } return filter; } } mlt-7.22.0/src/modules/movit/filter_movit_white_balance.yml000664 000000 000000 00000001072 14531534050 024046 0ustar00rootroot000000 000000 schema_version: 0.1 type: filter identifier: movit.white_balance title: White Balance (GLSL) version: 1 copyright: Dan Dennedy creator: Steinar H. Gunderson license: GPLv2 language: en tags: - Video description: Color correction in LMS color space. parameters: - identifier: neutral_color title: Neutral Color type: string widget: color default: 0x7f7f7f00 mutable: yes - identifier: color_temperature title: Color Temperature type: float minimum: 1000.0 maximum: 15000.0 default: 6500.0 unit: Kelvin mutable: yes mlt-7.22.0/src/modules/movit/mlt_flip_effect.h000664 000000 000000 00000002640 14531534050 021250 0ustar00rootroot000000 000000 /* * mlt_flip_effect.h * Copyright (C) 2013 Dan Dennedy * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef MLT_FLIP_EFFECT_H #define MLT_FLIP_EFFECT_H #include namespace Mlt { class VerticalFlip : public movit::Effect { public: VerticalFlip() {} virtual std::string effect_type_id() const { return "MltVerticalFlip"; } std::string output_fragment_shader() { return "vec4 FUNCNAME(vec2 tc) { tc.y = 1.0 - tc.y; return INPUT(tc); }\n"; } virtual bool needs_linear_light() const { return false; } virtual bool needs_srgb_primaries() const { return false; } AlphaHandling alpha_handling() const { return DONT_CARE_ALPHA_TYPE; } }; } // namespace Mlt #endif // MLT_FLIP_EFFECT_H mlt-7.22.0/src/modules/movit/mlt_movit_input.cpp000664 000000 000000 00000010557 14531534050 021720 0ustar00rootroot000000 000000 /* * mlt_movit_input.cpp * Copyright (C) 2013-2023 Dan Dennedy * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "mlt_movit_input.h" extern "C" { #include } using namespace movit; MltInput::MltInput(mlt_image_format format) : m_format(format) , m_width(0) , m_height(0) , input(0) , isRGB(true) {} MltInput::~MltInput() {} void MltInput::useFlatInput(MovitPixelFormat pix_fmt, unsigned width, unsigned height) { // In case someone didn't think properly and passed -1 to an unsigned if (int(width) < 1 || int(height) < 1) { mlt_log_error(NULL, "Invalid size %dx%d\n", width, height); return; } if (!input) { m_width = width; m_height = height; ImageFormat image_format; image_format.color_space = COLORSPACE_sRGB; image_format.gamma_curve = GAMMA_REC_709; // GAMMA_sRGB causes a color level problem input = new FlatInput(image_format, pix_fmt, GL_UNSIGNED_BYTE, width, height); } } void MltInput::useYCbCrInput(const ImageFormat &image_format, const YCbCrFormat &ycbcr_format, unsigned width, unsigned height) { // In case someone didn't think properly and passed -1 to an unsigned if (int(width) < 1 || int(height) < 1) { mlt_log_error(NULL, "Invalid size %dx%d\n", width, height); return; } if (!input) { m_width = width; m_height = height; input = new YCbCrInput(image_format, ycbcr_format, width, height, YCBCR_INPUT_PLANAR, ycbcr_format.num_levels == 1024 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_BYTE); isRGB = false; m_ycbcr_format = ycbcr_format; } } void MltInput::set_pixel_data(const unsigned char *data) { if (!input) { mlt_log_error(NULL, "No input for set_pixel_data"); return; } // In case someone didn't think properly and passed -1 to an unsigned if (int(m_width) < 1 || int(m_height) < 1) { mlt_log_error(NULL, "Invalid size %dx%d\n", m_width, m_height); return; } if (isRGB) { FlatInput *flat = (FlatInput *) input; flat->set_pixel_data(data); } else if (m_ycbcr_format.num_levels == 1024) { YCbCrInput *ycbcr = (YCbCrInput *) input; auto p = reinterpret_cast(data); ycbcr->set_pixel_data(0, p); ycbcr->set_pixel_data(1, &p[m_width * m_height]); ycbcr->set_pixel_data(2, &p[m_width * m_height + (m_width / m_ycbcr_format.chroma_subsampling_x * m_height / m_ycbcr_format.chroma_subsampling_y)]); } else { YCbCrInput *ycbcr = (YCbCrInput *) input; ycbcr->set_pixel_data(0, data); ycbcr->set_pixel_data(1, &data[m_width * m_height]); ycbcr->set_pixel_data(2, &data[m_width * m_height + (m_width / m_ycbcr_format.chroma_subsampling_x * m_height / m_ycbcr_format.chroma_subsampling_y)]); } } void MltInput::invalidate_pixel_data() { if (!input) { mlt_log_error(NULL, "Invalidate called without input\n"); return; } if (isRGB) { FlatInput *flat = (FlatInput *) input; flat->invalidate_pixel_data(); } else { YCbCrInput *ycbcr = (YCbCrInput *) input; ycbcr->invalidate_pixel_data(); } } mlt-7.22.0/src/modules/movit/mlt_movit_input.h000664 000000 000000 00000003633 14531534050 021362 0ustar00rootroot000000 000000 /* * mlt_movit_input.h * Copyright (C) 2013 Dan Dennedy * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef MLT_MOVIT_INPUT_H #define MLT_MOVIT_INPUT_H #include #include #include #include class MltInput { public: MltInput(mlt_image_format format); ~MltInput(); void useFlatInput(movit::MovitPixelFormat pix_fmt, unsigned width, unsigned height); void useYCbCrInput(const movit::ImageFormat &image_format, const movit::YCbCrFormat &ycbcr_format, unsigned width, unsigned height); void set_pixel_data(const unsigned char *data); void invalidate_pixel_data(); movit::Input *get_input() { return input; } // The original pixel format that was used to create this MltInput, // in case we change our mind later and want to convert on the CPU instead. mlt_image_format get_format() const { return m_format; } private: mlt_image_format m_format; unsigned m_width, m_height; // Note: Owned by the EffectChain, so should not be deleted by us. movit::Input *input; bool isRGB; movit::YCbCrFormat m_ycbcr_format; }; #endif // MLT_MOVIT_INPUT_H mlt-7.22.0/src/modules/movit/optional_effect.h000664 000000 000000 00000001472 14531534050 021271 0ustar00rootroot000000 000000 #ifndef OPTIONAL_EFFECT_H #define OPTIONAL_EFFECT_H #include #include #include // A wrapper effect that, at rewrite time, can remove itself entirely from the loop. // It does so if "disable" is set to a nonzero value at finalization time. template class OptionalEffect : public T { public: OptionalEffect() : disable(0) { this->register_int("disable", &disable); } virtual std::string effect_type_id() const { return "OptionalEffect[" + T::effect_type_id() + "]"; } virtual void rewrite_graph(movit::EffectChain *graph, movit::Node *self) { if (disable) { assert(self->incoming_links.size() == 1); graph->replace_sender(self, self->incoming_links[0]); self->disabled = true; } else { T::rewrite_graph(graph, self); } } private: int disable; }; #endif mlt-7.22.0/src/modules/movit/transition_movit_luma.cpp000664 000000 000000 00000021167 14531534050 023114 0ustar00rootroot000000 000000 /* * transition_movit_luma.cpp * Copyright (C) 2014-2023 Dan Dennedy * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include "filter_glsl_manager.h" #include "mlt_movit_input.h" #include #include #include #include #include using namespace movit; static int get_image(mlt_frame a_frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { int error; // Get the transition object mlt_transition transition = (mlt_transition) mlt_frame_pop_service(a_frame); mlt_service service = MLT_TRANSITION_SERVICE(transition); // Get the b frame from the stack mlt_frame b_frame = (mlt_frame) mlt_frame_pop_frame(a_frame); mlt_frame c_frame = (mlt_frame) mlt_frame_pop_frame(a_frame); // Get the properties of the transition mlt_properties properties = MLT_TRANSITION_PROPERTIES(transition); mlt_service_lock(service); // Get the transition parameters mlt_position position = mlt_transition_get_position(transition, a_frame); mlt_position length = mlt_transition_get_length(transition); int reverse = mlt_properties_get_int(properties, "reverse"); double mix = mlt_transition_get_progress(transition, a_frame); double inverse = 1.0 - mix; double softness = mlt_properties_anim_get_double(properties, "softness", position, length); if (c_frame) { // Set the Movit parameters. mlt_properties_set(properties, "_movit.parms.float.strength_first", nullptr); mlt_properties_set(properties, "_movit.parms.float.strength_second", nullptr); mlt_properties_set_double(properties, "_movit.parms.float.progress", reverse ? inverse : mix); mlt_properties_set_double(properties, "_movit.parms.float.transition_width", 1.0 / (softness + 1.0e-4)); mlt_properties_set_int(properties, "_movit.parms.int.inverse", !mlt_properties_get_int(properties, "invert")); uint8_t *a_image, *b_image, *c_image; // Get the images. *format = mlt_image_movit; error = mlt_frame_get_image(a_frame, &a_image, format, width, height, writable); error = mlt_frame_get_image(b_frame, &b_image, format, width, height, writable); error = mlt_frame_get_image(c_frame, &c_image, format, width, height, writable); if (*width < 1 || *height < 1) { mlt_log_error(service, "Invalid size for get_image: %dx%d", *width, *height); return error; } GlslManager::set_effect_input(service, a_frame, (mlt_service) a_image); GlslManager::set_effect_secondary_input(service, a_frame, (mlt_service) b_image, b_frame); GlslManager::set_effect_third_input(service, a_frame, (mlt_service) c_image, c_frame); GlslManager::set_effect(service, a_frame, new LumaMixEffect()); } else { // Set the Movit parameters. mlt_properties_set(properties, "_movit.parms.int.inverse", nullptr); mlt_properties_set(properties, "_movit.parms.float.progress", nullptr); mlt_properties_set(properties, "_movit.parms.float.transition_width", nullptr); mlt_properties_set_double(properties, "_movit.parms.float.strength_first", reverse ? mix : inverse); mlt_properties_set_double(properties, "_movit.parms.float.strength_second", reverse ? inverse : mix); uint8_t *a_image, *b_image; // Get the two images. *format = mlt_image_movit; error = mlt_frame_get_image(a_frame, &a_image, format, width, height, writable); error = mlt_frame_get_image(b_frame, &b_image, format, width, height, writable); if (*width < 1 || *height < 1) { mlt_log_error(service, "Invalid size for get_image: %dx%d", *width, *height); return error; } GlslManager::set_effect_input(service, a_frame, (mlt_service) a_image); GlslManager::set_effect_secondary_input(service, a_frame, (mlt_service) b_image, b_frame); GlslManager::set_effect(service, a_frame, new MixEffect()); } *image = (uint8_t *) service; mlt_service_unlock(service); return error; } static mlt_frame process(mlt_transition transition, mlt_frame a_frame, mlt_frame b_frame) { mlt_properties properties = MLT_TRANSITION_PROPERTIES(transition); // Obtain the wipe producer. char *resource = mlt_properties_get(properties, "resource"); char *last_resource = mlt_properties_get(properties, "_resource"); auto producer = (mlt_producer) mlt_properties_get_data(properties, "instance", nullptr); // If we haven't created the wipe producer or it has changed if (resource) if (!producer || strcmp(resource, last_resource)) { mlt_profile profile = mlt_service_profile(MLT_TRANSITION_SERVICE(transition)); // Store the last resource now mlt_properties_set(properties, "_resource", resource); producer = mlt_factory_producer(profile, nullptr, resource); if (producer) { mlt_properties_set(MLT_PRODUCER_PROPERTIES(producer), "eof", "loop"); } mlt_properties_set_data(properties, "instance", producer, 0, (mlt_destructor) mlt_producer_close, nullptr); } if (producer) { mlt_frame wipe = nullptr; mlt_position position = mlt_transition_get_position(transition, a_frame); mlt_properties_pass(MLT_PRODUCER_PROPERTIES(producer), properties, "producer."); mlt_producer_seek(producer, position); if (mlt_service_get_frame(MLT_PRODUCER_SERVICE(producer), &wipe, 0) == 0) { char name[64]; snprintf(name, sizeof(name), "movit.luma %s", mlt_properties_get(properties, "_unique_id")); mlt_properties_set_data(MLT_FRAME_PROPERTIES(a_frame), name, wipe, 0, (mlt_destructor) mlt_frame_close, nullptr); mlt_properties_set_int(MLT_FRAME_PROPERTIES(wipe), "distort", 1); mlt_frame_push_frame(a_frame, wipe); } else { mlt_frame_push_frame(a_frame, nullptr); } } else { // We may still not have a producer in which case, dissolve mlt_frame_push_frame(a_frame, nullptr); } mlt_frame_push_frame(a_frame, b_frame); mlt_frame_push_service(a_frame, transition); mlt_frame_push_get_image(a_frame, get_image); return a_frame; } extern "C" mlt_transition transition_movit_luma_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_transition transition = nullptr; GlslManager *glsl = GlslManager::get_instance(); if (glsl && (transition = mlt_transition_new())) { transition->process = process; mlt_properties_set(MLT_TRANSITION_PROPERTIES(transition), "resource", arg); // Inform apps and framework that this is a video only transition mlt_properties_set_int(MLT_TRANSITION_PROPERTIES(transition), "_transition_type", 1); } return transition; } mlt-7.22.0/src/modules/movit/transition_movit_luma.yml000664 000000 000000 00000002225 14531534050 023125 0ustar00rootroot000000 000000 schema_version: 0.2 type: transition identifier: movit.luma_mix title: Wipe (GLSL) version: 1 copyright: Dan Dennedy creator: Steinar H. Gunderson license: GPLv2 language: en tags: - Video description: A generic dissolve and wipe transition processor. parameters: - identifier: resource argument: yes title: Wipe File description: Gradient image or dissolve if not supplied. type: string mutable: yes - identifier: softness title: Softness description: The blurriness of the edges of the transition. type: float minimum: 0 maximum: 1 default: 0 mutable: yes - identifier: reverse title: Reverse type: integer mutable: yes description: Reverse the direction of the transition. default: 0 minimum: 0 maximum: 1 widget: checkbox - identifier: invert title: Invert type: integer mutable: yes description: Invert the wipe. default: 0 minimum: 0 maximum: 1 widget: checkbox - identifier: producer.* title: Producer mutable: yes description: > Properties may be set on the encapsulated producer that reads resource. readonly: no mlt-7.22.0/src/modules/movit/transition_movit_mix.cpp000664 000000 000000 00000010502 14531534050 022742 0ustar00rootroot000000 000000 /* * transition_movit_mix.cpp * Copyright (C) 2013 Dan Dennedy * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include "filter_glsl_manager.h" #include "mlt_flip_effect.h" #include "mlt_movit_input.h" #include #include #include #include using namespace movit; static int get_image(mlt_frame a_frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { int error; // Get the b frame from the stack mlt_frame b_frame = (mlt_frame) mlt_frame_pop_frame(a_frame); // Get the transition object mlt_transition transition = (mlt_transition) mlt_frame_pop_service(a_frame); mlt_service service = MLT_TRANSITION_SERVICE(transition); mlt_service_lock(service); // Get the properties of the transition mlt_properties properties = MLT_TRANSITION_PROPERTIES(transition); // Get the transition parameters mlt_position position = mlt_transition_get_position(transition, a_frame); mlt_position length = mlt_transition_get_length(transition); int reverse = mlt_properties_get_int(properties, "reverse"); const char *mix_str = mlt_properties_get(properties, "mix"); double mix = (mix_str && strlen(mix_str) > 0) ? mlt_properties_anim_get_double(properties, "mix", position, length) : mlt_transition_get_progress(transition, a_frame); double inverse = 1.0 - mix; // Set the Movit parameters. mlt_properties_set_double(properties, "_movit.parms.float.strength_first", reverse ? mix : inverse); mlt_properties_set_double(properties, "_movit.parms.float.strength_second", reverse ? inverse : mix); uint8_t *a_image, *b_image; // Get the two images. *format = mlt_image_movit; error = mlt_frame_get_image(a_frame, &a_image, format, width, height, writable); error = mlt_frame_get_image(b_frame, &b_image, format, width, height, writable); if (*width < 1 || *height < 1) { mlt_log_error(service, "Invalid size for get_image: %dx%d", *width, *height); return error; } GlslManager::set_effect_input(service, a_frame, (mlt_service) a_image); GlslManager::set_effect_secondary_input(service, a_frame, (mlt_service) b_image, b_frame); GlslManager::set_effect(service, a_frame, new MixEffect()); *image = (uint8_t *) service; mlt_service_unlock(service); return error; } static mlt_frame process(mlt_transition transition, mlt_frame a_frame, mlt_frame b_frame) { mlt_frame_push_service(a_frame, transition); mlt_frame_push_frame(a_frame, b_frame); mlt_frame_push_get_image(a_frame, get_image); return a_frame; } extern "C" mlt_transition transition_movit_mix_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_transition transition = NULL; GlslManager *glsl = GlslManager::get_instance(); if (glsl && (transition = mlt_transition_new())) { transition->process = process; mlt_properties_set(MLT_TRANSITION_PROPERTIES(transition), "mix", arg); // Inform apps and framework that this is a video only transition mlt_properties_set_int(MLT_TRANSITION_PROPERTIES(transition), "_transition_type", 1); } return transition; } mlt-7.22.0/src/modules/movit/transition_movit_mix.yml000664 000000 000000 00000001251 14531534050 022762 0ustar00rootroot000000 000000 schema_version: 7.0 type: transition identifier: movit.mix title: Dissolve (GLSL) version: 1 copyright: Dan Dennedy creator: Steinar H. Gunderson license: GPLv2 language: en tags: - Video description: A simple video cross-fade or mixing effect. parameters: - identifier: mix argument: yes title: Mix Level description: Performs a dissolve if a mix level is not supplied. type: float minimum: 0 maximum: 1 mutable: yes animation: yes - identifier: reverse title: Reverse type: integer mutable: yes description: > Reverse the direction of the transition. default: 0 minimum: 0 maximum: 1 widget: checkbox mlt-7.22.0/src/modules/movit/transition_movit_overlay.cpp000664 000000 000000 00000006305 14531534050 023634 0ustar00rootroot000000 000000 /* * transition_movit_overlay.cpp * Copyright (C) 2013 Dan Dennedy * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include "filter_glsl_manager.h" #include #include #include #include using namespace movit; static int get_image(mlt_frame a_frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { int error; // Get the b frame from the stack mlt_frame b_frame = (mlt_frame) mlt_frame_pop_frame(a_frame); // Get the transition object mlt_transition transition = (mlt_transition) mlt_frame_pop_service(a_frame); mlt_service service = MLT_TRANSITION_SERVICE(transition); mlt_service_lock(service); uint8_t *a_image, *b_image; // Get the two images. *format = mlt_image_movit; error = mlt_frame_get_image(a_frame, &a_image, format, width, height, writable); error = mlt_frame_get_image(b_frame, &b_image, format, width, height, writable); if (*width < 1 || *height < 1) { mlt_log_error(service, "Invalid size for get_image: %dx%d", *width, *height); return error; } GlslManager::set_effect_input(service, a_frame, (mlt_service) a_image); GlslManager::set_effect_secondary_input(service, a_frame, (mlt_service) b_image, b_frame); GlslManager::set_effect(service, a_frame, new OverlayEffect); *image = (uint8_t *) service; mlt_service_unlock(service); return error; } static mlt_frame process(mlt_transition transition, mlt_frame a_frame, mlt_frame b_frame) { mlt_frame_push_service(a_frame, transition); mlt_frame_push_frame(a_frame, b_frame); mlt_frame_push_get_image(a_frame, get_image); return a_frame; } extern "C" mlt_transition transition_movit_overlay_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_transition transition = NULL; GlslManager *glsl = GlslManager::get_instance(); if (glsl && (transition = mlt_transition_new())) { transition->process = process; // Inform apps and framework that this is a video only transition mlt_properties_set_int(MLT_TRANSITION_PROPERTIES(transition), "_transition_type", 1); } return transition; } mlt-7.22.0/src/modules/movit/transition_movit_overlay.yml000664 000000 000000 00000000453 14531534050 023651 0ustar00rootroot000000 000000 schema_version: 0.1 type: transition identifier: movit.overlay title: Overlay (GLSL) version: 1 copyright: Dan Dennedy creator: Steinar H. Gunderson license: GPLv2 language: en tags: - Video description: > A simple video overlay or alpha-compositing effect using the Porter-Duff over operation. mlt-7.22.0/src/modules/ndi/000775 000000 000000 00000000000 14531534050 015367 5ustar00rootroot000000 000000 mlt-7.22.0/src/modules/ndi/CMakeLists.txt000664 000000 000000 00000001142 14531534050 020125 0ustar00rootroot000000 000000 add_library(mltndi MODULE consumer_ndi.c factory.c factory.h producer_ndi.c ) file(GLOB YML "*.yml") add_custom_target(Other_ndi_Files SOURCES ${YML} ) target_compile_options(mltndi PRIVATE ${MLT_COMPILE_OPTIONS}) target_link_libraries(mltndi PRIVATE mlt NDI::NDI) if(CPU_SSE) target_compile_definitions(mltndi PRIVATE USE_SSE) endif() set_target_properties(mltndi PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${MLT_MODULE_OUTPUT_DIRECTORY}") install(TARGETS mltndi LIBRARY DESTINATION ${MLT_INSTALL_MODULE_DIR}) install(FILES consumer_ndi.yml producer_ndi.yml DESTINATION ${MLT_INSTALL_DATA_DIR}/ndi) mlt-7.22.0/src/modules/ndi/consumer_ndi.c000664 000000 000000 00000027342 14531534050 020230 0ustar00rootroot000000 000000 /* * consumer_ndi.c -- output through NewTek NDI * Copyright (C) 2016 Maksym Veremeyenko * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with consumer library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #define __STDC_FORMAT_MACROS /* see inttypes.h */ #define __STDC_LIMIT_MACROS #define __STDC_CONSTANT_MACROS #include #include #include #include #include #include #include #include #include "factory.h" #include "Processing.NDI.Lib.h" typedef struct { struct mlt_consumer_s parent; int f_running, f_exit; char *arg; pthread_t th; int count; int sliced_swab; } consumer_ndi_t; static void *consumer_ndi_feeder(void *p) { int i; mlt_frame last = NULL; mlt_consumer consumer = p; mlt_properties properties = MLT_CONSUMER_PROPERTIES(consumer); consumer_ndi_t *self = (consumer_ndi_t *) consumer->child; mlt_log_debug(MLT_CONSUMER_SERVICE(consumer), "%s: entering\n", __FUNCTION__); const NDIlib_send_create_t ndi_send_desc = {self->arg, NULL, true, false}; NDIlib_send_instance_t ndi_send = NDIlib_send_create(&ndi_send_desc); if (!ndi_send) { mlt_log_error(MLT_CONSUMER_SERVICE(consumer), "%s: NDIlib_send_create failed\n", __FUNCTION__); return NULL; } char *ndi_con_str = malloc(NDI_CON_STR_MAX); strncpy(ndi_con_str, "", NDI_CON_STR_MAX); const NDIlib_metadata_frame_t ndi_con_type = {// The length (int) strlen(ndi_con_str), // Timecode (synthesized for us !) NDIlib_send_timecode_synthesize, // The string (char *) ndi_con_str}; NDIlib_send_add_connection_metadata(ndi_send, &ndi_con_type); while (!self->f_exit) { mlt_frame frame = mlt_consumer_rt_frame(consumer); if (frame || last) { mlt_profile profile = mlt_service_profile(MLT_CONSUMER_SERVICE(consumer)); int m_isKeyer = 0, width = profile->width, height = profile->height; uint8_t *image = 0; mlt_frame frm = m_isKeyer ? frame : last; mlt_image_format format = 0 ? mlt_image_rgba : mlt_image_yuv422; int rendered = mlt_properties_get_int(MLT_FRAME_PROPERTIES(frm), "rendered"); if (rendered && !mlt_frame_get_image(frm, &image, &format, &width, &height, 0)) { mlt_log_debug(MLT_CONSUMER_SERVICE(consumer), "%s:%d: width=%d, height=%d\n", __FUNCTION__, __LINE__, width, height); uint8_t *buffer = 0; int64_t timecode; int stride = width * (m_isKeyer ? 4 : 2); int progressive = mlt_properties_get_int(MLT_FRAME_PROPERTIES(frame), "progressive"); timecode = 10000000LL * (uint64_t) self->count * (uint64_t) profile->frame_rate_den; timecode = timecode / (uint64_t) profile->frame_rate_num; const NDIlib_video_frame_t ndi_video_frame = {// Resolution width, height, // Use YCbCr video m_isKeyer ? NDIlib_FourCC_type_BGRA : NDIlib_FourCC_type_UYVY, // The frame-eate profile->frame_rate_num, profile->frame_rate_den, // The aspect ratio (double) profile->display_aspect_num / profile->display_aspect_den, // This is a progressive frame progressive ? NDIlib_frame_format_type_progressive : NDIlib_frame_format_type_interleaved, // Timecode timecode, // The video memory used for this frame buffer = (uint8_t *) malloc(height * stride), // The line to line stride of this image stride}; if (!m_isKeyer) { unsigned char *arg[3] = {image, buffer}; ssize_t size = stride * height; // Normal non-keyer playout - needs byte swapping if (!self->sliced_swab) swab2(arg[0], arg[1], size); else { arg[2] = (unsigned char *) size; mlt_slices_run_fifo(0, swab_sliced, arg); } } else if (!mlt_properties_get_int(MLT_FRAME_PROPERTIES(frame), "test_image")) { // Normal keyer output int y = height + 1; uint32_t *s = (uint32_t *) image; uint32_t *d = (uint32_t *) buffer; // Need to relocate alpha channel RGBA => ARGB while (--y) { int x = width + 1; while (--x) { *d++ = (*s << 8) | (*s >> 24); s++; } } } else { // Keying blank frames - nullify alpha memset(buffer, 0, stride * height); } mlt_audio_format aformat = mlt_audio_s16; int frequency = 48000; int m_channels = 2; int samples = mlt_audio_calculate_frame_samples(mlt_profile_fps(profile), frequency, self->count); int16_t *pcm = 0; if (!mlt_frame_get_audio(frm, (void **) &pcm, &aformat, &frequency, &m_channels, &samples)) { // Create an audio buffer const NDIlib_audio_frame_interleaved_16s_t audio_data = {// 48kHz frequency, // Lets submit stereo although there is nothing limiting us m_channels, // samples, // Timecode timecode, // reference_level 0, // The audio data, interleaved 16bpp pcm}; NDIlib_util_send_send_audio_interleaved_16s(ndi_send, &audio_data); } // We now submit the frame. NDIlib_send_send_video(ndi_send, &ndi_video_frame); // free buf free(ndi_video_frame.p_data); self->count++; } } if (frame) { if (last) mlt_frame_close(last); last = frame; } mlt_events_fire(properties, "consumer-frame-show", mlt_event_data_from_frame(frame)); } NDIlib_send_destroy(ndi_send); mlt_log_debug(MLT_CONSUMER_SERVICE(consumer), "%s: exiting\n", __FUNCTION__); if (last) mlt_frame_close(last); return NULL; } static int consumer_ndi_start(mlt_consumer consumer) { int r = 0; mlt_log_debug(MLT_CONSUMER_SERVICE(consumer), "%s: entering\n", __FUNCTION__); mlt_properties properties = MLT_CONSUMER_PROPERTIES(consumer); // Set interlacing properties mlt_profile profile = mlt_service_profile(MLT_CONSUMER_SERVICE(consumer)); mlt_properties_set_int(properties, "top_field_first", !profile->progressive); consumer_ndi_t *self = (consumer_ndi_t *) consumer->child; if (!self->f_running) { self->sliced_swab = mlt_properties_get_int(properties, "sliced_swab"); // set flags self->f_exit = 0; pthread_create(&self->th, NULL, consumer_ndi_feeder, consumer); // set flags self->f_running = 1; } mlt_log_debug(MLT_CONSUMER_SERVICE(consumer), "%s: exiting\n", __FUNCTION__); return r; } static int consumer_ndi_stop(mlt_consumer consumer) { int r = 0; mlt_log_debug(MLT_CONSUMER_SERVICE(consumer), "%s: entering\n", __FUNCTION__); consumer_ndi_t *self = (consumer_ndi_t *) consumer->child; if (self->f_running) { // rise flags self->f_exit = 1; // wait for thread pthread_join(self->th, NULL); // hide flags self->f_running = 0; } mlt_log_debug(MLT_CONSUMER_SERVICE(consumer), "%s: exiting\n", __FUNCTION__); return r; } static int consumer_ndi_is_stopped(mlt_consumer consumer) { consumer_ndi_t *self = (consumer_ndi_t *) consumer->child; return !self->f_running; } static void consumer_ndi_close(mlt_consumer consumer) { consumer_ndi_t *self = (consumer_ndi_t *) consumer->child; mlt_log_debug(MLT_CONSUMER_SERVICE(consumer), "%s: entering\n", __FUNCTION__); // Stop the consumer mlt_consumer_stop(consumer); // Close the parent consumer->close = NULL; mlt_consumer_close(consumer); // free context if (self->arg) free(self->arg); free(self); mlt_log_debug(NULL, "%s: exiting\n", __FUNCTION__); } /** Initialise the consumer. */ mlt_consumer consumer_ndi_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { // Allocate the consumer consumer_ndi_t *self = (consumer_ndi_t *) calloc(1, sizeof(consumer_ndi_t)); mlt_log_debug(NULL, "%s: entering id=[%s], arg=[%s]\n", __FUNCTION__, id, arg); // If allocated if (self && !mlt_consumer_init(&self->parent, self, profile)) { mlt_consumer parent = &self->parent; // Setup context if (arg) self->arg = strdup(arg); // Setup callbacks parent->close = consumer_ndi_close; parent->start = consumer_ndi_start; parent->stop = consumer_ndi_stop; parent->is_stopped = consumer_ndi_is_stopped; mlt_properties properties = MLT_CONSUMER_PROPERTIES(parent); mlt_properties_set(properties, "consumer.deinterlacer", "onefield"); return parent; } free(self); return NULL; } mlt-7.22.0/src/modules/ndi/consumer_ndi.yml000664 000000 000000 00000001066 14531534050 020602 0ustar00rootroot000000 000000 schema_version: 7.0 type: consumer identifier: ndi title: NDI Output version: 1.0 creator: Maksym Veremeyenko license: LGPLv2.1 language: en tags: - Audio - Video - Network description: Audio/Video over IP output using NewTek's NDI technology parameters: - identifier: resource argument: yes type: string title: NDI Sender name description: Name used for discovery by NDI receivers on the network required: yes mutable: no - identifier: meta.attr.* type: string title: NDI Device Metadata required: no mutable: no mlt-7.22.0/src/modules/ndi/factory.c000664 000000 000000 00000007500 14531534050 017204 0ustar00rootroot000000 000000 /* * factory.c -- output through NewTek NDI * Copyright (C) 2016 Maksym Veremeyenko * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with consumer library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #define __STDC_FORMAT_MACROS /* see inttypes.h */ #define __STDC_LIMIT_MACROS #define __STDC_CONSTANT_MACROS //#define _XOPEN_SOURCE 700 #include #include #include #include #include #include #include #include #include "factory.h" #include "Processing.NDI.Lib.h" void swab2(const void *from, void *to, int n) { #if defined(USE_SSE) #define SWAB_STEP 16 int cnt = n / SWAB_STEP; __asm__ volatile("loop_start: \n\t" /* load */ "movdqa 0(%[from]), %%xmm0 \n\t" "add $0x10, %[from] \n\t" /* duplicate to temp registers */ "movdqa %%xmm0, %%xmm1 \n\t" /* shift right temp register */ "psrlw $8, %%xmm1 \n\t" /* shift left main register */ "psllw $8, %%xmm0 \n\t" /* compose them back */ "por %%xmm0, %%xmm1 \n\t" /* save */ "movdqa %%xmm1, 0(%[to]) \n\t" "add $0x10, %[to] \n\t" "dec %[cnt] \n\t" "jnz loop_start \n\t" : [from] "+r"(from), [to] "+r"(to), [cnt] "+r"(cnt) : : "xmm0", "xmm1"); from = (unsigned char *) from + n - (n % SWAB_STEP); to = (unsigned char *) to + n - (n % SWAB_STEP); n = (n % SWAB_STEP); #endif swab((char *) from, (char *) to, n); }; #define SWAB_SLICED_ALIGN_POW 5 int swab_sliced(int id, int idx, int jobs, void *cookie) { unsigned char **args = (unsigned char **) cookie; ssize_t sz = (ssize_t) args[2]; ssize_t bsz = ((sz / jobs + (1 << SWAB_SLICED_ALIGN_POW) - 1) >> SWAB_SLICED_ALIGN_POW) << SWAB_SLICED_ALIGN_POW; ssize_t offset = bsz * idx; if (offset < sz) { if ((offset + bsz) > sz) bsz = sz - offset; swab2(args[0] + offset, args[1] + offset, bsz); } return 0; }; static void *create_service(mlt_profile profile, mlt_service_type type, const char *id, void *arg) { mlt_log_debug(NULL, "%s: entering id=[%s]\n", __FUNCTION__, id); if (!NDIlib_initialize()) { mlt_log_error(NULL, "%s: NDIlib_initialize failed\n", __FUNCTION__); return NULL; } if (type == mlt_service_producer_type) return producer_ndi_init(profile, type, id, (char *) arg); else if (type == mlt_service_consumer_type) return consumer_ndi_init(profile, type, id, (char *) arg); return NULL; } MLT_REPOSITORY { MLT_REGISTER(mlt_service_consumer_type, "ndi", create_service); MLT_REGISTER(mlt_service_producer_type, "ndi", create_service); } mlt-7.22.0/src/modules/ndi/factory.h000664 000000 000000 00000002707 14531534050 017215 0ustar00rootroot000000 000000 /* * factory.h -- output through NewTek NDI * Copyright (C) 2016 Maksym Veremeyenko * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with consumer library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef FACTORY_H #define FACTORY_H #include #define NDI_CON_STR_MAX 32768 mlt_producer producer_ndi_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); mlt_consumer consumer_ndi_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); int swab_sliced(int id, int idx, int jobs, void *cookie); void swab2(const void *from, void *to, int n); #endif /* FACTORY_H */ mlt-7.22.0/src/modules/ndi/producer_ndi.c000664 000000 000000 00000054516 14531534050 020223 0ustar00rootroot000000 000000 /* * producer_ndi.c -- output through NewTek NDI * Copyright (C) 2016 Maksym Veremeyenko * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with consumer library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #define __STDC_FORMAT_MACROS /* see inttypes.h */ #define __STDC_LIMIT_MACROS #define __STDC_CONSTANT_MACROS #include #include #include #include #include #include #include #include #include #include "Processing.NDI.Lib.h" #include "factory.h" #define NDI_TIMEBASE 10000000LL typedef struct { mlt_producer parent; int f_running, f_exit, f_timeout; char *arg; pthread_t th; int count; mlt_deque a_queue, v_queue; pthread_mutex_t lock; pthread_cond_t cond; NDIlib_recv_instance_t recv; int v_queue_limit, a_queue_limit, v_prefill; } producer_ndi_t; static void *producer_ndi_feeder(void *p) { mlt_producer producer = p; int ndi_src_idx; const NDIlib_source_t *ndi_srcs = NULL; NDIlib_video_frame_t *video = NULL; NDIlib_audio_frame_t audio; NDIlib_metadata_frame_t meta; producer_ndi_t *self = (producer_ndi_t *) producer->child; mlt_log_debug(MLT_PRODUCER_SERVICE(producer), "%s: entering\n", __FUNCTION__); // find the source const NDIlib_find_create_t find_create_desc = {.show_local_sources = true, .p_groups = NULL, .p_extra_ips = NULL}; // create a finder NDIlib_find_instance_t ndi_find = NDIlib_find_create2(&find_create_desc); if (!ndi_find) { mlt_log_error(MLT_PRODUCER_SERVICE(producer), "%s: NDIlib_find_create failed\n", __FUNCTION__); return NULL; } // wait for source mlt_log_debug(MLT_PRODUCER_SERVICE(producer), "%s: waiting for sources, searching for [%s]\n", __FUNCTION__, self->arg); for (ndi_src_idx = -1; ndi_src_idx == -1 && !self->f_exit;) { unsigned int j, f, n; // wait sources list changed f = NDIlib_find_wait_for_sources(ndi_find, 500); mlt_log_debug(MLT_PRODUCER_SERVICE(producer), "%s: NDIlib_find_wait_for_sources=%d\n", __FUNCTION__, f); if (!f) { mlt_log_debug(MLT_PRODUCER_SERVICE(producer), "%s: no more sources\n", __FUNCTION__); break; } // check if request present in sources list ndi_srcs = NDIlib_find_get_current_sources(ndi_find, &n); mlt_log_debug(MLT_PRODUCER_SERVICE(producer), "%s: found %d sources\n", __FUNCTION__, n); for (j = 0; j < n && ndi_src_idx == -1; j++) if (!strcmp(self->arg, ndi_srcs[j].p_ndi_name)) ndi_src_idx = j; } // exit if nothing if (self->f_exit || ndi_src_idx == -1) { mlt_log_error(MLT_PRODUCER_SERVICE(producer), "%s: exiting, self->f_exit=%d\n", __FUNCTION__, self->f_exit); NDIlib_find_destroy(ndi_find); return NULL; } mlt_log_debug(MLT_PRODUCER_SERVICE(producer), "%s: source [%s] found\n", __FUNCTION__, self->arg); // create receiver description NDIlib_recv_create_t recv_create_desc = {ndi_srcs[ndi_src_idx], NDIlib_recv_color_format_e_UYVY_RGBA, NDIlib_recv_bandwidth_highest, true}; // Create the receiver self->recv = NDIlib_recv_create2(&recv_create_desc); NDIlib_find_destroy(ndi_find); if (!self->recv) { mlt_log_error(MLT_PRODUCER_SERVICE(producer), "%s: exiting, NDIlib_recv_create2 failed\n", __FUNCTION__); return 0; } // set tally const NDIlib_tally_t tally_state = {true, false}; NDIlib_recv_set_tally(self->recv, &tally_state); while (!self->f_exit) { NDIlib_frame_type_e t; NDIlib_audio_frame_interleaved_16s_t *audio_packet, *audio_packet_prev; if (!video) video = mlt_pool_alloc(sizeof(NDIlib_video_frame_t)); t = NDIlib_recv_capture(self->recv, video, &audio, &meta, 10); mlt_log_debug(NULL, "%s:%d: NDIlib_recv_capture=%d\n", __FILE__, __LINE__, t); switch (t) { case NDIlib_frame_type_none: break; case NDIlib_frame_type_video: pthread_mutex_lock(&self->lock); mlt_deque_push_back(self->v_queue, video); if (mlt_deque_count(self->v_queue) >= self->v_queue_limit) { video = mlt_deque_pop_front(self->v_queue); NDIlib_recv_free_video(self->recv, video); } else video = NULL; pthread_cond_broadcast(&self->cond); pthread_mutex_unlock(&self->lock); break; case NDIlib_frame_type_audio: // convert to 16s interleaved audio_packet = mlt_pool_alloc(sizeof(NDIlib_audio_frame_interleaved_16s_t)); audio_packet->reference_level = 0; audio_packet->p_data = mlt_pool_alloc(2 * audio.no_channels * audio.no_samples); NDIlib_util_audio_to_interleaved_16s(&audio, audio_packet); NDIlib_recv_free_audio(self->recv, &audio); // store into queue pthread_mutex_lock(&self->lock); // check if it continues prev packet audio_packet_prev = mlt_deque_pop_back(self->a_queue); if (audio_packet_prev) { int64_t n = audio_packet_prev->timecode + NDI_TIMEBASE * audio_packet_prev->no_samples / audio_packet_prev->sample_rate, d = audio_packet->timecode - n; if (d && llabs(d) < 2) { mlt_log_debug(NULL, "%s:%d: audio_packet_prev->timecode=%" PRId64 ", audio_packet->timecode=%" PRId64 ", n=%" PRId64 ", d=%" PRId64 ", audio_packet_prev->no_samples=%d\n", __FILE__, __LINE__, audio_packet_prev->timecode, audio_packet->timecode, n, d, audio_packet_prev->no_samples); audio_packet_prev->p_data = mlt_pool_realloc(audio_packet_prev->p_data, (audio_packet_prev->no_samples + audio_packet->no_samples) * audio_packet->no_channels * 2); memcpy((unsigned char *) audio_packet_prev->p_data + 2 * audio_packet_prev->no_channels * audio_packet_prev->no_samples, audio_packet->p_data, 2 * audio_packet->no_channels * audio_packet->no_samples); audio_packet_prev->no_samples += audio_packet->no_samples; mlt_pool_release(audio_packet->p_data); mlt_pool_release(audio_packet); audio_packet = NULL; } mlt_deque_push_back(self->a_queue, audio_packet_prev); } if (audio_packet) mlt_deque_push_back(self->a_queue, audio_packet); if (mlt_deque_count(self->a_queue) >= self->a_queue_limit) { audio_packet = mlt_deque_pop_front(self->a_queue); mlt_pool_release(audio_packet->p_data); mlt_pool_release(audio_packet); } pthread_cond_broadcast(&self->cond); pthread_mutex_unlock(&self->lock); break; case NDIlib_frame_type_metadata: NDIlib_recv_free_metadata(self->recv, &meta); break; case NDIlib_frame_type_error: mlt_log_error(MLT_PRODUCER_SERVICE(producer), "%s: NDIlib_recv_capture failed\n", __FUNCTION__); break; } } if (video) mlt_pool_release(video); mlt_log_debug(MLT_PRODUCER_SERVICE(producer), "%s: exiting\n", __FUNCTION__); return NULL; } static int get_audio(mlt_frame frame, int16_t **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples) { mlt_properties fprops = MLT_FRAME_PROPERTIES(frame); NDIlib_recv_instance_t recv = mlt_properties_get_data(fprops, "ndi_recv", NULL); NDIlib_audio_frame_interleaved_16s_t *audio = mlt_properties_get_data(fprops, "ndi_audio", NULL); mlt_log_debug(NULL, "%s:%d: recv=%p, audio=%p\n", __FILE__, __LINE__, recv, audio); if (recv && audio) { size_t size; *format = mlt_audio_s16; *frequency = audio->sample_rate; *channels = audio->no_channels; *samples = audio->no_samples; size = 2 * (*samples) * (*channels); *buffer = audio->p_data; mlt_frame_set_audio(frame, (uint8_t *) *buffer, *format, size, NULL); } return 0; } static int get_image(mlt_frame frame, uint8_t **buffer, mlt_image_format *format, int *width, int *height, int writable) { mlt_properties fprops = MLT_FRAME_PROPERTIES(frame); NDIlib_recv_instance_t recv = mlt_properties_get_data(fprops, "ndi_recv", NULL); NDIlib_video_frame_t *video = mlt_properties_get_data(fprops, "ndi_video", NULL); mlt_log_debug(NULL, "%s:%d: recv=%p, video=%p\n", __FILE__, __LINE__, recv, video); if (recv && video) { uint8_t *dst = NULL; size_t size; int j, dst_stride = 0, stride; *width = video->xres; *height = video->yres; if (NDIlib_FourCC_type_UYVY == video->FourCC || NDIlib_FourCC_type_UYVA == video->FourCC) { dst_stride = 2 * video->xres; *format = mlt_image_yuv422; } else if (NDIlib_FourCC_type_RGBA == video->FourCC || NDIlib_FourCC_type_RGBX == video->FourCC) { dst_stride = 4 * video->xres; *format = mlt_image_rgba; } else *format = mlt_image_none; size = mlt_image_format_size(*format, *width, *height, NULL); dst = mlt_pool_alloc(size); stride = (dst_stride > video->line_stride_in_bytes) ? video->line_stride_in_bytes : dst_stride; mlt_log_debug(NULL, "%s: stride=%d\n", __FUNCTION__, stride); if (NDIlib_FourCC_type_UYVY == video->FourCC || NDIlib_FourCC_type_UYVA == video->FourCC) for (j = 0; j < video->yres; j++) swab2(video->p_data + j * video->line_stride_in_bytes, dst + j * dst_stride, stride); else if (NDIlib_FourCC_type_RGBA == video->FourCC || NDIlib_FourCC_type_RGBX == video->FourCC) for (j = 0; j < video->yres; j++) memcpy(dst + j * dst_stride, video->p_data + j * video->line_stride_in_bytes, stride); if (dst) { mlt_frame_set_image(frame, (uint8_t *) dst, size, (mlt_destructor) mlt_pool_release); *buffer = dst; } if (NDIlib_FourCC_type_UYVA == video->FourCC) { uint8_t *src = video->p_data + (*height) * video->line_stride_in_bytes; size = (*width) * (*height); dst = mlt_pool_alloc(size); dst_stride = *width; stride = (dst_stride > (video->line_stride_in_bytes / 2)) ? (video->line_stride_in_bytes / 2) : dst_stride; for (j = 0; j < video->yres; j++) memcpy(dst + j * dst_stride, src + j * (video->line_stride_in_bytes / 2), stride); mlt_frame_set_alpha(frame, (uint8_t *) dst, size, (mlt_destructor) mlt_pool_release); } mlt_properties_set_int(fprops, "progressive", (video->frame_format_type == NDIlib_frame_format_type_progressive)); mlt_properties_set_int(fprops, "top_field_first", (video->frame_format_type == NDIlib_frame_format_type_interleaved)); NDIlib_recv_free_video(recv, video); } return 0; } static int get_frame(mlt_producer producer, mlt_frame_ptr pframe, int index) { mlt_frame frame = NULL; NDIlib_audio_frame_interleaved_16s_t *audio_frame = NULL; NDIlib_video_frame_t *video = NULL; double fps = mlt_producer_get_fps(producer); mlt_position position = mlt_producer_position(producer); producer_ndi_t *self = (producer_ndi_t *) producer->child; pthread_mutex_lock(&self->lock); mlt_log_debug(NULL, "%s:%d: entering %s\n", __FILE__, __LINE__, __FUNCTION__); // run thread if (!self->f_running) { // set flags self->f_exit = 0; pthread_create(&self->th, NULL, producer_ndi_feeder, producer); // set flags self->f_running = 1; } mlt_log_debug(NULL, "%s:%d: audio_cnt=%d, video_cnt=%d\n", __FILE__, __LINE__, mlt_deque_count(self->a_queue), mlt_deque_count(self->v_queue)); // wait for prefill if (mlt_deque_count(self->v_queue) < self->v_prefill) { struct timespec tm; // Wait clock_gettime(CLOCK_REALTIME, &tm); tm.tv_nsec += self->v_prefill * 1000000000LL / fps; tm.tv_sec += tm.tv_nsec / 1000000000LL; tm.tv_nsec %= 1000000000LL; pthread_cond_timedwait(&self->cond, &self->lock, &tm); } // pop frame to use if (mlt_deque_count(self->v_queue) >= self->v_prefill) video = (NDIlib_video_frame_t *) mlt_deque_pop_front(self->v_queue); if (video) { int64_t video_timecode_out, video_dur; mlt_log_debug(NULL, "%s:%d: video: timecode=%" PRId64 "\n", __FILE__, __LINE__, video->timecode); video_dur = NDI_TIMEBASE * video->frame_rate_D / video->frame_rate_N; video_timecode_out = video->timecode + video_dur; // deal with audio while (1) { NDIlib_audio_frame_interleaved_16s_t *audio_packet; int64_t audio_packet_dur, audio_packet_timecode_out, T1, T2, dst_offset, src_offset, duration; // pop audio packet audio_packet = (NDIlib_audio_frame_interleaved_16s_t *) mlt_deque_pop_front( self->a_queue); // check if audio present if (!audio_packet) break; // check if packet in a future if (video_timecode_out < audio_packet->timecode) { // push it back to queue mlt_deque_push_front(self->a_queue, audio_packet); break; } // calc packet audio_packet_dur = NDI_TIMEBASE * audio_packet->no_samples / audio_packet->sample_rate; audio_packet_timecode_out = audio_packet_dur + audio_packet->timecode; // check if packet in the past if (audio_packet_timecode_out < video->timecode) { mlt_pool_release(audio_packet->p_data); mlt_pool_release(audio_packet); continue; } // allocate new audio frame if (!audio_frame) { audio_frame = (NDIlib_audio_frame_interleaved_16s_t *) mlt_pool_alloc( sizeof(NDIlib_audio_frame_interleaved_16s_t)); audio_frame->timecode = video->timecode; audio_frame->no_channels = audio_packet->no_channels; audio_frame->sample_rate = audio_packet->sample_rate; audio_frame->no_samples = audio_packet->sample_rate * video->frame_rate_D / video->frame_rate_N; audio_frame->p_data = (short *) mlt_pool_alloc(2 * audio_frame->no_samples * audio_frame->no_channels); } // copy data of overlapping region T1 = MAX(audio_packet->timecode, video->timecode); T2 = MIN(audio_packet_timecode_out, video_timecode_out); dst_offset = (T1 - audio_frame->timecode) * audio_frame->sample_rate / NDI_TIMEBASE; src_offset = (T1 - audio_packet->timecode) * audio_packet->sample_rate / NDI_TIMEBASE; duration = (T2 - T1) * audio_frame->sample_rate / NDI_TIMEBASE; ; memcpy((unsigned char *) audio_frame->p_data + 2 * audio_frame->no_channels * dst_offset, (unsigned char *) audio_packet->p_data + 2 * audio_packet->no_channels * src_offset, 2 * audio_packet->no_channels * duration); // save packet back if it will be used later or clear it if (video_timecode_out < audio_packet_timecode_out) { // push it back to queue mlt_deque_push_front(self->a_queue, audio_packet); break; } // free packet data mlt_pool_release(audio_packet->p_data); mlt_pool_release(audio_packet); } } pthread_mutex_unlock(&self->lock); *pframe = frame = mlt_frame_init(MLT_PRODUCER_SERVICE(producer)); if (frame) { mlt_properties p = MLT_FRAME_PROPERTIES(frame); mlt_properties_set_data(p, "ndi_recv", (void *) self->recv, 0, NULL, NULL); if (video) { mlt_properties_set_data(p, "ndi_video", (void *) video, 0, mlt_pool_release, NULL); mlt_frame_push_get_image(frame, get_image); } else mlt_log_error(NULL, "%s:%d: NO VIDEO\n", __FILE__, __LINE__); if (audio_frame) { mlt_properties_set_data(p, "ndi_audio", (void *) audio_frame, 0, mlt_pool_release, NULL); mlt_properties_set_data(p, "ndi_audio_data", (void *) audio_frame->p_data, 0, mlt_pool_release, NULL); mlt_frame_push_audio(frame, (void *) get_audio); } self->f_timeout = 0; } mlt_frame_set_position(frame, position); // Calculate the next timecode mlt_producer_prepare_next(producer); return 0; } static void producer_ndi_close(mlt_producer producer) { producer_ndi_t *self = (producer_ndi_t *) producer->child; mlt_log_debug(MLT_PRODUCER_SERVICE(producer), "%s: entering\n", __FUNCTION__); if (self->f_running) { // rise flags self->f_exit = 1; // signal threads pthread_cond_broadcast(&self->cond); // wait for thread pthread_join(self->th, NULL); // hide flags self->f_running = 0; // dequeue audio frames while (mlt_deque_count(self->a_queue)) { NDIlib_audio_frame_interleaved_16s_t *audio = (NDIlib_audio_frame_interleaved_16s_t *) mlt_deque_pop_front(self->a_queue); mlt_pool_release(audio->p_data); mlt_pool_release(audio); } // dequeue video frames while (mlt_deque_count(self->v_queue)) { NDIlib_video_frame_t *video = (NDIlib_video_frame_t *) mlt_deque_pop_front( self->v_queue); NDIlib_recv_free_video(self->recv, video); mlt_pool_release(video); } // close receiver NDIlib_recv_destroy(self->recv); } mlt_deque_close(self->a_queue); mlt_deque_close(self->v_queue); pthread_mutex_destroy(&self->lock); pthread_cond_destroy(&self->cond); free(producer->child); producer->close = NULL; mlt_producer_close(producer); mlt_log_debug(NULL, "%s: exiting\n", __FUNCTION__); } /** Initialise the producer. */ mlt_producer producer_ndi_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { // Allocate the producer producer_ndi_t *self = (producer_ndi_t *) calloc(1, sizeof(producer_ndi_t)); mlt_producer parent = (mlt_producer) calloc(1, sizeof(*parent)); mlt_log_debug(NULL, "%s: entering id=[%s], arg=[%s]\n", __FUNCTION__, id, arg); // If allocated if (self && !mlt_producer_init(parent, self)) { self->parent = parent; mlt_properties properties = MLT_CONSUMER_PROPERTIES(parent); // Setup context self->arg = strdup(arg); pthread_mutex_init(&self->lock, NULL); pthread_cond_init(&self->cond, NULL); self->v_queue = mlt_deque_init(); self->a_queue = mlt_deque_init(); self->v_queue_limit = 6; self->a_queue_limit = 6; self->v_prefill = 2; // Set callbacks parent->close = (mlt_destructor) producer_ndi_close; parent->get_frame = get_frame; // These properties effectively make it infinite. mlt_properties_set_int(properties, "length", INT_MAX); mlt_properties_set_int(properties, "out", INT_MAX - 1); mlt_properties_set(properties, "eof", "loop"); return parent; } free(self); return NULL; } mlt-7.22.0/src/modules/ndi/producer_ndi.yml000664 000000 000000 00000000660 14531534050 020571 0ustar00rootroot000000 000000 schema_version: 7.0 type: producer identifier: ndi title: NDI Input version: 1.0 creator: Maksym Veremeyenko license: LGPLv2.1 language: en tags: - Audio - Video - Network description: Audio/Video over IP input using NewTek's NDI technology parameters: - identifier: resource argument: yes type: string title: NDI Source name description: Name of the NDI Source to receive required: yes mutable: no mlt-7.22.0/src/modules/normalize/000775 000000 000000 00000000000 14531534050 016615 5ustar00rootroot000000 000000 mlt-7.22.0/src/modules/normalize/CMakeLists.txt000664 000000 000000 00000001105 14531534050 021352 0ustar00rootroot000000 000000 add_library(mltnormalize MODULE factory.c filter_audiolevel.c filter_volume.c ) file(GLOB YML "*.yml") add_custom_target(Other_normalize_Files SOURCES ${YML} ) target_compile_options(mltnormalize PRIVATE ${MLT_COMPILE_OPTIONS}) target_link_libraries(mltnormalize PRIVATE mlt m) set_target_properties(mltnormalize PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${MLT_MODULE_OUTPUT_DIRECTORY}") install(TARGETS mltnormalize LIBRARY DESTINATION ${MLT_INSTALL_MODULE_DIR}) install(FILES filter_audiolevel.yml filter_volume.yml DESTINATION ${MLT_INSTALL_DATA_DIR}/normalize ) mlt-7.22.0/src/modules/normalize/factory.c000664 000000 000000 00000003647 14531534050 020442 0ustar00rootroot000000 000000 /* * factory.c -- the factory method interfaces * Copyright (C) 2003-2014 Meltytech, LLC * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include extern mlt_filter filter_audiolevel_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_volume_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); static mlt_properties metadata(mlt_service_type type, const char *id, void *data) { char file[PATH_MAX]; snprintf(file, PATH_MAX, "%s/normalize/%s", mlt_environment("MLT_DATA"), (char *) data); return mlt_properties_parse_yaml(file); } MLT_REPOSITORY { MLT_REGISTER(mlt_service_filter_type, "audiolevel", filter_audiolevel_init); MLT_REGISTER(mlt_service_filter_type, "volume", filter_volume_init); MLT_REGISTER_METADATA(mlt_service_filter_type, "audiolevel", metadata, "filter_audiolevel.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "volume", metadata, "filter_volume.yml"); } mlt-7.22.0/src/modules/normalize/filter_audiolevel.c000664 000000 000000 00000012645 14531534050 022467 0ustar00rootroot000000 000000 /* * filter_audiolevel.c -- get the audio level of each channel * Copyright (C) 2002 Steve Harris * Copyright (C) 2010 Marco Gittler * Copyright (C) 2012-2023 Dan Dennedy * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #define AMPTODBFS(n) (log10(n) * 20.0) //---------------------------------------------------------------------------- // IEC standard dB scaling -- as borrowed from meterbridge (c) Steve Harris static inline double IEC_Scale(double dB) { double fScale = 1.0f; if (dB < -70.0f) fScale = 0.0f; else if (dB < -60.0f) // 0.0 .. 2.5 fScale = (dB + 70.0f) * 0.0025f; else if (dB < -50.0f) // 2.5 .. 7.5 fScale = (dB + 60.0f) * 0.005f + 0.025f; else if (dB < -40.0) // 7.5 .. 15.0 fScale = (dB + 50.0f) * 0.0075f + 0.075f; else if (dB < -30.0f) // 15.0 .. 30.0 fScale = (dB + 40.0f) * 0.015f + 0.15f; else if (dB < -20.0f) // 30.0 .. 50.0 fScale = (dB + 30.0f) * 0.02f + 0.3f; else if (dB < -0.001f || dB > 0.001f) // 50.0 .. 100.0 fScale = (dB + 20.0f) * 0.025f + 0.5f; return fScale; } static int filter_get_audio(mlt_frame frame, void **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples) { mlt_filter filter = mlt_frame_pop_audio(frame); // Get the properties from the filter mlt_properties filter_props = MLT_FILTER_PROPERTIES(filter); int iec_scale = mlt_properties_get_int(filter_props, "iec_scale"); int dbPeak = mlt_properties_get_int(filter_props, "dbpeak"); *format = mlt_audio_s16; int error = mlt_frame_get_audio(frame, buffer, format, frequency, channels, samples); if (error || !buffer || !buffer[0]) return error; int num_channels = *channels; int num_samples = *samples > 200 ? 200 : *samples; int num_oversample = 0; int c, s; char key[50]; int16_t *pcm = (int16_t *) *buffer; for (c = 0; c < *channels; c++) { double level = 0.0; if (dbPeak) { int16_t peakVal = 0; for (s = 0; s < num_samples; s++) { int16_t sample = abs(pcm[c + s * num_channels]); if (sample > peakVal) peakVal = sample; } if (peakVal == 0) level = -100; else level = AMPTODBFS((double) peakVal / (double) INT16_MAX); if (iec_scale) level = IEC_Scale(level); } else { double val = 0; for (s = 0; s < num_samples; s++) { double sample = fabs(pcm[c + s * num_channels] / 128.0); val += sample; if (sample == 128) num_oversample++; else num_oversample = 0; // 10 samples @max => show max signal if (num_oversample > 10) { level = 1.0; break; } // if 3 samples over max => 1 peak over 0 db (0 dB = 40.0) if (num_oversample > 3) level = 41.0 / 42.0; } // max amplitude = 40/42, 3to10 oversamples=41, more then 10 oversamples=42 if (level == 0.0 && num_samples > 0) level = val / num_samples * 40.0 / 42.0 / 127.0; if (iec_scale) level = IEC_Scale(AMPTODBFS(level)); } sprintf(key, "meta.media.audio_level.%d", c); mlt_properties_set_double(MLT_FRAME_PROPERTIES(frame), key, level); sprintf(key, "_audio_level.%d", c); mlt_properties_set_double(filter_props, key, level); mlt_log_debug(MLT_FILTER_SERVICE(filter), "channel %d level %f\n", c, level); } mlt_properties_set_position(filter_props, "_position", mlt_filter_get_position(filter, frame)); return error; } /** Filter processing. */ static mlt_frame filter_process(mlt_filter filter, mlt_frame frame) { mlt_frame_push_audio(frame, filter); mlt_frame_push_audio(frame, filter_get_audio); return frame; } /** Constructor for the filter. */ mlt_filter filter_audiolevel_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = mlt_filter_new(); if (filter) { filter->process = filter_process; mlt_properties_set_int(MLT_FILTER_PROPERTIES(filter), "iec_scale", 1); } return filter; } mlt-7.22.0/src/modules/normalize/filter_audiolevel.yml000664 000000 000000 00000002173 14531534050 023041 0ustar00rootroot000000 000000 schema_version: 7.0 type: filter identifier: audiolevel title: Audio Levels version: 2 copyright: Dan Dennedy, Marco Gittler, and Steve Harris creator: Dan Dennedy contributor: - Marco Gittler - Steve Harris license: GPLv2 language: en description: Compute the audio amplitude. notes: > This filter provides the amplitude level as a percentage value in floating point. This does not do any "slowing" of the data by averaging out peaks and troughs of short duration like a VU meter. Applications can also get this data on the frame as meta.media.audio_level. where is the channel number starting with 0. tags: - Audio parameters: - identifier: iec_scale title: Use IEC 60268-18 Scale type: boolean default: 1 widget: checkbox - identifier: dbpeak title: output true peak value in dB instead of a percentage in _audio_level. type: boolean default: 0 widget: checkbox - identifier: _audio_level. description: > is the channel number starting with 0. This is updated on every frame with audio. readonly: yes type: float minimum: 0 maximum: 1 mlt-7.22.0/src/modules/normalize/filter_volume.c000664 000000 000000 00000034346 14531534050 021647 0ustar00rootroot000000 000000 /* * filter_volume.c -- adjust audio volume * Copyright (C) 2003-2023 Meltytech, LLC * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #define EPSILON 0.00001 /* The following normalize functions come from the normalize utility: Copyright (C) 1999--2002 Chris Vaill */ #define samp_width 16 #ifndef ROUND #define ROUND(x) floor((x) + 0.5) #endif #define DBFSTOAMP(x) pow(10, (x) / 20.0) /** Return nonzero if the two strings are equal, ignoring case, up to the first n characters. */ int strncaseeq(const char *s1, const char *s2, size_t n) { for (; n > 0; n--) { if (tolower(*s1++) != tolower(*s2++)) return 0; } return 1; } /** Limiter function. / tanh((x + lev) / (1-lev)) * (1-lev) - lev (for x < -lev) | x' = | x (for |x| <= lev) | \ tanh((x - lev) / (1-lev)) * (1-lev) + lev (for x > lev) With limiter level = 0, this is equivalent to a tanh() function; with limiter level = 1, this is equivalent to clipping. */ static inline double limiter(double x, double lmtr_lvl) { double xp = x; if (x < -lmtr_lvl) xp = tanh((x + lmtr_lvl) / (1 - lmtr_lvl)) * (1 - lmtr_lvl) - lmtr_lvl; else if (x > lmtr_lvl) xp = tanh((x - lmtr_lvl) / (1 - lmtr_lvl)) * (1 - lmtr_lvl) + lmtr_lvl; return xp; } /** Takes a full smoothing window, and returns the value of the center element, smoothed. Currently, just does a mean filter, but we could do a median or gaussian filter here instead. */ static inline double get_smoothed_data(double *buf, int count) { int i, j; double smoothed = 0; for (i = 0, j = 0; i < count; i++) { if (buf[i] != -1.0) { smoothed += buf[i]; j++; } } if (j) smoothed /= j; return smoothed; } /** Get the max power level (using RMS) and peak level of the audio segment. */ double signal_max_power(int16_t *buffer, int channels, int samples, int16_t *peak) { // Determine numeric limits int bytes_per_samp = (samp_width - 1) / 8 + 1; int16_t max = (1 << (bytes_per_samp * 8 - 1)) - 1; int16_t min = -max - 1; double *sums = (double *) calloc(channels, sizeof(double)); int c, i; int16_t sample; double pow, maxpow = 0; /* initialize peaks to effectively -inf and +inf */ int16_t max_sample = min; int16_t min_sample = max; for (i = 0; i < samples; i++) { for (c = 0; c < channels; c++) { sample = *buffer++; sums[c] += (double) sample * (double) sample; /* track peak */ if (sample > max_sample) max_sample = sample; else if (sample < min_sample) min_sample = sample; } } for (c = 0; c < channels; c++) { pow = sums[c] / (double) samples; if (pow > maxpow) maxpow = pow; } free(sums); /* scale the pow value to be in the range 0.0 -- 1.0 */ maxpow /= ((double) min * (double) min); if (-min_sample > max_sample) *peak = min_sample / (double) min; else *peak = max_sample / (double) max; return sqrt(maxpow); } /* ------ End normalize functions --------------------------------------- */ /** Get the audio. */ static int filter_get_audio(mlt_frame frame, void **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples) { // Get the filter from the frame mlt_filter filter = mlt_frame_pop_audio(frame); // Get the properties from the filter mlt_properties filter_props = MLT_FILTER_PROPERTIES(filter); // Get the frame's filter instance properties mlt_properties instance_props = mlt_frame_unique_properties(frame, MLT_FILTER_SERVICE(filter)); // Get the parameters double gain = mlt_properties_get_double(instance_props, "gain"); double max_gain = mlt_properties_get_double(instance_props, "max_gain"); double limiter_level = 0.5; /* -6 dBFS */ int normalize = mlt_properties_get_int(instance_props, "normalize"); double amplitude = mlt_properties_get_double(instance_props, "amplitude"); int i, j; double sample; int16_t peak; // Use animated value for gain if "level" property is set char *level_property = mlt_properties_get(filter_props, "level"); if (level_property != NULL) { mlt_position position = mlt_filter_get_position(filter, frame); mlt_position length = mlt_filter_get_length2(filter, frame); gain = mlt_properties_anim_get_double(filter_props, "level", position, length); gain = DBFSTOAMP(gain); } if (mlt_properties_get(instance_props, "limiter") != NULL) limiter_level = mlt_properties_get_double(instance_props, "limiter"); // Get the producer's audio *format = normalize ? mlt_audio_s16 : mlt_audio_f32le; int error = mlt_frame_get_audio(frame, buffer, format, frequency, channels, samples); if (error || !buffer || !buffer[0]) return error; mlt_service_lock(MLT_FILTER_SERVICE(filter)); if (normalize) { int window = mlt_properties_get_int(filter_props, "window"); double *smooth_buffer = mlt_properties_get_data(filter_props, "smooth_buffer", NULL); if (window > 0 && smooth_buffer != NULL) { int smooth_index = mlt_properties_get_int(filter_props, "_smooth_index"); // Compute the signal power and put into smoothing buffer smooth_buffer[smooth_index] = signal_max_power(*buffer, *channels, *samples, &peak); if (smooth_buffer[smooth_index] > EPSILON) { mlt_properties_set_int(filter_props, "_smooth_index", (smooth_index + 1) % window); // Smooth the data and compute the gain gain *= amplitude / get_smoothed_data(smooth_buffer, window); } } else { gain *= amplitude / signal_max_power(*buffer, *channels, *samples, &peak); } } if (max_gain > 0 && gain > max_gain) gain = max_gain; // Initialise filter's previous gain value to prevent an inadvertent jump from 0 mlt_position last_position = mlt_properties_get_position(filter_props, "_last_position"); mlt_position current_position = mlt_frame_get_position(frame); if (mlt_properties_get(filter_props, "_previous_gain") == NULL || current_position != last_position + 1) mlt_properties_set_double(filter_props, "_previous_gain", gain); // Start the gain out at the previous double previous_gain = mlt_properties_get_double(filter_props, "_previous_gain"); // Determine ramp increment double gain_step = (gain - previous_gain) / *samples; // Save the current gain for the next iteration mlt_properties_set_double(filter_props, "_previous_gain", gain); mlt_properties_set_position(filter_props, "_last_position", current_position); mlt_service_unlock(MLT_FILTER_SERVICE(filter)); // Ramp from the previous gain to the current gain = previous_gain; // Apply the gain if (normalize) { int16_t *p = *buffer; // Determine numeric limits int bytes_per_samp = (samp_width - 1) / 8 + 1; int samplemax = (1 << (bytes_per_samp * 8 - 1)) - 1; for (i = 0; i < *samples; i++, gain += gain_step) { for (j = 0; j < *channels; j++) { sample = *p * gain; *p = ROUND(sample); if (gain > 1.0 && normalize) { /* use limiter function instead of clipping */ *p = ROUND(samplemax * limiter(sample / (double) samplemax, limiter_level)); } p++; } } } else { float *p = *buffer; for (i = 0; i < *samples; i++, gain += gain_step) { for (j = 0; j < *channels; j++, p++) { p[0] *= gain; } } } return 0; } /** Filter processing. */ static mlt_frame filter_process(mlt_filter filter, mlt_frame frame) { mlt_properties filter_props = MLT_FILTER_PROPERTIES(filter); mlt_properties instance_props = mlt_frame_unique_properties(frame, MLT_FILTER_SERVICE(filter)); double gain = 1.0; // no adjustment char *gain_str = mlt_properties_get(filter_props, "gain"); // Parse the gain property if (gain_str) { char *p_orig = strdup(gain_str); char *p = p_orig; if (strncaseeq(p, "normali", 7)) { mlt_properties_set(filter_props, "normalize", ""); mlt_properties_set(filter_props, "normalise", ""); } else { if (strcmp(p, "") != 0) gain = strtod(p, &p); while (isspace(*p)) p++; /* check if "dB" is given after number */ if (strncaseeq(p, "db", 2)) gain = DBFSTOAMP(gain); else gain = fabs(gain); // If there is an end adjust gain to the range if (mlt_properties_get(filter_props, "end") != NULL) { double end = -1; char *p = mlt_properties_get(filter_props, "end"); if (strcmp(p, "") != 0) end = strtod(p, &p); while (isspace(*p)) p++; /* check if "dB" is given after number */ if (strncaseeq(p, "db", 2)) end = DBFSTOAMP(end); else end = fabs(end); if (end != -1) gain += (end - gain) * mlt_filter_get_progress(filter, frame); } } free(p_orig); } mlt_properties_set_double(instance_props, "gain", gain); // Parse the maximum gain property if (mlt_properties_get(filter_props, "max_gain") != NULL) { char *p = mlt_properties_get(filter_props, "max_gain"); double gain = strtod(p, &p); // 0 = no max while (isspace(*p)) p++; /* check if "dB" is given after number */ if (strncaseeq(p, "db", 2)) gain = DBFSTOAMP(gain); else gain = fabs(gain); mlt_properties_set_double(instance_props, "max_gain", gain); } // Parse the limiter property if (mlt_properties_get(filter_props, "limiter") != NULL) { char *p = mlt_properties_get(filter_props, "limiter"); double level = 0.5; /* -6dBFS */ if (strcmp(p, "") != 0) level = strtod(p, &p); while (isspace(*p)) p++; /* check if "dB" is given after number */ if (strncaseeq(p, "db", 2)) { if (level > 0) level = -level; level = DBFSTOAMP(level); } else { if (level < 0) level = -level; } mlt_properties_set_double(instance_props, "limiter", level); } // Parse the normalize property char *norm = mlt_properties_get(filter_props, "normalize"); if (!norm) norm = mlt_properties_get(filter_props, "normalise"); if (norm != NULL) { char *p = norm; double amplitude = 0.2511886431509580; /* -12dBFS */ if (strcmp(p, "") != 0) amplitude = strtod(p, &p); while (isspace(*p)) p++; /* check if "dB" is given after number */ if (strncaseeq(p, "db", 2)) { if (amplitude > 0) amplitude = -amplitude; amplitude = DBFSTOAMP(amplitude); } else { if (amplitude < 0) amplitude = -amplitude; if (amplitude > 1.0) amplitude = 1.0; } // If there is an end adjust gain to the range if (mlt_properties_get(filter_props, "end") != NULL) { amplitude *= mlt_filter_get_progress(filter, frame); } mlt_properties_set_int(instance_props, "normalize", 1); mlt_properties_set_int(instance_props, "normalise", 1); mlt_properties_set_double(instance_props, "amplitude", amplitude); } // Parse the window property and allocate smoothing buffer if needed int window = mlt_properties_get_int(filter_props, "window"); if (mlt_properties_get(filter_props, "smooth_buffer") == NULL && window > 1) { // Create a smoothing buffer for the calculated "max power" of frame of audio used in normalization double *smooth_buffer = (double *) calloc(window, sizeof(double)); int i; for (i = 0; i < window; i++) smooth_buffer[i] = -1.0; mlt_properties_set_data(filter_props, "smooth_buffer", smooth_buffer, 0, free, NULL); } // Push the filter onto the stack mlt_frame_push_audio(frame, filter); // Override the get_audio method mlt_frame_push_audio(frame, filter_get_audio); return frame; } /** Constructor for the filter. */ mlt_filter filter_volume_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = calloc(1, sizeof(struct mlt_filter_s)); if (filter != NULL && mlt_filter_init(filter, NULL) == 0) { mlt_properties properties = MLT_FILTER_PROPERTIES(filter); filter->process = filter_process; if (arg != NULL) mlt_properties_set(properties, "gain", arg); mlt_properties_set_int(properties, "window", 75); mlt_properties_set(properties, "max_gain", "20dB"); mlt_properties_set(properties, "level", NULL); } return filter; } mlt-7.22.0/src/modules/normalize/filter_volume.yml000664 000000 000000 00000005631 14531534050 022221 0ustar00rootroot000000 000000 schema_version: 7.0 type: filter identifier: volume title: Volume version: 2 copyright: Meltytech, LLC creator: Dan Denneedy license: GPLv2 language: en tags: - Audio description: > Adjust an audio stream's volume level. This filter is based on the 'normalize' utility parameters: - identifier: gain (*DEPRECATED*) argument: yes title: Gain type: string description: > This parameter is deprecated; use "level" instead. The gain may be indicated as a floating point value of the gain adjustment. The gain may also be indicated as a numeric value with the suffix "dB" to adjust in terms of decibels. The gain may also be set to "normalize" to normalize the volume to the target amplitude -12dBFS. This value is discarded if value for property "level" is set. - identifier: window title: Window type: integer description: > The number of video frames over which to smooth normalization. default: 75 mutable: yes - identifier: normalize title: Normalize type: string description: > Normalize the volume to the specified amplitude. The normalization may be indicated as a floating point value of the relative volume. The normalization may also be indicated as a numeric value with the suffix "dB" to set the amplitude in decibels. default: -12dBFS mutable: yes - identifier: normalise title: Normalise (*DEPRECATED*) description: Deprecated. See normalize - identifier: limiter title: Limiter type: string description: > Limit all samples above the specified amplitude. The limiting may be indicated as a floating point value of the relative volume. The limiting may also be indicated as a numeric value with the suffix "dB" to set the limiting amplitude in decibels. default: -6dBFS mutable: yes - identifier: max_gain title: Max gain type: string description: > A floating point or decibel value of the maximum gain that can be applied during normalization. default: 20dB mutable: yes - identifier: end (*DEPRECATED*) title: End gain type: string description: > A gain value just like the Gain property. This causes the gain to be interpolated from 'gain' to 'end' over the duration. This value is discarded if value for property "level" is set. mutable: yes - identifier: max_gain title: Max gain type: string description: > A floating point or decibel value of the maximum gain that can be applied during normalization. default: 20dB mutable: yes - identifier: level title: Level type: float description: > The animated value of the gain adjustment in dB. Properties "gain" and "end" are ignored if this is set. unit: dB mutable: yes animation: yes mlt-7.22.0/src/modules/normalize/gpl000664 000000 000000 00000000000 14531534050 017310 0ustar00rootroot000000 000000 mlt-7.22.0/src/modules/oldfilm/000775 000000 000000 00000000000 14531534050 016243 5ustar00rootroot000000 000000 mlt-7.22.0/src/modules/oldfilm/CMakeLists.txt000664 000000 000000 00000001543 14531534050 021006 0ustar00rootroot000000 000000 add_library(mltoldfilm MODULE common.c common.h factory.c filter_dust.c filter_grain.c filter_lines.c filter_oldfilm.c filter_tcolor.c filter_vignette.c ) file(GLOB YML "*.yml") add_custom_target(Other_oldfilm_Files SOURCES ${YML} ) target_compile_options(mltoldfilm PRIVATE ${MLT_COMPILE_OPTIONS}) target_link_libraries(mltoldfilm PRIVATE mlt m) set_target_properties(mltoldfilm PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${MLT_MODULE_OUTPUT_DIRECTORY}") install(TARGETS mltoldfilm LIBRARY DESTINATION ${MLT_INSTALL_MODULE_DIR}) install(FILES filter_dust.yml filter_grain.yml filter_lines.yml filter_oldfilm.yml filter_tcolor.yml filter_vignette.yml dust1.svg dust2.svg dust3.svg dust4.svg dust5.svg fdust.svg grain.svg lines.svg oldfilm.svg tcolor.svg vignette.svg DESTINATION ${MLT_INSTALL_DATA_DIR}/oldfilm ) mlt-7.22.0/src/modules/oldfilm/common.c000664 000000 000000 00000002633 14531534050 017703 0ustar00rootroot000000 000000 /* * Copyright (C) 2022 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "common.h" #include void oldfilm_init_seed(oldfilm_rand_seed *seed, int init) { // Use the initial value to initialize the seed to arbitrary values. // This causes the algorithm to produce consistent results each time for the same frame number. seed->x = 521288629 + init - (init << 16); seed->y = 362436069 - init + (init << 16); } int oldfilm_fast_rand(oldfilm_rand_seed *seed) { static unsigned int a = 18000, b = 30903; seed->x = a * (seed->x & 65535) + (seed->x >> 16); seed->y = b * (seed->y & 65535) + (seed->y >> 16); int r = (seed->x << 16) + (seed->y & 65535); return abs(r); } mlt-7.22.0/src/modules/oldfilm/common.h000664 000000 000000 00000002006 14531534050 017702 0ustar00rootroot000000 000000 /* * Copyright (C) 2022 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef COMMON_H #define COMMON_H typedef struct { unsigned int x; unsigned int y; } oldfilm_rand_seed; void oldfilm_init_seed(oldfilm_rand_seed *seed, int init); int oldfilm_fast_rand(oldfilm_rand_seed *seed); #endif // COMMON_H mlt-7.22.0/src/modules/oldfilm/dust1.svg000664 000000 000000 00000012401 14531534050 020022 0ustar00rootroot000000 000000 image/svg+xml mlt-7.22.0/src/modules/oldfilm/dust2.svg000664 000000 000000 00000052562 14531534050 020037 0ustar00rootroot000000 000000 image/svg+xml mlt-7.22.0/src/modules/oldfilm/dust3.svg000664 000000 000000 00000004715 14531534050 020035 0ustar00rootroot000000 000000 image/svg+xml mlt-7.22.0/src/modules/oldfilm/dust4.svg000664 000000 000000 00000007414 14531534050 020035 0ustar00rootroot000000 000000 image/svg+xml mlt-7.22.0/src/modules/oldfilm/dust5.svg000664 000000 000000 00000004617 14531534050 020040 0ustar00rootroot000000 000000 image/svg+xml mlt-7.22.0/src/modules/oldfilm/factory.c000664 000000 000000 00000006612 14531534050 020063 0ustar00rootroot000000 000000 /* * factory.c -- the factory method interfaces * Copyright (c) 2007 Marco Gittler * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include extern mlt_filter filter_dust_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_grain_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_lines_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_oldfilm_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_tcolor_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_vignette_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); static mlt_properties oldfilm_metadata(mlt_service_type type, const char *id, void *data) { char file[PATH_MAX]; snprintf(file, PATH_MAX, "%s/oldfilm/filter_%s.yml", mlt_environment("MLT_DATA"), id); return mlt_properties_parse_yaml(file); } MLT_REPOSITORY { MLT_REGISTER(mlt_service_filter_type, "oldfilm", filter_oldfilm_init); MLT_REGISTER(mlt_service_filter_type, "dust", filter_dust_init); MLT_REGISTER(mlt_service_filter_type, "lines", filter_lines_init); MLT_REGISTER(mlt_service_filter_type, "grain", filter_grain_init); MLT_REGISTER(mlt_service_filter_type, "tcolor", filter_tcolor_init); MLT_REGISTER(mlt_service_filter_type, "vignette", filter_vignette_init); MLT_REGISTER_METADATA(mlt_service_filter_type, "vignette", oldfilm_metadata, NULL); MLT_REGISTER_METADATA(mlt_service_filter_type, "tcolor", oldfilm_metadata, NULL); MLT_REGISTER_METADATA(mlt_service_filter_type, "grain", oldfilm_metadata, NULL); MLT_REGISTER_METADATA(mlt_service_filter_type, "lines", oldfilm_metadata, NULL); MLT_REGISTER_METADATA(mlt_service_filter_type, "dust", oldfilm_metadata, NULL); MLT_REGISTER_METADATA(mlt_service_filter_type, "oldfilm", oldfilm_metadata, NULL); } mlt-7.22.0/src/modules/oldfilm/fdust.svg000664 000000 000000 00000010707 14531534050 020116 0ustar00rootroot000000 000000 image/svg+xml mlt-7.22.0/src/modules/oldfilm/filter_dust.c000664 000000 000000 00000026236 14531534050 020744 0ustar00rootroot000000 000000 /* * filter_dust.c -- dust filter * Copyright (c) 2007 Marco Gittler * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include static void overlay_image(uint8_t *src, int src_width, int src_height, uint8_t *overlay, int overlay_width, int overlay_height, uint8_t *alpha, int xpos, int ypos, int upsidedown, int mirror) { int x, y; for (y = ypos; y < src_height; y++) { if (y >= 0 && (y - ypos) < overlay_height) { uint8_t *scanline_image = src + src_width * y * 2; int overlay_y = upsidedown ? (overlay_height - (y - ypos) - 1) : (y - ypos); uint8_t *scanline_overlay = overlay + overlay_width * 2 * overlay_y; for (x = xpos; x < src_width && x - xpos < overlay_width; x++) { if (x > 0) { int overlay_x = mirror ? overlay_width - (x - xpos) - 1 : (x - xpos); double alp = (double) *(alpha + overlay_width * overlay_y + overlay_x) / 255.0; uint8_t *image_pixel = scanline_image + x * 2; uint8_t *overlay_pixel = scanline_overlay + overlay_x * 2; *image_pixel = (double) (*overlay_pixel) * alp + (double) *image_pixel * (1.0 - alp); if (xpos % 2 == 0) image_pixel++; else image_pixel += 3; mirror ? overlay_pixel-- : overlay_pixel++; *image_pixel = (double) (*(overlay_pixel)) * alp + (double) (*image_pixel) * (1.0 - alp); } } } } } static int filter_get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { mlt_filter filter = (mlt_filter) mlt_frame_pop_service(frame); mlt_properties properties = MLT_FILTER_PROPERTIES(filter); mlt_position pos = mlt_filter_get_position(filter, frame); mlt_position len = mlt_filter_get_length2(filter, frame); int maxdia = mlt_properties_anim_get_int(properties, "maxdiameter", pos, len); int maxcount = mlt_properties_anim_get_int(properties, "maxcount", pos, len); *format = mlt_image_yuv422; int error = mlt_frame_get_image(frame, image, format, width, height, 1); // Load svg char *factory = mlt_properties_get(properties, "factory"); char temp[PATH_MAX] = ""; snprintf(temp, sizeof(temp), "%s/oldfilm/", mlt_environment("MLT_DATA")); mlt_properties direntries = mlt_properties_new(); mlt_properties_dir_list(direntries, temp, "dust*.svg", 1); if (!maxcount) return 0; double position = mlt_filter_get_progress(filter, frame); srand(position * 10000); mlt_service_lock(MLT_FILTER_SERVICE(filter)); int im = rand() % maxcount; int piccount = mlt_properties_count(direntries); while (im-- && piccount) { int picnum = rand() % piccount; int y1 = rand() % *height; int x1 = rand() % *width; char resource[1024] = ""; char savename[1024] = "", savename1[1024] = "", cachedy[100]; int dx = (*width * maxdia / 100); int luma_width, luma_height; uint8_t *luma_image = NULL; uint8_t *alpha = NULL; int updown = rand() % 2; int mirror = rand() % 2; sprintf(resource, "%s", mlt_properties_get_value(direntries, picnum)); sprintf(savename, "cache-%d-%d", picnum, dx); sprintf(savename1, "cache-alpha-%d-%d", picnum, dx); sprintf(cachedy, "cache-dy-%d-%d", picnum, dx); luma_image = mlt_properties_get_data(properties, savename, NULL); alpha = mlt_properties_get_data(properties, savename1, NULL); if (luma_image == NULL || alpha == NULL) { mlt_profile profile = mlt_service_profile(MLT_FILTER_SERVICE(filter)); mlt_producer producer = mlt_factory_producer(profile, factory, resource); if (producer != NULL) { mlt_properties producer_properties = MLT_PRODUCER_PROPERTIES(producer); mlt_properties_set(producer_properties, "eof", "loop"); mlt_frame luma_frame = NULL; if (mlt_service_get_frame(MLT_PRODUCER_SERVICE(producer), &luma_frame, 0) == 0) { mlt_image_format luma_format = mlt_image_yuv422; luma_width = dx; luma_height = luma_width * mlt_properties_get_int(MLT_FRAME_PROPERTIES(luma_frame), "height") / mlt_properties_get_int(MLT_FRAME_PROPERTIES(luma_frame), "width"); mlt_properties_set(MLT_FRAME_PROPERTIES(luma_frame), "consumer.rescale", "best"); // none/nearest/tiles/hyper mlt_frame_get_image(luma_frame, &luma_image, &luma_format, &luma_width, &luma_height, 0); alpha = mlt_frame_get_alpha(luma_frame); if (!alpha) { int alphasize = luma_width * luma_height; alpha = mlt_pool_alloc(alphasize); memset(alpha, 255, alphasize); mlt_frame_set_alpha(luma_frame, alpha, alphasize, mlt_pool_release); } uint8_t *savealpha = mlt_pool_alloc(luma_width * luma_height); uint8_t *savepic = mlt_pool_alloc(luma_width * luma_height * 2); if (savealpha && savepic) { memcpy(savealpha, alpha, luma_width * luma_height); memcpy(savepic, luma_image, luma_width * luma_height * 2); mlt_properties_set_data(properties, savename, savepic, luma_width * luma_height * 2, mlt_pool_release, NULL); mlt_properties_set_data(properties, savename1, savealpha, luma_width * luma_height, mlt_pool_release, NULL); mlt_properties_set_int(properties, cachedy, luma_height); overlay_image(*image, *width, *height, luma_image, luma_width, luma_height, alpha, x1, y1, updown, mirror); } else { if (savealpha) mlt_pool_release(savealpha); if (savepic) mlt_pool_release(savepic); } mlt_frame_close(luma_frame); } mlt_producer_close(producer); } } else { overlay_image(*image, *width, *height, luma_image, dx, mlt_properties_get_int(properties, cachedy), alpha, x1, y1, updown, mirror); } } mlt_service_unlock(MLT_FILTER_SERVICE(filter)); if (piccount > 0) return 0; if (error == 0 && *image) { int h = *height; int w = *width; int im = rand() % maxcount; while (im--) { int type = im % 2; int y1 = rand() % h; int x1 = rand() % w; int dx = rand() % maxdia; int dy = rand() % maxdia; int x = 0, y = 0; double v = 0.0; for (x = -dx; x < dx; x++) { for (y = -dy; y < dy; y++) { if (x1 + x < w && x1 + x > 0 && y1 + y < h && y1 + y > 0) { uint8_t *pix = *image + (y + y1) * w * 2 + (x + x1) * 2; v = pow((double) x / (double) dx * 5.0, 2.0) + pow((double) y / (double) dy * 5.0, 2.0); if (v > 10) v = 10; v = 1.0 - (v / 10.0); switch (type) { case 0: *pix -= (*pix) * v; break; case 1: *pix += (255 - *pix) * v; break; } } } } } } return error; } static mlt_frame filter_process(mlt_filter filter, mlt_frame frame) { mlt_frame_push_service(frame, filter); mlt_frame_push_get_image(frame, filter_get_image); return frame; } mlt_filter filter_dust_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = mlt_filter_new(); if (filter != NULL) { filter->process = filter_process; mlt_properties_set(MLT_FILTER_PROPERTIES(filter), "maxdiameter", "2"); mlt_properties_set(MLT_FILTER_PROPERTIES(filter), "maxcount", "10"); } return filter; } mlt-7.22.0/src/modules/oldfilm/filter_dust.yml000664 000000 000000 00000002226 14531534050 021314 0ustar00rootroot000000 000000 schema_version: 7.0 type: filter # consumer, filter, producer, or transition identifier: dust title: Dust version: 0.2.5 copyright: Copyright (C) 2008 Marco Gittler license: GPL language: en creator: Marco Gittler tags: - Video # this may produce video description: Add dust and specks to the Video, as in old movies icon: filename: oldfilm/fdust.svg # relative to $MLT_DATA/modules/ content-type: image/svg notes: Implementation or additional usage notes go here. bugs: # this can be just for documentation, or the tool may disclose it to help user avoid pitfalls - need to do some speed improvement. parameters: - identifier: maxdiameter title: Maximal Diameter type: integer description: Maximal diameter of a dust piece readonly: no required: yes minimum: 0 maximum: 100 default: 2 mutable: yes animation: yes widget: spinner unit: '%' - identifier: maxcount title: Maximal number of dust type: integer description: How many dust pieces are on the image readonly: no required: yes minimum: 0 maximum: 400 default: 10 mutable: yes animation: yes widget: spinner mlt-7.22.0/src/modules/oldfilm/filter_grain.c000664 000000 000000 00000010060 14531534050 021051 0ustar00rootroot000000 000000 /* * filter_grain.c -- grain filter * Copyright (c) 2007 Marco Gittler * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "common.h" #include #include #include #include #include #include typedef struct { uint8_t *image; int width; int height; int noise; double contrast; double brightness; mlt_position pos; int min; int max_luma; } slice_desc; static int slice_proc(int id, int index, int jobs, void *data) { (void) id; // unused slice_desc *d = (slice_desc *) data; int slice_line_start, slice_height = mlt_slices_size_slice(jobs, index, d->height, &slice_line_start); uint8_t *p = d->image + slice_line_start * d->width * 2; oldfilm_rand_seed seed; oldfilm_init_seed(&seed, d->pos * jobs + index); for (int n = 0; n < slice_height * d->width; n++, p += 2) { if (p[0] > 20) { int pix = CLAMP(((double) p[0] - 127.0) * d->contrast + 127.0 + d->brightness, 0, 255); if (d->noise > 0) { pix -= oldfilm_fast_rand(&seed) % d->noise - d->noise; } p[0] = CLAMP(pix, d->min, d->max_luma); } } return 0; } static int filter_get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { mlt_filter filter = (mlt_filter) mlt_frame_pop_service(frame); mlt_properties properties = MLT_FILTER_PROPERTIES(filter); mlt_position pos = mlt_filter_get_position(filter, frame); mlt_position len = mlt_filter_get_length2(filter, frame); *format = mlt_image_yuv422; int error = mlt_frame_get_image(frame, image, format, width, height, 1); if (error == 0 && *image) { int noise = mlt_properties_anim_get_int(properties, "noise", pos, len); int full_range = mlt_properties_get_int(MLT_FRAME_PROPERTIES(frame), "full_range"); slice_desc desc = { .image = *image, .width = *width, .height = *height, .noise = noise, .contrast = mlt_properties_anim_get_double(properties, "contrast", pos, len) / 100.0, .brightness = 127.0 * (mlt_properties_anim_get_double(properties, "brightness", pos, len) - 100.0) / 100.0, .pos = pos, .min = full_range ? 0 : 16, .max_luma = full_range ? 255 : 235, }; mlt_slices_run_normal(0, slice_proc, &desc); } return error; } static mlt_frame filter_process(mlt_filter filter, mlt_frame frame) { mlt_frame_push_service(frame, filter); mlt_frame_push_get_image(frame, filter_get_image); return frame; } mlt_filter filter_grain_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = mlt_filter_new(); if (filter != NULL) { filter->process = filter_process; mlt_properties_set(MLT_FILTER_PROPERTIES(filter), "noise", "40"); mlt_properties_set(MLT_FILTER_PROPERTIES(filter), "contrast", "160"); mlt_properties_set(MLT_FILTER_PROPERTIES(filter), "brightness", "70"); } return filter; } mlt-7.22.0/src/modules/oldfilm/filter_grain.yml000664 000000 000000 00000002512 14531534050 021433 0ustar00rootroot000000 000000 schema_version: 7.0 type: filter # consumer, filter, producer, or transition identifier: grain title: Grain version: 0.2.5 copyright: Copyright (C) 2008 Marco Gittler license: GPL language: en creator: Marco Gittler tags: - Video # this may produce video description: Grain over the Image icon: filename: oldfilm/grain.svg # relative to $MLT_DATA/modules/ content-type: image/svg notes: Implementation or additional usage notes go here. bugs: # this can be just for documentation, or the tool may disclose it to help user avoid pitfalls - need to do some speed improvement. parameters: - identifier: noise title: Noise type: integer description: Maximal value of noise readonly: no required: yes minimum: 0 maximum: 200 default: 40 mutable: yes animation: yes widget: spinner unit: '%' - identifier: contrast title: Contrast type: integer description: Adjust contrast for the image readonly: no required: yes minimum: 0 maximum: 400 default: 160 mutable: yes animation: yes widget: spinner - identifier: brightness title: Brightness type: integer description: Adjust brightness for the image readonly: no required: yes minimum: 0 maximum: 400 default: 70 mutable: yes animation: yes widget: spinner mlt-7.22.0/src/modules/oldfilm/filter_lines.c000664 000000 000000 00000016633 14531534050 021077 0ustar00rootroot000000 000000 /* * filter_lines.c -- lines filter * Copyright (c) 2007 Marco Gittler * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "common.h" #include #include #include #include #include #include #include typedef struct { uint8_t *image; int width; int height; int dx; int ystart; int yend; int xmid; int type; double maxdarker; double maxlighter; int min; int max_luma; int max_chroma; } slice_desc; static int slice_proc(int id, int index, int jobs, void *data) { (void) id; // unused slice_desc *d = (slice_desc *) data; int slice_line_start, slice_height = mlt_slices_size_slice(jobs, index, d->height, &slice_line_start); uint8_t *p = d->image; for (int y = MAX(d->ystart, slice_line_start); y < MIN(d->yend, slice_line_start + slice_height); y++) { for (int x = -(d->dx); x < d->dx && (x + d->xmid) < d->width; x++) { if (x + d->xmid > 0) { int i = (y * d->width + x + d->xmid) * 2; double diff = 1.0 - (double) abs(x) / d->dx; switch (d->type) { case 1: //blackline p[i] = CLAMP(p[i] - (double) p[i] * diff * d->maxdarker / 100.0, d->min, d->max_luma); break; case 2: //whiteline p[i] = CLAMP(p[i] + (255.0 - (double) p[i]) * diff * d->maxlighter / 100.0, d->min, d->max_luma); break; case 3: //greenline p[i + 1] = CLAMP(p[i + 1] - (double) p[i + 1] * diff * d->maxlighter / 100.0, d->min, d->max_chroma); break; } } } } return 0; } static int filter_get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { mlt_filter filter = (mlt_filter) mlt_frame_pop_service(frame); mlt_properties properties = MLT_FILTER_PROPERTIES(filter); mlt_position pos = mlt_filter_get_position(filter, frame); mlt_position len = mlt_filter_get_length2(filter, frame); *format = mlt_image_yuv422; int error = mlt_frame_get_image(frame, image, format, width, height, 1); if (error == 0 && *image) { int line_width = mlt_properties_anim_get_int(properties, "line_width", pos, len); int num = mlt_properties_anim_get_int(properties, "num", pos, len); double maxdarker = (double) mlt_properties_anim_get_int(properties, "darker", pos, len); double maxlighter = (double) mlt_properties_anim_get_int(properties, "lighter", pos, len); int full_range = mlt_properties_get_int(MLT_FRAME_PROPERTIES(frame), "full_range"); int min = full_range ? 0 : 16; int max_luma = full_range ? 255 : 235; int max_chroma = full_range ? 255 : 240; char buf[256]; char typebuf[256]; mlt_profile profile = mlt_service_profile(MLT_FILTER_SERVICE(filter)); double scale = mlt_profile_scale_width(profile, *width); if (line_width > 1 && scale > 0.0) line_width = MAX(2, lrint(line_width * scale)); if (line_width < 1) return 0; double position = mlt_filter_get_progress(filter, frame); oldfilm_rand_seed seed; mlt_service_lock(MLT_FILTER_SERVICE(filter)); while (num--) { oldfilm_init_seed(&seed, position * 10000 + num); int type = (oldfilm_fast_rand(&seed) % 3) + 1; int xmid = (double) (*width) * oldfilm_fast_rand(&seed) / INT_MAX; int dx = oldfilm_fast_rand(&seed) % line_width; int ystart = oldfilm_fast_rand(&seed) % (*height); int yend = oldfilm_fast_rand(&seed) % (*height); sprintf(buf, "line%d", num); sprintf(typebuf, "typeline%d", num); maxlighter += oldfilm_fast_rand(&seed) % 30 - 15; maxdarker += oldfilm_fast_rand(&seed) % 30 - 15; if (!mlt_properties_get_int(properties, buf)) { mlt_properties_set_int(properties, buf, xmid); } if (!mlt_properties_get_int(properties, typebuf)) { mlt_properties_set_int(properties, typebuf, type); } xmid = mlt_properties_get_int(properties, buf); type = mlt_properties_get_int(properties, typebuf); if (position != mlt_properties_get_double(properties, "last_oldfilm_line_pos")) { xmid += (oldfilm_fast_rand(&seed) % 11 - 5); } if (yend < ystart) { yend = *height; } if (dx) { slice_desc desc = {.image = *image, .width = *width, .height = *height, .dx = dx, .ystart = ystart, .yend = yend, .xmid = xmid, .type = type, .maxdarker = maxdarker, .maxlighter = maxlighter, .min = min, .max_luma = max_luma, .max_chroma = max_chroma}; mlt_slices_run_normal(0, slice_proc, &desc); } mlt_properties_set_int(properties, buf, xmid); } mlt_properties_set_double(properties, "last_oldfilm_line_pos", position); mlt_service_unlock(MLT_FILTER_SERVICE(filter)); } return error; } static mlt_frame filter_process(mlt_filter filter, mlt_frame frame) { mlt_frame_push_service(frame, filter); mlt_frame_push_get_image(frame, filter_get_image); return frame; } mlt_filter filter_lines_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = mlt_filter_new(); if (filter != NULL) { filter->process = filter_process; mlt_properties_set_int(MLT_FILTER_PROPERTIES(filter), "line_width", 2); mlt_properties_set_int(MLT_FILTER_PROPERTIES(filter), "num", 5); mlt_properties_set_int(MLT_FILTER_PROPERTIES(filter), "darker", 40); mlt_properties_set_int(MLT_FILTER_PROPERTIES(filter), "lighter", 40); } return filter; } mlt-7.22.0/src/modules/oldfilm/filter_lines.yml000664 000000 000000 00000003225 14531534050 021447 0ustar00rootroot000000 000000 schema_version: 7.0 type: filter # consumer, filter, producer, or transition identifier: lines title: Scratchlines version: 0.2.6 copyright: Copyright (C) 2008 Marco Gittler license: GPL language: en creator: Marco Gittler tags: - Video # this may produce video description: Scratchlines over the Picture icon: filename: oldfilm/lines.svg # relative to $MLT_DATA/modules/ content-type: image/svg notes: Implementation or additional usage notes go here. bugs: # this can be just for documentation, or the tool may disclose it to help user avoid pitfalls - need to do some speed improvement. parameters: - identifier: line_width title: Width of line type: integer description: Linewidth in picture readonly: no required: yes minimum: 0 maximum: 100 default: 2 mutable: yes animation: yes widget: spinner unit: pixel - identifier: num title: Max number of lines type: integer description: Maximal number of lines in picture readonly: no required: yes minimum: 0 maximum: 100 default: 5 mutable: yes animation: yes widget: spinner unit: lines - identifier: darker title: Max darker type: integer description: Make image up to n values darker behind line readonly: no required: yes minimum: 0 maximum: 100 default: 40 mutable: yes animation: yes widget: spinner - identifier: lighter title: Max lighter type: integer description: Make image up to n values lighter behind line readonly: no required: yes minimum: 0 maximum: 100 default: 40 mutable: yes animation: yes widget: spinner mlt-7.22.0/src/modules/oldfilm/filter_oldfilm.c000664 000000 000000 00000017437 14531534050 021416 0ustar00rootroot000000 000000 /* * filter_oldfilm.c -- oldfilm filter * Copyright (c) 2007 Marco Gittler * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include static double sinarr[] = { 0.0, 0.0627587292804297, 0.125270029508395, 0.18728744713136, 0.2485664757507, 0.308865520098932, 0.3679468485397, 0.425577530335206, 0.481530353985902, 0.535584723021826, 0.587527525713892, 0.637153975276265, 0.684268417247276, 0.728685100865749, 0.770228911401552, 0.80873606055313, 0.84405473219009, 0.876045680894979, 0.904582780944473, 0.929553523565587, 0.95085946050647, 0.968416592172968, 0.982155698800724, 0.99202261335714, 0.997978435097294, 0.999999682931835, 0.99807838800221, 0.992222125098244, 0.982453982794196, 0.968812472421035, 0.951351376233828, 0.930139535372831, 0.90526057845426, 0.876812591860795, 0.844907733031696, 0.809671788277164, 0.771243676860277, 0.72977490330168, 0.685428960066342, 0.638380682987321, 0.588815561967795, 0.536929009678953, 0.48292559113694, 0.427018217196276, 0.369427305139443, 0.310379909672042, 0.250108827749629, 0.188851680765468, 0.12684997771773, 0.064348163049637, 0.00159265291648683, -0.0611691363208864, -0.123689763546002, -0.18572273843423, -0.247023493251739, -0.307350347074556, -0.366465458626247, -0.424135763977612, -0.48013389541149, -0.534239077829989, -0.586237999170027, -0.635925651395529, -0.683106138750633, -0.727593450087328, -0.769212192222595, -0.807798281433749, -0.84319959036574, -0.875276547799941, -0.903902688919827, -0.928965153904073, -0.950365132881376, -0.968018255492714, -0.981854923525203, -0.991820585306115, -0.997875950775248, -0.999997146387718, -0.998175809236459, -0.992419120023356, -0.982749774749007, -0.969205895232745, -0.951840878815686, -0.930723187839362, -0.905936079729926, -0.877577278752084, -0.845758590726883, -0.810605462232336, -0.772256486024771, -0.730862854630786, -0.68658776426406, -0.639605771417098, -0.590102104664575, -0.538271934391528, -0.484319603325524, -0.428457820906457, -0.37090682467023, -0.311893511952568, -0.251650545336281, -0.190415435368805, -0.128429604166398, -0.0659374335968388, 0.0, }; static int filter_get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { mlt_filter filter = (mlt_filter) mlt_frame_pop_service(frame); mlt_properties properties = MLT_FILTER_PROPERTIES(filter); mlt_position pos = mlt_filter_get_position(filter, frame); mlt_position len = mlt_filter_get_length2(filter, frame); *format = mlt_image_yuv422; int error = mlt_frame_get_image(frame, image, format, width, height, 1); if (error == 0 && *image) { int h = *height; int w = *width; int x = 0; int y = 0; double position = mlt_filter_get_progress(filter, frame); srand(position * 10000); int delta = mlt_properties_anim_get_int(properties, "delta", pos, len); int every = mlt_properties_anim_get_int(properties, "every", pos, len); int bdu = mlt_properties_anim_get_int(properties, "brightnessdelta_up", pos, len); int bdd = mlt_properties_anim_get_int(properties, "brightnessdelta_down", pos, len); int bevery = mlt_properties_anim_get_int(properties, "brightnessdelta_every", pos, len); int udu = mlt_properties_anim_get_int(properties, "unevendevelop_up", pos, len); int udd = mlt_properties_anim_get_int(properties, "unevendevelop_down", pos, len); int uduration = mlt_properties_anim_get_int(properties, "unevendevelop_duration", pos, len); int diffpic = 0; if (delta) { mlt_profile profile = mlt_service_profile(MLT_FILTER_SERVICE(filter)); delta *= mlt_profile_scale_width(profile, *width); diffpic = rand() % MAX(delta, 1) * 2 - delta; } int brightdelta = 0; if ((bdu + bdd) != 0) brightdelta = rand() % (bdu + bdd) - bdd; if (rand() % 100 > every) diffpic = 0; if (rand() % 100 > bevery) brightdelta = 0; int yend, ydiff; int unevendevelop_delta = 0; if (uduration > 0) { float uval = sinarr[(((int) position) % uduration) * 100 / uduration]; unevendevelop_delta = uval * (uval > 0 ? udu : udd); } if (diffpic <= 0) { y = h; yend = 0; ydiff = -1; } else { y = 0; yend = h; ydiff = 1; } int full_range = mlt_properties_get_int(MLT_FRAME_PROPERTIES(frame), "full_range"); int min = full_range ? 0 : 16; int max_luma = full_range ? 255 : 235; for (; y != yend; y += ydiff) { int newy = y + diffpic; uint8_t *p = &(*image)[y * w * 2]; uint8_t *q = &p[diffpic * w * 2]; for (x = 0; x < w * 2; x += 2) { if (newy > 0 && newy < h) { if (((int) q[x] + brightdelta + unevendevelop_delta) > max_luma) { p[x] = max_luma; } else if (((int) q[x] + brightdelta + unevendevelop_delta) < 0) { p[x] = min; } else { p[x] = q[x] + brightdelta + unevendevelop_delta; } p[x + 1] = q[x + 1]; } else { p[x] = min; } } } } return error; } static mlt_frame filter_process(mlt_filter filter, mlt_frame frame) { mlt_frame_push_service(frame, filter); mlt_frame_push_get_image(frame, filter_get_image); return frame; } mlt_filter filter_oldfilm_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = mlt_filter_new(); if (filter != NULL) { filter->process = filter_process; mlt_properties_set(MLT_FILTER_PROPERTIES(filter), "delta", "14"); mlt_properties_set(MLT_FILTER_PROPERTIES(filter), "every", "20"); mlt_properties_set(MLT_FILTER_PROPERTIES(filter), "brightnessdelta_up", "20"); mlt_properties_set(MLT_FILTER_PROPERTIES(filter), "brightnessdelta_down", "30"); mlt_properties_set(MLT_FILTER_PROPERTIES(filter), "brightnessdelta_every", "70"); mlt_properties_set(MLT_FILTER_PROPERTIES(filter), "unevendevelop_up", "60"); mlt_properties_set(MLT_FILTER_PROPERTIES(filter), "unevendevelop_down", "20"); mlt_properties_set(MLT_FILTER_PROPERTIES(filter), "unevendevelop_duration", "70"); } return filter; } mlt-7.22.0/src/modules/oldfilm/filter_oldfilm.yml000664 000000 000000 00000005425 14531534050 021767 0ustar00rootroot000000 000000 schema_version: 7.0 type: filter # consumer, filter, producer, or transition identifier: oldfilm title: Oldfilm version: 0.2.5 copyright: Copyright (C) 2008 Marco Gittler license: GPL language: en creator: Marco Gittler tags: - Video # this may produce video description: Moves the Picture up and down and random brightness change icon: filename: oldfilm/oldfilm.svg # relative to $MLT_DATA/modules/ content-type: image/svg notes: Implementation or additional usage notes go here. bugs: # this can be just for documentation, or the tool may disclose it to help user avoid pitfalls - need to do some speed improvement. parameters: - identifier: delta title: Y-Delta type: integer description: Maximum delta value of Up/Down move readonly: no required: yes minimum: 0 maximum: 400 default: 14 mutable: yes animation: yes widget: spinner unit: pixel - identifier: every title: '% of picture have a delta' type: integer description: n'th % have a Y-Delta in picture readonly: no required: yes minimum: 0 maximum: 100 default: 20 mutable: yes animation: yes widget: spinner unit: '%' - identifier: brightnessdelta_up title: Brightness up type: integer description: Makes image n values lighter readonly: no required: yes minimum: 0 maximum: 100 default: 20 mutable: yes animation: yes widget: spinner - identifier: brightnessdelta_down title: Brightness down type: integer description: Makes image n values darker readonly: no required: yes minimum: 0 maximum: 100 default: 30 mutable: yes animation: yes widget: spinner - identifier: brightnessdelta_every title: Brightness every type: integer description: Change value only for n/100 readonly: no required: yes minimum: 0 maximum: 100 default: 70 mutable: yes animation: yes widget: spinner unit: '%' - identifier: unevendevelop_up title: Unevendevelop up type: integer description: Makes image n values lighter readonly: no required: yes minimum: 0 maximum: 100 default: 60 mutable: yes animation: yes widget: spinner - identifier: unevendevelop_down title: Unevendevelop down type: integer description: Makes image n values darker readonly: no required: yes minimum: 0 maximum: 100 default: 20 mutable: yes animation: yes widget: spinner - identifier: unevendevelop_duration title: Unevendevelop Duration type: integer description: Time (in frames) of a up/down cycle readonly: no required: yes minimum: 0 maximum: 10000 default: 70 mutable: yes animation: yes widget: spinner unit: '%' mlt-7.22.0/src/modules/oldfilm/filter_tcolor.c000664 000000 000000 00000007037 14531534050 021265 0ustar00rootroot000000 000000 /* * filter_tcolor.c -- tcolor filter * Copyright (c) 2007 Marco Gittler * Copyright (c) 2022 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include typedef struct { uint8_t *image; int width; int height; double over_cr; double over_cb; } slice_desc; static int do_slice_proc(int id, int index, int jobs, void *data) { (void) id; // unused slice_desc *desc = (slice_desc *) data; int slice_line_start, slice_height = mlt_slices_size_slice(jobs, index, desc->height, &slice_line_start); int slice_line_end = slice_line_start + slice_height; int line_size = desc->width * 2; int x, y; for (y = slice_line_start; y < slice_line_end; y++) { uint8_t *p = desc->image + y * line_size; for (x = 0; x < line_size; x += 4) { p[x + 1] = CLAMP(((double) p[x + 1] - 127.0) * desc->over_cb + 127.0, 0, 255); p[x + 3] = CLAMP(((double) p[x + 3] - 127.0) * desc->over_cr + 127.0, 0, 255); } } return 0; } static int filter_get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { mlt_filter filter = (mlt_filter) mlt_frame_pop_service(frame); mlt_properties properties = MLT_FILTER_PROPERTIES(filter); mlt_position pos = mlt_filter_get_position(filter, frame); mlt_position len = mlt_filter_get_length2(filter, frame); *format = mlt_image_yuv422; int error = mlt_frame_get_image(frame, image, format, width, height, 1); if (error == 0 && *image) { slice_desc desc; desc.over_cr = mlt_properties_anim_get_double(properties, "oversaturate_cr", pos, len) / 100.0; desc.over_cb = mlt_properties_anim_get_double(properties, "oversaturate_cb", pos, len) / 100.0; desc.image = *image; desc.width = *width; desc.height = *height; mlt_slices_run_normal(0, do_slice_proc, &desc); } return error; } static mlt_frame filter_process(mlt_filter filter, mlt_frame frame) { mlt_frame_push_service(frame, filter); mlt_frame_push_get_image(frame, filter_get_image); return frame; } mlt_filter filter_tcolor_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = mlt_filter_new(); if (filter != NULL) { filter->process = filter_process; mlt_properties_set(MLT_FILTER_PROPERTIES(filter), "oversaturate_cr", "190"); mlt_properties_set(MLT_FILTER_PROPERTIES(filter), "oversaturate_cb", "190"); } return filter; } mlt-7.22.0/src/modules/oldfilm/filter_tcolor.yml000664 000000 000000 00000002353 14531534050 021640 0ustar00rootroot000000 000000 schema_version: 7.0 type: filter # consumer, filter, producer, or transition identifier: tcolor title: Technicolor version: 0.2.5 copyright: Copyright (C) 2008 Marco Gittler license: GPL language: en creator: Marco Gittler tags: - Video # this may produce video description: Oversaturate the Color in Video, like in old Technicolor movies icon: filename: oldfilm/tcolor.svg # relative to $MLT_DATA/modules/ content-type: image/svg notes: Implementation or additional usage notes go here. bugs: # this can be just for documentation, or the tool may disclose it to help user avoid pitfalls - need to do some speed improvement. parameters: - identifier: oversaturate_cr # 'argument' is a reserved name for a value supplied to the factory title: Blue/Yellow- axis type: integer description: Adjust factor for Blue/Yellow axis readonly: no required: yes minimum: -400 maximum: 400 default: 190 mutable: yes animation: yes widget: spinner - identifier: oversaturate_cb title: Red/Green-axis type: integer description: Adjust factor for Red/Green axis readonly: no required: yes minimum: -400 maximum: 400 default: 190 mutable: yes animation: yes widget: spinner mlt-7.22.0/src/modules/oldfilm/filter_vignette.c000664 000000 000000 00000012067 14531534050 021607 0ustar00rootroot000000 000000 /* * filter_vignette.c -- vignette filter * Copyright (c) 2007 Marco Gittler * Copyright (c) 2009-2022 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include typedef struct { uint8_t *image; int width; int height; double smooth; double radius; double cx, cy; double opacity; int mode; } slice_desc; static int slice_proc(int id, int index, int jobs, void *data) { (void) id; // unused slice_desc *d = (slice_desc *) data; int slice_line_start, slice_height = mlt_slices_size_slice(jobs, index, d->height, &slice_line_start); uint8_t *p = d->image + slice_line_start * d->width * 2; int w2 = d->cx, h2 = d->cy; double delta = 1.0; for (int y = slice_line_start; y < slice_line_start + slice_height; y++) { int h2_pow2 = pow(y - h2, 2.0); for (int x = 0; x < d->width; x++, p += 2) { int dx = sqrt(h2_pow2 + pow(x - w2, 2.0)); if (d->radius - d->smooth > dx) { // center, make not darker continue; } else if (d->radius + d->smooth <= dx) { // max dark after smooth area delta = 0.0; } else { // linear pos from of opacity (from 0 to 1) delta = (d->radius + d->smooth - dx) / (2.0 * d->smooth); if (d->mode == 1) { // make cos for smoother non linear fade delta = pow(cos(((1.0 - delta) * M_PI / 2.0)), 3.0); } } delta = MAX(d->opacity, delta); p[0] = p[0] * delta; p[1] = (p[1] - 127.0) * delta + 127.0; } } return 0; } static int filter_get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { mlt_filter filter = mlt_frame_pop_service(frame); *format = mlt_image_yuv422; int error = mlt_frame_get_image(frame, image, format, width, height, 1); if (error == 0 && *image) { mlt_properties filter_props = MLT_FILTER_PROPERTIES(filter); mlt_position pos = mlt_filter_get_position(filter, frame); mlt_position len = mlt_filter_get_length2(filter, frame); mlt_profile profile = mlt_service_profile(MLT_FILTER_SERVICE(filter)); double scale = mlt_profile_scale_width(profile, *width); slice_desc desc = {.image = *image, .width = *width, .height = *height, .smooth = mlt_properties_anim_get_double(filter_props, "smooth", pos, len) * 100.0 * scale, .radius = mlt_properties_anim_get_double(filter_props, "radius", pos, len) * *width, .cx = mlt_properties_anim_get_double(filter_props, "x", pos, len) * *width, .cy = mlt_properties_anim_get_double(filter_props, "y", pos, len) * *height, .opacity = mlt_properties_anim_get_double(filter_props, "opacity", pos, len), .mode = mlt_properties_get_int(filter_props, "mode")}; mlt_slices_run_normal(0, slice_proc, &desc); } return error; } static mlt_frame filter_process(mlt_filter filter, mlt_frame frame) { mlt_frame_push_service(frame, filter); mlt_frame_push_get_image(frame, filter_get_image); return frame; } mlt_filter filter_vignette_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = mlt_filter_new(); if (filter != NULL) { filter->process = filter_process; mlt_properties_set_double(MLT_FILTER_PROPERTIES(filter), "smooth", 0.8); mlt_properties_set_double(MLT_FILTER_PROPERTIES(filter), "radius", 0.5); mlt_properties_set_double(MLT_FILTER_PROPERTIES(filter), "x", 0.5); mlt_properties_set_double(MLT_FILTER_PROPERTIES(filter), "y", 0.5); mlt_properties_set_double(MLT_FILTER_PROPERTIES(filter), "opacity", 0.0); mlt_properties_set_double(MLT_FILTER_PROPERTIES(filter), "mode", 0); } return filter; } mlt-7.22.0/src/modules/oldfilm/filter_vignette.yml000664 000000 000000 00000003452 14531534050 022164 0ustar00rootroot000000 000000 schema_version: 7.0 type: filter # consumer, filter, producer, or transition identifier: vignette title: Vignette Effect version: 1.0 copyright: Copyright (C) 2008 Marco Gittler license: GPL language: en creator: Marco Gittler tags: - Video # this may produce video description: > Vignette around a point with adjustable smooth, radius, position and transparency icon: filename: oldfilm/vignette.svg # relative to $MLT_DATA/modules/ content-type: image/svg notes: Implementation or additional usage notes go here. bugs: # this can be just for documentation, or the tool may disclose it to help user avoid pitfalls - need to do some speed improvement. parameters: - identifier: smooth title: Feathering type: float readonly: no required: yes mutable: yes animation: yes minimum: 0.0 maximum: 1.0 default: 0.8 - identifier: radius title: Radius type: float readonly: no required: yes mutable: yes animation: yes minimum: 0.0 maximum: 1.0 default: 0.5 - identifier: x title: X Position type: float readonly: no required: yes mutable: yes animation: yes minimum: 0.0 maximum: 1.0 default: 0.5 - identifier: y title: Y Position type: float readonly: no required: yes mutable: yes animation: yes minimum: 0.0 maximum: 1.0 default: 0.5 - identifier: opacity title: Opacity type: float readonly: no required: yes mutable: yes animation: yes minimum: 0.0 maximum: 1.0 default: 0.0 - identifier: mode title: Mode type: integer description: Use linear (0) or cosinus (1) mode to fade from opac to black readonly: no required: yes mutable: yes minimum: 0 maximum: 1 default: 0 widget: checkbox mlt-7.22.0/src/modules/oldfilm/grain.svg000664 000000 000000 00000007552 14531534050 020075 0ustar00rootroot000000 000000 image/svg+xml mlt-7.22.0/src/modules/oldfilm/lines.svg000664 000000 000000 00000004422 14531534050 020100 0ustar00rootroot000000 000000 image/svg+xml mlt-7.22.0/src/modules/oldfilm/oldfilm.svg000664 000000 000000 00000004435 14531534050 020420 0ustar00rootroot000000 000000 image/svg+xml mlt-7.22.0/src/modules/oldfilm/tcolor.svg000664 000000 000000 00000005764 14531534050 020302 0ustar00rootroot000000 000000 image/svg+xml mlt-7.22.0/src/modules/oldfilm/vignette.svg000664 000000 000000 00000006344 14531534050 020620 0ustar00rootroot000000 000000 image/svg+xml mlt-7.22.0/src/modules/opencv/000775 000000 000000 00000000000 14531534050 016107 5ustar00rootroot000000 000000 mlt-7.22.0/src/modules/opencv/CMakeLists.txt000664 000000 000000 00000001026 14531534050 020646 0ustar00rootroot000000 000000 add_library(mltopencv MODULE factory.c filter_opencv_tracker.cpp) file(GLOB YML "*.yml") add_custom_target(Other_opencv_Files SOURCES ${YML} ) target_compile_options(mltopencv PRIVATE ${MLT_COMPILE_OPTIONS}) target_link_libraries(mltopencv PRIVATE mlt ${OpenCV_LIBS}) set_target_properties(mltopencv PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${MLT_MODULE_OUTPUT_DIRECTORY}") install(TARGETS mltopencv LIBRARY DESTINATION ${MLT_INSTALL_MODULE_DIR}) install(FILES filter_opencv_tracker.yml DESTINATION ${MLT_INSTALL_DATA_DIR}/opencv) mlt-7.22.0/src/modules/opencv/factory.c000664 000000 000000 00000003223 14531534050 017722 0ustar00rootroot000000 000000 /* * factory.c -- the factory method interfaces * Copyright (C) 2016 Jean-Baptiste Mardelle * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include extern mlt_filter filter_tracker_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); static mlt_properties metadata(mlt_service_type type, const char *id, void *data) { char file[PATH_MAX]; snprintf(file, PATH_MAX, "%s/opencv/%s", mlt_environment("MLT_DATA"), (char *) data); return mlt_properties_parse_yaml(file); } MLT_REPOSITORY { MLT_REGISTER(mlt_service_filter_type, "opencv.tracker", filter_tracker_init); MLT_REGISTER_METADATA(mlt_service_filter_type, "opencv.tracker", metadata, "filter_opencv_tracker.yml"); } mlt-7.22.0/src/modules/opencv/filter_opencv_tracker.cpp000664 000000 000000 00000060040 14531534050 023165 0ustar00rootroot000000 000000 /* * filter_tracker.cpp -- Motion tracker * Copyright (C) 2016 Jean-Baptiste Mardelle * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include #define CV_VERSION_INT (CV_VERSION_MAJOR << 16 | CV_VERSION_MINOR << 8 | CV_VERSION_REVISION) #if CV_VERSION_INT > 0x040502 #include #include // for stat() #include // for stat() #include // for stat() #endif typedef struct { cv::Ptr tracker; #if CV_VERSION_INT > 0x040502 cv::Ptr legacyTracker; #endif #if CV_VERSION_INT < 0x040500 cv::Rect2d boundingBox; #else cv::Rect boundingBox; #endif char *algo; mlt_rect startRect; bool initialized; bool playback; bool analyze; int last_position; int analyse_width; int analyse_height; mlt_position producer_in; mlt_position producer_length; bool legacyTracking; } private_data; static void property_changed(mlt_service owner, mlt_filter filter, mlt_event_data event_data) { private_data *pdata = (private_data *) filter->child; mlt_properties filter_properties = MLT_FILTER_PROPERTIES(filter); const char *name = mlt_event_data_to_string(event_data); if (name && !strcmp(name, "results")) { mlt_properties_anim_get_int(filter_properties, "results", 0, -1); mlt_animation anim = mlt_properties_get_animation(filter_properties, "results"); if (anim && mlt_animation_key_count(anim) > 0) { // We received valid analysis data pdata->initialized = true; pdata->playback = true; return; } else { // Analysis data was discarded pdata->initialized = false; pdata->producer_length = 0; pdata->playback = false; mlt_properties_set(filter_properties, "_results", NULL); } } if (!pdata->initialized) { return; } if (!strcmp(name, "rect")) { // The initial rect was changed, we need to reset the tracker with this new rect mlt_rect rect = mlt_properties_get_rect(filter_properties, "rect"); if (rect.x != pdata->startRect.x || rect.y != pdata->startRect.y || rect.w != pdata->startRect.w || rect.h != pdata->startRect.h) { pdata->playback = false; pdata->initialized = false; } } else if (!strcmp(name, "algo")) { char *algo = mlt_properties_get(filter_properties, "algo"); if (pdata->algo && *pdata->algo != '\0' && strcmp(algo, pdata->algo)) { pdata->playback = false; pdata->initialized = false; } } else if (!strcmp(name, "_reset")) { mlt_properties_set(filter_properties, "results", NULL); mlt_properties_set(filter_properties, "_results", NULL); pdata->initialized = false; pdata->playback = false; } } static void apply( mlt_filter filter, private_data *data, int width, int height, int position, int length) { mlt_properties properties = MLT_FILTER_PROPERTIES(filter); mlt_rect rect = mlt_properties_anim_get_rect(properties, "results", position, -1); mlt_profile profile = mlt_service_profile(MLT_FILTER_SERVICE(filter)); // Calculate the region now double scale_width = mlt_profile_scale_width(profile, width); double scale_height = mlt_profile_scale_height(profile, height); rect.x *= scale_width; rect.w *= scale_width; rect.y *= scale_height; rect.h *= scale_height; data->boundingBox.x = rect.x; data->boundingBox.y = rect.y; data->boundingBox.width = rect.w; data->boundingBox.height = rect.h; } static void analyze(mlt_filter filter, cv::Mat cvFrame, private_data *data, int width, int height, int position, int length) { mlt_properties filter_properties = MLT_FILTER_PROPERTIES(filter); if (data->analyse_width == -1) { // Store analyze width/height data->analyse_width = width; data->analyse_height = height; } else if (data->analyse_width != width || data->analyse_height != height) { // Frame size changed, reset all stored data data->initialized = false; data->analyse_width = width; data->analyse_height = height; } // Create tracker and initialize it if (!data->initialized) { // Build tracker data->tracker.reset(); #if CV_VERSION_INT > 0x040502 data->legacyTracker.reset(); #endif data->legacyTracking = false; data->algo = mlt_properties_get(filter_properties, "algo"); #if CV_VERSION_MAJOR > 3 || (CV_VERSION_MAJOR == 3 && CV_VERSION_MINOR >= 3) if (!data->algo || *data->algo == '\0' || !strcmp(data->algo, "KCF")) { data->tracker = cv::TrackerKCF::create(); } else if (!strcmp(data->algo, "MIL")) { data->tracker = cv::TrackerMIL::create(); } #if CV_VERSION_INT > 0x040502 else if (!strcmp(data->algo, "DaSIAM")) { if (mlt_properties_exists(filter_properties, "modelsfolder")) { char *modelsdir = mlt_properties_get(filter_properties, "modelsfolder"); cv::TrackerDaSiamRPN::Params parameters; char *model1 = (char *) calloc(1, 1000); char *model2 = (char *) calloc(1, 1000); char *model3 = (char *) calloc(1, 1000); strcat(model1, modelsdir); strcat(model2, modelsdir); strcat(model3, modelsdir); strcat(model1, "/dasiamrpn_model.onnx"); strcat(model2, "/dasiamrpn_kernel_cls1.onnx"); strcat(model3, "/dasiamrpn_kernel_r1.onnx"); // Models can be downloaded from: // - network: https://www.dropbox.com/s/rr1lk9355vzolqv/dasiamrpn_model.onnx?dl=0 // - kernel_r1: https://www.dropbox.com/s/999cqx5zrfi7w4p/dasiamrpn_kernel_r1.onnx?dl=0 // - kernel_cls1: https://www.dropbox.com/s/qvmtszx5h339a0w/dasiamrpn_kernel_cls1.onnx?dl=0 struct stat file_info; if (stat(model1, &file_info) == 0 && stat(model2, &file_info) == 0 && stat(model3, &file_info) == 0) { // Models found, process parameters.model = model1; parameters.kernel_cls1 = model2; parameters.kernel_r1 = model3; data->tracker = cv::TrackerDaSiamRPN::create(parameters); } else { mlt_log_error( MLT_FILTER_SERVICE(filter), "DaSIAM models not found, please provide a modelsfolder parameter\n"); } free(model1); free(model2); free(model3); } #if CV_VERSION_INT > 0x040600 } else if (!strcmp(data->algo, "Nano")) { if (mlt_properties_exists(filter_properties, "modelsfolder")) { char *modelsdir = mlt_properties_get(filter_properties, "modelsfolder"); cv::TrackerNano::Params parameters; char *model1 = (char *) calloc(1, 1000); char *model2 = (char *) calloc(1, 1000); strcat(model1, modelsdir); strcat(model2, modelsdir); strcat(model1, "/nanotrack_backbone_sim.onnx"); strcat(model2, "/nanotrack_head_sim.onnx"); // Models can be downloaded from: // https://github.com/HonglinChu/SiamTrackers/tree/master/NanoTrack/models/nanotrackv2 struct stat file_info; if (stat(model1, &file_info) == 0 && stat(model2, &file_info) == 0) { // Models found, process parameters.backbone = model1; parameters.neckhead = model2; data->tracker = cv::TrackerNano::create(parameters); } else { mlt_log_error( MLT_FILTER_SERVICE(filter), "Nano models not found, please provide a modelsfolder parameter\n"); } free(model1); free(model2); } #endif } else if (!strcmp(data->algo, "MOSSE")) { data->legacyTracking = true; data->legacyTracker = cv::legacy::tracking::TrackerMOSSE::create(); } else if (!strcmp(data->algo, "MEDIANFLOW")) { data->legacyTracking = true; data->legacyTracker = cv::legacy::tracking::TrackerMedianFlow::create(); } else if (!strcmp(data->algo, "CSRT")) { data->legacyTracking = true; data->legacyTracker = cv::legacy::tracking::TrackerCSRT::create(); } #endif #if CV_VERSION_INT >= 0x030402 && CV_VERSION_INT < 0x040500 else if (!strcmp(data->algo, "CSRT")) { data->tracker = cv::TrackerCSRT::create(); } else if (!strcmp(data->algo, "MOSSE")) { data->tracker = cv::TrackerMOSSE::create(); } #endif #if CV_VERSION_INT >= 0x030402 && CV_VERSION_INT < 0x040500 else if (!strcmp(data->algo, "TLD")) { data->tracker = cv::TrackerTLD::create(); } else { data->tracker = cv::TrackerBoosting::create(); } #endif // CV_VERSION_INT >= 0x030402 && CV_VERSION_INT < 0x040500 #else if (data->algo == NULL || !strcmp(data->algo, "")) { data->tracker = cv::Tracker::create("KCF"); } else { data->tracker = cv::Tracker::create(data->algo); } #endif // Discard previous results #if CV_VERSION_INT > 0x040502 if (data->tracker == NULL && data->legacyTracker == NULL) { #else if (data->tracker == NULL) { #endif mlt_log_error(MLT_FILTER_SERVICE(filter), "Tracker initialized FAILED\n"); } else { data->startRect = mlt_properties_get_rect(filter_properties, "rect"); mlt_profile profile = mlt_service_profile(MLT_FILTER_SERVICE(filter)); double scale_width = mlt_profile_scale_width(profile, width); double scale_height = mlt_profile_scale_height(profile, height); data->startRect.x *= scale_width; data->startRect.w *= scale_width; data->startRect.y *= scale_height; data->startRect.h *= scale_height; // Ensure startRect is within frame boundaries if (data->startRect.x > width) { data->startRect.x = width - 10; } else { data->startRect.x = MAX(0., data->startRect.x); } if (data->startRect.y > height) { data->startRect.y = height - 10; } else { data->startRect.y = MAX(0., data->startRect.y); } if (data->startRect.x + data->startRect.w > width) { data->startRect.w = width - data->startRect.x; } if (data->startRect.y + data->startRect.h > height) { data->startRect.h = height - data->startRect.y; } data->boundingBox.x = data->startRect.x; data->boundingBox.y = data->startRect.y; data->boundingBox.width = data->startRect.w; data->boundingBox.height = data->startRect.h; if (data->boundingBox.width < 1) { data->boundingBox.width = 50; } if (data->boundingBox.height < 1) { data->boundingBox.height = 50; } #if CV_VERSION_INT >= 0x030402 && CV_VERSION_INT < 0x040500 if (data->tracker->init(cvFrame, data->boundingBox)) { #else { try { if (data->legacyTracking) { #if CV_VERSION_INT > 0x040502 data->legacyTracker->init(cvFrame, data->boundingBox); #endif } else { data->tracker->init(cvFrame, data->boundingBox); } #endif data->initialized = true; data->analyze = true; data->last_position = position - 1; } catch (cv::Exception &e) { mlt_log_warning(MLT_FILTER_SERVICE(filter), "failed to initialize tracker: %s\n", e.what()); } } // init anim property mlt_properties_anim_get_int(filter_properties, "_results", 0, -1); mlt_properties_get_animation(filter_properties, "_results"); } } else { if (data->legacyTracking) { #if CV_VERSION_INT > 0x040502 cv::Rect2d rect(data->boundingBox); data->legacyTracker->update(cvFrame, rect); data->boundingBox = cv::Rect(rect); #endif } else { data->tracker->update(cvFrame, data->boundingBox); } } if (data->analyze && position != data->last_position + 1) { // We are in real time, do not try to store data data->analyze = false; } if (!data->analyze) { return; } // Store results in temp variable mlt_rect rect; rect.x = data->boundingBox.x; rect.y = data->boundingBox.y; rect.w = data->boundingBox.width; rect.h = data->boundingBox.height; rect.o = 0; int steps = mlt_properties_get_int(filter_properties, "steps"); if (steps > 1 && position > 0 && position < data->producer_length - 1) { if (position % steps == 0) mlt_properties_anim_set_rect(filter_properties, "_results", rect, position + data->producer_in, length, mlt_keyframe_smooth); } else { mlt_properties_anim_set_rect(filter_properties, "_results", rect, position + data->producer_in, length, mlt_keyframe_smooth); } if (position + 1 == data->producer_length) { //Analysis finished, store results mlt_animation anim = mlt_properties_get_animation(filter_properties, "_results"); mlt_properties_set(filter_properties, "results", strdup(mlt_animation_serialize(anim))); // Discard temporary data mlt_properties_set(filter_properties, "_results", (char *) NULL); data->playback = true; } data->last_position = position; } /** Get the image. */ static int filter_get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { int error = 0; mlt_filter filter = (mlt_filter) mlt_frame_pop_service(frame); mlt_properties filter_properties = MLT_FILTER_PROPERTIES(filter); mlt_position position = mlt_filter_get_position(filter, frame); mlt_service_lock(MLT_FILTER_SERVICE(filter)); int shape_width = mlt_properties_get_int(filter_properties, "shape_width"); int blur = mlt_properties_get_int(filter_properties, "blur"); cv::Mat cvFrame; private_data *data = (private_data *) filter->child; if (shape_width == 0 && blur == 0 && !data->playback) { error = mlt_frame_get_image(frame, image, format, width, height, 1); mlt_service_unlock(MLT_FILTER_SERVICE(filter)); return error; } else { *format = mlt_image_rgb; error = mlt_frame_get_image(frame, image, format, width, height, 1); cvFrame = cv::Mat(*height, *width, CV_8UC3, *image); } if (data->producer_length == 0) { mlt_producer producer = mlt_frame_get_original_producer(frame); producer = mlt_producer_cut_parent(producer); if (producer) { if (mlt_service_chain_type == mlt_service_identify(MLT_PRODUCER_SERVICE(producer))) { data->producer_length = mlt_filter_get_length2(filter, frame); } else { data->producer_in = mlt_producer_get_in(producer) + mlt_filter_get_in(filter); data->producer_length = mlt_producer_get_playtime(producer) - mlt_filter_get_in(filter); } } } if (!data->initialized) { mlt_properties_anim_get_int(filter_properties, "results", 0, -1); mlt_animation anim = mlt_properties_get_animation(filter_properties, "results"); if (anim && mlt_animation_key_count(anim) > 0) { data->initialized = true; data->playback = true; } } if (data->playback) { // Clip already analysed, don't re-process apply(filter, data, *width, *height, position, data->producer_in + data->producer_length); } else { analyze(filter, cvFrame, data, *width, *height, position, data->producer_in + data->producer_length); } // ensure bounding box is within the frame boundaries or OpenCV will crash if (data->boundingBox.x > *width) { data->boundingBox.x = *width - 10; } else { data->boundingBox.x = MAX(0., data->boundingBox.x); } if (data->boundingBox.y > *height) { data->boundingBox.y = *height - 10; } else { data->boundingBox.y = MAX(0., data->boundingBox.y); } if (data->boundingBox.x + data->boundingBox.width > *width) { data->boundingBox.width = *width - data->boundingBox.x; } if (data->boundingBox.y + data->boundingBox.height > *height) { data->boundingBox.height = *height - data->boundingBox.y; } if (blur > 0) { switch (mlt_properties_get_int(filter_properties, "blur_type")) { case 1: // Gaussian Blur cv::GaussianBlur(cvFrame(data->boundingBox), cvFrame(data->boundingBox), cv::Size(0, 0), blur); break; case 2: // Pixelate if (data->boundingBox.width > 0 && data->boundingBox.height > 0) { cv::Mat roi = cvFrame(data->boundingBox); cv::Mat res; cv::resize(roi, res, cv::Size(MAX(2, data->boundingBox.width / blur), MAX(2, data->boundingBox.height / blur)), cv::INTER_NEAREST); cv::resize(res, roi, cv::Size(data->boundingBox.width, data->boundingBox.height), 0, 0, cv::INTER_NEAREST); cvFrame(data->boundingBox) = roi; } break; case 3: // Opaque fill, handled in shape_width option shape_width = -1; break; case 0: // Median Blur ++blur; if (blur % 2 == 0) { // median blur param must be odd and, minimum 3 ++blur; } cv::medianBlur(cvFrame(data->boundingBox), cvFrame(data->boundingBox), blur); break; default: // Do nothing break; } } // Paint overlay shape if (shape_width != 0) { // Get the OpenCV image mlt_color shape_color = mlt_properties_get_color(filter_properties, "shape_color"); switch (mlt_properties_get_int(filter_properties, "shape")) { case 2: // Arrow cv::arrowedLine(cvFrame, cv::Point(data->boundingBox.x + data->boundingBox.width / 2, data->boundingBox.y - data->boundingBox.height / 2), cv::Point(data->boundingBox.x + data->boundingBox.width / 2, data->boundingBox.y), cv::Scalar(shape_color.r, shape_color.g, shape_color.b), MAX(shape_width, 1), 4, 0, .2); break; case 1: // Ellipse { cv::RotatedRect bounding = cv::RotatedRect(cv::Point2f(data->boundingBox.x + data->boundingBox.width / 2, data->boundingBox.y + data->boundingBox.height / 2), cv::Size2f(data->boundingBox.width, data->boundingBox.height), 0); cv::ellipse(cvFrame, bounding, cv::Scalar(shape_color.r, shape_color.g, shape_color.b), shape_width, 1); } break; case 0: default: // Rectangle cv::rectangle(cvFrame, data->boundingBox, cv::Scalar(shape_color.r, shape_color.g, shape_color.b), shape_width, 1); break; } } mlt_service_unlock(MLT_FILTER_SERVICE(filter)); return error; } /** Filter processing. */ static mlt_frame filter_process(mlt_filter filter, mlt_frame frame) { mlt_frame_push_service(frame, filter); mlt_frame_push_get_image(frame, filter_get_image); return frame; } static void filter_close(mlt_filter filter) { private_data *data = (private_data *) filter->child; free(data); filter->child = NULL; filter->close = NULL; filter->parent.close = NULL; mlt_service_close(&filter->parent); } /** Constructor for the filter. */ extern "C" { mlt_filter filter_tracker_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = mlt_filter_new(); private_data *data = (private_data *) calloc(1, sizeof(private_data)); if (filter && data) { mlt_properties properties = MLT_FILTER_PROPERTIES(filter); mlt_properties_set_int(properties, "shape_width", 1); mlt_properties_set_int(properties, "steps", 5); mlt_properties_set(properties, "algo", "KCF"); data->initialized = false; data->playback = false; data->boundingBox.x = 0; data->boundingBox.y = 0; data->boundingBox.width = 0; data->boundingBox.height = 0; data->analyze = false; data->last_position = -1; data->analyse_width = -1; data->analyse_height = -1; data->producer_in = 0; data->producer_length = 0; filter->child = data; // Create a unique ID for storing data on the frame filter->close = filter_close; filter->process = filter_process; mlt_events_listen(properties, filter, "property-changed", (mlt_listener) property_changed); } else { mlt_log_error(MLT_FILTER_SERVICE(filter), "Filter tracker failed\n"); if (filter) { mlt_filter_close(filter); } if (data) { free(data); } filter = NULL; } return filter; } } mlt-7.22.0/src/modules/opencv/filter_opencv_tracker.yml000664 000000 000000 00000007006 14531534050 023207 0ustar00rootroot000000 000000 schema_version: 7.0 type: filter identifier: opencv.tracker title: OpenCV Motion Tracker copyright: Jean-Baptiste Mardelle creator: Jean-Baptiste Mardelle version: 2 license: LGPLv2.1 language: en url: tags: - Video description: Track and follow an object in the video. notes: > If used in a non-linear workflow, this filter works best with two passes. First pass performs analysis and stores the result in a property. Parallel processing (real_time < -1 or > 1) is not supported for the first pass. Second pass simply applies the results to the image. If no analysis result is found, the filter does its best to work in real time. To analyse clip, you can use with melt, use 'melt ... -consumer xml:output.mlt all=1 real_time=-1'. Analysis data is stored in a "results" property. For the second pass, you can use output.mlt as the input. parameters: - identifier: rect title: Target Rect type: string description: > The rectangle defining the object to be followed (from the 1st frame of the filter). required: no readonly: no mutable: yes default: 0 0 50 50 - identifier: shape title: Shape type: integer description: > The shape to be drawn around tracked object. 0 for a rectangle, 1 for an ellipse, 2 for an arrow. readonly: no required: no minimum: 0 maximum: 5 default: 0 mutable: yes - identifier: shape_width title: Shape Width type: integer description: > Decide if we want to display a shape around followed object during playback. 0 means no display, -1 means a filled shape and > 0 determines the border width. readonly: no required: no minimum: -1 maximum: 100 default: 1 mutable: yes - identifier: shape_color title: Shape Color type: color description: > The color used to paint the shape around target object. readonly: no required: no default: 0xffffffff mutable: yes - identifier: blur title: Blur type: integer description: > Decide if we want to blur selected object. 0 means no blur, > 0 means blur intensity. readonly: no required: no minimum: 0 maximum: 100 default: 0 mutable: yes - identifier: blur_type title: Blur Type type: integer description: > Decide which blur method is used. 0 for median blur, 1 for gaussian blur, 2 for pixelate, 3 for opaque fill. readonly: no required: no minimum: 0 maximum: 100 default: 0 mutable: yes - identifier: algo title: Tracker Algorithm type: string description: > The algorithm used for tracking (OpenCV name). Check OpenCV doc for full algorithm list, most commons are KCF, MIL, BOOSTING, TLD readonly: no required: no default: KCF mutable: yes - identifier: steps title: Keyframes spacing type: integer description: > Defines the frequency of stored keyframes. A keyframe is created every steps frames. mutable: no readonly: no required: no default: 5 minimum: 0 - identifier: modelsfolder title: OpenCV models folder type: string description: > The folder where the tracker models are stored if any mutable: no readonly: no required: no - identifier: results title: Analysis Results type: string description: > Set after analysis. This is an animated rect following object designated with initial rect property. mutable: yes animation: yes readonly: yes mlt-7.22.0/src/modules/plus/000775 000000 000000 00000000000 14531534050 015600 5ustar00rootroot000000 000000 mlt-7.22.0/src/modules/plus/CMakeLists.txt000664 000000 000000 00000004115 14531534050 020341 0ustar00rootroot000000 000000 add_library(mltplus MODULE consumer_blipflash.c factory.c filter_affine.c filter_charcoal.c filter_chroma_hold.c filter_chroma.c filter_dynamictext.c filter_dynamic_loudness.c filter_invert.c filter_lift_gamma_gain.c filter_loudness.c filter_loudness_meter.c filter_lumakey.c filter_rgblut.c filter_sepia.c filter_shape.c filter_spot_remover.c filter_strobe.c filter_text.c filter_threshold.c filter_timer.c interp.h producer_blipflash.c producer_count.c producer_pgm.c transition_affine.c ) file(GLOB YML "*.yml") add_custom_target(Other_plus_Files SOURCES ${YML} ) target_compile_options(mltplus PRIVATE ${MLT_COMPILE_OPTIONS}) target_link_libraries(mltplus PRIVATE mlt m Threads::Threads) if(TARGET PkgConfig::FFTW) target_sources(mltplus PRIVATE filter_dance.c filter_fft.c) target_link_libraries(mltplus PRIVATE PkgConfig::FFTW) target_compile_definitions(mltplus PRIVATE USE_FFTW) install(FILES filter_dance.yml filter_fft.yml DESTINATION ${MLT_INSTALL_DATA_DIR}/plus) endif() if(TARGET PkgConfig::libebur128) target_link_libraries(mltplus PRIVATE PkgConfig::libebur128) else() target_sources(mltplus PRIVATE ebur128/ebur128.c) target_include_directories(mltplus PRIVATE ebur128 ebur128/queue) target_compile_definitions(mltplus PRIVATE USE_INTERNAL_LIBEBUR128) endif() set_target_properties(mltplus PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${MLT_MODULE_OUTPUT_DIRECTORY}") install(TARGETS mltplus LIBRARY DESTINATION ${MLT_INSTALL_MODULE_DIR}) install(FILES consumer_blipflash.yml filter_affine.yml filter_charcoal.yml filter_chroma_hold.yml filter_chroma.yml filter_dynamic_loudness.yml filter_dynamictext.yml filter_invert.yml filter_lift_gamma_gain.yml filter_loudness_meter.yml filter_loudness.yml filter_lumakey.yml filter_rgblut.yml filter_sepia.yml filter_shape.yml filter_spot_remover.yml filter_strobe.yml filter_text.yml filter_threshold.yml filter_timer.yml producer_blipflash.yml producer_count.yml producer_pgm.yml transition_affine.yml DESTINATION ${MLT_INSTALL_DATA_DIR}/plus ) mlt-7.22.0/src/modules/plus/consumer_blipflash.c000664 000000 000000 00000031746 14531534050 021636 0ustar00rootroot000000 000000 /* * consumer_blipflash.c -- a consumer to measure A/V sync from a blip/flash * source * Copyright (C) 2013 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ // mlt Header files #include #include #include // System header files #include #include #include #include #include // Private constants #define SAMPLE_FREQ 48000 #define FLASH_LUMA_THRESHOLD 150 #define BLIP_THRESHOLD 0.5 // Private types typedef struct { int64_t flash_history[2]; int flash_history_count; int64_t blip_history[2]; int blip_history_count; int blip_in_progress; int samples_since_blip; int blip; int flash; int sample_offset; FILE *out_file; int report_frames; } avsync_stats; // Forward references. static int consumer_start(mlt_consumer consumer); static int consumer_stop(mlt_consumer consumer); static int consumer_is_stopped(mlt_consumer consumer); static void *consumer_thread(void *arg); static void consumer_close(mlt_consumer consumer); /** Initialize the consumer. */ mlt_consumer consumer_blipflash_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { // Allocate the consumer mlt_consumer consumer = mlt_consumer_new(profile); mlt_properties consumer_properties = MLT_CONSUMER_PROPERTIES(consumer); avsync_stats *stats = NULL; // If memory allocated and initializes without error if (consumer != NULL) { // Set up start/stop/terminated callbacks consumer->close = consumer_close; consumer->start = consumer_start; consumer->stop = consumer_stop; consumer->is_stopped = consumer_is_stopped; stats = mlt_pool_alloc(sizeof(avsync_stats)); stats->flash_history_count = 0; stats->blip_history_count = 0; stats->blip_in_progress = 0; stats->samples_since_blip = 0; stats->blip = 0; stats->flash = 0; stats->sample_offset = INT_MAX; stats->report_frames = 0; stats->out_file = stdout; if (arg != NULL) { FILE *out_file = mlt_fopen(arg, "w"); if (out_file != NULL) stats->out_file = out_file; } mlt_properties_set_data(consumer_properties, "_stats", stats, 0, NULL, NULL); mlt_properties_set(consumer_properties, "report", "blip"); } // Return this return consumer; } /** Start the consumer. */ static int consumer_start(mlt_consumer consumer) { // Get the properties mlt_properties properties = MLT_CONSUMER_PROPERTIES(consumer); // Check that we're not already running if (!mlt_properties_get_int(properties, "_running")) { // Allocate a thread pthread_t *thread = calloc(1, sizeof(pthread_t)); // Assign the thread to properties mlt_properties_set_data(properties, "_thread", thread, sizeof(pthread_t), free, NULL); // Set the running state mlt_properties_set_int(properties, "_running", 1); // Create the thread pthread_create(thread, NULL, consumer_thread, consumer); } return 0; } /** Stop the consumer. */ static int consumer_stop(mlt_consumer consumer) { // Get the properties mlt_properties properties = MLT_CONSUMER_PROPERTIES(consumer); // Check that we're running if (mlt_properties_get_int(properties, "_running")) { // Get the thread pthread_t *thread = mlt_properties_get_data(properties, "_thread", NULL); // Stop the thread mlt_properties_set_int(properties, "_running", 0); // Wait for termination if (thread) pthread_join(*thread, NULL); } return 0; } /** Determine if the consumer is stopped. */ static int consumer_is_stopped(mlt_consumer consumer) { // Get the properties mlt_properties properties = MLT_CONSUMER_PROPERTIES(consumer); return !mlt_properties_get_int(properties, "_running"); } static void detect_flash(mlt_frame frame, mlt_position pos, double fps, avsync_stats *stats) { int width = 0; int height = 0; mlt_image_format format = mlt_image_yuv422; uint8_t *image = NULL; int error = mlt_frame_get_image(frame, &image, &format, &width, &height, 0); if (!error && format == mlt_image_yuv422 && image != NULL) { int i, j = 0; int y_accumulator = 0; // Add up the luma values from 4 samples in 4 different quadrants. for (i = 1; i < 3; i++) { int x = (width / 3) * i; x = x - x % 2; // Make sure this is a luma sample for (j = 1; j < 3; j++) { int y = (height / 3) * j; y_accumulator += image[y * height * 2 + x * 2]; } } // If the average luma value is > 150, assume it is a flash. stats->flash = (y_accumulator / 4) > FLASH_LUMA_THRESHOLD; } if (stats->flash) { stats->flash_history[1] = stats->flash_history[0]; stats->flash_history[0] = mlt_audio_calculate_samples_to_position(fps, SAMPLE_FREQ, pos); if (stats->flash_history_count < 2) { stats->flash_history_count++; } } } static void detect_blip(mlt_frame frame, mlt_position pos, double fps, avsync_stats *stats) { int frequency = SAMPLE_FREQ; int channels = 1; int samples = mlt_audio_calculate_frame_samples(fps, frequency, pos); mlt_audio_format format = mlt_audio_float; float *buffer = NULL; int error = mlt_frame_get_audio(frame, (void **) &buffer, &format, &frequency, &channels, &samples); if (!error && format == mlt_audio_float && buffer != NULL) { int i = 0; for (i = 0; i < samples; i++) { if (!stats->blip_in_progress) { if (buffer[i] > BLIP_THRESHOLD || buffer[i] < -BLIP_THRESHOLD) { // This sample must start a blip stats->blip_in_progress = 1; stats->samples_since_blip = 0; stats->blip_history[1] = stats->blip_history[0]; stats->blip_history[0] = mlt_audio_calculate_samples_to_position(fps, SAMPLE_FREQ, pos); stats->blip_history[0] += i; if (stats->blip_history_count < 2) { stats->blip_history_count++; } stats->blip = 1; } } else { if (buffer[i] > -BLIP_THRESHOLD && buffer[i] < BLIP_THRESHOLD) { if (++stats->samples_since_blip > frequency / 1000) { // One ms of silence means the blip is over stats->blip_in_progress = 0; stats->samples_since_blip = 0; } } else { stats->samples_since_blip = 0; } } } } } static void calculate_sync(avsync_stats *stats) { if (stats->blip || stats->flash) { if (stats->flash_history_count > 0 && stats->blip_history_count > 0 && stats->blip_history[0] == stats->flash_history[0]) { // The flash and blip occurred at the same time. stats->sample_offset = 0; } if (stats->flash_history_count > 1 && stats->blip_history_count > 0 && stats->blip_history[0] <= stats->flash_history[0] && stats->blip_history[0] >= stats->flash_history[1]) { // The latest blip occurred between two flashes if (stats->flash_history[0] - stats->blip_history[0] > stats->blip_history[0] - stats->flash_history[1]) { // Blip is closer to the previous flash. // F1---B0--------F0 // ^----^ // Video leads audio (negative number). stats->sample_offset = (int) (stats->flash_history[1] - stats->blip_history[0]); } else { // Blip is closer to the current flash. // F1--------B0---F0 // ^----^ // Audio leads video (positive number). stats->sample_offset = (int) (stats->flash_history[0] - stats->blip_history[0]); } } else if (stats->blip_history_count > 1 && stats->flash_history_count > 0 && stats->flash_history[0] <= stats->blip_history[0] && stats->flash_history[0] >= stats->blip_history[1]) { // The latest flash occurred between two blips if (stats->blip_history[0] - stats->flash_history[0] > stats->flash_history[0] - stats->blip_history[1]) { // Flash is closer to the previous blip. // B1---F0--------B0 // ^----^ // Audio leads video (positive number). stats->sample_offset = (int) (stats->flash_history[0] - stats->blip_history[1]); } else { // Flash is closer to the latest blip. // B1--------F0---B0 // ^----^ // Video leads audio (negative number). stats->sample_offset = (int) (stats->flash_history[0] - stats->blip_history[0]); } } } } static void report_results(avsync_stats *stats, mlt_position pos) { if (stats->report_frames || stats->blip) { if (stats->sample_offset == INT_MAX) { fprintf(stats->out_file, MLT_POSITION_FMT "\t??\n", pos); } else { // Convert to milliseconds. double ms_offset = (double) stats->sample_offset * 1000.0 / (double) SAMPLE_FREQ; fprintf(stats->out_file, MLT_POSITION_FMT "\t%02.02f\n", pos, ms_offset); } } stats->blip = 0; stats->flash = 0; } /** The main thread - the argument is simply the consumer. */ static void *consumer_thread(void *arg) { // Map the argument to the object mlt_consumer consumer = arg; // Get the properties mlt_properties properties = MLT_CONSUMER_PROPERTIES(consumer); // Convenience functionality int terminate_on_pause = mlt_properties_get_int(properties, "terminate_on_pause"); int terminated = 0; // Frame and size mlt_frame frame = NULL; // Loop while running while (!terminated && mlt_properties_get_int(properties, "_running")) { // Get the frame frame = mlt_consumer_rt_frame(consumer); // Check for termination if (terminate_on_pause && frame != NULL) terminated = mlt_properties_get_double(MLT_FRAME_PROPERTIES(frame), "_speed") == 0.0; // Check that we have a frame to work with if (frame) { avsync_stats *stats = mlt_properties_get_data(properties, "_stats", NULL); double fps = mlt_properties_get_double(properties, "fps"); mlt_position pos = mlt_frame_get_position(frame); if (!strcmp(mlt_properties_get(properties, "report"), "frame")) { stats->report_frames = 1; } else { stats->report_frames = 0; } detect_flash(frame, pos, fps, stats); detect_blip(frame, pos, fps, stats); calculate_sync(stats); report_results(stats, pos); // Close the frame mlt_events_fire(properties, "consumer-frame-show", mlt_event_data_from_frame(frame)); mlt_frame_close(frame); } } // Indicate that the consumer is stopped mlt_properties_set_int(properties, "_running", 0); mlt_consumer_stopped(consumer); return NULL; } /** Close the consumer. */ static void consumer_close(mlt_consumer consumer) { mlt_properties consumer_properties = MLT_CONSUMER_PROPERTIES(consumer); avsync_stats *stats = mlt_properties_get_data(consumer_properties, "_stats", NULL); // Stop the consumer mlt_consumer_stop(consumer); // Close the file if (stats->out_file != stdout) { fclose(stats->out_file); } // Clean up memory mlt_pool_release(stats); // Close the parent mlt_consumer_close(consumer); // Free the memory free(consumer); } mlt-7.22.0/src/modules/plus/consumer_blipflash.yml000664 000000 000000 00000001430 14531534050 022200 0ustar00rootroot000000 000000 schema_version: 7.0 type: consumer identifier: blipflash title: Blip Flash version: 1 copyright: Meltytech, LLC license: LGPLv2.1 language: en tags: - Video - Audio description: > Calculate the A/V sync for a blip flash source. Sync can be recalculated whenever a blip or a flash is detected. parameters: - identifier: resource argument: yes title: Report File type: string description: > The file to report the results to. If empty, the results will be reported to standard out. required: no widget: filesave - identifier: report title: Report Style type: string description: > When to report sync - every frame or only when blips occur. default: blip values: - blip - frame mutable: yes widget: combo mlt-7.22.0/src/modules/plus/ebur128/000775 000000 000000 00000000000 14531534050 016770 5ustar00rootroot000000 000000 mlt-7.22.0/src/modules/plus/ebur128/COPYING000664 000000 000000 00000002043 14531534050 020022 0ustar00rootroot000000 000000 Copyright (c) 2011 Jan KokemĂĽller Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. mlt-7.22.0/src/modules/plus/ebur128/ebur128.c000664 000000 000000 00000150102 14531534050 020323 0ustar00rootroot000000 000000 /* See COPYING file for copyright and license details. */ #include "ebur128.h" #include #include #include /* You may have to define _USE_MATH_DEFINES if you use MSVC */ #include #include /* This can be replaced by any BSD-like queue implementation. */ #include #define CHECK_ERROR(condition, errorcode, goto_point) \ if ((condition)) { \ errcode = (errorcode); \ goto goto_point; \ } #define EBUR128_MAX(a, b) (((a) > (b)) ? (a) : (b)) static int safe_size_mul(size_t nmemb, size_t size, size_t* result) { /* Adapted from OpenBSD reallocarray. */ #define MUL_NO_OVERFLOW (((size_t) 1) << (sizeof(size_t) * 4)) if ((nmemb >= MUL_NO_OVERFLOW || size >= MUL_NO_OVERFLOW) && /**/ nmemb > 0 && ((size_t) (-1)) / nmemb < size) { return 1; } #undef MUL_NO_OVERFLOW *result = nmemb * size; return 0; } STAILQ_HEAD(ebur128_double_queue, ebur128_dq_entry); struct ebur128_dq_entry { double z; STAILQ_ENTRY(ebur128_dq_entry) entries; }; #define ALMOST_ZERO 0.000001 #define FILTER_STATE_SIZE 5 typedef struct { unsigned int count; /* Number of coefficients in this subfilter */ unsigned int* index; /* Delay index of corresponding filter coeff */ double* coeff; /* List of subfilter coefficients */ } interp_filter; typedef struct { /* Data structure for polyphase FIR interpolator */ unsigned int factor; /* Interpolation factor of the interpolator */ unsigned int taps; /* Taps (prefer odd to increase zero coeffs) */ unsigned int channels; /* Number of channels */ unsigned int delay; /* Size of delay buffer */ interp_filter* filter; /* List of subfilters (one for each factor) */ float** z; /* List of delay buffers (one for each channel) */ unsigned int zi; /* Current delay buffer index */ } interpolator; /** BS.1770 filter state. */ typedef double filter_state[FILTER_STATE_SIZE]; struct ebur128_state_internal { /** Filtered audio data (used as ring buffer). */ double* audio_data; /** Size of audio_data array. */ size_t audio_data_frames; /** Current index for audio_data. */ size_t audio_data_index; /** How many frames are needed for a gating block. Will correspond to 400ms * of audio at initialization, and 100ms after the first block (75% overlap * as specified in the 2011 revision of BS1770). */ unsigned long needed_frames; /** The channel map. Has as many elements as there are channels. */ int* channel_map; /** How many samples fit in 100ms (rounded). */ unsigned long samples_in_100ms; /** BS.1770 filter coefficients (nominator). */ double b[5]; /** BS.1770 filter coefficients (denominator). */ double a[5]; /** one filter_state per channel. */ filter_state* v; /** Linked list of block energies. */ struct ebur128_double_queue block_list; unsigned long block_list_max; unsigned long block_list_size; /** Linked list of 3s-block energies, used to calculate LRA. */ struct ebur128_double_queue short_term_block_list; unsigned long st_block_list_max; unsigned long st_block_list_size; int use_histogram; unsigned long* block_energy_histogram; unsigned long* short_term_block_energy_histogram; /** Keeps track of when a new short term block is needed. */ size_t short_term_frame_counter; /** Maximum sample peak, one per channel */ double* sample_peak; double* prev_sample_peak; /** Maximum true peak, one per channel */ double* true_peak; double* prev_true_peak; interpolator* interp; float* resampler_buffer_input; size_t resampler_buffer_input_frames; float* resampler_buffer_output; size_t resampler_buffer_output_frames; /** The maximum window duration in ms. */ unsigned long window; unsigned long history; }; static double relative_gate = -10.0; /* Those will be calculated when initializing the library */ static double relative_gate_factor; static double minus_twenty_decibels; static double histogram_energies[1000]; static double histogram_energy_boundaries[1001]; static interpolator* interp_create(unsigned int taps, unsigned int factor, unsigned int channels) { int errcode = EBUR128_SUCCESS; interpolator* interp; unsigned int j; interp = (interpolator*) calloc(1, sizeof(interpolator)); CHECK_ERROR(!interp, EBUR128_ERROR_NOMEM, exit); interp->taps = taps; interp->factor = factor; interp->channels = channels; interp->delay = (interp->taps + interp->factor - 1) / interp->factor; /* Initialize the filter memory * One subfilter per interpolation factor. */ interp->filter = (interp_filter*) calloc(interp->factor, sizeof(*interp->filter)); CHECK_ERROR(!interp->filter, EBUR128_ERROR_NOMEM, free_interp); for (j = 0; j < interp->factor; j++) { interp->filter[j].index = (unsigned int*) calloc(interp->delay, sizeof(unsigned int)); interp->filter[j].coeff = (double*) calloc(interp->delay, sizeof(double)); CHECK_ERROR(!interp->filter[j].index || !interp->filter[j].coeff, EBUR128_ERROR_NOMEM, free_filter_index_coeff); } /* One delay buffer per channel. */ interp->z = (float**) calloc(interp->channels, sizeof(float*)); CHECK_ERROR(!interp->z, EBUR128_ERROR_NOMEM, free_filter_index_coeff); for (j = 0; j < interp->channels; j++) { interp->z[j] = (float*) calloc(interp->delay, sizeof(float)); CHECK_ERROR(!interp->z[j], EBUR128_ERROR_NOMEM, free_filter_z); } /* Calculate the filter coefficients */ for (j = 0; j < interp->taps; j++) { /* Calculate sinc */ double m = (double) j - (double) (interp->taps - 1) / 2.0; double c = 1.0; if (fabs(m) > ALMOST_ZERO) { c = sin(m * M_PI / interp->factor) / (m * M_PI / interp->factor); } /* Apply Hanning window */ c *= 0.5 * (1 - cos(2 * M_PI * j / (interp->taps - 1))); if (fabs(c) > ALMOST_ZERO) { /* Ignore any zero coeffs. */ /* Put the coefficient into the correct subfilter */ unsigned int f = j % interp->factor; unsigned int t = interp->filter[f].count++; interp->filter[f].coeff[t] = c; interp->filter[f].index[t] = j / interp->factor; } } if (errcode == EBUR128_SUCCESS) { return interp; } free_filter_z: for (j = 0; j < interp->channels; j++) { free(interp->z[j]); } free(interp->z); free_filter_index_coeff: for (j = 0; j < interp->factor; j++) { free(interp->filter[j].index); free(interp->filter[j].coeff); } free(interp->filter); free_interp: free(interp); exit: return NULL; } static void interp_destroy(interpolator* interp) { unsigned int j = 0; if (!interp) { return; } for (j = 0; j < interp->factor; j++) { free(interp->filter[j].index); free(interp->filter[j].coeff); } free(interp->filter); for (j = 0; j < interp->channels; j++) { free(interp->z[j]); } free(interp->z); free(interp); } static size_t interp_process(interpolator* interp, size_t frames, float* in, float* out) { size_t frame = 0; unsigned int chan = 0; unsigned int f = 0; unsigned int t = 0; unsigned int out_stride = interp->channels * interp->factor; float* outp = 0; double acc = 0; double c = 0; for (frame = 0; frame < frames; frame++) { for (chan = 0; chan < interp->channels; chan++) { /* Add sample to delay buffer */ interp->z[chan][interp->zi] = *in++; /* Apply coefficients */ outp = out + chan; for (f = 0; f < interp->factor; f++) { acc = 0.0; for (t = 0; t < interp->filter[f].count; t++) { int i = (int) interp->zi - (int) interp->filter[f].index[t]; if (i < 0) { i += (int) interp->delay; } c = interp->filter[f].coeff[t]; acc += (double) interp->z[chan][i] * c; } *outp = (float) acc; outp += interp->channels; } } out += out_stride; interp->zi++; if (interp->zi == interp->delay) { interp->zi = 0; } } return frames * interp->factor; } static int ebur128_init_filter(ebur128_state* st) { int errcode = EBUR128_SUCCESS; int i, j; double f0 = 1681.974450955533; double G = 3.999843853973347; double Q = 0.7071752369554196; double K = tan(M_PI * f0 / (double) st->samplerate); double Vh = pow(10.0, G / 20.0); double Vb = pow(Vh, 0.4996667741545416); double pb[3] = { 0.0, 0.0, 0.0 }; double pa[3] = { 1.0, 0.0, 0.0 }; double rb[3] = { 1.0, -2.0, 1.0 }; double ra[3] = { 1.0, 0.0, 0.0 }; double a0 = 1.0 + K / Q + K * K; pb[0] = (Vh + Vb * K / Q + K * K) / a0; pb[1] = 2.0 * (K * K - Vh) / a0; pb[2] = (Vh - Vb * K / Q + K * K) / a0; pa[1] = 2.0 * (K * K - 1.0) / a0; pa[2] = (1.0 - K / Q + K * K) / a0; /* fprintf(stderr, "%.14f %.14f %.14f %.14f %.14f\n", b1[0], b1[1], b1[2], a1[1], a1[2]); */ f0 = 38.13547087602444; Q = 0.5003270373238773; K = tan(M_PI * f0 / (double) st->samplerate); ra[1] = 2.0 * (K * K - 1.0) / (1.0 + K / Q + K * K); ra[2] = (1.0 - K / Q + K * K) / (1.0 + K / Q + K * K); /* fprintf(stderr, "%.14f %.14f\n", a2[1], a2[2]); */ st->d->b[0] = pb[0] * rb[0]; st->d->b[1] = pb[0] * rb[1] + pb[1] * rb[0]; st->d->b[2] = pb[0] * rb[2] + pb[1] * rb[1] + pb[2] * rb[0]; st->d->b[3] = pb[1] * rb[2] + pb[2] * rb[1]; st->d->b[4] = pb[2] * rb[2]; st->d->a[0] = pa[0] * ra[0]; st->d->a[1] = pa[0] * ra[1] + pa[1] * ra[0]; st->d->a[2] = pa[0] * ra[2] + pa[1] * ra[1] + pa[2] * ra[0]; st->d->a[3] = pa[1] * ra[2] + pa[2] * ra[1]; st->d->a[4] = pa[2] * ra[2]; st->d->v = (filter_state*) malloc(st->channels * sizeof(filter_state)); CHECK_ERROR(!st->d->v, EBUR128_ERROR_NOMEM, exit); for (i = 0; i < (int) st->channels; ++i) { for (j = 0; j < FILTER_STATE_SIZE; ++j) { st->d->v[i][j] = 0.0; } } exit: return errcode; } static int ebur128_init_channel_map(ebur128_state* st) { size_t i; st->d->channel_map = (int*) malloc(st->channels * sizeof(int)); if (!st->d->channel_map) { return EBUR128_ERROR_NOMEM; } if (st->channels == 4) { st->d->channel_map[0] = EBUR128_LEFT; st->d->channel_map[1] = EBUR128_RIGHT; st->d->channel_map[2] = EBUR128_LEFT_SURROUND; st->d->channel_map[3] = EBUR128_RIGHT_SURROUND; } else if (st->channels == 5) { st->d->channel_map[0] = EBUR128_LEFT; st->d->channel_map[1] = EBUR128_RIGHT; st->d->channel_map[2] = EBUR128_CENTER; st->d->channel_map[3] = EBUR128_LEFT_SURROUND; st->d->channel_map[4] = EBUR128_RIGHT_SURROUND; } else { for (i = 0; i < st->channels; ++i) { switch (i) { case 0: st->d->channel_map[i] = EBUR128_LEFT; break; case 1: st->d->channel_map[i] = EBUR128_RIGHT; break; case 2: st->d->channel_map[i] = EBUR128_CENTER; break; case 3: st->d->channel_map[i] = EBUR128_UNUSED; break; case 4: st->d->channel_map[i] = EBUR128_LEFT_SURROUND; break; case 5: st->d->channel_map[i] = EBUR128_RIGHT_SURROUND; break; default: st->d->channel_map[i] = EBUR128_UNUSED; break; } } } return EBUR128_SUCCESS; } static int ebur128_init_resampler(ebur128_state* st) { int errcode = EBUR128_SUCCESS; if (st->samplerate < 96000) { st->d->interp = interp_create(49, 4, st->channels); CHECK_ERROR(!st->d->interp, EBUR128_ERROR_NOMEM, exit) } else if (st->samplerate < 192000) { st->d->interp = interp_create(49, 2, st->channels); CHECK_ERROR(!st->d->interp, EBUR128_ERROR_NOMEM, exit) } else { st->d->resampler_buffer_input = NULL; st->d->resampler_buffer_output = NULL; st->d->interp = NULL; goto exit; } st->d->resampler_buffer_input_frames = st->d->samples_in_100ms * 4; st->d->resampler_buffer_input = (float*) malloc( st->d->resampler_buffer_input_frames * st->channels * sizeof(float)); CHECK_ERROR(!st->d->resampler_buffer_input, EBUR128_ERROR_NOMEM, free_interp) st->d->resampler_buffer_output_frames = st->d->resampler_buffer_input_frames * st->d->interp->factor; st->d->resampler_buffer_output = (float*) malloc( st->d->resampler_buffer_output_frames * st->channels * sizeof(float)); CHECK_ERROR(!st->d->resampler_buffer_output, EBUR128_ERROR_NOMEM, free_input) return errcode; free_interp: interp_destroy(st->d->interp); st->d->interp = NULL; free_input: free(st->d->resampler_buffer_input); st->d->resampler_buffer_input = NULL; exit: return errcode; } static void ebur128_destroy_resampler(ebur128_state* st) { free(st->d->resampler_buffer_input); st->d->resampler_buffer_input = NULL; free(st->d->resampler_buffer_output); st->d->resampler_buffer_output = NULL; interp_destroy(st->d->interp); st->d->interp = NULL; } void ebur128_get_version(int* major, int* minor, int* patch) { *major = EBUR128_VERSION_MAJOR; *minor = EBUR128_VERSION_MINOR; *patch = EBUR128_VERSION_PATCH; } #define VALIDATE_MAX_CHANNELS (64) #define VALIDATE_MAX_SAMPLERATE (2822400) #define VALIDATE_CHANNELS_AND_SAMPLERATE(err) \ do { \ if (channels == 0 || channels > VALIDATE_MAX_CHANNELS) { \ return (err); \ } \ \ if (samplerate < 16 || samplerate > VALIDATE_MAX_SAMPLERATE) { \ return (err); \ } \ } while (0); ebur128_state* ebur128_init(unsigned int channels, unsigned long samplerate, int mode) { int result; int errcode; ebur128_state* st; unsigned int i; size_t j; VALIDATE_CHANNELS_AND_SAMPLERATE(NULL); st = (ebur128_state*) malloc(sizeof(ebur128_state)); CHECK_ERROR(!st, 0, exit) st->d = (struct ebur128_state_internal*) malloc( sizeof(struct ebur128_state_internal)); CHECK_ERROR(!st->d, 0, free_state) st->channels = channels; errcode = ebur128_init_channel_map(st); CHECK_ERROR(errcode, 0, free_internal) st->d->sample_peak = (double*) malloc(channels * sizeof(double)); CHECK_ERROR(!st->d->sample_peak, 0, free_channel_map) st->d->prev_sample_peak = (double*) malloc(channels * sizeof(double)); CHECK_ERROR(!st->d->prev_sample_peak, 0, free_sample_peak) st->d->true_peak = (double*) malloc(channels * sizeof(double)); CHECK_ERROR(!st->d->true_peak, 0, free_prev_sample_peak) st->d->prev_true_peak = (double*) malloc(channels * sizeof(double)); CHECK_ERROR(!st->d->prev_true_peak, 0, free_true_peak) for (i = 0; i < channels; ++i) { st->d->sample_peak[i] = 0.0; st->d->prev_sample_peak[i] = 0.0; st->d->true_peak[i] = 0.0; st->d->prev_true_peak[i] = 0.0; } st->d->use_histogram = mode & EBUR128_MODE_HISTOGRAM ? 1 : 0; st->d->history = ULONG_MAX; st->samplerate = samplerate; st->d->samples_in_100ms = (st->samplerate + 5) / 10; st->mode = mode; if ((mode & EBUR128_MODE_S) == EBUR128_MODE_S) { st->d->window = 3000; } else if ((mode & EBUR128_MODE_M) == EBUR128_MODE_M) { st->d->window = 400; } else { goto free_prev_true_peak; } st->d->audio_data_frames = st->samplerate * st->d->window / 1000; if (st->d->audio_data_frames % st->d->samples_in_100ms) { /* round up to multiple of samples_in_100ms */ st->d->audio_data_frames = (st->d->audio_data_frames + st->d->samples_in_100ms) - (st->d->audio_data_frames % st->d->samples_in_100ms); } st->d->audio_data = (double*) malloc(st->d->audio_data_frames * st->channels * sizeof(double)); CHECK_ERROR(!st->d->audio_data, 0, free_prev_true_peak) for (j = 0; j < st->d->audio_data_frames * st->channels; ++j) { st->d->audio_data[j] = 0.0; } errcode = ebur128_init_filter(st); CHECK_ERROR(errcode, 0, free_audio_data) if (st->d->use_histogram) { st->d->block_energy_histogram = (unsigned long*) malloc(1000 * sizeof(unsigned long)); CHECK_ERROR(!st->d->block_energy_histogram, 0, free_filter) for (i = 0; i < 1000; ++i) { st->d->block_energy_histogram[i] = 0; } } else { st->d->block_energy_histogram = NULL; } if (st->d->use_histogram) { st->d->short_term_block_energy_histogram = (unsigned long*) malloc(1000 * sizeof(unsigned long)); CHECK_ERROR(!st->d->short_term_block_energy_histogram, 0, free_block_energy_histogram) for (i = 0; i < 1000; ++i) { st->d->short_term_block_energy_histogram[i] = 0; } } else { st->d->short_term_block_energy_histogram = NULL; } STAILQ_INIT(&st->d->block_list); st->d->block_list_size = 0; st->d->block_list_max = st->d->history / 100; STAILQ_INIT(&st->d->short_term_block_list); st->d->st_block_list_size = 0; st->d->st_block_list_max = st->d->history / 3000; st->d->short_term_frame_counter = 0; result = ebur128_init_resampler(st); CHECK_ERROR(result, 0, free_short_term_block_energy_histogram) /* the first block needs 400ms of audio data */ st->d->needed_frames = st->d->samples_in_100ms * 4; /* start at the beginning of the buffer */ st->d->audio_data_index = 0; /* initialize static constants */ relative_gate_factor = pow(10.0, relative_gate / 10.0); minus_twenty_decibels = pow(10.0, -20.0 / 10.0); histogram_energy_boundaries[0] = pow(10.0, (-70.0 + 0.691) / 10.0); if (st->d->use_histogram) { for (i = 0; i < 1000; ++i) { histogram_energies[i] = pow(10.0, ((double) i / 10.0 - 69.95 + 0.691) / 10.0); } for (i = 1; i < 1001; ++i) { histogram_energy_boundaries[i] = pow(10.0, ((double) i / 10.0 - 70.0 + 0.691) / 10.0); } } return st; free_short_term_block_energy_histogram: free(st->d->short_term_block_energy_histogram); free_block_energy_histogram: free(st->d->block_energy_histogram); free_filter: free(st->d->v); free_audio_data: free(st->d->audio_data); free_prev_true_peak: free(st->d->prev_true_peak); free_true_peak: free(st->d->true_peak); free_prev_sample_peak: free(st->d->prev_sample_peak); free_sample_peak: free(st->d->sample_peak); free_channel_map: free(st->d->channel_map); free_internal: free(st->d); free_state: free(st); exit: return NULL; } void ebur128_destroy(ebur128_state** st) { struct ebur128_dq_entry* entry; free((*st)->d->short_term_block_energy_histogram); free((*st)->d->block_energy_histogram); free((*st)->d->v); free((*st)->d->audio_data); free((*st)->d->channel_map); free((*st)->d->sample_peak); free((*st)->d->prev_sample_peak); free((*st)->d->true_peak); free((*st)->d->prev_true_peak); while (!STAILQ_EMPTY(&(*st)->d->block_list)) { entry = STAILQ_FIRST(&(*st)->d->block_list); STAILQ_REMOVE_HEAD(&(*st)->d->block_list, entries); free(entry); } while (!STAILQ_EMPTY(&(*st)->d->short_term_block_list)) { entry = STAILQ_FIRST(&(*st)->d->short_term_block_list); STAILQ_REMOVE_HEAD(&(*st)->d->short_term_block_list, entries); free(entry); } ebur128_destroy_resampler(*st); free((*st)->d); free(*st); *st = NULL; } static void ebur128_check_true_peak(ebur128_state* st, size_t frames) { size_t c, i, frames_out; frames_out = interp_process(st->d->interp, frames, st->d->resampler_buffer_input, st->d->resampler_buffer_output); for (i = 0; i < frames_out; ++i) { for (c = 0; c < st->channels; ++c) { double val = (double) st->d->resampler_buffer_output[i * st->channels + c]; if (EBUR128_MAX(val, -val) > st->d->prev_true_peak[c]) { st->d->prev_true_peak[c] = EBUR128_MAX(val, -val); } } } } #if defined(__SSE2_MATH__) || defined(_M_X64) || _M_IX86_FP >= 2 #include #define TURN_ON_FTZ \ unsigned int mxcsr = _mm_getcsr(); \ _mm_setcsr(mxcsr | _MM_FLUSH_ZERO_ON); #define TURN_OFF_FTZ _mm_setcsr(mxcsr); #define FLUSH_MANUALLY #else #warning "manual FTZ is being used, please enable SSE2 (-msse2 -mfpmath=sse)" #define TURN_ON_FTZ #define TURN_OFF_FTZ #define FLUSH_MANUALLY \ st->d->v[c][4] = fabs(st->d->v[c][4]) < DBL_MIN ? 0.0 : st->d->v[c][4]; \ st->d->v[c][3] = fabs(st->d->v[c][3]) < DBL_MIN ? 0.0 : st->d->v[c][3]; \ st->d->v[c][2] = fabs(st->d->v[c][2]) < DBL_MIN ? 0.0 : st->d->v[c][2]; \ st->d->v[c][1] = fabs(st->d->v[c][1]) < DBL_MIN ? 0.0 : st->d->v[c][1]; #endif #define EBUR128_FILTER(type, min_scale, max_scale) \ static void ebur128_filter_##type(ebur128_state* st, const type* src, \ size_t frames) { \ static double scaling_factor = \ EBUR128_MAX(-((double) (min_scale)), (double) (max_scale)); \ \ double* audio_data = st->d->audio_data + st->d->audio_data_index; \ size_t i, c; \ \ TURN_ON_FTZ \ \ if ((st->mode & EBUR128_MODE_SAMPLE_PEAK) == EBUR128_MODE_SAMPLE_PEAK) { \ for (c = 0; c < st->channels; ++c) { \ double max = 0.0; \ for (i = 0; i < frames; ++i) { \ double cur = (double) src[i * st->channels + c]; \ if (EBUR128_MAX(cur, -cur) > max) { \ max = EBUR128_MAX(cur, -cur); \ } \ } \ max /= scaling_factor; \ if (max > st->d->prev_sample_peak[c]) { \ st->d->prev_sample_peak[c] = max; \ } \ } \ } \ if ((st->mode & EBUR128_MODE_TRUE_PEAK) == EBUR128_MODE_TRUE_PEAK && \ st->d->interp) { \ for (i = 0; i < frames; ++i) { \ for (c = 0; c < st->channels; ++c) { \ st->d->resampler_buffer_input[i * st->channels + c] = \ (float) ((double) src[i * st->channels + c] / scaling_factor); \ } \ } \ ebur128_check_true_peak(st, frames); \ } \ for (c = 0; c < st->channels; ++c) { \ if (st->d->channel_map[c] == EBUR128_UNUSED) { \ continue; \ } \ for (i = 0; i < frames; ++i) { \ st->d->v[c][0] = \ (double) ((double) src[i * st->channels + c] / scaling_factor) - \ st->d->a[1] * st->d->v[c][1] - /**/ \ st->d->a[2] * st->d->v[c][2] - /**/ \ st->d->a[3] * st->d->v[c][3] - /**/ \ st->d->a[4] * st->d->v[c][4]; \ audio_data[i * st->channels + c] = /**/ \ st->d->b[0] * st->d->v[c][0] + /**/ \ st->d->b[1] * st->d->v[c][1] + /**/ \ st->d->b[2] * st->d->v[c][2] + /**/ \ st->d->b[3] * st->d->v[c][3] + /**/ \ st->d->b[4] * st->d->v[c][4]; \ st->d->v[c][4] = st->d->v[c][3]; \ st->d->v[c][3] = st->d->v[c][2]; \ st->d->v[c][2] = st->d->v[c][1]; \ st->d->v[c][1] = st->d->v[c][0]; \ } \ FLUSH_MANUALLY \ } \ TURN_OFF_FTZ \ } EBUR128_FILTER(short, SHRT_MIN, SHRT_MAX) EBUR128_FILTER(int, INT_MIN, INT_MAX) EBUR128_FILTER(float, -1.0f, 1.0f) EBUR128_FILTER(double, -1.0, 1.0) static double ebur128_energy_to_loudness(double energy) { return 10 * (log(energy) / log(10.0)) - 0.691; } static size_t find_histogram_index(double energy) { size_t index_min = 0; size_t index_max = 1000; size_t index_mid; do { index_mid = (index_min + index_max) / 2; if (energy >= histogram_energy_boundaries[index_mid]) { index_min = index_mid; } else { index_max = index_mid; } } while (index_max - index_min != 1); return index_min; } static int ebur128_calc_gating_block(ebur128_state* st, size_t frames_per_block, double* optional_output) { size_t i, c; double sum = 0.0; double channel_sum; for (c = 0; c < st->channels; ++c) { if (st->d->channel_map[c] == EBUR128_UNUSED) { continue; } channel_sum = 0.0; if (st->d->audio_data_index < frames_per_block * st->channels) { for (i = 0; i < st->d->audio_data_index / st->channels; ++i) { channel_sum += st->d->audio_data[i * st->channels + c] * st->d->audio_data[i * st->channels + c]; } for (i = st->d->audio_data_frames - (frames_per_block - st->d->audio_data_index / st->channels); i < st->d->audio_data_frames; ++i) { channel_sum += st->d->audio_data[i * st->channels + c] * st->d->audio_data[i * st->channels + c]; } } else { for (i = st->d->audio_data_index / st->channels - frames_per_block; i < st->d->audio_data_index / st->channels; ++i) { channel_sum += st->d->audio_data[i * st->channels + c] * st->d->audio_data[i * st->channels + c]; } } if (st->d->channel_map[c] == EBUR128_Mp110 || st->d->channel_map[c] == EBUR128_Mm110 || st->d->channel_map[c] == EBUR128_Mp060 || st->d->channel_map[c] == EBUR128_Mm060 || st->d->channel_map[c] == EBUR128_Mp090 || st->d->channel_map[c] == EBUR128_Mm090) { channel_sum *= 1.41; } else if (st->d->channel_map[c] == EBUR128_DUAL_MONO) { channel_sum *= 2.0; } sum += channel_sum; } sum /= (double) frames_per_block; if (optional_output) { *optional_output = sum; return EBUR128_SUCCESS; } if (sum >= histogram_energy_boundaries[0]) { if (st->d->use_histogram) { ++st->d->block_energy_histogram[find_histogram_index(sum)]; } else { struct ebur128_dq_entry* block; if (st->d->block_list_size == st->d->block_list_max) { block = STAILQ_FIRST(&st->d->block_list); STAILQ_REMOVE_HEAD(&st->d->block_list, entries); } else { block = (struct ebur128_dq_entry*) malloc(sizeof(struct ebur128_dq_entry)); if (!block) { return EBUR128_ERROR_NOMEM; } st->d->block_list_size++; } block->z = sum; STAILQ_INSERT_TAIL(&st->d->block_list, block, entries); } } return EBUR128_SUCCESS; } int ebur128_set_channel(ebur128_state* st, unsigned int channel_number, int value) { if (channel_number >= st->channels) { return EBUR128_ERROR_INVALID_CHANNEL_INDEX; } if (value == EBUR128_DUAL_MONO && (st->channels != 1 || channel_number != 0)) { fprintf(stderr, "EBUR128_DUAL_MONO only works with mono files!\n"); return EBUR128_ERROR_INVALID_CHANNEL_INDEX; } st->d->channel_map[channel_number] = value; return EBUR128_SUCCESS; } int ebur128_change_parameters(ebur128_state* st, unsigned int channels, unsigned long samplerate) { int errcode = EBUR128_SUCCESS; size_t j; /* This is needed to suppress a clang-tidy warning. */ #ifndef __has_builtin #define __has_builtin(x) 0 #endif #if __has_builtin(__builtin_unreachable) if (st->channels == 0) { __builtin_unreachable(); } #endif VALIDATE_CHANNELS_AND_SAMPLERATE(EBUR128_ERROR_NOMEM); if (channels == st->channels && samplerate == st->samplerate) { return EBUR128_ERROR_NO_CHANGE; } free(st->d->audio_data); st->d->audio_data = NULL; if (channels != st->channels) { unsigned int i; free(st->d->channel_map); st->d->channel_map = NULL; free(st->d->sample_peak); st->d->sample_peak = NULL; free(st->d->prev_sample_peak); st->d->prev_sample_peak = NULL; free(st->d->true_peak); st->d->true_peak = NULL; free(st->d->prev_true_peak); st->d->prev_true_peak = NULL; st->channels = channels; errcode = ebur128_init_channel_map(st); CHECK_ERROR(errcode, EBUR128_ERROR_NOMEM, exit) st->d->sample_peak = (double*) malloc(channels * sizeof(double)); CHECK_ERROR(!st->d->sample_peak, EBUR128_ERROR_NOMEM, exit) st->d->prev_sample_peak = (double*) malloc(channels * sizeof(double)); CHECK_ERROR(!st->d->prev_sample_peak, EBUR128_ERROR_NOMEM, exit) st->d->true_peak = (double*) malloc(channels * sizeof(double)); CHECK_ERROR(!st->d->true_peak, EBUR128_ERROR_NOMEM, exit) st->d->prev_true_peak = (double*) malloc(channels * sizeof(double)); CHECK_ERROR(!st->d->prev_true_peak, EBUR128_ERROR_NOMEM, exit) for (i = 0; i < channels; ++i) { st->d->sample_peak[i] = 0.0; st->d->prev_sample_peak[i] = 0.0; st->d->true_peak[i] = 0.0; st->d->prev_true_peak[i] = 0.0; } } if (samplerate != st->samplerate) { st->samplerate = samplerate; st->d->samples_in_100ms = (st->samplerate + 5) / 10; } /* If we're here, either samplerate or channels * have changed. Re-init filter. */ free(st->d->v); st->d->v = NULL; errcode = ebur128_init_filter(st); CHECK_ERROR(errcode, EBUR128_ERROR_NOMEM, exit) st->d->audio_data_frames = st->samplerate * st->d->window / 1000; if (st->d->audio_data_frames % st->d->samples_in_100ms) { /* round up to multiple of samples_in_100ms */ st->d->audio_data_frames = (st->d->audio_data_frames + st->d->samples_in_100ms) - (st->d->audio_data_frames % st->d->samples_in_100ms); } st->d->audio_data = (double*) malloc(st->d->audio_data_frames * st->channels * sizeof(double)); CHECK_ERROR(!st->d->audio_data, EBUR128_ERROR_NOMEM, exit) for (j = 0; j < st->d->audio_data_frames * st->channels; ++j) { st->d->audio_data[j] = 0.0; } ebur128_destroy_resampler(st); errcode = ebur128_init_resampler(st); CHECK_ERROR(errcode, EBUR128_ERROR_NOMEM, exit) /* the first block needs 400ms of audio data */ st->d->needed_frames = st->d->samples_in_100ms * 4; /* start at the beginning of the buffer */ st->d->audio_data_index = 0; /* reset short term frame counter */ st->d->short_term_frame_counter = 0; exit: return errcode; } int ebur128_set_max_window(ebur128_state* st, unsigned long window) { int errcode = EBUR128_SUCCESS; size_t j; if ((st->mode & EBUR128_MODE_S) == EBUR128_MODE_S && window < 3000) { window = 3000; } else if ((st->mode & EBUR128_MODE_M) == EBUR128_MODE_M && window < 400) { window = 400; } if (window == st->d->window) { return EBUR128_ERROR_NO_CHANGE; } size_t new_audio_data_frames; if (safe_size_mul(st->samplerate, window, &new_audio_data_frames) != 0 || new_audio_data_frames > ((size_t) -1) - st->d->samples_in_100ms) { return EBUR128_ERROR_NOMEM; } if (new_audio_data_frames % st->d->samples_in_100ms) { /* round up to multiple of samples_in_100ms */ new_audio_data_frames = (new_audio_data_frames + st->d->samples_in_100ms) - (new_audio_data_frames % st->d->samples_in_100ms); } size_t new_audio_data_size; if (safe_size_mul(new_audio_data_frames, st->channels * sizeof(double), &new_audio_data_size) != 0) { return EBUR128_ERROR_NOMEM; } double* new_audio_data = (double*) malloc(new_audio_data_size); CHECK_ERROR(!new_audio_data, EBUR128_ERROR_NOMEM, exit) st->d->window = window; free(st->d->audio_data); st->d->audio_data = new_audio_data; st->d->audio_data_frames = new_audio_data_frames; for (j = 0; j < st->d->audio_data_frames * st->channels; ++j) { st->d->audio_data[j] = 0.0; } /* the first block needs 400ms of audio data */ st->d->needed_frames = st->d->samples_in_100ms * 4; /* start at the beginning of the buffer */ st->d->audio_data_index = 0; /* reset short term frame counter */ st->d->short_term_frame_counter = 0; exit: return errcode; } int ebur128_set_max_history(ebur128_state* st, unsigned long history) { if ((st->mode & EBUR128_MODE_LRA) == EBUR128_MODE_LRA && history < 3000) { history = 3000; } else if ((st->mode & EBUR128_MODE_M) == EBUR128_MODE_M && history < 400) { history = 400; } if (history == st->d->history) { return EBUR128_ERROR_NO_CHANGE; } st->d->history = history; st->d->block_list_max = st->d->history / 100; st->d->st_block_list_max = st->d->history / 3000; while (st->d->block_list_size > st->d->block_list_max) { struct ebur128_dq_entry* block = STAILQ_FIRST(&st->d->block_list); STAILQ_REMOVE_HEAD(&st->d->block_list, entries); free(block); st->d->block_list_size--; } while (st->d->st_block_list_size > st->d->st_block_list_max) { struct ebur128_dq_entry* block = STAILQ_FIRST(&st->d->short_term_block_list); STAILQ_REMOVE_HEAD(&st->d->short_term_block_list, entries); free(block); st->d->st_block_list_size--; } return EBUR128_SUCCESS; } static int ebur128_energy_shortterm(ebur128_state* st, double* out); #define EBUR128_ADD_FRAMES(type) \ int ebur128_add_frames_##type(ebur128_state* st, const type* src, \ size_t frames) { \ size_t src_index = 0; \ unsigned int c = 0; \ for (c = 0; c < st->channels; c++) { \ st->d->prev_sample_peak[c] = 0.0; \ st->d->prev_true_peak[c] = 0.0; \ } \ while (frames > 0) { \ if (frames >= st->d->needed_frames) { \ ebur128_filter_##type(st, src + src_index, st->d->needed_frames); \ src_index += st->d->needed_frames * st->channels; \ frames -= st->d->needed_frames; \ st->d->audio_data_index += st->d->needed_frames * st->channels; \ /* calculate the new gating block */ \ if ((st->mode & EBUR128_MODE_I) == EBUR128_MODE_I) { \ if (ebur128_calc_gating_block(st, st->d->samples_in_100ms * 4, \ NULL)) { \ return EBUR128_ERROR_NOMEM; \ } \ } \ if ((st->mode & EBUR128_MODE_LRA) == EBUR128_MODE_LRA) { \ st->d->short_term_frame_counter += st->d->needed_frames; \ if (st->d->short_term_frame_counter == \ st->d->samples_in_100ms * 30) { \ struct ebur128_dq_entry* block; \ double st_energy; \ if (ebur128_energy_shortterm(st, &st_energy) == EBUR128_SUCCESS && \ st_energy >= histogram_energy_boundaries[0]) { \ if (st->d->use_histogram) { \ ++st->d->short_term_block_energy_histogram \ [find_histogram_index(st_energy)]; \ } else { \ if (st->d->st_block_list_size == st->d->st_block_list_max) { \ block = STAILQ_FIRST(&st->d->short_term_block_list); \ STAILQ_REMOVE_HEAD(&st->d->short_term_block_list, entries); \ } else { \ block = (struct ebur128_dq_entry*) malloc( \ sizeof(struct ebur128_dq_entry)); \ if (!block) { \ return EBUR128_ERROR_NOMEM; \ } \ st->d->st_block_list_size++; \ } \ block->z = st_energy; \ STAILQ_INSERT_TAIL(&st->d->short_term_block_list, block, \ entries); \ } \ } \ st->d->short_term_frame_counter = st->d->samples_in_100ms * 20; \ } \ } \ /* 100ms are needed for all blocks besides the first one */ \ st->d->needed_frames = st->d->samples_in_100ms; \ /* reset audio_data_index when buffer full */ \ if (st->d->audio_data_index == \ st->d->audio_data_frames * st->channels) { \ st->d->audio_data_index = 0; \ } \ } else { \ ebur128_filter_##type(st, src + src_index, frames); \ st->d->audio_data_index += frames * st->channels; \ if ((st->mode & EBUR128_MODE_LRA) == EBUR128_MODE_LRA) { \ st->d->short_term_frame_counter += frames; \ } \ st->d->needed_frames -= (unsigned long) frames; \ frames = 0; \ } \ } \ for (c = 0; c < st->channels; c++) { \ if (st->d->prev_sample_peak[c] > st->d->sample_peak[c]) { \ st->d->sample_peak[c] = st->d->prev_sample_peak[c]; \ } \ if (st->d->prev_true_peak[c] > st->d->true_peak[c]) { \ st->d->true_peak[c] = st->d->prev_true_peak[c]; \ } \ } \ return EBUR128_SUCCESS; \ } EBUR128_ADD_FRAMES(short) EBUR128_ADD_FRAMES(int) EBUR128_ADD_FRAMES(float) EBUR128_ADD_FRAMES(double) static int ebur128_calc_relative_threshold(ebur128_state* st, size_t* above_thresh_counter, double* relative_threshold) { struct ebur128_dq_entry* it; size_t i; if (st->d->use_histogram) { for (i = 0; i < 1000; ++i) { *relative_threshold += st->d->block_energy_histogram[i] * histogram_energies[i]; *above_thresh_counter += st->d->block_energy_histogram[i]; } } else { STAILQ_FOREACH(it, &st->d->block_list, entries) { ++*above_thresh_counter; *relative_threshold += it->z; } } return EBUR128_SUCCESS; } static int ebur128_gated_loudness(ebur128_state** sts, size_t size, double* out) { struct ebur128_dq_entry* it; double gated_loudness = 0.0; double relative_threshold = 0.0; size_t above_thresh_counter = 0; size_t i, j, start_index; for (i = 0; i < size; i++) { if (sts[i] && (sts[i]->mode & EBUR128_MODE_I) != EBUR128_MODE_I) { return EBUR128_ERROR_INVALID_MODE; } } for (i = 0; i < size; i++) { if (!sts[i]) { continue; } ebur128_calc_relative_threshold(sts[i], &above_thresh_counter, &relative_threshold); } if (!above_thresh_counter) { *out = -HUGE_VAL; return EBUR128_SUCCESS; } relative_threshold /= (double) above_thresh_counter; relative_threshold *= relative_gate_factor; above_thresh_counter = 0; if (relative_threshold < histogram_energy_boundaries[0]) { start_index = 0; } else { start_index = find_histogram_index(relative_threshold); if (relative_threshold > histogram_energies[start_index]) { ++start_index; } } for (i = 0; i < size; i++) { if (!sts[i]) { continue; } if (sts[i]->d->use_histogram) { for (j = start_index; j < 1000; ++j) { gated_loudness += sts[i]->d->block_energy_histogram[j] * histogram_energies[j]; above_thresh_counter += sts[i]->d->block_energy_histogram[j]; } } else { STAILQ_FOREACH(it, &sts[i]->d->block_list, entries) { if (it->z >= relative_threshold) { ++above_thresh_counter; gated_loudness += it->z; } } } } if (!above_thresh_counter) { *out = -HUGE_VAL; return EBUR128_SUCCESS; } gated_loudness /= (double) above_thresh_counter; *out = ebur128_energy_to_loudness(gated_loudness); return EBUR128_SUCCESS; } int ebur128_relative_threshold(ebur128_state* st, double* out) { double relative_threshold = 0.0; size_t above_thresh_counter = 0; if ((st->mode & EBUR128_MODE_I) != EBUR128_MODE_I) { return EBUR128_ERROR_INVALID_MODE; } ebur128_calc_relative_threshold(st, &above_thresh_counter, &relative_threshold); if (!above_thresh_counter) { *out = -70.0; return EBUR128_SUCCESS; } relative_threshold /= (double) above_thresh_counter; relative_threshold *= relative_gate_factor; *out = ebur128_energy_to_loudness(relative_threshold); return EBUR128_SUCCESS; } int ebur128_loudness_global(ebur128_state* st, double* out) { return ebur128_gated_loudness(&st, 1, out); } int ebur128_loudness_global_multiple(ebur128_state** sts, size_t size, double* out) { return ebur128_gated_loudness(sts, size, out); } static int ebur128_energy_in_interval(ebur128_state* st, size_t interval_frames, double* out) { if (interval_frames > st->d->audio_data_frames) { return EBUR128_ERROR_INVALID_MODE; } ebur128_calc_gating_block(st, interval_frames, out); return EBUR128_SUCCESS; } static int ebur128_energy_shortterm(ebur128_state* st, double* out) { return ebur128_energy_in_interval(st, st->d->samples_in_100ms * 30, out); } int ebur128_loudness_momentary(ebur128_state* st, double* out) { double energy; int error; error = ebur128_energy_in_interval(st, st->d->samples_in_100ms * 4, &energy); if (error) { return error; } if (energy <= 0.0) { *out = -HUGE_VAL; return EBUR128_SUCCESS; } *out = ebur128_energy_to_loudness(energy); return EBUR128_SUCCESS; } int ebur128_loudness_shortterm(ebur128_state* st, double* out) { double energy; int error; error = ebur128_energy_shortterm(st, &energy); if (error) { return error; } if (energy <= 0.0) { *out = -HUGE_VAL; return EBUR128_SUCCESS; } *out = ebur128_energy_to_loudness(energy); return EBUR128_SUCCESS; } int ebur128_loudness_window(ebur128_state* st, unsigned long window, double* out) { double energy; size_t interval_frames; int error; if (window > st->d->window) { return EBUR128_ERROR_INVALID_MODE; } interval_frames = st->samplerate * window / 1000; error = ebur128_energy_in_interval(st, interval_frames, &energy); if (error) { return error; } if (energy <= 0.0) { *out = -HUGE_VAL; return EBUR128_SUCCESS; } *out = ebur128_energy_to_loudness(energy); return EBUR128_SUCCESS; } static int ebur128_double_cmp(const void* p1, const void* p2) { const double* d1 = (const double*) p1; const double* d2 = (const double*) p2; return (*d1 > *d2) - (*d1 < *d2); } /* EBU - TECH 3342 */ int ebur128_loudness_range_multiple(ebur128_state** sts, size_t size, double* out) { size_t i, j; struct ebur128_dq_entry* it; double* stl_vector; size_t stl_size; double* stl_relgated; size_t stl_relgated_size; double stl_power, stl_integrated; /* High and low percentile energy */ double h_en, l_en; int use_histogram = 0; for (i = 0; i < size; ++i) { if (sts[i]) { if ((sts[i]->mode & EBUR128_MODE_LRA) != EBUR128_MODE_LRA) { return EBUR128_ERROR_INVALID_MODE; } if (i == 0 && sts[i]->mode & EBUR128_MODE_HISTOGRAM) { use_histogram = 1; } else if (use_histogram != !!(sts[i]->mode & EBUR128_MODE_HISTOGRAM)) { return EBUR128_ERROR_INVALID_MODE; } } } if (use_histogram) { unsigned long hist[1000] = { 0 }; size_t percentile_low, percentile_high; size_t index; stl_size = 0; stl_power = 0.0; for (i = 0; i < size; ++i) { if (!sts[i]) { continue; } for (j = 0; j < 1000; ++j) { hist[j] += sts[i]->d->short_term_block_energy_histogram[j]; stl_size += sts[i]->d->short_term_block_energy_histogram[j]; stl_power += sts[i]->d->short_term_block_energy_histogram[j] * histogram_energies[j]; } } if (!stl_size) { *out = 0.0; return EBUR128_SUCCESS; } stl_power /= stl_size; stl_integrated = minus_twenty_decibels * stl_power; if (stl_integrated < histogram_energy_boundaries[0]) { index = 0; } else { index = find_histogram_index(stl_integrated); if (stl_integrated > histogram_energies[index]) { ++index; } } stl_size = 0; for (j = index; j < 1000; ++j) { stl_size += hist[j]; } if (!stl_size) { *out = 0.0; return EBUR128_SUCCESS; } percentile_low = (size_t) ((stl_size - 1) * 0.1 + 0.5); percentile_high = (size_t) ((stl_size - 1) * 0.95 + 0.5); stl_size = 0; j = index; while (stl_size <= percentile_low) { stl_size += hist[j++]; } l_en = histogram_energies[j - 1]; while (stl_size <= percentile_high) { stl_size += hist[j++]; } h_en = histogram_energies[j - 1]; *out = ebur128_energy_to_loudness(h_en) - ebur128_energy_to_loudness(l_en); return EBUR128_SUCCESS; } stl_size = 0; for (i = 0; i < size; ++i) { if (!sts[i]) { continue; } STAILQ_FOREACH(it, &sts[i]->d->short_term_block_list, entries) { ++stl_size; } } if (!stl_size) { *out = 0.0; return EBUR128_SUCCESS; } stl_vector = (double*) malloc(stl_size * sizeof(double)); if (!stl_vector) { return EBUR128_ERROR_NOMEM; } j = 0; for (i = 0; i < size; ++i) { if (!sts[i]) { continue; } STAILQ_FOREACH(it, &sts[i]->d->short_term_block_list, entries) { stl_vector[j] = it->z; ++j; } } qsort(stl_vector, stl_size, sizeof(double), ebur128_double_cmp); stl_power = 0.0; for (i = 0; i < stl_size; ++i) { stl_power += stl_vector[i]; } stl_power /= (double) stl_size; stl_integrated = minus_twenty_decibels * stl_power; stl_relgated = stl_vector; stl_relgated_size = stl_size; while (stl_relgated_size > 0 && *stl_relgated < stl_integrated) { ++stl_relgated; --stl_relgated_size; } if (stl_relgated_size) { h_en = stl_relgated[(size_t) ((stl_relgated_size - 1) * 0.95 + 0.5)]; l_en = stl_relgated[(size_t) ((stl_relgated_size - 1) * 0.1 + 0.5)]; free(stl_vector); *out = ebur128_energy_to_loudness(h_en) - ebur128_energy_to_loudness(l_en); } else { free(stl_vector); *out = 0.0; } return EBUR128_SUCCESS; } int ebur128_loudness_range(ebur128_state* st, double* out) { return ebur128_loudness_range_multiple(&st, 1, out); } int ebur128_sample_peak(ebur128_state* st, unsigned int channel_number, double* out) { if ((st->mode & EBUR128_MODE_SAMPLE_PEAK) != EBUR128_MODE_SAMPLE_PEAK) { return EBUR128_ERROR_INVALID_MODE; } if (channel_number >= st->channels) { return EBUR128_ERROR_INVALID_CHANNEL_INDEX; } *out = st->d->sample_peak[channel_number]; return EBUR128_SUCCESS; } int ebur128_prev_sample_peak(ebur128_state* st, unsigned int channel_number, double* out) { if ((st->mode & EBUR128_MODE_SAMPLE_PEAK) != EBUR128_MODE_SAMPLE_PEAK) { return EBUR128_ERROR_INVALID_MODE; } if (channel_number >= st->channels) { return EBUR128_ERROR_INVALID_CHANNEL_INDEX; } *out = st->d->prev_sample_peak[channel_number]; return EBUR128_SUCCESS; } int ebur128_true_peak(ebur128_state* st, unsigned int channel_number, double* out) { if ((st->mode & EBUR128_MODE_TRUE_PEAK) != EBUR128_MODE_TRUE_PEAK) { return EBUR128_ERROR_INVALID_MODE; } if (channel_number >= st->channels) { return EBUR128_ERROR_INVALID_CHANNEL_INDEX; } *out = EBUR128_MAX(st->d->true_peak[channel_number], st->d->sample_peak[channel_number]); return EBUR128_SUCCESS; } int ebur128_prev_true_peak(ebur128_state* st, unsigned int channel_number, double* out) { if ((st->mode & EBUR128_MODE_TRUE_PEAK) != EBUR128_MODE_TRUE_PEAK) { return EBUR128_ERROR_INVALID_MODE; } if (channel_number >= st->channels) { return EBUR128_ERROR_INVALID_CHANNEL_INDEX; } *out = EBUR128_MAX(st->d->prev_true_peak[channel_number], st->d->prev_sample_peak[channel_number]); return EBUR128_SUCCESS; } mlt-7.22.0/src/modules/plus/ebur128/ebur128.h000664 000000 000000 00000036273 14531534050 020344 0ustar00rootroot000000 000000 /* See COPYING file for copyright and license details. */ #ifndef EBUR128_H_ #define EBUR128_H_ /** \file ebur128.h * \brief libebur128 - a library for loudness measurement according to * the EBU R128 standard. */ #ifdef __cplusplus extern "C" { #endif #define EBUR128_VERSION_MAJOR 1 #define EBUR128_VERSION_MINOR 2 #define EBUR128_VERSION_PATCH 6 #include /* for size_t */ /** \enum channel * Use these values when setting the channel map with ebur128_set_channel(). * See definitions in ITU R-REC-BS 1770-4 */ enum channel { EBUR128_UNUSED = 0, /**< unused channel (for example LFE channel) */ EBUR128_LEFT = 1, /**< */ EBUR128_Mp030 = 1, /**< itu M+030 */ EBUR128_RIGHT = 2, /**< */ EBUR128_Mm030 = 2, /**< itu M-030 */ EBUR128_CENTER = 3, /**< */ EBUR128_Mp000 = 3, /**< itu M+000 */ EBUR128_LEFT_SURROUND = 4, /**< */ EBUR128_Mp110 = 4, /**< itu M+110 */ EBUR128_RIGHT_SURROUND = 5, /**< */ EBUR128_Mm110 = 5, /**< itu M-110 */ EBUR128_DUAL_MONO, /**< a channel that is counted twice */ EBUR128_MpSC, /**< itu M+SC */ EBUR128_MmSC, /**< itu M-SC */ EBUR128_Mp060, /**< itu M+060 */ EBUR128_Mm060, /**< itu M-060 */ EBUR128_Mp090, /**< itu M+090 */ EBUR128_Mm090, /**< itu M-090 */ EBUR128_Mp135, /**< itu M+135 */ EBUR128_Mm135, /**< itu M-135 */ EBUR128_Mp180, /**< itu M+180 */ EBUR128_Up000, /**< itu U+000 */ EBUR128_Up030, /**< itu U+030 */ EBUR128_Um030, /**< itu U-030 */ EBUR128_Up045, /**< itu U+045 */ EBUR128_Um045, /**< itu U-030 */ EBUR128_Up090, /**< itu U+090 */ EBUR128_Um090, /**< itu U-090 */ EBUR128_Up110, /**< itu U+110 */ EBUR128_Um110, /**< itu U-110 */ EBUR128_Up135, /**< itu U+135 */ EBUR128_Um135, /**< itu U-135 */ EBUR128_Up180, /**< itu U+180 */ EBUR128_Tp000, /**< itu T+000 */ EBUR128_Bp000, /**< itu B+000 */ EBUR128_Bp045, /**< itu B+045 */ EBUR128_Bm045 /**< itu B-045 */ }; /** \enum error * Error return values. */ enum error { EBUR128_SUCCESS = 0, EBUR128_ERROR_NOMEM, EBUR128_ERROR_INVALID_MODE, EBUR128_ERROR_INVALID_CHANNEL_INDEX, EBUR128_ERROR_NO_CHANGE }; /** \enum mode * Use these values in ebur128_init (or'ed). Try to use the lowest possible * modes that suit your needs, as performance will be better. */ enum mode { /** can call ebur128_loudness_momentary */ EBUR128_MODE_M = (1 << 0), /** can call ebur128_loudness_shortterm */ EBUR128_MODE_S = (1 << 1) | EBUR128_MODE_M, /** can call ebur128_loudness_global_* and ebur128_relative_threshold */ EBUR128_MODE_I = (1 << 2) | EBUR128_MODE_M, /** can call ebur128_loudness_range */ EBUR128_MODE_LRA = (1 << 3) | EBUR128_MODE_S, /** can call ebur128_sample_peak */ EBUR128_MODE_SAMPLE_PEAK = (1 << 4) | EBUR128_MODE_M, /** can call ebur128_true_peak */ EBUR128_MODE_TRUE_PEAK = (1 << 5) | EBUR128_MODE_M | EBUR128_MODE_SAMPLE_PEAK, /** uses histogram algorithm to calculate loudness */ EBUR128_MODE_HISTOGRAM = (1 << 6) }; /** forward declaration of ebur128_state_internal */ struct ebur128_state_internal; /** \brief Contains information about the state of a loudness measurement. * * You should not need to modify this struct directly. */ typedef struct { int mode; /**< The current mode. */ unsigned int channels; /**< The number of channels. */ unsigned long samplerate; /**< The sample rate. */ struct ebur128_state_internal* d; /**< Internal state. */ } ebur128_state; /** \brief Get library version number. Do not pass null pointers here. * * @param major major version number of library * @param minor minor version number of library * @param patch patch version number of library */ void ebur128_get_version(int* major, int* minor, int* patch); /** \brief Initialize library state. * * @param channels the number of channels. * @param samplerate the sample rate. * @param mode see the mode enum for possible values. * @return an initialized library state, or NULL on error. */ ebur128_state* ebur128_init(unsigned int channels, unsigned long samplerate, int mode); /** \brief Destroy library state. * * @param st pointer to a library state. */ void ebur128_destroy(ebur128_state** st); /** \brief Set channel type. * * The default is: * - 0 -> EBUR128_LEFT * - 1 -> EBUR128_RIGHT * - 2 -> EBUR128_CENTER * - 3 -> EBUR128_UNUSED * - 4 -> EBUR128_LEFT_SURROUND * - 5 -> EBUR128_RIGHT_SURROUND * * @param st library state. * @param channel_number zero based channel index. * @param value channel type from the "channel" enum. * @return * - EBUR128_SUCCESS on success. * - EBUR128_ERROR_INVALID_CHANNEL_INDEX if invalid channel index. */ int ebur128_set_channel(ebur128_state* st, unsigned int channel_number, int value); /** \brief Change library parameters. * * Note that the channel map will be reset when setting a different number of * channels. The current unfinished block will be lost. * * @param st library state. * @param channels new number of channels. * @param samplerate new sample rate. * @return * - EBUR128_SUCCESS on success. * - EBUR128_ERROR_NOMEM on memory allocation error. The state will be * invalid and must be destroyed. * - EBUR128_ERROR_NO_CHANGE if channels and sample rate were not changed. */ int ebur128_change_parameters(ebur128_state* st, unsigned int channels, unsigned long samplerate); /** \brief Set the maximum window duration. * * Set the maximum duration that will be used for ebur128_loudness_window(). * Note that this destroys the current content of the audio buffer. * * @param st library state. * @param window duration of the window in ms. * @return * - EBUR128_SUCCESS on success. * - EBUR128_ERROR_NOMEM on memory allocation error. The state will be * invalid and must be destroyed. * - EBUR128_ERROR_NO_CHANGE if window duration not changed. */ int ebur128_set_max_window(ebur128_state* st, unsigned long window); /** \brief Set the maximum history. * * Set the maximum history that will be stored for loudness integration. * More history provides more accurate results, but requires more resources. * * Applies to ebur128_loudness_range() and ebur128_loudness_global() when * EBUR128_MODE_HISTOGRAM is not set. * * Default is ULONG_MAX (at least ~50 days). * Minimum is 3000ms for EBUR128_MODE_LRA and 400ms for EBUR128_MODE_M. * * @param st library state. * @param history duration of history in ms. * @return * - EBUR128_SUCCESS on success. * - EBUR128_ERROR_NO_CHANGE if history not changed. */ int ebur128_set_max_history(ebur128_state* st, unsigned long history); /** \brief Add frames to be processed. * * @param st library state. * @param src array of source frames. Channels must be interleaved. * @param frames number of frames. Not number of samples! * @return * - EBUR128_SUCCESS on success. * - EBUR128_ERROR_NOMEM on memory allocation error. */ int ebur128_add_frames_short(ebur128_state* st, const short* src, size_t frames); /** \brief See \ref ebur128_add_frames_short */ int ebur128_add_frames_int(ebur128_state* st, const int* src, size_t frames); /** \brief See \ref ebur128_add_frames_short */ int ebur128_add_frames_float(ebur128_state* st, const float* src, size_t frames); /** \brief See \ref ebur128_add_frames_short */ int ebur128_add_frames_double(ebur128_state* st, const double* src, size_t frames); /** \brief Get global integrated loudness in LUFS. * * @param st library state. * @param out integrated loudness in LUFS. -HUGE_VAL if result is negative * infinity. * @return * - EBUR128_SUCCESS on success. * - EBUR128_ERROR_INVALID_MODE if mode "EBUR128_MODE_I" has not been set. */ int ebur128_loudness_global(ebur128_state* st, double* out); /** \brief Get global integrated loudness in LUFS across multiple instances. * * @param sts array of library states. * @param size length of sts * @param out integrated loudness in LUFS. -HUGE_VAL if result is negative * infinity. * @return * - EBUR128_SUCCESS on success. * - EBUR128_ERROR_INVALID_MODE if mode "EBUR128_MODE_I" has not been set. */ int ebur128_loudness_global_multiple(ebur128_state** sts, size_t size, double* out); /** \brief Get momentary loudness (last 400ms) in LUFS. * * @param st library state. * @param out momentary loudness in LUFS. -HUGE_VAL if result is negative * infinity. * @return * - EBUR128_SUCCESS on success. */ int ebur128_loudness_momentary(ebur128_state* st, double* out); /** \brief Get short-term loudness (last 3s) in LUFS. * * @param st library state. * @param out short-term loudness in LUFS. -HUGE_VAL if result is negative * infinity. * @return * - EBUR128_SUCCESS on success. * - EBUR128_ERROR_INVALID_MODE if mode "EBUR128_MODE_S" has not been set. */ int ebur128_loudness_shortterm(ebur128_state* st, double* out); /** \brief Get loudness of the specified window in LUFS. * * window must not be larger than the current window set in st. * The current window can be changed by calling ebur128_set_max_window(). * * @param st library state. * @param window window in ms to calculate loudness. * @param out loudness in LUFS. -HUGE_VAL if result is negative infinity. * @return * - EBUR128_SUCCESS on success. * - EBUR128_ERROR_INVALID_MODE if window larger than current window in st. */ int ebur128_loudness_window(ebur128_state* st, unsigned long window, double* out); /** \brief Get loudness range (LRA) of programme in LU. * * Calculates loudness range according to EBU 3342. * * @param st library state. * @param out loudness range (LRA) in LU. Will not be changed in case of * error. EBUR128_ERROR_NOMEM or EBUR128_ERROR_INVALID_MODE will be * returned in this case. * @return * - EBUR128_SUCCESS on success. * - EBUR128_ERROR_NOMEM in case of memory allocation error. * - EBUR128_ERROR_INVALID_MODE if mode "EBUR128_MODE_LRA" has not been set. */ int ebur128_loudness_range(ebur128_state* st, double* out); /** \brief Get loudness range (LRA) in LU across multiple instances. * * Calculates loudness range according to EBU 3342. * * @param sts array of library states. * @param size length of sts * @param out loudness range (LRA) in LU. Will not be changed in case of * error. EBUR128_ERROR_NOMEM or EBUR128_ERROR_INVALID_MODE will be * returned in this case. * @return * - EBUR128_SUCCESS on success. * - EBUR128_ERROR_NOMEM in case of memory allocation error. * - EBUR128_ERROR_INVALID_MODE if mode "EBUR128_MODE_LRA" has not been set. */ int ebur128_loudness_range_multiple(ebur128_state** sts, size_t size, double* out); /** \brief Get maximum sample peak from all frames that have been processed. * * The equation to convert to dBFS is: 20 * log10(out) * * @param st library state * @param channel_number channel to analyse * @param out maximum sample peak in float format (1.0 is 0 dBFS) * @return * - EBUR128_SUCCESS on success. * - EBUR128_ERROR_INVALID_MODE if mode "EBUR128_MODE_SAMPLE_PEAK" has not * been set. * - EBUR128_ERROR_INVALID_CHANNEL_INDEX if invalid channel index. */ int ebur128_sample_peak(ebur128_state* st, unsigned int channel_number, double* out); /** \brief Get maximum sample peak from the last call to add_frames(). * * The equation to convert to dBFS is: 20 * log10(out) * * @param st library state * @param channel_number channel to analyse * @param out maximum sample peak in float format (1.0 is 0 dBFS) * @return * - EBUR128_SUCCESS on success. * - EBUR128_ERROR_INVALID_MODE if mode "EBUR128_MODE_SAMPLE_PEAK" has not * been set. * - EBUR128_ERROR_INVALID_CHANNEL_INDEX if invalid channel index. */ int ebur128_prev_sample_peak(ebur128_state* st, unsigned int channel_number, double* out); /** \brief Get maximum true peak from all frames that have been processed. * * Uses an implementation defined algorithm to calculate the true peak. Do not * try to compare resulting values across different versions of the library, * as the algorithm may change. * * The current implementation uses a custom polyphase FIR interpolator to * calculate true peak. Will oversample 4x for sample rates < 96000 Hz, 2x for * sample rates < 192000 Hz and leave the signal unchanged for 192000 Hz. * * The equation to convert to dBTP is: 20 * log10(out) * * @param st library state * @param channel_number channel to analyse * @param out maximum true peak in float format (1.0 is 0 dBTP) * @return * - EBUR128_SUCCESS on success. * - EBUR128_ERROR_INVALID_MODE if mode "EBUR128_MODE_TRUE_PEAK" has not * been set. * - EBUR128_ERROR_INVALID_CHANNEL_INDEX if invalid channel index. */ int ebur128_true_peak(ebur128_state* st, unsigned int channel_number, double* out); /** \brief Get maximum true peak from the last call to add_frames(). * * Uses an implementation defined algorithm to calculate the true peak. Do not * try to compare resulting values across different versions of the library, * as the algorithm may change. * * The current implementation uses a custom polyphase FIR interpolator to * calculate true peak. Will oversample 4x for sample rates < 96000 Hz, 2x for * sample rates < 192000 Hz and leave the signal unchanged for 192000 Hz. * * The equation to convert to dBTP is: 20 * log10(out) * * @param st library state * @param channel_number channel to analyse * @param out maximum true peak in float format (1.0 is 0 dBTP) * @return * - EBUR128_SUCCESS on success. * - EBUR128_ERROR_INVALID_MODE if mode "EBUR128_MODE_TRUE_PEAK" has not * been set. * - EBUR128_ERROR_INVALID_CHANNEL_INDEX if invalid channel index. */ int ebur128_prev_true_peak(ebur128_state* st, unsigned int channel_number, double* out); /** \brief Get relative threshold in LUFS. * * @param st library state * @param out relative threshold in LUFS. * @return * - EBUR128_SUCCESS on success. * - EBUR128_ERROR_INVALID_MODE if mode "EBUR128_MODE_I" has not * been set. */ int ebur128_relative_threshold(ebur128_state* st, double* out); #ifdef __cplusplus } #endif #endif /* EBUR128_H_ */ mlt-7.22.0/src/modules/plus/ebur128/queue/000775 000000 000000 00000000000 14531534050 020114 5ustar00rootroot000000 000000 mlt-7.22.0/src/modules/plus/ebur128/queue/sys/000775 000000 000000 00000000000 14531534050 020732 5ustar00rootroot000000 000000 mlt-7.22.0/src/modules/plus/ebur128/queue/sys/queue.h000664 000000 000000 00000053367 14531534050 022245 0ustar00rootroot000000 000000 /*- * Copyright (c) 1991, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. 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. * 4. Neither the name of the University 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 REGENTS 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 REGENTS 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. * * @(#)queue.h 8.5 (Berkeley) 8/20/94 * $FreeBSD: src/sys/sys/queue.h,v 1.68.2.4 2011/05/24 16:06:26 mdf Exp $ */ #ifndef _SYS_QUEUE_H_ #define _SYS_QUEUE_H_ #include /* * This file defines four types of data structures: singly-linked lists, * singly-linked tail queues, lists and tail queues. * * A singly-linked list is headed by a single forward pointer. The elements * are singly linked for minimum space and pointer manipulation overhead at * the expense of O(n) removal for arbitrary elements. New elements can be * added to the list after an existing element or at the head of the list. * Elements being removed from the head of the list should use the explicit * macro for this purpose for optimum efficiency. A singly-linked list may * only be traversed in the forward direction. Singly-linked lists are ideal * for applications with large datasets and few or no removals or for * implementing a LIFO queue. * * A singly-linked tail queue is headed by a pair of pointers, one to the * head of the list and the other to the tail of the list. The elements are * singly linked for minimum space and pointer manipulation overhead at the * expense of O(n) removal for arbitrary elements. New elements can be added * to the list after an existing element, at the head of the list, or at the * end of the list. Elements being removed from the head of the tail queue * should use the explicit macro for this purpose for optimum efficiency. * A singly-linked tail queue may only be traversed in the forward direction. * Singly-linked tail queues are ideal for applications with large datasets * and few or no removals or for implementing a FIFO queue. * * A list is headed by a single forward pointer (or an array of forward * pointers for a hash table header). The elements are doubly linked * so that an arbitrary element can be removed without a need to * traverse the list. New elements can be added to the list before * or after an existing element or at the head of the list. A list * may only be traversed in the forward direction. * * A tail queue is headed by a pair of pointers, one to the head of the * list and the other to the tail of the list. The elements are doubly * linked so that an arbitrary element can be removed without a need to * traverse the list. New elements can be added to the list before or * after an existing element, at the head of the list, or at the end of * the list. A tail queue may be traversed in either direction. * * For details on the use of these macros, see the queue(3) manual page. * * * SLIST LIST STAILQ TAILQ * _HEAD + + + + * _HEAD_INITIALIZER + + + + * _ENTRY + + + + * _INIT + + + + * _EMPTY + + + + * _FIRST + + + + * _NEXT + + + + * _PREV - - - + * _LAST - - + + * _FOREACH + + + + * _FOREACH_SAFE + + + + * _FOREACH_REVERSE - - - + * _FOREACH_REVERSE_SAFE - - - + * _INSERT_HEAD + + + + * _INSERT_BEFORE - + - + * _INSERT_AFTER + + + + * _INSERT_TAIL - - + + * _CONCAT - - + + * _REMOVE_AFTER + - + - * _REMOVE_HEAD + - + - * _REMOVE + + + + * _SWAP + + + + * */ #ifdef QUEUE_MACRO_DEBUG /* Store the last 2 places the queue element or head was altered */ struct qm_trace { char * lastfile; int lastline; char * prevfile; int prevline; }; #define TRACEBUF struct qm_trace trace; #define TRASHIT(x) do {(x) = (void *)-1;} while (0) #define QMD_SAVELINK(name, link) void **name = (void *)&(link) #define QMD_TRACE_HEAD(head) do { \ (head)->trace.prevline = (head)->trace.lastline; \ (head)->trace.prevfile = (head)->trace.lastfile; \ (head)->trace.lastline = __LINE__; \ (head)->trace.lastfile = __FILE__; \ } while (0) #define QMD_TRACE_ELEM(elem) do { \ (elem)->trace.prevline = (elem)->trace.lastline; \ (elem)->trace.prevfile = (elem)->trace.lastfile; \ (elem)->trace.lastline = __LINE__; \ (elem)->trace.lastfile = __FILE__; \ } while (0) #else #define QMD_TRACE_ELEM(elem) #define QMD_TRACE_HEAD(head) #define QMD_SAVELINK(name, link) #define TRACEBUF #define TRASHIT(x) #endif /* QUEUE_MACRO_DEBUG */ /* * Singly-linked List declarations. */ #define SLIST_HEAD(name, type) \ struct name { \ struct type *slh_first; /* first element */ \ } #define SLIST_HEAD_INITIALIZER(head) \ { NULL } #define SLIST_ENTRY(type) \ struct { \ struct type *sle_next; /* next element */ \ } /* * Singly-linked List functions. */ #define SLIST_EMPTY(head) ((head)->slh_first == NULL) #define SLIST_FIRST(head) ((head)->slh_first) #define SLIST_FOREACH(var, head, field) \ for ((var) = SLIST_FIRST((head)); \ (var); \ (var) = SLIST_NEXT((var), field)) #define SLIST_FOREACH_SAFE(var, head, field, tvar) \ for ((var) = SLIST_FIRST((head)); \ (var) && ((tvar) = SLIST_NEXT((var), field), 1); \ (var) = (tvar)) #define SLIST_FOREACH_PREVPTR(var, varp, head, field) \ for ((varp) = &SLIST_FIRST((head)); \ ((var) = *(varp)) != NULL; \ (varp) = &SLIST_NEXT((var), field)) #define SLIST_INIT(head) do { \ SLIST_FIRST((head)) = NULL; \ } while (0) #define SLIST_INSERT_AFTER(slistelm, elm, field) do { \ SLIST_NEXT((elm), field) = SLIST_NEXT((slistelm), field); \ SLIST_NEXT((slistelm), field) = (elm); \ } while (0) #define SLIST_INSERT_HEAD(head, elm, field) do { \ SLIST_NEXT((elm), field) = SLIST_FIRST((head)); \ SLIST_FIRST((head)) = (elm); \ } while (0) #define SLIST_NEXT(elm, field) ((elm)->field.sle_next) #define SLIST_REMOVE(head, elm, type, field) do { \ QMD_SAVELINK(oldnext, (elm)->field.sle_next); \ if (SLIST_FIRST((head)) == (elm)) { \ SLIST_REMOVE_HEAD((head), field); \ } \ else { \ struct type *curelm = SLIST_FIRST((head)); \ while (SLIST_NEXT(curelm, field) != (elm)) \ curelm = SLIST_NEXT(curelm, field); \ SLIST_REMOVE_AFTER(curelm, field); \ } \ TRASHIT(*oldnext); \ } while (0) #define SLIST_REMOVE_AFTER(elm, field) do { \ SLIST_NEXT(elm, field) = \ SLIST_NEXT(SLIST_NEXT(elm, field), field); \ } while (0) #define SLIST_REMOVE_HEAD(head, field) do { \ SLIST_FIRST((head)) = SLIST_NEXT(SLIST_FIRST((head)), field); \ } while (0) /* * Singly-linked Tail queue declarations. */ #define STAILQ_HEAD(name, type) \ struct name { \ struct type *stqh_first;/* first element */ \ struct type **stqh_last;/* addr of last next element */ \ } #define STAILQ_HEAD_INITIALIZER(head) \ { NULL, &(head).stqh_first } #define STAILQ_ENTRY(type) \ struct { \ struct type *stqe_next; /* next element */ \ } /* * Singly-linked Tail queue functions. */ #define STAILQ_CONCAT(head1, head2) do { \ if (!STAILQ_EMPTY((head2))) { \ *(head1)->stqh_last = (head2)->stqh_first; \ (head1)->stqh_last = (head2)->stqh_last; \ STAILQ_INIT((head2)); \ } \ } while (0) #define STAILQ_EMPTY(head) ((head)->stqh_first == NULL) #define STAILQ_FIRST(head) ((head)->stqh_first) #define STAILQ_FOREACH(var, head, field) \ for((var) = STAILQ_FIRST((head)); \ (var); \ (var) = STAILQ_NEXT((var), field)) #define STAILQ_FOREACH_SAFE(var, head, field, tvar) \ for ((var) = STAILQ_FIRST((head)); \ (var) && ((tvar) = STAILQ_NEXT((var), field), 1); \ (var) = (tvar)) #define STAILQ_INIT(head) do { \ STAILQ_FIRST((head)) = NULL; \ (head)->stqh_last = &STAILQ_FIRST((head)); \ } while (0) #define STAILQ_INSERT_AFTER(head, tqelm, elm, field) do { \ if ((STAILQ_NEXT((elm), field) = STAILQ_NEXT((tqelm), field)) == NULL)\ (head)->stqh_last = &STAILQ_NEXT((elm), field); \ STAILQ_NEXT((tqelm), field) = (elm); \ } while (0) #define STAILQ_INSERT_HEAD(head, elm, field) do { \ if ((STAILQ_NEXT((elm), field) = STAILQ_FIRST((head))) == NULL) \ (head)->stqh_last = &STAILQ_NEXT((elm), field); \ STAILQ_FIRST((head)) = (elm); \ } while (0) #define STAILQ_INSERT_TAIL(head, elm, field) do { \ STAILQ_NEXT((elm), field) = NULL; \ *(head)->stqh_last = (elm); \ (head)->stqh_last = &STAILQ_NEXT((elm), field); \ } while (0) #define STAILQ_LAST(head, type, field) \ (STAILQ_EMPTY((head)) ? \ NULL : \ ((struct type *)(void *) \ ((char *)((head)->stqh_last) - __offsetof(struct type, field)))) #define STAILQ_NEXT(elm, field) ((elm)->field.stqe_next) #define STAILQ_REMOVE(head, elm, type, field) do { \ QMD_SAVELINK(oldnext, (elm)->field.stqe_next); \ if (STAILQ_FIRST((head)) == (elm)) { \ STAILQ_REMOVE_HEAD((head), field); \ } \ else { \ struct type *curelm = STAILQ_FIRST((head)); \ while (STAILQ_NEXT(curelm, field) != (elm)) \ curelm = STAILQ_NEXT(curelm, field); \ STAILQ_REMOVE_AFTER(head, curelm, field); \ } \ TRASHIT(*oldnext); \ } while (0) #define STAILQ_REMOVE_AFTER(head, elm, field) do { \ if ((STAILQ_NEXT(elm, field) = \ STAILQ_NEXT(STAILQ_NEXT(elm, field), field)) == NULL) \ (head)->stqh_last = &STAILQ_NEXT((elm), field); \ } while (0) #define STAILQ_REMOVE_HEAD(head, field) do { \ if ((STAILQ_FIRST((head)) = \ STAILQ_NEXT(STAILQ_FIRST((head)), field)) == NULL) \ (head)->stqh_last = &STAILQ_FIRST((head)); \ } while (0) #define STAILQ_SWAP(head1, head2, type) do { \ struct type *swap_first = STAILQ_FIRST(head1); \ struct type **swap_last = (head1)->stqh_last; \ STAILQ_FIRST(head1) = STAILQ_FIRST(head2); \ (head1)->stqh_last = (head2)->stqh_last; \ STAILQ_FIRST(head2) = swap_first; \ (head2)->stqh_last = swap_last; \ if (STAILQ_EMPTY(head1)) \ (head1)->stqh_last = &STAILQ_FIRST(head1); \ if (STAILQ_EMPTY(head2)) \ (head2)->stqh_last = &STAILQ_FIRST(head2); \ } while (0) /* * List declarations. */ #define LIST_HEAD(name, type) \ struct name { \ struct type *lh_first; /* first element */ \ } #define LIST_HEAD_INITIALIZER(head) \ { NULL } #define LIST_ENTRY(type) \ struct { \ struct type *le_next; /* next element */ \ struct type **le_prev; /* address of previous next element */ \ } /* * List functions. */ #if (defined(_KERNEL) && defined(INVARIANTS)) #define QMD_LIST_CHECK_HEAD(head, field) do { \ if (LIST_FIRST((head)) != NULL && \ LIST_FIRST((head))->field.le_prev != \ &LIST_FIRST((head))) \ panic("Bad list head %p first->prev != head", (head)); \ } while (0) #define QMD_LIST_CHECK_NEXT(elm, field) do { \ if (LIST_NEXT((elm), field) != NULL && \ LIST_NEXT((elm), field)->field.le_prev != \ &((elm)->field.le_next)) \ panic("Bad link elm %p next->prev != elm", (elm)); \ } while (0) #define QMD_LIST_CHECK_PREV(elm, field) do { \ if (*(elm)->field.le_prev != (elm)) \ panic("Bad link elm %p prev->next != elm", (elm)); \ } while (0) #else #define QMD_LIST_CHECK_HEAD(head, field) #define QMD_LIST_CHECK_NEXT(elm, field) #define QMD_LIST_CHECK_PREV(elm, field) #endif /* (_KERNEL && INVARIANTS) */ #define LIST_EMPTY(head) ((head)->lh_first == NULL) #define LIST_FIRST(head) ((head)->lh_first) #define LIST_FOREACH(var, head, field) \ for ((var) = LIST_FIRST((head)); \ (var); \ (var) = LIST_NEXT((var), field)) #define LIST_FOREACH_SAFE(var, head, field, tvar) \ for ((var) = LIST_FIRST((head)); \ (var) && ((tvar) = LIST_NEXT((var), field), 1); \ (var) = (tvar)) #define LIST_INIT(head) do { \ LIST_FIRST((head)) = NULL; \ } while (0) #define LIST_INSERT_AFTER(listelm, elm, field) do { \ QMD_LIST_CHECK_NEXT(listelm, field); \ if ((LIST_NEXT((elm), field) = LIST_NEXT((listelm), field)) != NULL)\ LIST_NEXT((listelm), field)->field.le_prev = \ &LIST_NEXT((elm), field); \ LIST_NEXT((listelm), field) = (elm); \ (elm)->field.le_prev = &LIST_NEXT((listelm), field); \ } while (0) #define LIST_INSERT_BEFORE(listelm, elm, field) do { \ QMD_LIST_CHECK_PREV(listelm, field); \ (elm)->field.le_prev = (listelm)->field.le_prev; \ LIST_NEXT((elm), field) = (listelm); \ *(listelm)->field.le_prev = (elm); \ (listelm)->field.le_prev = &LIST_NEXT((elm), field); \ } while (0) #define LIST_INSERT_HEAD(head, elm, field) do { \ QMD_LIST_CHECK_HEAD((head), field); \ if ((LIST_NEXT((elm), field) = LIST_FIRST((head))) != NULL) \ LIST_FIRST((head))->field.le_prev = &LIST_NEXT((elm), field);\ LIST_FIRST((head)) = (elm); \ (elm)->field.le_prev = &LIST_FIRST((head)); \ } while (0) #define LIST_NEXT(elm, field) ((elm)->field.le_next) #define LIST_REMOVE(elm, field) do { \ QMD_SAVELINK(oldnext, (elm)->field.le_next); \ QMD_SAVELINK(oldprev, (elm)->field.le_prev); \ QMD_LIST_CHECK_NEXT(elm, field); \ QMD_LIST_CHECK_PREV(elm, field); \ if (LIST_NEXT((elm), field) != NULL) \ LIST_NEXT((elm), field)->field.le_prev = \ (elm)->field.le_prev; \ *(elm)->field.le_prev = LIST_NEXT((elm), field); \ TRASHIT(*oldnext); \ TRASHIT(*oldprev); \ } while (0) #define LIST_SWAP(head1, head2, type, field) do { \ struct type *swap_tmp = LIST_FIRST((head1)); \ LIST_FIRST((head1)) = LIST_FIRST((head2)); \ LIST_FIRST((head2)) = swap_tmp; \ if ((swap_tmp = LIST_FIRST((head1))) != NULL) \ swap_tmp->field.le_prev = &LIST_FIRST((head1)); \ if ((swap_tmp = LIST_FIRST((head2))) != NULL) \ swap_tmp->field.le_prev = &LIST_FIRST((head2)); \ } while (0) /* * Tail queue declarations. */ #define TAILQ_HEAD(name, type) \ struct name { \ struct type *tqh_first; /* first element */ \ struct type **tqh_last; /* addr of last next element */ \ TRACEBUF \ } #define TAILQ_HEAD_INITIALIZER(head) \ { NULL, &(head).tqh_first } #define TAILQ_ENTRY(type) \ struct { \ struct type *tqe_next; /* next element */ \ struct type **tqe_prev; /* address of previous next element */ \ TRACEBUF \ } /* * Tail queue functions. */ #if (defined(_KERNEL) && defined(INVARIANTS)) #define QMD_TAILQ_CHECK_HEAD(head, field) do { \ if (!TAILQ_EMPTY(head) && \ TAILQ_FIRST((head))->field.tqe_prev != \ &TAILQ_FIRST((head))) \ panic("Bad tailq head %p first->prev != head", (head)); \ } while (0) #define QMD_TAILQ_CHECK_TAIL(head, field) do { \ if (*(head)->tqh_last != NULL) \ panic("Bad tailq NEXT(%p->tqh_last) != NULL", (head)); \ } while (0) #define QMD_TAILQ_CHECK_NEXT(elm, field) do { \ if (TAILQ_NEXT((elm), field) != NULL && \ TAILQ_NEXT((elm), field)->field.tqe_prev != \ &((elm)->field.tqe_next)) \ panic("Bad link elm %p next->prev != elm", (elm)); \ } while (0) #define QMD_TAILQ_CHECK_PREV(elm, field) do { \ if (*(elm)->field.tqe_prev != (elm)) \ panic("Bad link elm %p prev->next != elm", (elm)); \ } while (0) #else #define QMD_TAILQ_CHECK_HEAD(head, field) #define QMD_TAILQ_CHECK_TAIL(head, headname) #define QMD_TAILQ_CHECK_NEXT(elm, field) #define QMD_TAILQ_CHECK_PREV(elm, field) #endif /* (_KERNEL && INVARIANTS) */ #define TAILQ_CONCAT(head1, head2, field) do { \ if (!TAILQ_EMPTY(head2)) { \ *(head1)->tqh_last = (head2)->tqh_first; \ (head2)->tqh_first->field.tqe_prev = (head1)->tqh_last; \ (head1)->tqh_last = (head2)->tqh_last; \ TAILQ_INIT((head2)); \ QMD_TRACE_HEAD(head1); \ QMD_TRACE_HEAD(head2); \ } \ } while (0) #define TAILQ_EMPTY(head) ((head)->tqh_first == NULL) #define TAILQ_FIRST(head) ((head)->tqh_first) #define TAILQ_FOREACH(var, head, field) \ for ((var) = TAILQ_FIRST((head)); \ (var); \ (var) = TAILQ_NEXT((var), field)) #define TAILQ_FOREACH_SAFE(var, head, field, tvar) \ for ((var) = TAILQ_FIRST((head)); \ (var) && ((tvar) = TAILQ_NEXT((var), field), 1); \ (var) = (tvar)) #define TAILQ_FOREACH_REVERSE(var, head, headname, field) \ for ((var) = TAILQ_LAST((head), headname); \ (var); \ (var) = TAILQ_PREV((var), headname, field)) #define TAILQ_FOREACH_REVERSE_SAFE(var, head, headname, field, tvar) \ for ((var) = TAILQ_LAST((head), headname); \ (var) && ((tvar) = TAILQ_PREV((var), headname, field), 1); \ (var) = (tvar)) #define TAILQ_INIT(head) do { \ TAILQ_FIRST((head)) = NULL; \ (head)->tqh_last = &TAILQ_FIRST((head)); \ QMD_TRACE_HEAD(head); \ } while (0) #define TAILQ_INSERT_AFTER(head, listelm, elm, field) do { \ QMD_TAILQ_CHECK_NEXT(listelm, field); \ if ((TAILQ_NEXT((elm), field) = TAILQ_NEXT((listelm), field)) != NULL)\ TAILQ_NEXT((elm), field)->field.tqe_prev = \ &TAILQ_NEXT((elm), field); \ else { \ (head)->tqh_last = &TAILQ_NEXT((elm), field); \ QMD_TRACE_HEAD(head); \ } \ TAILQ_NEXT((listelm), field) = (elm); \ (elm)->field.tqe_prev = &TAILQ_NEXT((listelm), field); \ QMD_TRACE_ELEM(&(elm)->field); \ QMD_TRACE_ELEM(&listelm->field); \ } while (0) #define TAILQ_INSERT_BEFORE(listelm, elm, field) do { \ QMD_TAILQ_CHECK_PREV(listelm, field); \ (elm)->field.tqe_prev = (listelm)->field.tqe_prev; \ TAILQ_NEXT((elm), field) = (listelm); \ *(listelm)->field.tqe_prev = (elm); \ (listelm)->field.tqe_prev = &TAILQ_NEXT((elm), field); \ QMD_TRACE_ELEM(&(elm)->field); \ QMD_TRACE_ELEM(&listelm->field); \ } while (0) #define TAILQ_INSERT_HEAD(head, elm, field) do { \ QMD_TAILQ_CHECK_HEAD(head, field); \ if ((TAILQ_NEXT((elm), field) = TAILQ_FIRST((head))) != NULL) \ TAILQ_FIRST((head))->field.tqe_prev = \ &TAILQ_NEXT((elm), field); \ else \ (head)->tqh_last = &TAILQ_NEXT((elm), field); \ TAILQ_FIRST((head)) = (elm); \ (elm)->field.tqe_prev = &TAILQ_FIRST((head)); \ QMD_TRACE_HEAD(head); \ QMD_TRACE_ELEM(&(elm)->field); \ } while (0) #define TAILQ_INSERT_TAIL(head, elm, field) do { \ QMD_TAILQ_CHECK_TAIL(head, field); \ TAILQ_NEXT((elm), field) = NULL; \ (elm)->field.tqe_prev = (head)->tqh_last; \ *(head)->tqh_last = (elm); \ (head)->tqh_last = &TAILQ_NEXT((elm), field); \ QMD_TRACE_HEAD(head); \ QMD_TRACE_ELEM(&(elm)->field); \ } while (0) #define TAILQ_LAST(head, headname) \ (*(((struct headname *)((head)->tqh_last))->tqh_last)) #define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next) #define TAILQ_PREV(elm, headname, field) \ (*(((struct headname *)((elm)->field.tqe_prev))->tqh_last)) #define TAILQ_REMOVE(head, elm, field) do { \ QMD_SAVELINK(oldnext, (elm)->field.tqe_next); \ QMD_SAVELINK(oldprev, (elm)->field.tqe_prev); \ QMD_TAILQ_CHECK_NEXT(elm, field); \ QMD_TAILQ_CHECK_PREV(elm, field); \ if ((TAILQ_NEXT((elm), field)) != NULL) \ TAILQ_NEXT((elm), field)->field.tqe_prev = \ (elm)->field.tqe_prev; \ else { \ (head)->tqh_last = (elm)->field.tqe_prev; \ QMD_TRACE_HEAD(head); \ } \ *(elm)->field.tqe_prev = TAILQ_NEXT((elm), field); \ TRASHIT(*oldnext); \ TRASHIT(*oldprev); \ QMD_TRACE_ELEM(&(elm)->field); \ } while (0) #define TAILQ_SWAP(head1, head2, type, field) do { \ struct type *swap_first = (head1)->tqh_first; \ struct type **swap_last = (head1)->tqh_last; \ (head1)->tqh_first = (head2)->tqh_first; \ (head1)->tqh_last = (head2)->tqh_last; \ (head2)->tqh_first = swap_first; \ (head2)->tqh_last = swap_last; \ if ((swap_first = (head1)->tqh_first) != NULL) \ swap_first->field.tqe_prev = &(head1)->tqh_first; \ else \ (head1)->tqh_last = &(head1)->tqh_first; \ if ((swap_first = (head2)->tqh_first) != NULL) \ swap_first->field.tqe_prev = &(head2)->tqh_first; \ else \ (head2)->tqh_last = &(head2)->tqh_first; \ } while (0) #ifdef _KERNEL /* * XXX insque() and remque() are an old way of handling certain queues. * They bogusly assumes that all queue heads look alike. */ struct quehead { struct quehead *qh_link; struct quehead *qh_rlink; }; #ifdef __CC_SUPPORTS___INLINE static __inline void insque(void *a, void *b) { struct quehead *element = (struct quehead *)a, *head = (struct quehead *)b; element->qh_link = head->qh_link; element->qh_rlink = head; head->qh_link = element; element->qh_link->qh_rlink = element; } static __inline void remque(void *a) { struct quehead *element = (struct quehead *)a; element->qh_link->qh_rlink = element->qh_rlink; element->qh_rlink->qh_link = element->qh_link; element->qh_rlink = 0; } #else /* !__CC_SUPPORTS___INLINE */ void insque(void *a, void *b); void remque(void *a); #endif /* __CC_SUPPORTS___INLINE */ #endif /* _KERNEL */ #endif /* !_SYS_QUEUE_H_ */ mlt-7.22.0/src/modules/plus/factory.c000664 000000 000000 00000030157 14531534050 017421 0ustar00rootroot000000 000000 /* * factory.c -- the factory method interfaces * Copyright (C) 2003-2018 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include extern mlt_consumer consumer_blipflash_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_affine_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_charcoal_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_chroma_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_chroma_hold_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_dynamictext_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_dynamic_loudness_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_invert_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_lift_gamma_gain_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_loudness_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_loudness_meter_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_lumakey_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_rgblut_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_sepia_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_shape_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_spot_remover_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_strobe_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_text_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_threshold_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_timer_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_producer producer_blipflash_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_producer producer_count_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_producer producer_pgm_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_transition transition_affine_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); #ifdef USE_FFTW extern mlt_filter filter_dance_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_fft_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); #endif static mlt_properties metadata(mlt_service_type type, const char *id, void *data) { char file[PATH_MAX]; snprintf(file, PATH_MAX, "%s/plus/%s", mlt_environment("MLT_DATA"), (char *) data); return mlt_properties_parse_yaml(file); } MLT_REPOSITORY { MLT_REGISTER(mlt_service_consumer_type, "blipflash", consumer_blipflash_init); MLT_REGISTER(mlt_service_filter_type, "affine", filter_affine_init); MLT_REGISTER(mlt_service_filter_type, "charcoal", filter_charcoal_init); MLT_REGISTER(mlt_service_filter_type, "chroma", filter_chroma_init); MLT_REGISTER(mlt_service_filter_type, "chroma_hold", filter_chroma_hold_init); MLT_REGISTER(mlt_service_filter_type, "dynamictext", filter_dynamictext_init); MLT_REGISTER(mlt_service_filter_type, "dynamic_loudness", filter_dynamic_loudness_init); MLT_REGISTER(mlt_service_filter_type, "invert", filter_invert_init); MLT_REGISTER(mlt_service_filter_type, "lift_gamma_gain", filter_lift_gamma_gain_init); MLT_REGISTER(mlt_service_filter_type, "loudness", filter_loudness_init); MLT_REGISTER(mlt_service_filter_type, "loudness_meter", filter_loudness_meter_init); MLT_REGISTER(mlt_service_filter_type, "lumakey", filter_lumakey_init); MLT_REGISTER(mlt_service_filter_type, "rgblut", filter_rgblut_init); MLT_REGISTER(mlt_service_filter_type, "sepia", filter_sepia_init); MLT_REGISTER(mlt_service_filter_type, "shape", filter_shape_init); MLT_REGISTER(mlt_service_filter_type, "spot_remover", filter_spot_remover_init); MLT_REGISTER(mlt_service_filter_type, "strobe", filter_strobe_init); MLT_REGISTER(mlt_service_filter_type, "text", filter_text_init); MLT_REGISTER(mlt_service_filter_type, "threshold", filter_threshold_init); MLT_REGISTER(mlt_service_filter_type, "timer", filter_timer_init); MLT_REGISTER(mlt_service_producer_type, "blipflash", producer_blipflash_init); MLT_REGISTER(mlt_service_producer_type, "count", producer_count_init); MLT_REGISTER(mlt_service_producer_type, "pgm", producer_pgm_init); MLT_REGISTER(mlt_service_transition_type, "affine", transition_affine_init); #ifdef USE_FFTW MLT_REGISTER(mlt_service_filter_type, "dance", filter_dance_init); MLT_REGISTER(mlt_service_filter_type, "fft", filter_fft_init); #endif MLT_REGISTER_METADATA(mlt_service_consumer_type, "blipflash", metadata, "consumer_blipflash.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "affine", metadata, "filter_affine.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "charcoal", metadata, "filter_charcoal.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "chroma", metadata, "filter_chroma.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "chroma_hold", metadata, "filter_chroma_hold.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "dynamictext", metadata, "filter_dynamictext.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "dynamic_loudness", metadata, "filter_dynamic_loudness.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "invert", metadata, "filter_invert.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "lift_gamma_gain", metadata, "filter_lift_gamma_gain.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "loudness", metadata, "filter_loudness.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "loudness_meter", metadata, "filter_loudness_meter.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "lumakey", metadata, "filter_lumakey.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "rgblut", metadata, "filter_rgblut.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "sepia", metadata, "filter_sepia.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "shape", metadata, "filter_shape.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "spot_remover", metadata, "filter_spot_remover.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "strobe", metadata, "filter_strobe.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "text", metadata, "filter_text.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "threshold", metadata, "filter_threshold.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "timer", metadata, "filter_timer.yml"); MLT_REGISTER_METADATA(mlt_service_producer_type, "blipflash", metadata, "producer_blipflash.yml"); MLT_REGISTER_METADATA(mlt_service_producer_type, "count", metadata, "producer_count.yml"); MLT_REGISTER_METADATA(mlt_service_producer_type, "pgm", metadata, "producer_pgm.yml"); MLT_REGISTER_METADATA(mlt_service_transition_type, "affine", metadata, "transition_affine.yml"); #ifdef USE_FFTW MLT_REGISTER_METADATA(mlt_service_filter_type, "dance", metadata, "filter_dance.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "fft", metadata, "filter_fft.yml"); #endif } mlt-7.22.0/src/modules/plus/filter_affine.c000664 000000 000000 00000015621 14531534050 020546 0ustar00rootroot000000 000000 /* * filter_affine.c -- affine filter * Copyright (C) 2003-2021 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #define MLT_AFFINE_COUNT_PROPERTY "filter_affine.count" /** Do it :-). */ static int filter_get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { // Get the filter mlt_filter filter = mlt_frame_pop_service(frame); // Get the properties mlt_properties properties = MLT_FILTER_PROPERTIES(filter); // Get the image int error = 0; *format = mlt_image_rgba; mlt_service_lock(MLT_FILTER_SERVICE(filter)); mlt_producer producer = mlt_properties_get_data(properties, "producer", NULL); mlt_transition transition = mlt_properties_get_data(properties, "transition", NULL); mlt_frame a_frame = NULL; mlt_profile profile = mlt_service_profile(MLT_FILTER_SERVICE(filter)); char *background = mlt_properties_get(properties, "background"); char *previous = mlt_properties_get(properties, "_background"); if (producer == NULL || (background && previous && strcmp(background, previous))) { producer = mlt_factory_producer(profile, NULL, background); mlt_properties_set_data(properties, "producer", producer, 0, (mlt_destructor) mlt_producer_close, NULL); mlt_properties_set(properties, "_background", background); } if (transition == NULL) { transition = mlt_factory_transition(profile, "affine", NULL); mlt_properties_set_data(properties, "transition", transition, 0, (mlt_destructor) mlt_transition_close, NULL); if (transition) mlt_properties_set_int(MLT_TRANSITION_PROPERTIES(transition), "b_alpha", 1); } if (producer != NULL && transition != NULL) { mlt_position position = mlt_filter_get_position(filter, frame); mlt_properties frame_properties = MLT_FRAME_PROPERTIES(frame); mlt_position in = mlt_filter_get_in(filter); mlt_position out = mlt_filter_get_out(filter); double consumer_ar = mlt_profile_sar(profile); mlt_transition_set_in_and_out(transition, in, out); if (out > 0) { mlt_properties_set_position(MLT_PRODUCER_PROPERTIES(producer), "length", out - in + 1); mlt_producer_set_in_and_out(producer, in, out); } mlt_producer_seek(producer, in + position); mlt_properties_pass(MLT_PRODUCER_PROPERTIES(producer), properties, "producer."); mlt_properties_pass(MLT_TRANSITION_PROPERTIES(transition), properties, "transition."); mlt_service_get_frame(MLT_PRODUCER_SERVICE(producer), &a_frame, 0); mlt_frame_set_position(a_frame, in + position); // Set the rescale interpolation to match the frame mlt_properties_set(MLT_FRAME_PROPERTIES(a_frame), "consumer.rescale", mlt_properties_get(frame_properties, "consumer.rescale")); // Special case - aspect_ratio = 0 if (mlt_frame_get_aspect_ratio(frame) == 0) mlt_frame_set_aspect_ratio(frame, consumer_ar); if (mlt_frame_get_aspect_ratio(a_frame) == 0) mlt_frame_set_aspect_ratio(a_frame, consumer_ar); // Add the affine transition onto the frame stack mlt_transition_process(transition, a_frame, frame); if (mlt_properties_get_int(properties, "use_normalized") || mlt_properties_get_int(properties, "use_normalized")) { // Use the normalized width & height mlt_profile profile = mlt_service_profile(MLT_FILTER_SERVICE(filter)); *width = profile->width; *height = profile->height; } // Prescale the image before applying affine filters if more than 1 if (mlt_properties_get_int(frame_properties, MLT_AFFINE_COUNT_PROPERTY) > 1) { mlt_properties_set_int(frame_properties, "always_scale", 1); } mlt_frame_get_image(a_frame, image, format, width, height, writable); mlt_properties_set_data(frame_properties, "affine_frame", a_frame, 0, (mlt_destructor) mlt_frame_close, NULL); mlt_frame_set_image(frame, *image, *width * *height * 4, NULL); uint8_t *alpha = mlt_frame_get_alpha(a_frame); if (alpha) { mlt_frame_set_alpha(frame, alpha, *width * *height, NULL); } mlt_service_unlock(MLT_FILTER_SERVICE(filter)); } else { mlt_service_unlock(MLT_FILTER_SERVICE(filter)); } return error; } /** Filter processing. */ static mlt_frame filter_process(mlt_filter filter, mlt_frame frame) { // Push the frame filter mlt_frame_push_service(frame, filter); mlt_frame_push_get_image(frame, filter_get_image); // Count the number of affine filters on this frame if (mlt_properties_get_int(MLT_FRAME_PROPERTIES(frame), MLT_AFFINE_COUNT_PROPERTY)) { int count = mlt_properties_get_int(MLT_FRAME_PROPERTIES(frame), MLT_AFFINE_COUNT_PROPERTY); mlt_properties_set_int(MLT_FRAME_PROPERTIES(frame), MLT_AFFINE_COUNT_PROPERTY, count + 1); } else { mlt_properties_set_int(MLT_FRAME_PROPERTIES(frame), MLT_AFFINE_COUNT_PROPERTY, 1); } return frame; } /** Constructor for the filter. */ mlt_filter filter_affine_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = mlt_filter_new(); if (filter != NULL) { filter->process = filter_process; mlt_properties_set(MLT_FILTER_PROPERTIES(filter), "background", arg ? arg : "colour:0"); } return filter; } mlt-7.22.0/src/modules/plus/filter_affine.yml000664 000000 000000 00000002373 14531534050 021125 0ustar00rootroot000000 000000 schema_version: 0.2 type: filter identifier: affine title: Transform version: 5 copyright: Meltytech, LLC creator: Charles Yates license: LGPLv2.1 language: en tags: - Video parameters: - identifier: background argument: yes title: Background description: > The specification for a producer to act as the background image. type: string default: colour:0 mutable: yes - identifier: producer.* title: Producer properties description: A property and its value to apply to the producer. type: properties mutable: yes - identifier: transition.* title: Transition properties description: > A property and its value to apply to the transition. This is the primary means to use this filter. See the "affine" transition for details. type: properties service-name: transition.affine mutable: yes - identifier: use_normalized title: Use normalized description: > Use the profile's video resolution when requesting the image from the filter's producer instead of the resolution requested by the consumer. type: boolean default: 0 mutable: yes - identifier: use_normalised title: Use normalized (*DEPRECATED*) description: deprecated. See use_normalized mlt-7.22.0/src/modules/plus/filter_charcoal.c000664 000000 000000 00000020625 14531534050 021072 0ustar00rootroot000000 000000 /* * filter_charcoal.c -- charcoal filter * Copyright (C) 2003-2022 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include static inline int get_Y(uint8_t *pixels, int width, int height, int x, int y, int max_luma) { if (x < 0 || x >= width || y < 0 || y >= height) { return max_luma; } else { uint8_t *pixel = pixels + y * (width << 1) + (x << 1); return *pixel; } } static inline int sqrti(int n) { int p = 0; int q = 1; int r = n; int h = 0; while (q <= n) q = q << 2; while (q != 1) { q = q >> 2; h = p + q; p = p >> 1; if (r >= h) { p = p + q; r = r - h; } } return p; } typedef struct { uint8_t *image; uint8_t *dest; int width; int height; int x_scatter; int y_scatter; int min; int max_luma; int max_chroma; int invert; int invert_luma; float scale; float mix; } slice_desc; static int slice_proc(int id, int index, int jobs, void *data) { (void) id; // unused slice_desc *d = (slice_desc *) data; int slice_line_start, slice_height = mlt_slices_size_slice(jobs, index, d->height, &slice_line_start); uint8_t *p = d->dest + slice_line_start * d->width * 2; uint8_t *q = d->image + slice_line_start * d->width * 2; int matrix[3][3]; int sum1; int sum2; float sum; int val; for (int y = slice_line_start; y < slice_line_start + slice_height; y++) { for (int x = 0; x < d->width; x++) { // Populate the matrix matrix[0][0] = get_Y(d->image, d->width, d->height, x - d->x_scatter, y - d->y_scatter, d->max_luma); matrix[0][1] = get_Y(d->image, d->width, d->height, x, y - d->y_scatter, d->max_luma); matrix[0][2] = get_Y(d->image, d->width, d->height, x + d->x_scatter, y - d->y_scatter, d->max_luma); matrix[1][0] = get_Y(d->image, d->width, d->height, x - d->x_scatter, y, d->max_luma); matrix[1][2] = get_Y(d->image, d->width, d->height, x + d->x_scatter, y, d->max_luma); matrix[2][0] = get_Y(d->image, d->width, d->height, x - d->x_scatter, y + d->y_scatter, d->max_luma); matrix[2][1] = get_Y(d->image, d->width, d->height, x, y + d->y_scatter, d->max_luma); matrix[2][2] = get_Y(d->image, d->width, d->height, x + d->x_scatter, y + d->y_scatter, d->max_luma); // Do calculations sum1 = (matrix[2][0] - matrix[0][0]) + ((matrix[2][1] - matrix[0][1]) << 1) + (matrix[2][2] - matrix[2][0]); sum2 = (matrix[0][2] - matrix[0][0]) + ((matrix[1][2] - matrix[1][0]) << 1) + (matrix[2][2] - matrix[2][0]); sum = d->scale * sqrti(sum1 * sum1 + sum2 * sum2); // Assign value *p++ = !d->invert ? (sum >= d->min && sum <= d->max_luma ? d->invert_luma - sum : sum < d->min ? d->max_luma : d->min) : (sum >= d->min && sum <= d->max_luma ? sum : sum < d->min ? d->min : d->max_luma); q++; val = 128 + d->mix * (*q++ - 128); val = CLAMP(val, d->min, d->max_chroma); *p++ = val; } } return 0; } static int filter_get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { // Get the filter mlt_filter filter = mlt_frame_pop_service(frame); mlt_properties properties = MLT_FILTER_PROPERTIES(filter); mlt_position position = mlt_filter_get_position(filter, frame); mlt_position length = mlt_filter_get_length2(filter, frame); // Get the image *format = mlt_image_yuv422; int error = mlt_frame_get_image(frame, image, format, width, height, 0); // Only process if we have no error and a valid colour space if (error == 0) { int size = *width * *height * 2; int full_range = mlt_properties_get_int(MLT_FRAME_PROPERTIES(frame), "full_range"); // Get the charcoal scatter value int x_scatter = mlt_properties_anim_get_double(properties, "x_scatter", position, length); int y_scatter = mlt_properties_anim_get_double(properties, "y_scatter", position, length); mlt_profile profile = mlt_service_profile(MLT_FILTER_SERVICE(filter)); double scale_x = mlt_profile_scale_width(profile, *width); double scale_y = mlt_profile_scale_height(profile, *height); if (scale_x > 0.0 || scale_y > 0.0) { x_scatter = MAX(1, lrint(x_scatter * scale_x)); y_scatter = MAX(1, lrint(y_scatter * scale_y)); } slice_desc desc = {// We need to create a new frame as this effect modifies the input .image = *image, .dest = mlt_pool_alloc(size), .width = *width, .height = *height, .x_scatter = x_scatter, .y_scatter = y_scatter, .min = full_range ? 0 : 16, .max_luma = full_range ? 255 : 235, .max_chroma = full_range ? 255 : 240, .invert = mlt_properties_anim_get_int(properties, "invert", position, length), .invert_luma = full_range ? 255 : 251, .scale = mlt_properties_anim_get_double(properties, "scale", position, length), .mix = mlt_properties_anim_get_double(properties, "mix", position, length)}; mlt_slices_run_normal(0, slice_proc, &desc); // Return the created image *image = desc.dest; // Store new and destroy old mlt_frame_set_image(frame, *image, size, mlt_pool_release); } return error; } /** Filter processing. */ static mlt_frame filter_process(mlt_filter filter, mlt_frame frame) { // Push the frame filter mlt_frame_push_service(frame, filter); mlt_frame_push_get_image(frame, filter_get_image); return frame; } /** Constructor for the filter. */ mlt_filter filter_charcoal_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = mlt_filter_new(); if (filter != NULL) { filter->process = filter_process; mlt_properties_set_int(MLT_FILTER_PROPERTIES(filter), "x_scatter", 1); mlt_properties_set_int(MLT_FILTER_PROPERTIES(filter), "y_scatter", 1); mlt_properties_set_double(MLT_FILTER_PROPERTIES(filter), "scale", 1.5); mlt_properties_set_double(MLT_FILTER_PROPERTIES(filter), "mix", 0.0); } return filter; } mlt-7.22.0/src/modules/plus/filter_charcoal.yml000664 000000 000000 00000001462 14531534050 021447 0ustar00rootroot000000 000000 schema_version: 7.0 type: filter identifier: charcoal title: Charcoal version: 1 copyright: Meltytech, LLC creator: Charles Yates license: LGPLv2.1 language: en tags: - Video parameters: - identifier: x_scatter title: Line Width type: integer default: 1 minimum: 1 mutable: yes animation: yes unit: pixels - identifier: y_scatter title: Line Height type: integer default: 1 minimum: 1 mutable: yes animation: yes unit: pixels - identifier: scale title: Contrast type: float default: 1.5 mutable: yes animation: yes - identifier: mix title: Color type: float default: 0 mutable: yes animation: yes - identifier: invert title: Invert type: boolean default: 0 mutable: yes animation: yes mlt-7.22.0/src/modules/plus/filter_chroma.c000664 000000 000000 00000007617 14531534050 020575 0ustar00rootroot000000 000000 /* * filter_chroma.c -- Maps a chroma key to the alpha channel * Copyright (C) 2007-2022 Meltytech, LLC * Author: Charles Yates * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include static inline int in_range(uint8_t v, uint8_t c, int var) { return ((int) v >= c - var) && ((int) v <= c + var); } static inline uint8_t alpha_value(uint8_t a, uint8_t *p, uint8_t u, uint8_t v, int var, int odd) { if (odd == 0) return (in_range(*(p + 1), u, var) && in_range(*(p + 3), v, var)) ? 0 : a; else return (in_range((*(p + 1) + *(p + 5)) / 2, u, var) && in_range((*(p + 3) + *(p + 7)) / 2, v, var)) ? 0 : a; } /** Get the images and map the chroma to the alpha of the frame. */ static int filter_get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { mlt_filter this = mlt_frame_pop_service(frame); mlt_position position = mlt_filter_get_position(this, frame); mlt_position length = mlt_filter_get_length2(this, frame); int variance = 200 * mlt_properties_anim_get_double(MLT_FILTER_PROPERTIES(this), "variance", position, length); mlt_color key_val = mlt_properties_anim_get_color(MLT_FILTER_PROPERTIES(this), "key", position, length); uint8_t u, v; RGB2UV_601_SCALED(key_val.r, key_val.g, key_val.b, u, v); *format = mlt_image_yuv422; if (mlt_frame_get_image(frame, image, format, width, height, writable) == 0) { uint8_t *alpha = mlt_frame_get_alpha(frame); if (!alpha) { int alphasize = *width * *height; alpha = mlt_pool_alloc(alphasize); memset(alpha, 255, alphasize); mlt_frame_set_alpha(frame, alpha, alphasize, mlt_pool_release); } uint8_t *p = *image; int size = *width * *height / 2; while (size--) { *alpha = alpha_value(*alpha, p, u, v, variance, 0); alpha++; *alpha = alpha_value(*alpha, p, u, v, variance, 1); alpha++; p += 4; } } return 0; } /** Filter processing. */ static mlt_frame filter_process(mlt_filter this, mlt_frame frame) { mlt_frame_push_service(frame, this); mlt_frame_push_service(frame, filter_get_image); return frame; } /** Constructor for the filter. */ mlt_filter filter_chroma_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter this = mlt_filter_new(); if (this != NULL) { mlt_properties_set(MLT_FILTER_PROPERTIES(this), "key", arg == NULL ? "#0000ff" : arg); mlt_properties_set_double(MLT_FILTER_PROPERTIES(this), "variance", 0.15); this->process = filter_process; } return this; } mlt-7.22.0/src/modules/plus/filter_chroma.yml000664 000000 000000 00000000674 14531534050 021150 0ustar00rootroot000000 000000 schema_version: 7.0 type: filter identifier: chroma title: Chroma Key version: 1 creator: Charles Yates license: LGPLv2.1 language: en tags: - Video parameters: - identifier: key title: Key Color type: color argument: yes mutable: yes default: #0000ff animation: yes - identifier: variance title: Variance type: float mutable: yes default: 0.15 minimum: 0 maximum: 1.0 animation: yes mlt-7.22.0/src/modules/plus/filter_chroma_hold.c000664 000000 000000 00000007434 14531534050 021600 0ustar00rootroot000000 000000 /* * filter_chroma.c -- Maps a chroma key to the alpha channel * Copyright (C) 2008-2022 Meltytech, LLC * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include static inline int in_range(uint8_t v, uint8_t c, int var) { return ((int) v >= c - var) && ((int) v <= c + var); } static inline uint8_t alpha_value(uint8_t a, uint8_t *p, uint8_t u, uint8_t v, int var, int odd) { if (odd == 0) return (in_range(*(p + 1), u, var) && in_range(*(p + 3), v, var)) ? 0 : a; else return (in_range((*(p + 1) + *(p + 5)) / 2, u, var) && in_range((*(p + 3) + *(p + 7)) / 2, v, var)) ? 0 : a; } /** Get the images and map the chroma to the alpha of the frame. */ static int filter_get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { mlt_filter this = mlt_frame_pop_service(frame); mlt_position position = mlt_filter_get_position(this, frame); mlt_position length = mlt_filter_get_length2(this, frame); int variance = 200 * mlt_properties_anim_get_double(MLT_FILTER_PROPERTIES(this), "variance", position, length); mlt_color key_val = mlt_properties_anim_get_color(MLT_FILTER_PROPERTIES(this), "key", position, length); uint8_t r = key_val.r; uint8_t g = key_val.g; uint8_t b = key_val.b; uint8_t u, v; RGB2UV_601_SCALED(r, g, b, u, v); *format = mlt_image_yuv422; if (mlt_frame_get_image(frame, image, format, width, height, writable) == 0) { uint8_t alpha = 0; uint8_t *p = *image; int size = *width * *height / 2; while (size--) { alpha = alpha_value(255, p, u, v, variance, 0); if (alpha) *(p + 1) = 128; alpha = alpha_value(255, p, u, v, variance, 1); if (alpha) *(p + 3) = 128; p += 4; } } return 0; } /** Filter processing. */ static mlt_frame filter_process(mlt_filter this, mlt_frame frame) { mlt_frame_push_service(frame, this); mlt_frame_push_service(frame, filter_get_image); return frame; } /** Constructor for the filter. */ mlt_filter filter_chroma_hold_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter this = mlt_filter_new(); if (this != NULL) { mlt_properties_set(MLT_FILTER_PROPERTIES(this), "key", arg == NULL ? "#c00000" : arg); mlt_properties_set_double(MLT_FILTER_PROPERTIES(this), "variance", 0.15); this->process = filter_process; } return this; } mlt-7.22.0/src/modules/plus/filter_chroma_hold.yml000664 000000 000000 00000001106 14531534050 022145 0ustar00rootroot000000 000000 schema_version: 7.0 type: filter identifier: chroma_hold title: Chroma Hold version: 1 creator: Charles Yates license: LGPLv2.1 language: en tags: - Video parameters: - identifier: key argument: yes type: color title: Key Color description: The color to keep. All others are desaturated. default: #c00000 mutable: yes animation: yes - identifier: variance title: Variance description: The threshold for colors similar to the key color. type: float default: 0.15 minimum: 0 maximum: 1.0 mutable: yes animation: yes mlt-7.22.0/src/modules/plus/filter_dance.c000664 000000 000000 00000026466 14531534050 020401 0ustar00rootroot000000 000000 /* * filter_dance.c -- animate images size and position to the audio * Copyright (C) 2015 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include // sin() #include // calloc(), free() #include // strdup() // Private Constants static const double PI = 3.14159265358979323846; // Private Types typedef struct { mlt_filter affine; mlt_filter fft; char *mag_prop_name; int rel_pos; double phase; int preprocess_warned; } private_data; static double apply(double positive, double negative, double mag, double max_range) { if (mag == 0.0) { return 0.0; } else if (mag > 0.0 && positive > 0.0) { return positive * mag * max_range; } else if (mag < 0.0 && negative > 0.0) { return negative * mag * max_range; } else if (positive) { return positive * fabs(mag) * max_range; } else if (negative) { return negative * -fabs(mag) * max_range; } return 0.0; } static int filter_get_audio(mlt_frame frame, void **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples) { mlt_filter filter = (mlt_filter) mlt_frame_pop_audio(frame); mlt_properties filter_properties = MLT_FILTER_PROPERTIES(filter); private_data *pdata = (private_data *) filter->child; mlt_profile profile = mlt_service_profile(MLT_FILTER_SERVICE(filter)); // Create the FFT filter the first time. if (!pdata->fft) { pdata->fft = mlt_factory_filter(profile, "fft", NULL); mlt_properties_set_int(MLT_FILTER_PROPERTIES(pdata->fft), "window_size", mlt_properties_get_int(filter_properties, "window_size")); if (!pdata->fft) { mlt_log_warning(MLT_FILTER_SERVICE(filter), "Unable to create FFT.\n"); return 1; } } mlt_properties fft_properties = MLT_FILTER_PROPERTIES(pdata->fft); double low_freq = mlt_properties_get_int(filter_properties, "frequency_low"); double hi_freq = mlt_properties_get_int(filter_properties, "frequency_high"); double threshold = mlt_properties_get_int(filter_properties, "threshold"); double osc = mlt_properties_get_int(filter_properties, "osc"); float peak = 0; // The service must stay locked while using the private data mlt_service_lock(MLT_FILTER_SERVICE(filter)); // Perform FFT processing on the frame mlt_filter_process(pdata->fft, frame); mlt_frame_get_audio(frame, buffer, format, frequency, channels, samples); float *bins = mlt_properties_get_data(fft_properties, "bins", NULL); double window_level = mlt_properties_get_double(fft_properties, "window_level"); if (bins && window_level == 1.0) { // Find the peak FFT magnitude in the configured range of frequencies int bin_count = mlt_properties_get_int(fft_properties, "bin_count"); double bin_width = mlt_properties_get_double(fft_properties, "bin_width"); int bin = 0; for (bin = 0; bin < bin_count; bin++) { double F = bin_width * (double) bin; if (F >= low_freq && F <= hi_freq) { if (bins[bin] > peak) { peak = bins[bin]; } } } } mlt_service_unlock(MLT_FILTER_SERVICE(filter)); // Scale the magnitude to dB and apply oscillation double dB = peak > 0.0 ? 20 * log10(peak) : -1000.0; double mag = 0.0; if (dB >= threshold) { // Scale to range 0.0-1.0 mag = 1 - (dB / threshold); if (osc != 0) { // Apply the oscillation double fps = mlt_profile_fps(profile); double t = pdata->rel_pos / fps; mag = mag * sin(2 * PI * osc * t + pdata->phase); } pdata->rel_pos++; } else { pdata->rel_pos = 1; // Alternate the phase so that the dancing alternates directions to the beat. pdata->phase = pdata->phase ? 0 : PI; mag = 0; } // Save the magnitude as a property on the frame to be used in get_image() mlt_properties_set_double(MLT_FRAME_PROPERTIES(frame), pdata->mag_prop_name, mag); return 0; } /** Get the image. */ static int filter_get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { int error = 0; mlt_filter filter = (mlt_filter) mlt_frame_pop_service(frame); private_data *pdata = (private_data *) filter->child; mlt_properties filter_properties = MLT_FILTER_PROPERTIES(filter); mlt_properties frame_properties = MLT_FRAME_PROPERTIES(frame); if (mlt_properties_exists(frame_properties, pdata->mag_prop_name)) { double mag = mlt_properties_get_double(frame_properties, pdata->mag_prop_name); mlt_profile profile = mlt_service_profile(MLT_FILTER_SERVICE(filter)); // scale_x and scale_y are in the range 0.0 to x.0 with: // 0.0 = the largest possible // < 1.0 = increase size (zoom in) // 1.0 = no scaling // > 1.0 = decrease size (zoom out) double initial_zoom = mlt_properties_get_double(filter_properties, "initial_zoom"); double zoom = mlt_properties_get_double(filter_properties, "zoom"); double scale_xy = (100.0 / initial_zoom) - (fabs(mag) * (zoom / 100.0)); if (scale_xy < 0.1) scale_xy = 0.1; // ox is in the range -width to +width with: // > 0 = offset to the left // 0 = no offset // < 0 = offset to the right double left = mlt_properties_get_double(filter_properties, "left"); double right = mlt_properties_get_double(filter_properties, "right"); double ox = apply(left, right, mag, (double) profile->width / 100.0); // oy is in the range -height to +height with: // > 0 = offset up // 0 = no offset // < 0 = offset down double up = mlt_properties_get_double(filter_properties, "up"); double down = mlt_properties_get_double(filter_properties, "down"); double oy = apply(up, down, mag, (double) profile->height / 100.0); // fix_rotate_x is in the range -360 to +360 with: // > 0 = rotate clockwise // 0 = no rotation // < 0 = rotate anticlockwise double counterclockwise = mlt_properties_get_double(filter_properties, "counterclockwise"); double clockwise = mlt_properties_get_double(filter_properties, "clockwise"); double fix_rotate_x = apply(clockwise, counterclockwise, mag, 1.0); // Perform the affine. mlt_service_lock(MLT_FILTER_SERVICE(filter)); mlt_properties affine_properties = MLT_FILTER_PROPERTIES(pdata->affine); mlt_properties_set_double(affine_properties, "transition.scale_x", scale_xy); mlt_properties_set_double(affine_properties, "transition.scale_y", scale_xy); mlt_properties_set_double(affine_properties, "transition.ox", ox); mlt_properties_set_double(affine_properties, "transition.oy", oy); mlt_properties_set_double(affine_properties, "transition.fix_rotate_x", fix_rotate_x); mlt_filter_process(pdata->affine, frame); error = mlt_frame_get_image(frame, image, format, width, height, 0); mlt_service_unlock(MLT_FILTER_SERVICE(filter)); } else { if (pdata->preprocess_warned++ == 2) { // This filter depends on the consumer processing the audio before the // video. mlt_log_warning(MLT_FILTER_SERVICE(filter), "Audio not preprocessed. Unable to dance.\n"); } mlt_frame_get_image(frame, image, format, width, height, 0); } return error; } /** Filter processing. */ static mlt_frame filter_process(mlt_filter filter, mlt_frame frame) { mlt_frame_push_audio(frame, filter); mlt_frame_push_audio(frame, filter_get_audio); mlt_frame_push_service(frame, filter); mlt_frame_push_get_image(frame, filter_get_image); return frame; } static void filter_close(mlt_filter filter) { private_data *pdata = (private_data *) filter->child; if (pdata) { mlt_filter_close(pdata->affine); mlt_filter_close(pdata->fft); free(pdata->mag_prop_name); free(pdata); } filter->child = NULL; filter->close = NULL; filter->parent.close = NULL; mlt_service_close(&filter->parent); } /** Constructor for the filter. */ mlt_filter filter_dance_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = mlt_filter_new(); private_data *pdata = (private_data *) calloc(1, sizeof(private_data)); mlt_filter affine_filter = mlt_factory_filter(profile, "affine", "colour:0x00000000"); if (filter && pdata && affine_filter) { mlt_properties properties = MLT_FILTER_PROPERTIES(filter); mlt_properties_set_int(properties, "_filter_private", 1); mlt_properties_set_int(properties, "frequency_low", 20); mlt_properties_set_int(properties, "frequency_high", 20000); mlt_properties_set_double(properties, "threshold", -30.0); mlt_properties_set_double(properties, "osc", 5.0); mlt_properties_set_double(properties, "initial_zoom", 100.0); mlt_properties_set_double(properties, "zoom", 0.0); mlt_properties_set_double(properties, "left", 0.0); mlt_properties_set_double(properties, "right", 0.0); mlt_properties_set_double(properties, "up", 0.0); mlt_properties_set_double(properties, "down", 0.0); mlt_properties_set_double(properties, "clockwise", 0.0); mlt_properties_set_double(properties, "counterclockwise", 0.0); mlt_properties_set_int(properties, "window_size", 2048); // Create a unique ID for storing data on the frame pdata->mag_prop_name = calloc(1, 20); snprintf(pdata->mag_prop_name, 20, "fft_mag.%p", filter); pdata->mag_prop_name[20 - 1] = '\0'; pdata->affine = affine_filter; pdata->fft = 0; filter->close = filter_close; filter->process = filter_process; filter->child = pdata; } else { mlt_log_error(MLT_FILTER_SERVICE(filter), "Filter dance failed\n"); if (filter) { mlt_filter_close(filter); } if (affine_filter) { mlt_filter_close(affine_filter); } if (pdata) { free(pdata); } filter = NULL; } return filter; } mlt-7.22.0/src/modules/plus/filter_dance.yml000664 000000 000000 00000011366 14531534050 020751 0ustar00rootroot000000 000000 schema_version: 0.3 type: filter identifier: dance title: Dance version: 1 copyright: Meltytech, LLC license: LGPLv2.1 language: en tags: - Video description: > An audio visualization filter that moves the image around proportional to the magnitude of the audio spectrum. parameters: - identifier: frequency_low title: Low Frequency type: integer description: > The low end of the frequency range to be used to influence the image motion. mutable: yes readonly: no default: 20 unit: Hz - identifier: frequency_high title: High Frequency type: integer description: > The high end of the frequency range to be used to influence the image motion. mutable: yes readonly: no default: 20000 unit: Hz - identifier: threshold title: Level Threshold type: float description: > The minimum amplitude of sound that must occur within the frequency range to cause the image to move. motion. mutable: yes readonly: no default: -30 minimum: -100 maximum: 0 unit: dB - identifier: osc title: Oscillation type: float description: > Oscillation can be useful to make the image move back and forth during long periods of sound. A value of 0 specifies no oscillation. mutable: yes readonly: no default: 5 minimum: 0 unit: Hz - identifier: initial_zoom title: Initial Zoom type: float description: | The amount to zoom the image before any motion occurs. This can be used to avoid black on the sides of the image when it moves. 100% = no zoom < 100% = zoom out (make the image smaller) > 100% = zoom in (make the image larger) mutable: yes readonly: no default: 100 minimum: 0 maximum: 5000 unit: '%' - identifier: zoom title: Zoom type: float description: | The amount that the audio affects the zoom of the image. < 0% = Image will zoom out (get smaller) with more sound 0% = no zoom > 0% = Image will zoom in (get larger) with more sound mutable: yes readonly: no default: 0 minimum: -100 maximum: 100 unit: '%' - identifier: left title: Left type: float description: | The amount that the audio affects the left offset of the image. 0% = no left offset > 0% = Image will move left with more sound mutable: yes readonly: no default: 0 minimum: 0 maximum: 100 unit: '%' - identifier: right title: Right type: float description: | The amount that the audio affects the right offset of the image. 0% = no right offset > 0% = Image will move right with more sound mutable: yes readonly: no default: 0 minimum: 0 maximum: 100 unit: '%' - identifier: up title: Up type: float description: | The amount that the audio affects the upward offset of the image. 0% = no upward offset > 0% = Image will move up with more sound mutable: yes readonly: no default: 0 minimum: 0 maximum: 100 unit: '%' - identifier: down title: Down type: float description: | The amount that the audio affects the downward offset of the image. 0% = no downward offset > 0% = Image will move down with more sound mutable: yes readonly: no default: 0 minimum: 0 maximum: 100 unit: '%' - identifier: clockwise title: Clockwise type: float description: | The amount that the audio affects the clockwise rotation of the image. 0% = no clockwise rotation > 0% = Image will rotate clockwise with more sound mutable: yes readonly: no default: 0 minimum: 0 maximum: 360 unit: degrees - identifier: counterclockwise title: Counterclockwise type: float description: | The amount that the audio affects the counterclockwise rotation of the image. 0% = no counterclockwise rotation > 0% = Image will rotate counterclockwise with more sound mutable: yes readonly: no default: 0 minimum: 0 maximum: 360 unit: degrees - identifier: window_size title: Window Size type: integer description: > The number of samples that the FFT will be performed on. If window_size is less than the number of samples in a frame, extra samples will be ignored. If window_size is more than the number of samples in a frame, samples will be buffered from previous frames to fill the window. The buffering is performed as a sliding window so that the most recent samples are always transformed. mutable: no readonly: no default: 2048 mlt-7.22.0/src/modules/plus/filter_dynamic_loudness.c000664 000000 000000 00000021260 14531534050 022652 0ustar00rootroot000000 000000 /* * filter_loudness.c -- normalize audio according to EBU R128 * Copyright (C) 2014 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include typedef struct { ebur128_state *r128; double target_gain; double start_gain; double end_gain; int reset; unsigned int time_elapsed_ms; mlt_position prev_o_pos; } private_data; static void property_changed(mlt_service owner, mlt_filter filter, mlt_event_data event_data) { const char *name = mlt_event_data_to_string(event_data); private_data *pdata = (private_data *) filter->child; if (name && pdata && !strcmp(name, "window")) { pdata->reset = 1; } } static void check_for_reset(mlt_filter filter, int channels, int frequency) { mlt_properties properties = MLT_FILTER_PROPERTIES(filter); private_data *pdata = (private_data *) filter->child; if (pdata->reset) { if (pdata->r128) { ebur128_destroy(&pdata->r128); } pdata->r128 = 0; pdata->target_gain = 0.0; pdata->start_gain = 0.0; pdata->end_gain = 0.0; pdata->reset = 0; pdata->time_elapsed_ms = 0; pdata->prev_o_pos = -1; mlt_properties_set_double(properties, "out_gain", 0.0); mlt_properties_set_double(properties, "in_loudness", -100.0); mlt_properties_set_int(properties, "reset_count", mlt_properties_get_int(properties, "reset_count") + 1); } if (!pdata->r128) { pdata->r128 = ebur128_init(channels, frequency, EBUR128_MODE_I); ebur128_set_max_window(pdata->r128, 400); ebur128_set_max_history(pdata->r128, mlt_properties_get_int(properties, "window") * 1000.0); } } static void analyze_audio(mlt_filter filter, void *buffer, int samples, int frequency) { mlt_properties properties = MLT_FILTER_PROPERTIES(filter); private_data *pdata = (private_data *) filter->child; int result = -1; double in_loudness = 0.0; mlt_profile profile = mlt_service_profile(MLT_FILTER_SERVICE(filter)); double fps = mlt_profile_fps(profile); ebur128_add_frames_float(pdata->r128, buffer, samples); if (pdata->time_elapsed_ms < 400) { // Waiting for first program loudness measurement. // Use window loudness as initial guess. result = ebur128_loudness_window(pdata->r128, pdata->time_elapsed_ms, &in_loudness); pdata->time_elapsed_ms += samples * 1000 / frequency; } else { result = ebur128_loudness_global(pdata->r128, &in_loudness); } if (result == EBUR128_SUCCESS && in_loudness != HUGE_VAL && in_loudness != -HUGE_VAL) { mlt_properties_set_double(properties, "in_loudness", in_loudness); double target_loudness = mlt_properties_get_double(properties, "target_loudness"); pdata->target_gain = target_loudness - in_loudness; // Make sure gain limits are not exceeded. double max_gain = mlt_properties_get_double(properties, "max_gain"); double min_gain = mlt_properties_get_double(properties, "min_gain"); if (pdata->target_gain > max_gain) { pdata->target_gain = max_gain; } else if (pdata->target_gain < min_gain) { pdata->target_gain = min_gain; } } // Make sure gain does not change too quickly. pdata->start_gain = pdata->end_gain; pdata->end_gain = pdata->target_gain; double max_frame_gain = mlt_properties_get_double(properties, "max_rate") / fps; if (pdata->start_gain - pdata->end_gain > max_frame_gain) { pdata->end_gain = pdata->start_gain - max_frame_gain; } else if (pdata->end_gain - pdata->start_gain > max_frame_gain) { pdata->end_gain = pdata->start_gain + max_frame_gain; } mlt_properties_set_double(properties, "out_gain", pdata->end_gain); } static int filter_get_audio(mlt_frame frame, void **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples) { mlt_filter filter = mlt_frame_pop_audio(frame); private_data *pdata = (private_data *) filter->child; mlt_position o_pos = mlt_frame_original_position(frame); // Get the producer's audio *format = mlt_audio_f32le; mlt_frame_get_audio(frame, buffer, format, frequency, channels, samples); mlt_service_lock(MLT_FILTER_SERVICE(filter)); if (mlt_properties_get_int(MLT_FILTER_PROPERTIES(filter), "discontinuity_reset") && abs(o_pos - pdata->prev_o_pos) > 1) { // Assume this is a new clip and restart // Use original position so that transitions between clips are detected. pdata->reset = 1; mlt_log_info(MLT_FILTER_SERVICE(filter), "Reset. Old Pos: %d\tNew Pos: %d\n", pdata->prev_o_pos, o_pos); } check_for_reset(filter, *channels, *frequency); if (o_pos != pdata->prev_o_pos) { // Only analyze the audio is the producer is not paused. analyze_audio(filter, *buffer, *samples, *frequency); } double start_coeff = pdata->start_gain > -90.0 ? pow(10.0, pdata->start_gain / 20.0) : 0.0; double end_coeff = pdata->end_gain > -90.0 ? pow(10.0, pdata->end_gain / 20.0) : 0.0; double coeff_factor = pow((end_coeff / start_coeff), 1.0 / (double) *samples); double coeff = start_coeff; float *p = *buffer; int s = 0; int c = 0; for (s = 0; s < *samples; s++) { coeff = coeff * coeff_factor; for (c = 0; c < *channels; c++) { *p = *p * coeff; p++; } } pdata->prev_o_pos = o_pos; mlt_service_unlock(MLT_FILTER_SERVICE(filter)); return 0; } /** Filter processing. */ static mlt_frame filter_process(mlt_filter filter, mlt_frame frame) { mlt_frame_push_audio(frame, filter); mlt_frame_push_audio(frame, filter_get_audio); return frame; } /** Destructor for the filter. */ static void filter_close(mlt_filter filter) { private_data *pdata = (private_data *) filter->child; if (pdata) { if (pdata->r128) { ebur128_destroy(&pdata->r128); } free(pdata); } filter->child = NULL; filter->close = NULL; filter->parent.close = NULL; mlt_service_close(&filter->parent); } /** Constructor for the filter. */ mlt_filter filter_dynamic_loudness_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = mlt_filter_new(); private_data *pdata = (private_data *) calloc(1, sizeof(private_data)); if (filter && pdata) { mlt_properties properties = MLT_FILTER_PROPERTIES(filter); mlt_properties_set(properties, "target_loudness", "-23.0"); mlt_properties_set(properties, "window", "3.0"); mlt_properties_set(properties, "max_gain", "15.0"); mlt_properties_set(properties, "min_gain", "-15.0"); mlt_properties_set(properties, "max_rate", "3.0"); mlt_properties_set(properties, "discontinuity_reset", "1"); mlt_properties_set(properties, "in_loudness", "-100.0"); mlt_properties_set(properties, "out_gain", "0.0"); mlt_properties_set(properties, "reset_count", "0"); pdata->target_gain = 0.0; pdata->start_gain = 0.0; pdata->end_gain = 0.0; pdata->r128 = 0; pdata->reset = 1; pdata->time_elapsed_ms = 0; pdata->prev_o_pos = 0; filter->close = filter_close; filter->process = filter_process; filter->child = pdata; mlt_events_listen(properties, filter, "property-changed", (mlt_listener) property_changed); } else { if (filter) { mlt_filter_close(filter); filter = NULL; } free(pdata); } return filter; } mlt-7.22.0/src/modules/plus/filter_dynamic_loudness.yml000664 000000 000000 00000005233 14531534050 023233 0ustar00rootroot000000 000000 schema_version: 0.3 type: filter identifier: dynamic_loudness title: Dynamic Loudness version: 1 copyright: Meltytech, LLC license: LGPLv2.1 language: en tags: - Audio description: Dynamically correct audio loudness as recommended by EBU R128. notes: > This filter adjusts the level of the audio based on the loudness of the input. It performs loudness measurement over a specified sliding window of time. Then, it adjusts the gain on the output based on the difference between the measured loudness and the target loudness in order to achieve the desired loudness. parameters: - identifier: target_loudness title: Target Program Loudness type: float description: > The target program loudness in LUFS (Loudness Units Full Scale). readonly: no mutable: yes default: -23.0 minimum: -50.0 maximum: -10.0 unit: LUFS - identifier: window title: Measurement Window type: float description: > The duration of time in seconds over which the loudness is calculated. readonly: no mutable: yes default: 3.0 minimum: 1 maximum: 100000 unit: seconds - identifier: max_gain title: Maximum Gain Increase type: float description: > The maximum amount that the gain will be increased by the filter. readonly: no mutable: yes default: 15 minimum: 0 maximum: 30 unit: dB - identifier: min_gain title: Maximum Gain Decrease type: float description: > The maximum amount that the gain will be decreased by the filter. readonly: no mutable: yes default: -15 minimum: 0 maximum: -30 unit: dB - identifier: discontinuity_reset title: Reset on Discontinuity type: boolean description: > Reset the measurement if a discontinuity occurs like seeking or new clip is detected. Useful for playlists and tracks. default: 1 - identifier: in_loudness title: Input Program Loudness type: float description: > The program loudness measured on the input over the duration of the window. readonly: yes unit: LUFS - identifier: out_gain title: Output Gain type: float description: > The amount of gain applied to the last frame. Updated with each new frame. readonly: yes unit: dB - identifier: reset_count title: Reset Count type: integer description: > The number of times the filter has reset the loudness measurement. The measurement is reset whenever the filter detects a discontinuity in the frame sequence. It also resets when it detects that the producer has changed if clip_reset is set. readonly: yes mlt-7.22.0/src/modules/plus/filter_dynamictext.c000664 000000 000000 00000030443 14531534050 021646 0ustar00rootroot000000 000000 /* * filter_dynamictext.c -- dynamic text overlay filter * Copyright (C) 2011-2020 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include // for basename() #include #include #include #include // for stat() #include // for stat() #include // for strftime() and gtime() #include // for stat() #define MAX_TEXT_LEN 512 /** Get the next token and indicate whether it is enclosed in "# #". */ static int get_next_token(char *str, int *pos, char *token, int *is_keyword) { int token_pos = 0; int str_len = strlen(str); if ((*pos) >= str_len || str[*pos] == '\0') { return 0; } if (str[*pos] == '#') { *is_keyword = 1; (*pos)++; } else { *is_keyword = 0; } while (*pos < str_len && token_pos < MAX_TEXT_LEN - 1) { if (str[*pos] == '\\' && str[(*pos) + 1] == '#') { // Escape Sequence - "#" preceded by "\" - copy the # into the token. token[token_pos] = '#'; token_pos++; (*pos)++; // skip "\" (*pos)++; // skip "#" } else if (str[*pos] == '#') { if (*is_keyword) { // Found the end of the keyword (*pos)++; } break; } else { token[token_pos] = str[*pos]; token_pos++; (*pos)++; } } token[token_pos] = '\0'; return 1; } static void get_timecode_str(mlt_filter filter, mlt_frame frame, char *text, mlt_time_format time_format) { mlt_position frames = mlt_frame_get_position(frame); mlt_properties properties = MLT_FILTER_PROPERTIES(filter); char *s = mlt_properties_frames_to_time(properties, frames, time_format); if (s) strncat(text, s, MAX_TEXT_LEN - strlen(text) - 1); } static void get_frame_str(mlt_filter filter, mlt_frame frame, char *text) { int pos = mlt_frame_get_position(frame); char s[12]; snprintf(s, sizeof(s) - 1, "%d", pos); strncat(text, s, MAX_TEXT_LEN - strlen(text) - 1); } static void get_filedate_str(const char *keyword, mlt_filter filter, mlt_frame frame, char *text) { mlt_producer producer = mlt_producer_cut_parent(mlt_frame_get_original_producer(frame)); mlt_properties producer_properties = MLT_PRODUCER_PROPERTIES(producer); char *filename = mlt_properties_get(producer_properties, "resource"); struct stat file_info; if (!stat(filename, &file_info)) { const char *format = "%Y/%m/%d"; int n = strlen("filedate") + 1; struct tm *time_info = gmtime(&(file_info.st_mtime)); char *date = calloc(1, MAX_TEXT_LEN); if (strlen(keyword) > n) format = &keyword[n]; strftime(date, MAX_TEXT_LEN, format, time_info); strncat(text, date, MAX_TEXT_LEN - strlen(text) - 1); free(date); } } static void get_localfiledate_str(const char *keyword, mlt_filter filter, mlt_frame frame, char *text) { mlt_producer producer = mlt_producer_cut_parent(mlt_frame_get_original_producer(frame)); mlt_properties producer_properties = MLT_PRODUCER_PROPERTIES(producer); char *filename = mlt_properties_get(producer_properties, "resource"); struct stat file_info; if (!stat(filename, &file_info)) { const char *format = "%Y/%m/%d"; int n = strlen("localfiledate") + 1; struct tm *time_info = localtime(&(file_info.st_mtime)); char *date = calloc(1, MAX_TEXT_LEN); if (strlen(keyword) > n) format = &keyword[n]; strftime(date, MAX_TEXT_LEN, format, time_info); strncat(text, date, MAX_TEXT_LEN - strlen(text) - 1); free(date); } } static void get_localtime_str(const char *keyword, char *text) { const char *format = "%Y/%m/%d %H:%M:%S"; int n = strlen("localtime") + 1; time_t now = time(NULL); struct tm *time_info = localtime(&now); char *date = calloc(1, MAX_TEXT_LEN); if (strlen(keyword) > n) format = &keyword[n]; strftime(date, MAX_TEXT_LEN, format, time_info); strncat(text, date, MAX_TEXT_LEN - strlen(text) - 1); free(date); } static void get_resource_str(mlt_filter filter, mlt_frame frame, char *text) { mlt_producer producer = mlt_producer_cut_parent(mlt_frame_get_original_producer(frame)); mlt_properties producer_properties = MLT_PRODUCER_PROPERTIES(producer); strncat(text, mlt_properties_get(producer_properties, "resource"), MAX_TEXT_LEN - strlen(text) - 1); } static void get_filename_str(mlt_filter filter, mlt_frame frame, char *text) { mlt_producer producer = mlt_producer_cut_parent(mlt_frame_get_original_producer(frame)); mlt_properties producer_properties = MLT_PRODUCER_PROPERTIES(producer); char *filename = mlt_properties_get(producer_properties, "resource"); if (access(filename, F_OK) == 0) { strncat(text, basename(filename), MAX_TEXT_LEN - strlen(text) - 1); } } static void get_basename_str(mlt_filter filter, mlt_frame frame, char *text) { mlt_producer producer = mlt_producer_cut_parent(mlt_frame_get_original_producer(frame)); mlt_properties producer_properties = MLT_PRODUCER_PROPERTIES(producer); char *filename = strdup(mlt_properties_get(producer_properties, "resource")); if (access(filename, F_OK) == 0) { char *bname = basename(filename); char *ext = strrchr(bname, '.'); if (ext) { *ext = '\0'; } strncat(text, bname, MAX_TEXT_LEN - strlen(text) - 1); } free(filename); } static void get_createdate_str(const char *keyword, mlt_filter filter, mlt_frame frame, char *text) { time_t creation_date = (time_t) (mlt_producer_get_creation_time(mlt_frame_get_original_producer(frame)) / 1000); const char *format = "%Y/%m/%d"; int n = strlen("createdate") + 1; if (strlen(keyword) > n) format = &keyword[n]; int text_length = strlen(text); strftime(text + text_length, MAX_TEXT_LEN - text_length - 1, format, localtime(&creation_date)); } /** Perform substitution for keywords that are enclosed in "# #". */ static void substitute_keywords(mlt_filter filter, char *result, char *value, mlt_frame frame) { char keyword[MAX_TEXT_LEN] = ""; int pos = 0; int is_keyword = 0; while (get_next_token(value, &pos, keyword, &is_keyword)) { if (!is_keyword) { strncat(result, keyword, MAX_TEXT_LEN - strlen(result) - 1); } else if (!strcmp(keyword, "timecode") || !strcmp(keyword, "smpte_df")) { get_timecode_str(filter, frame, result, mlt_time_smpte_df); } else if (!strcmp(keyword, "smpte_ndf")) { get_timecode_str(filter, frame, result, mlt_time_smpte_ndf); } else if (!strcmp(keyword, "frame")) { get_frame_str(filter, frame, result); } else if (!strncmp(keyword, "filedate", 8)) { get_filedate_str(keyword, filter, frame, result); } else if (!strncmp(keyword, "localfiledate", 13)) { get_localfiledate_str(keyword, filter, frame, result); } else if (!strncmp(keyword, "localtime", 9)) { get_localtime_str(keyword, result); } else if (!strcmp(keyword, "resource")) { get_resource_str(filter, frame, result); } else if (!strcmp(keyword, "filename")) { get_filename_str(filter, frame, result); } else if (!strcmp(keyword, "basename")) { get_basename_str(filter, frame, result); } else if (!strncmp(keyword, "createdate", 10)) { get_createdate_str(keyword, filter, frame, result); } else { // replace keyword with property value from this frame mlt_properties frame_properties = MLT_FRAME_PROPERTIES(frame); char *frame_value = mlt_properties_get(frame_properties, keyword); if (frame_value) { strncat(result, frame_value, MAX_TEXT_LEN - strlen(result) - 1); } } } } /** Filter processing. */ static mlt_frame filter_process(mlt_filter filter, mlt_frame frame) { mlt_properties properties = MLT_FILTER_PROPERTIES(filter); char *dynamic_text = mlt_properties_get(properties, "argument"); if (!dynamic_text || !strcmp("", dynamic_text)) return frame; mlt_filter text_filter = mlt_properties_get_data(properties, "_text_filter", NULL); mlt_properties text_filter_properties = mlt_frame_unique_properties(frame, MLT_FILTER_SERVICE(text_filter)); // Apply keyword substitution before passing the text to the filter. char *result = calloc(1, MAX_TEXT_LEN); substitute_keywords(filter, result, dynamic_text, frame); mlt_properties_set_string(text_filter_properties, "argument", result); free(result); mlt_properties_pass_list(text_filter_properties, properties, "geometry family size weight style fgcolour bgcolour olcolour pad " "halign valign outline opacity"); mlt_filter_set_in_and_out(text_filter, mlt_filter_get_in(filter), mlt_filter_get_out(filter)); return mlt_filter_process(text_filter, frame); } /** Constructor for the filter. */ mlt_filter filter_dynamictext_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = mlt_filter_new(); mlt_filter text_filter = mlt_factory_filter(profile, "qtext", NULL); if (!text_filter) text_filter = mlt_factory_filter(profile, "text", NULL); if (!text_filter) mlt_log_warning(MLT_FILTER_SERVICE(filter), "Unable to create text filter.\n"); if (filter && text_filter) { mlt_properties my_properties = MLT_FILTER_PROPERTIES(filter); // Register the text filter for reuse/destruction mlt_properties_set_data(my_properties, "_text_filter", text_filter, 0, (mlt_destructor) mlt_filter_close, NULL); // Assign default values mlt_properties_set_string(my_properties, "argument", arg ? arg : "#timecode#"); mlt_properties_set_string(my_properties, "geometry", "0%/0%:100%x100%:100%"); mlt_properties_set_string(my_properties, "family", "Sans"); mlt_properties_set_string(my_properties, "size", "48"); mlt_properties_set_string(my_properties, "weight", "400"); mlt_properties_set_string(my_properties, "style", "normal"); mlt_properties_set_string(my_properties, "fgcolour", "0x000000ff"); mlt_properties_set_string(my_properties, "bgcolour", "0x00000020"); mlt_properties_set_string(my_properties, "olcolour", "0x00000000"); mlt_properties_set_string(my_properties, "pad", "0"); mlt_properties_set_string(my_properties, "halign", "left"); mlt_properties_set_string(my_properties, "valign", "top"); mlt_properties_set_string(my_properties, "outline", "0"); mlt_properties_set_string(my_properties, "opacity", "1.0"); mlt_properties_set_int(my_properties, "_filter_private", 1); filter->process = filter_process; } else { if (filter) { mlt_filter_close(filter); } if (text_filter) { mlt_filter_close(text_filter); } filter = NULL; } return filter; } mlt-7.22.0/src/modules/plus/filter_dynamictext.yml000664 000000 000000 00000011770 14531534050 022227 0ustar00rootroot000000 000000 schema_version: 7.0 type: filter identifier: dynamictext title: Dynamic text version: 2 copyright: Meltytech, LLC license: LGPLv2.1 language: en tags: - Video description: Overlay dynamic text onto the video notes: > The dynamic text filter will search for keywords in the text to be overlaid and will replace those keywords on a frame-by-frame basis. parameters: - identifier: argument argument: yes title: Dynamic text type: string description: | The text to overlay. May include keywords enclosed in "#". Keywords include: * #createdate# - Best guess of file creation date * #smpte_df# - SMPTE drop-frame timecode of the frame * #smpte_ndf# - SMPTE non-drop-frame timecode of the frame * #timecode# - same as #smpte_df# * #frame# - frame number of the frame * #filedate# - modification date of the file (GMT) * #localfiledate# - modification date of the file (local) * #localtime# - current system date and time * #resource# - resource of the producer that produced the frame Timecode keywords are based on the framerate and position of the frame. Time-based keywords can include a strftime format string to customize the output as long as you put some delimiter except "#" between the keyword and the format string and the keyword comes first. For example, "#localtime %I:%M:%S %p#" shows only the time in 12-hour format. Keywords may also be any frame property (e.g. #meta.media.0.codec.frame_rate#) The # may be escaped with "\". required: yes readonly: no default: > #trick to escape "#" character #timecode# widget: text - identifier: geometry title: Geometry type: rect description: A set of X/Y coordinates by which to adjust the text. default: 0%/0%:100%x100%:100 - identifier: family title: Font family type: string description: > The typeface of the font. default: Sans readonly: no mutable: yes widget: combo - identifier: size title: Font size type: integer description: > The size in pixels of the font. default: 48 readonly: no mutable: yes widget: spinner - identifier: style title: Font style type: string description: > The style of the font. values: - normal - italic default: normal readonly: no mutable: yes widget: combo - identifier: weight title: Font weight type: integer description: The weight of the font. minimum: 100 maximum: 1000 default: 400 readonly: no mutable: yes widget: spinner - identifier: fgcolour title: Foreground color type: string description: > A color value is a hexadecimal representation of RGB plus alpha channel as 0xrrggbbaa. Colors can also be the words: white, black, red, green, or blue. You can also use a HTML-style color values #rrggbb or #aarrggbb. default: 0x000000ff readonly: no mutable: yes widget: color - identifier: bgcolour title: Background color type: string description: > A color value is a hexadecimal representation of RGB plus alpha channel as 0xrrggbbaa. Colors can also be the words: white, black, red, green, or blue. You can also use a HTML-style color values #rrggbb or #aarrggbb. default: 0x00000020 readonly: no mutable: yes widget: color - identifier: olcolour title: Outline color type: string description: > A color value is a hexadecimal representation of RGB plus alpha channel as 0xrrggbbaa. Colors can also be the words: white, black, red, green, or blue. You can also use a HTML-style color values #rrggbb or #aarrggbb. readonly: no mutable: yes widget: color - identifier: outline title: Outline Width type: string description: > The width of the outline in pixels. readonly: no default: 0 minimum: 0 maximum: 3 mutable: yes widget: spinner - identifier: pad title: Padding type: integer description: > The number of pixels to pad the background rectangle beyond edges of text. readonly: no default: 0 mutable: yes widget: spinner - identifier: halign title: Horizontal alignment description: > Set the horizontal alignment within the geometry rectangle. type: string default: left values: - left - centre - right mutable: yes widget: combo - identifier: valign title: Vertical alignment description: > Set the vertical alignment within the geometry rectangle. type: string default: top values: - top - middle - bottom mutable: yes widget: combo - identifier: opacity title: Opacity type: float description: Opacity of all elements - text, outline, and background readonly: no default: 1.0 minimum: 0 maximum: 1.0 mutable: yes widget: slider mlt-7.22.0/src/modules/plus/filter_fft.c000664 000000 000000 00000024360 14531534050 020075 0ustar00rootroot000000 000000 /* * filter_fft.c -- perform fft on audio * Copyright (C) 2015 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include // sqrt() #include // calloc(), free() #include // memset(), memmove() // Private Constants static const float MAX_S16_AMPLITUDE = 32768.0; static const int MIN_WINDOW_SIZE = 500; static const double PI = 3.14159265358979323846; // Private Types typedef struct { int initialized; unsigned int window_size; double *fft_in; fftw_complex *fft_out; fftw_plan fft_plan; int bin_count; int sample_buff_count; float *sample_buff; float *hann; float *out_bins; mlt_position expected_pos; } private_data; static int initFft(mlt_filter filter) { int error = 0; private_data *private = (private_data *) filter->child; mlt_properties filter_properties = MLT_FILTER_PROPERTIES(filter); if (private->window_size < MIN_WINDOW_SIZE) { private->window_size = mlt_properties_get_int(filter_properties, "window_size"); if (private->window_size >= MIN_WINDOW_SIZE) { private->initialized = 1; private->bin_count = private->window_size / 2 + 1; private->sample_buff_count = 0; private->out_bins = mlt_pool_alloc(private->bin_count * sizeof(*private->out_bins)); // Initialize sample buffer private->sample_buff = mlt_pool_alloc(private->window_size * sizeof(*private->sample_buff)); memset(private->sample_buff, 0, sizeof(*private->sample_buff) * private->window_size); // Initialize fftw variables private->fft_in = fftw_alloc_real(private->window_size); private->fft_out = fftw_alloc_complex(private->bin_count); private->fft_plan = fftw_plan_dft_r2c_1d(private->window_size, private->fft_in, private->fft_out, FFTW_ESTIMATE); // Initialize the hanning window function private->hann = mlt_pool_alloc(private->window_size * sizeof(*private->hann)); int i = 0; for (i = 0; i < private->window_size; i++) { private->hann[i] = 0.5 * (1 - cos(2 * PI * i / private->window_size)); } mlt_properties_set_int(filter_properties, "bin_count", private->bin_count); mlt_properties_set_data(filter_properties, "bins", private->out_bins, 0, 0, 0); } if (private->window_size < MIN_WINDOW_SIZE || !private->fft_in || !private->fft_out || !private->fft_plan) { mlt_log_error(MLT_FILTER_SERVICE(filter), "Unable to initialize FFT\n"); error = 1; private->window_size = 0; } } return error; } static int filter_get_audio(mlt_frame frame, void **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples) { mlt_filter filter = (mlt_filter) mlt_frame_pop_audio(frame); mlt_properties filter_properties = MLT_FILTER_PROPERTIES(filter); private_data *private = (private_data *) filter->child; int c = 0; int s = 0; // Sanity if (*format != mlt_audio_s16 && *format != mlt_audio_float) { *format = mlt_audio_float; } // Get the audio mlt_frame_get_audio(frame, buffer, format, frequency, channels, samples); // The service must stay locked while using the private FFT data mlt_service_lock(MLT_FILTER_SERVICE(filter)); if (!private->initialized) { private->expected_pos = mlt_frame_get_position(frame); } if (!initFft(filter)) { if (private->expected_pos != mlt_frame_get_position(frame)) { // Reset the sample buffer when seeking occurs. memset(private->sample_buff, 0, sizeof(*private->sample_buff) * private->window_size); private->sample_buff_count = 0; mlt_log_info(MLT_FILTER_SERVICE(filter), "Buffer Reset %d:%d\n", private->expected_pos, mlt_frame_get_position(frame)); private->expected_pos = mlt_frame_get_position(frame); } int new_samples = 0; int old_samples = 0; if (*samples >= private->window_size) { // Ignore samples that don't fit in the window new_samples = private->window_size; old_samples = 0; } else { new_samples = *samples; // Shift the previous samples (discarding oldest samples) old_samples = private->window_size - new_samples; memmove(private->sample_buff, private->sample_buff + new_samples, sizeof(*private->sample_buff) * old_samples); } // Zero out the space for the new samples memset(private->sample_buff + old_samples, 0, sizeof(*private->sample_buff) * new_samples); // Copy the new samples into the sample buffer if (*format == mlt_audio_s16) { int16_t *aud = (int16_t *) *buffer; // For each sample, add all channels for (c = 0; c < *channels; c++) { for (s = 0; s < new_samples; s++) { double sample = aud[s * *channels + c]; // Scale to +/-1 sample /= MAX_S16_AMPLITUDE; sample /= (double) *channels; private->sample_buff[old_samples + s] += sample; } } } else if (*format == mlt_audio_float) { float *aud = (float *) *buffer; // For each sample, add all channels for (c = 0; c < *channels; c++) { for (s = 0; s < new_samples; s++) { double sample = aud[c * *samples + s]; sample /= (double) *channels; private->sample_buff[old_samples + s] += sample; } } } else { mlt_log_error(MLT_FILTER_SERVICE(filter), "Unsupported format %d\n", *format); } private->sample_buff_count += *samples; if (private->sample_buff_count > private->window_size) { private->sample_buff_count = private->window_size; } // Copy samples to fft input while applying window function for (s = 0; s < private->window_size; s++) { private->fft_in[s] = private->sample_buff[s] * private->hann[s]; } // Perform the FFT fftw_execute(private->fft_plan); // Convert to magnitudes int bin = 0; for (bin = 0; bin < private->bin_count; bin++) { // Convert FFT output to magnitudes private->out_bins[bin] = sqrt(private->fft_out[bin][0] * private->fft_out[bin][0] + private->fft_out[bin][1] * private->fft_out[bin][1]); // Scale to 0.0 - 1.0 private->out_bins[bin] = (4.0 * private->out_bins[bin]) / (float) private->window_size; } private->expected_pos++; } mlt_properties_set_double(filter_properties, "bin_width", (double) *frequency / (double) private->window_size); mlt_properties_set_double(filter_properties, "window_level", (double) private->sample_buff_count / (double) private->window_size); mlt_service_unlock(MLT_FILTER_SERVICE(filter)); return 0; } /** Filter processing. */ static mlt_frame filter_process(mlt_filter filter, mlt_frame frame) { mlt_frame_push_audio(frame, filter); mlt_frame_push_audio(frame, filter_get_audio); return frame; } static void filter_close(mlt_filter filter) { private_data *private = (private_data *) filter->child; if (private) { fftw_free(private->fft_in); fftw_free(private->fft_out); fftw_destroy_plan(private->fft_plan); mlt_pool_release(private->sample_buff); mlt_pool_release(private->hann); mlt_pool_release(private->out_bins); free(private); } filter->child = NULL; filter->close = NULL; filter->parent.close = NULL; mlt_service_close(&filter->parent); } /** Constructor for the filter. */ mlt_filter filter_fft_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = mlt_filter_new(); private_data *private = (private_data *) calloc(1, sizeof(private_data)); if (filter && private) { mlt_properties properties = MLT_FILTER_PROPERTIES(filter); mlt_properties_set_int(properties, "_filter_private", 1); mlt_properties_set_int(properties, "window_size", 2048); mlt_properties_set_double(properties, "window_level", 0.0); mlt_properties_set_double(properties, "bin_width", 0.0); mlt_properties_set_int(properties, "bin_count", 0); mlt_properties_set_data(properties, "bins", 0, 0, 0, 0); memset(private, 0, sizeof(private_data)); filter->close = filter_close; filter->process = filter_process; filter->child = private; } else { mlt_log_error(MLT_FILTER_SERVICE(filter), "Filter FFT failed\n"); if (filter) { mlt_filter_close(filter); } if (private) { free(private); } filter = NULL; } return filter; } mlt-7.22.0/src/modules/plus/filter_fft.yml000664 000000 000000 00000003565 14531534050 020460 0ustar00rootroot000000 000000 schema_version: 0.3 type: filter identifier: fft title: FFT version: 1 copyright: Meltytech, LLC license: LGPLv2.1 language: en tags: - Audio description: > An audio filter that computes the FFT of the audio. This filter does not modify the audio or the image. It only computes the FFT and stores the result in the "bins" property of the filter. parameters: - identifier: window_size title: Window Size type: integer description: > The number of samples that the transform will be performed on. If window_size is less than the number of samples in a frame, extra samples will be ignored. If window_size is more than the number of samples in a frame, samples will be buffered from previous frames to fill the window. The buffering is performed as a sliding window so that the most recent samples are always transformed. mutable: no readonly: no default: 2048 - identifier: window_level title: Window Level type: float description: > The level of the sample window. 0 indicates that there are no samples in the window. 1.0 indicates that the window is full. The transform of a window that is not full may show frequency spikes that are not really present in the audio. readonly: yes minimum: 0 maximum: 1.0 - identifier: bin_width title: Bin Width type: float description: > The width of each bin in Hz. readonly: yes unit: Hz - identifier: bin_count title: Bin Count type: integer description: > The number of bins that are output from the transform. readonly: yes - identifier: bins title: Output Bins description: > A pointer to an array of floats that represent the magnitude of the output of the transform. bin[i] = sqrt( real[i]^2 + imag[i]^2 ) readonly: yes mlt-7.22.0/src/modules/plus/filter_invert.c000664 000000 000000 00000007137 14531534050 020630 0ustar00rootroot000000 000000 /* * filter_invert.c -- invert filter * Copyright (C) 2003-2022 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include typedef struct { uint8_t *image; int height; int width; int full_range; } slice_desc; static int do_slice_proc(int id, int index, int jobs, void *data) { (void) id; // unused slice_desc *desc = (slice_desc *) data; int slice_line_start, slice_height = mlt_slices_size_slice(jobs, index, desc->height, &slice_line_start); int slice_line_end = slice_line_start + slice_height; int line_size = desc->width * 2; int min = desc->full_range ? 0 : 16; int max_luma = desc->full_range ? 255 : 235; int max_chroma = desc->full_range ? 255 : 240; int invert_luma = desc->full_range ? 255 : 251; int x, y; for (y = slice_line_start; y < slice_line_end; y++) { uint8_t *p = desc->image + y * line_size; for (x = 0; x < line_size; x += 2) { p[x] = CLAMP(invert_luma - p[x], min, max_luma); p[x + 1] = CLAMP(256 - p[x + 1], min, max_chroma); } } return 0; } static int filter_get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { // Get the image mlt_filter filter = mlt_frame_pop_service(frame); *format = mlt_image_yuv422; int error = mlt_frame_get_image(frame, image, format, width, height, 1); // Only process if we have no error and a valid color space if (error == 0 && *format == mlt_image_yuv422) { slice_desc desc; desc.image = *image; desc.width = *width; desc.height = *height; desc.full_range = mlt_properties_get_int(MLT_FRAME_PROPERTIES(frame), "full_range"); mlt_slices_run_normal(0, do_slice_proc, &desc); int mask = mlt_properties_get_int(MLT_FILTER_PROPERTIES(filter), "alpha"); if (mask) { int size = *width * *height; uint8_t *alpha = mlt_pool_alloc(size); memset(alpha, mask, size); mlt_frame_set_alpha(frame, alpha, size, mlt_pool_release); } } return error; } /** Filter processing. */ static mlt_frame filter_process(mlt_filter filter, mlt_frame frame) { // Push the frame filter mlt_frame_push_service(frame, filter); mlt_frame_push_get_image(frame, filter_get_image); return frame; } /** Constructor for the filter. */ mlt_filter filter_invert_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = mlt_filter_new(); if (filter != NULL) { filter->process = filter_process; } return filter; } mlt-7.22.0/src/modules/plus/filter_invert.yml000664 000000 000000 00000000545 14531534050 021203 0ustar00rootroot000000 000000 schema_version: 7.0 type: filter identifier: invert title: Invert version: 1 copyright: Meltytech, LLC creator: Charles Yates license: LGPLv2.1 language: en tags: - Video parameters: - identifier: alpha type: integer title: Alpha Channel description: A value to overwrite the alpha channel. minimum: 1 maximum: 255 mutable: yes mlt-7.22.0/src/modules/plus/filter_lift_gamma_gain.c000664 000000 000000 00000022344 14531534050 022414 0ustar00rootroot000000 000000 /* * filter_lift_gamma_gain.cpp * Copyright (C) 2014-2022 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include typedef struct { uint8_t rlut[256]; uint8_t glut[256]; uint8_t blut[256]; double rlift, glift, blift; double rgamma, ggamma, bgamma; double rgain, ggain, bgain; } private_data; typedef struct { mlt_filter filter; uint8_t *image; mlt_image_format format; int width; int height; uint8_t rlut[256]; uint8_t glut[256]; uint8_t blut[256]; } sliced_desc; static void refresh_lut(mlt_filter filter, mlt_frame frame) { private_data *self = (private_data *) filter->child; mlt_properties properties = MLT_FILTER_PROPERTIES(filter); mlt_position position = mlt_filter_get_position(filter, frame); mlt_position length = mlt_filter_get_length2(filter, frame); double rlift = mlt_properties_anim_get_double(properties, "lift_r", position, length); double glift = mlt_properties_anim_get_double(properties, "lift_g", position, length); double blift = mlt_properties_anim_get_double(properties, "lift_b", position, length); double rgamma = mlt_properties_anim_get_double(properties, "gamma_r", position, length); double ggamma = mlt_properties_anim_get_double(properties, "gamma_g", position, length); double bgamma = mlt_properties_anim_get_double(properties, "gamma_b", position, length); double rgain = mlt_properties_anim_get_double(properties, "gain_r", position, length); double ggain = mlt_properties_anim_get_double(properties, "gain_g", position, length); double bgain = mlt_properties_anim_get_double(properties, "gain_b", position, length); // Only regenerate the LUT if something changed. if (self->rlift != rlift || self->glift != glift || self->blift != blift || self->rgamma != rgamma || self->ggamma != ggamma || self->bgamma != bgamma || self->rgain != rgain || self->ggain != ggain || self->bgain != bgain) { int i = 0; for (i = 0; i < 256; i++) { // Convert to gamma 2.2 double gamma22 = pow((double) i / 255.0, 1.0 / 2.2); double r = gamma22; double g = gamma22; double b = gamma22; // Apply lift r += rlift * (1.0 - r); g += glift * (1.0 - g); b += blift * (1.0 - b); // Clamp negative values r = MAX(r, 0.0); g = MAX(g, 0.0); b = MAX(b, 0.0); // Apply gamma r = pow(r, 2.2 / rgamma); g = pow(g, 2.2 / ggamma); b = pow(b, 2.2 / bgamma); // Apply gain r *= pow(rgain, 1.0 / rgamma); g *= pow(ggain, 1.0 / ggamma); b *= pow(bgain, 1.0 / bgamma); // Clamp values r = CLAMP(r, 0.0, 1.0); g = CLAMP(g, 0.0, 1.0); b = CLAMP(b, 0.0, 1.0); // Update LUT self->rlut[i] = lrint(r * 255.0); self->glut[i] = lrint(g * 255.0); self->blut[i] = lrint(b * 255.0); } // Store the values that created the LUT so that // changes can be detected. self->rlift = rlift; self->glift = glift; self->blift = blift; self->rgamma = rgamma; self->ggamma = ggamma; self->bgamma = bgamma; self->rgain = rgain; self->ggain = ggain; self->bgain = bgain; } } static int sliced_proc(int id, int index, int jobs, void *data) { (void) id; // unused sliced_desc *desc = ((sliced_desc *) data); int slice_line_start, slice_height = mlt_slices_size_slice(jobs, index, desc->height, &slice_line_start); int total = desc->width * slice_height + 1; uint8_t *sample = desc->image + slice_line_start * mlt_image_format_size(desc->format, desc->width, 1, NULL); switch (desc->format) { case mlt_image_rgb: while (--total) { *sample = desc->rlut[*sample]; sample++; *sample = desc->glut[*sample]; sample++; *sample = desc->blut[*sample]; sample++; } break; case mlt_image_rgba: while (--total) { *sample = desc->rlut[*sample]; sample++; *sample = desc->glut[*sample]; sample++; *sample = desc->blut[*sample]; sample++; sample++; // Skip alpha } break; default: mlt_log_error(MLT_FILTER_SERVICE(desc->filter), "Invalid image format: %s\n", mlt_image_format_name(desc->format)); break; } return 0; } static void apply_lut( mlt_filter filter, uint8_t *image, mlt_image_format format, int width, int height) { private_data *self = (private_data *) filter->child; sliced_desc *desc = malloc(sizeof(*desc)); desc->filter = filter; desc->image = image; desc->format = format; desc->width = width; desc->height = height; // Copy the LUT so that we can be frame-thread safe. mlt_service_lock(MLT_FILTER_SERVICE(filter)); memcpy(desc->rlut, self->rlut, sizeof(self->rlut)); memcpy(desc->glut, self->glut, sizeof(self->glut)); memcpy(desc->blut, self->blut, sizeof(self->blut)); mlt_service_unlock(MLT_FILTER_SERVICE(filter)); mlt_slices_run_normal(0, sliced_proc, desc); free(desc); } static int filter_get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { mlt_filter filter = (mlt_filter) mlt_frame_pop_service(frame); int error = 0; // Regenerate the LUT if necessary mlt_service_lock(MLT_FILTER_SERVICE(filter)); refresh_lut(filter, frame); mlt_service_unlock(MLT_FILTER_SERVICE(filter)); // Make sure the format is acceptable if (*format != mlt_image_rgb && *format != mlt_image_rgba) { *format = mlt_image_rgb; } // Get the image writable = 1; error = mlt_frame_get_image(frame, image, format, width, height, writable); // Apply the LUT if (!error) { apply_lut(filter, *image, *format, *width, *height); } return error; } static mlt_frame filter_process(mlt_filter filter, mlt_frame frame) { mlt_frame_push_service(frame, filter); mlt_frame_push_get_image(frame, filter_get_image); return frame; } static void filter_close(mlt_filter filter) { private_data *self = (private_data *) filter->child; free(self); filter->child = NULL; filter->close = NULL; filter->parent.close = NULL; mlt_service_close(&filter->parent); } mlt_filter filter_lift_gamma_gain_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = mlt_filter_new(); private_data *self = (private_data *) calloc(1, sizeof(private_data)); int i = 0; if (filter && self) { mlt_properties properties = MLT_FILTER_PROPERTIES(filter); // Initialize self data for (i = 0; i < 256; i++) { self->rlut[i] = i; self->glut[i] = i; self->blut[i] = i; } self->rlift = self->glift = self->blift = 0.0; self->rgamma = self->ggamma = self->bgamma = 1.0; self->rgain = self->ggain = self->bgain = 1.0; // Initialize filter properties mlt_properties_set_double(properties, "lift_r", self->rlift); mlt_properties_set_double(properties, "lift_g", self->glift); mlt_properties_set_double(properties, "lift_b", self->blift); mlt_properties_set_double(properties, "gamma_r", self->rgamma); mlt_properties_set_double(properties, "gamma_g", self->ggamma); mlt_properties_set_double(properties, "gamma_b", self->bgamma); mlt_properties_set_double(properties, "gain_r", self->rgain); mlt_properties_set_double(properties, "gain_g", self->ggain); mlt_properties_set_double(properties, "gain_b", self->bgain); filter->close = filter_close; filter->process = filter_process; filter->child = self; } else { mlt_log_error(MLT_FILTER_SERVICE(filter), "Filter lift_gamma_gain init failed\n"); mlt_filter_close(filter); filter = NULL; free(self); } return filter; } mlt-7.22.0/src/modules/plus/filter_lift_gamma_gain.yml000664 000000 000000 00000004230 14531534050 022765 0ustar00rootroot000000 000000 schema_version: 7.0 type: filter identifier: lift_gamma_gain title: Lift, Gamma, and Gain version: 1 copyright: Meltytech, LLC license: LGPLv2.1 language: en tags: - Video description: > A simple lift/gamma/gain effect, used for color grading. notes: > Very roughly speaking, lift=shadows, gamma=midtones and gain=highlights, although all parameters affect the entire curve. Mathematically speaking, it is a bit unusual to look at gamma as a color, but it works pretty well in practice. The classic formula is: output = (gain * (x + lift * (1-x)))^(1/gamma). The lift is a case where we actually would _not_ want linear light; since black by definition becomes equal to the lift color, we want lift to be pretty close to black, but in linear light that means lift affects the rest of the curve relatively little. Thus, we actually convert to gamma 2.2 before lift, and then back again afterwards. (Gain and gamma are, up to constants, commutative with the de-gamma operation.) parameters: - identifier: lift_r title: Lift Red type: float minimum: 0.0 default: 0.0 mutable: yes animation: yes - identifier: lift_g title: Lift Green type: float minimum: 0.0 default: 0.0 mutable: yes animation: yes - identifier: lift_b title: Lift Blue type: float minimum: 0.0 default: 0.0 mutable: yes animation: yes - identifier: gamma_r title: Gamma Red type: float minimum: 0.0 default: 1.0 mutable: yes animation: yes - identifier: gamma_g title: Gamma Green type: float minimum: 0.0 default: 1.0 mutable: yes animation: yes - identifier: gamma_b title: Gamma Blue type: float minimum: 0.0 default: 1.0 mutable: yes animation: yes - identifier: gain_r title: Gain Red type: float minimum: 0.0 default: 1.0 mutable: yes animation: yes - identifier: gain_g title: Gain Green type: float minimum: 0.0 default: 1.0 mutable: yes animation: yes - identifier: gain_b title: Gain Blue type: float minimum: 0.0 default: 1.0 mutable: yes animation: yes mlt-7.22.0/src/modules/plus/filter_loudness.c000664 000000 000000 00000016310 14531534050 021146 0ustar00rootroot000000 000000 /* * filter_loudness.c -- normalize audio according to EBU R128 * Copyright (C) 20142-2023 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #define MAX_RESULT_SIZE 512 typedef struct { ebur128_state *state; } analyze_data; typedef struct { analyze_data *analyze; mlt_position last_position; } private_data; static void destroy_analyze_data(mlt_filter filter) { private_data *private = (private_data *) filter->child; ebur128_destroy(&private->analyze->state); free(private->analyze); private->analyze = NULL; } static void init_analyze_data(mlt_filter filter, int channels, int samplerate) { private_data *private = (private_data *) filter->child; private->analyze = (analyze_data *) calloc(1, sizeof(analyze_data)); private->analyze->state = ebur128_init((unsigned int) channels, (unsigned long) samplerate, EBUR128_MODE_I | EBUR128_MODE_LRA | EBUR128_MODE_SAMPLE_PEAK); private->last_position = 0; } static void analyze(mlt_filter filter, mlt_frame frame, void **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples) { private_data *private = (private_data *) filter->child; mlt_position pos = mlt_filter_get_position(filter, frame); // If any frames are skipped, analysis data will be incomplete. if (private->analyze && pos != private->last_position + 1) { mlt_log_error(MLT_FILTER_SERVICE(filter), "Analysis Failed: Bad frame sequence\n"); destroy_analyze_data(filter); } // Analyze Audio if (!private->analyze && pos == 0) { init_analyze_data(filter, *channels, *frequency); } if (private->analyze) { ebur128_add_frames_float(private->analyze->state, *buffer, *samples); if (pos + 1 == mlt_filter_get_length2(filter, frame)) { mlt_properties properties = MLT_FILTER_PROPERTIES(filter); double loudness = 0.0; double range = 0.0; double tmpPeak = 0.0; double peak = 0.0; int i = 0; char result[MAX_RESULT_SIZE]; ebur128_loudness_global(private->analyze->state, &loudness); ebur128_loudness_range(private->analyze->state, &range); for (i = 0; i < *channels; i++) { ebur128_sample_peak(private->analyze->state, i, &tmpPeak); if (tmpPeak > peak) { peak = tmpPeak; } } snprintf(result, MAX_RESULT_SIZE, "L: %lf\tR: %lf\tP %lf", loudness, range, peak); result[MAX_RESULT_SIZE - 1] = '\0'; mlt_log_info(MLT_FILTER_SERVICE(filter), "Stored results: %s\n", result); mlt_properties_set(properties, "results", result); destroy_analyze_data(filter); } private->last_position = pos; } } static void apply(mlt_filter filter, mlt_frame frame, void **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples) { mlt_properties properties = MLT_FILTER_PROPERTIES(filter); char *results = mlt_properties_get(properties, "results"); double in_loudness; double in_range; double in_peak; int scan_return = sscanf(results, "L: %lf\tR: %lf\tP %lf", &in_loudness, &in_range, &in_peak); if (scan_return != 3) { mlt_log_error(MLT_FILTER_SERVICE(filter), "Unable to load results: %s\n", results); } else { double target_db = mlt_properties_get_double(properties, "program"); double delta_db = target_db - in_loudness; double coeff = delta_db > -90.0 ? pow(10.0, delta_db / 20.0) : 0.0; float *p = *buffer; int count = *samples * *channels; for (int i = 0; i < count; i++) { p[i] = p[i] * coeff; } } } /** Get the audio. */ static int filter_get_audio(mlt_frame frame, void **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples) { mlt_filter filter = mlt_frame_pop_audio(frame); mlt_properties properties = MLT_FILTER_PROPERTIES(filter); mlt_service_lock(MLT_FILTER_SERVICE(filter)); // Get the producer's audio *format = mlt_audio_f32le; mlt_frame_get_audio(frame, buffer, format, frequency, channels, samples); char *results = mlt_properties_get(properties, "results"); if (buffer && buffer[0] && results && strcmp(results, "")) { apply(filter, frame, buffer, format, frequency, channels, samples); } else { analyze(filter, frame, buffer, format, frequency, channels, samples); } mlt_service_unlock(MLT_FILTER_SERVICE(filter)); return 0; } /** Filter processing. */ static mlt_frame filter_process(mlt_filter filter, mlt_frame frame) { mlt_frame_push_audio(frame, filter); mlt_frame_push_audio(frame, filter_get_audio); return frame; } /** Destructor for the filter. */ static void filter_close(mlt_filter filter) { private_data *private = (private_data *) filter->child; if (private) { if (private->analyze) { destroy_analyze_data(filter); } free(private); } filter->child = NULL; filter->close = NULL; filter->parent.close = NULL; mlt_service_close(&filter->parent); } /** Constructor for the filter. */ mlt_filter filter_loudness_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = mlt_filter_new(); private_data *data = (private_data *) calloc(1, sizeof(private_data)); if (filter && data) { mlt_properties properties = MLT_FILTER_PROPERTIES(filter); mlt_properties_set(properties, "program", "-23.0"); data->analyze = NULL; filter->close = filter_close; filter->process = filter_process; filter->child = data; } else { if (filter) { mlt_filter_close(filter); filter = NULL; } if (data) { free(data); } } return filter; } mlt-7.22.0/src/modules/plus/filter_loudness.yml000664 000000 000000 00000002175 14531534050 021531 0ustar00rootroot000000 000000 schema_version: 0.1 type: filter identifier: loudness title: Loudness version: 1 copyright: Meltytech, LLC license: LGPLv2.1 language: en tags: - Audio description: Correct audio loudness as recommended by EBU R128. notes: > This filter requires two passes. The first pass performs analysis and stores the result in the "results" property. The second pass applies the results to the audio in order to achieve the desired loudness over the range of the filter. parameters: - identifier: results title: Analysis Results type: string description: > Set after analysis. Used during application. Loudness information about the original audio. When results are not supplied, the filter computes the results and stores them in this property when the last frame has been processed. mutable: no - identifier: program title: Target Program Loudness type: float description: > Used during application. The target program loudness in LUFS (Loudness Units Full Scale). readonly: no mutable: yes default: -23.0 minimum: -50.0 maximum: -10.0 unit: LUFS mlt-7.22.0/src/modules/plus/filter_loudness_meter.c000664 000000 000000 00000024533 14531534050 022350 0ustar00rootroot000000 000000 /* * filter_loudness_meter.c -- measure audio loudness according to EBU R128 * Copyright (C) 2016 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include typedef struct { ebur128_state *r128; int reset; mlt_position prev_pos; } private_data; static void property_changed(mlt_service owner, mlt_filter filter, mlt_event_data event_data) { const char *name = mlt_event_data_to_string(event_data); private_data *pdata = (private_data *) filter->child; if (name && pdata && (!strcmp(name, "reset") || !strcmp(name, "calc_program") || !strcmp(name, "calc_shortterm") || !strcmp(name, "calc_momentary") || !strcmp(name, "calc_range") || !strcmp(name, "calc_peak") || !strcmp(name, "calc_true_peak"))) { pdata->reset = 1; } } static void check_for_reset(mlt_filter filter, int channels, int frequency) { mlt_properties properties = MLT_FILTER_PROPERTIES(filter); private_data *pdata = (private_data *) filter->child; if (pdata->reset) { if (pdata->r128) { ebur128_destroy(&pdata->r128); } pdata->r128 = 0; pdata->reset = 0; pdata->prev_pos = -1; mlt_events_block(properties, filter); mlt_properties_set(properties, "frames_processed", "0"); mlt_properties_set(properties, "program", "-100.0"); mlt_properties_set(properties, "shortterm", "-100.0"); mlt_properties_set(properties, "momentary", "-100.0"); mlt_properties_set(properties, "range", "-1.0"); mlt_properties_set_int(properties, "reset_count", mlt_properties_get_int(properties, "reset_count") + 1); mlt_properties_set_int(properties, "reset", 0); mlt_events_unblock(properties, filter); } if (!pdata->r128) { int mode = EBUR128_MODE_HISTOGRAM; if (mlt_properties_get_int(MLT_FILTER_PROPERTIES(filter), "calc_program")) { mode |= EBUR128_MODE_I; } if (mlt_properties_get_int(MLT_FILTER_PROPERTIES(filter), "calc_shortterm")) { mode |= EBUR128_MODE_S; } if (mlt_properties_get_int(MLT_FILTER_PROPERTIES(filter), "calc_momentary")) { mode |= EBUR128_MODE_M; } if (mlt_properties_get_int(MLT_FILTER_PROPERTIES(filter), "calc_range")) { mode |= EBUR128_MODE_LRA; } if (mlt_properties_get_int(MLT_FILTER_PROPERTIES(filter), "calc_peak")) { mode |= EBUR128_MODE_SAMPLE_PEAK; } if (mlt_properties_get_int(MLT_FILTER_PROPERTIES(filter), "calc_true_peak")) { mode |= EBUR128_MODE_TRUE_PEAK; } pdata->r128 = ebur128_init(channels, frequency, mode); } } static void analyze_audio(mlt_filter filter, void *buffer, int samples) { mlt_properties properties = MLT_FILTER_PROPERTIES(filter); private_data *pdata = (private_data *) filter->child; int result = -1; double loudness = 0.0; ebur128_add_frames_float(pdata->r128, buffer, samples); if (mlt_properties_get_int(MLT_FILTER_PROPERTIES(filter), "calc_program")) { result = ebur128_loudness_global(pdata->r128, &loudness); if (result == EBUR128_SUCCESS && loudness != HUGE_VAL && loudness != -HUGE_VAL) { mlt_properties_set_double(properties, "program", loudness); } } if (mlt_properties_get_int(MLT_FILTER_PROPERTIES(filter), "calc_shortterm")) { result = ebur128_loudness_shortterm(pdata->r128, &loudness); if (result == EBUR128_SUCCESS && loudness != HUGE_VAL && loudness != -HUGE_VAL) { mlt_properties_set_double(properties, "shortterm", loudness); } } if (mlt_properties_get_int(MLT_FILTER_PROPERTIES(filter), "calc_momentary")) { result = ebur128_loudness_momentary(pdata->r128, &loudness); if (result == EBUR128_SUCCESS && loudness != HUGE_VAL && loudness != -HUGE_VAL) { mlt_properties_set_double(properties, "momentary", loudness); } } if (mlt_properties_get_int(MLT_FILTER_PROPERTIES(filter), "calc_range")) { double range = 0; result = ebur128_loudness_range(pdata->r128, &range); if (result == EBUR128_SUCCESS && range != HUGE_VAL && range != -HUGE_VAL) { mlt_properties_set_double(properties, "range", range); } } if (mlt_properties_get_int(MLT_FILTER_PROPERTIES(filter), "calc_peak")) { double prev_peak = 0.0; double max_peak = 0.0; int c = 0; for (c = 0; c < pdata->r128->channels; c++) { double peak; result = ebur128_sample_peak(pdata->r128, c, &peak); if (result == EBUR128_SUCCESS && peak != HUGE_VAL && peak > max_peak) { max_peak = peak; } result = ebur128_prev_sample_peak(pdata->r128, c, &peak); if (result == EBUR128_SUCCESS && peak != HUGE_VAL && peak > prev_peak) { prev_peak = peak; } } mlt_properties_set_double(properties, "max_peak", 20 * log10(max_peak)); mlt_properties_set_double(properties, "peak", 20 * log10(prev_peak)); } if (mlt_properties_get_int(MLT_FILTER_PROPERTIES(filter), "calc_true_peak")) { double prev_peak = 0.0; double max_peak = 0.0; int c = 0; for (c = 0; c < pdata->r128->channels; c++) { double peak; result = ebur128_true_peak(pdata->r128, c, &peak); if (result == EBUR128_SUCCESS && peak != HUGE_VAL && peak > max_peak) { max_peak = peak; } result = ebur128_prev_true_peak(pdata->r128, c, &peak); if (result == EBUR128_SUCCESS && peak != HUGE_VAL && peak > prev_peak) { prev_peak = peak; } } mlt_properties_set_double(properties, "max_true_peak", 20 * log10(max_peak)); mlt_properties_set_double(properties, "true_peak", 20 * log10(prev_peak)); } mlt_properties_set_position(properties, "frames_processed", mlt_properties_get_position(properties, "frames_processed") + 1); } static int filter_get_audio(mlt_frame frame, void **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples) { mlt_filter filter = mlt_frame_pop_audio(frame); private_data *pdata = (private_data *) filter->child; mlt_position pos = mlt_frame_get_position(frame); // Get the producer's audio *format = mlt_audio_f32le; mlt_frame_get_audio(frame, buffer, format, frequency, channels, samples); mlt_service_lock(MLT_FILTER_SERVICE(filter)); check_for_reset(filter, *channels, *frequency); if (pos != pdata->prev_pos) { // Only analyze the audio if the producer is not paused. analyze_audio(filter, *buffer, *samples); } pdata->prev_pos = pos; mlt_service_unlock(MLT_FILTER_SERVICE(filter)); return 0; } /** Filter processing. */ static mlt_frame filter_process(mlt_filter filter, mlt_frame frame) { mlt_frame_push_audio(frame, filter); mlt_frame_push_audio(frame, filter_get_audio); return frame; } /** Destructor for the filter. */ static void filter_close(mlt_filter filter) { private_data *pdata = (private_data *) filter->child; if (pdata) { if (pdata->r128) { ebur128_destroy(&pdata->r128); } free(pdata); } filter->child = NULL; filter->close = NULL; filter->parent.close = NULL; mlt_service_close(&filter->parent); } /** Constructor for the filter. */ mlt_filter filter_loudness_meter_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = mlt_filter_new(); private_data *pdata = (private_data *) calloc(1, sizeof(private_data)); if (filter && pdata) { mlt_properties properties = MLT_FILTER_PROPERTIES(filter); mlt_properties_set_int(properties, "calc_program", 1); mlt_properties_set_int(properties, "calc_shortterm", 1); mlt_properties_set_int(properties, "calc_momentary", 1); mlt_properties_set_int(properties, "calc_range", 1); mlt_properties_set_int(properties, "calc_peak", 1); mlt_properties_set_int(properties, "calc_true_peak", 1); mlt_properties_set(properties, "program", "-100.0"); mlt_properties_set(properties, "shortterm", "-100.0"); mlt_properties_set(properties, "momentary", "-100.0"); mlt_properties_set(properties, "range", "-1.0"); mlt_properties_set(properties, "peak", "-100.0"); mlt_properties_set(properties, "max_peak", "-100.0"); mlt_properties_set(properties, "true_peak", "-100.0"); mlt_properties_set(properties, "max_true_peak", "-100.0"); mlt_properties_set(properties, "reset", "1"); mlt_properties_set(properties, "reset_count", "0"); mlt_properties_set(properties, "frames_processed", "0"); pdata->r128 = 0; pdata->reset = 1; pdata->prev_pos = -1; filter->close = filter_close; filter->process = filter_process; filter->child = pdata; mlt_events_listen(properties, filter, "property-changed", (mlt_listener) property_changed); } else { if (filter) { mlt_filter_close(filter); filter = NULL; } free(pdata); } return filter; } mlt-7.22.0/src/modules/plus/filter_loudness_meter.yml000664 000000 000000 00000006726 14531534050 022733 0ustar00rootroot000000 000000 schema_version: 0.3 type: filter identifier: loudness_meter title: Loudness Meter version: 1 copyright: Meltytech, LLC license: LGPLv2.1 language: en tags: - Audio description: Measure audio loudness as recommended by EBU R128. parameters: - identifier: calc_program title: Calculate Program Loudness type: boolean description: > Whether to calculate program (integrated) loudness. readonly: no mutable: yes default: 1 - identifier: calc_shortterm title: Calculate Short-term Loudness type: boolean description: > Whether to calculate short-term loudness. readonly: no mutable: yes default: 1 - identifier: calc_momentary title: Calculate momentary Loudness type: boolean description: > Whether to calculate momentary loudness. readonly: no mutable: yes default: 1 - identifier: calc_range title: Calculate loudness range type: boolean description: > Whether to calculate loudness range. readonly: no mutable: yes default: 1 - identifier: calc_peak title: Calculate the peak sample level type: boolean description: > Whether to calculate the peak sample level. readonly: no mutable: yes default: 1 - identifier: calc_true_peak title: Calculate the true peak level type: boolean description: > Whether to calculate the true peak level. readonly: no mutable: yes default: 1 - identifier: program title: Program Loudness type: float description: The measured program loudness since the last reset. readonly: yes unit: LUFS - identifier: shortterm title: Short-term Loudness type: float description: The measured short-term loudness. readonly: yes unit: LUFS - identifier: momentary title: Momentary Loudness type: float description: The measured momentary loudness. readonly: yes unit: LUFS - identifier: range title: Loudness Range type: float description: The measured loudness range since the last reset. readonly: yes unit: LUFS - identifier: peak title: Peak type: float description: The measured peak sample value for the last frame that was processed. readonly: yes unit: dBFS - identifier: max_peak title: Max Peak type: float description: The measured peak sample value that has been received since the last reset. readonly: yes unit: dBFS - identifier: true_peak title: True Peak type: float description: The measured true peak value for the last frame that was processed. readonly: yes unit: dBTP - identifier: max_true_peak title: Max True Peak type: float description: The measured true peak value that has been received since the last reset. readonly: yes unit: dBTP - identifier: reset title: Reset type: boolean description: > Reset the measurement. Automatically resets back to 0 after the reset is complete. readonly: no mutable: yes default: 1 - identifier: reset_count title: Reset Count type: integer description: > The number of times the measurement has been reset. The filter is reset whenever reset is set to 1 or whenever a parameter is changed. readonly: yes - identifier: frames_processed title: Frames Processed type: integer description: > The number of frames that have been processed since the last reset. readonly: yes mlt-7.22.0/src/modules/plus/filter_lumakey.c000664 000000 000000 00000011226 14531534050 020762 0ustar00rootroot000000 000000 /* * filter_lumakey.c -- Luma Key filter for luma based image compositing * Copyright (C) 2014 Janne Liljeblad * Author: Janne Liljeblad * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include static int clamp(int value, int low, int high) { if (value < low) value = low; if (value > high) value = high; return value; } /** Builds a lookup table that maps luma values to opacity values. */ static void fill_opa_lut(int lgg_lut[], int prelevel, int postlevel, int slope_start, int slope_end) { int i; // Prelevel plateau for (i = 0; i < slope_start; i++) lgg_lut[i] = prelevel; // Value transition if (slope_start != slope_end) { double value = prelevel; double value_step = (double) (postlevel - prelevel) / (double) (slope_end - slope_start); for (i = slope_start; i < slope_end + 1; i++) { lgg_lut[i] = (int) value; value += value_step; } } // Postlevel plateau for (i = slope_end; i < 256; i++) lgg_lut[i] = postlevel; } /** Do image filtering. */ static int filter_get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { // Get the image mlt_filter filter = (mlt_filter) mlt_frame_pop_service(frame); mlt_properties properties = MLT_FILTER_PROPERTIES(filter); mlt_position position = mlt_filter_get_position(filter, frame); mlt_position length = mlt_filter_get_length2(filter, frame); *format = mlt_image_rgba; int error = mlt_frame_get_image(frame, image, format, width, height, 0); // Only process if we have no error and a valid colour space if (error == 0) { // Get values and force accepted ranges int threshold = mlt_properties_anim_get_int(properties, "threshold", position, length); int slope = mlt_properties_anim_get_int(properties, "slope", position, length); int prelevel = mlt_properties_anim_get_int(properties, "prelevel", position, length); int postlevel = mlt_properties_anim_get_int(properties, "postlevel", position, length); threshold = clamp(threshold, 0, 255); slope = clamp(slope, 0, 128); prelevel = clamp(prelevel, 0, 255); postlevel = clamp(postlevel, 0, 255); int slope_start = clamp(threshold - slope, 0, 255); int slope_end = clamp(threshold + slope, 0, 255); // Build lut int opa_lut[256]; fill_opa_lut(opa_lut, prelevel, postlevel, slope_start, slope_end); // Values for calculating visual luma from RGB double R = 0.3; double G = 0.59; double B = 0.11; // Filter int i = *width * *height + 1; uint8_t *sample = *image; uint8_t r, g, b; while (--i) { r = *sample++; g = *sample++; b = *sample++; *sample++ = opa_lut[(int) (R * r + g * G + b * B)]; } } return error; } /** Filter processing. */ static mlt_frame filter_process(mlt_filter filter, mlt_frame frame) { // Push the frame filter mlt_frame_push_service(frame, filter); mlt_frame_push_get_image(frame, filter_get_image); return frame; } /** Constructor for the filter. */ mlt_filter filter_lumakey_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = mlt_filter_new(); if (filter != NULL) { filter->process = filter_process; mlt_properties_set(MLT_FILTER_PROPERTIES(filter), "threshold", "128"); mlt_properties_set(MLT_FILTER_PROPERTIES(filter), "slope", "0"); mlt_properties_set(MLT_FILTER_PROPERTIES(filter), "prelevel", "0"); mlt_properties_set(MLT_FILTER_PROPERTIES(filter), "postlevel", "255"); } return filter; } mlt-7.22.0/src/modules/plus/filter_lumakey.yml000664 000000 000000 00000003130 14531534050 021334 0ustar00rootroot000000 000000 schema_version: 7.0 type: filter identifier: lumakey title: Lumakey version: 1 copyright: Janne Liljeblad creator: Janne Liljeblad license: LGPLv2.1 language: en tags: - Video description: > This filter modifies image's alpha channel as a function of its luma value. This is used together with a compositor to combine two images so that bright or dark areas of source image are overwritten on top of the destination image. parameters: - identifier: threshold title: Threshold type: integer minimum: 0 maximum: 255 default: 128 mutable: yes animation: yes description: > Luma value that defines the center point of transition from prelevel to postlevel opacity value. - identifier: slope title: Slope type: integer minimum: 0 maximum: 128 default: 0 mutable: yes animation: yes description: > This defines the width of the transition from prelevel to postlevel luma value. Start point of transition in opacity value is threshold - slope and end point is thresholt + slope, values are forced in range 0 - 255. - identifier: prelevel title: Pre-Threshold Luma Level type: integer minimum: 0 maximum: 255 default: 0 mutable: yes animation: yes description: > Opacity value before the transition in opacity value begins. - identifier: postlevel title: Post-Threshold Luma Level type: integer minimum: 0 maximum: 255 default: 255 mutable: yes animation: yes description: > Opacity value after the transition in opacity value ends. mlt-7.22.0/src/modules/plus/filter_rgblut.c000664 000000 000000 00000007151 14531534050 020614 0ustar00rootroot000000 000000 /* * filter_rgblut.c -- generic RGB look-up table filter with string interface * Copyright (C) 2014 Janne Liljeblad * Author: Janne Liljeblad * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include /** Fill channel lut with integers parsed from property string. */ static void fill_channel_lut(int lut[], char *channel_table_str) { mlt_tokeniser tokeniser = mlt_tokeniser_init(); mlt_tokeniser_parse_new(tokeniser, channel_table_str, ";"); // Only create lut from string if tokens count exactly right if (tokeniser->count == 256) { // Fill lut with token values int i; int val; for (i = 0; i < 256; i++) { val = atoi(tokeniser->tokens[i]); lut[i] = val; } } else { // Fill lut with linear no-op table int i; for (i = 0; i < 256; i++) { lut[i] = i; } } mlt_tokeniser_close(tokeniser); } /** Do it :-). */ static int filter_get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { // Get the image mlt_filter filter = mlt_frame_pop_service(frame); *format = mlt_image_rgb; int error = mlt_frame_get_image(frame, image, format, width, height, 0); // Only process if we have no error and a valid colour space if (error == 0) { // Create lut tables from properties for each RGB channel char *r_str = mlt_properties_get(MLT_FILTER_PROPERTIES(filter), "R_table"); int r_lut[256]; fill_channel_lut(r_lut, r_str); char *g_str = mlt_properties_get(MLT_FILTER_PROPERTIES(filter), "G_table"); int g_lut[256]; fill_channel_lut(g_lut, g_str); char *b_str = mlt_properties_get(MLT_FILTER_PROPERTIES(filter), "B_table"); int b_lut[256]; fill_channel_lut(b_lut, b_str); // Apply look-up tables into image int i = *width * *height + 1; uint8_t *p = *image; uint8_t *r = *image; while (--i) { *p++ = r_lut[*r++]; *p++ = g_lut[*r++]; *p++ = b_lut[*r++]; } } return error; } /** Filter processing. */ static mlt_frame filter_process(mlt_filter filter, mlt_frame frame) { // Push the frame filter mlt_frame_push_service(frame, filter); mlt_frame_push_get_image(frame, filter_get_image); return frame; } /** Constructor for the filter. */ mlt_filter filter_rgblut_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = mlt_filter_new(); if (filter != NULL) { filter->process = filter_process; } return filter; } mlt-7.22.0/src/modules/plus/filter_rgblut.yml000664 000000 000000 00000003114 14531534050 021166 0ustar00rootroot000000 000000 schema_version: 0.1 type: filter identifier: rgblut title: RGBLUT version: 1 copyright: Janne Liljeblad creator: Janne Liljeblad license: LGPLv2.1 language: en tags: - Video description: Converts input strings with exactly 256 semicolon separated integer values in range 0 - 255 to look-up tables that are then used to modify R, G, B values. This creates a generic string interface for color correction. parameters: - identifier: R_table title: Red channel look-up table type: string description: > Value is tokenised using semicolon separator into exactly 256 integer values in range 0 - 255 and a look-up table for red channel values is created and applied to image. If tokenising of value fails a linear table that returns input values unchanged is used instead. - identifier: G_table title: Green channel look-up table type: string description: > Value is tokenised using semicolon separator into exactly 256 integer values in range 0 - 255 and a look-up table for green channel values is created and applied to image. If tokenising of value fails a linear table that returns input values unchanged is used instead. - identifier: B_table title: Blue channel look-up table type: string description: > Value is tokenised using semicolon separator into exactly 256 integer values in range 0 - 255 and a look-up table for green channel values is created and applied to image. If tokenising of value fails a linear table that returns input values unchanged is used instead. mlt-7.22.0/src/modules/plus/filter_sepia.c000664 000000 000000 00000007050 14531534050 020414 0ustar00rootroot000000 000000 /* * filter_sepia.c -- sepia filter * Copyright (C) 2003-2022 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include typedef struct { uint8_t *image; int height; int width; uint8_t u; uint8_t v; } slice_desc; static int do_slice_proc(int id, int index, int jobs, void *data) { (void) id; // unused slice_desc *desc = (slice_desc *) data; int slice_line_start, slice_height = mlt_slices_size_slice(jobs, index, desc->height, &slice_line_start); int slice_line_end = slice_line_start + slice_height; int line_size = desc->width * 2; int uneven = desc->width % 2; int x, y; for (y = slice_line_start; y < slice_line_end; y++) { uint8_t *p = desc->image + y * line_size; for (x = 0; x < line_size; x += 4) { p[x + 1] = desc->u; p[x + 3] = desc->v; } if (uneven) { p[line_size - 1] = desc->u; } } return 0; } static int filter_get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { // Get the filter mlt_filter filter = mlt_frame_pop_service(frame); // Get the image *format = mlt_image_yuv422; int error = mlt_frame_get_image(frame, image, format, width, height, 1); // Only process if we have no error and a valid colour space if (error == 0 && *image) { mlt_properties properties = MLT_FILTER_PROPERTIES(filter); mlt_position position = mlt_filter_get_position(filter, frame); mlt_position length = mlt_filter_get_length2(filter, frame); slice_desc desc; desc.image = *image; desc.height = *height; desc.width = *width; desc.u = mlt_properties_anim_get_int(properties, "u", position, length); desc.v = mlt_properties_anim_get_int(properties, "v", position, length); mlt_slices_run_normal(0, do_slice_proc, &desc); } return error; } /** Filter processing. */ static mlt_frame filter_process(mlt_filter filter, mlt_frame frame) { // Push the frame filter mlt_frame_push_service(frame, filter); mlt_frame_push_get_image(frame, filter_get_image); return frame; } /** Constructor for the filter. */ mlt_filter filter_sepia_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = mlt_filter_new(); if (filter != NULL) { filter->process = filter_process; mlt_properties_set(MLT_FILTER_PROPERTIES(filter), "u", "75"); mlt_properties_set(MLT_FILTER_PROPERTIES(filter), "v", "150"); } return filter; } mlt-7.22.0/src/modules/plus/filter_sepia.yml000664 000000 000000 00000001062 14531534050 020770 0ustar00rootroot000000 000000 schema_version: 7.0 type: filter identifier: sepia title: Sepia version: 1 copyright: Meltytech, LLC creator: Charles Yates license: LGPLv2.1 language: en tags: - Video description: > Apply a color tint. Default values give a sepia tone like an old photograph. parameters: - identifier: u type: integer title: Yellow-Blue minimum: 0 maximum: 255 default: 75 mutable: yes animation: yes - identifier: v type: integer title: Cyan-Red minimum: 0 maximum: 255 default: 150 mutable: yes animation: yes mlt-7.22.0/src/modules/plus/filter_shape.c000664 000000 000000 00000040410 14531534050 020410 0ustar00rootroot000000 000000 /* * filter_shape.c -- Arbitrary alpha channel shaping * Copyright (C) 2008-2022 Meltytech, LLC * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include typedef struct { uint8_t *alpha; uint8_t *mask; int width; int height; double softness; double mix; int invert; int invert_mask; double offset; double divisor; } slice_desc; static inline double smoothstep(const double e1, const double e2, const double a) { if (a < e1) return 0.0; if (a >= e2) return 1.0; double v = (a - e1) / (e2 - e1); return (v * v * (3 - 2 * v)); } static int slice_alpha_add(int id, int index, int jobs, void *data) { (void) id; // unused slice_desc *desc = ((slice_desc *) data); int slice_line_start, slice_height = mlt_slices_size_slice(jobs, index, desc->height, &slice_line_start); int size = desc->width * slice_height; uint8_t *p = desc->alpha + slice_line_start * desc->width; uint8_t *q = desc->mask + slice_line_start * desc->width; uint32_t b; for (int i = 0; i < size; ++i) { b = (uint32_t) (q[i] ^ desc->invert_mask); p[i] = ((uint8_t) MIN((uint32_t) p[i] + b, 255)) ^ desc->invert; } return 0; } static int slice_alpha_maximum(int id, int index, int jobs, void *data) { (void) id; // unused slice_desc *desc = ((slice_desc *) data); int slice_line_start, slice_height = mlt_slices_size_slice(jobs, index, desc->height, &slice_line_start); int size = desc->width * slice_height; uint8_t *p = desc->alpha + slice_line_start * desc->width; uint8_t *q = desc->mask + slice_line_start * desc->width; for (int i = 0; i < size; ++i) { p[i] = MAX(p[i], (q[i] ^ desc->invert_mask)) ^ desc->invert; } return 0; } static int slice_alpha_minimum(int id, int index, int jobs, void *data) { (void) id; // unused slice_desc *desc = ((slice_desc *) data); int slice_line_start, slice_height = mlt_slices_size_slice(jobs, index, desc->height, &slice_line_start); int size = desc->width * slice_height; uint8_t *p = desc->alpha + slice_line_start * desc->width; uint8_t *q = desc->mask + slice_line_start * desc->width; for (int i = 0; i < size; ++i) { p[i] = MIN(p[i], (q[i] ^ desc->invert_mask)) ^ desc->invert; } return 0; } static int slice_alpha_overwrite(int id, int index, int jobs, void *data) { (void) id; // unused slice_desc *desc = ((slice_desc *) data); int slice_line_start, slice_height = mlt_slices_size_slice(jobs, index, desc->height, &slice_line_start); int size = desc->width * slice_height; uint8_t *p = desc->alpha + slice_line_start * desc->width; uint8_t *q = desc->mask + slice_line_start * desc->width; for (int i = 0; i < size; ++i) { p[i] = (q[i] ^ desc->invert_mask) ^ desc->invert; } return 0; } static int slice_alpha_subtract(int id, int index, int jobs, void *data) { (void) id; // unused slice_desc *desc = ((slice_desc *) data); int slice_line_start, slice_height = mlt_slices_size_slice(jobs, index, desc->height, &slice_line_start); int size = desc->width * slice_height; uint8_t *p = desc->alpha + slice_line_start * desc->width; uint8_t *q = desc->mask + slice_line_start * desc->width; uint8_t a; for (int i = 0; i < size; ++i) { a = q[i] ^ desc->invert_mask; p[i] = (p[i] > a ? p[i] - a : 0) ^ desc->invert; } return 0; } static int slice_alpha_proc(int id, int index, int jobs, void *data) { (void) id; // unused slice_desc *desc = ((slice_desc *) data); int slice_line_start, slice_height = mlt_slices_size_slice(jobs, index, desc->height, &slice_line_start); int size = desc->width * slice_height; uint8_t *p = desc->alpha + slice_line_start * desc->width; uint8_t *q = desc->mask + slice_line_start * desc->width; double a; for (int i = 0; i < size; ++i) { a = (double) (q[i] ^ desc->invert_mask) / desc->divisor; a = 1.0 - smoothstep(a, a + desc->softness, desc->mix); p[i] = (uint8_t) (p[i] * a) ^ desc->invert; } return 0; } static int slice_luma_proc(int id, int index, int jobs, void *data) { (void) id; // unused slice_desc *desc = ((slice_desc *) data); int slice_line_start, slice_height = mlt_slices_size_slice(jobs, index, desc->height, &slice_line_start); int size = desc->width * slice_height; uint8_t *p = desc->alpha + slice_line_start * desc->width; uint8_t *q = desc->mask + slice_line_start * desc->width * 2; double a = 0; for (int i = 0; i < size; ++i) { a = ((double) (q[i * 2] ^ desc->invert_mask) - desc->offset) / desc->divisor; a = smoothstep(a, a + desc->softness, desc->mix); p[i] = (uint8_t) (p[i] * a) ^ desc->invert; } return 0; } /** Get the images and apply the luminance of the mask to the alpha of the frame. */ static int filter_get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { // Fetch the data from the stack (mix, mask, filter) double mix = mlt_deque_pop_back_double(MLT_FRAME_IMAGE_STACK(frame)); mlt_frame mask = mlt_frame_pop_service(frame); mlt_filter filter = mlt_frame_pop_service(frame); // Obtain the constants double softness = mlt_properties_get_double(MLT_FILTER_PROPERTIES(filter), "softness"); int use_luminance = mlt_properties_get_int(MLT_FILTER_PROPERTIES(filter), "use_luminance"); int use_mix = mlt_properties_get_int(MLT_FILTER_PROPERTIES(filter), "use_mix"); int invert = mlt_properties_get_int(MLT_FILTER_PROPERTIES(filter), "invert") * 255; int invert_mask = mlt_properties_get_int(MLT_FILTER_PROPERTIES(filter), "invert_mask") * 255; if (mlt_properties_get_int(MLT_FILTER_PROPERTIES(filter), "reverse")) { mix = 1.0 - mix; invert = !mlt_properties_get_int(MLT_FILTER_PROPERTIES(filter), "invert") * 255; } // Render the frame *format = mlt_image_yuv422; *width -= *width % 2; if (mlt_frame_get_image(frame, image, format, width, height, 1) == 0 && (!use_luminance || !use_mix || (int) mix != 1 || invert == 255 || invert_mask == 255)) { // Obtain a scaled/distorted mask to match uint8_t *mask_img = NULL; mlt_image_format mask_fmt = mlt_image_yuv422; mlt_properties_set_int(MLT_FRAME_PROPERTIES(mask), "distort", 1); mlt_properties_copy(MLT_FRAME_PROPERTIES(mask), MLT_FRAME_PROPERTIES(frame), "consumer."); if (mlt_frame_get_image(mask, &mask_img, &mask_fmt, width, height, 0) == 0) { int size = *width * *height; uint8_t *p = mlt_frame_get_alpha(frame); if (!p) { int alphasize = *width * *height; p = mlt_pool_alloc(alphasize); memset(p, 255, alphasize); mlt_frame_set_alpha(frame, p, alphasize, mlt_pool_release); } if (!use_luminance) { uint8_t *q = mlt_frame_get_alpha(mask); if (!q) { mlt_log_warning(MLT_FILTER_SERVICE(filter), "failed to get alpha channel from mask: %s\n", mlt_properties_get(MLT_FILTER_PROPERTIES(filter), "resource")); int alphasize = *width * *height; q = mlt_pool_alloc(alphasize); memset(q, 255, alphasize); mlt_frame_set_alpha(mask, q, alphasize, mlt_pool_release); } slice_desc desc = {.alpha = p, .mask = q, .width = *width, .height = *height, .softness = softness, .mix = mix, .invert = invert, .invert_mask = invert_mask, .offset = 0.0, .divisor = 255.0}; if (use_mix) { mlt_slices_run_normal(0, slice_alpha_proc, &desc); } else { const char *op = mlt_properties_get(MLT_FILTER_PROPERTIES(filter), "alpha_operation"); if (op && op[0] != '\0') { if (op[0] == 'a') mlt_slices_run_normal(0, slice_alpha_add, &desc); else if (op[0] == 's') mlt_slices_run_normal(0, slice_alpha_subtract, &desc); else if (!strncmp("ma", op, 2)) mlt_slices_run_normal(0, slice_alpha_maximum, &desc); else if (!strncmp("mi", op, 2)) mlt_slices_run_normal(0, slice_alpha_minimum, &desc); else mlt_slices_run_normal(0, slice_alpha_overwrite, &desc); } else { mlt_slices_run_normal(0, slice_alpha_overwrite, &desc); } } } else if (!use_mix) { // Do not apply threshold filter. uint8_t *q = mask_img; while (size--) { *p = *q ^ invert_mask; p++; q += 2; } } else if ((int) mix != 1 || invert == 255 || invert_mask == 255) { int full_range = mlt_properties_get_int(MLT_FRAME_PROPERTIES(frame), "full_range"); slice_desc desc = {.alpha = p, .mask = mask_img, .width = *width, .height = *height, .softness = softness * (1.0 - mix), .mix = mix, .invert = invert, .invert_mask = invert_mask, .offset = full_range ? 0.0 : 16.0, .divisor = full_range ? 255.0 : 235.0}; mlt_slices_run_normal(0, slice_luma_proc, &desc); } } } return 0; } /** Filter processing. */ static mlt_frame filter_process(mlt_filter filter, mlt_frame frame) { // Obtain the shape instance char *resource = mlt_properties_get(MLT_FILTER_PROPERTIES(filter), "resource"); if (!resource) return frame; char *last_resource = mlt_properties_get(MLT_FILTER_PROPERTIES(filter), "_resource"); mlt_producer producer = mlt_properties_get_data(MLT_FILTER_PROPERTIES(filter), "instance", NULL); // Calculate the position and length int position = mlt_filter_get_position(filter, frame); mlt_position length = mlt_filter_get_length2(filter, frame); // If we haven't created the instance or it's changed if (producer == NULL || !last_resource || strcmp(resource, last_resource)) { mlt_profile profile = mlt_service_profile(MLT_FILTER_SERVICE(filter)); char temp[PATH_MAX]; // Store the last resource now mlt_properties_set(MLT_FILTER_PROPERTIES(filter), "_resource", resource); // This is a hack - the idea is that we can indirectly reference the // luma modules pgm or png images by a short cut like %luma01.pgm - we then replace // the % with the full path to the image and use it if it exists, if not, check for // the file ending in a .png, and failing that, default to a fade in if (strchr(resource, '%')) { FILE *test; snprintf(temp, sizeof(temp), "%s/lumas/%s/%s", mlt_environment("MLT_DATA"), mlt_profile_lumas_dir(profile), strchr(resource, '%') + 1); test = mlt_fopen(temp, "r"); if (test == NULL) { strcat(temp, ".png"); test = mlt_fopen(temp, "r"); } if (test) { fclose(test); resource = temp; } } producer = mlt_factory_producer(profile, NULL, resource); if (producer != NULL) mlt_properties_set(MLT_PRODUCER_PROPERTIES(producer), "eof", "loop"); mlt_properties_set_data(MLT_FILTER_PROPERTIES(filter), "instance", producer, 0, (mlt_destructor) mlt_producer_close, NULL); } // We may still not have a producer in which case, we do nothing if (producer != NULL) { mlt_frame mask = NULL; double alpha_mix = mlt_properties_anim_get_double(MLT_FILTER_PROPERTIES(filter), "mix", position, length); mlt_properties_pass(MLT_PRODUCER_PROPERTIES(producer), MLT_FILTER_PROPERTIES(filter), "producer."); mlt_properties_clear(MLT_FILTER_PROPERTIES(filter), "producer.refresh"); mlt_producer_seek(producer, position); if (mlt_service_get_frame(MLT_PRODUCER_SERVICE(producer), &mask, 0) == 0) { char name[64]; snprintf(name, sizeof(name), "shape %s", mlt_properties_get(MLT_FILTER_PROPERTIES(filter), "_unique_id")); mlt_properties_set_data(MLT_FRAME_PROPERTIES(frame), name, mask, 0, (mlt_destructor) mlt_frame_close, NULL); mlt_frame_push_service(frame, filter); mlt_frame_push_service(frame, mask); mlt_deque_push_back_double(MLT_FRAME_IMAGE_STACK(frame), alpha_mix / 100.0); mlt_frame_push_get_image(frame, filter_get_image); if (mlt_properties_get_int(MLT_FILTER_PROPERTIES(filter), "audio_match")) { mlt_properties_set_int(MLT_FRAME_PROPERTIES(frame), "meta.mixdown", 1); mlt_properties_set_double(MLT_FRAME_PROPERTIES(frame), "meta.volume", alpha_mix / 100.0); } mlt_properties_set_int(MLT_FRAME_PROPERTIES(frame), "always_scale", 1); } } return frame; } /** Constructor for the filter. */ mlt_filter filter_shape_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = mlt_filter_new(); if (filter != NULL) { mlt_properties_set(MLT_FILTER_PROPERTIES(filter), "resource", arg); mlt_properties_set(MLT_FILTER_PROPERTIES(filter), "mix", "100"); mlt_properties_set_int(MLT_FILTER_PROPERTIES(filter), "use_mix", 1); mlt_properties_set_int(MLT_FILTER_PROPERTIES(filter), "audio_match", 1); mlt_properties_set_int(MLT_FILTER_PROPERTIES(filter), "invert", 0); mlt_properties_set_double(MLT_FILTER_PROPERTIES(filter), "softness", 0.1); filter->process = filter_process; } return filter; } mlt-7.22.0/src/modules/plus/filter_shape.yml000664 000000 000000 00000005373 14531534050 021000 0ustar00rootroot000000 000000 schema_version: 7.0 type: filter identifier: shape title: Shape Alpha version: 6 creator: Charles Yates license: LGPLv2.1 language: en tags: - Video parameters: - identifier: resource argument: yes title: File type: string description: > The name of a file or MLT producer URL. To use a luma wipe from the lumas module, put % in front of the base name of the luma file e.g. %luma16.pgm required: true mutable: yes - identifier: mix title: Threshold type: float description: > Convert alpha or luma values below this level as opaque and above this level as transparent. This is mostly useful for luma wipe images. unit: '%' minimum: 0 maximum: 100 default: 100 mutable: yes animation: yes - identifier: softness title: Softness type: float mutable: yes description: > When using mix (threshold) how soft to make the edge around the threshold. 0.0 = no softness, 1.0 = too soft minimum: 0 maximum: 1 default: 0.1 mutable: yes - identifier: invert title: Invert type: boolean description: Invert the resulting alpha channel. default: no mutable: yes - identifier: invert_mask title: Invert Mask description: Use the inverse of the resource's alpha channel or luma value. type: boolean default: no mutable: yes - identifier: reverse title: Reverse type: boolean description: > Use the complement of the mix level. This also inverts the output alpha, which is probably not what you want. See also invert_mask. default: no mutable: yes - identifier: use_luminance title: Use Luma type: boolean description: Use the image luma instead of the alpha channel. default: no mutable: yes - identifier: use_mix title: Use Threshold type: boolean description: > Whether to apply a threshold filter to the luma or alpha or not. If not, luma or alpha value of the resource (File) is copied to the alpha channel. default: yes mutable: yes - identifier: audio_match title: Audio volume follows Threshold type: boolean description: > This controls whether to also apply a volume level adjstment corresponding to the current mix property. The default is retained for legacy reason, but it is generally not recommended to enable this. default: yes mutable: yes - identifier: alpha_operation title: Alpha Operation type: string description: > The way to combine the alpha channel of the mask with the source, but this currently only works when use_mix = 0. default: overwrite mutable: yes values: - add - overwrite - maximum - minimum - subtract mlt-7.22.0/src/modules/plus/filter_spot_remover.c000664 000000 000000 00000021654 14531534050 022045 0ustar00rootroot000000 000000 /* * filter_remover.c -- filter to interpolate pixels to cover an area * Copyright (c) 2018-2020 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include /** Scale a rectangle by the specified factors. */ static mlt_rect scale_rect(mlt_rect rect, double x_scale, double y_scale) { rect.x = rect.x / x_scale; rect.y = rect.y / y_scale; rect.w = rect.w / x_scale; rect.h = rect.h / y_scale; return rect; } /** Constrain a rect to be within the max dimensions with an additional 1 pixel * padding. */ static mlt_rect constrain_rect(mlt_rect rect, int max_x, int max_y) { rect.x = round(rect.x); rect.y = round(rect.y); rect.w = round(rect.w); rect.h = round(rect.h); if (rect.x < 0) { rect.w = rect.w + rect.x - 1; rect.x = 1; } if (rect.y < 0) { rect.h = rect.h + rect.y - 1; rect.y = 1; } if (rect.x + rect.w < 0) { rect.w = 0; } if (rect.y + rect.h < 0) { rect.h = 0; } if (rect.x < 1) { rect.x = 1; } if (rect.y < 1) { rect.y = 1; } if (rect.x + rect.w > max_x - 1) { rect.w = max_x - rect.x - 1; } if (rect.y + rect.h > max_y - 1) { rect.h = max_y - rect.y - 1; } return rect; } typedef struct { uint8_t *chan[4]; // pointer to the first value in the channel int rowCount[4]; // the number of values in each line (row) int step[4]; // the space between values in each line mlt_rect rect[4]; // rect the area to be removed } slice_desc; /** Perform spot removal on a channel. * * Values within the rectangle are replaced with interpolated values. * Each value is an interpolation of the first values outside of the rect on * the top, bottom, left and right of the value being interpolated. */ static int remove_spot_channel_proc(int id, int index, int jobs, void *data) { (void) id; // unused (void) jobs; // unused slice_desc *desc = ((slice_desc *) data); uint8_t *chan = desc->chan[index]; int rowCount = desc->rowCount[index]; int step = desc->step[index]; mlt_rect rect = desc->rect[index]; int yStop = rect.y + rect.h; int xStop = rect.x + rect.w; int rowSize = rowCount * step; int y; for (y = rect.y; y < yStop; y++) { uint8_t *xValueL = chan + (y * rowSize) + (((int) rect.x - 1) * step); uint8_t *xValueR = xValueL + ((int) rect.w * step); uint8_t *p = chan + (y * rowSize) + ((int) rect.x * step); double yRatio = 1.0 - ((y - rect.y) / rect.h); int x; for (x = rect.x; x < xStop; x++) { uint8_t *yValueT = chan + (((int) rect.y - 1) * rowSize) + (x * step); uint8_t *yValueB = yValueT + (int) rect.h * rowSize; double xRatio = 1.0 - ((x - rect.x) / rect.w); unsigned int xValueInterp = (*xValueL * xRatio) + (*xValueR * (1.0 - xRatio)); unsigned int yValueInterp = (*yValueT * yRatio) + (*yValueB * (1.0 - yRatio)); unsigned int value = (xValueInterp + yValueInterp) / 2; if (value > 255) value = 255; *p = value; p += step; } } return 0; } static int filter_get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { int error = 0; mlt_filter filter = (mlt_filter) mlt_frame_pop_service(frame); mlt_properties filter_properties = MLT_FILTER_PROPERTIES(filter); char *rect_str = mlt_properties_get(filter_properties, "rect"); if (!rect_str) { mlt_log_warning(MLT_FILTER_SERVICE(filter), "rect property not set\n"); return mlt_frame_get_image(frame, image, format, width, height, writable); } mlt_profile profile = mlt_service_profile(MLT_FILTER_SERVICE(filter)); mlt_position position = mlt_filter_get_position(filter, frame); mlt_position length = mlt_filter_get_length2(filter, frame); mlt_rect rect = mlt_properties_anim_get_rect(filter_properties, "rect", position, length); if (strchr(rect_str, '%')) { rect.x *= profile->width; rect.w *= profile->width; rect.y *= profile->height; rect.h *= profile->height; } double scale = mlt_profile_scale_width(profile, *width); rect.x *= scale; rect.w *= scale; scale = mlt_profile_scale_height(profile, *height); rect.y *= scale; rect.h *= scale; rect = constrain_rect(rect, profile->width * scale, profile->height * scale); if (rect.w < 1 || rect.h < 1) { mlt_log_info(MLT_FILTER_SERVICE(filter), "rect invalid\n"); return mlt_frame_get_image(frame, image, format, width, height, writable); } switch (*format) { case mlt_image_rgba: case mlt_image_rgb: case mlt_image_yuv422: case mlt_image_yuv420p: // These formats are all supported break; default: *format = mlt_image_rgba; break; } error = mlt_frame_get_image(frame, image, format, width, height, 1); if (error) return error; struct mlt_image_s img; mlt_image_set_values(&img, *image, *format, *width, *height); // Process each plane in a separate thread. int i; slice_desc desc; int jobs = 0; switch (*format) { case mlt_image_rgba: jobs = 4; for (i = 0; i < 4; i++) { desc.chan[i] = img.planes[0] + i; desc.rowCount[i] = img.width; desc.step[i] = 4; desc.rect[i] = rect; } break; case mlt_image_rgb: jobs = 3; for (i = 0; i < 3; i++) { desc.chan[i] = img.planes[0] + i; desc.rowCount[i] = img.width; desc.step[i] = 4; desc.rect[i] = rect; } break; case mlt_image_yuv422: jobs = 3; // Y desc.chan[0] = img.planes[0]; desc.rowCount[0] = img.width; desc.step[0] = 2; desc.rect[0] = rect; // U desc.chan[1] = img.planes[0] + 1; desc.rowCount[1] = img.width / 2; desc.step[1] = 4; desc.rect[1] = constrain_rect(scale_rect(rect, 2, 1), img.width / 2, img.height); // V desc.chan[2] = img.planes[0] + 3; desc.rowCount[2] = img.width / 2; desc.step[2] = 4; desc.rect[2] = constrain_rect(scale_rect(rect, 2, 1), img.width / 2, img.height); break; case mlt_image_yuv420p: jobs = 3; // Y desc.chan[0] = img.planes[0]; desc.rowCount[0] = img.width; desc.step[0] = 1; desc.rect[0] = rect; // U desc.chan[1] = img.planes[1]; desc.rowCount[1] = img.width / 2; desc.step[1] = 1; desc.rect[1] = constrain_rect(scale_rect(rect, 2, 2), img.width / 2, img.height / 2); // V desc.chan[2] = img.planes[2]; desc.rowCount[2] = img.width / 2; desc.step[2] = 1; desc.rect[2] = constrain_rect(scale_rect(rect, 2, 2), img.width / 2, img.height / 2); break; default: return 1; } uint8_t *alpha = mlt_frame_get_alpha(frame); if (alpha && *format != mlt_image_rgba) { jobs++; desc.chan[3] = alpha; desc.rowCount[3] = img.width; desc.step[3] = 1; desc.rect[3] = rect; } mlt_slices_run_normal(jobs, remove_spot_channel_proc, &desc); return error; } static mlt_frame filter_process(mlt_filter filter, mlt_frame frame) { mlt_frame_push_service(frame, filter); mlt_frame_push_get_image(frame, filter_get_image); return frame; } mlt_filter filter_spot_remover_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = mlt_filter_new(); if (filter) { mlt_properties properties = MLT_FILTER_PROPERTIES(filter); mlt_properties_set(properties, "rect", "0% 0% 10% 10%"); filter->process = filter_process; } else { mlt_log_error(NULL, "Filter spot_remover initialization failed\n"); } return filter; } mlt-7.22.0/src/modules/plus/filter_spot_remover.yml000664 000000 000000 00000001255 14531534050 022417 0ustar00rootroot000000 000000 schema_version: 7.0 type: filter identifier: spot_remover title: Spot Remover version: 1 copyright: Meltytech, LLC license: LGPLv2.1 language: en tags: - Video description: > Replace an area with interpolated pixels. The new pixel values are interpolated from the nearest pixels immediately outside of the specified area. parameters: - identifier: rect title: Rectangle description: > Defines the rectangle of the area that will be removed. Format is: "X Y W H". X, Y, W, H are assumed to be pixel units unless they have the suffix '%'. type: rect default: "0 0 10% 10%" readonly: no mutable: yes animation: yes mlt-7.22.0/src/modules/plus/filter_strobe.c000664 000000 000000 00000006666 14531534050 020625 0ustar00rootroot000000 000000 /* * filter_strobe.c -- simple strobing filter * Copyright (C) 2020 Martin Sandsmark * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include #include #include static int filter_get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { mlt_filter filter = mlt_frame_pop_service(frame); mlt_properties properties = MLT_FILTER_PROPERTIES(filter); int error = mlt_frame_get_image(frame, image, format, width, height, 1); if (error) { return error; } mlt_position position = mlt_filter_get_position(filter, frame); mlt_position length = mlt_filter_get_length2(filter, frame); int invert = mlt_properties_anim_get_int(properties, "strobe_invert", position, length); int interval = mlt_properties_anim_get_int(properties, "interval", position, length); int do_strobe = (position % (interval + 1)) > interval / 2; if (invert) { do_strobe = !do_strobe; } if (do_strobe != 1) { return 0; } assert(*width >= 0); assert(*height >= 0); size_t pixelCount = *width * *height; if (*format == mlt_image_rgba) { uint8_t *bytes = *image; for (size_t i = 3; i < pixelCount * 4; i += 4) { bytes[i] = 0; } // Clear any alpha buffer that may be attached to the frame mlt_frame_set_alpha(frame, NULL, 0, NULL); } else { uint8_t *alpha = mlt_pool_alloc(pixelCount); memset(alpha, 0, pixelCount); mlt_frame_set_alpha(frame, alpha, pixelCount, mlt_pool_release); } return 0; } /** Filter processing. */ static mlt_frame filter_process(mlt_filter filter, mlt_frame frame) { // Push the filter on to the stack mlt_frame_push_service(frame, filter); // Push the frame filter mlt_frame_push_get_image(frame, filter_get_image); return frame; } /** Constructor for the filter. */ mlt_filter filter_strobe_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = mlt_filter_new(); if (filter != NULL) { filter->process = filter_process; // If strobe_invert == 1, the odd number of frames will be filtered out mlt_properties_set(MLT_FILTER_PROPERTIES(filter), "strobe_invert", "0"); mlt_properties_set(MLT_FILTER_PROPERTIES(filter), "interval", "1"); } return filter; } mlt-7.22.0/src/modules/plus/filter_strobe.yml000664 000000 000000 00000001350 14531534050 021165 0ustar00rootroot000000 000000 schema_version: 7.0 type: filter identifier: strobe title: Alpha strobing version: 1 copyright: Martin Sandsmark creator: Martin Sandsmark license: LGPLv2.1 language: en description: Strobes the alpha channel to 0. Many other filters overwrite the alpha channel, in that case this needs to be last. tags: - Video parameters: - identifier: strobe_invert title: Invert type: boolean description: Whether to invert which frames are on and which is off default: 0 mutable: yes animation: yes - identifier: interval title: Interval type: integer description: > Duration of strobe default: 1 minimum: 1 maximum: 100 readonly: no mutable: yes animation: yes widget: spinner mlt-7.22.0/src/modules/plus/filter_text.c000664 000000 000000 00000027322 14531534050 020303 0ustar00rootroot000000 000000 /* * filter_text.c -- text overlay filter * Copyright (C) 2018-2021 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include static void property_changed(mlt_service owner, mlt_filter filter, mlt_event_data event_data) { const char *name = mlt_event_data_to_string(event_data); if (!name) return; if (!strcmp("geometry", name) || !strcmp("family", name) || !strcmp("size", name) || !strcmp("weight", name) || !strcmp("style", name) || !strcmp("fgcolour", name) || !strcmp("bgcolour", name) || !strcmp("olcolour", name) || !strcmp("pad", name) || !strcmp("halign", name) || !strcmp("valign", name) || !strcmp("outline", name)) { mlt_properties_set_int(MLT_FILTER_PROPERTIES(filter), "_reset", 1); } } static void setup_producer(mlt_producer producer, mlt_properties my_properties) { mlt_properties producer_properties = MLT_PRODUCER_PROPERTIES(producer); // Pass the properties to the text producer mlt_properties_set_string(producer_properties, "family", mlt_properties_get(my_properties, "family")); mlt_properties_set_string(producer_properties, "size", mlt_properties_get(my_properties, "size")); mlt_properties_set_string(producer_properties, "weight", mlt_properties_get(my_properties, "weight")); mlt_properties_set_string(producer_properties, "style", mlt_properties_get(my_properties, "style")); mlt_properties_set_string(producer_properties, "fgcolour", mlt_properties_get(my_properties, "fgcolour")); mlt_properties_set_string(producer_properties, "bgcolour", mlt_properties_get(my_properties, "bgcolour")); mlt_properties_set_string(producer_properties, "olcolour", mlt_properties_get(my_properties, "olcolour")); mlt_properties_set_string(producer_properties, "pad", mlt_properties_get(my_properties, "pad")); mlt_properties_set_string(producer_properties, "outline", mlt_properties_get(my_properties, "outline")); mlt_properties_set_string(producer_properties, "align", mlt_properties_get(my_properties, "halign")); } static void setup_transition(mlt_filter filter, mlt_transition transition, mlt_frame frame, mlt_properties my_properties) { mlt_properties transition_properties = MLT_TRANSITION_PROPERTIES(transition); mlt_position position = mlt_filter_get_position(filter, frame); mlt_position length = mlt_filter_get_length2(filter, frame); mlt_service_lock(MLT_TRANSITION_SERVICE(transition)); mlt_rect rect = mlt_properties_anim_get_rect(my_properties, "geometry", position, length); if (mlt_properties_get(my_properties, "geometry") && strchr(mlt_properties_get(my_properties, "geometry"), '%')) { mlt_profile profile = mlt_service_profile(MLT_FILTER_SERVICE(filter)); rect.x *= profile->width; rect.y *= profile->height; rect.w *= profile->width; rect.h *= profile->height; } mlt_properties_set_rect(transition_properties, "rect", rect); mlt_properties_set_string(transition_properties, "halign", mlt_properties_get(my_properties, "halign")); mlt_properties_set_string(transition_properties, "valign", mlt_properties_get(my_properties, "valign")); mlt_service_unlock(MLT_TRANSITION_SERVICE(transition)); } static mlt_properties get_filter_properties(mlt_filter filter, mlt_frame frame) { mlt_properties properties = mlt_frame_get_unique_properties(frame, MLT_FILTER_SERVICE(filter)); if (!properties) properties = MLT_FILTER_PROPERTIES(filter); return properties; } /** Get the image. */ static int filter_get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { int error = 0; mlt_filter filter = mlt_frame_pop_service(frame); char *argument = (char *) mlt_frame_pop_service(frame); mlt_properties my_properties = MLT_FILTER_PROPERTIES(filter); mlt_properties properties = get_filter_properties(filter, frame); mlt_producer producer = mlt_properties_get_data(my_properties, "_producer", NULL); mlt_transition transition = mlt_properties_get_data(my_properties, "_transition", NULL); mlt_frame b_frame = 0; mlt_position position = 0; // Configure this filter mlt_service_lock(MLT_FILTER_SERVICE(filter)); if (mlt_properties_get_int(my_properties, "_reset")) { setup_producer(producer, properties); setup_transition(filter, transition, frame, properties); } mlt_properties_set_string(MLT_PRODUCER_PROPERTIES(producer), "text", argument); // Make sure the producer is in the correct position position = mlt_filter_get_position(filter, frame); mlt_producer_seek(producer, position); // Get the b frame and process with transition if successful if (mlt_service_get_frame(MLT_PRODUCER_SERVICE(producer), &b_frame, 0) == 0) { // This lock needs to also protect the producer properties from being // modified in setup_producer() while also being used in mlt_service_get_frame(). mlt_service_unlock(MLT_FILTER_SERVICE(filter)); // Get the a and b frame properties mlt_properties a_props = MLT_FRAME_PROPERTIES(frame); mlt_properties b_props = MLT_FRAME_PROPERTIES(b_frame); // Set the b_frame to be in the same position and have same consumer requirements mlt_frame_set_position(b_frame, position); mlt_properties_set_int(b_props, "consumer.progressive", mlt_properties_get_int(a_props, "consumer.progressive")); mlt_properties_set_double(b_props, "consumer_scale", mlt_properties_get_double(a_props, "consumer_scale")); // Apply all filters that are attached to this filter to the b frame mlt_service_apply_filters(MLT_FILTER_SERVICE(filter), b_frame, 0); // Process the frame mlt_transition_process(transition, frame, b_frame); // Get the image error = mlt_frame_get_image(frame, image, format, width, height, writable); // Close the temporary frames mlt_frame_close(b_frame); } else { mlt_service_unlock(MLT_FILTER_SERVICE(filter)); } free(argument); return error; } /** Filter processing. */ static mlt_frame filter_process(mlt_filter filter, mlt_frame frame) { mlt_properties properties = get_filter_properties(filter, frame); char *argument = mlt_properties_get(properties, "argument"); if (!argument || !strcmp("", argument)) return frame; // Save the text to be used by get_image() to support parallel processing // when this filter is encapsulated by other filters. mlt_frame_push_service(frame, strdup(argument)); // Push the filter on to the stack mlt_frame_push_service(frame, filter); // Push the get_image on to the stack mlt_frame_push_get_image(frame, filter_get_image); return frame; } /** Constructor for the filter. */ mlt_filter filter_text_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = mlt_filter_new(); mlt_transition transition = mlt_factory_transition(profile, "affine", NULL); mlt_producer producer = mlt_factory_producer(profile, mlt_environment("MLT_PRODUCER"), "qtext:"); // Use pango if qtext is not available. if (!producer) producer = mlt_factory_producer(profile, mlt_environment("MLT_PRODUCER"), "pango:"); if (!producer) mlt_log_warning(MLT_FILTER_SERVICE(filter), "QT or GTK modules required for text.\n"); if (filter && transition && producer) { mlt_properties my_properties = MLT_FILTER_PROPERTIES(filter); // Register the transition for reuse/destruction mlt_properties_set_int(MLT_TRANSITION_PROPERTIES(transition), "fill", 0); mlt_properties_set_int(MLT_TRANSITION_PROPERTIES(transition), "b_scaled", 1); mlt_properties_set_data(my_properties, "_transition", transition, 0, (mlt_destructor) mlt_transition_close, NULL); // Register the producer for reuse/destruction mlt_properties_set_data(my_properties, "_producer", producer, 0, (mlt_destructor) mlt_producer_close, NULL); // Ensure that we loop mlt_properties_set_string(MLT_PRODUCER_PROPERTIES(producer), "eof", "loop"); // Listen for property changes. mlt_events_listen(MLT_FILTER_PROPERTIES(filter), filter, "property-changed", (mlt_listener) property_changed); // Assign default values mlt_properties_set_string(my_properties, "argument", arg ? arg : "text"); mlt_properties_set_string(my_properties, "geometry", "0%/0%:100%x100%:100%"); mlt_properties_set_string(my_properties, "family", "Sans"); mlt_properties_set_string(my_properties, "size", "48"); mlt_properties_set_string(my_properties, "weight", "400"); mlt_properties_set_string(my_properties, "style", "normal"); mlt_properties_set_string(my_properties, "fgcolour", "0x000000ff"); mlt_properties_set_string(my_properties, "bgcolour", "0x00000020"); mlt_properties_set_string(my_properties, "olcolour", "0x00000000"); mlt_properties_set_string(my_properties, "pad", "0"); mlt_properties_set_string(my_properties, "halign", "left"); mlt_properties_set_string(my_properties, "valign", "top"); mlt_properties_set_string(my_properties, "outline", "0"); mlt_properties_set_int(my_properties, "_reset", 1); mlt_properties_set_int(my_properties, "_filter_private", 1); filter->process = filter_process; } else { if (filter) { mlt_filter_close(filter); } if (transition) { mlt_transition_close(transition); } if (producer) { mlt_producer_close(producer); } filter = NULL; } return filter; } mlt-7.22.0/src/modules/plus/filter_text.yml000664 000000 000000 00000006751 14531534050 020665 0ustar00rootroot000000 000000 schema_version: 7.0 type: filter identifier: text title: Text version: 1 copyright: Meltytech, LLC license: LGPLv2.1 language: en tags: - Video description: Overlay text onto the video parameters: - identifier: argument title: Text type: string description: | The text to overlay. required: yes argument: yes readonly: no default: text widget: text - identifier: geometry title: Geometry type: rect description: A set of X/Y coordinates by which to adjust the text. default: 0%/0%:100%x100%:100 mutable: yes animation: yes - identifier: family title: Font family type: string description: > The typeface of the font. default: Sans readonly: no mutable: yes widget: combo - identifier: size title: Font size type: integer description: > The size in pixels of the font. default: 48 readonly: no mutable: yes widget: spinner - identifier: style title: Font style type: string description: > The style of the font. values: - normal - italic default: normal readonly: no mutable: yes widget: combo - identifier: weight title: Font weight type: integer description: The weight of the font. minimum: 100 maximum: 1000 default: 400 readonly: no mutable: yes widget: spinner - identifier: fgcolour title: Foreground color type: string description: > A color value is a hexadecimal representation of RGB plus alpha channel as 0xrrggbbaa. Colors can also be the words: white, black, red, green, or blue. You can also use a HTML-style color values #rrggbb or #aarrggbb. default: 0x000000ff readonly: no mutable: yes widget: color - identifier: bgcolour title: Background color type: string description: > A color value is a hexadecimal representation of RGB plus alpha channel as 0xrrggbbaa. Colors can also be the words: white, black, red, green, or blue. You can also use a HTML-style color values #rrggbb or #aarrggbb. default: 0x00000020 readonly: no mutable: yes widget: color - identifier: olcolour title: Outline color type: string description: > A color value is a hexadecimal representation of RGB plus alpha channel as 0xrrggbbaa. Colors can also be the words: white, black, red, green, or blue. You can also use a HTML-style color values #rrggbb or #aarrggbb. readonly: no mutable: yes widget: color - identifier: outline title: Outline Width type: string description: > The width of the outline in pixels. readonly: no default: 0 minimum: 0 maximum: 3 mutable: yes widget: spinner - identifier: pad title: Padding type: integer description: > The number of pixels to pad the background rectangle beyond edges of text. readonly: no default: 0 mutable: yes widget: spinner - identifier: halign title: Horizontal alignment description: > Set the horizontal alignment within the geometry rectangle. type: string default: left values: - left - centre - right mutable: yes widget: combo - identifier: valign title: Vertical alignment description: > Set the vertical alignment within the geometry rectangle. type: string default: top values: - top - middle - bottom mutable: yes widget: combo mlt-7.22.0/src/modules/plus/filter_threshold.c000664 000000 000000 00000011203 14531534050 021302 0ustar00rootroot000000 000000 /* * filter_threshold.c -- Arbitrary alpha channel shaping * Copyright (C) 2008-2022 Meltytech, LLC * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include typedef struct { int midpoint; int use_alpha; int invert; int full_luma; uint8_t *image; uint8_t *alpha; int width; int height; } slice_desc; static int do_slice_proc(int id, int index, int jobs, void *data) { (void) id; // unused slice_desc *desc = (slice_desc *) data; int slice_line_start, slice_height = mlt_slices_size_slice(jobs, index, desc->height, &slice_line_start); int size = desc->width * slice_height * 2; uint8_t white = desc->full_luma ? 255 : 235; uint8_t black = desc->full_luma ? 0 : 16; uint8_t A = desc->invert ? white : black; uint8_t B = desc->invert ? black : white; uint8_t *p = desc->image + (slice_line_start * desc->width * 2); int i = 0; if (!desc->use_alpha) { for (i = 0; i < size; i += 2) { if (p[i] < desc->midpoint) p[i] = A; else p[i] = B; p[i + 1] = 128; } } else { if (desc->alpha) { uint8_t *alpha = desc->alpha + (slice_line_start * desc->width); for (i = 0; i < size; i += 2) { if (alpha[i / 2] < desc->midpoint) p[i] = A; else p[i] = B; p[i + 1] = 128; } } else { for (i = 0; i < size; i += 2) { p[i] = B; p[i + 1] = 128; } } } return 0; } /** Get the images and apply the luminance of the mask to the alpha of the frame. */ static int filter_get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { mlt_filter filter = mlt_frame_pop_service(frame); // Render the frame *format = mlt_image_yuv422; if (mlt_frame_get_image(frame, image, format, width, height, writable) == 0) { slice_desc desc; mlt_properties properties = mlt_filter_properties(filter); mlt_position position = mlt_filter_get_position(filter, frame); mlt_position length = mlt_filter_get_length2(filter, frame); desc.midpoint = mlt_properties_anim_get_int(properties, "midpoint", position, length); desc.use_alpha = mlt_properties_get_int(properties, "use_alpha"); desc.invert = mlt_properties_get_int(properties, "invert"); desc.full_luma = mlt_properties_get_int(MLT_FRAME_PROPERTIES(frame), "full_luma"); desc.image = *image; desc.alpha = NULL; desc.width = *width; desc.height = *height; if (desc.use_alpha) { desc.alpha = mlt_frame_get_alpha(frame); } mlt_slices_run_normal(0, do_slice_proc, &desc); } return 0; } /** Filter processing. */ static mlt_frame filter_process(mlt_filter filter, mlt_frame frame) { mlt_frame_push_service(frame, filter); mlt_frame_push_get_image(frame, filter_get_image); return frame; } /** Constructor for the filter. */ mlt_filter filter_threshold_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = mlt_filter_new(); if (filter != NULL) { mlt_properties_set_int(MLT_FILTER_PROPERTIES(filter), "midpoint", 128); mlt_properties_set_int(MLT_FILTER_PROPERTIES(filter), "use_alpha", 0); mlt_properties_set_int(MLT_FILTER_PROPERTIES(filter), "invert", 0); filter->process = filter_process; } return filter; } mlt-7.22.0/src/modules/plus/filter_threshold.yml000664 000000 000000 00000001315 14531534050 021664 0ustar00rootroot000000 000000 schema_version: 0.3 type: filter identifier: threshold title: Threshold version: 2 creator: Charles Yates license: LGPLv2.1 language: en tags: - Video parameters: - identifier: midpoint title: Threshold type: integer description: The value of the threshold mutable: yes minimum: 0 maximum: 255 default: 128 - identifier: use_alpha title: Use alpha channel type: boolean description: Whether to compare the midpoint with the alpha channel instead of luma mutable: yes default: 0 widget: checkbox - identifier: invert title: Invert type: integer description: Whether to swap black and white mutable: yes default: 0 widget: checkbox mlt-7.22.0/src/modules/plus/filter_timer.c000664 000000 000000 00000016675 14531534050 020450 0ustar00rootroot000000 000000 /* * filter_timer.c -- timer text overlay filter * Copyright (C) 2018-2019 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #define MAX_TEXT_LEN 512 double time_to_seconds(char *time) { int hours = 0; int mins = 0; double secs = 0; if (time) sscanf(time, "%d:%d:%lf", &hours, &mins, &secs); return (hours * 60.0 * 60.0) + (mins * 60.0) + secs; } static void get_timer_str(mlt_filter filter, mlt_frame frame, char *text) { mlt_properties properties = MLT_FILTER_PROPERTIES(filter); double filter_speed = mlt_properties_get_double(properties, "speed"); mlt_position current_frame = mlt_filter_get_position(filter, frame) * filter_speed; char *direction = mlt_properties_get(properties, "direction"); double timer_start = time_to_seconds(mlt_properties_get(properties, "start")); double timer_duration = time_to_seconds(mlt_properties_get(properties, "duration")); double timer_offset = time_to_seconds(mlt_properties_get(properties, "offset")); double value = time_to_seconds( mlt_properties_frames_to_time(properties, current_frame, mlt_time_clock)); if (timer_duration <= 0.0) { // "duration" of zero means entire length of the filter. mlt_position filter_length = mlt_filter_get_length2(filter, frame) - 1; double filter_duration = time_to_seconds( mlt_properties_frames_to_time(properties, filter_length, mlt_time_clock)); timer_duration = (filter_duration - timer_start) * filter_speed; } if (value < timer_start * filter_speed) { // Hold at 0 until start time. value = 0.0; } else { value = value - timer_start * filter_speed; if (value > timer_duration) { // Hold at duration after the timer has elapsed. value = timer_duration; } } // Apply direction. if (direction && !strcmp(direction, "down")) { value = timer_duration - value; } // Apply offset value += timer_offset; int hours = value / (60 * 60); int mins = (value / 60) - (hours * 60); double secs = value - (double) (mins * 60) - (double) (hours * 60 * 60); char *format = mlt_properties_get(properties, "format"); if (!strcmp(format, "HH:MM:SS")) { snprintf(text, MAX_TEXT_LEN, "%02d:%02d:%02d", hours, mins, (int) floor(secs)); } else if (!strcmp(format, "HH:MM:SS.S")) { snprintf(text, MAX_TEXT_LEN, "%02d:%02d:%04.1f", hours, mins, floor(secs * 10.0) / 10.0); } else if (!strcmp(format, "MM:SS")) { snprintf(text, MAX_TEXT_LEN, "%02d:%02d", hours * 60 + mins, (int) floor(secs)); } else if (!strcmp(format, "MM:SS.SS")) { snprintf(text, MAX_TEXT_LEN, "%02d:%05.2f", hours * 60 + mins, floor(secs * 100.0) / 100.0); } else if (!strcmp(format, "MM:SS.SSS")) { snprintf(text, MAX_TEXT_LEN, "%02d:%06.3f", hours * 60 + mins, floor(secs * 1000.0) / 1000.0); } else if (!strcmp(format, "SS")) { snprintf(text, MAX_TEXT_LEN, "%02d", (int) floor(value)); } else if (!strcmp(format, "SS.S")) { snprintf(text, MAX_TEXT_LEN, "%04.1f", floor(value * 10.0) / 10.0); } else if (!strcmp(format, "SS.SS")) { snprintf(text, MAX_TEXT_LEN, "%05.2f", floor(value * 100.0) / 100.0); } else if (!strcmp(format, "SS.SSS")) { snprintf(text, MAX_TEXT_LEN, "%06.3f", floor(value * 1000.0) / 1000.0); } } static mlt_frame filter_process(mlt_filter filter, mlt_frame frame) { mlt_properties properties = MLT_FILTER_PROPERTIES(filter); mlt_filter text_filter = mlt_properties_get_data(properties, "_text_filter", NULL); mlt_properties text_filter_properties = mlt_frame_unique_properties(frame, MLT_FILTER_SERVICE(text_filter)); char *result = calloc(1, MAX_TEXT_LEN); get_timer_str(filter, frame, result); mlt_properties_set(text_filter_properties, "argument", result); free(result); mlt_properties_pass_list(text_filter_properties, properties, "geometry family size weight style fgcolour bgcolour olcolour pad " "halign valign outline opacity"); mlt_filter_set_in_and_out(text_filter, mlt_filter_get_in(filter), mlt_filter_get_out(filter)); return mlt_filter_process(text_filter, frame); } /** Constructor for the filter. */ mlt_filter filter_timer_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = mlt_filter_new(); mlt_filter text_filter = mlt_factory_filter(profile, "qtext", NULL); if (!text_filter) text_filter = mlt_factory_filter(profile, "text", NULL); if (!text_filter) mlt_log_warning(MLT_FILTER_SERVICE(filter), "Unable to create text filter.\n"); if (filter && text_filter) { mlt_properties my_properties = MLT_FILTER_PROPERTIES(filter); // Register the text filter for reuse/destruction mlt_properties_set_data(my_properties, "_text_filter", text_filter, 0, (mlt_destructor) mlt_filter_close, NULL); // Assign default values mlt_properties_set(my_properties, "format", "SS.SS"); mlt_properties_set(my_properties, "start", "00:00:00.000"); mlt_properties_set(my_properties, "duration", "00:10:00.000"); mlt_properties_set(my_properties, "offset", "00:00:00.000"); mlt_properties_set_double(my_properties, "speed", 1.0); mlt_properties_set(my_properties, "direction", "up"); mlt_properties_set(my_properties, "geometry", "0%/0%:100%x100%:100%"); mlt_properties_set(my_properties, "family", "Sans"); mlt_properties_set(my_properties, "size", "48"); mlt_properties_set(my_properties, "weight", "400"); mlt_properties_set(my_properties, "style", "normal"); mlt_properties_set(my_properties, "fgcolour", "0x000000ff"); mlt_properties_set(my_properties, "bgcolour", "0x00000020"); mlt_properties_set(my_properties, "olcolour", "0x00000000"); mlt_properties_set(my_properties, "pad", "0"); mlt_properties_set(my_properties, "halign", "left"); mlt_properties_set(my_properties, "valign", "top"); mlt_properties_set(my_properties, "outline", "0"); mlt_properties_set_string(my_properties, "opacity", "1.0"); mlt_properties_set_int(my_properties, "_filter_private", 1); filter->process = filter_process; } else { if (filter) { mlt_filter_close(filter); } if (text_filter) { mlt_filter_close(text_filter); } filter = NULL; } return filter; } mlt-7.22.0/src/modules/plus/filter_timer.yml000664 000000 000000 00000013527 14531534050 021020 0ustar00rootroot000000 000000 schema_version: 0.1 type: filter identifier: timer title: Timer version: 2 copyright: Meltytech, LLC license: LGPLv2.1 language: en tags: - Video description: Overlay a timer onto the video. The timer can count up or down. parameters: - identifier: format title: Format type: string description: > The time format of the overlaid timer text. values: - HH:MM:SS - HH:MM:SS.S - MM:SS - MM:SS.SS - MM:SS.SSS - SS - SS.S - SS.SS - SS.SSS default: SS.SS readonly: no mutable: yes widget: combo - identifier: start title: Start type: string description: > The time that the timer will start counting up or down. The text will be frozen at 00:00:00.000 from the start of the filter until the start time has elapsed. Must be in the format HH:MM:SS.SSS default: 00:00:00.000 readonly: no mutable: yes widget: text - identifier: duration title: Duration type: string description: > The maximum elapsed duration of the timer after the start time has elapsed. The text will be frozen at the duration time after the duration has elapsed. Must be in the format HH:MM:SS.SSS default: 00:00:10.000 readonly: no mutable: yes widget: text - identifier: offset title: Offset type: string description: > An offset to be added to the timer value. When the direction is "down", the timer will count down to "offset" instead of 00:00:00.000. When the direction is up, the timer will count up starting from "offset". Must be in the format HH:MM:SS.SSS default: 00:00:00.000 readonly: no mutable: yes widget: text - identifier: speed title: Speed type: float description: > Clock speed multiplier. For example, speed 10.0 makes the timer tick 10 seconds for each second of playback. Scales Duration but does not affect Start or Offset. For example: start 5s, duration 30s, offset 7s and speed 10.0 will have the timer start at playback time 00:00:05.000 with value 00:00:07, count 10 seconds per second of playback and stop at playback time 00:00:08.000 with value 00:00:37. default: 1.0 readonly: no mutable: yes - identifier: direction title: Direction type: string description: > Whether the counter should count up from 00:00:00.000 or down from the duration time. values: - up - down default: up readonly: no mutable: yes widget: combo - identifier: geometry title: Geometry type: rect description: A set of X/Y coordinates by which to adjust the text. default: 0%/0%:100%x100%:100 - identifier: family title: Font family type: string description: > The typeface of the font. default: Sans readonly: no mutable: yes widget: combo - identifier: size title: Font size type: integer description: > The size in pixels of the font. default: 48 readonly: no mutable: yes widget: spinner - identifier: style title: Font style type: string description: > The style of the font. values: - normal - italic default: normal readonly: no mutable: yes widget: combo - identifier: weight title: Font weight type: integer description: The weight of the font. minimum: 100 maximum: 1000 default: 400 readonly: no mutable: yes widget: spinner - identifier: fgcolour title: Foreground color type: string description: > A color value is a hexadecimal representation of RGB plus alpha channel as 0xrrggbbaa. Colors can also be the words: white, black, red, green, or blue. You can also use a HTML-style color values #rrggbb or #aarrggbb. default: 0x000000ff readonly: no mutable: yes widget: color - identifier: bgcolour title: Background color type: string description: > A color value is a hexadecimal representation of RGB plus alpha channel as 0xrrggbbaa. Colors can also be the words: white, black, red, green, or blue. You can also use a HTML-style color values #rrggbb or #aarrggbb. default: 0x00000020 readonly: no mutable: yes widget: color - identifier: olcolour title: Outline color type: string description: > A color value is a hexadecimal representation of RGB plus alpha channel as 0xrrggbbaa. Colors can also be the words: white, black, red, green, or blue. You can also use a HTML-style color values #rrggbb or #aarrggbb. readonly: no mutable: yes widget: color - identifier: outline title: Outline Width type: string description: > The width of the outline in pixels. readonly: no default: 0 minimum: 0 maximum: 3 mutable: yes widget: spinner - identifier: pad title: Padding type: integer description: > The number of pixels to pad the background rectangle beyond edges of text. readonly: no default: 0 mutable: yes widget: spinner - identifier: halign title: Horizontal alignment description: > Set the horizontal alignment within the geometry rectangle. type: string default: left values: - left - centre - right mutable: yes widget: combo - identifier: valign title: Vertical alignment description: > Set the vertical alignment within the geometry rectangle. type: string default: top values: - top - middle - bottom mutable: yes widget: combo - identifier: opacity title: Opacity type: float description: Opacity of all elements - text, outline, and background readonly: no default: 1.0 minimum: 0 maximum: 1.0 mutable: yes widget: slider mlt-7.22.0/src/modules/plus/interp.h000664 000000 000000 00000012536 14531534050 017261 0ustar00rootroot000000 000000 //interp.c /* * Copyright (C) 2010 Marko Cebokli http://lea.hamradio.si/~s57uuu * Copyright (C) 2010-2022 Meltytech, LLC * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include #include #include //#define TEST_XY_LIMITS //-------------------------------------------------------- // pointer to an interpolating function // parameters: // source image // source width // source height // X coordinate // Y coordinate // opacity // destination image // flag to overwrite alpha channel typedef int (*interpp)(unsigned char *, int, int, float, float, float, unsigned char *, int); // nearest neighbor int interpNN_b32( unsigned char *s, int w, int h, float x, float y, float o, unsigned char *d, int is_atop) { #ifdef TEST_XY_LIMITS if ((x < 0) || (x >= w) || (y < 0) || (y >= h)) return -1; #endif int p = (int) rintf(x) * 4 + (int) rintf(y) * 4 * w; float alpha_s = (float) s[p + 3] / 255.0f * o; float alpha_d = (float) d[3] / 255.0f; float alpha = alpha_s + alpha_d - alpha_s * alpha_d; d[3] = is_atop ? s[p + 3] : (255 * alpha); alpha = alpha_s / alpha; d[0] = d[0] * (1.0f - alpha) + s[p] * alpha; d[1] = d[1] * (1.0f - alpha) + s[p + 1] * alpha; d[2] = d[2] * (1.0f - alpha) + s[p + 2] * alpha; return 0; } // bilinear int interpBL_b32( unsigned char *s, int w, int h, float x, float y, float o, unsigned char *d, int is_atop) { int m, n, k, l, n1, l1, k1; float a, b; #ifdef TEST_XY_LIMITS if ((x < 0) || (x >= w) || (y < 0) || (y >= h)) return -1; #endif m = (int) floorf(x); if (m + 2 > w) m = w - 2; n = (int) floorf(y); if (n + 2 > h) n = h - 2; k = n * w + m; l = (n + 1) * w + m; k1 = 4 * (k + 1); l1 = 4 * (l + 1); n1 = 4 * ((n + 1) * w + m); l = 4 * l; k = 4 * k; a = s[k + 3] + (s[k1 + 3] - s[k + 3]) * (x - (float) m); b = s[l + 3] + (s[l1 + 3] - s[n1 + 3]) * (x - (float) m); float alpha_s = a + (b - a) * (y - (float) n); float alpha_d = (float) d[3] / 255.0f; if (is_atop) d[3] = alpha_s; alpha_s = alpha_s / 255.0f * o; float alpha = alpha_s + alpha_d - alpha_s * alpha_d; if (!is_atop) d[3] = 255 * alpha; alpha = alpha_s / alpha; a = s[k] + (s[k1] - s[k]) * (x - (float) m); b = s[l] + (s[l1] - s[n1]) * (x - (float) m); d[0] = d[0] * (1.0f - alpha) + (a + (b - a) * (y - (float) n)) * alpha; a = s[k + 1] + (s[k1 + 1] - s[k + 1]) * (x - (float) m); b = s[l + 1] + (s[l1 + 1] - s[n1 + 1]) * (x - (float) m); d[1] = d[1] * (1.0f - alpha) + (a + (b - a) * (y - (float) n)) * alpha; a = s[k + 2] + (s[k1 + 2] - s[k + 2]) * (x - (float) m); b = s[l + 2] + (s[l1 + 2] - s[n1 + 2]) * (x - (float) m); d[2] = d[2] * (1.0f - alpha) + (a + (b - a) * (y - (float) n)) * alpha; return 0; } // bicubic int interpBC_b32( unsigned char *s, int w, int h, float x, float y, float o, unsigned char *d, int is_atop) { int i, j, b, l, m, n; float k; float p[4], p1[4], p2[4], p3[4], p4[4]; float alpha = 1.0; #ifdef TEST_XY_LIMITS if ((x < 0) || (x >= w) || (y < 0) || (y >= h)) return -1; #endif m = (int) ceilf(x) - 2; if (m < 0) m = 0; if ((m + 5) > w) m = w - 4; n = (int) ceilf(y) - 2; if (n < 0) n = 0; if ((n + 5) > h) n = h - 4; for (b = 3; b > -1; b--) { // first after y (four columns) for (i = 0; i < 4; i++) { l = m + (i + n) * w; p1[i] = s[4 * l + b]; p2[i] = s[4 * (l + 1) + b]; p3[i] = s[4 * (l + 2) + b]; p4[i] = s[4 * (l + 3) + b]; } for (j = 1; j < 4; j++) for (i = 3; i >= j; i--) { k = (y - i - n) / j; p1[i] = p1[i] + k * (p1[i] - p1[i - 1]); p2[i] = p2[i] + k * (p2[i] - p2[i - 1]); p3[i] = p3[i] + k * (p3[i] - p3[i - 1]); p4[i] = p4[i] + k * (p4[i] - p4[i - 1]); } // now after x p[0] = p1[3]; p[1] = p2[3]; p[2] = p3[3]; p[3] = p4[3]; for (j = 1; j < 4; j++) for (i = 3; i >= j; i--) p[i] = p[i] + (x - i - m) / j * (p[i] - p[i - 1]); if (p[3] < 0.0f) p[3] = 0.0f; if (p[3] > 255.0f) p[3] = 255.0f; if (b == 3) { float alpha_s = (float) p[3] / 255.0f * o; float alpha_d = (float) d[3] / 255.0f; alpha = alpha_s + alpha_d - alpha_s * alpha_d; d[3] = is_atop ? p[3] : (255 * alpha); alpha = alpha_s / alpha; } else { d[b] = d[b] * (1.0f - alpha) + p[3] * alpha; } } return 0; } mlt-7.22.0/src/modules/plus/producer_blipflash.c000664 000000 000000 00000025124 14531534050 021617 0ustar00rootroot000000 000000 /* * producer_blipflash.c -- blip/flash generating producer * Copyright (C) 2013 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include /** Fill an audio buffer with 1kHz "blip" samples. */ static void fill_blip( mlt_properties producer_properties, float *buffer, int frequency, int channels, int samples) { int new_size = samples * channels * sizeof(float); int old_size = 0; float *blip = mlt_properties_get_data(producer_properties, "_blip", &old_size); if (!blip || new_size > old_size) { blip = mlt_pool_alloc(new_size); // Fill the blip buffer if (blip != NULL) { int s = 0; int c = 0; for (s = 0; s < samples; s++) { float f = 1000.0; float t = (float) s / (float) frequency; // Add 90 deg so the blip always starts at 1 for easy detection. float phase = M_PI / 2; float value = sin(2 * M_PI * f * t + phase); for (c = 0; c < channels; c++) { float *sample_ptr = ((float *) blip) + (c * samples) + s; *sample_ptr = value; } } } // Cache the audio blip to save from regenerating it with every blip. mlt_properties_set_data(producer_properties, "_blip", blip, new_size, mlt_pool_release, NULL); }; if (blip) memcpy(buffer, blip, new_size); } static int producer_get_audio(mlt_frame frame, int16_t **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples) { mlt_producer producer = mlt_properties_get_data(MLT_FRAME_PROPERTIES(frame), "_producer_blipflash", NULL); mlt_properties producer_properties = MLT_PRODUCER_PROPERTIES(producer); int size = *samples * *channels * sizeof(float); double fps = mlt_producer_get_fps(producer); int frames = mlt_frame_get_position(frame) + mlt_properties_get_int(producer_properties, "offset"); int seconds = frames / fps; // Correct the returns if necessary *format = mlt_audio_float; *frequency = *frequency <= 0 ? 48000 : *frequency; *channels = *channels <= 0 ? 2 : *channels; *samples = *samples <= 0 ? mlt_audio_calculate_frame_samples(fps, *frequency, frames) : *samples; // Allocate the buffer *buffer = mlt_pool_alloc(size); // Determine if this should be a blip or silence. frames = frames % lrint(fps); seconds = seconds % mlt_properties_get_int(producer_properties, "period"); if (seconds == 0 && frames == 0) { fill_blip(producer_properties, (float *) *buffer, *frequency, *channels, *samples); } else { // Fill silence. memset(*buffer, 0, size); } // Set the buffer for destruction mlt_frame_set_audio(frame, *buffer, *format, size, mlt_pool_release); return 0; } /** Fill an image buffer with either white (flash) or black as requested. */ static void fill_image(mlt_properties producer_properties, char *color, uint8_t *buffer, mlt_image_format format, int width, int height) { int new_size = mlt_image_format_size(format, width, height, NULL); int old_size = 0; uint8_t *image = mlt_properties_get_data(producer_properties, color, &old_size); if (!image || new_size > old_size) { // Need to create a new cached image. image = mlt_pool_alloc(new_size); if (image != NULL) { uint8_t r, g, b; uint8_t *p = image; if (!strcmp(color, "_flash")) { r = g = b = 255; // White } else { r = g = b = 0; // Black } switch (format) { default: case mlt_image_yuv422: { int uneven = width % 2; int count = (width - uneven) / 2 + 1; uint8_t y, u, v; RGB2YUV_601_SCALED(r, g, b, y, u, v); int i = height + 1; while (--i) { int j = count; while (--j) { *p++ = y; *p++ = u; *p++ = y; *p++ = v; } if (uneven) { *p++ = y; *p++ = u; } } break; } case mlt_image_rgb: { int i = width * height + 1; while (--i) { *p++ = r; *p++ = g; *p++ = b; } break; } case mlt_image_rgba: { int i = width * height + 1; while (--i) { *p++ = r; *p++ = g; *p++ = b; *p++ = 255; // alpha } break; } } // Cache the image to save from regenerating it with every frame. mlt_properties_set_data(producer_properties, color, image, new_size, mlt_pool_release, NULL); } } if (image) memcpy(buffer, image, new_size); } static int producer_get_image(mlt_frame frame, uint8_t **buffer, mlt_image_format *format, int *width, int *height, int writable) { mlt_properties properties = MLT_FRAME_PROPERTIES(frame); mlt_producer producer = mlt_properties_get_data(MLT_FRAME_PROPERTIES(frame), "_producer_blipflash", NULL); mlt_properties producer_properties = MLT_PRODUCER_PROPERTIES(producer); int size = 0; double fps = mlt_producer_get_fps(producer); int frames = mlt_frame_get_position(frame); int seconds = frames / fps; mlt_service_lock(MLT_PRODUCER_SERVICE(producer)); // Correct the returns if necessary if (*format != mlt_image_yuv422 && *format != mlt_image_rgb && *format != mlt_image_rgba) *format = mlt_image_yuv422; if (*width <= 0) *width = mlt_service_profile(MLT_PRODUCER_SERVICE(producer))->width; if (*height <= 0) *height = mlt_service_profile(MLT_PRODUCER_SERVICE(producer))->height; // Allocate the buffer size = mlt_image_format_size(*format, *width, *height, NULL); *buffer = mlt_pool_alloc(size); // Determine if this should be a flash or black. frames = frames % lrint(fps); seconds = seconds % mlt_properties_get_int(producer_properties, "period"); if (seconds == 0 && frames == 0) { fill_image(producer_properties, "_flash", *buffer, *format, *width, *height); } else { fill_image(producer_properties, "_black", *buffer, *format, *width, *height); } mlt_service_unlock(MLT_PRODUCER_SERVICE(producer)); // Create the alpha channel int alpha_size = *width * *height; uint8_t *alpha = mlt_pool_alloc(alpha_size); if (alpha) memset(alpha, 255, alpha_size); // Update the frame mlt_frame_set_image(frame, *buffer, size, mlt_pool_release); mlt_frame_set_alpha(frame, alpha, alpha_size, mlt_pool_release); mlt_properties_set_double(properties, "aspect_ratio", mlt_properties_get_double(producer_properties, "aspect_ratio")); mlt_properties_set_int(properties, "progressive", 1); mlt_properties_set_int(properties, "meta.media.width", *width); mlt_properties_set_int(properties, "meta.media.height", *height); return 0; } static int producer_get_frame(mlt_producer producer, mlt_frame_ptr frame, int index) { // Generate a frame *frame = mlt_frame_init(MLT_PRODUCER_SERVICE(producer)); if (*frame != NULL) { // Obtain properties of frame mlt_properties frame_properties = MLT_FRAME_PROPERTIES(*frame); // Save the producer to be used later mlt_properties_set_data(frame_properties, "_producer_blipflash", producer, 0, NULL, NULL); // Update time code on the frame mlt_frame_set_position(*frame, mlt_producer_position(producer)); // Configure callbacks mlt_frame_push_get_image(*frame, producer_get_image); mlt_frame_push_audio(*frame, producer_get_audio); } // Calculate the next time code mlt_producer_prepare_next(producer); return 0; } static void producer_close(mlt_producer this) { this->close = NULL; mlt_producer_close(this); free(this); } /** Initialize. */ mlt_producer producer_blipflash_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { // Create a new producer object mlt_producer producer = mlt_producer_new(profile); mlt_properties producer_properties = MLT_PRODUCER_PROPERTIES(producer); // Initialize the producer if (producer) { mlt_properties_set_int(producer_properties, "period", 1); mlt_properties_set_int(producer_properties, "offset", 0); // Callback registration producer->get_frame = producer_get_frame; producer->close = (mlt_destructor) producer_close; } return producer; } mlt-7.22.0/src/modules/plus/producer_blipflash.yml000664 000000 000000 00000001522 14531534050 022172 0ustar00rootroot000000 000000 schema_version: 0.1 type: producer identifier: blipflash title: Blip Flash version: 1 copyright: Meltytech, LLC license: LGPLv2.1 language: en tags: - Audio - Video description: > Generate periodic synchronized audio blips and video flashes. Blips are a 1kHz tone and last the duration of the flash frame. parameters: - identifier: period title: Flash Period type: integer description: > The period between flashes in seconds. default: 1 readonly: no mutable: yes widget: spinner - identifier: offset title: Audio Offset type: integer description: > The number of frames to offset the audio. A positive number results in audio earlier than video. An negative number results in audio later than video. default: 0 readonly: no mutable: yes widget: spinner mlt-7.22.0/src/modules/plus/producer_count.c000664 000000 000000 00000056651 14531534050 021014 0ustar00rootroot000000 000000 /* * producer_count.c -- counting producer * Copyright (C) 2013-2023 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include /* Private Constants */ #define MAX_TEXT_LEN 512 #define LINE_PIXEL_VALUE 0x00 #define RING_PIXEL_VALUE 0xff #define CLOCK_PIXEL_VALUE 0x50 #define FRAME_BACKGROUND_COLOR "0xd0d0d0ff" #define TEXT_BACKGROUND_COLOR "0x00000000" #define TEXT_FOREGROUND_COLOR "0x000000ff" // Ratio of graphic elements relative to image size #define LINE_WIDTH_RATIO 1 #define OUTER_RING_RATIO 90 #define INNER_RING_RATIO 80 #define TEXT_SIZE_RATIO 70 typedef struct { mlt_position position; int fps; int hours; int minutes; int seconds; int frames; char sep; // Either : or ; (for ndf) } time_info; static void get_time_info(mlt_producer producer, mlt_frame frame, time_info *info) { mlt_properties producer_properties = MLT_PRODUCER_PROPERTIES(producer); mlt_position position = mlt_frame_original_position(frame); info->fps = ceil(mlt_producer_get_fps(producer)); char *direction = mlt_properties_get(producer_properties, "direction"); if (!strcmp(direction, "down")) { mlt_position length = mlt_properties_get_int(producer_properties, "length"); info->position = length - 1 - position; } else { info->position = position; } char *tc_str = NULL; if (mlt_properties_get_int(producer_properties, "drop")) { tc_str = mlt_properties_frames_to_time(producer_properties, info->position, mlt_time_smpte_df); } else { tc_str = mlt_properties_frames_to_time(producer_properties, info->position, mlt_time_smpte_ndf); } sscanf(tc_str, "%02d:%02d:%02d%c%d", &info->hours, &info->minutes, &info->seconds, &info->sep, &info->frames); } static inline void mix_pixel(uint8_t *image, int width, int x, int y, int value, float mix) { uint8_t *p = image + ((y * width) + x) * 4; if (mix != 1.0) { value = ((float) value * mix) + ((float) *p * (1.0 - mix)); } *p = value; p++; *p = value; p++; *p = value; } /** Fill an audio buffer with 1kHz samples. */ static void fill_beep( mlt_properties producer_properties, float *buffer, int frequency, int channels, int samples) { int s = 0; int c = 0; for (s = 0; s < samples; s++) { float f = 1000.0; float t = (float) s / (float) frequency; float value = sin(2 * M_PI * f * t); for (c = 0; c < channels; c++) { float *sample_ptr = buffer + (c * samples) + s; *sample_ptr = value; } } } static int producer_get_audio(mlt_frame frame, int16_t **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples) { mlt_producer producer = (mlt_producer) mlt_frame_pop_audio(frame); mlt_properties producer_properties = MLT_PRODUCER_PROPERTIES(producer); char *sound = mlt_properties_get(producer_properties, "sound"); double fps = mlt_producer_get_fps(producer); mlt_position position = mlt_frame_original_position(frame); int size = 0; int do_beep = 0; time_info info; if (fps == 0) fps = 25; // Correct the returns if necessary *format = mlt_audio_float; *frequency = *frequency <= 0 ? 48000 : *frequency; *channels = *channels <= 0 ? 2 : *channels; *samples = *samples <= 0 ? mlt_audio_calculate_frame_samples(fps, *frequency, position) : *samples; // Allocate the buffer size = *samples * *channels * sizeof(float); *buffer = mlt_pool_alloc(size); mlt_service_lock(MLT_PRODUCER_SERVICE(producer)); get_time_info(producer, frame, &info); // Determine if this should be a tone or silence. if (strcmp(sound, "none")) { if (!strcmp(sound, "2pop")) { mlt_position out = mlt_properties_get_int(producer_properties, "out"); mlt_position frames = out - position; if (frames == (info.fps * 2)) { do_beep = 1; } } else if (!strcmp(sound, "frame0")) { if (info.frames == 0) { do_beep = 1; } } } if (do_beep) { fill_beep(producer_properties, (float *) *buffer, *frequency, *channels, *samples); } else { // Fill silence. memset(*buffer, 0, size); } mlt_service_unlock(MLT_PRODUCER_SERVICE(producer)); // Set the buffer for destruction mlt_frame_set_audio(frame, *buffer, *format, size, mlt_pool_release); return 0; } static mlt_frame get_background_frame(mlt_producer producer) { mlt_properties producer_properties = MLT_PRODUCER_PROPERTIES(producer); mlt_frame bg_frame = NULL; mlt_producer color_producer = mlt_properties_get_data(producer_properties, "_color_producer", NULL); if (!color_producer) { mlt_profile profile = mlt_service_profile(MLT_PRODUCER_SERVICE(producer)); color_producer = mlt_factory_producer(profile, "loader-nogl", "colour"); mlt_properties_set_data(producer_properties, "_color_producer", color_producer, 0, (mlt_destructor) mlt_producer_close, NULL); mlt_properties color_properties = MLT_PRODUCER_PROPERTIES(color_producer); mlt_properties_set(color_properties, "colour", FRAME_BACKGROUND_COLOR); } if (color_producer) { mlt_producer_seek(color_producer, 0); mlt_service_get_frame(MLT_PRODUCER_SERVICE(color_producer), &bg_frame, 0); } return bg_frame; } static mlt_frame get_text_frame(mlt_producer producer, time_info *info) { mlt_properties producer_properties = MLT_PRODUCER_PROPERTIES(producer); mlt_producer text_producer = mlt_properties_get_data(producer_properties, "_text_producer", NULL); mlt_profile profile = mlt_service_profile(MLT_PRODUCER_SERVICE(producer)); mlt_frame text_frame = NULL; if (!text_producer) { text_producer = mlt_factory_producer(profile, "loader-nogl", "qtext"); // Use pango if qtext is not available. if (!text_producer) text_producer = mlt_factory_producer(profile, "loader-nogl", "pango"); if (!text_producer) mlt_log_warning(MLT_PRODUCER_SERVICE(producer), "QT or GTK modules required for count producer.\n"); // Save the producer for future use. mlt_properties_set_data(producer_properties, "_text_producer", text_producer, 0, (mlt_destructor) mlt_producer_close, NULL); // Calculate the font size. char font_size[MAX_TEXT_LEN]; snprintf(font_size, MAX_TEXT_LEN - 1, "%dpx", profile->height * TEXT_SIZE_RATIO / 100); // Configure the producer. mlt_properties text_properties = MLT_PRODUCER_PROPERTIES(text_producer); mlt_properties_set(text_properties, "size", font_size); mlt_properties_set(text_properties, "weight", "400"); mlt_properties_set(text_properties, "fgcolour", TEXT_FOREGROUND_COLOR); mlt_properties_set(text_properties, "bgcolour", TEXT_BACKGROUND_COLOR); mlt_properties_set(text_properties, "pad", "0"); mlt_properties_set(text_properties, "outline", "0"); mlt_properties_set(text_properties, "align", "center"); } if (text_producer) { mlt_properties text_properties = MLT_PRODUCER_PROPERTIES(text_producer); char *style = mlt_properties_get(producer_properties, "style"); char text[MAX_TEXT_LEN] = ""; // Apply the time style if (!strcmp(style, "frames")) { snprintf(text, MAX_TEXT_LEN - 1, MLT_POSITION_FMT, info->position); } else if (!strcmp(style, "timecode")) { snprintf(text, MAX_TEXT_LEN - 1, "%02d:%02d:%02d%c%0*d", info->hours, info->minutes, info->seconds, info->sep, (info->fps > 999 ? 4 : info->fps > 99 ? 3 : 2), info->frames); } else if (!strcmp(style, "clock")) { snprintf(text, MAX_TEXT_LEN - 1, "%.2d:%.2d:%.2d", info->hours, info->minutes, info->seconds); } else if (!strcmp(style, "seconds+1")) { snprintf(text, MAX_TEXT_LEN - 1, "%d", info->seconds + 1); } else // seconds { snprintf(text, MAX_TEXT_LEN - 1, "%d", info->seconds); } mlt_properties_set(text_properties, "text", text); // Get the frame. mlt_service_get_frame(MLT_PRODUCER_SERVICE(text_producer), &text_frame, 0); } return text_frame; } static void draw_ring(uint8_t *image, mlt_profile profile, int radius, int line_width) { float sar = mlt_profile_sar(profile); int x_center = profile->width / 2; int y_center = profile->height / 2; int max_radius = radius + line_width; int a = max_radius + 1; int b = 0; line_width += 1; // Compensate for aliasing. // Scan through each pixel in one quadrant of the circle. while (a--) { b = (max_radius / sar) + 1.0; while (b--) { // Use Pythagorean theorem to determine the distance from this pixel to the center. float a2 = a * a; float b2 = b * sar * b * sar; float c = sqrtf(a2 + b2); float distance = c - radius; if (distance > 0 && distance < line_width) { // This pixel is within the ring. float mix = 1.0; if (distance < 1.0) { // Antialias the outside of the ring mix = distance; } else if ((float) line_width - distance < 1.0) { // Antialias the inside of the ring mix = (float) line_width - distance; } // Apply this value to all 4 quadrants of the circle. mix_pixel(image, profile->width, x_center + b, y_center - a, RING_PIXEL_VALUE, mix); mix_pixel(image, profile->width, x_center - b, y_center - a, RING_PIXEL_VALUE, mix); mix_pixel(image, profile->width, x_center + b, y_center + a, RING_PIXEL_VALUE, mix); mix_pixel(image, profile->width, x_center - b, y_center + a, RING_PIXEL_VALUE, mix); } } } } static void draw_cross(uint8_t *image, mlt_profile profile, int line_width) { int x = 0; int y = 0; int i = 0; // Draw a horizontal line i = line_width; while (i--) { y = (profile->height - line_width) / 2 + i; x = profile->width - 1; while (x--) { mix_pixel(image, profile->width, x, y, LINE_PIXEL_VALUE, 1.0); } } // Draw a vertical line line_width = lrint((float) line_width * mlt_profile_sar(profile)); i = line_width; while (i--) { x = (profile->width - line_width) / 2 + i; y = profile->height - 1; while (y--) { mix_pixel(image, profile->width, x, y, LINE_PIXEL_VALUE, 1.0); } } } static void draw_clock(uint8_t *image, mlt_profile profile, int angle, int line_width) { float sar = mlt_profile_sar(profile); int q = 0; int x_center = profile->width / 2; int y_center = profile->height / 2; line_width += 1; // Compensate for aliasing. // Look at each quadrant of the frame to see what should be done. for (q = 1; q <= 4; q++) { int max_angle = q * 90; int x_sign = (q == 1 || q == 2) ? 1 : -1; int y_sign = (q == 1 || q == 4) ? 1 : -1; int x_start = x_center * x_sign; int y_start = y_center * y_sign; // Compensate for rounding error of even lengths // (there is no "middle" pixel so everything is offset). if (x_sign == 1 && profile->width % 2 == 0) x_start--; if (y_sign == -1 && profile->height % 2 == 0) y_start++; if (angle >= max_angle) { // This quadrant is completely behind the clock hand. Fill it in. int dx = x_start + x_sign; while (dx) { dx -= x_sign; int dy = y_start + y_sign; while (dy) { dy -= y_sign; mix_pixel(image, profile->width, x_center + dx, y_center - dy, CLOCK_PIXEL_VALUE, 1.0); } } } else if (max_angle - angle < 90) { // This quadrant is partially filled // Calculate a point (vx,vy) that lies on the line created by the angle from 0,0. int vx = 0; int vy = y_start; float lv = 0; // Assume maximum y and calculate the corresponding x value // for a point at the other end of this line. if (x_sign * y_sign == 1) { vx = x_sign * sar * y_center / tan((max_angle - angle) * M_PI / 180.0); } else { vx = x_sign * sar * y_center * tan((max_angle - angle) * M_PI / 180.0); } // Calculate the length of the line defined by vx,vy lv = sqrtf((float) (vx * vx) * sar * sar + (float) vy * vy); // Scan through each pixel in the quadrant counting up/down to 0,0. int dx = x_start + x_sign; while (dx) { dx -= x_sign; int dy = y_start + y_sign; while (dy) { dy -= y_sign; // Calculate the cross product to determine which side of // the line this pixel lies on. int xp = vx * (vy - dy) - vy * (vx - dx); xp = xp * -1; // Easier to work with positive. Positive number means "behind" the line. if (xp > 0) { // This pixel is behind the clock hand and should be filled in. // Calculate the distance from the pixel to the line to determine // if it is part of the clock hand. float distance = (float) xp / lv; int val = CLOCK_PIXEL_VALUE; float mix = 1.0; if (distance < line_width) { // This pixel makes up the clock hand. val = LINE_PIXEL_VALUE; if (distance < 1.0) { // Antialias the outside of the clock hand mix = distance; } else if ((float) line_width - distance < 1.0) { // Antialias the inside of the clock hand mix_pixel(image, profile->width, x_center + dx, y_center - dy, CLOCK_PIXEL_VALUE, 1.0); mix = (float) line_width - distance; } } mix_pixel(image, profile->width, x_center + dx, y_center - dy, val, mix); } } } } } } static void add_clock_to_frame(mlt_producer producer, mlt_frame frame, time_info *info) { mlt_profile profile = mlt_service_profile(MLT_PRODUCER_SERVICE(producer)); mlt_properties producer_properties = MLT_PRODUCER_PROPERTIES(producer); uint8_t *image = NULL; mlt_image_format format = mlt_image_rgba; int size = 0; int width = profile->width; int height = profile->height; int line_width = LINE_WIDTH_RATIO * (width > height ? height : width) / 100; int radius = (width > height ? height : width) / 2; char *direction = mlt_properties_get(producer_properties, "direction"); int clock_angle = 0; mlt_frame_get_image(frame, &image, &format, &width, &height, 1); // Calculate the angle for the clock. int frames = info->frames; if (!strcmp(direction, "down")) { frames = info->fps - info->frames - 1; } clock_angle = (frames + 1) * 360 / info->fps; draw_clock(image, profile, clock_angle, line_width); draw_cross(image, profile, line_width); draw_ring(image, profile, (radius * OUTER_RING_RATIO) / 100, line_width); draw_ring(image, profile, (radius * INNER_RING_RATIO) / 100, line_width); size = mlt_image_format_size(format, width, height, NULL); mlt_frame_set_image(frame, image, size, mlt_pool_release); } static void add_text_to_bg(mlt_producer producer, mlt_frame bg_frame, mlt_frame text_frame) { mlt_properties producer_properties = MLT_PRODUCER_PROPERTIES(producer); mlt_transition transition = mlt_properties_get_data(producer_properties, "_transition", NULL); if (!transition) { mlt_profile profile = mlt_service_profile(MLT_PRODUCER_SERVICE(producer)); transition = mlt_factory_transition(profile, "composite", NULL); // Save the transition for future use. mlt_properties_set_data(producer_properties, "_transition", transition, 0, (mlt_destructor) mlt_transition_close, NULL); // Configure the transition. mlt_properties transition_properties = MLT_TRANSITION_PROPERTIES(transition); mlt_properties_set(transition_properties, "geometry", "0%/0%:100%x100%:100"); mlt_properties_set(transition_properties, "halign", "center"); mlt_properties_set(transition_properties, "valign", "middle"); } if (transition && bg_frame && text_frame) { // Apply the transition. mlt_transition_process(transition, bg_frame, text_frame); } } static int producer_get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { mlt_producer producer = mlt_frame_pop_service(frame); mlt_frame bg_frame = NULL; mlt_frame text_frame = NULL; int error = 1; int size = 0; char *background = mlt_properties_get(MLT_PRODUCER_PROPERTIES(producer), "background"); time_info info; mlt_service_lock(MLT_PRODUCER_SERVICE(producer)); get_time_info(producer, frame, &info); bg_frame = get_background_frame(producer); if (!strcmp(background, "clock")) { add_clock_to_frame(producer, bg_frame, &info); } text_frame = get_text_frame(producer, &info); add_text_to_bg(producer, bg_frame, text_frame); if (bg_frame) { // Get the image from the background frame. error = mlt_frame_get_image(bg_frame, image, format, width, height, writable); size = mlt_image_format_size(*format, *width, *height, NULL); // Detach the image from the bg_frame so it is not released. mlt_frame_set_image(bg_frame, *image, size, NULL); // Attach the image to the input frame. mlt_frame_set_image(frame, *image, size, mlt_pool_release); mlt_frame_close(bg_frame); } if (text_frame) { mlt_frame_close(text_frame); } mlt_service_unlock(MLT_PRODUCER_SERVICE(producer)); return error; } static int producer_get_frame(mlt_producer producer, mlt_frame_ptr frame, int index) { // Generate a frame *frame = mlt_frame_init(MLT_PRODUCER_SERVICE(producer)); mlt_profile profile = mlt_service_profile(MLT_PRODUCER_SERVICE(producer)); if (*frame != NULL) { // Obtain properties of frame mlt_properties frame_properties = MLT_FRAME_PROPERTIES(*frame); // Update time code on the frame mlt_frame_set_position(*frame, mlt_producer_frame(producer)); mlt_properties_set_int(frame_properties, "progressive", 1); mlt_properties_set_double(frame_properties, "aspect_ratio", mlt_profile_sar(profile)); mlt_properties_set_int(frame_properties, "meta.media.width", profile->width); mlt_properties_set_int(frame_properties, "meta.media.height", profile->height); // Inform framework that this producer creates rgba frames by default mlt_properties_set_int(frame_properties, "format", mlt_image_rgba); // Configure callbacks mlt_frame_push_service(*frame, producer); mlt_frame_push_get_image(*frame, producer_get_image); mlt_frame_push_audio(*frame, producer); mlt_frame_push_audio(*frame, producer_get_audio); } // Calculate the next time code mlt_producer_prepare_next(producer); return 0; } static void producer_close(mlt_producer this) { this->close = NULL; mlt_producer_close(this); free(this); } /** Initialize. */ mlt_producer producer_count_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { // Create a new producer object mlt_producer producer = mlt_producer_new(profile); // Initialize the producer if (producer) { mlt_properties properties = MLT_PRODUCER_PROPERTIES(producer); mlt_properties_set(properties, "direction", "down"); mlt_properties_set(properties, "style", "seconds+1"); mlt_properties_set(properties, "sound", "none"); mlt_properties_set(properties, "background", "clock"); mlt_properties_set(properties, "drop", "0"); mlt_properties_clear(properties, "resource"); // Callback registration producer->get_frame = producer_get_frame; producer->close = (mlt_destructor) producer_close; } return producer; } mlt-7.22.0/src/modules/plus/producer_count.yml000664 000000 000000 00000003747 14531534050 021371 0ustar00rootroot000000 000000 schema_version: 7.0 type: producer identifier: count title: Count version: 2 copyright: Meltytech, LLC license: LGPLv2.1 language: en tags: - Audio - Video description: > Generate frames with a counter and synchronized tone. The counter can go up or down. parameters: - identifier: direction title: Count Direction description: Whether to count up or down. type: string default: down values: - up - down mutable: yes widget: combo - identifier: style title: Counter Style description: | The style of the counter. * seconds - seconds counting up from or down to 0 * seconds+1 - seconds counting up from or down to 1 * frames - frames * timecode - timecode in the format HH:MM:SS:FF * clock - clock in the format HH:MM:SS type: string default: seconds+1 values: - seconds - seconds+1 - frames - timecode - clock mutable: yes widget: combo - identifier: sound title: Sound description: | The sound to be produced. * silent - No sound * 2pop - A 1kHz beep exactly two seconds before the out point * frame0 - A 1kHz beep at frame 0 of every second type: string default: silent values: - none - 2pop - frame0 mutable: yes widget: combo - identifier: background title: Background description: | The background style. * none - No background * clock - Film style clock animation type: string default: clock values: - none - clock mutable: yes widget: combo - identifier: drop title: Drop Frame description: | Use SMPTE style drop-frame counting for non-integer frame rates. The clock and timecode will advance two frames every minute if necessary to keep time with wall clock time mutable: yes type: integer minimum: 0 maximum: 1 default: 0 widget: checkbox mlt-7.22.0/src/modules/plus/producer_pgm.c000664 000000 000000 00000016717 14531534050 020446 0ustar00rootroot000000 000000 /* * producer_pgm.c -- PGM producer * Copyright (C) 2008-2022 Meltytech, LLC * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include static int read_pgm(char *name, uint8_t **image, int *width, int *height, int *maxval); static int producer_get_frame(mlt_producer producer, mlt_frame_ptr frame, int index); static void producer_close(mlt_producer parent); mlt_producer producer_pgm_init(mlt_profile profile, mlt_service_type type, const char *id, char *resource) { mlt_producer this = NULL; uint8_t *image = NULL; int width = 0; int height = 0; int maxval = 0; if (read_pgm(resource, &image, &width, &height, &maxval)) { if (resource && strstr(resource, "%luma")) { // Failed to read file; generate it. mlt_luma_map luma = mlt_luma_map_new(resource); if (profile) { luma->w = profile->width; luma->h = profile->height; } uint16_t *map = mlt_luma_map_render(luma); if (map) { int n = luma->w * luma->h; image = mlt_pool_alloc(n * 2); width = luma->w; height = luma->h; for (int i = 0; i < n; i++) { image[2 * i] = 16 + map[i] * 219 / USHRT_MAX; image[2 * i + 1] = 128; } mlt_pool_release(map); } free(luma); } } if (image) { this = calloc(1, sizeof(struct mlt_producer_s)); if (this != NULL && mlt_producer_init(this, NULL) == 0) { mlt_properties properties = MLT_PRODUCER_PROPERTIES(this); this->get_frame = producer_get_frame; this->close = (mlt_destructor) producer_close; mlt_properties_set(properties, "resource", resource); mlt_properties_set_data(properties, "image", image, 0, mlt_pool_release, NULL); mlt_properties_set_int(properties, "meta.media.width", width); mlt_properties_set_int(properties, "meta.media.height", height); } else { mlt_pool_release(image); free(this); this = NULL; } } return this; } /** Load the PGM file. */ static int read_pgm(char *name, uint8_t **image, int *width, int *height, int *maxval) { uint8_t *input = NULL; int error = 0; FILE *f = mlt_fopen(name, "rb"); char data[512]; // Initialise *image = NULL; *width = 0; *height = 0; *maxval = 0; // Get the magic code if (f != NULL && fgets(data, 511, f) != NULL && data[0] == 'P' && data[1] == '5') { char *p = data + 2; int i = 0; int val = 0; // PGM Header parser (probably needs to be strengthened) for (i = 0; !error && i < 3; i++) { if (*p != '\0' && *p != '\n') val = strtol(p, &p, 10); else p = NULL; while (error == 0 && p == NULL) { if (fgets(data, 511, f) == NULL) error = 1; else if (data[0] != '#') val = strtol(data, &p, 10); } switch (i) { case 0: *width = val; break; case 1: *height = val; break; case 2: *maxval = val; break; } } if (!error) { // Determine if this is one or two bytes per pixel int bpp = *maxval > 255 ? 2 : 1; int size = *width * *height * bpp; uint8_t *p; // Allocate temporary storage for the data and the image input = mlt_pool_alloc(*width * *height * bpp); *image = mlt_pool_alloc(*width * *height * sizeof(uint8_t) * 2); p = *image; error = *image == NULL || input == NULL; if (!error) { // Read the raw data error = fread(input, *width * *height * bpp, 1, f) != 1; if (!error) { // Convert to yuv422 (very lossy - need to extend this to allow 16 bit alpha out) for (i = 0; i < size; i += bpp) { *p++ = 16 + (input[i] * 219) / 255; *p++ = 128; } } } } if (error) mlt_pool_release(*image); mlt_pool_release(input); } else { error = 1; } if (f != NULL) fclose(f); return error; } static int producer_get_image(mlt_frame this, uint8_t **buffer, mlt_image_format *format, int *width, int *height, int writable) { mlt_producer producer = mlt_frame_pop_service(this); int real_width = mlt_properties_get_int(MLT_FRAME_PROPERTIES(this), "meta.media.width"); int real_height = mlt_properties_get_int(MLT_FRAME_PROPERTIES(this), "meta.media.height"); int size = real_width * real_height; uint8_t *image = mlt_pool_alloc(size * 2); uint8_t *source = mlt_properties_get_data(MLT_PRODUCER_PROPERTIES(producer), "image", NULL); mlt_frame_set_image(this, image, size * 2, mlt_pool_release); *width = real_width; *height = real_height; *format = mlt_image_yuv422; *buffer = image; if (image != NULL && source != NULL) memcpy(image, source, size * 2); return 0; } static int producer_get_frame(mlt_producer producer, mlt_frame_ptr frame, int index) { // Construct a test frame *frame = mlt_frame_init(MLT_PRODUCER_SERVICE(producer)); // Get the frames properties mlt_properties properties = MLT_FRAME_PROPERTIES(*frame); // Pass the data on the frame properties mlt_properties_set_int(properties, "has_image", 1); mlt_properties_set_int(properties, "progressive", 1); mlt_properties_set_double(properties, "aspect_ratio", 1); // Inform framework that this producer creates yuv frames by default mlt_properties_set_int(properties, "format", mlt_image_yuv422); // Push the image callback mlt_frame_push_service(*frame, producer); mlt_frame_push_get_image(*frame, producer_get_image); // Update timecode on the frame we're creating mlt_frame_set_position(*frame, mlt_producer_position(producer)); // Calculate the next timecode mlt_producer_prepare_next(producer); return 0; } static void producer_close(mlt_producer parent) { parent->close = NULL; mlt_producer_close(parent); free(parent); } mlt-7.22.0/src/modules/plus/producer_pgm.yml000664 000000 000000 00000000425 14531534050 021012 0ustar00rootroot000000 000000 schema_version: 7.0 type: producer identifier: pgm title: PGM Image version: 1 creator: Charles Yates license: LGPLv2.1 language: en tags: - Video parameters: - identifier: resource argument: yes type: string title: File Name required: yes mutable: no mlt-7.22.0/src/modules/plus/transition_affine.c000664 000000 000000 00000066771 14531534050 021467 0ustar00rootroot000000 000000 /* * transition_affine.c -- affine transformations * Copyright (C) 2003-2022 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include #include #include "interp.h" #define MLT_AFFINE_MAX_DIMENSION (16000) static double alignment_parse(char *align) { int ret = 0.0; if (align == NULL) ; else if (isdigit(align[0])) ret = atoi(align); else if (align[0] == 'c' || align[0] == 'm') ret = 1.0; else if (align[0] == 'r' || align[0] == 'b') ret = 2.0; return ret; } static mlt_position repeat_position(mlt_properties properties, const char *name, mlt_position position, int length) { // Make mlt_properties parse and refresh animation. mlt_properties_anim_get_double(properties, name, position, length); mlt_animation animation = mlt_properties_get_animation(properties, name); if (animation) { // Apply repeat and mirror options. int anim_length = mlt_animation_get_length(animation); int repeat_off = mlt_properties_get_int(properties, "repeat_off"); if (!repeat_off && position >= anim_length && anim_length != 0) { int section = position / anim_length; int mirror_off = mlt_properties_get_int(properties, "mirror_off"); position -= section * anim_length; if (!mirror_off && section % 2 == 1) position = anim_length - position; } } return position; } static double anim_get_angle(mlt_properties properties, const char *name, mlt_position position, mlt_position length) { double result = 0.0; if (mlt_properties_get(properties, name)) { position = repeat_position(properties, name, position, length); result = mlt_properties_anim_get_double(properties, name, position, length); if (strchr(mlt_properties_get(properties, name), '%')) result *= 360; } return result; } typedef struct { double matrix[3][3]; } affine_t; static void affine_init(double affine[3][3]) { affine[0][0] = 1; affine[0][1] = 0; affine[0][2] = 0; affine[1][0] = 0; affine[1][1] = 1; affine[1][2] = 0; affine[2][0] = 0; affine[2][1] = 0; affine[2][2] = 1; } // Multiply two this affine transform with that static void affine_multiply(double affine[3][3], double matrix[3][3]) { double output[3][3]; int i; int j; for (i = 0; i < 3; i++) for (j = 0; j < 3; j++) output[i][j] = affine[i][0] * matrix[j][0] + affine[i][1] * matrix[j][1] + affine[i][2] * matrix[j][2]; affine[0][0] = output[0][0]; affine[0][1] = output[0][1]; affine[0][2] = output[0][2]; affine[1][0] = output[1][0]; affine[1][1] = output[1][1]; affine[1][2] = output[1][2]; affine[2][0] = output[2][0]; affine[2][1] = output[2][1]; affine[2][2] = output[2][2]; } // Rotate by a given angle static void affine_rotate_x(double affine[3][3], double angle) { double matrix[3][3]; matrix[0][0] = cos(angle * M_PI / 180); matrix[0][1] = 0 - sin(angle * M_PI / 180); matrix[0][2] = 0; matrix[1][0] = sin(angle * M_PI / 180); matrix[1][1] = cos(angle * M_PI / 180); matrix[1][2] = 0; matrix[2][0] = 0; matrix[2][1] = 0; matrix[2][2] = 1; affine_multiply(affine, matrix); } static void affine_rotate_y(double affine[3][3], double angle) { double matrix[3][3]; matrix[0][0] = cos(angle * M_PI / 180); matrix[0][1] = 0; matrix[0][2] = 0 - sin(angle * M_PI / 180); matrix[1][0] = 0; matrix[1][1] = 1; matrix[1][2] = 0; matrix[2][0] = sin(angle * M_PI / 180); matrix[2][1] = 0; matrix[2][2] = cos(angle * M_PI / 180); affine_multiply(affine, matrix); } static void affine_rotate_z(double affine[3][3], double angle) { double matrix[3][3]; matrix[0][0] = 1; matrix[0][1] = 0; matrix[0][2] = 0; matrix[1][0] = 0; matrix[1][1] = cos(angle * M_PI / 180); matrix[1][2] = sin(angle * M_PI / 180); matrix[2][0] = 0; matrix[2][1] = -sin(angle * M_PI / 180); matrix[2][2] = cos(angle * M_PI / 180); affine_multiply(affine, matrix); } static void affine_scale(double affine[3][3], double sx, double sy) { double matrix[3][3]; matrix[0][0] = sx; matrix[0][1] = 0; matrix[0][2] = 0; matrix[1][0] = 0; matrix[1][1] = sy; matrix[1][2] = 0; matrix[2][0] = 0; matrix[2][1] = 0; matrix[2][2] = 1; affine_multiply(affine, matrix); } // Shear by a given value static void affine_shear(double affine[3][3], double shear_x, double shear_y, double shear_z) { double matrix[3][3]; matrix[0][0] = 1; matrix[0][1] = tan(shear_x * M_PI / 180); matrix[0][2] = 0; matrix[1][0] = tan(shear_y * M_PI / 180); matrix[1][1] = 1; matrix[1][2] = tan(shear_z * M_PI / 180); matrix[2][0] = 0; matrix[2][1] = 0; matrix[2][2] = 1; affine_multiply(affine, matrix); } static void affine_offset(double affine[3][3], double x, double y) { affine[0][2] += x; affine[1][2] += y; } // Obtain the mapped x coordinate of the input static inline double MapX(double affine[3][3], double x, double y) { return affine[0][0] * x + affine[0][1] * y + affine[0][2]; } // Obtain the mapped y coordinate of the input static inline double MapY(double affine[3][3], double x, double y) { return affine[1][0] * x + affine[1][1] * y + affine[1][2]; } static inline double MapZ(double affine[3][3], double x, double y) { return affine[2][0] * x + affine[2][1] * y + affine[2][2]; } static void affine_max_output( double affine[3][3], double *w, double *h, double dz, double max_width, double max_height) { int tlx = MapX(affine, -max_width, max_height) / dz; int tly = MapY(affine, -max_width, max_height) / dz; int trx = MapX(affine, max_width, max_height) / dz; int try = MapY(affine, max_width, max_height) / dz; int blx = MapX(affine, -max_width, -max_height) / dz; int bly = MapY(affine, -max_width, -max_height) / dz; int brx = MapX(affine, max_width, -max_height) / dz; int bry = MapY(affine, max_width, -max_height) / dz; int max_x; int max_y; int min_x; int min_y; max_x = MAX(tlx, trx); max_x = MAX(max_x, blx); max_x = MAX(max_x, brx); min_x = MIN(tlx, trx); min_x = MIN(min_x, blx); min_x = MIN(min_x, brx); max_y = MAX(tly, try); max_y = MAX(max_y, bly); max_y = MAX(max_y, bry); min_y = MIN(tly, try); min_y = MIN(min_y, bly); min_y = MIN(min_y, bry); *w = (double) (max_x - min_x + 1) / max_width / 2.0; *h = (double) (max_y - min_y + 1) / max_height / 2.0; } #define IN_RANGE(v, r) (v >= -r / 2 && v < r / 2) static inline void get_affine(affine_t *affine, mlt_transition transition, double position, int length, double scale_width, double scale_height) { mlt_properties properties = MLT_TRANSITION_PROPERTIES(transition); int keyed = mlt_properties_get_int(properties, "keyed"); if (keyed == 0) { double fix_rotate_x = anim_get_angle(properties, "fix_rotate_x", position, length); double fix_rotate_y = anim_get_angle(properties, "fix_rotate_y", position, length); double fix_rotate_z = anim_get_angle(properties, "fix_rotate_z", position, length); double rotate_x = mlt_properties_get_double(properties, "rotate_x"); double rotate_y = mlt_properties_get_double(properties, "rotate_y"); double rotate_z = mlt_properties_get_double(properties, "rotate_z"); double fix_shear_x = anim_get_angle(properties, "fix_shear_x", position, length); double fix_shear_y = anim_get_angle(properties, "fix_shear_y", position, length); double fix_shear_z = anim_get_angle(properties, "fix_shear_z", position, length); double shear_x = mlt_properties_get_double(properties, "shear_x"); double shear_y = mlt_properties_get_double(properties, "shear_y"); double shear_z = mlt_properties_get_double(properties, "shear_z"); double ox = mlt_properties_anim_get_double(properties, "ox", position, length); double oy = mlt_properties_anim_get_double(properties, "oy", position, length); affine_rotate_x(affine->matrix, fix_rotate_x + rotate_x * position); affine_rotate_y(affine->matrix, fix_rotate_y + rotate_y * position); affine_rotate_z(affine->matrix, fix_rotate_z + rotate_z * position); affine_shear(affine->matrix, fix_shear_x + shear_x * position, fix_shear_y + shear_y * position, fix_shear_z + shear_z * position); affine_offset(affine->matrix, ox * scale_width, oy * scale_height); } else { double rotate_x = anim_get_angle(properties, "rotate_x", position, length); double rotate_y = anim_get_angle(properties, "rotate_y", position, length); double rotate_z = anim_get_angle(properties, "rotate_z", position, length); double shear_x = anim_get_angle(properties, "shear_x", position, length); double shear_y = anim_get_angle(properties, "shear_y", position, length); double shear_z = anim_get_angle(properties, "shear_z", position, length); double o_x = mlt_properties_anim_get_double(properties, "ox", repeat_position(properties, "ox", position, length), length); double o_y = mlt_properties_anim_get_double(properties, "oy", repeat_position(properties, "oy", position, length), length); affine_rotate_x(affine->matrix, rotate_x); affine_rotate_y(affine->matrix, rotate_y); affine_rotate_z(affine->matrix, rotate_z); affine_shear(affine->matrix, shear_x, shear_y, shear_z); affine_offset(affine->matrix, o_x * scale_width, o_y * scale_height); } } struct sliced_desc { uint8_t *a_image, *b_image; interpp interp; affine_t affine; int a_width, a_height, b_width, b_height; double lower_x, lower_y; double dz, mix; double x_offset, y_offset; int b_alpha; double minima, xmax, ymax; }; static int sliced_proc(int id, int index, int jobs, void *cookie) { (void) id; // unused struct sliced_desc ctx = *((struct sliced_desc *) cookie); int starty, height_slice = mlt_slices_size_slice(jobs, index, ctx.a_height, &starty); double x, y; double dx, dy; int i, j; ctx.a_image += starty * (ctx.a_width * 4); for (i = 0, y = ctx.lower_y; i < ctx.a_height; i++, y++) { if (i >= starty && i < (starty + height_slice)) { for (j = 0, x = ctx.lower_x; j < ctx.a_width; j++, x++) { dx = MapX(ctx.affine.matrix, x, y) / ctx.dz + ctx.x_offset; dy = MapY(ctx.affine.matrix, x, y) / ctx.dz + ctx.y_offset; if (dx >= ctx.minima && dx <= ctx.xmax && dy >= ctx.minima && dy <= ctx.ymax) ctx.interp(ctx.b_image, ctx.b_width, ctx.b_height, dx, dy, ctx.mix, ctx.a_image, ctx.b_alpha); ctx.a_image += 4; } } } return 0; } /** Get the image. */ static int transition_get_image(mlt_frame a_frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { // Get the b frame from the stack mlt_frame b_frame = mlt_frame_pop_frame(a_frame); // Get the transition object mlt_transition transition = mlt_frame_pop_service(a_frame); // Get the properties of the transition mlt_properties properties = MLT_TRANSITION_PROPERTIES(transition); // Get the properties of the a frame mlt_properties a_props = MLT_FRAME_PROPERTIES(a_frame); // Get the properties of the b frame mlt_properties b_props = MLT_FRAME_PROPERTIES(b_frame); // Image, format, width, height and image for the b frame uint8_t *b_image = NULL; mlt_image_format b_format = mlt_image_rgba; int b_width = mlt_properties_get_int(b_props, "meta.media.width"); int b_height = mlt_properties_get_int(b_props, "meta.media.height"); double b_ar = mlt_frame_get_aspect_ratio(b_frame); double b_dar = b_ar * b_width / b_height; // Assign the current position mlt_position position = mlt_transition_get_position(transition, a_frame); int mirror = mlt_properties_get_position(properties, "mirror"); int length = mlt_transition_get_length(transition); if (mlt_properties_get_int(properties, "always_active")) { mlt_properties props = mlt_properties_get_data(b_props, "_producer", NULL); mlt_position in = mlt_properties_get_int(props, "in"); mlt_position out = mlt_properties_get_int(props, "out"); length = out - in + 1; } // Obtain the normalized width and height from the a_frame mlt_profile profile = mlt_service_profile(MLT_TRANSITION_SERVICE(transition)); int normalized_width = profile->width; int normalized_height = profile->height; double consumer_ar = mlt_profile_sar(profile); if (mirror && position > length / 2) position = abs(position - length); // Preview scaling is not working correctly when offsets are active. I have // not figured out the math; so, disable preview scaling for now. double ox = mlt_properties_anim_get_double(properties, "ox", position, length); double oy = mlt_properties_anim_get_double(properties, "oy", position, length); if (ox != 0.0 || oy != 0.0) { *width = normalized_width; *height = normalized_height; } // Fetch the a frame image *format = mlt_image_rgba; int error = mlt_frame_get_image(a_frame, image, format, width, height, 1); if (error || !image) return error; // Calculate the region now double scale_width = mlt_profile_scale_width(profile, *width); double scale_height = mlt_profile_scale_height(profile, *height); mlt_rect result = {0, 0, normalized_width, normalized_height, 1.0}; mlt_service_lock(MLT_TRANSITION_SERVICE(transition)); if (mlt_properties_get(properties, "rect")) { // Determine length and obtain cycle double cycle = mlt_properties_get_double(properties, "cycle"); // Allow a repeat cycle if (cycle >= 1) length = cycle; else if (cycle > 0) length *= cycle; mlt_position anim_pos = repeat_position(properties, "rect", position, length); result = mlt_properties_anim_get_rect(properties, "rect", anim_pos, length); if (mlt_properties_get(properties, "rect") && strchr(mlt_properties_get(properties, "rect"), '%')) { result.x *= normalized_width; result.y *= normalized_height; result.w *= normalized_width; result.h *= normalized_height; } result.o = (result.o == DBL_MIN) ? 1.0 : MIN(result.o, 1.0); } int threads = mlt_properties_get_int(properties, "threads"); threads = CLAMP(threads, 0, mlt_slices_count_normal()); if (threads == 1) mlt_service_unlock(MLT_TRANSITION_SERVICE(transition)); result.x *= scale_width; result.y *= scale_height; result.w *= scale_width; result.h *= scale_height; double geometry_w = result.w; double geometry_h = result.h; int fill = mlt_properties_get_int(properties, "fill"); int distort = mlt_properties_get_int(properties, "distort"); if (!fill) { double geometry_dar = result.w * consumer_ar / result.h; if (b_dar > geometry_dar) { result.w = MIN(result.w, b_width * b_ar / consumer_ar); result.h = result.w * consumer_ar / b_dar; } else { result.h = MIN(result.h, b_height); result.w = result.h * b_dar / consumer_ar; } } // Fetch the b frame image if (scale_width != 1.0 || scale_height != 1.0) { // Scale request of b frame image to consumer scale maintaining its aspect ratio. b_height = CLAMP(*height, 1, MLT_AFFINE_MAX_DIMENSION); b_width = MAX(b_height * b_dar / b_ar, 1); if (b_width > MLT_AFFINE_MAX_DIMENSION) { b_width = CLAMP(*width, 1, MLT_AFFINE_MAX_DIMENSION); b_height = MAX(b_width * b_ar / b_dar, 1); } // Set the rescale interpolation to match the frame mlt_properties_set(b_props, "consumer.rescale", mlt_properties_get(a_props, "consumer.rescale")); // Disable padding (resize filter) mlt_properties_set_int(b_props, "distort", 1); } else if (mlt_properties_get_int(b_props, "always_scale") || (!mlt_properties_get_int(b_props, "interpolation_not_required") && (fill || distort || b_width > result.w || b_height > result.h || mlt_properties_get_int(properties, "b_scaled")))) { // Request b frame image scaled to what is needed. b_height = CLAMP(result.h, 1, MLT_AFFINE_MAX_DIMENSION); b_width = MAX(b_height * b_dar / b_ar, 1); if (b_width > MLT_AFFINE_MAX_DIMENSION) { b_width = CLAMP(result.w, 1, MLT_AFFINE_MAX_DIMENSION); b_height = MAX(b_width * b_ar / b_dar, 1); } // Set the rescale interpolation to match the frame mlt_properties_set(b_props, "consumer.rescale", mlt_properties_get(a_props, "consumer.rescale")); // Disable padding (resize filter) mlt_properties_set_int(b_props, "distort", 1); } else { // Request at resolution of b frame image. This only happens when not using fill or distort mode // and the image is smaller than the rect with the intention to prevent scaling of the // image and merely position and possibly transform. mlt_properties_set_int(b_props, "rescale_width", b_width); mlt_properties_set_int(b_props, "rescale_height", b_height); const char *b_resource = mlt_properties_get(MLT_PRODUCER_PROPERTIES( mlt_frame_get_original_producer(b_frame)), "resource"); // Check if we are applied as a filter inside a transition if (b_resource && !strcmp("", b_resource)) { // Set the rescale interpolation to match the frame mlt_properties_set(b_props, "consumer.rescale", mlt_properties_get(a_props, "consumer.rescale")); } else { // Suppress padding and aspect normalization. mlt_properties_set(b_props, "consumer.rescale", "none"); } } mlt_log_debug(MLT_TRANSITION_SERVICE(transition), "requesting image B at resolution %dx%d\n", b_width, b_height); // This is not a field-aware transform. mlt_properties_set_int(b_props, "consumer.progressive", 1); error = mlt_frame_get_image(b_frame, &b_image, &b_format, &b_width, &b_height, 0); if (error || !b_image) { // Remove potentially large image on the B frame. mlt_frame_set_image(b_frame, NULL, 0, NULL); if (threads != 1) mlt_service_unlock(MLT_TRANSITION_SERVICE(transition)); return error; } // Check that both images are of the correct format and process if (*format == mlt_image_rgba && b_format == mlt_image_rgba) { double sw, sh; // Get values from the transition double scale_x = mlt_properties_anim_get_double(properties, "scale_x", position, length); double scale_y = mlt_properties_anim_get_double(properties, "scale_y", position, length); int scale = mlt_properties_get_int(properties, "scale"); double geom_scale_x = (double) b_width / result.w; double geom_scale_y = (double) b_height / result.h; struct sliced_desc desc = {.a_image = *image, .b_image = b_image, .interp = interpBL_b32, .a_width = *width, .a_height = *height, .b_width = b_width, .b_height = b_height, .lower_x = -(result.x + result.w / 2.0), // center .lower_y = -(result.y + result.h / 2.0), // middle .mix = result.o, .x_offset = (double) b_width / 2.0, .y_offset = (double) b_height / 2.0, .b_alpha = mlt_properties_get_int(properties, "b_alpha"), // Affine boundaries .minima = 0, .xmax = b_width - 1, .ymax = b_height - 1}; // Recalculate vars if alignment supplied. if (mlt_properties_get(properties, "halign") || mlt_properties_get(properties, "valign")) { double halign = alignment_parse(mlt_properties_get(properties, "halign")); double valign = alignment_parse(mlt_properties_get(properties, "valign")); desc.x_offset = halign * b_width / 2.0; desc.y_offset = valign * b_height / 2.0; desc.lower_x = -(result.x + geometry_w * halign / 2.0f); desc.lower_y = -(result.y + geometry_h * valign / 2.0f); } affine_init(desc.affine.matrix); // Compute the affine transform get_affine(&desc.affine, transition, (double) position, length, scale_width, scale_height); desc.dz = MapZ(desc.affine.matrix, 0, 0); if ((int) fabs(desc.dz * 1000) < 25) { if (threads != 1) mlt_service_unlock(MLT_TRANSITION_SERVICE(transition)); return 0; } if (mlt_properties_get_int(properties, "invert_scale")) { scale_x = 1.0 / scale_x; scale_y = 1.0 / scale_y; } // Factor scaling into the transformation based on output resolution. if (distort) { scale_x = geom_scale_x * (scale_x == 0 ? 1 : scale_x); scale_y = geom_scale_y * (scale_y == 0 ? 1 : scale_y); } else { // Determine scale with respect to aspect ratio. double consumer_dar = consumer_ar * normalized_width / normalized_height; if (b_dar > consumer_dar) { scale_x = geom_scale_x * (scale_x == 0 ? 1 : scale_x); scale_y = geom_scale_x * (scale_y == 0 ? 1 : scale_y); scale_y *= b_ar / consumer_ar; } else { scale_x = geom_scale_y * (scale_x == 0 ? 1 : scale_x); scale_y = geom_scale_y * (scale_y == 0 ? 1 : scale_y); scale_x *= consumer_ar / b_ar; } } if (scale) { affine_max_output(desc.affine.matrix, &sw, &sh, desc.dz, *width, *height); affine_scale(desc.affine.matrix, sw * MIN(geom_scale_x, geom_scale_y), sh * MIN(geom_scale_x, geom_scale_y)); } else if (scale_x != 0 && scale_y != 0) { affine_scale(desc.affine.matrix, scale_x, scale_y); } char *interps = mlt_properties_get(a_props, "consumer.rescale"); // Copy in case string is changed. if (interps) interps = strdup(interps); // Set the interpolation function if (interps == NULL || strcmp(interps, "nearest") == 0 || strcmp(interps, "neighbor") == 0 || strcmp(interps, "tiles") == 0 || strcmp(interps, "fast_bilinear") == 0) { desc.interp = interpNN_b32; // uses lrintf. Values should be >= -0.5 and < max + 0.5 desc.minima -= 0.5; desc.xmax += 0.49; desc.ymax += 0.49; } else if (strcmp(interps, "bilinear") == 0) { desc.interp = interpBL_b32; // uses floorf. } else if (strcmp(interps, "bicubic") == 0 || strcmp(interps, "hyper") == 0 || strcmp(interps, "sinc") == 0 || strcmp(interps, "lanczos") == 0 || strcmp(interps, "spline") == 0) { // TODO: lanczos 8x8 // TODO: spline 4x4 or 6x6 desc.interp = interpBC_b32; // uses ceilf. Values should be > -1 and <= max. desc.minima -= 1; } free(interps); // Do the transform with interpolation if (threads == 1) sliced_proc(0, 0, 1, &desc); else mlt_slices_run_normal(threads, sliced_proc, &desc); // Remove potentially large image on the B frame. mlt_frame_set_image(b_frame, NULL, 0, NULL); } if (threads != 1) mlt_service_unlock(MLT_TRANSITION_SERVICE(transition)); return 0; } /** Affine transition processing. */ static mlt_frame transition_process(mlt_transition transition, mlt_frame a_frame, mlt_frame b_frame) { // Push the transition on to the frame mlt_frame_push_service(a_frame, transition); // Push the b_frame on to the stack mlt_frame_push_frame(a_frame, b_frame); // Push the transition method mlt_frame_push_get_image(a_frame, transition_get_image); return a_frame; } /** Constructor for the filter. */ mlt_transition transition_affine_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_transition transition = mlt_transition_new(); if (transition != NULL) { mlt_properties_set_int(MLT_TRANSITION_PROPERTIES(transition), "distort", 0); mlt_properties_set(MLT_TRANSITION_PROPERTIES(transition), "rect", "0%/0%:100%x100%:100%"); // Inform apps and framework that this is a video only transition mlt_properties_set_int(MLT_TRANSITION_PROPERTIES(transition), "_transition_type", 1); mlt_properties_set_int(MLT_TRANSITION_PROPERTIES(transition), "fill", 1); transition->process = transition_process; } return transition; } mlt-7.22.0/src/modules/plus/transition_affine.yml000664 000000 000000 00000020045 14531534050 022026 0ustar00rootroot000000 000000 schema_version: 7.0 type: transition identifier: affine title: Transform version: 6 copyright: Meltytech, LLC creator: Charles Yates contributor: - Dan Dennedy license: LGPLv2.1 language: en tags: - Video parameters: - identifier: distort title: Ignore aspect ratio description: > Determines whether the image aspect ratio will be distorted while scaling to completely fill the rectangle. type: boolean default: 0 mutable: yes widget: checkbox - identifier: fill title: Upscale to fill description: > Determines whether the image will be scaled up to fill the rectangle or whether the size will be constrained to 100% of the profile resolution. type: boolean default: 1 mutable: yes widget: checkbox - identifier: repeat_off title: Disable looping description: > When animating properties with keyframes, whether to repeat the animation after it reaches the last key frame. type: boolean default: 0 mutable: yes widget: checkbox - identifier: mirror_off title: Disable ping-pong description: > When animating properties with keyframes and repeat_off=0, whether the animation alternates between reverses and forwards for each repetition. type: boolean default: 0 mutable: yes widget: checkbox - identifier: cycle title: Period description: > The duration to use when interpreting key frames for animation. If 0, the default, the transition length is used. If in range (0, 1), a percentage of transition length; otherwise, the number of frames. type: float default: 0 mutable: yes - identifier: keyed title: Key-framed description: Whether rotate, shear, and offset are key-framed or not. type: boolean default: 0 mutable: yes widget: checkbox - identifier: ox title: Horizontal offset type: float minimum: 0 default: 0 mutable: yes animation: yes unit: pixels - identifier: oy title: Vertical offset type: float minimum: 0 default: 0 mutable: yes animation: yes unit: pixels - identifier: rotate_x title: Rotate on X axis description: > Animate rotation around the X axis. If keyed=0, the amount to rotate per frame. type: float unit: degrees default: 0 mutable: yes - identifier: rotate_y title: Rotate on Y axis description: > Animate rotation around the Y axis. If keyed=0, the amount to rotate per frame. type: float unit: degrees default: 0 mutable: yes - identifier: rotate_z title: Rotate on Z axis description: > Animate rotation around the Z axis. If keyed=0, the amount to rotate per frame. type: float unit: degrees default: 0 mutable: yes - identifier: fix_rotate_x title: X axis rotation description: Fixed amount of rotation around the X axis. type: float unit: degrees default: 0 mutable: yes animation: yes - identifier: fix_rotate_y title: Y axis rotation description: Fixed amount of rotation around the Y axis. type: float unit: degrees default: 0 mutable: yes animation: yes - identifier: fix_rotate_z title: Z axis rotation description: Fixed amount of rotation around the Z axis. type: float unit: degrees default: 0 mutable: yes animation: yes - identifier: shear_x title: Shear along X axis description: > Animate shear along the X axis. If keyed=0, the shear angle increment per frame. type: float unit: degrees default: 0 mutable: yes - identifier: shear_y title: Shear along Y axis description: > Animate shear along the Y axis. If keyed=0, the shear angle increment per frame. type: float unit: degrees default: 0 mutable: yes - identifier: shear_z title: Shear along Z axis description: > Animate shear along the Z axis. If keyed=0, the shear angle increment per frame. type: float unit: degrees default: 0 mutable: yes - identifier: fix_shear_x title: X axis shear description: Fixed amount of shear along the X axis. type: float unit: degrees default: 0 mutable: yes animation: yes - identifier: fix_shear_y title: Y axis shear description: Fixed amount of shear along the Y axis. type: float unit: degrees default: 0 mutable: yes animation: yes - identifier: fix_shear_z title: Z axis shear description: Fixed amount of shear along the Z axis. type: float unit: degrees default: 0 mutable: yes animation: yes - identifier: mirror title: Ping-pong description: > When animating properties with key frames, whether the animation should behave with a ping-pong effect once over the duration of the transition. It will run in the forward direction over the first half the transition and in the reverse direction over the second half. type: boolean - identifier: scale title: Scale description: > Whether to automatic upscale B frame image to ensure the rectangle is filled. type: boolean default: 0 mutable: yes widget: checkbox - identifier: scale_x title: Horizontal scale description: A scale factor applied along the X axis. type: float default: 0 mutable: yes animation: yes - identifier: scale_y title: Vertical scale description: A scale factor applied along the Y axis. type: float default: 0 mutable: yes animation: yes - identifier: invert_scale title: Invert Scale description: > Whether to invert the scale_x and scale_y values. This is helpful to make animation interpolation sane because otherwise the scale values do not animate linearly. type: boolean default: 0 mutable: yes widget: checkbox - identifier: b_alpha title: Affect alpha channel description: > Whether to use the B frame's alpha channel in transformations for the output, The affine filter sets this to 1 by default. Basically, this tells the blend function to use the Porter-Duff atop mode instead of the default over. type: boolean default: 0 mutable: yes - identifier: fill title: Fill rectangle description: > Determines whether the image will be scaled up to fill the rectangle. Otherwise, if the B frame image fits within the rectangle, it will not be scaled. If 0, and the B frame image exceeds the rectangle, then it is scaled down to fit within the rectangle. type: boolean default: 1 mutable: yes widget: checkbox - identifier: halign title: Horizontal alignment description: > Set the horizontal alignment within the rectangle. type: string default: left values: - left - center - right mutable: yes widget: combo - identifier: valign title: Vertical alignment description: > Set the vertical alignment within the rectangle. type: string default: top values: - top - middle - bottom mutable: yes widget: combo - identifier: threads title: Thread count description: > Use 0 to use the slice count, which defaults to the number of detected CPUs. Otherwise, set the number of threads to use up to the slice count. minimum: 0 default: 0 - identifier: rect title: Rectangle description: > This specifies the size and position of the image. The format of this is "X/Y:WxH[:opacity]" and can be animated with key frames. If you use percentages you must use them for every field in the above format. For example, you cannot mix and match usage of absolute coordinates and percentages for size and opacity. type: rect default: "0%/0%:100%x100%:100%" readonly: no mutable: yes animation: yes - identifier: b_scaled title: Do not use full resolution of B frame type: boolean default: 0 mutable: yes mlt-7.22.0/src/modules/plusgpl/000775 000000 000000 00000000000 14531534050 016303 5ustar00rootroot000000 000000 mlt-7.22.0/src/modules/plusgpl/CMakeLists.txt000664 000000 000000 00000001621 14531534050 021043 0ustar00rootroot000000 000000 add_library(mltplusgpl MODULE cJSON.c cJSON.h consumer_cbrts.c factory.c filter_burn.c filter_lumaliftgaingamma.c filter_rotoscoping.c filter_telecide.c image.c utils.c utils.h ) file(GLOB YML "*.yml") add_custom_target(Other_plsugpl_Files SOURCES ${YML} ) target_compile_options(mltplusgpl PRIVATE ${MLT_COMPILE_OPTIONS}) target_link_libraries(mltplusgpl PRIVATE mlt m Threads::Threads) if(WIN32) target_link_libraries(mltplusgpl PRIVATE ws2_32) elseif(UNIX AND NOT APPLE AND NOT ANDROID) target_link_libraries(mltplusgpl PRIVATE rt) endif() set_target_properties(mltplusgpl PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${MLT_MODULE_OUTPUT_DIRECTORY}") install(TARGETS mltplusgpl LIBRARY DESTINATION ${MLT_INSTALL_MODULE_DIR}) install(FILES consumer_cbrts.yml filter_burningtv.yml filter_lumaliftgaingamma.yml filter_rotoscoping.yml DESTINATION ${MLT_INSTALL_DATA_DIR}/plusgpl ) mlt-7.22.0/src/modules/plusgpl/cJSON.c000664 000000 000000 00000044277 14531534050 017401 0ustar00rootroot000000 000000 /* Copyright (c) 2009 Dave Gamble Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ // cJSON // JSON parser in C. #include #include #include #include #include #include #include #include "cJSON.h" static int cJSON_strcasecmp(const char *s1,const char *s2) { if (!s1) return (s1==s2)?0:1; if (!s2) return 1; for(; tolower(*s1) == tolower(*s2); ++s1, ++s2) if(*s1 == 0) return 0; return tolower(*(const unsigned char *)s1) - tolower(*(const unsigned char *)s2); } static void *(*cJSON_malloc)(size_t sz) = malloc; static void (*cJSON_free)(void *ptr) = free; static char* cJSON_strdup(const char* str) { size_t len; char* copy; len = strlen(str) + 1; if (!(copy = (char*)cJSON_malloc(len))) return 0; memcpy(copy,str,len); return copy; } void cJSON_InitHooks(cJSON_Hooks* hooks) { if (!hooks) { /* Reset hooks */ cJSON_malloc = malloc; cJSON_free = free; return; } cJSON_malloc = (hooks->malloc_fn)?hooks->malloc_fn:malloc; cJSON_free = (hooks->free_fn)?hooks->free_fn:free; } // Internal constructor. static cJSON *cJSON_New_Item() { cJSON* node = (cJSON*)cJSON_malloc(sizeof(cJSON)); if (node) memset(node,0,sizeof(cJSON)); return node; } // Delete a cJSON structure. void cJSON_Delete(cJSON *c) { cJSON *next; while (c) { next=c->next; if (!(c->type&cJSON_IsReference) && c->child) cJSON_Delete(c->child); if (!(c->type&cJSON_IsReference) && c->valuestring) cJSON_free(c->valuestring); if (c->string) cJSON_free(c->string); cJSON_free(c); c=next; } } // Parse the input text to generate a number, and populate the result into item. static const char *parse_number(cJSON *item,const char *num) { double n=0,sign=1,scale=0;int subscale=0,signsubscale=1; // Could use sscanf for this? if (*num=='-') sign=-1,num++; // Has sign? if (*num=='0') num++; // is zero if (*num>='1' && *num<='9') do n=(n*10.0)+(*num++ -'0'); while (*num>='0' && *num<='9'); // Number? if (*num=='.') {num++; do n=(n*10.0)+(*num++ -'0'),scale--; while (*num>='0' && *num<='9');} // Fractional part? if (*num=='e' || *num=='E') // Exponent? { num++;if (*num=='+') num++; else if (*num=='-') signsubscale=-1,num++; // With sign? while (*num>='0' && *num<='9') subscale=(subscale*10)+(*num++ - '0'); // Number? } n=sign*n*pow(10.0,(scale+subscale*signsubscale)); // number = +/- number.fraction * 10^+/- exponent item->valuedouble=n; item->valueint=(int)n; item->type=cJSON_Number; return num; } // Render the number nicely from the given item into a string. static char *print_number(cJSON *item) { char *str; double d=item->valuedouble; if (fabs(((double)item->valueint)-d)<=DBL_EPSILON && d<=INT_MAX && d>=INT_MIN) { str=(char*)cJSON_malloc(21); // 2^64+1 can be represented in 21 chars. if (str) sprintf(str,"%d",item->valueint); } else { str=(char*)cJSON_malloc(64); // This is a nice tradeoff. if (str) { if (fabs(floor(d)-d)<=DBL_EPSILON) sprintf(str,"%.0f",d); else if (fabs(d)<1.0e-6 || fabs(d)>1.0e9) sprintf(str,"%e",d); else sprintf(str,"%f",d); } } return str; } // Parse the input text into an unescaped cstring, and populate item. static const unsigned char firstByteMark[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC }; static const char *parse_string(cJSON *item,const char *str) { const char *ptr=str+1;char *ptr2;char *out;int len=0;unsigned uc; if (*str!='\"') return 0; // not a string! while (*ptr!='\"' && (unsigned char)*ptr>31 && ++len) if (*ptr++ == '\\') ptr++; // Skip escaped quotes. out=(char*)cJSON_malloc(len+1); // This is how long we need for the string, roughly. if (!out) return 0; ptr=str+1;ptr2=out; while (*ptr!='\"' && (unsigned char)*ptr>31) { if (*ptr!='\\') *ptr2++=*ptr++; else { ptr++; switch (*ptr) { case 'b': *ptr2++='\b'; break; case 'f': *ptr2++='\f'; break; case 'n': *ptr2++='\n'; break; case 'r': *ptr2++='\r'; break; case 't': *ptr2++='\t'; break; case 'u': // transcode utf16 to utf8. DOES NOT SUPPORT SURROGATE PAIRS CORRECTLY. sscanf(ptr+1,"%4x",&uc); // get the unicode char. len=3;if (uc<0x80) len=1;else if (uc<0x800) len=2;ptr2+=len; switch (len) { case 3: *--ptr2 =((uc | 0x80) & 0xBF); uc >>= 6; case 2: *--ptr2 =((uc | 0x80) & 0xBF); uc >>= 6; case 1: *--ptr2 =(uc | firstByteMark[len]); } ptr2+=len;ptr+=4; break; default: *ptr2++=*ptr; break; } ptr++; } } *ptr2=0; if (*ptr=='\"') ptr++; item->valuestring=out; item->type=cJSON_String; return ptr; } // Render the cstring provided to an escaped version that can be printed. static char *print_string_ptr(const char *str) { const char *ptr;char *ptr2,*out;int len=0; if (!str) return cJSON_strdup(""); ptr=str;while (*ptr && ++len) {if ((unsigned char)*ptr<32 || *ptr=='\"' || *ptr=='\\') len++;ptr++;} out=(char*)cJSON_malloc(len+3); if (!out) return 0; ptr2=out;ptr=str; *ptr2++='\"'; while (*ptr) { if ((unsigned char)*ptr>31 && *ptr!='\"' && *ptr!='\\') *ptr2++=*ptr++; else { *ptr2++='\\'; switch (*ptr++) { case '\\': *ptr2++='\\'; break; case '\"': *ptr2++='\"'; break; case '\b': *ptr2++='b'; break; case '\f': *ptr2++='f'; break; case '\n': *ptr2++='n'; break; case '\r': *ptr2++='r'; break; case '\t': *ptr2++='t'; break; default: ptr2--; break; // eviscerate with prejudice. } } } *ptr2++='\"';*ptr2++=0; return out; } // Invote print_string_ptr (which is useful) on an item. static char *print_string(cJSON *item) {return print_string_ptr(item->valuestring);} // Predeclare these prototypes. static const char *parse_value(cJSON *item,const char *value); static char *print_value(cJSON *item,int depth,int fmt); static const char *parse_array(cJSON *item,const char *value); static char *print_array(cJSON *item,int depth,int fmt); static const char *parse_object(cJSON *item,const char *value); static char *print_object(cJSON *item,int depth,int fmt); // Utility to jump whitespace and cr/lf static const char *skip(const char *in) {while (in && (unsigned char)*in<=32) in++; return in;} // Parse an object - create a new root, and populate. cJSON *cJSON_Parse(const char *value) { cJSON *c=cJSON_New_Item(); if (!c) return 0; /* memory fail */ if (!parse_value(c,skip(value))) {cJSON_Delete(c);return 0;} return c; } // Render a cJSON item/entity/structure to text. char *cJSON_Print(cJSON *item) {return print_value(item,0,1);} char *cJSON_PrintUnformatted(cJSON *item) {return print_value(item,0,0);} // Parser core - when encountering text, process appropriately. static const char *parse_value(cJSON *item,const char *value) { if (!value) return 0; // Fail on null. if (!strncmp(value,"null",4)) { item->type=cJSON_NULL; return value+4; } if (!strncmp(value,"false",5)) { item->type=cJSON_False; return value+5; } if (!strncmp(value,"true",4)) { item->type=cJSON_True; item->valueint=1; return value+4; } if (*value=='\"') { return parse_string(item,value); } if (*value=='-' || (*value>='0' && *value<='9')) { return parse_number(item,value); } if (*value=='[') { return parse_array(item,value); } if (*value=='{') { return parse_object(item,value); } return 0; // failure. } // Render a value to text. static char *print_value(cJSON *item,int depth,int fmt) { char *out=0; if (!item) return 0; switch ((item->type)&255) { case cJSON_NULL: out=cJSON_strdup("null"); break; case cJSON_False: out=cJSON_strdup("false");break; case cJSON_True: out=cJSON_strdup("true"); break; case cJSON_Number: out=print_number(item);break; case cJSON_String: out=print_string(item);break; case cJSON_Array: out=print_array(item,depth,fmt);break; case cJSON_Object: out=print_object(item,depth,fmt);break; } return out; } // Build an array from input text. static const char *parse_array(cJSON *item,const char *value) { cJSON *child; if (*value!='[') return 0; // not an array! item->type=cJSON_Array; value=skip(value+1); if (*value==']') return value+1; // empty array. item->child=child=cJSON_New_Item(); if (!item->child) return 0; // memory fail value=skip(parse_value(child,skip(value))); // skip any spacing, get the value. if (!value) return 0; while (*value==',') { cJSON *new_item; if (!(new_item=cJSON_New_Item())) return 0; // memory fail child->next=new_item;new_item->prev=child;child=new_item; value=skip(parse_value(child,skip(value+1))); if (!value) return 0; // memory fail } if (*value==']') return value+1; // end of array return 0; // malformed. } // Render an array to text static char *print_array(cJSON *item,int depth,int fmt) { char **entries; char *out=0,*ptr,*ret;int len=5; cJSON *child=item->child; int numentries=0,i=0,fail=0; // How many entries in the array? while (child) numentries++,child=child->next; // Allocate an array to hold the values for each entries=(char**)cJSON_malloc(numentries*sizeof(char*)); if (!entries) return 0; memset(entries,0,numentries*sizeof(char*)); // Retrieve all the results: child=item->child; while (child && !fail) { ret=print_value(child,depth+1,fmt); entries[i++]=ret; if (ret) len+=strlen(ret)+2+(fmt?1:0); else fail=1; child=child->next; } // If we didn't fail, try to malloc the output string if (!fail) out=cJSON_malloc(len); // If that fails, we fail. if (!out) fail=1; // Handle failure. if (fail) { for (i=0;itype=cJSON_Object; value=skip(value+1); if (*value=='}') return value+1; // empty array. item->child=child=cJSON_New_Item(); if (!item->child) return 0; value=skip(parse_string(child,skip(value))); if (!value) return 0; child->string=child->valuestring;child->valuestring=0; if (*value!=':') return 0; // fail! value=skip(parse_value(child,skip(value+1))); // skip any spacing, get the value. if (!value) return 0; while (*value==',') { cJSON *new_item; if (!(new_item=cJSON_New_Item())) return 0; // memory fail child->next=new_item;new_item->prev=child;child=new_item; value=skip(parse_string(child,skip(value+1))); if (!value) return 0; child->string=child->valuestring;child->valuestring=0; if (*value!=':') return 0; // fail! value=skip(parse_value(child,skip(value+1))); // skip any spacing, get the value. if (!value) return 0; } if (*value=='}') return value+1; // end of array return 0; // malformed. } // Render an object to text. static char *print_object(cJSON *item,int depth,int fmt) { char **entries=0,**names=0; char *out=0,*ptr,*ret,*str;int len=7,i=0,j; cJSON *child=item->child; int numentries=0,fail=0; // Count the number of entries. while (child) numentries++,child=child->next; // Allocate space for the names and the objects entries=(char**)cJSON_malloc(numentries*sizeof(char*)); if (!entries) return 0; names=(char**)cJSON_malloc(numentries*sizeof(char*)); if (!names) {cJSON_free(entries);return 0;} memset(entries,0,sizeof(char*)*numentries); memset(names,0,sizeof(char*)*numentries); // Collect all the results into our arrays: child=item->child;depth++;if (fmt) len+=depth; while (child) { names[i]=str=print_string_ptr(child->string); entries[i++]=ret=print_value(child,depth,fmt); if (str && ret) len+=strlen(ret)+strlen(str)+2+(fmt?2+depth:0); else fail=1; child=child->next; } // Try to allocate the output string if (!fail) out=(char*)cJSON_malloc(len); if (!out) fail=1; // Handle failure if (fail) { for (i=0;ichild;int i=0;while(c)i++,c=c->next;return i;} cJSON *cJSON_GetArrayItem(cJSON *array,int item) {cJSON *c=array->child; while (c && item>0) item--,c=c->next; return c;} cJSON *cJSON_GetObjectItem(cJSON *object,const char *string) {cJSON *c=object->child; while (c && cJSON_strcasecmp(c->string,string)) c=c->next; return c;} // Utility for array list handling. static void suffix_object(cJSON *prev,cJSON *item) {prev->next=item;item->prev=prev;} // Utility for handling references. static cJSON *create_reference(cJSON *item) {cJSON *ref=cJSON_New_Item();if (!ref) return 0;memcpy(ref,item,sizeof(cJSON));ref->string=0;ref->type|=cJSON_IsReference;ref->next=ref->prev=0;return ref;} // Add item to array/object. void cJSON_AddItemToArray(cJSON *array, cJSON *item) {cJSON *c=array->child;if (!item) return; if (!c) {array->child=item;} else {while (c && c->next) c=c->next; suffix_object(c,item);}} void cJSON_AddItemToObject(cJSON *object,const char *string,cJSON *item) {if (!item) return; if (item->string) cJSON_free(item->string);item->string=cJSON_strdup(string);cJSON_AddItemToArray(object,item);} void cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item) {cJSON_AddItemToArray(array,create_reference(item));} void cJSON_AddItemReferenceToObject(cJSON *object,const char *string,cJSON *item) {cJSON_AddItemToObject(object,string,create_reference(item));} cJSON *cJSON_DetachItemFromArray(cJSON *array,int which) {cJSON *c=array->child;while (c && which>0) c=c->next,which--;if (!c) return 0; if (c->prev) c->prev->next=c->next; if (c->next) c->next->prev=c->prev; if (c==array->child) array->child=c->next; c->prev=c->next=0; return c;} void cJSON_DeleteItemFromArray(cJSON *array,int which) {cJSON_Delete(cJSON_DetachItemFromArray(array,which));} cJSON *cJSON_DetachItemFromObject(cJSON *object,const char *string) {int i=0;cJSON *c=object->child;while (c && cJSON_strcasecmp(c->string,string)) i++,c=c->next;if (c) return cJSON_DetachItemFromArray(object,i);return 0;} void cJSON_DeleteItemFromObject(cJSON *object,const char *string) {cJSON_Delete(cJSON_DetachItemFromObject(object,string));} // Replace array/object items with new ones. void cJSON_ReplaceItemInArray(cJSON *array,int which,cJSON *newitem) {cJSON *c=array->child;while (c && which>0) c=c->next,which--;if (!c) return; newitem->next=c->next;newitem->prev=c->prev;if (newitem->next) newitem->next->prev=newitem; if (c==array->child) array->child=newitem; else newitem->prev->next=newitem;c->next=c->prev=0;cJSON_Delete(c);} void cJSON_ReplaceItemInObject(cJSON *object,const char *string,cJSON *newitem){int i=0;cJSON *c=object->child;while(c && cJSON_strcasecmp(c->string,string))i++,c=c->next;if(c){newitem->string=cJSON_strdup(string);cJSON_ReplaceItemInArray(object,i,newitem);}} // Create basic types: cJSON *cJSON_CreateNull() {cJSON *item=cJSON_New_Item();if(item)item->type=cJSON_NULL;return item;} cJSON *cJSON_CreateTrue() {cJSON *item=cJSON_New_Item();if(item)item->type=cJSON_True;return item;} cJSON *cJSON_CreateFalse() {cJSON *item=cJSON_New_Item();if(item)item->type=cJSON_False;return item;} cJSON *cJSON_CreateBool(int b) {cJSON *item=cJSON_New_Item();if(item)item->type=b?cJSON_True:cJSON_False;return item;} cJSON *cJSON_CreateNumber(double num) {cJSON *item=cJSON_New_Item();if(item){item->type=cJSON_Number;item->valuedouble=num;item->valueint=(int)num;}return item;} cJSON *cJSON_CreateString(const char *string) {cJSON *item=cJSON_New_Item();if(item){item->type=cJSON_String;item->valuestring=cJSON_strdup(string);}return item;} cJSON *cJSON_CreateArray() {cJSON *item=cJSON_New_Item();if(item)item->type=cJSON_Array;return item;} cJSON *cJSON_CreateObject() {cJSON *item=cJSON_New_Item();if(item)item->type=cJSON_Object;return item;} // Create Arrays: cJSON *cJSON_CreateIntArray(int *numbers,int count) {int i;cJSON *n=0,*p=0,*a=cJSON_CreateArray();for(i=0;a && ichild=n;else suffix_object(p,n);p=n;}return a;} cJSON *cJSON_CreateFloatArray(float *numbers,int count) {int i;cJSON *n=0,*p=0,*a=cJSON_CreateArray();for(i=0;a && ichild=n;else suffix_object(p,n);p=n;}return a;} cJSON *cJSON_CreateDoubleArray(double *numbers,int count) {int i;cJSON *n=0,*p=0,*a=cJSON_CreateArray();for(i=0;a && ichild=n;else suffix_object(p,n);p=n;}return a;} cJSON *cJSON_CreateStringArray(const char **strings,int count) {int i;cJSON *n=0,*p=0,*a=cJSON_CreateArray();for(i=0;a && ichild=n;else suffix_object(p,n);p=n;}return a;} mlt-7.22.0/src/modules/plusgpl/cJSON.h000664 000000 000000 00000012515 14531534050 017374 0ustar00rootroot000000 000000 /* Copyright (c) 2009 Dave Gamble Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef cJSON__h #define cJSON__h #ifdef __cplusplus extern "C" { #endif // cJSON Types: #define cJSON_False 0 #define cJSON_True 1 #define cJSON_NULL 2 #define cJSON_Number 3 #define cJSON_String 4 #define cJSON_Array 5 #define cJSON_Object 6 #define cJSON_IsReference 256 // The cJSON structure: typedef struct cJSON { struct cJSON *next,*prev; // next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem struct cJSON *child; // An array or object item will have a child pointer pointing to a chain of the items in the array/object. int type; // The type of the item, as above. char *valuestring; // The item's string, if type==cJSON_String int valueint; // The item's number, if type==cJSON_Number double valuedouble; // The item's number, if type==cJSON_Number char *string; // The item's name string, if this item is the child of, or is in the list of subitems of an object. } cJSON; typedef struct cJSON_Hooks { void *(*malloc_fn)(size_t sz); void (*free_fn)(void *ptr); } cJSON_Hooks; // Supply malloc, realloc and free functions to cJSON extern void cJSON_InitHooks(cJSON_Hooks* hooks); // Supply a block of JSON, and this returns a cJSON object you can interrogate. Call cJSON_Delete when finished. extern cJSON *cJSON_Parse(const char *value); // Render a cJSON entity to text for transfer/storage. Free the char* when finished. extern char *cJSON_Print(cJSON *item); // Render a cJSON entity to text for transfer/storage without any formatting. Free the char* when finished. extern char *cJSON_PrintUnformatted(cJSON *item); // Delete a cJSON entity and all subentities. extern void cJSON_Delete(cJSON *c); // Returns the number of items in an array (or object). extern int cJSON_GetArraySize(cJSON *array); // Retrieve item number "item" from array "array". Returns NULL if unsuccessful. extern cJSON *cJSON_GetArrayItem(cJSON *array,int item); // Get item "string" from object. Case insensitive. extern cJSON *cJSON_GetObjectItem(cJSON *object,const char *string); // These calls create a cJSON item of the appropriate type. extern cJSON *cJSON_CreateNull(); extern cJSON *cJSON_CreateTrue(); extern cJSON *cJSON_CreateFalse(); extern cJSON *cJSON_CreateBool(int b); extern cJSON *cJSON_CreateNumber(double num); extern cJSON *cJSON_CreateString(const char *string); extern cJSON *cJSON_CreateArray(); extern cJSON *cJSON_CreateObject(); // These utilities create an Array of count items. extern cJSON *cJSON_CreateIntArray(int *numbers,int count); extern cJSON *cJSON_CreateFloatArray(float *numbers,int count); extern cJSON *cJSON_CreateDoubleArray(double *numbers,int count); extern cJSON *cJSON_CreateStringArray(const char **strings,int count); // Append item to the specified array/object. extern void cJSON_AddItemToArray(cJSON *array, cJSON *item); extern void cJSON_AddItemToObject(cJSON *object,const char *string,cJSON *item); // Append reference to item to the specified array/object. Use this when you want to add an existing cJSON to a new cJSON, but don't want to corrupt your existing cJSON. extern void cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item); extern void cJSON_AddItemReferenceToObject(cJSON *object,const char *string,cJSON *item); // Remove/Detach items from Arrays/Objects. extern cJSON *cJSON_DetachItemFromArray(cJSON *array,int which); extern void cJSON_DeleteItemFromArray(cJSON *array,int which); extern cJSON *cJSON_DetachItemFromObject(cJSON *object,const char *string); extern void cJSON_DeleteItemFromObject(cJSON *object,const char *string); // Update array items. extern void cJSON_ReplaceItemInArray(cJSON *array,int which,cJSON *newitem); extern void cJSON_ReplaceItemInObject(cJSON *object,const char *string,cJSON *newitem); #define cJSON_AddNullToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateNull()) #define cJSON_AddTrueToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateTrue()) #define cJSON_AddFalseToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateFalse()) #define cJSON_AddNumberToObject(object,name,n) cJSON_AddItemToObject(object, name, cJSON_CreateNumber(n)) #define cJSON_AddStringToObject(object,name,s) cJSON_AddItemToObject(object, name, cJSON_CreateString(s)) #ifdef __cplusplus } #endif #endif mlt-7.22.0/src/modules/plusgpl/consumer_cbrts.c000664 000000 000000 00000124237 14531534050 021510 0ustar00rootroot000000 000000 /* * consumer_cbrts.c -- output constant bitrate MPEG-2 transport stream * * Copyright (C) 2010-2015 Broadcasting Center Europe S.A. http://www.bce.lu * an RTL Group Company http://www.rtlgroup.com * Author: Dan Dennedy * Some ideas and portions come from OpenCaster, Copyright (C) Lorenzo Pallara * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifdef _WIN32 #include #else #include #endif #include #include #include #include #include #include #include #include #include #include // includes for socket IO #if (_POSIX_C_SOURCE >= 1 || _XOPEN_SOURCE || _POSIX_SOURCE) && (_POSIX_TIMERS > 0) #if !(defined(__FreeBSD_kernel__) && defined(__GLIBC__)) #define CBRTS_BSD_SOCKETS 1 #include #include #include #include #include #endif #endif #include #include #define TSP_BYTES (188) #define MAX_PID (8192) #define SCR_HZ (27000000ULL) #define NULL_PID (0x1fff) #define PAT_PID (0) #define SDT_PID (0x11) #define PCR_SMOOTHING (12) #define PCR_PERIOD_MS (20) #define RTP_BYTES (12) #define UDP_MTU (RTP_BYTES + TSP_BYTES * 7) #define REMUX_BUFFER_MIN (10) #define REMUX_BUFFER_MAX (50) #define UDP_BUFFER_MINIMUM (100) #define UDP_BUFFER_DEFAULT (1000) #define RTP_VERSION (2) #define RTP_PAYLOAD (33) #define RTP_HZ (90000) #define PIDOF(packet) (ntohs(*((uint16_t *) (packet + 1))) & 0x1fff) #define HASPCR(packet) ((packet[3] & 0x20) && (packet[4] != 0) && (packet[5] & 0x10)) #define CCOF(packet) (packet[3] & 0x0f) #define ADAPTOF(packet) ((packet[3] >> 4) & 0x03) typedef struct consumer_cbrts_s *consumer_cbrts; struct consumer_cbrts_s { struct mlt_consumer_s parent; mlt_consumer avformat; pthread_t thread; int joined; int running; mlt_position last_position; mlt_event event_registered; int fd; uint8_t *leftover_data[TSP_BYTES]; int leftover_size; mlt_deque tsp_packets; uint64_t previous_pcr; uint64_t previous_packet_count; uint64_t packet_count; int is_stuffing_set; int thread_running; uint8_t pcr_count; uint16_t pmt_pid; int is_si_sdt; int is_si_pat; int is_si_pmt; int dropped; uint8_t continuity_count[MAX_PID]; uint64_t output_counter; #ifdef CBRTS_BSD_SOCKETS struct addrinfo *addr; struct timespec timer; uint32_t nsec_per_packet; uint32_t femto_per_packet; uint64_t femto_counter; #endif int (*write_tsp)(consumer_cbrts, const void *buf, size_t count); uint8_t udp_packet[UDP_MTU]; size_t udp_bytes; size_t udp_packet_size; mlt_deque udp_packets; pthread_t output_thread; pthread_mutex_t udp_deque_mutex; pthread_cond_t udp_deque_cond; uint64_t muxrate; int udp_buffer_max; uint16_t rtp_sequence; uint32_t rtp_ssrc; uint32_t rtp_counter; }; typedef struct { int size; int period; int packet_count; uint16_t pid; uint8_t data[4096]; } ts_section; typedef struct { uint8_t *data; size_t size; } buffer_t; static uint8_t null_packet[TSP_BYTES]; /** Forward references to static functions. */ static int consumer_start(mlt_consumer parent); static int consumer_stop(mlt_consumer parent); static int consumer_is_stopped(mlt_consumer parent); static void consumer_close(mlt_consumer parent); static void *consumer_thread(void *); static void on_data_received(mlt_properties properties, mlt_consumer consumer, mlt_event_data); mlt_consumer consumer_cbrts_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { consumer_cbrts self = calloc(sizeof(struct consumer_cbrts_s), 1); if (self && mlt_consumer_init(&self->parent, self, profile) == 0) { // Get the parent consumer object mlt_consumer parent = &self->parent; // Get the properties mlt_properties properties = MLT_CONSUMER_PROPERTIES(parent); // Create child consumers self->avformat = mlt_factory_consumer(profile, "avformat", NULL); parent->close = consumer_close; parent->start = consumer_start; parent->stop = consumer_stop; parent->is_stopped = consumer_is_stopped; self->joined = 1; self->tsp_packets = mlt_deque_init(); self->udp_packets = mlt_deque_init(); // Create the null packet memset(null_packet, 0xFF, TSP_BYTES); null_packet[0] = 0x47; null_packet[1] = 0x1f; null_packet[2] = 0xff; null_packet[3] = 0x10; // Create the deque mutex and condition pthread_mutex_init(&self->udp_deque_mutex, NULL); pthread_cond_init(&self->udp_deque_cond, NULL); // Set consumer property defaults mlt_properties_set_int(properties, "real_time", -1); return parent; } free(self); return NULL; } static ts_section *load_section(const char *filename) { ts_section *section = NULL; int fd; if (!filename) return NULL; if ((fd = open(filename, O_RDONLY)) < 0) { mlt_log_error(NULL, "cbrts consumer failed to load section file %s\n", filename); return NULL; } section = malloc(sizeof(*section)); memset(section, 0xff, sizeof(*section)); section->size = 0; if (read(fd, section->data, 3)) { // get the size uint16_t *p = (uint16_t *) §ion->data[1]; section->size = p[0]; section->size = ntohs(section->size) & 0x0FFF; // read the data if (section->size <= sizeof(section->data) - 3) { ssize_t has_read = 0; while (has_read < section->size) { ssize_t n = read(fd, section->data + 3 + has_read, section->size); if (n > 0) has_read += n; else break; } section->size += 3; } else { mlt_log_error(NULL, "Section too big - skipped.\n"); } } close(fd); return section; } static void load_sections(consumer_cbrts self, mlt_properties properties) { int n = mlt_properties_count(properties); // Store the sections with automatic cleanup // and make it easy to iterate over the data sections. mlt_properties si_properties = mlt_properties_get_data(properties, "si.properties", NULL); if (!si_properties) { si_properties = mlt_properties_new(); mlt_properties_set_data(properties, "si.properties", si_properties, 0, (mlt_destructor) mlt_properties_close, NULL); } while (n--) { // Look for si..file=filename const char *name = mlt_properties_get_name(properties, n); if (strncmp("si.", name, 3) == 0 && strncmp(".file", name + strlen(name) - 5, 5) == 0) { size_t len = strlen(name); char *si_name = strdup(name + 3); // unbreak compilation on OpenBSD #ifdef si_pid #undef si_pid #endif char si_pid[len + 1]; si_name[len - 3 - 5] = 0; strcpy(si_pid, "si."); strcat(si_pid, si_name); strcat(si_pid, ".pid"); // Look for si..pid= if (mlt_properties_get(properties, si_pid)) { char *filename = mlt_properties_get_value(properties, n); ts_section *section = load_section(filename); if (section) { // Determine the periodicity of the section, if supplied char si_time[len + 1]; strcpy(si_time, "si."); strcat(si_time, si_name); strcat(si_time, ".time"); int time = mlt_properties_get_int(properties, si_time); if (time == 0) time = 200; // Set flags if we are replacing PAT or SDT if (strncasecmp("pat", si_name, 3) == 0) self->is_si_pat = 1; else if (strncasecmp("pmt", si_name, 3) == 0) self->is_si_pmt = 1; else if (strncasecmp("sdt", si_name, 3) == 0) self->is_si_sdt = 1; // Calculate the period and get the PID section->period = (self->muxrate * time) / (TSP_BYTES * 8 * 1000); // output one immediately section->packet_count = section->period - 1; mlt_log_verbose(NULL, "SI %s time=%d period=%d file=%s\n", si_name, time, section->period, filename); section->pid = mlt_properties_get_int(properties, si_pid); mlt_properties_set_data(si_properties, si_name, section, section->size, free, NULL); } } free(si_name); } } } static void write_section(consumer_cbrts self, ts_section *section) { uint8_t *packet; const uint8_t *data_ptr = section->data; uint8_t *p; int size = section->size; int first, len; while (size > 0) { first = (section->data == data_ptr); p = packet = malloc(TSP_BYTES); *p++ = 0x47; *p = (section->pid >> 8); if (first) *p |= 0x40; p++; *p++ = section->pid; *p++ = 0x10; // continuity count will be written later if (first) *p++ = 0; /* 0 offset */ len = TSP_BYTES - (p - packet); if (len > size) len = size; memcpy(p, data_ptr, len); p += len; /* add known padding data */ len = TSP_BYTES - (p - packet); if (len > 0) memset(p, 0xff, len); mlt_deque_push_back(self->tsp_packets, packet); self->packet_count++; data_ptr += len; size -= len; } } static void write_sections(consumer_cbrts self) { mlt_properties properties = mlt_properties_get_data(MLT_CONSUMER_PROPERTIES(&self->parent), "si.properties", NULL); if (properties) { int n = mlt_properties_count(properties); while (n--) { ts_section *section = mlt_properties_get_data_at(properties, n, NULL); if (++section->packet_count == section->period) { section->packet_count = 0; write_section(self, section); } } } } static uint64_t get_pcr(uint8_t *packet) { uint64_t pcr = 0; pcr += (uint64_t) packet[6] << 25; pcr += packet[7] << 17; pcr += packet[8] << 9; pcr += packet[9] << 1; pcr += packet[10] >> 7; pcr *= 300; // convert 90KHz to 27MHz pcr += (packet[10] & 0x01) << 8; pcr += packet[11]; return pcr; } static void set_pcr(uint8_t *packet, uint64_t pcr) { uint64_t pcr_base = pcr / 300; uint64_t pcr_ext = pcr % 300; packet[6] = pcr_base >> 25; packet[7] = pcr_base >> 17; packet[8] = pcr_base >> 9; packet[9] = pcr_base >> 1; packet[10] = ((pcr_base & 1) << 7) | 0x7E | ((pcr_ext & 0x100) >> 8); packet[11] = pcr_ext; } static uint64_t update_pcr(consumer_cbrts self, uint64_t muxrate, unsigned packets) { return self->previous_pcr + packets * TSP_BYTES * 8 * SCR_HZ / muxrate; } static uint32_t get_rtp_timestamp(consumer_cbrts self) { return self->rtp_counter++ * self->udp_packet_size * 8 * RTP_HZ / self->muxrate; } static double measure_bitrate(consumer_cbrts self, uint64_t pcr, int drop) { double muxrate = 0; if (self->is_stuffing_set || self->previous_pcr) { muxrate = (self->packet_count - self->previous_packet_count - drop) * TSP_BYTES * 8; if (pcr >= self->previous_pcr) muxrate /= (double) (pcr - self->previous_pcr) / SCR_HZ; else muxrate /= (((double) (1ULL << 33) - 1) * 300 - self->previous_pcr + pcr) / SCR_HZ; mlt_log_debug(NULL, "measured TS bitrate %.1f bits/sec PCR %" PRIu64 "\n", muxrate, pcr); } return muxrate; } static int writen(consumer_cbrts self, const void *buf, size_t count) { int result = 0; int written = 0; while (written < count) { if ((result = write(self->fd, buf + written, count - written)) < 0) { mlt_log_error(MLT_CONSUMER_SERVICE(&self->parent), "Failed to write: %s\n", strerror(errno)); break; } written += result; } return result; } #ifdef CBRTS_BSD_SOCKETS static int sendn(consumer_cbrts self, const void *buf, size_t count) { int result = 0; int written = 0; while (written < count) { result = sendto(self->fd, buf + written, count - written, 0, self->addr->ai_addr, self->addr->ai_addrlen); if (result < 0) { mlt_log_error(MLT_CONSUMER_SERVICE(&self->parent), "Failed to send: %s\n", strerror(errno)); exit(EXIT_FAILURE); break; } written += result; } return result; } #endif static int write_udp(consumer_cbrts self, const void *buf, size_t count) { int result = 0; #ifdef CBRTS_BSD_SOCKETS if (!self->timer.tv_sec) clock_gettime(CLOCK_MONOTONIC, &self->timer); self->femto_counter += self->femto_per_packet; self->timer.tv_nsec += self->femto_counter / 1000000; self->femto_counter = self->femto_counter % 1000000; self->timer.tv_nsec += self->nsec_per_packet; self->timer.tv_sec += self->timer.tv_nsec / 1000000000; self->timer.tv_nsec = self->timer.tv_nsec % 1000000000; clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &self->timer, NULL); result = sendn(self, buf, count); #endif return result; } // socket IO code static int create_socket(consumer_cbrts self) { int result = -1; #ifdef CBRTS_BSD_SOCKETS struct addrinfo hints = {0}; mlt_properties properties = MLT_CONSUMER_PROPERTIES(&self->parent); const char *hostname = mlt_properties_get(properties, "udp.address"); const char *port = "1234"; if (mlt_properties_get(properties, "udp.port")) port = mlt_properties_get(properties, "udp.port"); // Resolve the address string and port. hints.ai_socktype = SOCK_DGRAM; hints.ai_family = AF_UNSPEC; hints.ai_flags = AI_PASSIVE; result = getaddrinfo(hostname, port, &hints, &self->addr); if (result < 0) { mlt_log_error(MLT_CONSUMER_SERVICE(&self->parent), "Error resolving UDP address and port: %s.\n", gai_strerror(result)); return result; } // Create the socket descriptor. struct addrinfo *addr = self->addr; for (; addr; addr = addr->ai_next) { result = socket(addr->ai_addr->sa_family, SOCK_DGRAM, addr->ai_protocol); if (result != -1) { // success self->fd = result; result = 0; break; } } if (result < 0) { mlt_log_error(MLT_CONSUMER_SERVICE(&self->parent), "Error creating socket: %s.\n", strerror(errno)); freeaddrinfo(self->addr); self->addr = NULL; return result; } // Set the reuse address socket option if not disabled (explicitly set 0). int reuse = mlt_properties_get_int(properties, "udp.reuse") || !mlt_properties_get(properties, "udp.reuse"); if (reuse) { result = setsockopt(self->fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)); if (result < 0) { mlt_log_error(MLT_CONSUMER_SERVICE(&self->parent), "Error setting the reuse address socket option.\n"); close(self->fd); freeaddrinfo(self->addr); self->addr = NULL; return result; } } // Set the socket buffer size if supplied. if (mlt_properties_get(properties, "udp.sockbufsize")) { int sockbufsize = mlt_properties_get_int(properties, "udp.sockbufsize"); result = setsockopt(self->fd, SOL_SOCKET, SO_SNDBUF, &sockbufsize, sizeof(sockbufsize)); if (result < 0) { mlt_log_error(MLT_CONSUMER_SERVICE(&self->parent), "Error setting the socket buffer size.\n"); close(self->fd); freeaddrinfo(self->addr); self->addr = NULL; return result; } } // Set the multicast TTL if supplied. if (mlt_properties_get(properties, "udp.ttl")) { int ttl = mlt_properties_get_int(properties, "udp.ttl"); if (addr->ai_addr->sa_family == AF_INET) { result = setsockopt(self->fd, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)); } else if (addr->ai_addr->sa_family == AF_INET6) { result = setsockopt(self->fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &ttl, sizeof(ttl)); } if (result < 0) { mlt_log_error(MLT_CONSUMER_SERVICE(&self->parent), "Error setting the multicast TTL.\n"); close(self->fd); freeaddrinfo(self->addr); self->addr = NULL; return result; } } // Set the multicast interface if supplied. if (mlt_properties_get(properties, "udp.interface")) { const char *interface = mlt_properties_get(properties, "udp.interface"); unsigned int iface = if_nametoindex(interface); if (iface) { if (addr->ai_addr->sa_family == AF_INET) { struct ip_mreqn req = {{0}}; req.imr_ifindex = iface; result = setsockopt(self->fd, IPPROTO_IP, IP_MULTICAST_IF, &req, sizeof(req)); } else if (addr->ai_addr->sa_family == AF_INET6) { result = setsockopt(self->fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, &iface, sizeof(iface)); } if (result < 0) { mlt_log_error(MLT_CONSUMER_SERVICE(&self->parent), "Error setting the multicast interface.\n"); close(self->fd); freeaddrinfo(self->addr); self->addr = NULL; return result; } } else { mlt_log_warning(MLT_CONSUMER_SERVICE(&self->parent), "The network interface \"%s\" was not found.\n", interface); } } #endif return result; } static void *output_thread(void *arg) { consumer_cbrts self = arg; int result = 0; while (self->thread_running) { pthread_mutex_lock(&self->udp_deque_mutex); while (self->thread_running && mlt_deque_count(self->udp_packets) < 1) pthread_cond_wait(&self->udp_deque_cond, &self->udp_deque_mutex); pthread_mutex_unlock(&self->udp_deque_mutex); // Dequeue the UDP packets and write them. int i = mlt_deque_count(self->udp_packets); mlt_log_debug(MLT_CONSUMER_SERVICE(&self->parent), "%s: count %d\n", __FUNCTION__, i); while (self->thread_running && i-- && result >= 0) { pthread_mutex_lock(&self->udp_deque_mutex); uint8_t *packet = mlt_deque_pop_front(self->udp_packets); pthread_cond_broadcast(&self->udp_deque_cond); pthread_mutex_unlock(&self->udp_deque_mutex); size_t size = self->rtp_ssrc ? RTP_BYTES + self->udp_packet_size : self->udp_packet_size; result = write_udp(self, packet, size); free(packet); } } return NULL; } static int enqueue_udp(consumer_cbrts self, const void *buf, size_t count) { // Append TSP to the UDP packet. memcpy(&self->udp_packet[self->udp_bytes], buf, count); self->udp_bytes = (self->udp_bytes + count) % self->udp_packet_size; // Send the UDP packet. if (!self->udp_bytes) { size_t offset = self->rtp_ssrc ? RTP_BYTES : 0; // Duplicate the packet. uint8_t *packet = malloc(self->udp_packet_size + offset); memcpy(packet + offset, self->udp_packet, self->udp_packet_size); // Add the RTP header. if (self->rtp_ssrc) { // Padding, extension, and CSRC count are all 0. packet[0] = RTP_VERSION << 6; // Marker bit is 0. packet[1] = RTP_PAYLOAD & 0x7f; packet[2] = (self->rtp_sequence >> 8) & 0xff; packet[3] = (self->rtp_sequence >> 0) & 0xff; // Timestamp in next 4 bytes. uint32_t timestamp = get_rtp_timestamp(self); packet[4] = (timestamp >> 24) & 0xff; packet[5] = (timestamp >> 16) & 0xff; packet[6] = (timestamp >> 8) & 0xff; packet[7] = (timestamp >> 0) & 0xff; // SSRC in next 4 bytes. packet[8] = (self->rtp_ssrc >> 24) & 0xff; packet[9] = (self->rtp_ssrc >> 16) & 0xff; packet[10] = (self->rtp_ssrc >> 8) & 0xff; packet[11] = (self->rtp_ssrc >> 0) & 0xff; self->rtp_sequence++; } // Wait for room in the fifo. pthread_mutex_lock(&self->udp_deque_mutex); while (self->thread_running && mlt_deque_count(self->udp_packets) >= self->udp_buffer_max) pthread_cond_wait(&self->udp_deque_cond, &self->udp_deque_mutex); // Add the packet to the fifo. mlt_deque_push_back(self->udp_packets, packet); pthread_cond_broadcast(&self->udp_deque_cond); pthread_mutex_unlock(&self->udp_deque_mutex); } return 0; } static int insert_pcr(consumer_cbrts self, uint16_t pid, uint8_t cc, uint64_t pcr) { uint8_t packet[TSP_BYTES]; uint8_t *p = packet; *p++ = 0x47; *p++ = pid >> 8; *p++ = pid; *p++ = 0x20 | cc; // Adaptation only // Continuity Count field does not increment (see 13818-1 section 2.4.3.3) *p++ = TSP_BYTES - 5; // Adaptation Field Length *p++ = 0x10; // Adaptation flags: PCR present set_pcr(packet, pcr); p += 6; // 6 pcr bytes memset(p, 0xff, TSP_BYTES - (p - packet)); // stuffing return self->write_tsp(self, packet, TSP_BYTES); } static int output_cbr(consumer_cbrts self, uint64_t input_rate, uint64_t output_rate, uint64_t *pcr) { int n = mlt_deque_count(self->tsp_packets); unsigned output_packets = 0; unsigned packets_since_pcr = 0; int result = 0; int dropped = 0; int warned = 0; uint16_t pcr_pid = 0; uint8_t cc = 0xff; float ms_since_pcr; float ms_to_end; uint64_t input_counter = 0; uint64_t last_input_counter; mlt_log_debug(NULL, "%s: n %i output_counter %" PRIu64 " input_rate %" PRIu64 "\n", __FUNCTION__, n, self->output_counter, input_rate); while (self->thread_running && n-- && result >= 0) { uint8_t *packet = mlt_deque_pop_front(self->tsp_packets); uint16_t pid = PIDOF(packet); // Check for overflow if (input_rate > output_rate && !HASPCR(packet) && pid != SDT_PID && pid != PAT_PID && pid != self->pmt_pid) { if (!warned) { mlt_log_warning(MLT_CONSUMER_SERVICE(&self->parent), "muxrate too low %" PRIu64 " > %" PRIu64 "\n", input_rate, output_rate); warned = 1; } // Skip this packet free(packet); // Compute new input_rate based on dropped count input_rate = measure_bitrate(self, *pcr, ++dropped); continue; } if (HASPCR(packet)) { pcr_pid = pid; set_pcr(packet, update_pcr(self, output_rate, output_packets)); packets_since_pcr = 0; } // Rewrite the continuity counter if not only adaptation field if (ADAPTOF(packet) != 2) { packet[3] = (packet[3] & 0xf0) | self->continuity_count[pid]; self->continuity_count[pid] = (self->continuity_count[pid] + 1) & 0xf; } if (pcr_pid && pid == pcr_pid) cc = CCOF(packet); result = self->write_tsp(self, packet, TSP_BYTES); free(packet); if (result < 0) break; output_packets++; packets_since_pcr++; self->output_counter += TSP_BYTES * 8 * output_rate; input_counter += TSP_BYTES * 8 * input_rate; // See if we need to output a dummy packet with PCR ms_since_pcr = (float) (packets_since_pcr + 1) * 8 * TSP_BYTES * 1000 / output_rate; ms_to_end = (float) n * 8 * TSP_BYTES * 1000 / input_rate; if (pcr_pid && ms_since_pcr >= PCR_PERIOD_MS && ms_to_end > PCR_PERIOD_MS / 2.0) { uint64_t new_pcr = update_pcr(self, output_rate, output_packets); if (ms_since_pcr > 40) mlt_log_warning(NULL, "exceeded PCR interval %.2f ms queued %.2f ms\n", ms_since_pcr, ms_to_end); if ((result = insert_pcr(self, pcr_pid, cc, new_pcr)) < 0) break; packets_since_pcr = 0; output_packets++; input_counter += TSP_BYTES * 8 * input_rate; } // Output null packets as needed while (self->thread_running && input_counter + (TSP_BYTES * 8 * input_rate) <= self->output_counter) { // See if we need to output a dummy packet with PCR ms_since_pcr = (float) (packets_since_pcr + 1) * 8 * TSP_BYTES * 1000 / output_rate; ms_to_end = (float) n * 8 * TSP_BYTES * 1000 / input_rate; if (pcr_pid && ms_since_pcr >= PCR_PERIOD_MS && ms_to_end > PCR_PERIOD_MS / 2.0) { uint64_t new_pcr = update_pcr(self, output_rate, output_packets); if (ms_since_pcr > 40) mlt_log_warning(NULL, "exceeded PCR interval %.2f ms queued %.2f ms\n", ms_since_pcr, ms_to_end); if ((result = insert_pcr(self, pcr_pid, cc, new_pcr)) < 0) break; packets_since_pcr = 0; } else { // Otherwise output a null packet if ((result = self->write_tsp(self, null_packet, TSP_BYTES)) < 0) break; packets_since_pcr++; } output_packets++; // Increment input last_input_counter = input_counter; input_counter += TSP_BYTES * 8 * input_rate; // Handle wrapping if (last_input_counter > input_counter) { last_input_counter -= self->output_counter; self->output_counter = 0; input_counter = last_input_counter + TSP_BYTES * 8 * input_rate; } } } // Reset counters leaving a residual output count if (input_counter < self->output_counter) self->output_counter -= input_counter; else self->output_counter = 0; // Warn if the PCR interval is too large or small ms_since_pcr = (float) packets_since_pcr * 8 * TSP_BYTES * 1000 / output_rate; if (ms_since_pcr > 40) mlt_log_warning(NULL, "exceeded PCR interval %.2f ms\n", ms_since_pcr); else if (ms_since_pcr < PCR_PERIOD_MS / 2.0) mlt_log_debug(NULL, "PCR interval too short %.2f ms\n", ms_since_pcr); // Update the current PCR based on number of packets output *pcr = update_pcr(self, output_rate, output_packets); return result; } static void get_pmt_pid(consumer_cbrts self, uint8_t *packet) { // Skip 5 bytes of TSP header + 8 bytes of section header + 2 bytes of service ID uint16_t *p = (uint16_t *) (packet + 5 + 8 + 2); self->pmt_pid = ntohs(p[0]) & 0x1fff; mlt_log_debug(NULL, "PMT PID 0x%04x\n", self->pmt_pid); return; } static int remux_packet(consumer_cbrts self, uint8_t *packet) { mlt_service service = MLT_CONSUMER_SERVICE(&self->parent); uint16_t pid = PIDOF(packet); int result = 0; write_sections(self); // Sanity checks if (packet[0] != 0x47) { mlt_log_panic(service, "NOT SYNC BYTE 0x%02x\n", packet[0]); exit(1); } if (pid == NULL_PID) { mlt_log_panic(service, "NULL PACKET\n"); exit(1); } // Measure the bitrate between consecutive PCRs if (HASPCR(packet)) { if (self->pcr_count++ % PCR_SMOOTHING == 0) { uint64_t pcr = get_pcr(packet); double input_rate = measure_bitrate(self, pcr, 0); if (input_rate > 0) { self->is_stuffing_set = 1; if (input_rate > 1.0) { result = output_cbr(self, input_rate, self->muxrate, &pcr); set_pcr(packet, pcr); } } self->previous_pcr = pcr; self->previous_packet_count = self->packet_count; } } mlt_deque_push_back(self->tsp_packets, packet); self->packet_count++; return result; } static void start_output_thread(consumer_cbrts self) { int rtprio = mlt_properties_get_int(MLT_CONSUMER_PROPERTIES(&self->parent), "udp.rtprio"); self->thread_running = 1; if (rtprio > 0) { // Use realtime priority class struct sched_param priority; pthread_attr_t thread_attributes; pthread_attr_init(&thread_attributes); priority.sched_priority = rtprio; pthread_attr_setschedpolicy(&thread_attributes, SCHED_FIFO); pthread_attr_setschedparam(&thread_attributes, &priority); #if !defined(__ANDROID__) || (defined(__ANDROID__) && __ANDROID_API__ >= 28) pthread_attr_setinheritsched(&thread_attributes, PTHREAD_EXPLICIT_SCHED); #endif pthread_attr_setscope(&thread_attributes, PTHREAD_SCOPE_SYSTEM); if (pthread_create(&self->output_thread, &thread_attributes, output_thread, self) < 0) { mlt_log_info(MLT_CONSUMER_SERVICE(&self->parent), "failed to initialize output thread with realtime priority\n"); pthread_create(&self->output_thread, &thread_attributes, output_thread, self); } pthread_attr_destroy(&thread_attributes); } else { // Use normal priority class pthread_create(&self->output_thread, NULL, output_thread, self); } } static void stop_output_thread(consumer_cbrts self) { self->thread_running = 0; // Broadcast to the condition in case it's waiting. pthread_mutex_lock(&self->udp_deque_mutex); pthread_cond_broadcast(&self->udp_deque_cond); pthread_mutex_unlock(&self->udp_deque_mutex); // Join the thread. pthread_join(self->output_thread, NULL); // Release the buffered packets. pthread_mutex_lock(&self->udp_deque_mutex); int n = mlt_deque_count(self->udp_packets); while (n--) free(mlt_deque_pop_back(self->udp_packets)); pthread_mutex_unlock(&self->udp_deque_mutex); } static inline int filter_packet(consumer_cbrts self, uint8_t *packet) { uint16_t pid = PIDOF(packet); // We are going to keep the existing PMT; replace all other signaling sections. int result = (pid == NULL_PID) || (pid == PAT_PID && self->is_si_pat) || (pid == self->pmt_pid && self->is_si_pmt) || (pid == SDT_PID && self->is_si_sdt); // Get the PMT PID from the PAT if (pid == PAT_PID && !self->pmt_pid) { get_pmt_pid(self, packet); if (self->is_si_pmt) result = 1; } return result; } static void filter_remux_or_write_packet(consumer_cbrts self, uint8_t *packet) { int remux = !mlt_properties_get_int(MLT_CONSUMER_PROPERTIES(&self->parent), "noremux"); // Filter out packets if (remux) { if (!filter_packet(self, packet)) remux_packet(self, packet); else free(packet); } else { self->write_tsp(self, packet, TSP_BYTES); free(packet); } } static void on_data_received(mlt_properties properties, mlt_consumer consumer, mlt_event_data event_data) { buffer_t *buffer = mlt_event_data_to_object(event_data); uint8_t *buf = buffer->data; size_t size = buffer->size; if (size > 0) { consumer_cbrts self = (consumer_cbrts) consumer->child; // Sanity check if (self->leftover_size == 0 && buf[0] != 0x47) { mlt_log_verbose(MLT_CONSUMER_SERVICE(consumer), "NOT SYNC BYTE 0x%02x\n", buf[0]); while (size && buf[0] != 0x47) { buf++; size--; } if (size <= 0) exit(1); } // Enqueue the packets int num_packets = (self->leftover_size + size) / TSP_BYTES; int remaining = (self->leftover_size + size) % TSP_BYTES; uint8_t *packet = NULL; int i; // mlt_log_verbose( MLT_CONSUMER_SERVICE(consumer), "%s: packets %d remaining %i\n", __FUNCTION__, num_packets, self->leftover_size ); if (self->leftover_size) { packet = malloc(TSP_BYTES); memcpy(packet, self->leftover_data, self->leftover_size); memcpy(packet + self->leftover_size, buf, TSP_BYTES - self->leftover_size); buf += TSP_BYTES - self->leftover_size; --num_packets; filter_remux_or_write_packet(self, packet); } for (i = 0; i < num_packets; i++, buf += TSP_BYTES) { packet = malloc(TSP_BYTES); memcpy(packet, buf, TSP_BYTES); filter_remux_or_write_packet(self, packet); } self->leftover_size = remaining; memcpy(self->leftover_data, buf, self->leftover_size); if (!self->thread_running) start_output_thread(self); mlt_log_debug(MLT_CONSUMER_SERVICE(consumer), "%s: %p 0x%x (%u)\n", __FUNCTION__, buf, *buf, (unsigned int) size % TSP_BYTES); // Do direct output // result = self->write_tsp( self, buf, size ); } } static int consumer_start(mlt_consumer parent) { consumer_cbrts self = parent->child; if (!self->running) { mlt_properties properties = MLT_CONSUMER_PROPERTIES(parent); mlt_properties avformat = MLT_CONSUMER_PROPERTIES(self->avformat); // Cleanup after a possible abort consumer_stop(parent); // Pass properties down mlt_properties_pass(avformat, properties, ""); mlt_properties_set_data(avformat, "app_lock", mlt_properties_get_data(properties, "app_lock", NULL), 0, NULL, NULL); mlt_properties_set_data(avformat, "app_unlock", mlt_properties_get_data(properties, "app_unlock", NULL), 0, NULL, NULL); mlt_properties_set_int(avformat, "put_mode", 1); mlt_properties_set_int(avformat, "real_time", -1); mlt_properties_set_int(avformat, "buffer", 2); mlt_properties_set_int(avformat, "terminate_on_pause", 0); mlt_properties_set_int(avformat, "muxrate", 1); mlt_properties_set_int(avformat, "redirect", 1); mlt_properties_set(avformat, "f", "mpegts"); self->dropped = 0; self->fd = STDOUT_FILENO; self->write_tsp = writen; self->muxrate = mlt_properties_get_int64(MLT_CONSUMER_PROPERTIES(&self->parent), "muxrate"); if (mlt_properties_get(properties, "udp.address")) { if (create_socket(self) >= 0) { int is_rtp = 1; if (mlt_properties_get(properties, "udp.rtp")) is_rtp = !!mlt_properties_get_int(properties, "udp.rtp"); if (is_rtp) { self->rtp_ssrc = mlt_properties_get_int(properties, "udp.rtp_ssrc"); while (!self->rtp_ssrc) self->rtp_ssrc = (uint32_t) rand(); self->rtp_counter = (uint32_t) rand(); } self->udp_packet_size = mlt_properties_get_int(properties, "udp.nb_tsp") * TSP_BYTES; if (self->udp_packet_size <= 0 || self->udp_packet_size > UDP_MTU) self->udp_packet_size = 7 * TSP_BYTES; #ifdef CBRTS_BSD_SOCKETS self->nsec_per_packet = 1000000000UL * self->udp_packet_size * 8 / self->muxrate; self->femto_per_packet = 1000000000000000ULL * self->udp_packet_size * 8 / self->muxrate - self->nsec_per_packet * 1000000; #endif self->udp_buffer_max = mlt_properties_get_int(properties, "udp.buffer"); if (self->udp_buffer_max < UDP_BUFFER_MINIMUM) self->udp_buffer_max = UDP_BUFFER_DEFAULT; self->write_tsp = enqueue_udp; } } // Load the DVB PSI/SI sections load_sections(self, properties); // Start the FFmpeg consumer and our thread mlt_consumer_start(self->avformat); pthread_create(&self->thread, NULL, consumer_thread, self); self->running = 1; self->joined = 0; } return 0; } static int consumer_stop(mlt_consumer parent) { // Get the actual object consumer_cbrts self = parent->child; if (!self->joined) { mlt_properties properties = MLT_CONSUMER_PROPERTIES(parent); int app_locked = mlt_properties_get_int(properties, "app_locked"); void (*lock)(void) = mlt_properties_get_data(properties, "app_lock", NULL); void (*unlock)(void) = mlt_properties_get_data(properties, "app_unlock", NULL); if (app_locked && unlock) unlock(); // Kill the threads and clean up self->running = 0; #ifndef _WIN32 if (self->thread) #endif pthread_join(self->thread, NULL); self->joined = 1; if (self->avformat) mlt_consumer_stop(self->avformat); stop_output_thread(self); if (self->fd > 1) close(self->fd); if (app_locked && lock) lock(); } return 0; } static int consumer_is_stopped(mlt_consumer parent) { consumer_cbrts self = parent->child; return !self->running; } static void *consumer_thread(void *arg) { // Identify the arg consumer_cbrts self = arg; // Get the consumer and producer mlt_consumer consumer = &self->parent; // internal initialization mlt_frame frame = NULL; int last_position = -1; // Loop until told not to while (self->running) { // Get a frame from the attached producer frame = mlt_consumer_rt_frame(consumer); // Ensure that we have a frame if (self->running && frame) { if (mlt_properties_get_int(MLT_FRAME_PROPERTIES(frame), "rendered") != 1) { mlt_frame_close(frame); mlt_log_warning(MLT_CONSUMER_SERVICE(consumer), "dropped frame %d\n", ++self->dropped); continue; } // Get the speed of the frame double speed = mlt_properties_get_double(MLT_FRAME_PROPERTIES(frame), "_speed"); // Optimisation to reduce latency if (speed == 1.0) { if (last_position != -1 && last_position + 1 != mlt_frame_get_position(frame)) mlt_consumer_purge(self->avformat); last_position = mlt_frame_get_position(frame); } else { //mlt_consumer_purge( this->play ); last_position = -1; } mlt_consumer_put_frame(self->avformat, frame); // Setup event listener as a callback from consumer avformat if (!self->event_registered) self->event_registered = mlt_events_listen(MLT_CONSUMER_PROPERTIES(self->avformat), consumer, "avformat-write", (mlt_listener) on_data_received); } else { if (frame) mlt_frame_close(frame); mlt_consumer_put_frame(self->avformat, NULL); self->running = 0; } } return NULL; } /** Callback to allow override of the close method. */ static void consumer_close(mlt_consumer parent) { // Get the actual object consumer_cbrts self = parent->child; // Stop the consumer mlt_consumer_stop(parent); // Close the child consumers mlt_consumer_close(self->avformat); // Now clean up the rest mlt_deque_close(self->tsp_packets); mlt_deque_close(self->udp_packets); mlt_consumer_close(parent); // Finally clean up this free(self); } mlt-7.22.0/src/modules/plusgpl/consumer_cbrts.yml000664 000000 000000 00000007113 14531534050 022060 0ustar00rootroot000000 000000 schema_version: 0.3 type: consumer identifier: cbrts title: CBR MPEG2-TS version: 2 copyright: Copyright (C) 2010-2015 Broadcasting Center Europe S.A. http://www.bce.lu license: GPLv2 language: en creator: Dan Dennedy tags: - Audio - Video description: Constant bit-rate MPEG-2 Transport Stream notes: | All properties, except some key operational properties such as real_time and terminate_on_pause, set on the this consumer are passed onto an encapsulated avformat consumer - no special prefix required. While some avformat properties can accept a "k" suffix, this consumer requires "muxrate" but does not understand the "k" suffix; so, specify the value in bytes per second. The stream is always output to STDOUT at this time. You can rewrite and insert table sections into the transport stream. If you choose to rewrite the PMT sections, then you need to know how libavformat sets the PIDs on the elementary streams. Currently, the video stream is 256 (0x100) and audio streams start at 257, incrementing from there. There are conventions for property names to pass the .sec files to the consumer. The conventions are: si.
.file= si.
.pid= si.
.time=
is really anything, but typically: pat, sdt, nit, eit, etc. "pat," "pmt," and "sdt" are special such that when supplied, they cause libavformat's corresponding sections to be filtered out and replaced with yours. You should always use PID 16 for NIT, 17 for SDT, and of course, 0 for PAT; PMT may be anything, but libavformat uses 4095 (0xfff). The time property indicates the frequency to insert the section - every N milliseconds. parameters: - identifier: muxrate type: integer unit: bytes/second - identifier: udp.rtprio title: Real-time priority description: > When set to a valid value, this makes the network output thread run with a real-time policy and priority where 1 is lowest and 99 is highest. type: integer minimum: 1 maximum: 99 - identifier: udp.address title: UDP address description: > If an IP address is provided, the stream is sent over UDP instead of STDOUT. type: string - identifier: udp.port title: UDP port type: integer minimum: 0 default: 1234 - identifier: udp.ttl title: Multicast TTL description: > The multicast time-to-live value controls how many routing hops the multicast will survive. type: integer minimum: 0 maximum: 255 - identifier: udp.reuse title: Reuse socket address description: > When not supplied, the socket is opened with the reuse address option. Set this to 0 to disable that. type: boolean default: 1 - identifier: udp.sockbufsize title: Socket buffer size type: integer minimum: 1 unit: bytes - identifier: udp.nb_tsp title: TS packets per UDP packet type: integer minimum: 0 maximum: 7 default: 7 - identifier: udp.buffer title: Max buffer IP packets type: integer minimum: 100 default: 1000 - identifier: udp.rtp title: Use RTP type: boolean default: 1 - identifier: udp.rtp_ssrc title: RTP synchronization source type: integer description: The default is a random number, but you can override it. - identifier: udp.interface title: Multicast interface name description: > Normally the multicast interface is selected by the IP routing table configured on the system, but this might be more convenient. It takes a name like "eth0". type: string mlt-7.22.0/src/modules/plusgpl/factory.c000664 000000 000000 00000007003 14531534050 020116 0ustar00rootroot000000 000000 /* * factory.c -- the factory method interfaces * Copyright (C) 2008-2022 Dan Dennedy * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include extern mlt_consumer consumer_cbrts_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_burn_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_lumaliftgaingamma_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_rotoscoping_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_telecide_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); static mlt_properties metadata(mlt_service_type type, const char *id, void *data) { char file[PATH_MAX]; snprintf(file, PATH_MAX, "%s/plusgpl/%s", mlt_environment("MLT_DATA"), (char *) data); return mlt_properties_parse_yaml(file); } MLT_REPOSITORY { MLT_REGISTER(mlt_service_consumer_type, "cbrts", consumer_cbrts_init); MLT_REGISTER(mlt_service_filter_type, "BurningTV", filter_burn_init); MLT_REGISTER(mlt_service_filter_type, "burningtv", filter_burn_init); MLT_REGISTER(mlt_service_filter_type, "lumaliftgaingamma", filter_lumaliftgaingamma_init); MLT_REGISTER(mlt_service_filter_type, "rotoscoping", filter_rotoscoping_init); MLT_REGISTER(mlt_service_filter_type, "telecide", filter_telecide_init); MLT_REGISTER_METADATA(mlt_service_consumer_type, "cbrts", metadata, "consumer_cbrts.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "BurningTV", metadata, "filter_burningtv.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "burningtv", metadata, "filter_burningtv.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "lumaliftgaingamma", metadata, "filter_lumaliftgaingamma.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "rotoscoping", metadata, "filter_rotoscoping.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "telecide", metadata, "filter_telecide.yml"); } mlt-7.22.0/src/modules/plusgpl/filter_burn.c000664 000000 000000 00000017414 14531534050 020771 0ustar00rootroot000000 000000 /* * filter_burn.c -- burning filter * Copyright (C) 2007 Stephane Fillod * * Filter taken from EffecTV - Realtime Digital Video Effector * Copyright (C) 2001-2006 FUKUCHI Kentaro * * BurningTV - burns incoming objects. * Copyright (C) 2001-2002 FUKUCHI Kentaro * * Fire routine is taken from Frank Jan Sorensen's demo program. * * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include "utils.h" #include #include #include #include #define MaxColor 120 #define Decay 15 #define MAGIC_THRESHOLD "50" static RGB32 palette[256]; static void makePalette(void) { int i, r, g, b; uint8_t *p = (uint8_t *) palette; for (i = 0; i < MaxColor; i++) { HSItoRGB(4.6 - 1.5 * i / MaxColor, (double) i / MaxColor, (double) i / MaxColor, &r, &g, &b); *p++ = r & 0xfe; *p++ = g & 0xfe; *p++ = b & 0xfe; p++; } for (i = MaxColor; i < 256; i++) { if (r < 255) r++; if (r < 255) r++; if (r < 255) r++; if (g < 255) g++; if (g < 255) g++; if (b < 255) b++; if (b < 255) b++; *p++ = r & 0xfe; *p++ = g & 0xfe; *p++ = b & 0xfe; p++; } } static int filter_get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { RGB32 *background; unsigned char *diff; unsigned char *buffer; // Get the filter mlt_filter filter = mlt_frame_pop_service(frame); // Get the image *format = mlt_image_rgba; int error = mlt_frame_get_image(frame, image, format, width, height, 1); // Only process if we have no error and a valid colour space if (error == 0) { mlt_properties properties = MLT_FILTER_PROPERTIES(filter); mlt_position pos = mlt_filter_get_position(filter, frame); mlt_position len = mlt_filter_get_length2(filter, frame); // Get the "Burn the foreground" value int burn_foreground = mlt_properties_get_int(properties, "foreground"); int animated_threshold = mlt_properties_anim_get_int(properties, "threshold", pos, len); int y_threshold = image_set_threshold_y(animated_threshold); // We'll process pixel by pixel int x = 0; int y = 0; int i; int video_width = *width; int video_height = *height; int video_area = video_width * video_height; // We need to create a new frame as this effect modifies the input RGB32 *dest = (RGB32 *) *image; RGB32 *src = (RGB32 *) *image; unsigned char v, w; RGB32 a, b, c; mlt_service_lock(MLT_FILTER_SERVICE(filter)); diff = mlt_properties_get_data(MLT_FILTER_PROPERTIES(filter), "_diff", NULL); if (diff == NULL) { // TODO: What if the image size changes? diff = mlt_pool_alloc(video_area * sizeof(unsigned char)); mlt_properties_set_data(MLT_FILTER_PROPERTIES(filter), "_diff", diff, video_area * sizeof(unsigned char), mlt_pool_release, NULL); } buffer = mlt_properties_get_data(MLT_FILTER_PROPERTIES(filter), "_buffer", NULL); if (buffer == NULL) { // TODO: What if the image size changes? buffer = mlt_pool_alloc(video_area * sizeof(unsigned char)); memset(buffer, 0, video_area * sizeof(unsigned char)); mlt_properties_set_data(MLT_FILTER_PROPERTIES(filter), "_buffer", buffer, video_area * sizeof(unsigned char), mlt_pool_release, NULL); } if (burn_foreground == 1) { /* to burn the foreground, we need a background */ background = mlt_properties_get_data(MLT_FILTER_PROPERTIES(filter), "_background", NULL); if (background == NULL) { // TODO: What if the image size changes? background = mlt_pool_alloc(video_area * sizeof(RGB32)); image_bgset_y(background, src, video_area, y_threshold); mlt_properties_set_data(MLT_FILTER_PROPERTIES(filter), "_background", background, video_area * sizeof(RGB32), mlt_pool_release, NULL); } } if (burn_foreground == 1) { image_bgsubtract_y(diff, background, src, video_area, y_threshold); } else { /* default */ image_y_over(diff, src, video_area, y_threshold); } for (x = 1; x < video_width - 1; x++) { v = 0; for (y = 0; y < video_height - 1; y++) { w = diff[y * video_width + x]; buffer[y * video_width + x] |= v ^ w; v = w; } } for (x = 1; x < video_width - 1; x++) { i = video_width + x; for (y = 1; y < video_height; y++) { v = buffer[i]; if (v < Decay) buffer[i - video_width] = 0; else buffer[i - video_width + fastrand() % 3 - 1] = v - (fastrand() & Decay); i += video_width; } } i = 1; for (y = 0; y < video_height; y++) { for (x = 1; x < video_width - 1; x++) { /* FIXME: endianness? */ a = (src[i] & 0xfefeff) + palette[buffer[i]]; b = a & 0x1010100; // Add alpha if necessary or use src alpha. c = palette[buffer[i]] ? 0xff000000 : src[i] & 0xff000000; dest[i] = a | (b - (b >> 8)) | c; i++; } i += 2; } mlt_service_unlock(MLT_FILTER_SERVICE(filter)); } return error; } /** Filter processing. */ static mlt_frame filter_process(mlt_filter filter, mlt_frame frame) { // Push the frame filter mlt_frame_push_service(frame, filter); mlt_frame_push_get_image(frame, filter_get_image); return frame; } /** Constructor for the filter. */ mlt_filter filter_burn_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = mlt_filter_new(); if (filter != NULL) { filter->process = filter_process; mlt_properties_set(MLT_FILTER_PROPERTIES(filter), "foreground", "0"); mlt_properties_set(MLT_FILTER_PROPERTIES(filter), "threshold", MAGIC_THRESHOLD); } if (!palette[128]) { makePalette(); } return filter; } mlt-7.22.0/src/modules/plusgpl/filter_burningtv.yml000664 000000 000000 00000001247 14531534050 022415 0ustar00rootroot000000 000000 schema_version: 7.0 type: filter identifier: burningtv title: BurningTV version: 1 copyright: FUKUCHI Kentaro, Stephane Fillod creator: FUKUCHI Kentaro, Stephane Fillod contributor: - Jan Sorensen license: GPLv2 language: en tags: - Video parameters: - identifier: foreground title: Foreground description: > Whether to separate the background and burn only the foreground. The background is based upon the first frame received by the filter. type: boolean default: 0 mutable: yes - identifier: threshold title: Movement Threshold type: integer minimum: 0 maximum: 255 default: 50 mutable: yes animation: yes mlt-7.22.0/src/modules/plusgpl/filter_lumaliftgaingamma.c000664 000000 000000 00000011626 14531534050 023501 0ustar00rootroot000000 000000 /* * filter_lumaliftgaingamma.c -- Lift Gain Gamma filter for luma correction * Copyright (C) 2014 Janne Liljeblad * Author: Janne Liljeblad * * This filter is a port from Gimp and is distributed under a compatible license. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include static double clamp(double value, double low, double high) { if (value < low) value = low; if (value > high) value = high; return value; } static double lut_value(double value, double lift, double gain, double gamma) { double nvalue; double power; value = clamp(value + lift, 0, 1); if (gain < 0.0) value = value * (1.0 + gain); else value = value + ((1.0 - value) * gain); if (gamma < 0.0) { if (value > 0.5) nvalue = 1.0 - value; else nvalue = value; if (nvalue < 0.0) nvalue = 0.0; nvalue = 0.5 * pow(nvalue * 2.0, (double) (1.0 + gamma)); if (value > 0.5) value = 1.0 - nvalue; else value = nvalue; } else { if (value > 0.5) nvalue = 1.0 - value; else nvalue = value; if (nvalue < 0.0) nvalue = 0.0; power = (gamma == 1.0) ? 127 : 1.0 / (1.0 - gamma); nvalue = 0.5 * pow(2.0 * nvalue, power); if (value > 0.5) value = 1.0 - nvalue; else value = nvalue; } return value; } static void fill_lgg_lut(int lgg_lut[], double lift, double gain, double gamma) { int i; double val; for (i = 0; i < 256; i++) { val = (double) i / 255.0; lgg_lut[i] = (int) (lut_value(val, lift, gain, gamma) * 255.0); } } /** Do image filtering. */ static int filter_get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { // Get the image mlt_filter filter = (mlt_filter) mlt_frame_pop_service(frame); mlt_properties properties = MLT_FILTER_PROPERTIES(filter); mlt_position position = mlt_filter_get_position(filter, frame); mlt_position length = mlt_filter_get_length2(filter, frame); *format = mlt_image_rgb; int error = mlt_frame_get_image(frame, image, format, width, height, 0); // Only process if we have no error and a valid colour space if (error == 0) { // Get values and force accepted ranges double lift = mlt_properties_anim_get_double(properties, "lift", position, length); double gain = mlt_properties_anim_get_double(properties, "gain", position, length); double gamma = mlt_properties_anim_get_double(properties, "gamma", position, length); lift = clamp(lift, -0.5, 0.5); gain = clamp(gain, -0.5, 0.5); gamma = clamp(gamma, -1.0, 1.0); // Build lut int lgg_lut[256]; fill_lgg_lut(lgg_lut, lift, gain, gamma); // Filter int i = *width * *height + 1; uint8_t *p = *image; uint8_t *r = *image; while (--i) { *p++ = lgg_lut[*r++]; *p++ = lgg_lut[*r++]; *p++ = lgg_lut[*r++]; } } return error; } /** Filter processing. */ static mlt_frame filter_process(mlt_filter filter, mlt_frame frame) { // Push the frame filter mlt_frame_push_service(frame, filter); mlt_frame_push_get_image(frame, filter_get_image); return frame; } /** Constructor for the filter. */ mlt_filter filter_lumaliftgaingamma_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = mlt_filter_new(); if (filter != NULL) { filter->process = filter_process; mlt_properties_set(MLT_FILTER_PROPERTIES(filter), "lift", "0"); mlt_properties_set(MLT_FILTER_PROPERTIES(filter), "gain", "0"); mlt_properties_set(MLT_FILTER_PROPERTIES(filter), "gamma", "0"); } return filter; } mlt-7.22.0/src/modules/plusgpl/filter_lumaliftgaingamma.yml000664 000000 000000 00000002000 14531534050 024042 0ustar00rootroot000000 000000 schema_version: 7.0 type: filter identifier: lumaliftgaingamma title: LumaLiftGainGamma version: 1 copyright: Janne Liljeblad creator: Janne Liljeblad license: GPL language: en tags: - Video description: > Filter can be used to apply lift, gain and gamma correction to luma values of image. parameters: - identifier: lift title: Lift type: float minimum: -0.5 maximum: 0.5 default: 0 description: > Adds a value computed using parameter value to color channel values. mutable: yes animation: yes - identifier: gain title: Gain type: float minimum: -0.5 maximum: 0.5 default: 0 description: > Multiplies color channel values by value computed using parameter value. mutable: yes animation: yes - identifier: gamma title: Gamma type: float minimum: -1.0 maximum: 1.0 default: 0 description: > Applies a gamma correction to all color channel values. mutable: yes animation: yes mlt-7.22.0/src/modules/plusgpl/filter_rotoscoping.c000664 000000 000000 00000055152 14531534050 022372 0ustar00rootroot000000 000000 /* * rotoscoping.c -- keyframable vector based rotoscoping * Copyright (C) 2011 Till Theato * Copyright (C) 2021 Meltytech, LLC * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include "cJSON.h" #include #include #include #include #define SQR(x) (x) * (x) /** x, y tuple with double precision */ typedef struct PointF { double x; double y; } PointF; typedef struct BPointF { struct PointF h1; struct PointF p; struct PointF h2; } BPointF; enum MODES { MODE_RGB, MODE_ALPHA, MODE_LUMA }; const char *MODESTR[3] = {"rgb", "alpha", "luma"}; enum ALPHAOPERATIONS { ALPHA_CLEAR, ALPHA_MAX, ALPHA_MIN, ALPHA_ADD, ALPHA_SUB }; const char *ALPHAOPERATIONSTR[5] = {"clear", "max", "min", "add", "sub"}; /** Returns the index of \param string in \param stringList. * Useful for assigning string parameters to enums. */ static int stringValue(const char *string, const char **stringList, int max) { int i; for (i = 0; i < max; i++) if (strcmp(stringList[i], string) == 0) return i; return 0; } /** Sets "spline_is_dirty" to 1 if property "spline" was changed. * We then know when to parse the json stored in "spline" */ static void rotoPropertyChanged(mlt_service owner, mlt_filter this, mlt_event_data event_data) { const char *name = mlt_event_data_to_string(event_data); if (name && !strcmp(name, "spline")) mlt_properties_set_int(MLT_FILTER_PROPERTIES(this), "_spline_is_dirty", 1); } /** Linear interp */ static inline void lerp(const PointF *a, const PointF *b, PointF *result, double t) { result->x = a->x + (b->x - a->x) * t; result->y = a->y + (b->y - a->y) * t; } /** Linear interp. with t = 0.5 * Speed gain? */ static inline void lerpHalf(const PointF *a, const PointF *b, PointF *result) { result->x = (a->x + b->x) * .5; result->y = (a->y + b->y) * .5; } /** Helper for using qsort with an array of integers. */ int ncompare(const void *a, const void *b) { return *(const int *) a - *(const int *) b; } /** Turns a json array with two children into a point (x, y tuple). */ static void jsonGetPoint(cJSON *json, PointF *point) { if (cJSON_GetArraySize(json) == 2) { point->x = json->child->valuedouble; point->y = json->child->next->valuedouble; } } /** * Turns the array of json elements into an array of BĂ©zier points. * \param array cJSON array. values have to be BĂ©zier points: handle 1, point , handl2 * ( [ [ [h1x, h1y], [px, py], [h2x, h2y] ], ... ] ) * \param points pointer to array of points. Will be allocated and filled with the points in \param array * \return number of points */ static int json2BCurves(cJSON *array, BPointF **points) { int count = cJSON_GetArraySize(array); cJSON *child = array->child; *points = mlt_pool_alloc(count * sizeof(BPointF)); int i = 0; do { if (child && cJSON_GetArraySize(child) == 3) { jsonGetPoint(child->child, &(*points)[i].h1); jsonGetPoint(child->child->next, &(*points)[i].p); jsonGetPoint(child->child->next->next, &(*points)[i].h2); i++; } } while (child && (child = child->next)); if (i < count) *points = mlt_pool_realloc(*points, i * sizeof(BPointF)); return i; } /** Blurs \param src horizontally. \See function blur. */ static void blurHorizontal(uint8_t *src, uint8_t *dst, int width, int height, int radius) { int x, y, kx, yOff, total, amount, amountInit; amountInit = radius * 2 + 1; for (y = 0; y < height; ++y) { total = 0; yOff = y * width; // Process entire window for first pixel int size = MIN(radius + 1, width); for (kx = 0; kx < size; ++kx) total += src[yOff + kx]; dst[yOff] = total / (radius + 1); // Subsequent pixels just update window total for (x = 1; x < width; ++x) { amount = amountInit; // Subtract pixel leaving window if (x - radius - 1 >= 0) total -= src[yOff + x - radius - 1]; else amount -= radius - x; // Add pixel entering window if (x + radius < width) total += src[yOff + x + radius]; else amount -= radius - width + x; dst[yOff + x] = total / amount; } } } /** Blurs \param src vertically. \See function blur. */ static void blurVertical(uint8_t *src, uint8_t *dst, int width, int height, int radius) { int x, y, ky, total, amount, amountInit; amountInit = radius * 2 + 1; for (x = 0; x < width; ++x) { total = 0; int size = MIN(radius + 1, height); for (ky = 0; ky < size; ++ky) total += src[x + ky * width]; dst[x] = total / (radius + 1); for (y = 1; y < height; ++y) { amount = amountInit; if (y - radius - 1 >= 0) total -= src[(y - radius - 1) * width + x]; else amount -= radius - y; if (y + radius < height) total += src[(y + radius) * width + x]; else amount -= radius - height + y; dst[y * width + x] = total / amount; } } } /** * Blurs the \param map using a simple "average" blur. * \param map Will be blurred; 1bpp * \param width x dimension of channel stored in \param map * \param height y dimension of channel stored in \param map * \param radius blur radius * \param passes blur passes */ static void blur(uint8_t *map, int width, int height, int radius, int passes) { uint8_t *src = mlt_pool_alloc(width * height); uint8_t *tmp = mlt_pool_alloc(width * height); int i; for (i = 0; i < passes; ++i) { memcpy(src, map, width * height); blurHorizontal(src, tmp, width, height, radius); blurVertical(tmp, map, width, height, radius); } mlt_pool_release(src); mlt_pool_release(tmp); } /** * Determines which points are located in the polygon and sets their value in \param map to \param value * \param vertices points defining the polygon * \param count number of vertices * \param with x range * \param height y range * \param value value identifying points in the polygon * \param map array of integers of the dimension width * height. * The map entries belonging to the points in the polygon will be set to \param set * 255 the others to !set * 255. */ static void fillMap(PointF *vertices, int count, int width, int height, int invert, uint8_t *map) { int nodes, nodeX[1024], pixelY, i, j, value; value = !invert * 255; memset(map, invert * 255, width * height); // Loop through the rows of the image for (pixelY = 0; pixelY < height; pixelY++) { /* * Build a list of nodes. * nodes are located at the borders of the polygon * and therefore indicate a move from in to out or vice versa */ nodes = 0; for (i = 0, j = count - 1; i < count; j = i++) if ((vertices[i].y > (double) pixelY) != (vertices[j].y > (double) pixelY)) nodeX[nodes++] = (int) (vertices[i].x + (pixelY - vertices[i].y) / (vertices[j].y - vertices[i].y) * (vertices[j].x - vertices[i].x)); qsort(nodeX, nodes, sizeof(int), ncompare); // Set map values for points between the node pairs to 1 for (i = 0; i < nodes; i += 2) { if (nodeX[i] >= width) break; if (nodeX[i + 1] > 0) { nodeX[i] = MAX(0, nodeX[i]); nodeX[i + 1] = MIN(nodeX[i + 1], width); memset(map + width * pixelY + nodeX[i], value, nodeX[i + 1] - nodeX[i]); } } } } /** Determines the point in the middle of the BĂ©zier curve (t = 0.5) defined by \param p1 and \param p2 * using De Casteljau's algorithm. */ static void deCasteljau(BPointF *p1, BPointF *p2, BPointF *mid) { struct PointF ab, bc, cd; lerpHalf(&(p1->p), &(p1->h2), &ab); lerpHalf(&(p1->h2), &(p2->h1), &bc); lerpHalf(&(p2->h1), &(p2->p), &cd); lerpHalf(&ab, &bc, &(mid->h1)); // mid->h1 = abbc lerpHalf(&bc, &cd, &(mid->h2)); // mid->h2 = bccd lerpHalf(&(mid->h1), &(mid->h2), &(mid->p)); p1->h2 = ab; p2->h1 = cd; } /** * Calculates points for the cubic BĂ©zier curve defined by \param p1 and \param p2. * Points are calculated until the squared distanced between neighbour points is smaller than 2. * \param points Pointer to list of points. Will be allocated and filled with calculated points. * \param count Number of calculated points in \param points * \param size Allocated size of \param points (in elements not in bytes) */ static void curvePoints(BPointF p1, BPointF p2, PointF **points, int *count, int *size) { double errorSqr = SQR(p1.p.x - p2.p.x) + SQR(p1.p.y - p2.p.y); if (*count + 1 >= *size) { *size += (int) sqrt(errorSqr / 2) + 1; *points = mlt_pool_realloc(*points, *size * sizeof(struct PointF)); } (*points)[(*count)++] = p1.p; if (errorSqr <= 2) return; BPointF mid; deCasteljau(&p1, &p2, &mid); curvePoints(p1, mid, points, count, size); curvePoints(mid, p2, points, count, size); (*points)[*(count)++] = p2.p; } /** Do it :-). */ static int filter_get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { mlt_properties unique = mlt_frame_pop_service(frame); int mode = mlt_properties_get_int(unique, "mode"); // Rotoscoping points are based on the profile size / aspect ratio, so check the requested image matches profile mlt_profile profile = mlt_service_profile(MLT_FILTER_SERVICE((mlt_filter) unique)); int normalized_width = profile->width; int normalized_height = profile->height; // Special case - aspect_ratio = 0 if (mlt_frame_get_aspect_ratio(frame) == 0) { double output_ar = mlt_profile_sar(profile); mlt_frame_set_aspect_ratio(frame, output_ar); } double b_ar = mlt_frame_get_aspect_ratio(frame); int scalex = *width; int scaley = *height; int offsetx = 0; int offsety = 0; // Compare aspect ratio if (*height > 0 && 100 * *width / *height != 100 * normalized_width / normalized_height) { // Source has a different aspect ratio, apply scaling double xfactor = normalized_width / *width; double yfactor = normalized_height / *height; if (xfactor < yfactor) { // Image will be stretched horizontally scalex = *width / b_ar; scaley = *width * normalized_height / normalized_width / b_ar; } else { // Image will be stretched vertically scaley = *height / b_ar; scalex = *height * normalized_width / normalized_height / b_ar; } offsetx = (scalex - *width) / 2; offsety = (scaley - *height) / 2; } // Get the image if (mode == MODE_RGB) *format = mlt_image_rgb; int error = mlt_frame_get_image(frame, image, format, width, height, writable); // Only process if we have no error and a valid colour space if (!error) { BPointF *bpoints; struct PointF *points; int bcount, length, count, size, i, j; bpoints = mlt_properties_get_data(unique, "points", &length); bcount = length / sizeof(BPointF); for (i = 0; i < bcount; i++) { // map to image dimensions bpoints[i].h1.x *= scalex; bpoints[i].p.x *= scalex; bpoints[i].h2.x *= scalex; bpoints[i].h1.x -= offsetx; bpoints[i].p.x -= offsetx; bpoints[i].h2.x -= offsetx; bpoints[i].h1.y *= scaley; bpoints[i].p.y *= scaley; bpoints[i].h2.y *= scaley; bpoints[i].h1.y -= offsety; bpoints[i].p.y -= offsety; bpoints[i].h2.y -= offsety; } count = 0; size = 1; points = mlt_pool_alloc(size * sizeof(struct PointF)); for (i = 0; i < bcount; i++) { j = (i + 1) % bcount; curvePoints(bpoints[i], bpoints[j], &points, &count, &size); } if (count) { length = *width * *height; uint8_t *map = mlt_pool_alloc(length); int invert = mlt_properties_get_int(unique, "invert"); fillMap(points, count, *width, *height, invert, map); int feather = mlt_properties_get_int(unique, "feather"); if (feather && mode != MODE_RGB) { // Adapt feathering to consumer scaling double scale_width = mlt_profile_scale_width(profile, *width); feather = MAX(1, (int) (feather * scale_width)); blur(map, *width, *height, feather, mlt_properties_get_int(unique, "feather_passes")); } int bpp; size = mlt_image_format_size(*format, *width, *height, &bpp); uint8_t *p = *image; uint8_t *q = *image + size; i = 0; uint8_t *alpha; switch (mode) { case MODE_RGB: // *format == mlt_image_rgb while (p != q) { if (!map[i++]) p[0] = p[1] = p[2] = 0; p += 3; } break; case MODE_LUMA: switch (*format) { case mlt_image_rgb: case mlt_image_rgba: while (p != q) { p[0] = p[1] = p[2] = map[i++]; p += bpp; } break; case mlt_image_yuv422: while (p != q) { p[0] = map[i++]; p[1] = 128; p += 2; } break; case mlt_image_yuv420p: memcpy(p, map, length); memset(p + length, 128, length / 2); break; default: break; } break; case MODE_ALPHA: switch (*format) { case mlt_image_rgba: switch (mlt_properties_get_int(unique, "alpha_operation")) { case ALPHA_CLEAR: while (p != q) { p[3] = map[i++]; p += 4; } break; case ALPHA_MAX: while (p != q) { p[3] = MAX(p[3], map[i]); p += 4; i++; } break; case ALPHA_MIN: while (p != q) { p[3] = MIN(p[3], map[i]); p += 4; i++; } break; case ALPHA_ADD: while (p != q) { p[3] = MIN(p[3] + map[i], 255); p += 4; i++; } break; case ALPHA_SUB: while (p != q) { p[3] = MAX(p[3] - map[i], 0); p += 4; i++; } break; } break; default: alpha = mlt_frame_get_alpha(frame); if (!alpha) { alpha = mlt_pool_alloc(length); memset(alpha, 255, length); mlt_frame_set_alpha(frame, alpha, length, mlt_pool_release); } switch (mlt_properties_get_int(unique, "alpha_operation")) { case ALPHA_CLEAR: memcpy(alpha, map, length); break; case ALPHA_MAX: for (; i < length; i++, alpha++) *alpha = MAX(map[i], *alpha); break; case ALPHA_MIN: for (; i < length; i++, alpha++) *alpha = MIN(map[i], *alpha); break; case ALPHA_ADD: for (; i < length; i++, alpha++) *alpha = MIN(*alpha + map[i], 255); break; case ALPHA_SUB: for (; i < length; i++, alpha++) *alpha = MAX(*alpha - map[i], 0); break; } break; } break; } mlt_pool_release(map); } mlt_pool_release(points); } return error; } /** Filter processing. */ static mlt_frame filter_process(mlt_filter filter, mlt_frame frame) { mlt_properties properties = MLT_FILTER_PROPERTIES(filter); int splineIsDirty = mlt_properties_get_int(properties, "_spline_is_dirty"); char *modeStr = mlt_properties_get(properties, "mode"); cJSON *root = mlt_properties_get_data(properties, "_spline_parsed", NULL); if (splineIsDirty || root == NULL) { // we need to (re-)parse char *spline = mlt_properties_get(properties, "spline"); root = cJSON_Parse(spline); mlt_properties_set_data(properties, "_spline_parsed", root, 0, (mlt_destructor) cJSON_Delete, NULL); mlt_properties_set_int(properties, "_spline_is_dirty", 0); } if (root == NULL) return frame; BPointF *points; int count, i; if (root->type == cJSON_Array) { /* * constant */ count = json2BCurves(root, &points); } else if (root->type == cJSON_Object) { /* * keyframes */ mlt_position time, pos1, pos2; time = mlt_frame_get_position(frame); cJSON *keyframe = root->child; cJSON *keyframeOld = keyframe; if (!keyframe) return frame; while (atoi(keyframe->string) < time && keyframe->next) { keyframeOld = keyframe; keyframe = keyframe->next; } pos1 = atoi(keyframeOld->string); pos2 = atoi(keyframe->string); if (pos1 >= pos2 || time >= pos2) { // keyframes in wrong order or before first / after last keyframe count = json2BCurves(keyframe, &points); } else { /* * pos1 < time < pos2 */ BPointF *p1, *p2; int c1 = json2BCurves(keyframeOld, &p1); int c2 = json2BCurves(keyframe, &p2); // range 0-1 double position = (time - pos1) / (double) (pos2 - pos1); count = MIN(c1, c2); // additional points are ignored points = mlt_pool_alloc(count * sizeof(BPointF)); for (i = 0; i < count; i++) { lerp(&(p1[i].h1), &(p2[i].h1), &(points[i].h1), position); lerp(&(p1[i].p), &(p2[i].p), &(points[i].p), position); lerp(&(p1[i].h2), &(p2[i].h2), &(points[i].h2), position); } mlt_pool_release(p1); mlt_pool_release(p2); } } else { return frame; } mlt_properties unique = mlt_frame_unique_properties(frame, MLT_FILTER_SERVICE(filter)); mlt_properties_set_data(unique, "points", points, count * sizeof(BPointF), (mlt_destructor) mlt_pool_release, NULL); mlt_properties_set_int(unique, "mode", stringValue(modeStr, MODESTR, 3)); mlt_properties_set_int(unique, "alpha_operation", stringValue(mlt_properties_get(properties, "alpha_operation"), ALPHAOPERATIONSTR, 5)); mlt_properties_set_int(unique, "invert", mlt_properties_get_int(properties, "invert")); mlt_properties_set_int(unique, "feather", mlt_properties_get_int(properties, "feather")); mlt_properties_set_int(unique, "feather_passes", mlt_properties_get_int(properties, "feather_passes")); mlt_frame_push_service(frame, unique); mlt_frame_push_get_image(frame, filter_get_image); return frame; } /** Constructor for the filter. */ mlt_filter filter_rotoscoping_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = mlt_filter_new(); if (filter) { filter->process = filter_process; mlt_properties properties = MLT_FILTER_PROPERTIES(filter); mlt_properties_set(properties, "mode", "alpha"); mlt_properties_set(properties, "alpha_operation", "clear"); mlt_properties_set_int(properties, "invert", 0); mlt_properties_set_int(properties, "feather", 0); mlt_properties_set_int(properties, "feather_passes", 1); if (arg) mlt_properties_set(properties, "spline", arg); mlt_events_listen(properties, filter, "property-changed", (mlt_listener) rotoPropertyChanged); } return filter; } mlt-7.22.0/src/modules/plusgpl/filter_rotoscoping.yml000664 000000 000000 00000005130 14531534050 022740 0ustar00rootroot000000 000000 schema_version: 0.1 type: filter identifier: rotoscoping title: Rotoscoping copyright: Copyright (C) 2011 Till Theato version: 0.3 license: GPL language: en creator: Till Theato tags: - Video description: Keyframable vector based rotoscoping bugs: - in some cases top most row in polygon is assigned to outside parameters: - identifier: mode title: Mode type: string description: How to visualize the area described by the spline readonly: no required: no default: alpha mutable: yes widget: dropdown values: - alpha - luma - rgb - identifier: alpha_operation title: Alpha Operation type: string description: | How to proceed with the current alpha mask (only if mode = alpha). clear = existing alpha mask is overwritten max = maximum: existing alpha mask, mask generated by this filter min = minimum: existing alpha mask, mask generated by this filter add = existing alpha mask + generated mask sub = existing alpha mask - generated mask readonly: no required: no default: clear mutable: yes widget: dropdown values: - clear - max - min - add - sub - identifier: invert title: Invert type: integer description: use area inside of spline (0) or the outside (1) readonly: no required: no minimum: 0 maximum: 1 default: 0 mutable: yes widget: checkbox - identifier: feather title: Feather type: integer description: amount of feathering (radius of "average" blur applied on mask) readonly: no required: no minimum: 0 maximum: 1000 # no real limit default: 0 mutable: yes widget: spinner - identifier: feather_passes title: Feathering passes type: integer description: number of blur (feathering) passes readonly: no required: no minimum: 1 maximum: 1000 # no real limit default: 1 mutable: yes widget: spinner - identifier: spline title: Spline type: string description: > The spline, a list of cubic BĂ©zier curves, is described using JSON. The most basic parts are the coordinate tuples: [x, y]; x,y will be mapped from the range 0-1 to the image dimensions. Next layer are the BĂ©zier points: [handle 1, point, handle 2] with handle 1, point, handle 2 being coordinate tuples The spline is a list of BĂ©zier points. Optionally keyframes can be defined as a object of frame values, relative to the filter's in point, assigned to splines. readonly: no required: yes mutable: yes mlt-7.22.0/src/modules/plusgpl/filter_telecide.c000664 000000 000000 00000134115 14531534050 021577 0ustar00rootroot000000 000000 /* * filter_telecide.c -- Donald Graft's Inverse Telecine Filter * Copyright (C) 2003 Donald A. Graft * Copyright (C) 2008 Dan Dennedy * Author: Dan Dennedy * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #define MAX_CYCLE 6 #define BLKSIZE 24 #define BLKSIZE_TIMES2 (2 * BLKSIZE) #define GUIDE_32 1 #define GUIDE_22 2 #define GUIDE_32322 3 #define AHEAD 0 #define BEHIND 1 #define POST_METRICS 1 #define POST_FULL 2 #define POST_FULL_MAP 3 #define POST_FULL_NOMATCH 4 #define POST_FULL_NOMATCH_MAP 5 #define CACHE_SIZE 100000 #define P 0 #define C 1 #define N 2 #define PBLOCK 3 #define CBLOCK 4 #define NO_BACK 0 #define BACK_ON_COMBED 1 #define ALWAYS_BACK 2 struct CACHE_ENTRY { unsigned int frame; unsigned int metrics[5]; unsigned int chosen; }; struct PREDICTION { unsigned int metric; unsigned int phase; unsigned int predicted; unsigned int predicted_metric; }; struct context_s { int is_configured; mlt_properties image_cache; int out; int tff, chroma, blend, hints, show, debug; float dthresh, gthresh, vthresh, vthresh_saved, bthresh; int y0, y1, nt, guide, post, back, back_saved; int pitch, dpitch, pitchover2, pitchtimes4; int w, h, wover2, hover2, hplus1over2, hminus2; int xblocks, yblocks; #ifdef WINDOWED_MATCH unsigned int *matchc, *matchp, highest_matchc, highest_matchp; #endif unsigned int *sumc, *sump, highest_sumc, highest_sump; int vmetric; unsigned int *overrides, *overrides_p; int film, override, inpattern, found; int force; // Used by field matching. unsigned char *fprp, *fcrp, *fcrp_saved, *fnrp; unsigned char *dstp, *finalp; int chosen; unsigned int p, c, pblock, cblock, lowest, predicted, predicted_metric; unsigned int np, nc, npblock, ncblock, nframe; float mismatch; int pframe, x, y; unsigned char *crp, *prp; unsigned char *crpU, *prpU; unsigned char *crpV, *prpV; int hard; char status[80]; // Metrics cache. struct CACHE_ENTRY *cache; // Pattern guidance data. int cycle; struct PREDICTION pred[MAX_CYCLE + 1]; }; typedef struct context_s *context; static inline void BitBlt( uint8_t *dstp, int dst_pitch, const uint8_t *srcp, int src_pitch, int row_size, int height) { uint32_t y; for (y = 0; y < height; y++) { memcpy(dstp, srcp, row_size); dstp += dst_pitch; srcp += src_pitch; } } static void Show(context cx, int frame, mlt_properties properties) { char use; char buf1[64] = {0}; char buf2[64] = {0}; char buf3[64] = {0}; char buf4[512] = {0}; if (cx->chosen == P) use = 'p'; else if (cx->chosen == C) use = 'c'; else use = 'n'; snprintf(buf1, sizeof(buf1), "Telecide: frame %d: matches: %d %d %d\n", frame, cx->p, cx->c, cx->np); if (cx->post) { snprintf(buf2, sizeof(buf2), "Telecide: frame %d: vmetrics: %d %d %d [chosen=%d]\n", frame, cx->pblock, cx->cblock, cx->npblock, cx->vmetric); } if (cx->guide) { snprintf(buf3, sizeof(buf3), "pattern mismatch=%0.2f%%\n", cx->mismatch); } snprintf(buf4, sizeof(buf4), "%s%s%sTelecide: frame %d: [%s %c]%s %s\n", buf1, buf2, buf3, frame, cx->found ? "forcing" : "using", use, cx->post ? (cx->film ? " [progressive]" : " [interlaced]") : "", cx->guide ? cx->status : ""); mlt_properties_set(properties, "meta.attr.telecide.markup", buf4); } static void Debug(context cx, int frame) { char use; if (cx->chosen == P) use = 'p'; else if (cx->chosen == C) use = 'c'; else use = 'n'; fprintf(stderr, "Telecide: frame %d: matches: %d %d %d\n", frame, cx->p, cx->c, cx->np); if (cx->post) fprintf(stderr, "Telecide: frame %d: vmetrics: %d %d %d [chosen=%d]\n", frame, cx->pblock, cx->cblock, cx->npblock, cx->vmetric); if (cx->guide) fprintf(stderr, "pattern mismatch=%0.2f%%\n", cx->mismatch); fprintf(stderr, "Telecide: frame %d: [%s %c]%s %s\n", frame, cx->found ? "forcing" : "using", use, cx->post ? (cx->film ? " [progressive]" : " [interlaced]") : "", cx->guide ? cx->status : ""); } static void WriteHints(int film, int inpattern, mlt_properties frame_properties) { mlt_properties_set_int(frame_properties, "telecide.progressive", film); mlt_properties_set_int(frame_properties, "telecide.in_pattern", inpattern); } static void PutChosen(context cx, int frame, unsigned int chosen) { int f = frame % CACHE_SIZE; if (frame < 0 || frame > cx->out || cx->cache[f].frame != frame) return; cx->cache[f].chosen = chosen; } static void CacheInsert( context cx, int frame, unsigned int p, unsigned int pblock, unsigned int c, unsigned int cblock) { int f = frame % CACHE_SIZE; if (frame < 0 || frame > cx->out) fprintf(stderr, "%s: internal error: invalid frame %d for CacheInsert", __FUNCTION__, frame); cx->cache[f].frame = frame; cx->cache[f].metrics[P] = p; if (f) cx->cache[f - 1].metrics[N] = p; cx->cache[f].metrics[C] = c; cx->cache[f].metrics[PBLOCK] = pblock; cx->cache[f].metrics[CBLOCK] = cblock; cx->cache[f].chosen = 0xff; } static int CacheQuery(context cx, int frame, unsigned int *p, unsigned int *pblock, unsigned int *c, unsigned int *cblock) { int f; f = frame % CACHE_SIZE; if (frame < 0 || frame > cx->out) fprintf(stderr, "%s: internal error: invalid frame %d for CacheQuery", __FUNCTION__, frame); if (cx->cache[f].frame != frame) { return 0; } *p = cx->cache[f].metrics[P]; *c = cx->cache[f].metrics[C]; *pblock = cx->cache[f].metrics[PBLOCK]; *cblock = cx->cache[f].metrics[CBLOCK]; return 1; } static int PredictHardYUY2(context cx, int frame, unsigned int *predicted, unsigned int *predicted_metric) { // Look for pattern in the actual delivered matches of the previous cycle of frames. // If a pattern is found, use that to predict the current match. if (cx->guide == GUIDE_22) { if (cx->cache[(frame - cx->cycle) % CACHE_SIZE].chosen == 0xff || cx->cache[(frame - cx->cycle + 1) % CACHE_SIZE].chosen == 0xff) return 0; switch ((cx->cache[(frame - cx->cycle) % CACHE_SIZE].chosen << 4) + (cx->cache[(frame - cx->cycle + 1) % CACHE_SIZE].chosen)) { case 0x11: *predicted = C; *predicted_metric = cx->cache[frame % CACHE_SIZE].metrics[C]; break; case 0x22: *predicted = N; *predicted_metric = cx->cache[frame % CACHE_SIZE].metrics[N]; break; default: return 0; } } else if (cx->guide == GUIDE_32) { if (cx->cache[(frame - cx->cycle) % CACHE_SIZE].chosen == 0xff || cx->cache[(frame - cx->cycle + 1) % CACHE_SIZE].chosen == 0xff || cx->cache[(frame - cx->cycle + 2) % CACHE_SIZE].chosen == 0xff || cx->cache[(frame - cx->cycle + 3) % CACHE_SIZE].chosen == 0xff || cx->cache[(frame - cx->cycle + 4) % CACHE_SIZE].chosen == 0xff) return 0; switch ((cx->cache[(frame - cx->cycle) % CACHE_SIZE].chosen << 16) + (cx->cache[(frame - cx->cycle + 1) % CACHE_SIZE].chosen << 12) + (cx->cache[(frame - cx->cycle + 2) % CACHE_SIZE].chosen << 8) + (cx->cache[(frame - cx->cycle + 3) % CACHE_SIZE].chosen << 4) + (cx->cache[(frame - cx->cycle + 4) % CACHE_SIZE].chosen)) { case 0x11122: case 0x11221: case 0x12211: case 0x12221: case 0x21122: case 0x11222: *predicted = C; *predicted_metric = cx->cache[frame % CACHE_SIZE].metrics[C]; break; case 0x22111: case 0x21112: case 0x22112: case 0x22211: *predicted = N; *predicted_metric = cx->cache[frame % CACHE_SIZE].metrics[N]; break; default: return 0; } } else if (cx->guide == GUIDE_32322) { if (cx->cache[(frame - cx->cycle) % CACHE_SIZE].chosen == 0xff || cx->cache[(frame - cx->cycle + 1) % CACHE_SIZE].chosen == 0xff || cx->cache[(frame - cx->cycle + 2) % CACHE_SIZE].chosen == 0xff || cx->cache[(frame - cx->cycle + 3) % CACHE_SIZE].chosen == 0xff || cx->cache[(frame - cx->cycle + 4) % CACHE_SIZE].chosen == 0xff || cx->cache[(frame - cx->cycle + 5) % CACHE_SIZE].chosen == 0xff) return 0; switch ((cx->cache[(frame - cx->cycle) % CACHE_SIZE].chosen << 20) + (cx->cache[(frame - cx->cycle + 1) % CACHE_SIZE].chosen << 16) + (cx->cache[(frame - cx->cycle + 2) % CACHE_SIZE].chosen << 12) + (cx->cache[(frame - cx->cycle + 3) % CACHE_SIZE].chosen << 8) + (cx->cache[(frame - cx->cycle + 4) % CACHE_SIZE].chosen << 4) + (cx->cache[(frame - cx->cycle + 5) % CACHE_SIZE].chosen)) { case 0x111122: case 0x111221: case 0x112211: case 0x122111: case 0x111222: case 0x112221: case 0x122211: case 0x222111: *predicted = C; *predicted_metric = cx->cache[frame % CACHE_SIZE].metrics[C]; break; case 0x221111: case 0x211112: case 0x221112: case 0x211122: *predicted = N; *predicted_metric = cx->cache[frame % CACHE_SIZE].metrics[N]; break; default: return 0; } } #ifdef DEBUG_PATTERN_GUIDANCE fprintf(stderr, "%s: pos=%d HARD: predicted=%d\n", __FUNCTION__, frame, *predicted); #endif return 1; } static struct PREDICTION *PredictSoftYUY2(context cx, int frame) { // Use heuristics to look forward for a match. int i, j, y, c, n, phase; unsigned int metric; cx->pred[0].metric = 0xffffffff; if (frame < 0 || frame > cx->out - cx->cycle) return cx->pred; // Look at the next cycle of frames. for (y = frame + 1; y <= frame + cx->cycle; y++) { // Look for a frame where the current and next match values are // very close. Those are candidates to predict the phase, because // that condition should occur only once per cycle. Store the candidate // phases and predictions in a list sorted by goodness. The list will // be used by the caller to try the phases in order. c = cx->cache[y % CACHE_SIZE].metrics[C]; n = cx->cache[y % CACHE_SIZE].metrics[N]; if (c == 0) c = 1; metric = (100 * abs(c - n)) / c; phase = y % cx->cycle; if (metric < 5) { // Place the new candidate phase in sorted order in the list. // Find the insertion point. i = 0; while (metric > cx->pred[i].metric) i++; // Find the end-of-list marker. j = 0; while (cx->pred[j].metric != 0xffffffff) j++; // Shift all items below the insertion point down by one to make // room for the insertion. j++; for (; j > i; j--) { cx->pred[j].metric = cx->pred[j - 1].metric; cx->pred[j].phase = cx->pred[j - 1].phase; cx->pred[j].predicted = cx->pred[j - 1].predicted; cx->pred[j].predicted_metric = cx->pred[j - 1].predicted_metric; } // Insert the new candidate data. cx->pred[j].metric = metric; cx->pred[j].phase = phase; if (cx->guide == GUIDE_32) { switch ((frame % cx->cycle) - phase) { case -4: cx->pred[j].predicted = N; cx->pred[j].predicted_metric = cx->cache[frame % CACHE_SIZE].metrics[N]; break; case -3: cx->pred[j].predicted = N; cx->pred[j].predicted_metric = cx->cache[frame % CACHE_SIZE].metrics[N]; break; case -2: cx->pred[j].predicted = C; cx->pred[j].predicted_metric = cx->cache[frame % CACHE_SIZE].metrics[C]; break; case -1: cx->pred[j].predicted = C; cx->pred[j].predicted_metric = cx->cache[frame % CACHE_SIZE].metrics[C]; break; case 0: cx->pred[j].predicted = C; cx->pred[j].predicted_metric = cx->cache[frame % CACHE_SIZE].metrics[C]; break; case +1: cx->pred[j].predicted = N; cx->pred[j].predicted_metric = cx->cache[frame % CACHE_SIZE].metrics[N]; break; case +2: cx->pred[j].predicted = N; cx->pred[j].predicted_metric = cx->cache[frame % CACHE_SIZE].metrics[N]; break; case +3: cx->pred[j].predicted = C; cx->pred[j].predicted_metric = cx->cache[frame % CACHE_SIZE].metrics[C]; break; case +4: cx->pred[j].predicted = C; cx->pred[j].predicted_metric = cx->cache[frame % CACHE_SIZE].metrics[C]; break; } } else if (cx->guide == GUIDE_32322) { switch ((frame % cx->cycle) - phase) { case -5: cx->pred[j].predicted = N; cx->pred[j].predicted_metric = cx->cache[frame % CACHE_SIZE].metrics[N]; break; case -4: cx->pred[j].predicted = N; cx->pred[j].predicted_metric = cx->cache[frame % CACHE_SIZE].metrics[N]; break; case -3: cx->pred[j].predicted = C; cx->pred[j].predicted_metric = cx->cache[frame % CACHE_SIZE].metrics[C]; break; case -2: cx->pred[j].predicted = C; cx->pred[j].predicted_metric = cx->cache[frame % CACHE_SIZE].metrics[C]; break; case -1: cx->pred[j].predicted = C; cx->pred[j].predicted_metric = cx->cache[frame % CACHE_SIZE].metrics[C]; break; case 0: cx->pred[j].predicted = C; cx->pred[j].predicted_metric = cx->cache[frame % CACHE_SIZE].metrics[C]; break; case +1: cx->pred[j].predicted = N; cx->pred[j].predicted_metric = cx->cache[frame % CACHE_SIZE].metrics[N]; break; case +2: cx->pred[j].predicted = N; cx->pred[j].predicted_metric = cx->cache[frame % CACHE_SIZE].metrics[N]; break; case +3: cx->pred[j].predicted = C; cx->pred[j].predicted_metric = cx->cache[frame % CACHE_SIZE].metrics[C]; break; case +4: cx->pred[j].predicted = C; cx->pred[j].predicted_metric = cx->cache[frame % CACHE_SIZE].metrics[C]; break; case +5: cx->pred[j].predicted = C; cx->pred[j].predicted_metric = cx->cache[frame % CACHE_SIZE].metrics[C]; break; } } } #ifdef DEBUG_PATTERN_GUIDANCE fprintf(stderr, "%s: pos=%d metric=%d phase=%d\n", __FUNCTION__, frame, metric, phase); #endif } return cx->pred; } static void CalculateMetrics(context cx, int frame, unsigned char *fcrp, unsigned char *fcrpU, unsigned char *fcrpV, unsigned char *fprp, unsigned char *fprpU, unsigned char *fprpV) { int x, y, p, c, tmp1, tmp2, skip; int vc; unsigned char *currbot0, *currbot2, *prevbot0, *prevbot2; unsigned char *prevtop0, *prevtop2, *prevtop4, *currtop0, *currtop2, *currtop4; unsigned char *a0, *a2, *b0, *b2, *b4; unsigned int diff, index; #define T 4 /* Clear the block sums. */ for (y = 0; y < cx->yblocks; y++) { for (x = 0; x < cx->xblocks; x++) { #ifdef WINDOWED_MATCH matchp[y * xblocks + x] = 0; matchc[y * xblocks + x] = 0; #endif cx->sump[y * cx->xblocks + x] = 0; cx->sumc[y * cx->xblocks + x] = 0; } } /* Find the best field match. Subsample the frames for speed. */ currbot0 = fcrp + cx->pitch; currbot2 = fcrp + 3 * cx->pitch; currtop0 = fcrp; currtop2 = fcrp + 2 * cx->pitch; currtop4 = fcrp + 4 * cx->pitch; prevbot0 = fprp + cx->pitch; prevbot2 = fprp + 3 * cx->pitch; prevtop0 = fprp; prevtop2 = fprp + 2 * cx->pitch; prevtop4 = fprp + 4 * cx->pitch; if (cx->tff) { a0 = prevbot0; a2 = prevbot2; b0 = currtop0; b2 = currtop2; b4 = currtop4; } else { a0 = currbot0; a2 = currbot2; b0 = prevtop0; b2 = prevtop2; b4 = prevtop4; } p = c = 0; // Calculate the field match and film/video metrics. skip = 1 + (!cx->chroma); for (y = 0, index = 0; y < cx->h - 4; y += 4) { /* Exclusion band. Good for ignoring subtitles. */ if (cx->y0 == cx->y1 || y < cx->y0 || y > cx->y1) { for (x = 0; x < cx->w;) { index = (y / BLKSIZE) * cx->xblocks + x / BLKSIZE_TIMES2; // Test combination with current frame. tmp1 = ((long) currbot0[x] + (long) currbot2[x]); diff = labs((((long) currtop0[x] + (long) currtop2[x] + (long) currtop4[x])) - (tmp1 >> 1) - tmp1); if (diff > cx->nt) { c += diff; #ifdef WINDOWED_MATCH matchc[index] += diff; #endif } tmp1 = currbot0[x] + T; tmp2 = currbot0[x] - T; vc = (tmp1 < currtop0[x] && tmp1 < currtop2[x]) || (tmp2 > currtop0[x] && tmp2 > currtop2[x]); if (vc) { cx->sumc[index]++; } // Test combination with previous frame. tmp1 = ((long) a0[x] + (long) a2[x]); diff = labs((((long) b0[x] + (long) b2[x] + (long) b4[x])) - (tmp1 >> 1) - tmp1); if (diff > cx->nt) { p += diff; #ifdef WINDOWED_MATCH matchp[index] += diff; #endif } tmp1 = a0[x] + T; tmp2 = a0[x] - T; vc = (tmp1 < b0[x] && tmp1 < b2[x]) || (tmp2 > b0[x] && tmp2 > b2[x]); if (vc) { cx->sump[index]++; } x += skip; if (!(x & 3)) x += 4; } } currbot0 += cx->pitchtimes4; currbot2 += cx->pitchtimes4; currtop0 += cx->pitchtimes4; currtop2 += cx->pitchtimes4; currtop4 += cx->pitchtimes4; a0 += cx->pitchtimes4; a2 += cx->pitchtimes4; b0 += cx->pitchtimes4; b2 += cx->pitchtimes4; b4 += cx->pitchtimes4; } if (cx->post) { cx->highest_sump = 0; for (y = 0; y < cx->yblocks; y++) { for (x = 0; x < cx->xblocks; x++) { if (cx->sump[y * cx->xblocks + x] > cx->highest_sump) { cx->highest_sump = cx->sump[y * cx->xblocks + x]; } } } cx->highest_sumc = 0; for (y = 0; y < cx->yblocks; y++) { for (x = 0; x < cx->xblocks; x++) { if (cx->sumc[y * cx->xblocks + x] > cx->highest_sumc) { cx->highest_sumc = cx->sumc[y * cx->xblocks + x]; } } } } #ifdef WINDOWED_MATCH CacheInsert(frame, highest_matchp, highest_sump, highest_matchc, highest_sumc); #else CacheInsert(cx, frame, p, cx->highest_sump, c, cx->highest_sumc); #endif } /** Process the image. */ static int get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { // Get the filter service mlt_filter filter = mlt_frame_pop_service(frame); mlt_properties properties = MLT_FILTER_PROPERTIES(filter); mlt_properties frame_properties = mlt_frame_properties(frame); context cx = mlt_properties_get_data(properties, "context", NULL); mlt_service producer = mlt_service_producer(mlt_filter_service(filter)); cx->out = producer ? mlt_producer_get_playtime(MLT_PRODUCER(producer)) : 999999; if (!cx->is_configured) { cx->back = mlt_properties_get_int(properties, "back"); cx->chroma = mlt_properties_get_int(properties, "chroma"); cx->guide = mlt_properties_get_int(properties, "guide"); cx->gthresh = mlt_properties_get_double(properties, "gthresh"); cx->post = mlt_properties_get_int(properties, "post"); cx->vthresh = mlt_properties_get_double(properties, "vthresh"); cx->bthresh = mlt_properties_get_double(properties, "bthresh"); cx->dthresh = mlt_properties_get_double(properties, "dthresh"); cx->blend = mlt_properties_get_int(properties, "blend"); cx->nt = mlt_properties_get_int(properties, "nt"); cx->y0 = mlt_properties_get_int(properties, "y0"); cx->y1 = mlt_properties_get_int(properties, "y1"); cx->hints = mlt_properties_get_int(properties, "hints"); cx->debug = mlt_properties_get_int(properties, "debug"); cx->show = mlt_properties_get_int(properties, "show"); } // Get the image int error = mlt_frame_get_image(frame, image, format, width, height, 1); if (!cx->sump) { int guide = mlt_properties_get_int(properties, "guide"); cx->cycle = 0; if (guide == GUIDE_32) { // 24fps to 30 fps telecine. cx->cycle = 5; } else if (guide == GUIDE_22) { // PAL guidance (expect the current match to be continued). cx->cycle = 2; } else if (guide == GUIDE_32322) { // 25fps to 30 fps telecine. cx->cycle = 6; } cx->xblocks = (*width + BLKSIZE - 1) / BLKSIZE; cx->yblocks = (*height + BLKSIZE - 1) / BLKSIZE; cx->sump = (unsigned int *) mlt_pool_alloc(cx->xblocks * cx->yblocks * sizeof(unsigned int)); cx->sumc = (unsigned int *) mlt_pool_alloc(cx->xblocks * cx->yblocks * sizeof(unsigned int)); mlt_properties_set_data(properties, "sump", cx->sump, cx->xblocks * cx->yblocks * sizeof(unsigned int), (mlt_destructor) mlt_pool_release, NULL); mlt_properties_set_data(properties, "sumc", cx->sumc, cx->xblocks * cx->yblocks * sizeof(unsigned int), (mlt_destructor) mlt_pool_release, NULL); cx->tff = mlt_properties_get_int(frame_properties, "top_field_first"); } // Only process if we have no error and a valid colour space if (error == 0 && *format == mlt_image_yuv422) { // Put the current image into the image cache, keyed on position size_t image_size = (*width * *height) << 1; mlt_position pos = mlt_filter_get_position(filter, frame); uint8_t *image_copy = mlt_pool_alloc(image_size); memcpy(image_copy, *image, image_size); char key[20]; sprintf(key, MLT_POSITION_FMT, pos); mlt_properties_set_data(cx->image_cache, key, image_copy, image_size, (mlt_destructor) mlt_pool_release, NULL); // Only if we have enough frame images cached if (pos > 1 && pos > cx->cycle + 1) { pos -= cx->cycle + 1; // Get the current frame image sprintf(key, MLT_POSITION_FMT, pos); cx->fcrp = mlt_properties_get_data(cx->image_cache, key, NULL); if (!cx->fcrp) return error; // Get the previous frame image cx->pframe = pos == 0 ? 0 : pos - 1; sprintf(key, "%d", cx->pframe); cx->fprp = mlt_properties_get_data(cx->image_cache, key, NULL); if (!cx->fprp) return error; // Get the next frame image cx->nframe = pos > cx->out ? cx->out : pos + 1; sprintf(key, "%d", cx->nframe); cx->fnrp = mlt_properties_get_data(cx->image_cache, key, NULL); if (!cx->fnrp) return error; cx->pitch = *width << 1; cx->pitchover2 = cx->pitch >> 1; cx->pitchtimes4 = cx->pitch << 2; cx->w = *width << 1; cx->h = *height; if ((cx->w / 2) & 1) fprintf(stderr, "%s: width must be a multiple of 2\n", __FUNCTION__); if (cx->h & 1) fprintf(stderr, "%s: height must be a multiple of 2\n", __FUNCTION__); cx->wover2 = cx->w / 2; cx->hover2 = cx->h / 2; cx->hplus1over2 = (cx->h + 1) / 2; cx->hminus2 = cx->h - 2; cx->dpitch = cx->pitch; // Ensure that the metrics for the frames // after the current frame are in the cache. They will be used for // pattern guidance. if (cx->guide) { for (cx->y = pos + 1; (cx->y <= pos + cx->cycle + 1) && (cx->y <= cx->out); cx->y++) { if (!CacheQuery(cx, cx->y, &cx->p, &cx->pblock, &cx->c, &cx->cblock)) { sprintf(key, "%d", cx->y); cx->crp = (unsigned char *) mlt_properties_get_data(cx->image_cache, key, NULL); sprintf(key, "%d", cx->y ? cx->y - 1 : 1); cx->prp = (unsigned char *) mlt_properties_get_data(cx->image_cache, key, NULL); CalculateMetrics(cx, cx->y, cx->crp, NULL, NULL, cx->prp, NULL, NULL); } } } // Check for manual overrides of the field matching. cx->found = 0; cx->film = 1; cx->override = 0; cx->inpattern = 0; cx->back = cx->back_saved; // Get the metrics for the current-previous (p), current-current (c), and current-next (n) match candidates. if (!CacheQuery(cx, pos, &cx->p, &cx->pblock, &cx->c, &cx->cblock)) { CalculateMetrics(cx, pos, cx->fcrp, NULL, NULL, cx->fprp, NULL, NULL); CacheQuery(cx, pos, &cx->p, &cx->pblock, &cx->c, &cx->cblock); } if (!CacheQuery(cx, cx->nframe, &cx->np, &cx->npblock, &cx->nc, &cx->ncblock)) { CalculateMetrics(cx, cx->nframe, cx->fnrp, NULL, NULL, cx->fcrp, NULL, NULL); CacheQuery(cx, cx->nframe, &cx->np, &cx->npblock, &cx->nc, &cx->ncblock); } // Determine the best candidate match. if (!cx->found) { cx->lowest = cx->c; cx->chosen = C; if (cx->back == ALWAYS_BACK && cx->p < cx->lowest) { cx->lowest = cx->p; cx->chosen = P; } if (cx->np < cx->lowest) { cx->lowest = cx->np; cx->chosen = N; } } if ((pos == 0 && cx->chosen == P) || (pos == cx->out && cx->chosen == N)) { cx->chosen = C; cx->lowest = cx->c; } // See if we can apply pattern guidance. cx->mismatch = 100.0; if (cx->guide) { cx->hard = 0; if (pos >= cx->cycle && PredictHardYUY2(cx, pos, &cx->predicted, &cx->predicted_metric)) { cx->inpattern = 1; cx->mismatch = 0.0; cx->hard = 1; if (cx->chosen != cx->predicted) { // The chosen frame doesn't match the prediction. if (cx->predicted_metric == 0) cx->mismatch = 0.0; else cx->mismatch = (100.0 * (cx->predicted_metric - cx->lowest)) / cx->predicted_metric; if (cx->mismatch < cx->gthresh) { // It's close enough, so use the predicted one. if (!cx->found) { cx->chosen = cx->predicted; cx->override = 1; } } else { cx->hard = 0; cx->inpattern = 0; } } } if (!cx->hard && cx->guide != GUIDE_22) { int i; struct PREDICTION *pred = PredictSoftYUY2(cx, pos); if ((pos <= cx->out - cx->cycle) && (pred[0].metric != 0xffffffff)) { // Apply pattern guidance. // If the predicted match metric is within defined percentage of the // best calculated one, then override the calculated match with the // predicted match. i = 0; while (pred[i].metric != 0xffffffff) { cx->predicted = pred[i].predicted; cx->predicted_metric = pred[i].predicted_metric; #ifdef DEBUG_PATTERN_GUIDANCE fprintf(stderr, "%s: pos=%d predicted=%d\n", __FUNCTION__, pos, cx->predicted); #endif if (cx->chosen != cx->predicted) { // The chosen frame doesn't match the prediction. if (cx->predicted_metric == 0) cx->mismatch = 0.0; else cx->mismatch = (100.0 * (cx->predicted_metric - cx->lowest)) / cx->predicted_metric; if ((int) cx->mismatch <= cx->gthresh) { // It's close enough, so use the predicted one. if (!cx->found) { cx->chosen = cx->predicted; cx->override = 1; } cx->inpattern = 1; break; } else { // Looks like we're not in a predictable pattern. cx->inpattern = 0; } } else { cx->inpattern = 1; cx->mismatch = 0.0; break; } i++; } } } } // Check the match for progressive versus interlaced. if (cx->post) { if (cx->chosen == P) cx->vmetric = cx->pblock; else if (cx->chosen == C) cx->vmetric = cx->cblock; else if (cx->chosen == N) cx->vmetric = cx->npblock; if (!cx->found && cx->back == BACK_ON_COMBED && cx->vmetric > cx->bthresh && cx->p < cx->lowest) { // Backward match. cx->vmetric = cx->pblock; cx->chosen = P; cx->inpattern = 0; cx->mismatch = 100; } if (cx->vmetric > cx->vthresh) { // After field matching and pattern guidance the frame is still combed. cx->film = 0; if (!cx->found && (cx->post == POST_FULL_NOMATCH || cx->post == POST_FULL_NOMATCH_MAP)) { cx->chosen = C; cx->vmetric = cx->cblock; cx->inpattern = 0; cx->mismatch = 100; } } } cx->vthresh = cx->vthresh_saved; // Setup strings for debug info. if (cx->inpattern && !cx->override) strcpy(cx->status, "[in-pattern]"); else if (cx->inpattern && cx->override) strcpy(cx->status, "[in-pattern*]"); else strcpy(cx->status, "[out-of-pattern]"); // Assemble and output the reconstructed frame according to the final match. cx->dstp = *image; if (cx->chosen == N) { // The best match was with the next frame. if (cx->tff) { BitBlt(cx->dstp, 2 * cx->dpitch, cx->fnrp, 2 * cx->pitch, cx->w, cx->hover2); BitBlt(cx->dstp + cx->dpitch, 2 * cx->dpitch, cx->fcrp + cx->pitch, 2 * cx->pitch, cx->w, cx->hover2); } else { BitBlt(cx->dstp, 2 * cx->dpitch, cx->fcrp, 2 * cx->pitch, cx->w, cx->hplus1over2); BitBlt(cx->dstp + cx->dpitch, 2 * cx->dpitch, cx->fnrp + cx->pitch, 2 * cx->pitch, cx->w, cx->hover2); } } else if (cx->chosen == C) { // The best match was with the current frame. BitBlt(cx->dstp, 2 * cx->dpitch, cx->fcrp, 2 * cx->pitch, cx->w, cx->hplus1over2); BitBlt(cx->dstp + cx->dpitch, 2 * cx->dpitch, cx->fcrp + cx->pitch, 2 * cx->pitch, cx->w, cx->hover2); } else if (!cx->tff) { // The best match was with the previous frame. BitBlt(cx->dstp, 2 * cx->dpitch, cx->fprp, 2 * cx->pitch, cx->w, cx->hplus1over2); BitBlt(cx->dstp + cx->dpitch, 2 * cx->dpitch, cx->fcrp + cx->pitch, 2 * cx->pitch, cx->w, cx->hover2); } else { // The best match was with the previous frame. BitBlt(cx->dstp, 2 * cx->dpitch, cx->fcrp, 2 * cx->pitch, cx->w, cx->hplus1over2); BitBlt(cx->dstp + cx->dpitch, 2 * cx->dpitch, cx->fprp + cx->pitch, 2 * cx->pitch, cx->w, cx->hover2); } if (cx->guide) PutChosen(cx, pos, cx->chosen); if (!cx->post || cx->post == POST_METRICS) { if (cx->force == '+') cx->film = 0; else if (cx->force == '-') cx->film = 1; } else if ((cx->force == '+') || ((cx->post == POST_FULL || cx->post == POST_FULL_MAP || cx->post == POST_FULL_NOMATCH || cx->post == POST_FULL_NOMATCH_MAP) && (cx->film == 0 && cx->force != '-'))) { unsigned char *dstpp, *dstpn; int v1, v2; if (cx->blend) { // Do first and last lines. uint8_t *final = mlt_pool_alloc(image_size); cx->finalp = final; mlt_frame_set_image(frame, final, image_size, mlt_pool_release); dstpn = cx->dstp + cx->dpitch; for (cx->x = 0; cx->x < cx->w; cx->x++) { cx->finalp[cx->x] = (((int) cx->dstp[cx->x] + (int) dstpn[cx->x]) >> 1); } cx->finalp = final + (cx->h - 1) * cx->dpitch; cx->dstp = *image + (cx->h - 1) * cx->dpitch; dstpp = cx->dstp - cx->dpitch; for (cx->x = 0; cx->x < cx->w; cx->x++) { cx->finalp[cx->x] = (((int) cx->dstp[cx->x] + (int) dstpp[cx->x]) >> 1); } // Now do the rest. cx->dstp = *image + cx->dpitch; dstpp = cx->dstp - cx->dpitch; dstpn = cx->dstp + cx->dpitch; cx->finalp = final + cx->dpitch; for (cx->y = 1; cx->y < cx->h - 1; cx->y++) { for (cx->x = 0; cx->x < cx->w; cx->x++) { v1 = (int) cx->dstp[cx->x] - cx->dthresh; if (v1 < 0) v1 = 0; v2 = (int) cx->dstp[cx->x] + cx->dthresh; if (v2 > 235) v2 = 235; if ((v1 > dstpp[cx->x] && v1 > dstpn[cx->x]) || (v2 < dstpp[cx->x] && v2 < dstpn[cx->x])) { if (cx->post == POST_FULL_MAP || cx->post == POST_FULL_NOMATCH_MAP) { if (cx->x & 1) cx->finalp[cx->x] = 128; else cx->finalp[cx->x] = 235; } else cx->finalp[cx->x] = ((int) dstpp[cx->x] + (int) dstpn[cx->x] + (int) cx->dstp[cx->x] + (int) cx->dstp[cx->x]) >> 2; } else cx->finalp[cx->x] = cx->dstp[cx->x]; } cx->finalp += cx->dpitch; cx->dstp += cx->dpitch; dstpp += cx->dpitch; dstpn += cx->dpitch; } if (cx->show) Show(cx, pos, frame_properties); if (cx->debug) Debug(cx, pos); if (cx->hints) WriteHints(cx->film, cx->inpattern, frame_properties); goto final; } // Interpolate mode. cx->dstp = *image + cx->dpitch; dstpp = cx->dstp - cx->dpitch; dstpn = cx->dstp + cx->dpitch; for (cx->y = 1; cx->y < cx->h - 1; cx->y += 2) { for (cx->x = 0; cx->x < cx->w; cx->x++) { v1 = (int) cx->dstp[cx->x] - cx->dthresh; if (v1 < 0) v1 = 0; v2 = (int) cx->dstp[cx->x] + cx->dthresh; if (v2 > 235) v2 = 235; if ((v1 > dstpp[cx->x] && v1 > dstpn[cx->x]) || (v2 < dstpp[cx->x] && v2 < dstpn[cx->x])) { if (cx->post == POST_FULL_MAP || cx->post == POST_FULL_NOMATCH_MAP) { if (cx->x & 1) cx->dstp[cx->x] = 128; else cx->dstp[cx->x] = 235; } else cx->dstp[cx->x] = (dstpp[cx->x] + dstpn[cx->x]) >> 1; } } cx->dstp += 2 * cx->dpitch; dstpp += 2 * cx->dpitch; dstpn += 2 * cx->dpitch; } } if (cx->show) Show(cx, pos, frame_properties); if (cx->debug) Debug(cx, pos); if (cx->hints) WriteHints(cx->film, cx->inpattern, frame_properties); final: // Flush frame at tail of period from the cache sprintf(key, MLT_POSITION_FMT, pos - 1); mlt_properties_set_data(cx->image_cache, key, NULL, 0, NULL, NULL); } else { // Signal the first {cycle} frames as invalid mlt_properties_set_int(frame_properties, "garbage", 1); } } else if (error == 0 && *format == mlt_image_yuv420p) { fprintf(stderr, "%s: %d pos " MLT_POSITION_FMT "\n", __FUNCTION__, *width * *height * 3 / 2, mlt_frame_get_position(frame)); } return error; } /** Process the frame object. */ static mlt_frame process(mlt_filter filter, mlt_frame frame) { // Push the filter on to the stack mlt_frame_push_service(frame, filter); // Push the frame filter mlt_frame_push_get_image(frame, get_image); return frame; } /** Constructor for the filter. */ mlt_filter filter_telecide_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = mlt_filter_new(); if (filter != NULL) { filter->process = process; // Allocate the context and set up for garbage collection context cx = (context) mlt_pool_alloc(sizeof(struct context_s)); memset(cx, 0, sizeof(struct context_s)); mlt_properties properties = MLT_FILTER_PROPERTIES(filter); mlt_properties_set_data(properties, "context", cx, sizeof(struct context_s), (mlt_destructor) mlt_pool_release, NULL); // Allocate the metrics cache and set up for garbage collection cx->cache = (struct CACHE_ENTRY *) mlt_pool_alloc(CACHE_SIZE * sizeof(struct CACHE_ENTRY)); mlt_properties_set_data(properties, "cache", cx->cache, CACHE_SIZE * sizeof(struct CACHE_ENTRY), (mlt_destructor) mlt_pool_release, NULL); int i; for (i = 0; i < CACHE_SIZE; i++) { cx->cache[i].frame = 0xffffffff; cx->cache[i].chosen = 0xff; } // Allocate the image cache and set up for garbage collection cx->image_cache = mlt_properties_new(); mlt_properties_set_data(properties, "image_cache", cx->image_cache, 0, (mlt_destructor) mlt_properties_close, NULL); // Initialize the parameter defaults mlt_properties_set_int(properties, "guide", 0); mlt_properties_set_int(properties, "back", 0); mlt_properties_set_int(properties, "chroma", 0); mlt_properties_set_int(properties, "post", POST_FULL); mlt_properties_set_double(properties, "gthresh", 10.0); mlt_properties_set_double(properties, "vthresh", 50.0); mlt_properties_set_double(properties, "bthresh", 50.0); mlt_properties_set_double(properties, "dthresh", 7.0); mlt_properties_set_int(properties, "blend", 0); mlt_properties_set_int(properties, "nt", 10); mlt_properties_set_int(properties, "y0", 0); mlt_properties_set_int(properties, "y1", 0); mlt_properties_set_int(properties, "hints", 1); } return filter; } mlt-7.22.0/src/modules/plusgpl/filter_telecide.yml000664 000000 000000 00000001020 14531534050 022142 0ustar00rootroot000000 000000 schema_version: 7.0 type: filter identifier: telecide title: Inverse Telecine (*DEPRECATED*) version: 1 copyright: Donald A. Graft and Dan Dennedy creator: Donald A. Graft contributor: - Dan Dennedy license: GPLv2 language: en tags: - Video description: Deinterlace frames that were repeated in a telecine process. notes: > This is old work, which never materialized to a completely usable form. It is not able to change the frame rate. The video is deinterlaced in a smart manner, but repeated frames are not removed. mlt-7.22.0/src/modules/plusgpl/gpl000664 000000 000000 00000000000 14531534050 016776 0ustar00rootroot000000 000000 mlt-7.22.0/src/modules/plusgpl/image.c000664 000000 000000 00000013605 14531534050 017536 0ustar00rootroot000000 000000 /* * EffecTV - Realtime Digital Video Effector * Copyright (C) 2001-2006 FUKUCHI Kentaro * * image.c: utilities for image processing. * */ #include #include #include "utils.h" /* * Collection of background subtraction functions */ /* checks only fake-Y value */ /* In these function Y value is treated as R*2+G*4+B. */ int image_set_threshold_y(int threshold) { int y_threshold = threshold * 7; /* fake-Y value is timed by 7 */ return y_threshold; } void image_bgset_y(RGB32 *background, const RGB32 *src, int video_area, int y_threshold) { int i; int R, G, B; const RGB32 *p; short *q; p = src; q = (short *)background; for(i=0; i>(16-1); G = ((*p)&0xff00)>>(8-2); B = (*p)&0xff; *q = (short)(R + G + B); p++; q++; } } void image_bgsubtract_y(unsigned char *diff, const RGB32 *background, const RGB32 *src, int video_area, int y_threshold) { int i; int R, G, B; const RGB32 *p; const short *q; unsigned char *r; int v; p = src; q = (const short *)background; r = diff; for(i=0; i>(16-1); G = ((*p)&0xff00)>>(8-2); B = (*p)&0xff; v = (R + G + B) - (int)(*q); *r = ((v + y_threshold)>>24) | ((y_threshold - v)>>24); p++; q++; r++; } /* The origin of subtraction function is; * diff(src, dest) = (abs(src - dest) > threshold) ? 0xff : 0; * * This functions is transformed to; * (threshold > (src - dest) > -threshold) ? 0 : 0xff; * * (v + threshold)>>24 is 0xff when v is less than -threshold. * (v - threshold)>>24 is 0xff when v is less than threshold. * So, ((v + threshold)>>24) | ((threshold - v)>>24) will become 0xff when * abs(src - dest) > threshold. */ } /* Background image is refreshed every frame */ void image_bgsubtract_update_y(unsigned char *diff, RGB32 *background, const RGB32 *src, int video_area, int y_threshold) { int i; int R, G, B; const RGB32 *p; short *q; unsigned char *r; int v; p = src; q = (short *)background; r = diff; for(i=0; i>(16-1); G = ((*p)&0xff00)>>(8-2); B = (*p)&0xff; v = (R + G + B) - (int)(*q); *q = (short)(R + G + B); *r = ((v + y_threshold)>>24) | ((y_threshold - v)>>24); p++; q++; r++; } } /* checks each RGB value */ /* The range of r, g, b are [0..7] */ RGB32 image_set_threshold_RGB(int r, int g, int b) { unsigned char R, G, B; RGB32 rgb_threshold; R = G = B = 0xff; R = R<>8); b = b ^ 0xffffff; a = a ^ b; a = a & rgb_threshold; *r++ = (0 - a)>>24; } } void image_bgsubtract_update_RGB(unsigned char *diff, RGB32 *background, const RGB32 *src, int video_area, RGB32 rgb_threshold) { int i; const RGB32 *p; RGB32 *q; unsigned a, b; unsigned char *r; p = src; q = background; r = diff; for(i=0; i>8); b = b ^ 0xffffff; a = a ^ b; a = a & rgb_threshold; *r++ = (0 - a)>>24; } } /* noise filter for subtracted image. */ void image_diff_filter(unsigned char *diff2, const unsigned char *diff, int width, int height) { int x, y; const unsigned char *src; unsigned char *dest; unsigned int count; unsigned int sum1, sum2, sum3; src = diff; dest = diff2 + width +1; for(y=1; y>24; src++; } dest += 2; } } /* Y value filters */ void image_y_over(unsigned char *diff, const RGB32 *src, int video_area, int y_threshold) { int i; int R, G, B, v; unsigned char *p = diff; for(i = video_area; i>0; i--) { R = ((*src)&0xff0000)>>(16-1); G = ((*src)&0xff00)>>(8-2); B = (*src)&0xff; v = y_threshold - (R + G + B); *p = (unsigned char)(v>>24); src++; p++; } } void image_y_under(unsigned char *diff, const RGB32 *src, int video_area, int y_threshold) { int i; int R, G, B, v; unsigned char *p = diff; for(i = video_area; i>0; i--) { R = ((*src)&0xff0000)>>(16-1); G = ((*src)&0xff00)>>(8-2); B = (*src)&0xff; v = (R + G + B) - y_threshold; *p = (unsigned char)(v>>24); src++; p++; } } /* tiny edge detection */ void image_edge(unsigned char *diff2, const RGB32 *src, int width, int height, int y_threshold) { int x, y; const unsigned char *p; unsigned char *q; int r, g, b; int ar, ag, ab; int w; p = (const unsigned char *)src; q = diff2; w = width * sizeof(RGB32); for(y=0; y y_threshold) { *q = 255; } else { *q = 0; } q++; p += 4; } p += 4; *q++ = 0; } memset(q, 0, width); } /* horizontal flipping */ void image_hflip(const RGB32 *src, RGB32 *dest, int width, int height) { int x, y; src += width - 1; for(y=0; y #include "utils.h" /* * HSI color system utilities */ static int itrunc(double f) { int i; i=(int)f; if(i<0)i=0; if(i>255)i=255; return i; } void HSItoRGB(double H, double S, double I, int *r, int *g, int *b) { double T,Rv,Gv,Bv; Rv=1+S*sin(H-2*M_PI/3); Gv=1+S*sin(H); Bv=1+S*sin(H+2*M_PI/3); T=255.999*I/2; *r=itrunc(Rv*T); *g=itrunc(Gv*T); *b=itrunc(Bv*T); } /* * fastrand - fast fake random number generator * Warning: The low-order bits of numbers generated by fastrand() * are bad as random numbers. For example, fastrand()%4 * generates 1,2,3,0,1,2,3,0... * You should use high-order bits. */ #ifdef __APPLE__ static #endif unsigned int fastrand_val; unsigned int fastrand(void) { return (fastrand_val=fastrand_val*1103515245+12345); } void fastsrand(unsigned int seed) { fastrand_val = seed; } mlt-7.22.0/src/modules/plusgpl/utils.h000664 000000 000000 00000003735 14531534050 017624 0ustar00rootroot000000 000000 /* * EffecTV - Realtime Digital Video Effector * Copyright (C) 2001-2006 FUKUCHI Kentaro * * utils.h: header file for utils * */ #ifndef __UTILS_H__ #define __UTILS_H__ #include typedef uint32_t RGB32; /* DEFINE's by nullset@dookie.net */ #define RED(n) ((n>>16) & 0x000000FF) #define GREEN(n) ((n>>8) & 0x000000FF) #define BLUE(n) ((n>>0) & 0x000000FF) #define RGB(r,g,b) ((0<<24) + (r<<16) + (g <<8) + (b)) #define INTENSITY(n) ( ( (RED(n)+GREEN(n)+BLUE(n))/3)) /* utils.c */ void HSItoRGB(double H, double S, double I, int *r, int *g, int *b); #ifndef __APPLE__ extern unsigned int fastrand_val; #define inline_fastrand() (fastrand_val=fastrand_val*1103515245+12345) #endif unsigned int fastrand(void); void fastsrand(unsigned int); /* image.c */ int image_set_threshold_y(int threshold); void image_bgset_y(RGB32 *background, const RGB32 *src, int video_area, int y_threshold); void image_bgsubtract_y(unsigned char *diff, const RGB32 *background, const RGB32 *src, int video_area, int y_threshold); void image_bgsubtract_update_y(unsigned char *diff, RGB32 *background, const RGB32 *src, int video_area, int y_threshold); RGB32 image_set_threshold_RGB(int r, int g, int b); void image_bgset_RGB(RGB32 *background, const RGB32 *src, int video_area); void image_bgsubtract_RGB(unsigned char *diff, const RGB32 *background, const RGB32 *src, int video_area, RGB32 rgb_threshold); void image_bgsubtract_update_RGB(unsigned char *diff, RGB32 *background, const RGB32 *src, int video_area, RGB32 rgb_threshold); void image_diff_filter(unsigned char *diff2, const unsigned char *diff, int width, int height); void image_y_over(unsigned char *diff, const RGB32 *src, int video_area, int y_threshold); void image_y_under(unsigned char *diff, const RGB32 *src, int video_area, int y_threshold); void image_edge(unsigned char *diff2, const RGB32 *src, int width, int height, int y_threshold); void image_hflip(const RGB32 *src, RGB32 *dest, int width, int height); #endif /* __UTILS_H__ */ mlt-7.22.0/src/modules/qt/000775 000000 000000 00000000000 14531534050 015241 5ustar00rootroot000000 000000 mlt-7.22.0/src/modules/qt/CMakeLists.txt000664 000000 000000 00000007136 14531534050 020010 0ustar00rootroot000000 000000 set(CMAKE_AUTOMOC ON) function(mlt_add_qt_module ARG_TARGET) cmake_parse_arguments(PARSE_ARGV 1 ARG "" "QT_VERSION;DATADIR" "") if ("${ARG_TARGET}" STREQUAL "") message(FATAL_ERROR "mlt_add_qt_module called without a valid target name.") endif() if (NOT (("${ARG_QT_VERSION}" STREQUAL "5") OR ("${ARG_QT_VERSION}" STREQUAL "6"))) message(FATAL_ERROR "mlt_add_qt_module called without a valid Qt Version (allowed are 5 or 6).") endif() if ("${ARG_DATADIR}" STREQUAL "") message(FATAL_ERROR "mlt_add_qt_module called without a valid data dir name.") endif() add_library(${ARG_TARGET} MODULE common.cpp common.h consumer_qglsl.cpp factory.c filter_audiolevelgraph.cpp filter_audiowaveform.cpp filter_gpsgraphic.cpp filter_gpsgraphic.h filter_gpstext.cpp filter_qtext.cpp filter_qtblend.cpp filter_qtcrop.cpp filter_typewriter.cpp gps_drawing.cpp gps_parser.cpp gps_parser.h graph.cpp graph.h kdenlivetitle_wrapper.cpp producer_kdenlivetitle.c producer_qimage.c producer_qtext.cpp qimage_wrapper.cpp transition_qtblend.cpp typewriter.cpp ) file(GLOB YML "*.yml") add_custom_target(Other_${ARG_TARGET}_Files SOURCES ${YML} ) target_compile_options(${ARG_TARGET} PRIVATE ${MLT_COMPILE_OPTIONS}) target_link_libraries(${ARG_TARGET} PRIVATE mlt++ mlt m Threads::Threads Qt${ARG_QT_VERSION}::Core Qt${ARG_QT_VERSION}::Gui Qt${ARG_QT_VERSION}::Xml ) if(ARG_QT_VERSION EQUAL 6) target_link_libraries(${ARG_TARGET} PRIVATE Qt6::SvgWidgets Qt6::Core5Compat ) else() target_link_libraries(${ARG_TARGET} PRIVATE Qt${ARG_QT_VERSION}::Svg Qt${ARG_QT_VERSION}::Widgets ) endif() target_compile_definitions(${ARG_TARGET} PRIVATE USE_QT_OPENGL) if(NOT WINDOWS_DEPLOY) target_compile_definitions(${ARG_TARGET} PRIVATE NODEPLOY) endif() if(GPL3) target_sources(${ARG_TARGET} PRIVATE transition_vqm.cpp) target_compile_definitions(${ARG_TARGET} PRIVATE GPL3) install(FILES transition_vqm.yml DESTINATION ${MLT_INSTALL_DATA_DIR}/${ARG_DATADIR}) endif() if(TARGET PkgConfig::FFTW) target_sources(${ARG_TARGET} PRIVATE filter_audiospectrum.cpp filter_lightshow.cpp) target_link_libraries(${ARG_TARGET} PRIVATE PkgConfig::FFTW) target_compile_definitions(${ARG_TARGET} PRIVATE USE_FFTW) install(FILES filter_audiospectrum.yml filter_lightshow.yml DESTINATION ${MLT_INSTALL_DATA_DIR}/${ARG_DATADIR}) endif() if(TARGET PkgConfig::libexif) target_link_libraries(${ARG_TARGET} PRIVATE PkgConfig::libexif) target_compile_definitions(${ARG_TARGET} PRIVATE USE_EXIF) endif() set_target_properties(${ARG_TARGET} PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${MLT_MODULE_OUTPUT_DIRECTORY}") install(TARGETS ${ARG_TARGET} LIBRARY DESTINATION ${MLT_INSTALL_MODULE_DIR}) install(FILES filter_audiolevelgraph.yml filter_audiospectrum.yml filter_audiowaveform.yml filter_gpsgraphic.yml filter_gpstext.yml filter_qtblend.yml filter_qtcrop.yml filter_qtext.yml filter_typewriter.yml producer_kdenlivetitle.yml producer_qimage.yml producer_qtext.yml transition_qtblend.yml DESTINATION ${MLT_INSTALL_DATA_DIR}/${ARG_DATADIR} ) endfunction() if (MOD_QT) mlt_add_qt_module(mltqt QT_VERSION 5 DATADIR qt) endif() if (MOD_QT6) mlt_add_qt_module(mltqt6 QT_VERSION 6 DATADIR qt6) endif() mlt-7.22.0/src/modules/qt/common.cpp000664 000000 000000 00000011644 14531534050 017243 0ustar00rootroot000000 000000 /* * Copyright (C) 2014-2023 Dan Dennedy * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "common.h" #include #include #include #if defined(Q_OS_UNIX) && !defined(Q_OS_MAC) && !defined(Q_OS_ANDROID) #include #include #endif bool createQApplicationIfNeeded(mlt_service service) { if (!qApp) { #if defined(Q_OS_WIN) && defined(NODEPLOY) QCoreApplication::addLibraryPath(QString(mlt_environment("MLT_APPDIR")) + QStringLiteral("/bin")); QCoreApplication::addLibraryPath(QString(mlt_environment("MLT_APPDIR")) + QStringLiteral("/plugins")); #endif #if defined(Q_OS_UNIX) && !defined(Q_OS_MAC) && !defined(Q_OS_ANDROID) if (getenv("DISPLAY") == 0) { mlt_log_error( service, "The MLT Qt module requires a X11 environment.\n" "Please either run melt from an X session or use a fake X server like xvfb:\n" "xvfb-run -a melt (...)\n"); return false; } #endif if (!mlt_properties_get(mlt_global_properties(), "qt_argv")) mlt_properties_set(mlt_global_properties(), "qt_argv", "MLT"); static int argc = 1; static char *argv[] = {mlt_properties_get(mlt_global_properties(), "qt_argv")}; new QApplication(argc, argv); const char *localename = mlt_properties_get_lcnumeric(MLT_SERVICE_PROPERTIES(service)); QLocale::setDefault(QLocale(localename)); #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) QImageReader::setAllocationLimit(1024); #endif } return true; } void convert_qimage_to_mlt_rgba(QImage *qImg, uint8_t *mImg, int width, int height) { #if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0) // QImage::Format_RGBA8888 was added in Qt5.2 // Nothing to do in this case because the image was modified directly. // Destination pointer must be the same pointer that was provided to // convert_mlt_to_qimage_rgba() Q_ASSERT(mImg == qImg->constBits()); #else int y = height + 1; while (--y) { QRgb *src = (QRgb *) qImg->scanLine(height - y); int x = width + 1; while (--x) { *mImg++ = qRed(*src); *mImg++ = qGreen(*src); *mImg++ = qBlue(*src); *mImg++ = qAlpha(*src); src++; } } #endif } void convert_mlt_to_qimage_rgba(uint8_t *mImg, QImage *qImg, int width, int height) { #if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0) // QImage::Format_RGBA8888 was added in Qt5.2 // Initialize the QImage with the MLT image because the data formats match. *qImg = QImage(mImg, width, height, QImage::Format_RGBA8888); #else *qImg = QImage(width, height, QImage::Format_ARGB32); int y = height + 1; while (--y) { QRgb *dst = (QRgb *) qImg->scanLine(height - y); int x = width + 1; while (--x) { *dst++ = qRgba(mImg[0], mImg[1], mImg[2], mImg[3]); mImg += 4; } } #endif } int create_image(mlt_frame frame, uint8_t **image, mlt_image_format *image_format, int *width, int *height, int writable) { int error = 0; mlt_properties frame_properties = MLT_FRAME_PROPERTIES(frame); *image_format = mlt_image_rgba; // Use the width and height suggested by the rescale filter. if (mlt_properties_get_int(frame_properties, "rescale_width") > 0) *width = mlt_properties_get_int(frame_properties, "rescale_width"); if (mlt_properties_get_int(frame_properties, "rescale_height") > 0) *height = mlt_properties_get_int(frame_properties, "rescale_height"); // If no size is requested, use native size. if (*width <= 0) *width = mlt_properties_get_int(frame_properties, "meta.media.width"); if (*height <= 0) *height = mlt_properties_get_int(frame_properties, "meta.media.height"); int size = mlt_image_format_size(*image_format, *width, *height, NULL); *image = static_cast(mlt_pool_alloc(size)); memset(*image, 0, size); // Transparent mlt_frame_set_image(frame, *image, size, mlt_pool_release); return error; } mlt-7.22.0/src/modules/qt/common.h000664 000000 000000 00000002466 14531534050 016712 0ustar00rootroot000000 000000 /* * Copyright (C) 2014 Dan Dennedy * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef COMMON_H #define COMMON_H #include class QImage; bool createQApplicationIfNeeded(mlt_service service); void convert_qimage_to_mlt_rgba(QImage *qImg, uint8_t *mImg, int width, int height); void convert_mlt_to_qimage_rgba(uint8_t *mImg, QImage *qImg, int width, int height); int create_image(mlt_frame frame, uint8_t **image, mlt_image_format *image_format, int *width, int *height, int writable); #endif // COMMON_H mlt-7.22.0/src/modules/qt/consumer_qglsl.cpp000664 000000 000000 00000013672 14531534050 021013 0ustar00rootroot000000 000000 /* * consumer_qglsl.cpp * Copyright (C) 2012-2023 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "common.h" #include #include #include #include #include #include typedef void *(*thread_function_t)(void *); class RenderThread : public QThread { public: RenderThread(thread_function_t function, void *data) : QThread(0) , m_function(function) , m_data(data) , m_context(new QOpenGLContext) , m_surface(new QOffscreenSurface) { QSurfaceFormat format; format.setProfile(QSurfaceFormat::CoreProfile); format.setMajorVersion(3); format.setMinorVersion(2); format.setDepthBufferSize(0); format.setStencilBufferSize(0); m_context->setFormat(format); m_context->create(); m_context->moveToThread(this); m_surface->setFormat(format); m_surface->create(); } ~RenderThread() { m_surface->destroy(); } protected: void run() { Q_ASSERT(m_context->isValid()); m_context->makeCurrent(m_surface.get()); m_function(m_data); m_context->doneCurrent(); } private: thread_function_t m_function; void *m_data; std::unique_ptr m_context; std::unique_ptr m_surface; }; static void onThreadCreate(mlt_properties owner, mlt_consumer self, mlt_event_data event_data) { Q_UNUSED(owner) mlt_event_data_thread *t = (mlt_event_data_thread *) mlt_event_data_to_object(event_data); auto thread = new RenderThread((thread_function_t) t->function, t->data); *t->thread = thread; thread->start(); } static void onThreadJoin(mlt_properties owner, mlt_consumer self, mlt_event_data event_data) { Q_UNUSED(owner) Q_UNUSED(self) auto threadData = (mlt_event_data_thread *) mlt_event_data_to_object(event_data); if (threadData && threadData->thread) { auto thread = (RenderThread *) *threadData->thread; if (thread) { thread->quit(); thread->wait(); qApp->processEvents(); delete thread; } } } static void onThreadStarted(mlt_properties owner, mlt_consumer consumer) { mlt_service service = MLT_CONSUMER_SERVICE(consumer); mlt_properties properties = MLT_CONSUMER_PROPERTIES(consumer); mlt_filter filter = (mlt_filter) mlt_properties_get_data(properties, "glslManager", NULL); mlt_properties filter_properties = MLT_FILTER_PROPERTIES(filter); mlt_log_debug(service, "%s\n", __FUNCTION__); { mlt_events_fire(filter_properties, "init glsl", mlt_event_data_none()); if (!mlt_properties_get_int(filter_properties, "glsl_supported")) { mlt_log_fatal(service, "OpenGL Shading Language rendering is not supported on this machine.\n"); mlt_events_fire(properties, "consumer-fatal-error", mlt_event_data_none()); } } } static void onThreadStopped(mlt_properties owner, mlt_consumer consumer) { mlt_properties properties = MLT_CONSUMER_PROPERTIES(consumer); mlt_filter filter = (mlt_filter) mlt_properties_get_data(properties, "glslManager", NULL); mlt_events_fire(MLT_FILTER_PROPERTIES(filter), "close glsl", mlt_event_data_none()); } extern "C" { mlt_consumer consumer_qglsl_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_consumer consumer = mlt_factory_consumer(profile, "multi", arg); if (consumer) { mlt_filter filter = mlt_factory_filter(profile, "glsl.manager", 0); if (filter) { mlt_properties properties = MLT_CONSUMER_PROPERTIES(consumer); mlt_properties_set_data(properties, "glslManager", filter, 0, (mlt_destructor) mlt_filter_close, NULL); mlt_events_register(properties, "consumer-cleanup"); mlt_events_listen(properties, consumer, "consumer-thread-started", (mlt_listener) onThreadStarted); mlt_events_listen(properties, consumer, "consumer-thread-stopped", (mlt_listener) onThreadStopped); if (!createQApplicationIfNeeded(MLT_CONSUMER_SERVICE(consumer))) { mlt_filter_close(filter); mlt_consumer_close(consumer); return NULL; } mlt_events_listen(properties, consumer, "consumer-thread-create", (mlt_listener) onThreadCreate); mlt_events_listen(properties, consumer, "consumer-thread-join", (mlt_listener) onThreadJoin); qApp->processEvents(); return consumer; } mlt_consumer_close(consumer); } return NULL; } } mlt-7.22.0/src/modules/qt/consumer_qglsl.yml000664 000000 000000 00000000554 14531534050 021025 0ustar00rootroot000000 000000 schema_version: 7.0 type: consumer identifier: qglsl title: Qt OpenGL Context version: 1 copyright: Meltytech, LLC license: LGPLv2.1 language: en tags: - Video description: > Creates an OpenGL context using Qt to use with the movit module in terminal (melt) or headless (xvfb) situations. This is based on the "multi" consumer; so, see its documentation. mlt-7.22.0/src/modules/qt/factory.c000664 000000 000000 00000020406 14531534050 017056 0ustar00rootroot000000 000000 /* * Copyright (C) 2008-2023 Meltytech, LLC * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #ifdef USE_QT_OPENGL extern mlt_consumer consumer_qglsl_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); #endif extern mlt_filter filter_audiolevelgraph_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_audiowaveform_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_gpsgraphic_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_gpstext_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_qtext_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_producer producer_qimage_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_producer producer_qtext_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_producer producer_kdenlivetitle_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_transition transition_vqm_init(mlt_profile profile, mlt_service_type type, const char *id, void *arg); extern mlt_transition transition_qtblend_init(mlt_profile profile, mlt_service_type type, const char *id, void *arg); extern mlt_filter filter_qtblend_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_qtcrop_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_typewriter_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); #ifdef USE_FFTW extern mlt_filter filter_audiospectrum_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_lightshow_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); #endif static mlt_properties metadata(mlt_service_type type, const char *id, void *data) { char file[PATH_MAX]; #if QT_VERSION_MAJOR < 6 snprintf(file, PATH_MAX, "%s/qt/%s", mlt_environment("MLT_DATA"), (char *) data); #else snprintf(file, PATH_MAX, "%s/qt6/%s", mlt_environment("MLT_DATA"), (char *) data); #endif return mlt_properties_parse_yaml(file); } MLT_REPOSITORY { #ifdef USE_QT_OPENGL MLT_REGISTER(mlt_service_consumer_type, "qglsl", consumer_qglsl_init); #endif MLT_REGISTER(mlt_service_filter_type, "audiolevelgraph", filter_audiolevelgraph_init); MLT_REGISTER(mlt_service_filter_type, "audiowaveform", filter_audiowaveform_init); MLT_REGISTER(mlt_service_filter_type, "gpsgraphic", filter_gpsgraphic_init); MLT_REGISTER(mlt_service_filter_type, "gpstext", filter_gpstext_init); MLT_REGISTER(mlt_service_filter_type, "qtext", filter_qtext_init); MLT_REGISTER(mlt_service_producer_type, "qimage", producer_qimage_init); MLT_REGISTER(mlt_service_producer_type, "qtext", producer_qtext_init); MLT_REGISTER(mlt_service_producer_type, "kdenlivetitle", producer_kdenlivetitle_init); MLT_REGISTER(mlt_service_transition_type, "qtblend", transition_qtblend_init); MLT_REGISTER(mlt_service_filter_type, "qtblend", filter_qtblend_init); MLT_REGISTER(mlt_service_filter_type, "qtcrop", filter_qtcrop_init); MLT_REGISTER(mlt_service_filter_type, "typewriter", filter_typewriter_init); MLT_REGISTER_METADATA(mlt_service_consumer_type, "qglsl", metadata, "consumer_qglsl.yml"); MLT_REGISTER_METADATA(mlt_service_transition_type, "qtblend", metadata, "transition_qtblend.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "qtblend", metadata, "filter_qtblend.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "qtcrop", metadata, "filter_qtcrop.yml"); #ifdef USE_FFTW MLT_REGISTER(mlt_service_filter_type, "audiospectrum", filter_audiospectrum_init); MLT_REGISTER(mlt_service_filter_type, "lightshow", filter_lightshow_init); #endif MLT_REGISTER_METADATA(mlt_service_filter_type, "audiolevelgraph", metadata, "filter_audiolevelgraph.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "audiowaveform", metadata, "filter_audiowaveform.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "gpsgraphic", metadata, "filter_gpsgraphic.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "gpstext", metadata, "filter_gpstext.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "qtext", metadata, "filter_qtext.yml"); #ifdef USE_FFTW MLT_REGISTER_METADATA(mlt_service_filter_type, "lightshow", metadata, "filter_lightshow.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "audiospectrum", metadata, "filter_audiospectrum.yml"); #endif MLT_REGISTER_METADATA(mlt_service_producer_type, "qimage", metadata, "producer_qimage.yml"); MLT_REGISTER_METADATA(mlt_service_producer_type, "qtext", metadata, "producer_qtext.yml"); MLT_REGISTER_METADATA(mlt_service_producer_type, "kdenlivetitle", metadata, "producer_kdenlivetitle.yml"); #ifdef GPL3 MLT_REGISTER(mlt_service_transition_type, "vqm", transition_vqm_init); MLT_REGISTER_METADATA(mlt_service_transition_type, "vqm", metadata, "transition_vqm.yml"); #endif MLT_REGISTER_METADATA(mlt_service_filter_type, "typewriter", metadata, "filter_typewriter.yml"); } mlt-7.22.0/src/modules/qt/filter_audiolevelgraph.cpp000664 000000 000000 00000026234 14531534050 022474 0ustar00rootroot000000 000000 /* * filter_audiolevel.cpp -- audio level visualization filter * Copyright (c) 2021-2022 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "common.h" #include "graph.h" #include // memset #include #include #include //pow #include #include #include namespace { // Private Types typedef struct { mlt_filter levels_filter; int preprocess_warned; } private_data; } // namespace static int filter_get_audio(mlt_frame frame, void **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples) { mlt_filter filter = (mlt_filter) mlt_frame_pop_audio(frame); private_data *pdata = (private_data *) filter->child; // Create the audiolevel filter the first time. if (!pdata->levels_filter) { mlt_profile profile = mlt_service_profile(MLT_FILTER_SERVICE(filter)); pdata->levels_filter = mlt_factory_filter(profile, "audiolevel", NULL); if (!pdata->levels_filter) { mlt_log_warning(MLT_FILTER_SERVICE(filter), "Unable to create audiolevel filter.\n"); return 1; } } // The service must stay locked while using the private data mlt_service_lock(MLT_FILTER_SERVICE(filter)); // Perform audio level processing on the frame mlt_filter_process(pdata->levels_filter, frame); mlt_frame_get_audio(frame, buffer, format, frequency, channels, samples); mlt_service_unlock(MLT_FILTER_SERVICE(filter)); return 0; } double get_level_from_frame(mlt_frame frame, int channel) { char prop_str[30]; snprintf(prop_str, 30, "meta.media.audio_level.%d", channel); return mlt_properties_get_double(MLT_FRAME_PROPERTIES(frame), prop_str); } static void convert_levels(mlt_filter filter, mlt_frame frame, int channels, float *levels) { mlt_properties filter_properties = MLT_FILTER_PROPERTIES(filter); mlt_properties frame_properties = MLT_FRAME_PROPERTIES(frame); int reverse = mlt_properties_get_int(filter_properties, "reverse"); int real_channels = mlt_properties_get_int(frame_properties, "audio_channels"); if (real_channels == 0) real_channels = 1; int chan_index = 0; for (chan_index = 0; chan_index < channels; chan_index++) { double level = 0.0; if (channels == 1) { // For 1 channel display, average all channel levels int real_chan_index = 0; for (real_chan_index = 0; real_chan_index < real_channels; real_chan_index++) { level += get_level_from_frame(frame, real_chan_index); } level /= real_channels; } else { int real_chan_index = chan_index % real_channels; level = get_level_from_frame(frame, real_chan_index); } if (reverse) { levels[channels - chan_index - 1] = level; } else { levels[chan_index] = level; } } } static void draw_levels(mlt_filter filter, mlt_frame frame, QImage *qimg, int width, int height) { mlt_properties filter_properties = MLT_FILTER_PROPERTIES(filter); mlt_position position = mlt_filter_get_position(filter, frame); mlt_position length = mlt_filter_get_length2(filter, frame); mlt_profile profile = mlt_service_profile(MLT_FILTER_SERVICE(filter)); mlt_rect rect = mlt_properties_anim_get_rect(filter_properties, "rect", position, length); if (strchr(mlt_properties_get(filter_properties, "rect"), '%')) { rect.x *= qimg->width(); rect.w *= qimg->width(); rect.y *= qimg->height(); rect.h *= qimg->height(); } double scale = mlt_profile_scale_width(profile, width); rect.x *= scale; rect.w *= scale; scale = mlt_profile_scale_height(profile, height); rect.y *= scale; rect.h *= scale; char *graph_type = mlt_properties_get(filter_properties, "type"); int mirror = mlt_properties_get_int(filter_properties, "mirror"); int segments = mlt_properties_anim_get_int(filter_properties, "segments", position, length); int segment_gap = mlt_properties_anim_get_int(filter_properties, "segment_gap", position, length) * scale; int segment_width = mlt_properties_anim_get_int(filter_properties, "thickness", position, length) * scale; QVector colors = get_graph_colors(filter_properties, position, length); QRectF r(rect.x, rect.y, rect.w, rect.h); QPainter p(qimg); if (mirror) { // Draw two half rectangle instead of one full rectangle. r.setHeight(r.height() / 2.0); } setup_graph_painter(p, r, filter_properties, position, length); setup_graph_pen(p, r, filter_properties, scale, position, length); int channels = mlt_properties_anim_get_int(filter_properties, "channels", position, length); if (channels == 0) { // "0" means use number of channels in the frame channels = mlt_properties_get_int(MLT_FRAME_PROPERTIES(frame), "audio_channels"); } if (channels == 0) channels = 1; float *levels = (float *) mlt_pool_alloc(channels * sizeof(float)); convert_levels(filter, frame, channels, levels); if (graph_type && graph_type[0] == 'b') { paint_bar_graph(p, r, channels, levels); } else { paint_segment_graph(p, r, channels, levels, colors, segments, segment_gap, segment_width); } if (mirror) { // Second rectangle is mirrored. p.translate(0, r.y() * 2 + r.height() * 2); p.scale(1, -1); if (graph_type && graph_type[0] == 'b') { paint_bar_graph(p, r, channels, levels); } else { paint_segment_graph(p, r, channels, levels, colors, segments, segment_gap, segment_width); } } mlt_pool_release(levels); p.end(); } /** Get the image. */ static int filter_get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { int error = 0; mlt_filter filter = (mlt_filter) mlt_frame_pop_service(frame); private_data *pdata = (private_data *) filter->child; mlt_properties frame_properties = MLT_FRAME_PROPERTIES(frame); if (mlt_properties_get(frame_properties, "meta.media.audio_level.0")) { // Get the current image *format = mlt_image_rgba; error = mlt_frame_get_image(frame, image, format, width, height, 1); // Draw the audio levels if (!error) { QImage qimg(*width, *height, QImage::Format_ARGB32); convert_mlt_to_qimage_rgba(*image, &qimg, *width, *height); draw_levels(filter, frame, &qimg, *width, *height); convert_qimage_to_mlt_rgba(&qimg, *image, *width, *height); } } else { if (pdata->preprocess_warned++ == 2) { // This filter depends on the consumer processing the audio before // the video. mlt_log_warning(MLT_FILTER_SERVICE(filter), "Audio not preprocessed.\n"); } mlt_frame_get_image(frame, image, format, width, height, writable); } return error; } /** Filter processing. */ static mlt_frame filter_process(mlt_filter filter, mlt_frame frame) { if (mlt_frame_is_test_card(frame)) { // The producer does not generate video. This filter will create an // image on the producer's behalf. mlt_properties frame_properties = MLT_FRAME_PROPERTIES(frame); mlt_profile profile = mlt_service_profile(MLT_FILTER_SERVICE(filter)); mlt_properties_set_int(frame_properties, "progressive", 1); mlt_properties_set_double(frame_properties, "aspect_ratio", mlt_profile_sar(profile)); mlt_properties_set_int(frame_properties, "meta.media.width", profile->width); mlt_properties_set_int(frame_properties, "meta.media.height", profile->height); // Tell the framework that there really is an image. mlt_properties_set_int(frame_properties, "test_image", 0); // Push a callback to create the image. mlt_frame_push_get_image(frame, create_image); } mlt_frame_push_audio(frame, filter); mlt_frame_push_audio(frame, (void *) filter_get_audio); mlt_frame_push_service(frame, filter); mlt_frame_push_get_image(frame, filter_get_image); return frame; } static void filter_close(mlt_filter filter) { private_data *pdata = (private_data *) filter->child; if (pdata) { mlt_filter_close(pdata->levels_filter); free(pdata); } filter->child = NULL; filter->close = NULL; filter->parent.close = NULL; mlt_service_close(&filter->parent); } /** Constructor for the filter. */ extern "C" { mlt_filter filter_audiolevelgraph_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = mlt_filter_new(); private_data *pdata = (private_data *) calloc(1, sizeof(private_data)); if (filter && pdata && createQApplicationIfNeeded(MLT_FILTER_SERVICE(filter))) { mlt_properties properties = MLT_FILTER_PROPERTIES(filter); mlt_properties_set_int(properties, "_filter_private", 1); mlt_properties_set(properties, "type", "bar"); mlt_properties_set(properties, "bgcolor", "0x00000000"); mlt_properties_set(properties, "color.1", "0xffffffff"); mlt_properties_set(properties, "rect", "0% 0% 100% 100%"); mlt_properties_set(properties, "thickness", "0"); mlt_properties_set(properties, "fill", "0"); mlt_properties_set(properties, "mirror", "0"); mlt_properties_set(properties, "reverse", "0"); mlt_properties_set(properties, "angle", "0"); mlt_properties_set(properties, "gorient", "v"); mlt_properties_set_int(properties, "channels", 2); mlt_properties_set_int(properties, "segment_gap", 10); pdata->levels_filter = 0; filter->close = filter_close; filter->process = filter_process; filter->child = pdata; } else { mlt_log_error(MLT_FILTER_SERVICE(filter), "Filter audio level graph failed\n"); if (filter) { mlt_filter_close(filter); } if (pdata) { free(pdata); } filter = NULL; } return filter; } } mlt-7.22.0/src/modules/qt/filter_audiolevelgraph.yml000664 000000 000000 00000010141 14531534050 022501 0ustar00rootroot000000 000000 schema_version: 0.3 type: filter identifier: audiolevelgraph title: Audio Level Visualization Filter version: 1 copyright: Meltytech, LLC license: LGPLv2.1 language: en tags: - Video description: > An audio visualization filter that draws an audio level meter on the image. parameters: - identifier: type title: Graph type description: The type of graph to display the levels. type: string default: bar values: - segment - bar readonly: no mutable: yes widget: combo - identifier: bgcolor title: Background Color type: color description: | The background color to be applied to the entire frame. The default color is transparent. A color value is a hexadecimal representation of RGB plus alpha channel as 0xrrggbbaa. Colors can also be the words: white, black, red, green, or blue. You can also use a HTML-style color values #rrggbb or #aarrggbb. readonly: no mutable: yes widget: color animation: yes - identifier: color.* title: Foreground color type: color description: | The color of the waveform. Multiple colors can be specified with incrementing suffixes to cause the waveform to be drawn in a gradient. color.1 is the top of the waveform. Subsequent colors will produce a gradient toward the bottom. By default, the filter has one color defined: color.1=0xffffffff" This results in a white waveform. To create a gradient, define more colors: color.2=green color.3=0x77777777 A color value is a hexadecimal representation of RGB plus alpha channel as 0xrrggbbaa. Colors can also be the words: white, black, red, green, or blue. You can also use a HTML-style color values #rrggbb or #aarrggbb. readonly: no mutable: yes widget: color animation: yes - identifier: thickness title: Line Thickness type: integer description: > The thickness of the bar or segments. readonly: no default: 0 minimum: 0 maximum: 20 mutable: yes widget: spinner unit: pixels animation: yes - identifier: angle title: Angle type: float description: > The rotation angle to be applied to the waveform. readonly: no default: 0 minimum: 0 maximum: 360 mutable: yes widget: spinner animation: yes - identifier: rect title: Rectangle description: > Defines the rectangle that the waveform(s) should be drawn in. Format is: "X Y W H". X, Y, W, H are assumed to be pixel units unless they have the suffix '%'. type: rect default: "0 0 100% 100%" readonly: no mutable: yes animation: yes - identifier: mirror title: Mirror description: > Mirror the spectrum about the center of the rectangle. type: boolean default: 0 readonly: no mutable: yes widget: checkbox - identifier: reverse title: Reverse description: > Draw the points starting with the right channel first. type: boolean default: 0 readonly: no mutable: yes widget: checkbox - identifier: gorient title: Gradient Orientation description: Direction of the color gradient. type: string default: vertical values: - vertical - horizontal readonly: no mutable: yes widget: combo - identifier: channels title: Channels type: integer description: > The number of channels to show. mutable: yes readonly: no default: 2 animation: yes - identifier: segments title: Segments type: integer description: > The number of segments to draw if the graph type is "segment". mutable: yes readonly: no default: 10 minimum: 2 maximum: 100 animation: yes - identifier: segment_gap title: Segment Gap type: integer description: > The space in pixels between the segments. mutable: yes readonly: no default: 10 minimum: 0 maximum: 100 unit: pixels animation: yes mlt-7.22.0/src/modules/qt/filter_audiospectrum.cpp000664 000000 000000 00000036113 14531534050 022202 0ustar00rootroot000000 000000 /* * filter_audiospectrum.cpp -- audio spectrum visualization filter * Copyright (c) 2015-2022 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "common.h" #include "graph.h" #include // memset #include #include #include //pow #include #include #include // Private Types typedef struct { mlt_filter fft; char *fft_prop_name; int preprocess_warned; } private_data; static int filter_get_audio(mlt_frame frame, void **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples) { mlt_filter filter = (mlt_filter) mlt_frame_pop_audio(frame); mlt_properties filter_properties = MLT_FILTER_PROPERTIES(filter); private_data *pdata = (private_data *) filter->child; // Create the FFT filter the first time. if (!pdata->fft) { mlt_profile profile = mlt_service_profile(MLT_FILTER_SERVICE(filter)); pdata->fft = mlt_factory_filter(profile, "fft", NULL); mlt_properties_set_int(MLT_FILTER_PROPERTIES(pdata->fft), "window_size", mlt_properties_get_int(filter_properties, "window_size")); if (!pdata->fft) { mlt_log_warning(MLT_FILTER_SERVICE(filter), "Unable to create FFT.\n"); return 1; } } mlt_properties fft_properties = MLT_FILTER_PROPERTIES(pdata->fft); // The service must stay locked while using the private data mlt_service_lock(MLT_FILTER_SERVICE(filter)); // Perform FFT processing on the frame mlt_filter_process(pdata->fft, frame); mlt_frame_get_audio(frame, buffer, format, frequency, channels, samples); float *bins = (float *) mlt_properties_get_data(fft_properties, "bins", NULL); if (bins) { double window_level = mlt_properties_get_double(fft_properties, "window_level"); int bin_count = mlt_properties_get_int(fft_properties, "bin_count"); size_t bins_size = bin_count * sizeof(float); float *save_bins = (float *) mlt_pool_alloc(bins_size); if (window_level == 1.0) { memcpy(save_bins, bins, bins_size); } else { memset(save_bins, 0, bins_size); } // Save the bin data as a property on the frame to be used in get_image() mlt_properties_set_data(MLT_FRAME_PROPERTIES(frame), pdata->fft_prop_name, save_bins, bins_size, mlt_pool_release, NULL); } mlt_service_unlock(MLT_FILTER_SERVICE(filter)); return 0; } static void convert_fft_to_spectrum(mlt_filter filter, mlt_frame frame, int spect_bands, float *spectrum) { private_data *pdata = (private_data *) filter->child; mlt_properties filter_properties = MLT_FILTER_PROPERTIES(filter); mlt_properties fft_properties = MLT_FILTER_PROPERTIES(pdata->fft); mlt_properties frame_properties = MLT_FRAME_PROPERTIES(frame); mlt_position position = mlt_filter_get_position(filter, frame); mlt_position length = mlt_filter_get_length2(filter, frame); double low_freq = mlt_properties_anim_get_int(filter_properties, "frequency_low", position, length); double hi_freq = mlt_properties_anim_get_int(filter_properties, "frequency_high", position, length); int bin_count = mlt_properties_get_int(fft_properties, "bin_count"); double bin_width = mlt_properties_get_double(fft_properties, "bin_width"); float *bins = (float *) mlt_properties_get_data(frame_properties, pdata->fft_prop_name, NULL); double threshold = mlt_properties_anim_get_int(filter_properties, "threshold", position, length); int reverse = mlt_properties_get_int(filter_properties, "reverse"); // Map the linear fft bin frequencies to a log scale spectrum. double band_freq_factor = pow(hi_freq / low_freq, 1.0 / (double) spect_bands); double band_freq_low = low_freq; double band_freq_hi = band_freq_low * band_freq_factor; int bin_index = 0; int spect_index = 0; double bin_freq = 0; // Skip bins that occur before the low frequency of the spectrum while (bin_freq < band_freq_low) { bin_freq += bin_width; bin_index++; } for (spect_index = 0; spect_index < spect_bands && bin_index < bin_count; spect_index++) { float mag = 0.0; if (bin_freq > band_freq_hi) { // There is no bin for this point. Interpolate between the two closest. if (bin_index == 0) { mag = bins[bin_index]; } else { double y0 = bins[bin_index - 1]; double y1 = bins[bin_index]; double spect_center = band_freq_low + (band_freq_hi - band_freq_low) / 2; double prev_freq = bin_freq - bin_width; double t = bin_width / (spect_center - prev_freq); mag = y0 + (y1 - y0) * t; } } else { // Find the bin frequency with the greatest magnitude in the range for // this spectrum point. while (bin_freq < band_freq_hi && bin_index < bin_count) { if (mag < bins[bin_index]) { mag = bins[bin_index]; } bin_freq += bin_width; bin_index++; } } // Scale the magnitude to the range 0.0-1.0 based on dB double dB = mag > 0.0 ? 20 * log10(mag) : -1000.0; double spect_val = 0; if (dB >= threshold) { spect_val = 1.0 - (dB / threshold); } if (reverse) { spectrum[spect_bands - spect_index - 1] = spect_val; } else { spectrum[spect_index] = spect_val; } // Calculate the next spectrum point frequency range. band_freq_low = band_freq_hi; band_freq_hi = band_freq_hi * band_freq_factor; } } static void draw_spectrum(mlt_filter filter, mlt_frame frame, QImage *qimg, int width, int height) { mlt_properties filter_properties = MLT_FILTER_PROPERTIES(filter); mlt_position position = mlt_filter_get_position(filter, frame); mlt_position length = mlt_filter_get_length2(filter, frame); mlt_profile profile = mlt_service_profile(MLT_FILTER_SERVICE(filter)); mlt_rect rect = mlt_properties_anim_get_rect(filter_properties, "rect", position, length); if (strchr(mlt_properties_get(filter_properties, "rect"), '%')) { rect.x *= qimg->width(); rect.w *= qimg->width(); rect.y *= qimg->height(); rect.h *= qimg->height(); } double scale = mlt_profile_scale_width(profile, width); rect.x *= scale; rect.w *= scale; scale = mlt_profile_scale_height(profile, height); rect.y *= scale; rect.h *= scale; char *graph_type = mlt_properties_get(filter_properties, "type"); int mirror = mlt_properties_get_int(filter_properties, "mirror"); int fill = mlt_properties_get_int(filter_properties, "fill"); double tension = mlt_properties_anim_get_double(filter_properties, "tension", position, length); int segments = mlt_properties_anim_get_int(filter_properties, "segments", position, length); int segment_gap = mlt_properties_anim_get_int(filter_properties, "segment_gap", position, length) * scale; int segment_width = mlt_properties_anim_get_int(filter_properties, "thickness", position, length) * scale; QVector colors = get_graph_colors(filter_properties, position, length); QRectF r(rect.x, rect.y, rect.w, rect.h); QPainter p(qimg); if (mirror) { // Draw two half rectangle instead of one full rectangle. r.setHeight(r.height() / 2.0); } setup_graph_painter(p, r, filter_properties, position, length); setup_graph_pen(p, r, filter_properties, scale, position, length); int bands = mlt_properties_anim_get_int(filter_properties, "bands", position, length); if (bands == 0) { // "0" means match rectangle width bands = r.width(); } float *spectrum = (float *) mlt_pool_alloc(bands * sizeof(float)); convert_fft_to_spectrum(filter, frame, bands, spectrum); if (graph_type && graph_type[0] == 'b') { paint_bar_graph(p, r, bands, spectrum); } else if (graph_type && graph_type[0] == 's') { paint_segment_graph(p, r, bands, spectrum, colors, segments, segment_gap, segment_width); } else { paint_line_graph(p, r, bands, spectrum, tension, fill); } if (mirror) { // Second rectangle is mirrored. p.translate(0, r.y() * 2 + r.height() * 2); p.scale(1, -1); if (graph_type && graph_type[0] == 'b') { paint_bar_graph(p, r, bands, spectrum); } else if (graph_type && graph_type[0] == 's') { paint_segment_graph(p, r, bands, spectrum, colors, segments, segment_gap, segment_width); } else { paint_line_graph(p, r, bands, spectrum, tension, fill); } } mlt_pool_release(spectrum); p.end(); } /** Get the image. */ static int filter_get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { int error = 0; mlt_filter filter = (mlt_filter) mlt_frame_pop_service(frame); private_data *pdata = (private_data *) filter->child; mlt_properties frame_properties = MLT_FRAME_PROPERTIES(frame); if (mlt_properties_get_data(frame_properties, pdata->fft_prop_name, NULL)) { // Get the current image *format = mlt_image_rgba; error = mlt_frame_get_image(frame, image, format, width, height, 1); // Draw the spectrum if (!error) { QImage qimg(*width, *height, QImage::Format_ARGB32); convert_mlt_to_qimage_rgba(*image, &qimg, *width, *height); draw_spectrum(filter, frame, &qimg, *width, *height); convert_qimage_to_mlt_rgba(&qimg, *image, *width, *height); } } else { if (pdata->preprocess_warned++ == 2) { // This filter depends on the consumer processing the audio before // the video. mlt_log_warning(MLT_FILTER_SERVICE(filter), "Audio not preprocessed.\n"); } mlt_frame_get_image(frame, image, format, width, height, writable); } return error; } /** Filter processing. */ static mlt_frame filter_process(mlt_filter filter, mlt_frame frame) { if (mlt_frame_is_test_card(frame)) { // The producer does not generate video. This filter will create an // image on the producer's behalf. mlt_properties frame_properties = MLT_FRAME_PROPERTIES(frame); mlt_profile profile = mlt_service_profile(MLT_FILTER_SERVICE(filter)); mlt_properties_set_int(frame_properties, "progressive", 1); mlt_properties_set_double(frame_properties, "aspect_ratio", mlt_profile_sar(profile)); mlt_properties_set_int(frame_properties, "meta.media.width", profile->width); mlt_properties_set_int(frame_properties, "meta.media.height", profile->height); // Tell the framework that there really is an image. mlt_properties_set_int(frame_properties, "test_image", 0); // Push a callback to create the image. mlt_frame_push_get_image(frame, create_image); } mlt_frame_push_audio(frame, filter); mlt_frame_push_audio(frame, (void *) filter_get_audio); mlt_frame_push_service(frame, filter); mlt_frame_push_get_image(frame, filter_get_image); return frame; } static void filter_close(mlt_filter filter) { private_data *pdata = (private_data *) filter->child; if (pdata) { mlt_filter_close(pdata->fft); free(pdata->fft_prop_name); free(pdata); } filter->child = NULL; filter->close = NULL; filter->parent.close = NULL; mlt_service_close(&filter->parent); } /** Constructor for the filter. */ extern "C" { mlt_filter filter_audiospectrum_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = mlt_filter_new(); private_data *pdata = (private_data *) calloc(1, sizeof(private_data)); if (filter && pdata && createQApplicationIfNeeded(MLT_FILTER_SERVICE(filter))) { mlt_properties properties = MLT_FILTER_PROPERTIES(filter); mlt_properties_set_int(properties, "_filter_private", 1); mlt_properties_set_int(properties, "frequency_low", 20); mlt_properties_set_int(properties, "frequency_high", 20000); mlt_properties_set(properties, "type", "line"); mlt_properties_set(properties, "bgcolor", "0x00000000"); mlt_properties_set(properties, "color.1", "0xffffffff"); mlt_properties_set(properties, "rect", "0% 0% 100% 100%"); mlt_properties_set(properties, "thickness", "0"); mlt_properties_set(properties, "fill", "0"); mlt_properties_set(properties, "mirror", "0"); mlt_properties_set(properties, "reverse", "0"); mlt_properties_set(properties, "tension", "0.4"); mlt_properties_set(properties, "angle", "0"); mlt_properties_set(properties, "gorient", "v"); mlt_properties_set_int(properties, "segment_gap", 10); mlt_properties_set_int(properties, "bands", 31); mlt_properties_set_double(properties, "threshold", -60.0); mlt_properties_set_int(properties, "window_size", 8192); // Create a unique ID for storing data on the frame pdata->fft_prop_name = (char *) calloc(1, 20); snprintf(pdata->fft_prop_name, 20, "fft.%p", filter); pdata->fft_prop_name[20 - 1] = '\0'; pdata->fft = 0; filter->close = filter_close; filter->process = filter_process; filter->child = pdata; } else { mlt_log_error(MLT_FILTER_SERVICE(filter), "Filter audio spectrum failed\n"); if (filter) { mlt_filter_close(filter); } if (pdata) { free(pdata); } filter = NULL; } return filter; } } mlt-7.22.0/src/modules/qt/filter_audiospectrum.yml000664 000000 000000 00000014452 14531534050 022223 0ustar00rootroot000000 000000 schema_version: 7.0 type: filter identifier: audiospectrum title: Audio Spectrum Filter version: 1 copyright: Meltytech, LLC license: LGPLv2.1 language: en tags: - Video description: > An audio visualization filter that draws an audio spectrum on the image. parameters: - identifier: type title: Graph type description: The type of graph to display the spectrum. type: string default: line values: - line - bar readonly: no mutable: yes widget: combo - identifier: bgcolor title: Background Color type: color description: | The background color to be applied to the entire frame. The default color is transparent. A color value is a hexadecimal representation of RGB plus alpha channel as 0xrrggbbaa. Colors can also be the words: white, black, red, green, or blue. You can also use a HTML-style color values #rrggbb or #aarrggbb. readonly: no mutable: yes widget: color animation: yes - identifier: color.* title: Foreground color type: color description: | The color of the waveform. Multiple colors can be specified with incrementing suffixes to cause the waveform to be drawn in a gradient. color.1 is the top of the waveform. Subsequent colors will produce a gradient toward the bottom. By default, the filter has one color defined: color.1=0xffffffff" This results in a white waveform. To create a gradient, define more colors: color.2=green color.3=0x77777777 A color value is a hexadecimal representation of RGB plus alpha channel as 0xrrggbbaa. Colors can also be the words: white, black, red, green, or blue. You can also use a HTML-style color values #rrggbb or #aarrggbb. readonly: no mutable: yes widget: color animation: yes - identifier: thickness title: Line Thickness type: integer description: > The thickness of the line used to draw the waveform for line graph. The thickness of the bar for bar graph. readonly: no default: 0 minimum: 0 maximum: 20 mutable: yes widget: spinner unit: pixels animation: yes - identifier: angle title: Angle type: float description: > The rotation angle to be applied to the waveform. readonly: no default: 0 minimum: 0 maximum: 360 mutable: yes widget: spinner animation: yes - identifier: rect title: Rectangle description: > Defines the rectangle that the waveform(s) should be drawn in. Format is: "X Y W H". X, Y, W, H are assumed to be pixel units unless they have the suffix '%'. type: rect default: "0 0 100% 100%" readonly: no mutable: yes animation: yes - identifier: fill title: Fill description: > Whether the area under the waveform should be filled in. Only applies to line graph type. type: boolean default: 0 readonly: no mutable: yes widget: checkbox - identifier: mirror title: Mirror description: > Mirror the spectrum about the center of the rectangle. type: boolean default: 0 readonly: no mutable: yes widget: checkbox - identifier: reverse title: Reverse description: > Draw the points starting with the highest frequency first. type: boolean default: 0 readonly: no mutable: yes widget: checkbox - identifier: tension title: Line Tension description: > Affects the amount of curve in the line interpolating between points. 0.0 = a straight line between points. 1.0 = very curved lines between points. values < 0 and > 1 will cause loops in the lines. Only applies to line graph type. type: float default: 0.4 readonly: no mutable: yes animation: yes - identifier: gorient title: Gradient Orientation description: Direction of the color gradient. type: string default: vertical values: - vertical - horizontal readonly: no mutable: yes widget: combo - identifier: segment_gap title: Segment Gap type: integer description: > The space in pixels between the segments. mutable: yes readonly: no default: 10 minimum: 0 maximum: 100 unit: pixels animation: yes - identifier: bands title: Points type: integer description: > The number of bands to draw in the spectrum. Each band shows up as a data point in the graph. mutable: yes readonly: no default: 31 animation: yes - identifier: frequency_low title: Low Frequency type: integer description: > The low end of the frequency range to be used for the graph. motion. mutable: yes readonly: no default: 20 unit: Hz animation: yes - identifier: frequency_high title: High Frequency type: integer description: > The high end of the frequency range to be used for the graph. motion. mutable: yes readonly: no default: 20000 unit: Hz animation: yes - identifier: threshold title: Level Threshold type: float description: > The minimum amplitude of sound that must occur within the frequency range to cause the value to be applied. motion. mutable: yes readonly: no default: -30 minimum: -100 maximum: 0 unit: dB animation: yes - identifier: segments title: Segments type: integer description: > The number of segments to draw if the graph type is "segment". mutable: yes readonly: no default: 10 minimum: 2 maximum: 100 animation: yes - identifier: window_size title: Window Size type: integer description: > The number of samples that the FFT will be performed on. If window_size is less than the number of samples in a frame, extra samples will be ignored. If window_size is more than the number of samples in a frame, samples will be buffered from previous frames to fill the window. The buffering is performed as a sliding window so that the most recent samples are always transformed. mutable: no readonly: no default: 2048 mlt-7.22.0/src/modules/qt/filter_audiowaveform.cpp000664 000000 000000 00000042556 14531534050 022176 0ustar00rootroot000000 000000 /* * filter_audiowaveform.cpp -- audio waveform visualization filter * Copyright (c) 2015-2021 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "common.h" #include "graph.h" #include #include #include #include #include static const qreal MAX_S16_AMPLITUDE = 32768.0; namespace { // Private Types typedef struct { char *buffer_prop_name; int reset_window; int16_t *window_buffer; int window_samples; int window_frequency; int window_channels; } private_data; typedef struct { int16_t *buffer; int samples; int channels; } save_buffer; } // namespace static save_buffer *create_save_buffer(int samples, int channels, int16_t *buffer) { save_buffer *ret = (save_buffer *) calloc(1, sizeof(save_buffer)); int buffer_size = samples * channels * sizeof(int16_t); ret->samples = samples; ret->channels = channels; ret->buffer = (int16_t *) calloc(1, buffer_size); memcpy(ret->buffer, buffer, buffer_size); return ret; } static void destory_save_buffer(void *ptr) { if (!ptr) { mlt_log_error(NULL, "Invalid save_buffer ptr.\n"); return; } save_buffer *buff = (save_buffer *) ptr; free(buff->buffer); free(buff); } static void property_changed(mlt_service owner, mlt_filter filter, mlt_event_data event_data) { const char *name = mlt_event_data_to_string(event_data); if (name && !strcmp(name, "window")) { private_data *pdata = (private_data *) filter->child; pdata->reset_window = 1; } } static int filter_get_audio(mlt_frame frame, void **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples) { int error = 0; mlt_filter filter = (mlt_filter) mlt_frame_pop_audio(frame); private_data *pdata = (private_data *) filter->child; if (*format != mlt_audio_s16 && *format != mlt_audio_float) { *format = mlt_audio_float; } error = mlt_frame_get_audio(frame, buffer, format, frequency, channels, samples); if (error) { return error; } if (*frequency != pdata->window_frequency || *channels != pdata->window_channels) { pdata->reset_window = true; } if (pdata->reset_window) { mlt_log_info(MLT_FILTER_SERVICE(filter), "Reset window buffer: %d.\n", mlt_properties_get_int(MLT_FILTER_PROPERTIES(filter), "window")); mlt_profile profile = mlt_service_profile(MLT_FILTER_SERVICE(filter)); double fps = mlt_profile_fps(profile); int frame_samples = mlt_audio_calculate_frame_samples(fps, *frequency, mlt_frame_get_position(frame)); int window_ms = mlt_properties_get_int(MLT_FILTER_PROPERTIES(filter), "window"); pdata->window_frequency = *frequency; pdata->window_channels = *channels; pdata->window_samples = window_ms * *frequency / 1000; if (pdata->window_samples < frame_samples) { pdata->window_samples = frame_samples; } free(pdata->window_buffer); pdata->window_buffer = (int16_t *) calloc(1, pdata->window_samples * pdata->window_channels * sizeof(int16_t)); pdata->reset_window = 0; } int new_sample_count = *samples; if (new_sample_count > pdata->window_samples) { new_sample_count = pdata->window_samples; } int old_sample_count = pdata->window_samples - new_sample_count; int window_buff_bytes = pdata->window_samples * pdata->window_channels * sizeof(int16_t); int new_sample_bytes = new_sample_count * pdata->window_channels * sizeof(int16_t); int old_sample_bytes = old_sample_count * pdata->window_channels * sizeof(int16_t); // Move the old samples ahead in the window buffer to make room for new samples. if (new_sample_bytes < window_buff_bytes) { char *old_sample_src = (char *) pdata->window_buffer + new_sample_bytes; char *old_sample_dst = (char *) pdata->window_buffer; memmove(old_sample_dst, old_sample_src, old_sample_bytes); } // Copy the new samples to the back of the window buffer. if (*format == mlt_audio_s16) { char *new_sample_src = (char *) *buffer; char *new_sample_dst = (char *) pdata->window_buffer + old_sample_bytes; memcpy(new_sample_dst, new_sample_src, new_sample_bytes); } else // mlt_audio_float { for (int c = 0; c < pdata->window_channels; c++) { float *src = (float *) *buffer + (*samples * c); int16_t *dst = pdata->window_buffer + (old_sample_count * pdata->window_channels) + c; for (int s = 0; s < new_sample_count; s++) { *dst = *src * MAX_S16_AMPLITUDE; src++; dst += pdata->window_channels; } } } // Copy the window buffer and pass it along with the frame. save_buffer *out = create_save_buffer(pdata->window_samples, pdata->window_channels, pdata->window_buffer); mlt_properties_set_data(MLT_FRAME_PROPERTIES(frame), pdata->buffer_prop_name, out, sizeof(save_buffer), destory_save_buffer, NULL); return 0; } static void paint_waveform( QPainter &p, QRectF &rect, int16_t *audio, int samples, int channels, int fill) { int width = rect.width(); const int16_t *q = audio; qreal half_height = rect.height() / 2.0; qreal center_y = rect.y() + half_height; if (samples < width) { // For each x position on the waveform, find the sample value that // applies to that position and draw a point at that location. QPoint point(0, *q * half_height / MAX_S16_AMPLITUDE + center_y); QPoint lastPoint = point; int lastSample = 0; for (int x = 0; x < width; x++) { int sample = (x * samples) / width; if (sample != lastSample) { lastSample = sample; q += channels; } lastPoint.setX(x + rect.x()); lastPoint.setY(point.y()); point.setX(x + rect.x()); point.setY(*q * half_height / MAX_S16_AMPLITUDE + center_y); if (fill) { // Draw the line all the way to 0 to "fill" it in. if ((point.y() > center_y && lastPoint.y() > center_y) || (point.y() < center_y && lastPoint.y() < center_y)) { lastPoint.setY(center_y); } } if (point.y() == lastPoint.y()) { p.drawPoint(point); } else { p.drawLine(lastPoint, point); } } } else { // For each x position on the waveform, find the min and max sample // values that apply to that position. Draw a vertical line from the // min value to the max value. QPoint high; QPoint low; qreal max = *q; qreal min = *q; int lastX = 0; for (int s = 0; s <= samples; s++) { int x = (s * width) / samples; if (x != lastX) { // The min and max have been determined for the previous x // So draw the line if (fill) { // Draw the line all the way to 0 to "fill" it in. if (max > 0 && min > 0) { min = 0; } else if (min < 0 && max < 0) { max = 0; } } high.setX(lastX + rect.x()); high.setY(max * half_height / MAX_S16_AMPLITUDE + center_y); low.setX(lastX + rect.x()); low.setY(min * half_height / MAX_S16_AMPLITUDE + center_y); if (high.y() == low.y()) { p.drawPoint(high); } else { p.drawLine(low, high); } lastX = x; // Swap max and min so that the next line picks up where // this one left off. int tmp = max; max = min; min = tmp; } if (*q > max) max = *q; if (*q < min) min = *q; q += channels; } } } static void draw_waveforms(mlt_filter filter, mlt_frame frame, QImage *qimg, int16_t *audio, int channels, int samples, int width, int height) { mlt_properties filter_properties = MLT_FILTER_PROPERTIES(filter); mlt_position position = mlt_filter_get_position(filter, frame); mlt_position length = mlt_filter_get_length2(filter, frame); mlt_profile profile = mlt_service_profile(MLT_FILTER_SERVICE(filter)); int show_channel = mlt_properties_anim_get_int(filter_properties, "show_channel", position, length); int fill = mlt_properties_get_int(filter_properties, "fill"); mlt_rect rect = mlt_properties_anim_get_rect(filter_properties, "rect", position, length); if (strchr(mlt_properties_get(filter_properties, "rect"), '%')) { rect.x *= qimg->width(); rect.w *= qimg->width(); rect.y *= qimg->height(); rect.h *= qimg->height(); } double scale = mlt_profile_scale_width(profile, width); rect.x *= scale; rect.w *= scale; scale = mlt_profile_scale_height(profile, height); rect.y *= scale; rect.h *= scale; QRectF r(rect.x, rect.y, rect.w, rect.h); QPainter p(qimg); setup_graph_painter(p, r, filter_properties, position, length); if (show_channel == -1) // Combine all channels { if (channels > 1) { int16_t *in = audio; int16_t *out = audio; for (int s = 0; s < samples; s++) { double acc = 0.0; for (int c = 0; c < channels; c++) { acc += *in++; } *out++ = acc / channels; } channels = 1; } show_channel = 1; } if (show_channel == 0) // Show all channels { QRectF c_rect = r; qreal c_height = r.height() / channels; for (int c = 0; c < channels; c++) { // Divide the rectangle into smaller rectangles for each channel. c_rect.setY(r.y() + c_height * c); c_rect.setHeight(c_height); setup_graph_pen(p, c_rect, filter_properties, scale, position, length); paint_waveform(p, c_rect, audio + c, samples, channels, fill); } } else if (show_channel > 0) { // Show one specific channel if (show_channel > channels) { // Sanity show_channel = 1; } setup_graph_pen(p, r, filter_properties, scale, position, length); paint_waveform(p, r, audio + show_channel - 1, samples, channels, fill); } p.end(); } static int filter_get_image(mlt_frame frame, uint8_t **image, mlt_image_format *image_format, int *width, int *height, int writable) { int error = 0; mlt_properties frame_properties = MLT_FRAME_PROPERTIES(frame); mlt_filter filter = (mlt_filter) mlt_frame_pop_service(frame); private_data *pdata = (private_data *) filter->child; save_buffer *audio = (save_buffer *) mlt_properties_get_data(frame_properties, pdata->buffer_prop_name, NULL); if (audio) { // Get the current image *image_format = mlt_image_rgba; error = mlt_frame_get_image(frame, image, image_format, width, height, writable); // Draw the waveforms if (!error) { QImage qimg(*width, *height, QImage::Format_ARGB32); convert_mlt_to_qimage_rgba(*image, &qimg, *width, *height); draw_waveforms(filter, frame, &qimg, audio->buffer, audio->channels, audio->samples, *width, *height); convert_qimage_to_mlt_rgba(&qimg, *image, *width, *height); } } else { // This filter depends on the consumer processing the audio before // the video. mlt_log_warning(MLT_FILTER_SERVICE(filter), "Audio not preprocessed.\n"); return mlt_frame_get_image(frame, image, image_format, width, height, writable); } return error; } /** Filter processing. */ static mlt_frame filter_process(mlt_filter filter, mlt_frame frame) { mlt_properties frame_properties = MLT_FRAME_PROPERTIES(frame); if (mlt_frame_is_test_card(frame)) { // The producer does not generate video. This filter will create an // image on the producer's behalf. mlt_profile profile = mlt_service_profile(MLT_FILTER_SERVICE(filter)); mlt_properties_set_int(frame_properties, "progressive", 1); mlt_properties_set_double(frame_properties, "aspect_ratio", mlt_profile_sar(profile)); mlt_properties_set_int(frame_properties, "meta.media.width", profile->width); mlt_properties_set_int(frame_properties, "meta.media.height", profile->height); // Tell the framework that there really is an image. mlt_properties_set_int(frame_properties, "test_image", 0); // Push a callback to create the image. mlt_frame_push_get_image(frame, create_image); } mlt_frame_push_audio(frame, filter); mlt_frame_push_audio(frame, (void *) filter_get_audio); mlt_frame_push_service(frame, filter); mlt_frame_push_get_image(frame, filter_get_image); return frame; } static void filter_close(mlt_filter filter) { private_data *pdata = (private_data *) filter->child; if (pdata) { free(pdata->window_buffer); free(pdata->buffer_prop_name); free(pdata); } filter->child = NULL; filter->close = NULL; filter->parent.close = NULL; mlt_service_close(&filter->parent); } /** Constructor for the filter. */ extern "C" { mlt_filter filter_audiowaveform_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = mlt_filter_new(); private_data *pdata = (private_data *) calloc(1, sizeof(private_data)); if (filter && pdata) { if (!createQApplicationIfNeeded(MLT_FILTER_SERVICE(filter))) { mlt_filter_close(filter); return NULL; } mlt_properties filter_properties = MLT_FILTER_PROPERTIES(filter); mlt_properties_set(filter_properties, "bgcolor", "0x00000000"); mlt_properties_set(filter_properties, "color.1", "0xffffffff"); mlt_properties_set(filter_properties, "thickness", "0"); mlt_properties_set(filter_properties, "show_channel", "0"); mlt_properties_set(filter_properties, "angle", "0"); mlt_properties_set(filter_properties, "rect", "0 0 100% 100%"); mlt_properties_set(filter_properties, "fill", "0"); mlt_properties_set(filter_properties, "gorient", "v"); mlt_properties_set_int(filter_properties, "window", 0); pdata->reset_window = 1; // Create a unique ID for storing data on the frame pdata->buffer_prop_name = (char *) calloc(1, 20); snprintf(pdata->buffer_prop_name, 20, "audiowave.%p", filter); pdata->buffer_prop_name[20 - 1] = '\0'; filter->close = filter_close; filter->process = filter_process; filter->child = pdata; mlt_events_listen(filter_properties, filter, "property-changed", (mlt_listener) property_changed); } else { mlt_log_error(MLT_FILTER_SERVICE(filter), "Failed to initialize\n"); if (filter) { mlt_filter_close(filter); } if (pdata) { free(pdata); } filter = NULL; } return filter; } } mlt-7.22.0/src/modules/qt/filter_audiowaveform.yml000664 000000 000000 00000007536 14531534050 022214 0ustar00rootroot000000 000000 schema_version: 7.0 type: filter identifier: audiowaveform title: Audio Waveform Filter version: 1 copyright: Meltytech, LLC license: LGPLv2.1 language: en tags: - Video description: > An audio visualization filter that draws an audio waveform on the image. parameters: - identifier: bgcolor title: Background Color type: color description: | The background color to be applied to the entire frame. The default color is transparent. A color value is a hexadecimal representation of RGB plus alpha channel as 0xrrggbbaa. Colors can also be the words: white, black, red, green, or blue. You can also use a HTML-style color values #rrggbb or #aarrggbb. readonly: no mutable: yes widget: color animation: yes - identifier: color.* title: Foreground color type: color description: | The color of the waveform. Multiple colors can be specified with incrementing suffixes to cause the waveform to be drawn in a gradient. color.1 is the top of the waveform. Subsequent colors will produce a gradient toward the bottom. By default, the filter has one color defined: color.1=0xffffffff" This results in a white waveform. To create a gradient, define more colors: color.2=green color.3=0x77777777 A color value is a hexadecimal representation of RGB plus alpha channel as 0xrrggbbaa. Colors can also be the words: white, black, red, green, or blue. You can also use a HTML-style color values #rrggbb or #aarrggbb. readonly: no mutable: yes widget: color animation: yes - identifier: thickness title: Line Thickness type: integer description: > The thickness of the line used to draw the waveform. readonly: no default: 0 minimum: 0 maximum: 20 mutable: yes widget: spinner unit: pixels - identifier: show_channel title: Audio Channel type: integer description: > The audio channel to draw. "0" indicates that all channels should be drawn. "-1" indicates that all channels will be added together in a single waveform. readonly: no default: 0 minimum: 0 maximum: 20 mutable: yes widget: spinner animation: yes - identifier: angle title: Angle type: float description: > The rotation angle to be applied to the waveform. readonly: no default: 0 minimum: 0 maximum: 360 mutable: yes widget: spinner - identifier: rect title: Rectangle description: > Defines the rectangle that the waveform(s) should be drawn in. Format is: "X Y W H". X, Y, W, H are assumed to be pixel units unless they have the suffix '%'. type: rect default: "0 0 100% 100%" readonly: no mutable: yes animation: yes - identifier: fill title: Fill description: Whether the area under the waveform should be filled in. type: boolean default: 0 readonly: no mutable: yes widget: checkbox - identifier: gorient title: Gradient Orientation description: Direction of the color gradient. type: string default: vertical values: - vertical - horizontal readonly: no mutable: yes widget: combo - identifier: window title: Window type: integer description: > The duration of the audio (in ms) to be drawn in the waveform. If the window is less than the duration of a frame, the duration of a frame will be used. If the window is more than the duration of a frame, samples will be buffered from previous frames to fill the window. The buffering is performed as a sliding window so that the most recent samples are always transformed. mutable: no readonly: no default: 0 mlt-7.22.0/src/modules/qt/filter_gpsgraphic.cpp000664 000000 000000 00000105232 14531534050 021444 0ustar00rootroot000000 000000 /* * filter_gpsgraphic.cpp -- draws gps related graphics * Copyright (c) 2015-2022 Meltytech, LLC * Original author: Daniel F * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "filter_gpsgraphic.h" #include "common.h" #include static QMutex f_mutex; // Sets the private data to default values and frees gps points array static void default_priv_data(private_data *pdata) { if (pdata) { if (pdata->gps_points_r) { free(pdata->gps_points_r); pdata->gps_points_r = NULL; } if (pdata->gps_points_p) { free(pdata->gps_points_p); pdata->gps_points_p = NULL; } pdata->gps_points_size = 0; pdata->last_smooth_lvl = 0; pdata->last_searched_index = 0; pdata->first_gps_time = 0; pdata->last_gps_time = 0; pdata->gps_offset = 0; pdata->speed_multiplier = 1.0; pdata->last_filename[0] = '\0'; pdata->interpolated = 0; pdata->minmax.set_defaults(); memset(&pdata->ui_crops, 0, sizeof(pdata->ui_crops)); pdata->graph_data_source = 0; pdata->graph_type = 0; memset(&pdata->img_rect, 0, sizeof(pdata->img_rect)); pdata->last_bg_img_path[0] = '\0'; pdata->map_aspect_ratio_from_distance = 0.0; pdata->bg_img = QImage(); pdata->bg_img_scaled = QImage(); pdata->bg_img_scaled_width = 0.0; pdata->bg_img_scaled_height = 0.0; pdata->bg_img_matched_rect = QRectF(); pdata->swap_180 = 0; } } //like decimals_needed() but graph data source aware int decimals_needed_bysrc(mlt_filter filter, double v) { private_data *pdata = (private_data *) filter->child; if (pdata->graph_data_source == gspg_location_src) return 6; if (pdata->graph_data_source == gpsg_altitude_src || pdata->graph_data_source == gpsg_speed_src) return (decimals_needed(v)); if (pdata->graph_data_source == gpsg_hr_src) return 0; return 0; } //convert_[distance|speed]_to_format but graph data source aware double convert_bysrc_to_format(mlt_filter filter, double val) { private_data *pdata = (private_data *) filter->child; char *legend_unit = mlt_properties_get(MLT_FILTER_PROPERTIES(filter), "legend_unit"); if (pdata->graph_data_source == gpsg_altitude_src) return convert_distance_to_format(val, legend_unit); if (pdata->graph_data_source == gpsg_speed_src) return convert_speed_to_format(val, legend_unit); return val; } //helps pass filter private data for gps_parser.cpp calls gps_private_data filter_to_gps_data(mlt_filter filter) { private_data *pdata = (private_data *) filter->child; gps_private_data ret; ret.gps_points_r = pdata->gps_points_r; ret.gps_points_p = pdata->gps_points_p; ret.ptr_to_gps_points_r = &pdata->gps_points_r; ret.ptr_to_gps_points_p = &pdata->gps_points_p; ret.gps_points_size = &pdata->gps_points_size; ret.last_searched_index = &pdata->last_searched_index; ret.first_gps_time = &pdata->first_gps_time; ret.last_gps_time = &pdata->last_gps_time; ret.interpolated = &pdata->interpolated; ret.swap180 = &pdata->swap_180; ret.gps_proc_start_t = 0; ret.last_smooth_lvl = pdata->last_smooth_lvl; ret.last_filename = pdata->last_filename; ret.filter = filter; return ret; } /* Gets the value from pdata->minmax (or custom|existing gps_point) depending on data_src value get_type represents: 0=current_value, 1=max_value, -1=min_value subtype is used for longitude (planned more but didn't make sense so far) gps_p is used to get (only for get_type=0) the proper value by source from points not in pdata->gps_points_p[] (mostly weighted point) */ double get_by_src(mlt_filter filter, int get_type, int i_gps /* = 0 */, int subtype /* = 0 */, gps_point_proc *gps_p /* = NULL */) { private_data *pdata = (private_data *) filter->child; if (i_gps < 0 || i_gps >= pdata->gps_points_size) return 0; if (pdata->graph_data_source == gspg_location_src) { if (get_type == -1) { if (subtype == gpsg_latitude_id) return pdata->minmax.min_lat; else if (subtype == gpsg_longitude_id) return pdata->minmax.min_lon; } else if (get_type == 1) { if (subtype == gpsg_latitude_id) return pdata->minmax.max_lat; else if (subtype == gpsg_longitude_id) return pdata->minmax.max_lon; } else if (get_type == 0) { if (subtype == gpsg_latitude_id) { if (gps_p == NULL) return pdata->gps_points_p[i_gps].lat; else if (gps_p != NULL) return gps_p->lat; } else if (subtype == gpsg_longitude_id) { if (gps_p == NULL) return pdata->gps_points_p[i_gps].lon; else if (gps_p != NULL) return gps_p->lon; } } } else if (pdata->graph_data_source == gpsg_altitude_src) { if (get_type == -1) return pdata->minmax.min_ele; else if (get_type == 1) return pdata->minmax.max_ele; else if (get_type == 0) { if (gps_p == NULL) return pdata->gps_points_p[i_gps].ele; else if (gps_p != NULL) return gps_p->ele; } } else if (pdata->graph_data_source == gpsg_hr_src) { if (get_type == -1) return pdata->minmax.min_hr; else if (get_type == 1) return pdata->minmax.max_hr; else if (get_type == 0) { if (gps_p == NULL) return pdata->gps_points_p[i_gps].hr; else if (gps_p != NULL) return gps_p->hr; } } else if (pdata->graph_data_source == gpsg_speed_src) { if (get_type == -1) return pdata->minmax.min_speed; else if (get_type == 1) return pdata->minmax.max_speed; else if (get_type == 0) { if (gps_p == NULL) return pdata->gps_points_p[i_gps].speed; else if (gps_p != NULL) return gps_p->speed; } } mlt_log_warning(filter, "Invalid combination of arguments to get_by_src: (get_type=%d, i_gps=%d, " "subtype=%d, gps_p=%p)\n", get_type, i_gps, subtype, gps_p); return 0; } //get_by_src helper shortcuts double get_min_bysrc(mlt_filter filter, int subtype /* = 0 */) { return get_by_src(filter, -1, 0, subtype, NULL); } double get_max_bysrc(mlt_filter filter, int subtype /* = 0 */) { return get_by_src(filter, 1, 0, subtype, NULL); } double get_crtval_bysrc(mlt_filter filter, int i_gps, int subtype /* = 0 */, gps_point_proc *gps_p /* = NULL */) { return get_by_src(filter, 0, i_gps, subtype, gps_p); } // Returns the unix time (miliseconds) of "Media Created" metadata, or fallbacks to "Modified Time" from OS static int64_t get_original_video_file_time_mseconds(mlt_frame frame) { mlt_producer producer = mlt_producer_cut_parent(mlt_frame_get_original_producer(frame)); return mlt_producer_get_creation_time(producer); } /** Returns absolute* current frame time in miliseconds (original file creation + current timecode) * *also applies speed_multiplier */ static int64_t get_current_frame_time_ms(mlt_filter filter, mlt_frame frame) { mlt_properties properties = MLT_FILTER_PROPERTIES(filter); private_data *pdata = (private_data *) filter->child; int64_t file_time = 0, fr_time = 0; file_time = get_original_video_file_time_mseconds(frame); mlt_position frame_position = mlt_frame_original_position(frame); // mlt_log_info(filter, "frame_pos=%d, frame_orig_pos=%d; file_time=%d\n", mlt_frame_get_position(frame), mlt_frame_original_position(frame), file_time/1000); f_mutex.lock(); char *s = mlt_properties_frames_to_time(properties, frame_position, mlt_time_clock); if (s) { int h = 0, m = 0, sec = 0, msec = 0; sscanf(s, "%d:%d:%d.%d", &h, &m, &sec, &msec); fr_time = (h * 3600 + m * 60 + sec) * 1000 + msec; } else mlt_log_warning(filter, "get_current_frame_time_ms time string null, giving up " "[mlt_frame_original_position()=%d], retry result:%s\n", frame_position, mlt_properties_frames_to_time(properties, frame_position, mlt_time_clock)); f_mutex.unlock(); return file_time + fr_time * pdata->speed_multiplier; } //gets the nearest gps point [index] according to video time + input offset int get_now_gpspoint_index(mlt_filter filter, mlt_frame frame, bool force_result /* = true */) { private_data *pdata = (private_data *) filter->child; int64_t video_time_synced = get_current_frame_time_ms(filter, frame) + pdata->gps_offset; return binary_search_gps(filter_to_gps_data(filter), video_time_synced, force_result); } //returns the next gps point with a valid value for crt_source (starting with crt_i+1) int get_next_valid_gpspoint_index(mlt_filter filter, int crt_i) { private_data *pdata = (private_data *) filter->child; while (++crt_i < pdata->gps_points_size && get_crtval_bysrc(filter, crt_i) == GPS_UNINIT) ; //maybe TODO: add restriction for MAX_GPS_TIME? and allow depending on force_result return CLAMP(crt_i, 0, pdata->gps_points_size - 1); } //returns an interpolated gps point at the exact current video time + offset gps_point_proc get_now_weighted_gpspoint(mlt_filter filter, mlt_frame frame, bool force_result /* = true */) { private_data *pdata = (private_data *) filter->child; gps_point_proc crt; int i_now = -1, non_forced_i = -1; int max_gps_diff_ms = get_max_gps_diff_ms(filter_to_gps_data(filter)); int64_t video_time_synced = get_current_frame_time_ms(filter, frame) + pdata->gps_offset; i_now = binary_search_gps(filter_to_gps_data(filter), video_time_synced, force_result); //make sure we don't mistakenly interpolate between gps[0] and gps[0+1] because the "forced" search returned gps[0] due to outside time non_forced_i = binary_search_gps(filter_to_gps_data(filter), video_time_synced, false); if (i_now == -1) //if force_result is true this will never happen return uninit_gps_proc_point; //interpolate if everything ok int next_i = get_next_valid_gpspoint_index(filter, i_now); if (non_forced_i != -1) crt = weighted_middle_point_proc(&pdata->gps_points_p[i_now], &pdata->gps_points_p[next_i], video_time_synced, max_gps_diff_ms); else crt = pdata->gps_points_p[i_now]; return crt; } //initializes stuff and chooses which type of graph draw to call static void draw_graphics(mlt_filter filter, mlt_frame frame, QImage *qimg, int width, int height, s_base_crops &used_crops) { private_data *pdata = (private_data *) filter->child; mlt_properties properties = MLT_FILTER_PROPERTIES(filter); QPainter p(qimg); prepare_canvas(filter, frame, qimg, p, width, height, used_crops); if (pdata->ui_crops.start_index < pdata->ui_crops.end_index) { //draw the main graph line if (pdata->graph_type == gpsg_2d_map_graph || pdata->graph_type == gpsg_crop_center_graph) draw_main_line_graph(filter, frame, p, used_crops); else if (pdata->graph_type == gpsg_speedometer_graph) draw_main_speedometer(filter, frame, p, used_crops); } else mlt_log_info(filter, "min > max so nothing to print (index: start=%d,end=%d; start_p:%f,end_p:%f; " "vertical: UIbot=%f,UItop=%f; horizontal: UIleft:%f,UIright:%f)\n", pdata->ui_crops.start_index, pdata->ui_crops.end_index, mlt_properties_get_double(properties, "trim_start_p"), mlt_properties_get_double(properties, "trim_end_p"), pdata->ui_crops.bot, pdata->ui_crops.top, pdata->ui_crops.left, pdata->ui_crops.right); p.end(); } template static double get_as_percentage(T val, T min, T max) { double ret = 1; if (max - min != 0) ret = (double) (val - min) / (max - min); return ret; } //computes used_crops from pdata->ui_crops (they change on each frame so can't store them in pdata) static void process_frame_properties(mlt_filter filter, mlt_frame frame, s_base_crops &used_crops) { private_data *pdata = (private_data *) filter->child; //if CROP_CENTER we'll compute used_crops from pdata->ui_crops every frame to keep the point in center if (pdata->graph_type == gpsg_crop_center_graph) { int i_now = get_now_gpspoint_index(filter, frame); gps_point_proc crt_weighted = get_now_weighted_gpspoint(filter, frame); if (get_crtval_bysrc(filter, i_now, 0, &crt_weighted) != GPS_UNINIT) { double now_perc_vertical = get_as_percentage(get_crtval_bysrc(filter, 0, 0, &crt_weighted), get_min_bysrc(filter), get_max_bysrc(filter)); double now_perc_horizontal; if (pdata->graph_data_source == gspg_location_src) now_perc_horizontal = get_as_percentage(crt_weighted.lon, get_min_bysrc(filter, gpsg_longitude_id), get_max_bysrc(filter, gpsg_longitude_id)); else now_perc_horizontal = get_as_percentage(crt_weighted.time, pdata->ui_crops.min_crop_time, pdata->ui_crops.max_crop_time); //final crop is now_percentage +/- half of 100 - user requested crop (so a 90% crop means show 10% of the map, 5% above and 5% below the centered now_point) double delta_h = (100 - pdata->ui_crops.left) / 2.0; used_crops.left = now_perc_horizontal * 100 - delta_h; used_crops.right = now_perc_horizontal * 100 + delta_h; double delta_v = (100 - pdata->ui_crops.bot) / 2.0; used_crops.bot = now_perc_vertical * 100 - delta_v; used_crops.top = now_perc_vertical * 100 + delta_v; //if not 2d map, center for vertical axis is not something we want, so just use standard crop logic if (pdata->graph_data_source != gspg_location_src) { used_crops.bot = pdata->ui_crops.bot; used_crops.top = pdata->ui_crops.top; } } } else /* --> not gpsg_crop_center_graph */ { used_crops.bot = pdata->ui_crops.bot; used_crops.top = pdata->ui_crops.top; used_crops.left = pdata->ui_crops.left; used_crops.right = pdata->ui_crops.right; } } static int filter_get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { int error = 0; mlt_filter filter = (mlt_filter) mlt_frame_pop_service(frame); s_base_crops used_crops = {0, 100, 0, 100}; // Get the current image *format = mlt_image_rgba; error = mlt_frame_get_image(frame, image, format, width, height, writable); // Draw the graph if (!error) { process_frame_properties(filter, frame, used_crops); QImage qimg(*width, *height, QImage::Format_ARGB32); convert_mlt_to_qimage_rgba(*image, &qimg, *width, *height); draw_graphics(filter, frame, &qimg, *width, *height, used_crops); convert_qimage_to_mlt_rgba(&qimg, *image, *width, *height); } else mlt_log_warning(MLT_FILTER_SERVICE(filter), "mlt_frame_get_image error=%d, can't draw at all\n", error); return error; } //if crop mode is absolute we'll need to convert those values to a percentage as the rest of the code expects static void convert_crop_values_to_percentages(mlt_filter filter, mlt_frame frame) { private_data *pdata = (private_data *) filter->child; mlt_properties properties = MLT_FILTER_PROPERTIES(filter); int crop_mode_v = mlt_properties_get_int(properties, "crop_mode_v"); int crop_mode_h = mlt_properties_get_int(properties, "crop_mode_h"); if (crop_mode_h + crop_mode_v == 0) //0=already percentage, 1=need to convert return; double v_min = get_min_bysrc(filter); double v_max = get_max_bysrc(filter); v_min = convert_bysrc_to_format(filter, v_min); v_max = convert_bysrc_to_format(filter, v_max); //now calculate the percentages //resulted_crop_percentage = (input_absolute_value - crt_type_minimum_value) / (crt_type_maximum_value - crt_type_minimum_value) * 100 if (crop_mode_v) { pdata->ui_crops.bot = (pdata->ui_crops.bot - v_min) / (v_max - v_min) * 100; pdata->ui_crops.top = (pdata->ui_crops.top - v_min) / (v_max - v_min) * 100; } //this is just to cover all cases, I doubt someone would manually input coordinates or unix time (in milliseconds) when they can just visually set the percentage if (crop_mode_h) { if (pdata->graph_data_source == gspg_location_src) //longitude { v_min = get_min_bysrc(filter, gpsg_longitude_id); v_max = get_max_bysrc(filter, gpsg_longitude_id); pdata->ui_crops.left = (pdata->ui_crops.left - v_min) / (v_max - v_min) * 100; pdata->ui_crops.right = (pdata->ui_crops.right - v_min) / (v_max - v_min) * 100; } else //time { v_min = pdata->ui_crops.min_crop_time; v_max = pdata->ui_crops.max_crop_time; pdata->ui_crops.left = (pdata->ui_crops.left - v_min) / (v_max - v_min) * 100; pdata->ui_crops.right = (pdata->ui_crops.right - v_min) / (v_max - v_min) * 100; } } } //goes through ALL gps data and finds min/max for gps, altitude, hr + computes map's aspect ratio and middle point static void find_minmax_of_data(mlt_filter filter) { private_data *pdata = (private_data *) filter->child; pdata->minmax.set_defaults(); #define assign_if_smaller(x, m) \ if (x != GPS_UNINIT && x < m) \ m = x; #define assign_if_bigger(x, m) \ if (x != GPS_UNINIT && x > m) \ m = x; for (int i = 0; i < pdata->gps_points_size; i++) { gps_point_proc *crt = &pdata->gps_points_p[i]; assign_if_smaller(crt->lat, pdata->minmax.min_lat); assign_if_bigger(crt->lat, pdata->minmax.max_lat); assign_if_smaller(crt->lon, pdata->minmax.min_lon); assign_if_bigger(crt->lon, pdata->minmax.max_lon); assign_if_smaller(crt->ele, pdata->minmax.min_ele); assign_if_bigger(crt->ele, pdata->minmax.max_ele); assign_if_smaller(crt->speed, pdata->minmax.min_speed); assign_if_bigger(crt->speed, pdata->minmax.max_speed); assign_if_smaller(crt->hr, pdata->minmax.min_hr); assign_if_bigger(crt->hr, pdata->minmax.max_hr); assign_if_smaller(crt->grade_p, pdata->minmax.min_grade_p); assign_if_bigger(crt->grade_p, pdata->minmax.max_grade_p); } #undef assign_if_smaller #undef assign_if_bigger //compute the map aspect ratio using real distances (used to correctly overlay background image over gps track) double map_aspect_ratio = 1; double map_width = distance_haversine_2p(pdata->minmax.min_lat, pdata->minmax.min_lon, pdata->minmax.min_lat, pdata->minmax.max_lon); double map_height = distance_haversine_2p(pdata->minmax.min_lat, pdata->minmax.min_lon, pdata->minmax.max_lat, pdata->minmax.min_lon); if (map_width && map_height) map_aspect_ratio = map_width / map_height; pdata->map_aspect_ratio_from_distance = map_aspect_ratio; mlt_properties_set_double(MLT_FILTER_PROPERTIES(filter), "map_original_aspect_ratio", map_aspect_ratio); // //compute the gps track aspect ratio (from coords) // the other one seems better almost every time // map_aspect_ratio = 1; // map_width = pdata->minmax.max_lon - pdata->minmax.min_lon; // map_height = pdata->minmax.max_lat - pdata->minmax.min_lat; // if (map_width && map_height) // map_aspect_ratio = map_width / map_height; // mlt_properties_set_double(MLT_FILTER_PROPERTIES( filter ), "map_original_aspect_ratio", map_aspect_ratio); char middle_point[255]; double middle_lat = (pdata->minmax.min_lat + pdata->minmax.max_lat) / 2; double middle_lon = (pdata->minmax.min_lon + pdata->minmax.max_lon) / 2; snprintf(middle_point, 255, "%.6f, %.6f", middle_lat, swap_180_if_needed(middle_lon)); mlt_properties_set(MLT_FILTER_PROPERTIES(filter), "map_coords_hint", middle_point); mlt_log_info(filter, "gps file [%d points] minmax: min_lat,lon-max_lat,lon: %f,%f:%f,%f; ele: %f,%f; " "speed:%f,%f; " "hr:%f,%f; grade_p:%f,%f%%, map_ar:%f, mid_point:%s \n", pdata->gps_points_size, pdata->minmax.min_lat, pdata->minmax.min_lon, pdata->minmax.max_lat, pdata->minmax.max_lon, pdata->minmax.min_ele, pdata->minmax.max_ele, pdata->minmax.min_speed, pdata->minmax.max_speed, pdata->minmax.min_hr, pdata->minmax.max_hr, pdata->minmax.min_grade_p, pdata->minmax.max_grade_p, map_aspect_ratio, middle_point); } // Reads and updates all necessary filter properties, and smooths+processes the gps data if needed static void process_filter_properties(mlt_filter filter, mlt_frame frame) { mlt_properties properties = MLT_FILTER_PROPERTIES(filter); private_data *pdata = (private_data *) filter->child; char do_smoothing = 0; //read properties int read_video_offset1 = mlt_properties_get_int(properties, "time_offset"); int read_smooth_val = mlt_properties_get_int(properties, "smoothing_value"); double read_speed_multiplier = mlt_properties_get_double(properties, "speed_multiplier"); int64_t original_video_time = get_original_video_file_time_mseconds(frame); //process properties pdata->gps_offset = (int64_t) read_video_offset1 * 1000; pdata->speed_multiplier = (read_speed_multiplier ? read_speed_multiplier : 1); if (pdata->last_smooth_lvl != read_smooth_val) { pdata->last_smooth_lvl = read_smooth_val; do_smoothing = 1; } char video_start_text[255], gps_start_text[255]; mseconds_to_timestring(original_video_time, NULL, video_start_text); mseconds_to_timestring(pdata->first_gps_time, NULL, gps_start_text); if (do_smoothing) { //also always does processing + updates minmax process_gps_smoothing(filter_to_gps_data(filter), 1); find_minmax_of_data(filter); } pdata->graph_data_source = mlt_properties_get_int(properties, "graph_data_source"); pdata->graph_type = mlt_properties_get_int(properties, "graph_type"); pdata->ui_crops.trim_start_p = mlt_properties_get_double(properties, "trim_start_p"); pdata->ui_crops.start_index = pdata->gps_points_size / 100.0 * pdata->ui_crops.trim_start_p; pdata->ui_crops.start_index = CLAMP(pdata->ui_crops.start_index, 0, pdata->gps_points_size - 1); pdata->ui_crops.min_crop_time = pdata->gps_points_p[pdata->ui_crops.start_index].time; pdata->ui_crops.trim_end_p = mlt_properties_get_double(properties, "trim_end_p"); pdata->ui_crops.end_index = pdata->gps_points_size / 100.0 * pdata->ui_crops.trim_end_p; pdata->ui_crops.end_index = CLAMP(pdata->ui_crops.end_index, 0, pdata->gps_points_size - 1); pdata->ui_crops.max_crop_time = pdata->gps_points_p[pdata->ui_crops.end_index].time; pdata->ui_crops.bot = mlt_properties_get_double(properties, "crop_bot_p"); pdata->ui_crops.top = mlt_properties_get_double(properties, "crop_top_p"); pdata->ui_crops.left = mlt_properties_get_double(properties, "crop_left_p"); pdata->ui_crops.right = mlt_properties_get_double(properties, "crop_right_p"); convert_crop_values_to_percentages(filter, frame); //(re-)read background img file if path changed char *bg_path = mlt_properties_get(properties, "bg_img_path"); bool new_img_loaded = false; if (bg_path && strlen(bg_path) > 0 && bg_path[0] != '!') { if (strcmp(bg_path, pdata->last_bg_img_path)) { if (pdata->bg_img.load(bg_path)) { pdata->bg_img = pdata->bg_img.convertToFormat(QImage::Format_ARGB32); strncpy(pdata->last_bg_img_path, bg_path, 255); new_img_loaded = true; } else { mlt_log_warning(filter, "Failed to load background image: %s\n", bg_path); } } } else //delete the previous image { if (strlen(pdata->last_bg_img_path) > 0) { pdata->last_bg_img_path[0] = 0; pdata->bg_img = QImage(); pdata->bg_img_scaled = QImage(); } } //process the image so it matches the map when overlaid //-> image center must equal gps track center //-> unless image size is already perfectly matching track min/max, we'll need user input to re-scale it double bg_scale_w = mlt_properties_get_double(properties, "bg_scale_w"); if (!pdata->bg_img.isNull() && (new_img_loaded || bg_scale_w != pdata->bg_img_scaled_width)) //only update if something changed { double map_ar = pdata->map_aspect_ratio_from_distance; //mlt_properties_get_double(properties, "map_original_aspect_ratio"); double bg_ar = (double) pdata->bg_img.width() / pdata->bg_img.height(); double bg_scale_h = bg_scale_w * bg_ar / map_ar; pdata->bg_img_scaled_width = bg_scale_w; pdata->bg_img_scaled_height = bg_scale_h; // mlt_log_info(filter, "scale w=%f -> h=%f; bg_ar=%f, map_ar=%f", bg_scale_w, bg_scale_h, bg_ar, map_ar); //step 1) scale image width or height by map_aspect_ratio so now they both have the same aspect_ratio double new_bg_width = pdata->bg_img.width(); double new_bg_height = pdata->bg_img.height(); if (map_ar > 1) new_bg_width *= map_ar; else new_bg_height /= map_ar; pdata->bg_img_scaled = pdata->bg_img.scaled(new_bg_width, new_bg_height, Qt::IgnoreAspectRatio, Qt::SmoothTransformation); // mlt_log_info(filter, "scaled image= x,y w,h= %d,%d %d,%d", pdata->bg_img_scaled.rect().x(), pdata->bg_img_scaled.rect().y(), pdata->bg_img_scaled.rect().width(), pdata->bg_img_scaled.rect().height()); // pdata->bg_img_scaled.save("qimage_saved02_scaled_by_mapAR.jpg"); //next steps are in prepare_canvas() as they depend on each frame position (used_crops) } //write properties mlt_properties_set(properties, "gps_start_text", gps_start_text); mlt_properties_set(properties, "video_start_text", video_start_text); mlt_properties_set_int(properties, "auto_gps_offset_start", (pdata->first_gps_time - original_video_time) / 1000); mlt_properties_set_int(properties, "auto_gps_offset_now", (pdata->first_gps_time - get_current_frame_time_ms(filter, frame)) / 1000); } //checks if a new file is selected and if so, does private_data cleanup and calls qxml_parse_file //+ computes some one time pdata values static void process_file(mlt_filter filter, mlt_frame frame) { private_data *pdata = (private_data *) filter->child; mlt_properties properties = MLT_FILTER_PROPERTIES(filter); char *filename = mlt_properties_get(properties, "resource"); bool guess_offset = (mlt_properties_get_int(properties, "time_offset") == 0) && (strlen(pdata->last_filename) == 0); //if there's no file selected just return if (!filename || !strcmp(filename, "")) return; //check if the file has been changed, if not, current data is ok, do nothing if (strcmp(pdata->last_filename, filename)) { // mlt_log_info(filter, "Reading new file: last_filename (%s) != entered_filename (%s), swap_180 = %d \n", pdata->last_filename, filename, swap); default_priv_data(pdata); strcpy(pdata->last_filename, filename); if (qxml_parse_file(filter_to_gps_data(filter)) == 1) { get_first_gps_time(filter_to_gps_data(filter)); get_last_gps_time(filter_to_gps_data(filter)); //when loading the first file, sync gps start with video start int64_t original_video_time = get_original_video_file_time_mseconds(frame); if (guess_offset) { pdata->gps_offset = pdata->first_gps_time - original_video_time; mlt_properties_set_int(properties, "time_offset", pdata->gps_offset / 1000); } //assume smooth is 5 (default) so we can guarantee *gps_points_p and min/max allocation pdata->last_smooth_lvl = 5; process_gps_smoothing(filter_to_gps_data(filter), 1); find_minmax_of_data(filter); } else { default_priv_data(pdata); //store name in pdata or it'll retry reading every frame if bad file strcpy(pdata->last_filename, filename); } } } /** Filter processing. */ static mlt_frame filter_process(mlt_filter filter, mlt_frame frame) { private_data *pdata = (private_data *) filter->child; process_file(filter, frame); if (pdata->gps_points_r == NULL || pdata->gps_points_size == 0) { return frame; } process_filter_properties(filter, frame); mlt_frame_push_service(frame, filter); mlt_frame_push_get_image(frame, filter_get_image); return frame; } static void filter_close(mlt_filter filter) { private_data *pdata = (private_data *) filter->child; default_priv_data(pdata); free(pdata); filter->child = NULL; filter->close = NULL; filter->parent.close = NULL; mlt_service_close(&filter->parent); } /** Constructor for the filter. */ extern "C" { mlt_filter filter_gpsgraphic_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = mlt_filter_new(); private_data *pdata = (private_data *) calloc(1, sizeof(private_data)); if (filter && pdata && createQApplicationIfNeeded(MLT_FILTER_SERVICE(filter))) { mlt_properties properties = MLT_FILTER_PROPERTIES(filter); mlt_properties_set_string(properties, "resource", arg); //sync stuff mlt_properties_set_int(properties, "time_offset", 0); mlt_properties_set_int(properties, "smoothing_value", 5); mlt_properties_set_double(properties, "speed_multiplier", 1); //graph data stuff mlt_properties_set_int(properties, "graph_data_source", 2); mlt_properties_set_int(properties, "graph_type", 0); mlt_properties_set_double(properties, "trim_start_p", 0); mlt_properties_set_double(properties, "trim_end_p", 100); mlt_properties_set_int(properties, "crop_mode_h", 0); mlt_properties_set_double(properties, "crop_left_p", 0); mlt_properties_set_double(properties, "crop_right_p", 100); mlt_properties_set_int(properties, "crop_mode_v", 0); mlt_properties_set_double(properties, "crop_bot_p", 0); mlt_properties_set_double(properties, "crop_top_p", 100); //graph design stuff mlt_properties_set_int(properties, "color_style", 1); mlt_properties_set(properties, "color.1", "#ff00aaff"); mlt_properties_set(properties, "color.2", "#ff00e000"); mlt_properties_set(properties, "color.3", "#ffffff00"); mlt_properties_set(properties, "color.4", "#ffff8c00"); mlt_properties_set(properties, "color.5", "#ffff0000"); mlt_properties_set_int(properties, "show_now_dot", 1); mlt_properties_set(properties, "now_dot_color", "#00ffffff"); mlt_properties_set_int(properties, "show_now_text", 1); mlt_properties_set_double(properties, "angle", 0); mlt_properties_set_int(properties, "thickness", 5); mlt_properties_set(properties, "rect", "10% 10% 30% 30%"); mlt_properties_set_int(properties, "show_grid", 0); mlt_properties_set(properties, "legend_unit", ""); mlt_properties_set_int(properties, "draw_individual_dots", 0); //background image mlt_properties_set(properties, "map_coords_hint", ""); mlt_properties_set(properties, "bg_img_path", ""); mlt_properties_set_double(properties, "bg_scale_w", 1); mlt_properties_set_double(properties, "bg_opacity", 1); filter->close = filter_close; filter->process = filter_process; filter->child = pdata; } else { mlt_log_error(MLT_FILTER_SERVICE(filter), "Filter gps graphic failed\n"); if (filter) { mlt_filter_close(filter); } if (pdata) { free(pdata); } filter = NULL; } return filter; } } mlt-7.22.0/src/modules/qt/filter_gpsgraphic.h000664 000000 000000 00000014443 14531534050 021114 0ustar00rootroot000000 000000 #ifndef GPS_GRAPHIC_H #define GPS_GRAPHIC_H //only using get_graph_colors() from graph.h #include "gps_parser.h" #include "graph.h" #include #include struct s_crops { double trim_start_p, trim_end_p; double bot, top, left, right; int64_t min_crop_time, max_crop_time; int start_index, end_index; }; struct s_gps_data_bounds { double min_lat, max_lat, min_lon, max_lon; double min_ele, max_ele, min_speed, max_speed; double min_hr, max_hr, min_grade_p, max_grade_p; void set_defaults() { min_lat = 90, min_lon = 180, max_lat = -90, max_lon = -180; min_ele = 99999, max_ele = -99999, min_speed = 99999, max_speed = -99999; min_hr = 99999, max_hr = 0, min_grade_p = 99999, max_grade_p = -99999; } }; namespace { typedef struct { gps_point_raw *gps_points_r; //raw gps data from file gps_point_proc *gps_points_p; //processed gps data int gps_points_size; int last_smooth_lvl; int last_searched_index; //used to optimize repeated searches int64_t first_gps_time, last_gps_time; int64_t gps_offset; double speed_multiplier; char last_filename[256]; //gps file fullpath char interpolated; s_gps_data_bounds minmax; s_crops ui_crops; int graph_data_source, graph_type; mlt_rect img_rect; char last_bg_img_path[256]; double map_aspect_ratio_from_distance; QImage bg_img, bg_img_scaled; double bg_img_scaled_width, bg_img_scaled_height; QRectF bg_img_matched_rect; int swap_180; } private_data; } // namespace typedef enum { gspg_location_src = 0, gpsg_altitude_src, gpsg_hr_src, gpsg_speed_src } gpsg_data_sources; typedef enum { gpsg_latitude_id = gspg_location_src, gpsg_longitude_id = gpsg_latitude_id + 100 } gpsg_data_subids; typedef enum { gpsg_2d_map_graph = 0, gpsg_crop_center_graph, gpsg_speedometer_graph //gpsg_ladder_graph, //was thinking a vertical ruler/gauge thing for altitude, couldn't design something that actually looks good //gpsg_compass_graph //probably easy to adapt from speedometer but not really that important to be worth the extra code } gpsg_graph_types; typedef enum { gpsg_color_by_solid = 0, gpsg_color_by_solid_past_future, gpsg_color_by_solid_past, gpsg_color_by_solid_future, gpsg_color_by_vertical_gradient, gpsg_color_by_horizontal_gradient, gpsg_color_by_duration, gpsg_color_by_altitude, gpsg_color_by_hr, gpsg_color_by_speed, gpsg_color_by_speed_max100, gpsg_color_by_grade_max90, gpsg_color_by_grade_max20 } gpsg_color_styles; struct s_base_crops { double bot, top, left, right; }; struct point_2d { double x, y; }; /**** functions in filter_gpsgraphic.cpp needed in gps_drawing.cpp ****/ //apply crop percentages to [min, max] then compute value val as a percentage of [new_min, new_max] //if inside_only is true, result is always inside interval [0..1] template double crop_and_normalize(T val, T min, T max, double p_min, double p_max, bool inside_only = false) { double ret = 0; T delta = max - min; T new_min = min + p_min * delta / 100.0; T new_max = min + delta * p_max / 100.0; if (new_max == new_min) //divide by zero -> draw a constant line ret = 0.5; else ret = (double) (val - new_min) / (new_max - new_min); // mlt_log_info(NULL, "crop_and_normalize (val=%f, min=%f, max=%f, p_min=%f, p_max=%f) result=%f [restricted to 0..1]\n", val, min, max, p_min, p_max, ret); if (inside_only) ret = CLAMP(ret, 0, 1); return ret; } //convert_[distance|speed]_to_format but graph data source aware double convert_bysrc_to_format(mlt_filter filter, double val); //like decimals_needed() but graph data source aware int decimals_needed_bysrc(mlt_filter filter, double v); /* Gets the value from pdata->minmax (or custom|existing gps_point) depending on data_src value get_type represents: 0=current_value, 1=max_value, -1=min_value subtype is used for longitude gps_p is used to get (only for get_type=0) the proper value by source from points not in pdata->gps_points_p[] */ double get_by_src( mlt_filter filter, int get_type, int i_gps = 0, int subtype = 0, gps_point_proc *gps_p = NULL); //get_by_src shortcuts double get_min_bysrc(mlt_filter filter, int subtype = 0); double get_max_bysrc(mlt_filter filter, int subtype = 0); double get_crtval_bysrc(mlt_filter filter, int i_gps, int subtype = 0, gps_point_proc *gps_p = NULL); //returns the next gps point with a valid value for crt_source (starting with crt_i+1) int get_next_valid_gpspoint_index(mlt_filter filter, int crt_i); //gets the nearest gps point [index] according to video time + input offset int get_now_gpspoint_index(mlt_filter filter, mlt_frame frame, bool force_result = true); //returns an interpolated gps point at the exact current video time + offset gps_point_proc get_now_weighted_gpspoint(mlt_filter filter, mlt_frame frame, bool force_result = true); gps_private_data filter_to_gps_data(mlt_filter filter); /**** functions in gps_drawing.cpp needed in filter_gpsgraphic.cpp ****/ //draws 5 horizontal (+5 vertical for 2D map) with small text for each line showing the graph value at that point void draw_legend_grid(mlt_filter filter, mlt_frame frame, QPainter &p, s_base_crops &used_crops); //inits drawing rect +clipping, antialiasing, and applies rotate void prepare_canvas(mlt_filter filter, mlt_frame frame, QImage *qimg, QPainter &p, int width, int height, s_base_crops &used_crops); //draws a small dot/disc on the map/graph according to the current video time + sync void draw_now_dot(mlt_filter filter, mlt_frame frame, QPainter &p, s_base_crops &used_crops); //draws a speedometer from the current data source (for map it displays % of total) void draw_main_speedometer(mlt_filter filter, mlt_frame frame, QPainter &p, s_base_crops &used_crops); //draws a 2d graph of the chosen graph source (for non-location, second axis is time) void draw_main_line_graph(mlt_filter filter, mlt_frame frame, QPainter &p, s_base_crops &used_crops); #endif mlt-7.22.0/src/modules/qt/filter_gpsgraphic.yml000664 000000 000000 00000040174 14531534050 021466 0ustar00rootroot000000 000000 schema_version: 7.0 type: filter identifier: gpsgraphic title: GPS Graphic version: 2 copyright: Meltytech, LLC creator: Daniel F license: LGPLv2.1 language: en tags: - Video description: Overlay GPS-related graphics onto the video parameters: - identifier: resource title: GPS File/URL description: > The fullpath of file containing location (GPS) data. Supported extensions: .gpx, .tcx. type: string required: yes readonly: no widget: fileopen - identifier: time_offset title: Time offset description: > An offset (in seconds) to be added to the video file to match it to the GPS track. Most of the time this will at least need to be set to the timezone difference between the 2 files plus some seconds if the video recording device isn't precisely set to correct time. GPS time is always exact and in UTC. Use positive values if GPS is ahead of video and negative otherwise. type: integer default: 0 required: no readonly: no mutable: yes widget: text - identifier: smoothing_value title: Smoothing level description: > How many GPS points to smooth in order to eliminate GPS errors. A value of 1 does not smooth locations, it only calculates the extra fields (speed, distance, etc) it also interpolates missing values for heart rate and altitude. type: integer default: 5 minimum: 1 readonly: no required: no mutable: yes widget: text - identifier: speed_multiplier title: Speed multiplier description: > If the video file is a timelapse (or slow motion), use this value to set the speed up/slow down ratio. Sample values: 30 for 30x timelapse, 0.25 for 4x slow motion footage. type: float default: 1 readonly: no required: no mutable: yes widget: text # graph data source params: - identifier: graph_data_source title: Graph data source description: | What data from the GPS file is used for drawing: 0 = GPS location/track 1 = altitude (if available) 2 = heart rate (if available) 3 = speed (always available, computed from location) type: integer default: 0 minimum: 0 maximum: 3 readonly: no mutable: yes widget: combo - identifier: graph_type title: Graph type description: | How to draw the selected data: 0 = a basic 2D map line (for location) or 1D graph per time (others) 1 = zooms in onto the map/graph and centers around the current location 2 = draws a speedometer (available for all data sources but recommended only for speed; for the map type it represents the "percentage done" from trimmed start - end) Note: for type 1 (follow centered dot): * crop values are only valid as a percentage and only the bottom (resp left) values will be taken into consideration as both values (ie: bot/top) will need to be equal to keep the dot centered) * if data source is not GPS location, the centering will only be done for horizontal axis (time), vertical axis crop will behave just like for the type 0 (it will statically keep the same min/max limit allowing the now_dot to move up and down). type: integer default: 0 minimum: 0 maximum: 2 readonly: no mutable: yes widget: combo - identifier: trim_start_p title: Trim start description: > Trim data from the start of the gps file (as a percentage of total). Note: both trim_start_p and trim_end_p are computed from the total file so trimming 50% start and 50% end will result in trimming the entire file. type: float default: 0 minimum: 0 maximum: 100 readonly: no mutable: yes widget: spinner - identifier: trim_end_p title: Trim end description: > Trim data from the end of the gps file (as a *reversed* percentage of total, ie: 100 means no trim, 50 means half of total file, 5 means trim 95% of the file). Note: both trim_start_p and trim_end_p are computed from the total file so trimming 50% start and 50% end will result in trimming the entire file. type: float default: 100 minimum: 0 maximum: 100 readonly: no mutable: yes widget: spinner - identifier: crop_mode_h title: Crop mode horizontal description: | Decides how to interpret the crop_left_p and crop_right_p values: 0 = a percentage from min..max 1 = an absolute value (ie: it can crop between 22.2 and 22.3 degrees of longitude) Note: for the horizontal type, absolute values are the longitude (for the location source type) and time (in miliseconds since epoch) for the rest of the data source types) type: integer default: 0 minimum: 0 maximum: 1 readonly: no mutable: yes widget: combo - identifier: crop_left_p title: Crop left description: > Crops data from the left side of the graph (effectively zooming in). The value is interpreted either as a percentage of total or an absolute value depending on crop_mode_h. In percentage mode the values are not restricted to 0-100 to allow for "zoom out" behaviour (ie: cropping -50 left will add an extra half of the total horizontal distance). Values over 100 (in % mode) will effectively not display anything. If graph_type is speedometer, all crop left/right values are ignored. type: float default: 0 readonly: no mutable: yes widget: spinner - identifier: crop_right_p title: Crop left description: > Same as crop_left_p but for the right side and percentage type is interpreted as an inverse percentage (ie: 100 = do not crop anything). Values under 0 will effectively not display anything. type: float default: 100 readonly: no mutable: yes widget: spinner - identifier: crop_mode_v title: Crop mode vertical description: | Decides how to interpret the crop_bot_p and crop_top_p values: 0 = a percentage from min..max 1 = an absolute value (ie: it can zoom in to between 100 and 150m of altitude to show small changes in altitude between those 2 values better) Note: for the vertical type, absolute values are latitude degrees (for the location source type) and altitude, heart rate, speed for the others interpreted as the legend_unit type where applicable (ie: a value of 10 for altitude will be considered meters by default but if changing legend_unit to feet it will mean 10 feet). type: integer default: 0 minimum: 0 maximum: 1 readonly: no mutable: yes widget: combo - identifier: crop_bot_p title: Crop left description: > Crops data from the bottom side of the graph (effectively zooming in). The value is interpreted either as a percentage of total or an absolute value depending on crop_mode_v. In percentage mode the values are not restricted to 0-100 to allow for "zoom out" behaviour (ie: cropping -50 bot will add an extra half of the total vertical distance to the bottom). Values over 100 (in % mode) will effectively not display anything. If graph_type is speedometer, this will set the minimum needle position which will clamp all values that are lower. type: float default: 0 readonly: no mutable: yes widget: spinner - identifier: crop_top_p title: Crop left description: > Same as crop_bot_p but for the top side and percentage type is interpreted as an inverse percentage (ie: 100 = do not crop anything). Values under 0 will effectively not display anything. type: float default: 100 readonly: no mutable: yes widget: spinner # graph design params: - identifier: color_style title: Graph color style description: | Chooses one of the following styles to draw the graph line: 0 = one color -> same color and size for the entire graph 1 = two colors -> same as 2 and 3 but the entire line is the same thickness 2 = solid past, thin future -> from the beginning of the graph to the current position (="past") it will be drawn using the 1st color and chosen thickness, but for the "future" part of the graph it will use the 2nd color and thickness will be 2px (or 1px if main thickness is below 3) 3 = solid future, thin past 4 = vertical gradient -> the line will be coloured as a vertical gradient relative to the entire rect area 5 = horizontal gradient 6 = color by duration -> the selected colors will be used as a gradient, in chronological time (except for location source, this will effectively be a left to right gradient for 1D graphs) 7 = color by altitude -> the selected colors will be used as a gradient from the minimum altitude value from file to the maximum one, not affected by crops or trim 8 = color by heart rate 9 = color by speed -> same as above but gradient is affected by smoothing 10 = color by speed (max 100km/h) -> max speed accepted is 100km/h - to cover bad gps errors 11 = color by grade (max 90 degrees) -> the gradient follows the grade value but relative to a maximum of 90 degrees (if max grade is less than 90, it will use that value); affected by smoothing 12 = Color by grade (max 20 degrees) -> as above but max color is relative to max 20 degrees; this provides expected colors for most GPS tracks otherwise a single steeper area would make the rest of the track indistinguishible from flat ground type: integer default: 1 minimum: 0 maximum: 9 readonly: no mutable: yes widget: combo - identifier: color.* title: Colours type: color description: | Sets the colours of the graph line. Multiple colours can be specified with incrementing suffixes to cause the line to be drawn in a specific way (ie: gradient or past/future). By default, the filter has 5 colors (blue, green, yellow, orange, red): color.1 = #ff00aaff color.2 = #ff00e000 color.3 = #ffffff00 color.4 = #ffff8c00 color.5 = #ffff0000 A color value is a hexadecimal representation of RGB plus alpha channel as 0xrrggbbaa. Colors can also be the words: white, black, red, green, or blue. You can also use a HTML-style color values #rrggbb or #aarrggbb. readonly: no mutable: yes widget: color animation: yes - identifier: show_now_dot title: Show now dot description: > Enable it to draw a disc at the current location/time over the graph line. If graph type is speedometer, this affects the needle. type: boolean default: 0 readonly: no mutable: yes widget: checkbox - identifier: now_dot_color title: Now dot colour type: color description: > Choose the outer circle color of the now_dot disc. The size of this circle is the same as the line thickness. The inside of the disc is always white. If the alpha value of the color is 0 (default) this will use the same colour as the nearby (or past) line (including for gradient types) thus effectively making it change color in time. A color value is a hexadecimal representation of RGB plus alpha channel as 0xrrggbbaa. Colors can also be the words: white, black, red, green, or blue. You can also use a HTML-style color values #rrggbb or #aarrggbb. readonly: no mutable: yes widget: color animation: yes - identifier: show_now_text title: Show now text description: > Enable it to draw the current value in big white bold letters on the bottom right side of the rect. The legend_unit value will be appended at the end and it will be used as the current unit (if a valid unit is found ie: kilometers if "km" is found anywhere in the legend_unit string). type: boolean default: 0 readonly: no mutable: yes widget: checkbox - identifier: angle title: Rotation description: > Rotate the entire graph rect. For speedometer type the text stays horizontal. type: float default: 0 readonly: no mutable: yes - identifier: thickness title: Thickness description: > Sets the thickness of the line graph in px. type: integer default: 5 minimum: 1 readonly: no mutable: yes - identifier: show_grid title: Draw legend description: > If enabled it will draw 5 horizontal (and vertical for map type) lines with small values each corresponding to the current data source value at 0%, 25%, 50%, 75% and 100% of current graph shown, affected by the legend_unit type if applicable and with the string appended to the value. For speedometer type this shows division values (but without appending unit). type: boolean default: 0 readonly: no mutable: yes widget: checkbox - identifier: legend_unit title: Legend unit description: > Sets the unit to be used for displaying values of type altitude and speed. Default is meters and km/h respectively. The unit is matched anywhere in the string so extra spaces can be used to tweak displaying. Supported formats, distance: m|meter, km|kilometer*, mi|mile*, ft|feet, nm|nautical*; speed: ms|m/s|meter, km|km/h|kilometer, mi|mi/h|mile, ft|ft/s|feet, kn|nm/h|knots, mmin|m/min, ftmin|ft/min. type: string default: m readonly: no mutable: yes - identifier: draw_individual_dots title: Show gots only description: > If enabled the graph will be drawn using individual dots instead of lines. This will effectively show the individual data points as affected by smoothing (ie: for location data it will display each GPS fix if smoothing is 1) and can either be used as a cool effect when zoomed in enough or to debug unexpected line jumps. type: boolean default: 0 readonly: no mutable: yes widget: checkbox - identifier: rect title: Rectangle description: > Defines the rectangle that the graph should be drawn in. Format is: "X Y W H". X, Y, W, H are assumed to be pixel units unless they have the suffix '%'. type: rect default: "0 0 100% 100%" readonly: no mutable: yes # background params: - identifier: bg_img_path title: Background image description: > If a valid image file is selected it will be used as a background for the rect area. Paths starting with the "!" character are ignored. TIP: use a map image to add context to the gps track. type: string required: no readonly: no widget: fileopen - identifier: bg_scale_w title: Background scale description: > Scale the background image (relative to center) to match it to the above gps track. Values smaller than 1 zoom into the image, values larger than 1 zoom out. 0 hides it. type: float default: 1 minimum: 0 maximum: 2 readonly: no mutable: yes - identifier: bg_opacity title: Background opacity description: > Sets the opacity of the background image. type: float default: 1 minimum: 0 maximum: 1 readonly: no mutable: yes # read only: - identifier: gps_start_text title: GPS start time description: Date and time of the first valid GPS point. readonly: yes - identifier: video_start_text title: Video start time description: Date and time of the video file. readonly: yes - identifier: auto_gps_offset_start title: Auto offset start description: > Provides a helper offset to guarantee start of video file syncs with the start of gps file. Correctly sets the offset if video file and gps recording was started at the same time. readonly: yes - identifier: auto_gps_offset_now title: Auto offset now description: > Provides a helper offset to sync the first gps point to current video time (it is updated every second). Correctly sets the offset if you video record the moment gps starts. readonly: yes - identifier: map_coords_hint title: Map hint description: Returns the middle lat, lon coordinates of the gps file. readonly: yes mlt-7.22.0/src/modules/qt/filter_gpstext.cpp000664 000000 000000 00000072617 14531534050 021025 0ustar00rootroot000000 000000 /* * filter_gpstext.c -- overlays GPS data as text on video * Copyright (C) 2011-2022 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "gps_parser.h" #include static QMutex f_mutex; #define MAX_TEXT_LEN 1024 namespace { typedef struct { gps_point_raw *gps_points_r; //raw gps data from file gps_point_proc *gps_points_p; //processed gps data int gps_points_size; int last_smooth_lvl; int last_searched_index; //used to optimize repeated searches int64_t first_gps_time; int64_t last_gps_time; int64_t gps_offset; int64_t gps_proc_start_t; //process only points after this time (epoch miliseconds) double speed_multiplier; double updates_per_second; char last_filename[256]; //gps file fullpath char interpolated; int swap_180; } private_data; } // namespace // Sets the private data to default values and frees gps points array static void default_priv_data(private_data *pdata) { if (pdata) { if (pdata->gps_points_r) free(pdata->gps_points_r); if (pdata->gps_points_p) free(pdata->gps_points_p); memset(pdata, 0, sizeof(private_data)); pdata->speed_multiplier = 1; pdata->updates_per_second = 1; } } // Collects often used filter private data into a single variable static gps_private_data filter_to_gps_data(mlt_filter filter) { private_data *pdata = (private_data *) filter->child; gps_private_data ret; ret.gps_points_r = pdata->gps_points_r; ret.gps_points_p = pdata->gps_points_p; ret.ptr_to_gps_points_r = &pdata->gps_points_r; ret.ptr_to_gps_points_p = &pdata->gps_points_p; ret.gps_points_size = &pdata->gps_points_size; ret.last_searched_index = &pdata->last_searched_index; ret.first_gps_time = &pdata->first_gps_time; ret.last_gps_time = &pdata->last_gps_time; ret.interpolated = &pdata->interpolated; ret.swap180 = &pdata->swap_180; ret.gps_proc_start_t = pdata->gps_proc_start_t; ret.last_smooth_lvl = pdata->last_smooth_lvl; ret.last_filename = pdata->last_filename; ret.filter = filter; return ret; } // Get the next token and indicate whether it is enclosed in "# #". static int get_next_token(char *str, int *pos, char *token, int *is_keyword) { int token_pos = 0; int str_len = strlen(str); if ((*pos) >= str_len || str[*pos] == '\0') return 0; if (str[*pos] == '#') { *is_keyword = 1; (*pos)++; } else *is_keyword = 0; while (*pos < str_len && token_pos < MAX_TEXT_LEN - 1) { if (str[*pos] == '\\' && str[(*pos) + 1] == '#') { // Escape Sequence - "#" preceded by "\" - copy the # into the token. token[token_pos] = '#'; token_pos++; (*pos)++; // skip "\" (*pos)++; // skip "#" } else if (str[*pos] == '#') { if (*is_keyword) { // Found the end of the keyword (*pos)++; } break; } else { token[token_pos] = str[*pos]; token_pos++; (*pos)++; } } token[token_pos] = '\0'; return 1; } /* Processes the offset (seconds) in a time keyword and removes it * #gps_datetime_now +3600# -> add 1hour * returns value in miliesconds */ static int64_t extract_offset_time_ms_keyword(char *keyword) { //mlt_log_info(NULL, "extract_offset_time_keyword: in keyword: %s", keyword); char *end = NULL; if (keyword == NULL) return 0; int val = strtol(keyword, &end, 0); //if found, remove the processed number if (val != 0) { if (strlen(end) == 0) *keyword = '\0'; else memmove(keyword, end, strlen(end) + 1); } return val * 1000; } /** Replaces the GPS keywords with actual values, also parses any keyword extra format. * Returns "--" for keywords with no valid return. */ static void gps_point_to_output(mlt_filter filter, char *keyword, char *result_gps_text, int i_now, int64_t req_time, gps_point_proc now_gps) { private_data *pdata = (private_data *) filter->child; char *format = NULL; char gps_text[MAX_TEXT_LEN]; strcpy(gps_text, "--"); if (i_now == -1 || pdata->gps_points_r == NULL) { strncat(result_gps_text, gps_text, MAX_TEXT_LEN - strlen(result_gps_text) - 1); return; } //assign the raw or processed values to a tmp point (depending on smoothing value) gps_point_proc crt_point = uninit_gps_proc_point; gps_point_raw raw = pdata->gps_points_r[i_now]; if (pdata->last_smooth_lvl == 0) { crt_point.lat = raw.lat; crt_point.lon = raw.lon; crt_point.speed = raw.speed; crt_point.total_dist = raw.total_dist; crt_point.ele = raw.ele; crt_point.bearing = raw.bearing; crt_point.hr = raw.hr; crt_point.cad = raw.cad; crt_point.atemp = raw.atemp; } else { if (pdata->gps_points_p == NULL) return; //now_gps is already interpolated to frame time + updates_per_second + speed if (pdata->updates_per_second != 0) crt_point = now_gps; else crt_point = pdata->gps_points_p[i_now]; } /* for every keyword: we first check if the "RAW" keyword is used and if so, we print the value read from file (or --) then we check if there's a format keyword and apply the necessary conversion/format. The format must appear after the main keyword, RAW and format order is not important (strstr is used) */ if (!strncmp(keyword, "gps_lat", strlen("gps_lat")) && crt_point.lat != GPS_UNINIT) { if (strstr(keyword, "RAW")) { if (raw.lat == GPS_UNINIT) return; snprintf(gps_text, 10, "%3.6f", raw.lat); } else { snprintf(gps_text, 10, "%3.6f", crt_point.lat); } } else if (!strncmp(keyword, "gps_lon", strlen("gps_lon")) && crt_point.lon != GPS_UNINIT) { if (strstr(keyword, "RAW")) { if (raw.lon == GPS_UNINIT) return; snprintf(gps_text, 10, "%3.6f", swap_180_if_needed(raw.lon)); } else { snprintf(gps_text, 10, "%3.6f", swap_180_if_needed(crt_point.lon)); } } else if (!strncmp(keyword, "gps_elev", strlen("gps_elev")) && crt_point.ele != GPS_UNINIT) { if (strlen(keyword) > strlen("gps_elev")) format = keyword + strlen("gps_elev"); double val; if (strstr(keyword, "RAW")) { if (raw.ele == GPS_UNINIT) return; val = convert_distance_to_format(raw.ele, format); } else { val = convert_distance_to_format(crt_point.ele, format); } snprintf(gps_text, 10, "%.*f", decimals_needed(val), val); } else if (!strncmp(keyword, "gps_speed", strlen("gps_speed")) && crt_point.speed != GPS_UNINIT) { if (strlen(keyword) > strlen("gps_speed")) format = keyword + strlen("gps_speed"); double val = 0; if (strstr(keyword, "RAW")) { if (raw.speed == GPS_UNINIT) return; } else { val = crt_point.speed; if (strstr(keyword, "vertical")) val = crt_point.speed_vertical; else if (strstr(keyword, "3d")) val = crt_point.speed_3d; } val = convert_speed_to_format(val, format); snprintf(gps_text, 10, "%.*f", decimals_needed(val), val); } else if (!strncmp(keyword, "gps_hr", strlen("gps_hr")) && crt_point.hr != GPS_UNINIT) { if (strstr(keyword, "RAW")) { if (raw.hr == GPS_UNINIT) return; snprintf(gps_text, 10, "%.0f", raw.hr); } else { snprintf(gps_text, 10, "%.0f", crt_point.hr); } } else if (!strncmp(keyword, "gps_bearing", strlen("gps_bearing")) && crt_point.bearing != GPS_UNINIT) { if (strstr(keyword, "RAW")) { if (raw.bearing == GPS_UNINIT) return; snprintf(gps_text, 10, "%.0f", raw.bearing); } else { snprintf(gps_text, 10, "%.0f", crt_point.bearing); } } else if (!strncmp(keyword, "gps_compass", strlen("gps_compass")) && crt_point.bearing != GPS_UNINIT) { if (strstr(keyword, "RAW")) { if (raw.bearing == GPS_UNINIT) return; snprintf(gps_text, 4, "%s", bearing_to_compass(raw.bearing)); } else { snprintf(gps_text, 4, "%s", bearing_to_compass(crt_point.bearing)); } } else if (!strncmp(keyword, "gps_cadence", strlen("gps_cadence")) && crt_point.cad != GPS_UNINIT) { if (strstr(keyword, "RAW")) { if (raw.cad == GPS_UNINIT) return; snprintf(gps_text, 10, "%.0f", raw.cad); } else { snprintf(gps_text, 10, "%.0f", crt_point.cad); } } else if (!strncmp(keyword, "gps_temperature", strlen("gps_temperature")) && crt_point.atemp != GPS_UNINIT) { double atemp; if (strstr(keyword, "RAW")) { if (raw.atemp == GPS_UNINIT) return; atemp = raw.atemp; } else { atemp = crt_point.atemp; } if (strstr(keyword, "F")) atemp = atemp * 1.8 + 32; else if (strstr(keyword, "K")) atemp = atemp + 273.15; snprintf(gps_text, 10, "%.*f", decimals_needed_maxone(atemp), atemp); } else if (!strncmp(keyword, "gps_vdist_up", strlen("gps_vdist_up")) && crt_point.elev_up != GPS_UNINIT) { if (strlen(keyword) > strlen("gps_vdist_up")) format = keyword + strlen("gps_vdist_up"); double val = convert_distance_to_format(fabs(crt_point.elev_up), format); snprintf(gps_text, 10, "%.*f", decimals_needed(val), val); } else if (!strncmp(keyword, "gps_vdist_down", strlen("gps_vdist_down")) && crt_point.elev_down != GPS_UNINIT) { if (strlen(keyword) > strlen("gps_vdist_down")) format = keyword + strlen("gps_vdist_down"); double val = convert_distance_to_format(fabs(crt_point.elev_down), format); snprintf(gps_text, 10, "%.*f", decimals_needed(val), val); } else if (!strncmp(keyword, "gps_dist_uphill", strlen("gps_dist_uphill")) && crt_point.dist_up != GPS_UNINIT) { if (strlen(keyword) > strlen("gps_dist_uphill")) format = keyword + strlen("gps_dist_uphill"); double val = convert_distance_to_format(crt_point.dist_up, format); snprintf(gps_text, 10, "%.*f", decimals_needed(val), val); } else if (!strncmp(keyword, "gps_dist_downhill", strlen("gps_dist_downhill")) && crt_point.dist_down != GPS_UNINIT) { if (strlen(keyword) > strlen("gps_dist_downhill")) format = keyword + strlen("gps_dist_downhill"); double val = convert_distance_to_format(crt_point.dist_down, format); snprintf(gps_text, 10, "%.*f", decimals_needed(val), val); } else if (!strncmp(keyword, "gps_dist_flat", strlen("gps_dist_flat")) && crt_point.dist_flat != GPS_UNINIT) { if (strlen(keyword) > strlen("gps_dist_flat")) format = keyword + strlen("gps_dist_flat"); double val = convert_distance_to_format(crt_point.dist_flat, format); snprintf(gps_text, 10, "%.*f", decimals_needed(val), val); } //NOTE: gps_dist must be below gps_dist_up/down/flat or it'll match them else if (!strncmp(keyword, "gps_dist", strlen("gps_dist")) && crt_point.total_dist != GPS_UNINIT) { if (strlen(keyword) > strlen("gps_dist")) format = keyword + strlen("gps_dist"); double val; if (strstr(keyword, "RAW")) { if (raw.total_dist == GPS_UNINIT) return; val = convert_distance_to_format(raw.total_dist, format); } else { val = convert_distance_to_format(crt_point.total_dist, format); } snprintf(gps_text, 10, "%.*f", decimals_needed(val), val); } else if (!strncmp(keyword, "gps_grade_percentage", strlen("gps_grade_percentage")) && crt_point.grade_p != GPS_UNINIT) { double val = crt_point.grade_p; snprintf(gps_text, 10, "%+.*f", decimals_needed_maxone(val), val); } else if (!strncmp(keyword, "gps_grade_degrees", strlen("gps_grade_degrees")) && crt_point.grade_p != GPS_UNINIT) { double val = to_deg(atan(crt_point.grade_p / 100.0)); snprintf(gps_text, 10, "%+.*f", decimals_needed_maxone(val), val); } else if (!strncmp(keyword, "gps_datetime_now", strlen("gps_datetime_now")) && raw.time != GPS_UNINIT) { int64_t val = 0; char *offset = NULL; if ((offset = strstr(keyword, "+")) != NULL || (offset = strstr(keyword, "-")) != NULL) val = extract_offset_time_ms_keyword(offset); if (strlen(keyword) > strlen("gps_datetime_now")) format = keyword + strlen("gps_datetime_now"); mseconds_to_timestring(raw.time + val, format, gps_text); } strncat(result_gps_text, gps_text, MAX_TEXT_LEN - strlen(result_gps_text) - 1); // mlt_log_info(NULL, "filter_gps.c gps_point_to_output, keyword=%s, result_gps_text=%s", keyword, result_gps_text); } // Returns the unix time (miliseconds) of "Media Created" metadata, or fallbacks to "Modified Time" from OS static int64_t get_original_video_file_time_mseconds(mlt_frame frame) { mlt_producer producer = mlt_producer_cut_parent(mlt_frame_get_original_producer(frame)); return mlt_producer_get_creation_time(producer); } //Restricts how many updates per second are done (the searched gps - frame time is altered) static int64_t restrict_updates(int64_t fr, double upd_per_sec) { if (upd_per_sec == 0) // = disabled return fr; int64_t rez = fr - fr % (int) (1000.0 / upd_per_sec); //mlt_log_info(NULL, "_time restrict: %d [%f x] -> %d", fr%100000, upd_per_sec, rez%100000); return rez; } /** Returns absolute* current frame time in miliseconds * (original file creation + current timecode) * *also applies updates_per_second and speed_multiplier */ static int64_t get_current_frame_time_ms(mlt_filter filter, mlt_frame frame) { mlt_properties properties = MLT_FILTER_PROPERTIES(filter); private_data *pdata = (private_data *) filter->child; int64_t file_time = 0, fr_time = 0; file_time = get_original_video_file_time_mseconds(frame); mlt_position frame_position = mlt_frame_original_position(frame); f_mutex.lock(); char *s = mlt_properties_frames_to_time(properties, frame_position, mlt_time_clock); if (s) { int h = 0, m = 0, sec = 0, msec = 0; sscanf(s, "%d:%d:%d.%d", &h, &m, &sec, &msec); fr_time = (h * 3600 + m * 60 + sec) * 1000 + msec; } else mlt_log_warning(filter, "get_current_frame_time_ms time string null, giving up " "[mlt_frame_original_position()=%d], retry result:%s\n", frame_position, mlt_properties_frames_to_time(properties, frame_position, mlt_time_clock)); f_mutex.unlock(); return file_time + restrict_updates(fr_time, pdata->updates_per_second) * pdata->speed_multiplier; } /** Reads and updates all necessary filter properties, and processes the gps data if needed */ static void process_filter_properties(mlt_filter filter, mlt_frame frame) { mlt_properties properties = MLT_FILTER_PROPERTIES(filter); private_data *pdata = (private_data *) filter->child; char do_smoothing = 0, do_processing = 0; //read properties int read_video_offset1 = mlt_properties_get_int(properties, "time_offset"); int read_smooth_val = mlt_properties_get_int(properties, "smoothing_value"); char *read_gps_processing_start_time = mlt_properties_get(properties, "gps_processing_start_time"); double read_speed_multiplier = mlt_properties_get_double(properties, "speed_multiplier"); double read_updates_per_second = mlt_properties_get_double(properties, "updates_per_second"); int64_t original_video_time = get_original_video_file_time_mseconds(frame); // mlt_log_info(filter, "process_filter_properties - read values: offset1=%d, smooth=%d, gps_start_time=%s, speed=%f, updates=%f", // read_video_offset1, read_smooth_val, read_gps_processing_start_time, read_speed_multiplier, read_updates_per_second); //process properties pdata->gps_offset = (int64_t) read_video_offset1 * 1000; pdata->speed_multiplier = (read_speed_multiplier ? read_speed_multiplier : 1); pdata->updates_per_second = read_updates_per_second; if (pdata->last_smooth_lvl != read_smooth_val) { pdata->last_smooth_lvl = read_smooth_val; do_smoothing = 1; } if (read_gps_processing_start_time != NULL) { int64_t gps_proc_t = 0; if (strlen(read_gps_processing_start_time) != 0 && strcmp(read_gps_processing_start_time, "yyyy-MM-dd hh:mm:ss")) gps_proc_t = datetimeXMLstring_to_mseconds(read_gps_processing_start_time, (char *) "%Y-%m-%d %H:%M:%S"); if (gps_proc_t != pdata->gps_proc_start_t) { pdata->gps_proc_start_t = gps_proc_t; do_processing = 1; } } else if (pdata->gps_proc_start_t != 0) { pdata->gps_proc_start_t = 0; do_processing = 1; } char video_start_text[255], gps_start_text[255]; mseconds_to_timestring(original_video_time, NULL, video_start_text); mseconds_to_timestring(pdata->first_gps_time, NULL, gps_start_text); if (do_smoothing) process_gps_smoothing(filter_to_gps_data(filter), 1); else if (do_processing) //smoothing also does processing recalculate_gps_data(filter_to_gps_data(filter)); char gps_processing_start_now[255]; int64_t gps_now = get_current_frame_time_ms(filter, frame) + pdata->gps_offset; mseconds_to_timestring(gps_now, NULL, gps_processing_start_now); //write properties mlt_properties_set(properties, "gps_start_text", gps_start_text); mlt_properties_set(properties, "video_start_text", video_start_text); mlt_properties_set_int(properties, "auto_gps_offset_start", (pdata->first_gps_time - original_video_time) / 1000); mlt_properties_set_int(properties, "auto_gps_offset_now", (pdata->first_gps_time - get_current_frame_time_ms(filter, frame)) / 1000); mlt_properties_set(properties, "auto_gps_processing_start_now", gps_processing_start_now); } /** Replaces file_datetime_now with absolute time-date string (video created + current timecode) * (time includes speed_multiplier and updates per second) */ static void get_current_frame_time_str(char *keyword, mlt_filter filter, mlt_frame frame, char *result) { int64_t val = 0; char *offset = NULL, *format = NULL; //check for seconds offset if ((offset = strstr(keyword, "+")) != NULL || (offset = strstr(keyword, "-")) != NULL) val = extract_offset_time_ms_keyword(offset); //check for time format char text[MAX_TEXT_LEN]; if (strlen(keyword) > strlen("file_datetime_now")) format = keyword + strlen("file_datetime_now"); mseconds_to_timestring(val + get_current_frame_time_ms(filter, frame), format, text); strncat(result, text, MAX_TEXT_LEN - strlen(result) - 1); } /** Perform substitution for keywords that are enclosed in "# #". * Also prepares [current] gps point */ static void substitute_keywords(mlt_filter filter, char *result, char *value, mlt_frame frame) { private_data *pdata = (private_data *) filter->child; char keyword[MAX_TEXT_LEN] = ""; int pos = 0, is_keyword = 0; //prepare current gps point here so it is reused for all keywords int64_t video_time_synced = get_current_frame_time_ms(filter, frame) + pdata->gps_offset; int i_now = binary_search_gps(filter_to_gps_data(filter), video_time_synced); int max_gps_diff_ms = get_max_gps_diff_ms(filter_to_gps_data(filter)); gps_point_proc crt_point = uninit_gps_proc_point; if (i_now != -1 && pdata->gps_points_p && time_val_between_indices_proc(video_time_synced, pdata->gps_points_p, i_now, pdata->gps_points_size - 1, max_gps_diff_ms, false)) crt_point = weighted_middle_point_proc(&pdata->gps_points_p[i_now], &pdata->gps_points_p[i_now + 1], video_time_synced, max_gps_diff_ms); while (get_next_token(value, &pos, keyword, &is_keyword)) { if (!is_keyword) { strncat(result, keyword, MAX_TEXT_LEN - strlen(result) - 1); } else if (!strncmp(keyword, "gps_", strlen("gps_"))) { gps_point_to_output(filter, keyword, result, i_now, video_time_synced, crt_point); } else if (!strncmp(keyword, "file_datetime_now", strlen("file_datetime_now"))) { get_current_frame_time_str(keyword, filter, frame, result); } else { // replace keyword with property value from this frame mlt_properties frame_properties = MLT_FRAME_PROPERTIES(frame); char *frame_value = mlt_properties_get(frame_properties, keyword); if (frame_value) { strncat(result, frame_value, MAX_TEXT_LEN - strlen(result) - 1); } } } } //checks if a new file is selected and if so, does private_data cleanup and calls xml_parse_file static void process_file(mlt_filter filter, mlt_frame frame) { private_data *pdata = (private_data *) filter->child; mlt_properties properties = MLT_FILTER_PROPERTIES(filter); char *filename = mlt_properties_get(properties, "resource"); if (filename == NULL) filename = mlt_properties_get(properties, "gps.file"); /* for backwards compatibility with v1 */ bool guess_offset = (mlt_properties_get_int(properties, "time_offset") == 0) && (strlen(pdata->last_filename) == 0); //if there's no file selected just return if (!filename || !strcmp(filename, "")) return; //check if the file has been changed, if not, current data is ok, do nothing if (strcmp(pdata->last_filename, filename)) { // mlt_log_info(filter, "Reading new file: last_filename (%s) != entered_filename (%s), swap_180 = %d\n", pdata->last_filename, filename, swap); default_priv_data(pdata); strcpy(pdata->last_filename, filename); if (qxml_parse_file(filter_to_gps_data(filter)) == 1) { get_first_gps_time(filter_to_gps_data(filter)); get_last_gps_time(filter_to_gps_data(filter)); //when loading the first file, sync gps start with video start int64_t original_video_time = get_original_video_file_time_mseconds(frame); if (guess_offset) { pdata->gps_offset = pdata->first_gps_time - original_video_time; mlt_properties_set_int(properties, "time_offset", pdata->gps_offset / 1000); } //assume smooth is 5 (default) so we can guarantee *gps_points_p and save some time pdata->last_smooth_lvl = 5; process_gps_smoothing(filter_to_gps_data(filter), 1); } else { default_priv_data(pdata); //store name in pdata or it'll retry reading every frame if bad file strcpy(pdata->last_filename, filename); } } } /** Filter processing. */ static mlt_frame filter_process(mlt_filter filter, mlt_frame frame) { mlt_properties properties = MLT_FILTER_PROPERTIES(filter); char *dynamic_text = mlt_properties_get(properties, "argument"); if (!dynamic_text || !strcmp("", dynamic_text)) return frame; mlt_filter text_filter = (mlt_filter) mlt_properties_get_data(properties, "_text_filter", NULL); mlt_properties text_filter_properties = mlt_frame_unique_properties(frame, MLT_FILTER_SERVICE(text_filter)); //read and process file if needed process_file(filter, frame); process_filter_properties(filter, frame); // Apply keyword substitution before passing the text to the filter. char *result = (char *) calloc(1, MAX_TEXT_LEN); substitute_keywords(filter, result, dynamic_text, frame); mlt_properties_set_string(text_filter_properties, "argument", result); free(result); mlt_properties_pass_list(text_filter_properties, properties, "geometry family size weight style fgcolour bgcolour olcolour pad " "halign valign outline opacity"); mlt_filter_set_in_and_out(text_filter, mlt_filter_get_in(filter), mlt_filter_get_out(filter)); return mlt_filter_process(text_filter, frame); } /** Destructor for the filter. */ static void filter_close(mlt_filter filter) { private_data *pdata = (private_data *) filter->child; default_priv_data(pdata); free(pdata); filter->child = NULL; filter->close = NULL; filter->parent.close = NULL; mlt_service_close(&filter->parent); } /** Constructor for the filter. */ extern "C" { mlt_filter filter_gpstext_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { (void) type; // unused (void) id; // unused mlt_filter filter = mlt_filter_new(); private_data *pdata = (private_data *) calloc(1, sizeof(private_data)); default_priv_data(pdata); mlt_filter text_filter = mlt_factory_filter(profile, "qtext", NULL); if (!text_filter) text_filter = mlt_factory_filter(profile, "text", NULL); if (!text_filter) mlt_log_warning(MLT_FILTER_SERVICE(filter), "Unable to create text filter.\n"); if (filter && text_filter && pdata) { mlt_properties my_properties = MLT_FILTER_PROPERTIES(filter); // Register the text filter for reuse/destruction mlt_properties_set_data(my_properties, "_text_filter", text_filter, 0, (mlt_destructor) mlt_filter_close, NULL); // Assign default values mlt_properties_set_string(my_properties, "argument", arg ? arg : "Speed: #gps_speed#km/h\n" "Distance: #gps_dist#m\n" "Altitude: #gps_elev#m\n\n" "GPS time: #gps_datetime_now# UTC\n" "GPS location: #gps_lat#, #gps_lon#"); mlt_properties_set_string(my_properties, "geometry", "10%/10%:80%x80%:100%"); mlt_properties_set_string(my_properties, "family", "Sans"); mlt_properties_set_string(my_properties, "size", "26"); mlt_properties_set_string(my_properties, "weight", "400"); mlt_properties_set_string(my_properties, "style", "normal"); mlt_properties_set_string(my_properties, "fgcolour", "0xffffffff"); mlt_properties_set_string(my_properties, "bgcolour", "0x00000000"); mlt_properties_set_string(my_properties, "olcolour", "0x000000ff"); mlt_properties_set_string(my_properties, "pad", "5"); mlt_properties_set_string(my_properties, "halign", "left"); mlt_properties_set_string(my_properties, "valign", "bottom"); mlt_properties_set_string(my_properties, "outline", "0"); mlt_properties_set_string(my_properties, "opacity", "1.0"); mlt_properties_set_int(my_properties, "_filter_private", 1); mlt_properties_set_int(my_properties, "time_offset", 0); mlt_properties_set_int(my_properties, "smoothing_value", 5); mlt_properties_set_int(my_properties, "speed_multiplier", 1); mlt_properties_set_int(my_properties, "updates_per_second", 1); filter->close = filter_close; filter->process = filter_process; filter->child = pdata; } else { if (filter) { mlt_filter_close(filter); } if (text_filter) { mlt_filter_close(text_filter); } free(pdata); filter = NULL; } return filter; } } mlt-7.22.0/src/modules/qt/filter_gpstext.yml000664 000000 000000 00000024734 14531534050 021041 0ustar00rootroot000000 000000 schema_version: 7.0 type: filter identifier: gpstext title: GPS Text version: 4 copyright: Meltytech, LLC creator: Daniel F license: LGPLv2.1 language: en tags: - Video description: Overlay GPS-related text onto the video notes: > The GPS text filter will search for keywords in the text to be overlaid and will replace those keywords on a frame-by-frame basis. It is based on dynamictext filter. parameters: - identifier: argument argument: yes title: GPS text type: string description: | The text to overlay. May include keywords enclosed in "#". Keywords include: * #gps_lat# - the GPS latitude value * #gps_lon# - the GPS longitude value * #gps_elev# - the GPS altitude value * #gps_speed# - GPS speed * #gps_dist# - total distance so far * #gps_datetime_now# - date and time of current gps point shown * #file_datetime_now# - date and time of current frame in video file * #hr# - heart rate (if present in gps file) * #gps_bearing# - current GPS bearing (degrees 0-360) * #gps_compass# - current GPS direction (8 divisions: N, NE, E, etc) * #gps_vdist_up# - total GPS altitude gain so far * #gps_vdist_down# - total GPS altitude loss so far * #gps_dist_uphill# - total distance travelled uphill so far * #gps_dist_downhill# - total distance travelled downhill so far * #gps_dist_flat# - total distance travelled on flat area so far * #gps_cadence# - (bike) cadence, in revolutions per minute * #gps_temperature# - ambient temperature ( in .gpx) * #gps_grade_percentage# - gradient of GPS track (computed from GPS and elev) * #gps_grade_degrees# - same as above but converted to degrees An extra word "RAW" (uppercase!) can be added to the keyword to display the unchanged value from the file. If a keyword can't produce valid data it will print "--". Time-based keywords can include a strftime format string to customize the output and a number (representing seconds) preceeded by '+' or '-' which will offset the actual time. For example, "#gps_datetime_now %I:%M:%S %p +3600#" shows only the time in 12-hour format, offset by 1 hour. The speed keyword can have an extra "vertical" or "3D" word as a format to show vertical speed, and 3D speed (takes into account altitude). Speed and distance keywords may include an extra format keyword to convert the value to metric/imperial units. Default is meters and km/h respectively. Supported formats, distance: m|meter, km|kilometer*, mi|mile*, ft|feet, nm|nautical*; speed: ms|m/s|meter, km|km/h|kilo, mi|mi/h|mile, ft|ft/s|feet, kn|nm/h|knots, mmin|m/min, ftmin|ft/min. Computed values are calculated since beginning of GPS file or since "gps_processing_start_time" property, if set. Temperature can include the extra uppercase letter "F" or "K" to convert degrees Celsius (default) to Fahrenheit or Kelvin respectively. The # may be escaped with "\". required: yes readonly: no default: > Speed: #gps_speed#km/h\n Distance: #gps_dist#m\n Altitude: #gps_elev#m\n\n GPS time: #gps_datetime_now#\n GPS location: #gps_lat#, #gps_lon# widget: text - identifier: resource title: GPS File/URL description: > "The fullpath of file containing location (GPS) data. Supported extensions: .gpx, .tcx." type: string required: yes readonly: no widget: fileopen - identifier: time_offset title: Time offset type: integer description: > An offset (in seconds) to be added to the video file to match it to the GPS track. Most of the time this will at least need to be set to the timezone difference between the 2 files plus some seconds if the video recording device isn't precisely set to correct time. GPS time is always exact and in UTC. Use positive values if GPS is ahead of video and negative otherwise. default: 0 readonly: no required: no mutable: yes widget: text - identifier: smoothing_value title: Smoothing level type: integer description: > How many GPS points to smooth in order to eliminate GPS errors. A value of 0 only exposes the raw values from file. A value of 1 does not smooth locations, it only calculates the extra fields (speed, distance, etc) it also interpolates missing values for heart rate and altitude. default: 5 minimum: 0 readonly: no required: no mutable: yes widget: text - identifier: gps_processing_start_time title: GPS processing start time type: string description: > A UTC date and time (formatted as "yyyy-MM-dd hh:mm:ss") from which to start processing the gps file. Use if the GPS file has extra points before the video and you want to ignore them (can be used to track the distance since beginning of a segment). If the string provided doesn't perfectly match the format, it will be ignored. default: "yyyy-MM-dd hh:mm:ss" readonly: no required: no mutable: yes widget: text - identifier: speed_multiplier title: Speed multiplier type: float description: > If the video file is a timelapse (or slow motion), use this value to set the speed up/slow down ratio. Sample values: 30 for 30x timelapse, 0.25 for 4x slow motion footage. default: 1 readonly: no required: no mutable: yes widget: text - identifier: updates_per_second title: Updates per second type: float description: > Controls how many times per second is the GPS text updated on video (interpolated). A value of 0 will only update text when a real gps point has been recorded. Can be used in combination with speed_multiplier. default: 1 readonly: no required: no mutable: yes widget: text # READ ONLY: - identifier: gps_start_text title: GPS start time description: Date and time of the first valid GPS point. readonly: yes - identifier: video_start_text title: Video start time description: Date and time of the video file. readonly: yes - identifier: auto_gps_offset_start title: Auto offset start description: > Provides a helper offset to guarantee start of video file syncs with the start of gps file. Correctly sets the offset if video file and gps recording was started at the same time. readonly: yes - identifier: auto_gps_offset_now title: Auto offset now description: > Provides a helper offset to sync the first gps point to current video time (it is updated every second). Correctly sets the offset if you video record the moment gps starts. readonly: yes - identifier: auto_gps_processing_start_now title: Auto gps processing start now description: > Provides a helper offset to sync the gps_processing_start_time property to current video time (it is updated every second). DateTime string. readonly: yes - identifier: geometry title: Geometry type: rect description: A set of X/Y coordinates by which to adjust the text. default: 10%/10%:80%x80%:100% - identifier: family title: Font family type: string description: > The typeface of the font. default: Sans readonly: no mutable: yes widget: combo - identifier: size title: Font size type: integer description: > The size in pixels of the font. default: 26 readonly: no mutable: yes widget: spinner - identifier: style title: Font style type: string description: > The style of the font. values: - normal - italic default: normal readonly: no mutable: yes widget: combo - identifier: weight title: Font weight type: integer description: The weight of the font. minimum: 100 maximum: 1000 default: 400 readonly: no mutable: yes widget: spinner - identifier: fgcolour title: Foreground color type: string description: > A color value is a hexadecimal representation of RGB plus alpha channel as 0xrrggbbaa. Colors can also be the words: white, black, red, green, or blue. You can also use a HTML-style color values #rrggbb or #aarrggbb. default: 0xffffffff readonly: no mutable: yes widget: color - identifier: bgcolour title: Background color type: string description: > A color value is a hexadecimal representation of RGB plus alpha channel as 0xrrggbbaa. Colors can also be the words: white, black, red, green, or blue. You can also use a HTML-style color values #rrggbb or #aarrggbb. default: 0x00000000 readonly: no mutable: yes widget: color - identifier: olcolour title: Outline color type: string description: > A color value is a hexadecimal representation of RGB plus alpha channel as 0xrrggbbaa. Colors can also be the words: white, black, red, green, or blue. You can also use a HTML-style color values #rrggbb or #aarrggbb. readonly: no mutable: yes widget: color - identifier: outline title: Outline Width type: string description: > The width of the outline in pixels. readonly: no default: 0 minimum: 0 maximum: 3 mutable: yes widget: spinner - identifier: pad title: Padding type: integer description: > The number of pixels to pad the background rectangle beyond edges of text. readonly: no default: 0 mutable: yes widget: spinner - identifier: halign title: Horizontal alignment description: > Set the horizontal alignment within the geometry rectangle. type: string default: left values: - left - centre - right mutable: yes widget: combo - identifier: valign title: Vertical alignment description: > Set the vertical alignment within the geometry rectangle. type: string default: bottom values: - top - middle - bottom mutable: yes widget: combo - identifier: opacity title: Opacity type: float description: Opacity of all elements - text, outline, and background readonly: no default: 1.0 minimum: 0 maximum: 1.0 mutable: yes widget: slider mlt-7.22.0/src/modules/qt/filter_lightshow.cpp000664 000000 000000 00000030213 14531534050 021321 0ustar00rootroot000000 000000 /* * filter_lightshow.cpp -- animate color to the audio * Copyright (C) 2015-2020 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "common.h" #include #include // sin() #include // calloc(), free() #include #include // Private Constants static const double PI = 3.14159265358979323846; namespace { // Private Types typedef struct { mlt_filter fft; char *mag_prop_name; int rel_pos; int preprocess_warned; } private_data; } // namespace static int filter_get_audio(mlt_frame frame, void **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples) { mlt_filter filter = (mlt_filter) mlt_frame_pop_audio(frame); mlt_properties filter_properties = MLT_FILTER_PROPERTIES(filter); private_data *pdata = (private_data *) filter->child; // Create the FFT filter the first time. if (!pdata->fft) { mlt_profile profile = mlt_service_profile(MLT_FILTER_SERVICE(filter)); pdata->fft = mlt_factory_filter(profile, "fft", NULL); mlt_properties_set_int(MLT_FILTER_PROPERTIES(pdata->fft), "window_size", mlt_properties_get_int(filter_properties, "window_size")); if (!pdata->fft) { mlt_log_warning(MLT_FILTER_SERVICE(filter), "Unable to create FFT.\n"); return 1; } } mlt_properties fft_properties = MLT_FILTER_PROPERTIES(pdata->fft); double low_freq = mlt_properties_get_int(filter_properties, "frequency_low"); double hi_freq = mlt_properties_get_int(filter_properties, "frequency_high"); double threshold = mlt_properties_get_int(filter_properties, "threshold"); double osc = mlt_properties_get_int(filter_properties, "osc"); float peak = 0; // The service must stay locked while using the private data mlt_service_lock(MLT_FILTER_SERVICE(filter)); // Perform FFT processing on the frame mlt_filter_process(pdata->fft, frame); mlt_frame_get_audio(frame, buffer, format, frequency, channels, samples); float *bins = (float *) mlt_properties_get_data(fft_properties, "bins", NULL); double window_level = mlt_properties_get_double(fft_properties, "window_level"); if (bins && window_level == 1.0) { // Find the peak FFT magnitude in the configured range of frequencies int bin_count = mlt_properties_get_int(fft_properties, "bin_count"); double bin_width = mlt_properties_get_double(fft_properties, "bin_width"); int bin = 0; for (bin = 0; bin < bin_count; bin++) { double F = bin_width * (double) bin; if (F >= low_freq && F <= hi_freq) { if (bins[bin] > peak) { peak = bins[bin]; } } } } mlt_service_unlock(MLT_FILTER_SERVICE(filter)); // Scale the magnitude to dB and apply oscillation double dB = peak > 0.0 ? 20 * log10(peak) : -1000.0; double mag = 0.0; if (dB >= threshold) { // Scale to range 0.0-1.0 mag = 1 - (dB / threshold); if (osc != 0) { // Apply the oscillation double fps = mlt_profile_fps(mlt_service_profile(MLT_FILTER_SERVICE(filter))); double t = pdata->rel_pos / fps; mag = mag * sin(2 * PI * osc * t); } pdata->rel_pos++; } else { pdata->rel_pos = 1; mag = 0; } // Save the magnitude as a property on the frame to be used in get_image() mlt_properties_set_double(MLT_FRAME_PROPERTIES(frame), pdata->mag_prop_name, mag); return 0; } static void setup_pen(QPainter &p, QRect &rect, mlt_properties filter_properties) { QVector colors; bool color_found = true; // Find user specified colors for the gradient while (color_found) { QString prop_name = QString("color.") + QString::number(colors.size() + 1); if (mlt_properties_exists(filter_properties, prop_name.toUtf8().constData())) { mlt_color mcolor = mlt_properties_get_color(filter_properties, prop_name.toUtf8().constData()); colors.append(QColor(mcolor.r, mcolor.g, mcolor.b, mcolor.a)); } else { color_found = false; } } if (!colors.size()) { // No color specified. Just use white. p.setBrush(Qt::white); } else if (colors.size() == 1) { // Only use one color p.setBrush(colors[0]); } else { // Use Gradient qreal sx = 1.0; qreal sy = 1.0; qreal dx = rect.x(); qreal dy = rect.y(); qreal radius = rect.width() / 2; if (rect.width() > rect.height()) { radius = rect.height() / 2; sx = (qreal) rect.width() / (qreal) rect.height(); } else if (rect.height() > rect.width()) { radius = rect.width() / 2; sy = (qreal) rect.height() / (qreal) rect.width(); } QPointF center(radius, radius); QRadialGradient gradient(center, radius); qreal step = 1.0 / (colors.size() - 1); for (int i = 0; i < colors.size(); i++) { gradient.setColorAt((qreal) i * step, colors[i]); } QBrush brush(gradient); QTransform transform(sx, 0.0, 0.0, 0.0, sy, 0.0, dx, dy, 1.0); brush.setTransform(transform); p.setBrush(brush); } p.setPen(QColor(0, 0, 0, 0)); // Clear pen } static void draw_light(mlt_properties filter_properties, QImage *qimg, mlt_rect *rect, double mag) { QPainter p(qimg); QRect r(rect->x, rect->y, rect->w, rect->h); p.setRenderHint(QPainter::Antialiasing); // Output transparency = input transparency p.setCompositionMode(QPainter::CompositionMode_SourceAtop); setup_pen(p, r, filter_properties); p.setOpacity(mag); p.drawRect(r); p.end(); } /** Get the image. */ static int filter_get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { int error = 0; mlt_filter filter = (mlt_filter) mlt_frame_pop_service(frame); private_data *pdata = (private_data *) filter->child; mlt_properties filter_properties = MLT_FILTER_PROPERTIES(filter); mlt_properties frame_properties = MLT_FRAME_PROPERTIES(frame); if (mlt_properties_exists(frame_properties, pdata->mag_prop_name)) { double mag = mlt_properties_get_double(frame_properties, pdata->mag_prop_name); mlt_position position = mlt_filter_get_position(filter, frame); mlt_position length = mlt_filter_get_length2(filter, frame); mlt_rect rect = mlt_properties_anim_get_rect(filter_properties, "rect", position, length); // Get the current image *format = mlt_image_rgba; error = mlt_frame_get_image(frame, image, format, width, height, 1); if (strchr(mlt_properties_get(filter_properties, "rect"), '%')) { rect.x *= *width; rect.w *= *width; rect.y *= *height; rect.h *= *height; } else { mlt_profile profile = mlt_service_profile(MLT_FILTER_SERVICE(filter)); double scale = mlt_profile_scale_width(profile, *width); rect.x *= scale; rect.w *= scale; scale = mlt_profile_scale_height(profile, *height); rect.y *= scale; rect.h *= scale; } // Draw the light if (!error) { QImage qimg(*width, *height, QImage::Format_ARGB32); convert_mlt_to_qimage_rgba(*image, &qimg, *width, *height); draw_light(filter_properties, &qimg, &rect, mag); convert_qimage_to_mlt_rgba(&qimg, *image, *width, *height); } } else { if (pdata->preprocess_warned++ == 2) { // This filter depends on the consumer processing the audio before // the video. mlt_log_warning(MLT_FILTER_SERVICE(filter), "Audio not preprocessed.\n"); } mlt_frame_get_image(frame, image, format, width, height, writable); } return error; } /** Filter processing. */ static mlt_frame filter_process(mlt_filter filter, mlt_frame frame) { if (mlt_frame_is_test_card(frame)) { // The producer does not generate video. This filter will create an // image on the producer's behalf. mlt_properties frame_properties = MLT_FRAME_PROPERTIES(frame); mlt_profile profile = mlt_service_profile(MLT_FILTER_SERVICE(filter)); mlt_properties_set_int(frame_properties, "progressive", 1); mlt_properties_set_double(frame_properties, "aspect_ratio", mlt_profile_sar(profile)); mlt_properties_set_int(frame_properties, "meta.media.width", profile->width); mlt_properties_set_int(frame_properties, "meta.media.height", profile->height); // Tell the framework that there really is an image. mlt_properties_set_int(frame_properties, "test_image", 0); // Push a callback to create the image. mlt_frame_push_get_image(frame, create_image); } mlt_frame_push_audio(frame, filter); mlt_frame_push_audio(frame, (void *) filter_get_audio); mlt_frame_push_service(frame, filter); mlt_frame_push_get_image(frame, filter_get_image); return frame; } static void filter_close(mlt_filter filter) { private_data *pdata = (private_data *) filter->child; if (pdata) { mlt_filter_close(pdata->fft); free(pdata->mag_prop_name); free(pdata); } filter->child = NULL; filter->close = NULL; filter->parent.close = NULL; mlt_service_close(&filter->parent); } /** Constructor for the filter. */ extern "C" { mlt_filter filter_lightshow_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = mlt_filter_new(); private_data *pdata = (private_data *) calloc(1, sizeof(private_data)); if (filter && pdata && createQApplicationIfNeeded(MLT_FILTER_SERVICE(filter))) { mlt_properties properties = MLT_FILTER_PROPERTIES(filter); mlt_properties_set_int(properties, "_filter_private", 1); mlt_properties_set_int(properties, "frequency_low", 20); mlt_properties_set_int(properties, "frequency_high", 20000); mlt_properties_set_double(properties, "threshold", -30.0); mlt_properties_set_double(properties, "osc", 5.0); mlt_properties_set(properties, "color.1", "0xffffffff"); mlt_properties_set(properties, "rect", "0% 0% 100% 100%"); mlt_properties_set_int(properties, "window_size", 2048); // Create a unique ID for storing data on the frame pdata->mag_prop_name = (char *) calloc(1, 20); snprintf(pdata->mag_prop_name, 20, "fft_mag.%p", filter); pdata->mag_prop_name[20 - 1] = '\0'; pdata->fft = 0; filter->close = filter_close; filter->process = filter_process; filter->child = pdata; } else { mlt_log_error(MLT_FILTER_SERVICE(filter), "Filter lightshow failed\n"); if (filter) { mlt_filter_close(filter); } if (pdata) { free(pdata); } filter = NULL; } return filter; } } mlt-7.22.0/src/modules/qt/filter_lightshow.yml000664 000000 000000 00000006307 14531534050 021347 0ustar00rootroot000000 000000 schema_version: 7.0 type: filter identifier: lightshow title: Light Show version: 1 copyright: Meltytech, LLC license: LGPLv2.1 language: en tags: - Video description: > An audio visualization filter that colors the image proportional to the magnitude of the audio spectrum. parameters: - identifier: frequency_low title: Low Frequency type: integer description: > The low end of the frequency range to be used to influence the image motion. mutable: yes readonly: no default: 20 unit: Hz - identifier: frequency_high title: High Frequency type: integer description: > The high end of the frequency range to be used to influence the image motion. mutable: yes readonly: no default: 20000 unit: Hz - identifier: threshold title: Level Threshold type: float description: > The minimum amplitude of sound that must occur within the frequency range to cause the image to move. motion. mutable: yes readonly: no default: -30 minimum: -100 maximum: 0 unit: dB - identifier: osc title: Oscillation type: float description: > Oscillation can be useful to make the image move back and forth during long periods of sound. A value of 0 specifies no oscillation. mutable: yes readonly: no default: 5 minimum: 0 unit: Hz - identifier: color.* title: Light Color type: color description: | The color of the light. Multiple colors can be specified with incrementing suffixes to cause the waveform to be drawn in a circular gradient. color.1 is the inside of the circle. Subsequent colors will produce a gradient toward the outside. By default, the filter has one color defined: color.1=0xffffffff" This results in the image being lightened. To create a gradient, define more colors: color.2=green color.3=0x77777777 color.4=0x00000000 A color value is a hexadecimal representation of RGB plus alpha channel as 0xrrggbbaa. Colors can also be the words: white, black, red, green, or blue. You can also use a HTML-style color values #rrggbb or #aarrggbb. readonly: no mutable: yes widget: color - identifier: rect title: Rectangle description: > Defines the rectangle that the color should be drawn in. Format is: "X Y W H". X, Y, W, H are assumed to be pixel units unless they have the suffix '%'. type: rect default: "0 0 100% 100%" readonly: no mutable: yes animation: yes - identifier: window_size title: Window Size type: integer description: > The number of samples that the FFT will be performed on. If window_size is less than the number of samples in a frame, extra samples will be ignored. If window_size is more than the number of samples in a frame, samples will be buffered from previous frames to fill the window. The buffering is performed as a sliding window so that the most recent samples are always transformed. mutable: no readonly: no default: 2048 mlt-7.22.0/src/modules/qt/filter_qtblend.cpp000664 000000 000000 00000020425 14531534050 020746 0ustar00rootroot000000 000000 /* * filter_lightshow.cpp -- animate color to the audio * Copyright (C) 2015 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "common.h" #include #include // sin() #include // calloc(), free() #include // strchr() #include #include #include /** Get the image. */ static int filter_get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { int error = 0; // Get the filter mlt_filter filter = (mlt_filter) mlt_frame_pop_service(frame); // Get the properties mlt_properties properties = MLT_FILTER_PROPERTIES(filter); mlt_properties frame_properties = MLT_FRAME_PROPERTIES(frame); bool hasAlpha = false; // Only process if we have no error and a valid colour space mlt_service_lock(MLT_FILTER_SERVICE(filter)); mlt_profile profile = mlt_service_profile(MLT_FILTER_SERVICE(filter)); mlt_position position = mlt_filter_get_position(filter, frame); mlt_position length = mlt_filter_get_length2(filter, frame); mlt_service_unlock(MLT_FILTER_SERVICE(filter)); // Check transform QTransform transform; int normalized_width = profile->width; int normalized_height = profile->height; double consumer_ar = mlt_profile_sar(profile); // Destination rect mlt_rect rect = {0, 0, normalized_width * mlt_profile_scale_width(profile, *width), normalized_height * mlt_profile_scale_height(profile, *height), 1.0}; int b_width = mlt_properties_get_int(frame_properties, "meta.media.width"); int b_height = mlt_properties_get_int(frame_properties, "meta.media.height"); if (b_height == 0) { b_width = normalized_width; b_height = normalized_height; } // Special case - aspect_ratio = 0 if (mlt_frame_get_aspect_ratio(frame) == 0) { double output_ar = mlt_profile_sar(profile); mlt_frame_set_aspect_ratio(frame, output_ar); } double b_ar = mlt_frame_get_aspect_ratio(frame); double b_dar = b_ar * b_width / b_height; double opacity = 1.0; if (mlt_properties_get(properties, "rect")) { rect = mlt_properties_anim_get_rect(properties, "rect", position, length); if (::strchr(mlt_properties_get(properties, "rect"), '%')) { rect.x *= normalized_width; rect.y *= normalized_height; rect.w *= normalized_width; rect.h *= normalized_height; } double scale = mlt_profile_scale_width(profile, *width); if (scale != 1.0) { rect.x *= scale; rect.w *= scale; } scale = mlt_profile_scale_height(profile, *height); if (scale != 1.0) { rect.y *= scale; rect.h *= scale; } transform.translate(rect.x, rect.y); opacity = rect.o; hasAlpha = rect.o < 1 || rect.x != 0 || rect.y != 0 || rect.w != *width || rect.h != *height; if (mlt_properties_get_int(properties, "distort") == 0) { b_height = qMax(1, qMin((int) rect.h, b_height)); b_width = qMax(1, int(b_height * b_dar / b_ar / consumer_ar)); } else { b_width = qMax(1, int(b_width * b_ar / consumer_ar)); } if (!hasAlpha && (b_width < *width || b_height < *height)) { hasAlpha = true; } } else { b_width = *width; b_height = *height; if (b_width < normalized_width || b_height < normalized_height) { hasAlpha = true; } } if (mlt_properties_get(properties, "rotation")) { double angle = mlt_properties_anim_get_double(properties, "rotation", position, length); if (angle != 0.0) { if (mlt_properties_get_int(properties, "rotate_center")) { transform.translate(rect.w / 2.0, rect.h / 2.0); transform.rotate(angle); transform.translate(-rect.w / 2.0, -rect.h / 2.0); } else { // old style rotation (from top left corner) to keep compatibility transform.rotate(angle); } hasAlpha = true; } } if (!hasAlpha && mlt_properties_get_int(properties, "compositing") != 0) { hasAlpha = true; } if (!hasAlpha) { uint8_t *src_image = NULL; error = mlt_frame_get_image(frame, &src_image, format, &b_width, &b_height, 0); if (*format == mlt_image_rgba || mlt_frame_get_alpha(frame)) { hasAlpha = true; } else { // Prepare output image *image = src_image; *width = b_width; *height = b_height; return 0; } } // fetch image *format = mlt_image_rgba; uint8_t *src_image = NULL; error = mlt_frame_get_image(frame, &src_image, format, &b_width, &b_height, 0); // Put source buffer into QImage QImage sourceImage; convert_mlt_to_qimage_rgba(src_image, &sourceImage, b_width, b_height); int image_size = mlt_image_format_size(*format, *width, *height, NULL); // resize to rect if (mlt_properties_get_int(properties, "distort")) { transform.scale(rect.w / b_width, rect.h / b_height); } else { // Determine scale with respect to aspect ratio. double geometry_dar = rect.w * consumer_ar / rect.h; double scale; if (b_dar > geometry_dar) { scale = rect.w / b_width; } else { scale = rect.h / b_height * b_ar; } // Center image in rect transform.translate((rect.w - (b_width * scale)) / 2.0, (rect.h - (b_height * scale)) / 2.0); transform.scale(scale, scale); } uint8_t *dest_image = NULL; dest_image = (uint8_t *) mlt_pool_alloc(image_size); QImage destImage; convert_mlt_to_qimage_rgba(dest_image, &destImage, *width, *height); destImage.fill(mlt_properties_get_int(properties, "background_color")); QPainter painter(&destImage); painter.setCompositionMode( (QPainter::CompositionMode) mlt_properties_get_int(properties, "compositing")); painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform); painter.setTransform(transform); painter.setOpacity(opacity); // Composite top frame painter.drawImage(0, 0, sourceImage); // finish Qt drawing painter.end(); convert_qimage_to_mlt_rgba(&destImage, dest_image, *width, *height); *image = dest_image; mlt_frame_set_image(frame, *image, *width * *height * 4, mlt_pool_release); return error; } /** Filter processing. */ static mlt_frame filter_process(mlt_filter filter, mlt_frame frame) { mlt_frame_push_service(frame, filter); mlt_frame_push_get_image(frame, filter_get_image); return frame; } /** Constructor for the filter. */ extern "C" { mlt_filter filter_qtblend_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = mlt_filter_new(); if (filter && createQApplicationIfNeeded(MLT_FILTER_SERVICE(filter))) { filter->process = filter_process; mlt_properties properties = MLT_FILTER_PROPERTIES(filter); mlt_properties_set_int(properties, "rotate_center", 0); } else { mlt_log_error(MLT_FILTER_SERVICE(filter), "Filter qtblend failed\n"); if (filter) { mlt_filter_close(filter); } filter = NULL; } return filter; } } mlt-7.22.0/src/modules/qt/filter_qtblend.yml000664 000000 000000 00000002727 14531534050 020772 0ustar00rootroot000000 000000 schema_version: 7.0 type: filter identifier: qtblend title: Composite and transform version: 2 copyright: Meltytech, LLC creator: Jean-Baptiste Mardelle license: LGPLv2.1 language: en tags: - Video description: > A filter allowing compositing and transform. parameters: - identifier: rect title: Rectangle type: rect description: > Keyframable rectangle specification. mutable: yes animation: yes - identifier: compositing title: Composition mode description: > Defines which composition operation will be performed (see QPainter CompositionMode for doc). type: integer default: 0 minimum: 0 maximum: 40 mutable: yes widget: spinner - identifier: rotation title: Rotation angle description: > Angle for rotation. type: float default: 1 minimum: 0 maximum: 360 mutable: yes animation: yes widget: spinner unit: degrees - identifier: rotate_center title: Rotate from center type: integer description: > Process the rotation from center if set, otherwise from top left corner minimum: 0 maximum: 1 mutable: yes widget: checkbox - identifier: background_color title: Background color type: integer description: > An integer formed like 0xaabbggrr that will be used as background color for the compositing operation. Defaults to 0 (fully transparent) default: 0 mutable: yes widget: color mlt-7.22.0/src/modules/qt/filter_qtcrop.cpp000664 000000 000000 00000011215 14531534050 020622 0ustar00rootroot000000 000000 /* * filter_qtcrop.cpp -- cropping filter * Copyright (c) 2020 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "common.h" #include "math.h" #include #include #include #include #include #include static int get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { int error = 0; mlt_filter filter = (mlt_filter) mlt_frame_pop_service(frame); mlt_properties properties = MLT_FILTER_PROPERTIES(filter); mlt_profile profile = mlt_service_profile(MLT_FILTER_SERVICE(filter)); mlt_position position = mlt_filter_get_position(filter, frame); mlt_position length = mlt_filter_get_length2(filter, frame); mlt_rect rect = mlt_properties_anim_get_rect(properties, "rect", position, length); // Get the current image *format = mlt_image_rgba; mlt_properties_set_int(MLT_FRAME_PROPERTIES(frame), "resize_alpha", 255); error = mlt_frame_get_image(frame, image, format, width, height, writable); if (!error && *format == mlt_image_rgba) { QImage bgImage; convert_mlt_to_qimage_rgba(*image, &bgImage, *width, *height); QImage fgImage = bgImage.copy(); QPainter painter(&bgImage); QPainterPath path; mlt_color color = mlt_properties_anim_get_color(properties, "color", position, length); double radius = mlt_properties_anim_get_double(properties, "radius", position, length); painter.setRenderHints(QPainter::Antialiasing #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) | QPainter::HighQualityAntialiasing #endif ); bgImage.fill(QColor(color.r, color.g, color.b, color.a)); if (mlt_properties_get_int(properties, "circle")) { QPointF center(double(*width) / 2.0, double(*height) / 2.0); double hypotenuse = sqrt(pow(*width, 2) + pow(*height, 2)); radius *= 0.5 * hypotenuse; path.addEllipse(center, radius, radius); } else { const char *s = mlt_properties_get(properties, "rect"); if (qstrlen(s) > 0 && strchr(s, '%')) { rect.x *= *width; rect.w *= *width; rect.y *= *height; rect.h *= *height; } else { double scale = mlt_profile_scale_width(profile, *width); double scale_height = mlt_profile_scale_height(profile, *height); rect.x *= scale; rect.y *= scale_height; rect.w *= scale; rect.h *= scale_height; } radius *= 0.5 * MIN(rect.w, rect.h); path.addRoundedRect(rect.x, rect.y, rect.w, rect.h, radius, radius); } painter.setClipPath(path); painter.drawImage(QPointF(0, 0), fgImage); painter.end(); convert_qimage_to_mlt_rgba(&bgImage, *image, *width, *height); } return error; } static mlt_frame process(mlt_filter filter, mlt_frame frame) { mlt_frame_push_service(frame, filter); mlt_frame_push_get_image(frame, get_image); return frame; } extern "C" { mlt_filter filter_qtcrop_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { Q_UNUSED(profile) Q_UNUSED(type) Q_UNUSED(id) mlt_filter filter = mlt_filter_new(); if (!filter || !createQApplicationIfNeeded(MLT_FILTER_SERVICE(filter))) { mlt_filter_close(filter); return NULL; } filter->process = process; mlt_properties properties = MLT_FILTER_PROPERTIES(filter); mlt_properties_set_string(properties, "rect", arg ? arg : "0%/0%:100%x100%"); mlt_properties_set_int(properties, "circle", 0); mlt_properties_set_string(properties, "color", "#00000000"); mlt_properties_set_double(properties, "radius", 0.0); return filter; } } mlt-7.22.0/src/modules/qt/filter_qtcrop.yml000664 000000 000000 00000002040 14531534050 020635 0ustar00rootroot000000 000000 schema_version: 7.0 type: filter identifier: qtcrop title: Crop by padding version: 1 copyright: Meltytech, LLC creator: Dan Dennedy license: LGPLv2.1 language: en tags: - Video description: > This filter crops the image to a rounded rectangle or circle by padding it with a color. parameters: - identifier: rect title: Rectangle type: rect description: Keyframable rectangle specification argument: yes default: "0%/0%:100%x100%" mutable: yes animation: yes - identifier: circle title: Use Circle description: Whether to use a circle instead of a rectangle type: boolean default: 0 mutable: yes - identifier: radius title: Radius description: Keyframable amount of circle or rectangle rounding type: float default: 0.0 minimum: 0.0 maximum: 1.0 mutable: yes animation: yes - identifier: color title: Padding color type: color description: The color to use for padding including alpha default: "#00000000" mutable: yes animation: yes mlt-7.22.0/src/modules/qt/filter_qtext.cpp000664 000000 000000 00000043061 14531534050 020463 0ustar00rootroot000000 000000 /* * filter_qtext.cpp -- text overlay filter * Copyright (c) 2018-2021 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "common.h" #include #include #include #include #include #include #include #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) #include #else #include #endif #include static QMutex g_mutex; static QRectF get_text_path(QPainterPath *qpath, mlt_properties filter_properties, const char *text, double scale) { int outline = mlt_properties_get_int(filter_properties, "outline") * scale; char halign = mlt_properties_get(filter_properties, "halign")[0]; char style = mlt_properties_get(filter_properties, "style")[0]; int pad = mlt_properties_get_int(filter_properties, "pad") * scale; int offset = pad + (outline / 2); int width = 0; int height = 0; qpath->setFillRule(Qt::WindingFill); // Get the strings to display QString s = QString::fromUtf8(text); QStringList lines = s.split("\n"); // Configure the font QFont font; font.setPixelSize(mlt_properties_get_int(filter_properties, "size") * scale); font.setFamily(mlt_properties_get(filter_properties, "family")); #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) font.setWeight(QFont::Weight((mlt_properties_get_int(filter_properties, "weight") / 10) - 1)); #else font.setWeight(QFont::Weight(mlt_properties_get_int(filter_properties, "weight"))); #endif switch (style) { case 'i': case 'I': font.setStyle(QFont::StyleItalic); break; } QFontMetrics fm(font); // Determine the text rectangle size height = fm.lineSpacing() * lines.size(); for (int i = 0; i < lines.size(); ++i) { const QString line = lines[i]; #if (QT_VERSION > QT_VERSION_CHECK(5, 11, 0)) int line_width = fm.horizontalAdvance(line); #else int line_width = fm.width(line); #endif int bearing = (line.size() > 0) ? fm.leftBearing(line.at(0)) : 0; if (bearing < 0) line_width -= bearing; bearing = (line.size() > 0) ? fm.rightBearing(line.at(line.size() - 1)) : 0; if (bearing < 0) line_width -= bearing; if (line_width > width) width = line_width; } // Lay out the text in the path int x = 0; int y = fm.ascent() + offset; for (int i = 0; i < lines.size(); ++i) { QString line = lines.at(i); x = offset; #if (QT_VERSION > QT_VERSION_CHECK(5, 11, 0)) int line_width = fm.horizontalAdvance(line); #else int line_width = fm.width(line); #endif int bearing = (line.size() > 0) ? fm.leftBearing(line.at(0)) : 0; if (bearing < 0) { line_width -= bearing; x -= bearing; } bearing = (line.size() > 0) ? fm.rightBearing(line.at(line.size() - 1)) : 0; if (bearing < 0) line_width -= bearing; switch (halign) { default: case 'l': case 'L': break; case 'c': case 'C': x += (width - line_width) / 2; break; case 'r': case 'R': x += width - line_width; break; } qpath->addText(x, y, font, line); y += fm.lineSpacing(); } // Account for outline and pad width += offset * 2; height += offset * 2; // Sanity check if (width == 0) width = 1; height += 2; // I found some fonts whose descenders get cut off. return QRectF(0, 0, width, height); } static QColor get_qcolor(mlt_properties filter_properties, const char *name, int position, int length) { mlt_color color = mlt_properties_anim_get_color(filter_properties, name, position, length); return QColor(color.r, color.g, color.b, color.a); } static QPen get_qpen(mlt_properties filter_properties, int position, int length) { QColor color; int outline = mlt_properties_get_int(filter_properties, "outline"); QPen pen; pen.setWidth(outline); if (outline) { color = get_qcolor(filter_properties, "olcolour", position, length); } else { color = get_qcolor(filter_properties, "bgcolour", position, length); } pen.setColor(color); return pen; } static QBrush get_qbrush(mlt_properties filter_properties, int position, int length) { QColor color = get_qcolor(filter_properties, "fgcolour", position, length); return QBrush(color); } static void transform_painter(QPainter *painter, mlt_rect frame_rect, QRectF path_rect, mlt_properties filter_properties, mlt_profile profile) { qreal sx = 1.0; qreal sy = mlt_profile_sar(profile); qreal path_width = path_rect.width() * sx; if (path_width > frame_rect.w) { sx *= frame_rect.w / path_width; sy *= frame_rect.w / path_width; } qreal path_height = path_rect.height() * sy; if (path_height > frame_rect.h) { sx *= frame_rect.h / path_height; sy *= frame_rect.h / path_height; } qreal dx = frame_rect.x; qreal dy = frame_rect.y; char halign = mlt_properties_get(filter_properties, "halign")[0]; switch (halign) { default: case 'l': case 'L': break; case 'c': case 'C': dx += (frame_rect.w - (sx * path_rect.width())) / 2; break; case 'r': case 'R': dx += frame_rect.w - (sx * path_rect.width()); break; } char valign = mlt_properties_get(filter_properties, "valign")[0]; switch (valign) { default: case 't': case 'T': break; case 'm': case 'M': dy += (frame_rect.h - (sy * path_rect.height())) / 2; break; case 'b': case 'B': dy += frame_rect.h - (sy * path_rect.height()); break; } QTransform transform; transform.translate(dx, dy); transform.scale(sx, sy); painter->setTransform(transform); } static void paint_background( QPainter *painter, QRectF path_rect, mlt_properties filter_properties, int position, int length) { QColor bg_color = get_qcolor(filter_properties, "bgcolour", position, length); painter->fillRect(path_rect, bg_color); } static void paint_text(QPainter *painter, QPainterPath *qpath, mlt_properties filter_properties, int position, int length) { QPen pen = get_qpen(filter_properties, position, length); painter->setPen(pen); QBrush brush = get_qbrush(filter_properties, position, length); painter->setBrush(brush); painter->drawPath(*qpath); } static void close_qtextdoc(void *p) { delete static_cast(p); } static QTextDocument *get_rich_text(mlt_properties properties, double width, double height) { QTextDocument *doc = (QTextDocument *) mlt_properties_get_data(properties, "QTextDocument", NULL); auto html = QString::fromUtf8(mlt_properties_get(properties, "html")); auto prevHtml = QString::fromUtf8(mlt_properties_get(properties, "_html")); auto resource = QString::fromUtf8(mlt_properties_get(properties, "resource")); auto prevResource = QString::fromUtf8(mlt_properties_get(properties, "_resource")); auto prevWidth = mlt_properties_get_double(properties, "_width"); auto prevHeight = mlt_properties_get_double(properties, "_height"); bool changed = !doc || qAbs(width - prevWidth) > 1 || qAbs(height - prevHeight) > 1; if (!resource.isEmpty() && (changed || resource != prevResource)) { QFile file(resource); if (file.open(QFile::ReadOnly)) { QByteArray data = file.readAll(); QTextCodec *codec = QTextCodec::codecForHtml(data); doc = new QTextDocument; doc->setPageSize(QSizeF(width, height)); doc->setHtml(codec->toUnicode(data)); mlt_properties_set_data(properties, "QTextDocument", doc, 0, (mlt_destructor) close_qtextdoc, NULL); mlt_properties_set(properties, "_resource", resource.toUtf8().constData()); mlt_properties_set_double(properties, "_width", width); mlt_properties_set_double(properties, "_height", height); } } else if (!html.isEmpty() && (changed || html != prevHtml)) { // fprintf(stderr, "%s\n", html.toUtf8().constData()); doc = new QTextDocument; doc->setPageSize(QSizeF(width, height)); doc->setHtml(html); mlt_properties_set_data(properties, "QTextDocument", doc, 0, (mlt_destructor) close_qtextdoc, NULL); mlt_properties_set(properties, "_html", html.toUtf8().constData()); mlt_properties_set_double(properties, "_width", width); mlt_properties_set_double(properties, "_height", height); } return doc; } static mlt_properties get_filter_properties(mlt_filter filter, mlt_frame frame) { mlt_properties properties = mlt_frame_get_unique_properties(frame, MLT_FILTER_SERVICE(filter)); if (!properties) properties = MLT_FILTER_PROPERTIES(filter); return properties; } static int filter_get_image(mlt_frame frame, uint8_t **image, mlt_image_format *image_format, int *width, int *height, int writable) { int error = 0; mlt_filter filter = (mlt_filter) mlt_frame_pop_service(frame); char *argument = (char *) mlt_frame_pop_service(frame); mlt_properties filter_properties = get_filter_properties(filter, frame); mlt_profile profile = mlt_service_profile(MLT_FILTER_SERVICE(filter)); mlt_position position = mlt_filter_get_position(filter, frame); mlt_position length = mlt_filter_get_length2(filter, frame); bool isRichText = qstrlen(mlt_properties_get(filter_properties, "html")) > 0 || qstrlen(mlt_properties_get(filter_properties, "resource")) > 0; double opacity = 1.0; if (mlt_properties_exists(filter_properties, "opacity")) { opacity = mlt_properties_anim_get_double(filter_properties, "opacity", position, length); } if (opacity == 0.0) { free(argument); return mlt_frame_get_image(frame, image, image_format, width, height, writable); } QString geom_str = QString::fromLatin1(mlt_properties_get(filter_properties, "geometry")); if (geom_str.isEmpty()) { free(argument); mlt_log_warning(MLT_FILTER_SERVICE(filter), "geometry property not set\n"); return mlt_frame_get_image(frame, image, image_format, width, height, writable); } mlt_rect rect = mlt_properties_anim_get_rect(filter_properties, "geometry", position, length); // Get the current image *image_format = mlt_image_rgba; mlt_properties_set_int(MLT_FRAME_PROPERTIES(frame), "resize_alpha", 255); mlt_service_lock(MLT_FILTER_SERVICE(filter)); error = mlt_frame_get_image(frame, image, image_format, width, height, writable); if (!error) { double scale = mlt_profile_scale_width(profile, *width); double scale_height = mlt_profile_scale_height(profile, *height); if (geom_str.contains('%')) { rect.x *= *width; rect.w *= *width; rect.y *= *height; rect.h *= *height; } else { rect.x *= scale; rect.y *= scale_height; rect.w *= scale; rect.h *= scale_height; } QImage qimg; convert_mlt_to_qimage_rgba(*image, &qimg, *width, *height); QPainterPath text_path; #ifdef Q_OS_WIN auto pixel_ratio = mlt_properties_get_double(filter_properties, "pixel_ratio"); #else auto pixel_ratio = 1.0; #endif QRectF path_rect(0, 0, rect.w / scale * pixel_ratio, rect.h / scale_height * pixel_ratio); QPainter painter(&qimg); painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) | QPainter::HighQualityAntialiasing #endif ); painter.setOpacity(opacity); if (isRichText) { auto overflowY = mlt_properties_exists(filter_properties, "overflow-y") ? !!mlt_properties_get_int(filter_properties, "overflow-y") : (path_rect.height() >= profile->height * pixel_ratio); auto drawRect = overflowY ? QRectF() : path_rect; QMutexLocker mutexLock(&g_mutex); auto doc = get_rich_text(filter_properties, path_rect.width(), std::numeric_limits::max()); if (doc) { transform_painter(&painter, rect, path_rect, filter_properties, profile); if (overflowY) { path_rect.setHeight(qMax(path_rect.height(), doc->size().height())); } paint_background(&painter, path_rect, filter_properties, position, length); doc->drawContents(&painter, drawRect); } } else { path_rect = get_text_path(&text_path, filter_properties, argument, scale); transform_painter(&painter, rect, path_rect, filter_properties, profile); paint_background(&painter, path_rect, filter_properties, position, length); paint_text(&painter, &text_path, filter_properties, position, length); } painter.end(); convert_qimage_to_mlt_rgba(&qimg, *image, *width, *height); } mlt_service_unlock(MLT_FILTER_SERVICE(filter)); free(argument); return error; } /** Filter processing. */ static mlt_frame filter_process(mlt_filter filter, mlt_frame frame) { mlt_properties properties = get_filter_properties(filter, frame); if (mlt_properties_get_int(properties, "_hide")) { return frame; } char *argument = mlt_properties_get(properties, "argument"); char *html = mlt_properties_get(properties, "html"); char *resource = mlt_properties_get(properties, "resource"); // Save the text to be used by get_image() to support parallel processing // when this filter is encapsulated by other filters. if (qstrlen(resource)) { mlt_frame_push_service(frame, NULL); } else if (qstrlen(html)) { mlt_frame_push_service(frame, NULL); } else if (qstrlen(argument)) { mlt_frame_push_service(frame, strdup(argument)); } else { return frame; } // Push the filter on to the stack mlt_frame_push_service(frame, filter); // Push the get_image on to the stack mlt_frame_push_get_image(frame, filter_get_image); return frame; } /** Constructor for the filter. */ extern "C" { mlt_filter filter_qtext_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = mlt_filter_new(); if (!filter) return NULL; if (!createQApplicationIfNeeded(MLT_FILTER_SERVICE(filter))) { mlt_filter_close(filter); return NULL; } filter->process = filter_process; mlt_properties filter_properties = MLT_FILTER_PROPERTIES(filter); // Assign default values mlt_properties_set_string(filter_properties, "argument", arg ? arg : "text"); mlt_properties_set_string(filter_properties, "geometry", "0%/0%:100%x100%:100%"); mlt_properties_set_string(filter_properties, "family", "Sans"); mlt_properties_set_string(filter_properties, "size", "48"); mlt_properties_set_string(filter_properties, "weight", "400"); mlt_properties_set_string(filter_properties, "style", "normal"); mlt_properties_set_string(filter_properties, "fgcolour", "0x000000ff"); mlt_properties_set_string(filter_properties, "bgcolour", "0x00000020"); mlt_properties_set_string(filter_properties, "olcolour", "0x00000000"); mlt_properties_set_string(filter_properties, "pad", "0"); mlt_properties_set_string(filter_properties, "halign", "left"); mlt_properties_set_string(filter_properties, "valign", "top"); mlt_properties_set_string(filter_properties, "outline", "0"); mlt_properties_set_double(filter_properties, "pixel_ratio", 1.0); mlt_properties_set_double(filter_properties, "opacity", 1.0); mlt_properties_set_int(filter_properties, "_filter_private", 1); return filter; } } mlt-7.22.0/src/modules/qt/filter_qtext.yml000664 000000 000000 00000014241 14531534050 020500 0ustar00rootroot000000 000000 schema_version: 7.0 type: filter identifier: qtext title: QText version: 4 copyright: Meltytech, LLC license: LGPLv2.1 language: en tags: - Video description: Overlay text onto the video parameters: - identifier: argument title: Text type: string description: | The text to overlay. argument: yes required: yes readonly: no default: text widget: text - identifier: geometry title: Geometry type: rect description: A set of X/Y coordinates by which to adjust the text. default: 0%/0%:100%x100%:100 mutable: yes animation: yes - identifier: family title: Font family type: string description: > The typeface of the font. default: Sans readonly: no mutable: yes widget: combo - identifier: size title: Font size type: integer description: > The size in pixels of the font. default: 48 readonly: no mutable: yes widget: spinner unit: pixels - identifier: style title: Font style type: string description: > The style of the font. values: - normal - italic default: normal readonly: no mutable: yes widget: combo - identifier: weight title: Font weight type: integer description: The weight of the font. minimum: 100 maximum: 1000 default: 400 readonly: no mutable: yes widget: spinner - identifier: fgcolour title: Foreground color type: color description: > A color value is a hexadecimal representation of RGB plus alpha channel as 0xrrggbbaa. Colors can also be the words: white, black, red, green, or blue. You can also use a HTML-style color values #rrggbb or #aarrggbb. default: 0x000000ff readonly: no mutable: yes widget: color animation: yes - identifier: bgcolour title: Background color type: color description: > A color value is a hexadecimal representation of RGB plus alpha channel as 0xrrggbbaa. Colors can also be the words: white, black, red, green, or blue. You can also use a HTML-style color values #rrggbb or #aarrggbb. default: 0x00000020 readonly: no mutable: yes widget: color animation: yes - identifier: olcolour title: Outline color type: color description: > A color value is a hexadecimal representation of RGB plus alpha channel as 0xrrggbbaa. Colors can also be the words: white, black, red, green, or blue. You can also use a HTML-style color values #rrggbb or #aarrggbb. readonly: no mutable: yes widget: color animation: yes - identifier: outline title: Outline Width type: string description: > The width of the outline in pixels. readonly: no default: 0 minimum: 0 maximum: 3 mutable: yes widget: spinner unit: pixels - identifier: pad title: Padding type: integer description: > The number of pixels to pad the background rectangle beyond edges of text. readonly: no default: 0 mutable: yes widget: spinner unit: pixels - identifier: halign title: Horizontal alignment description: > Set the horizontal alignment within the geometry rectangle. type: string default: left values: - l - left - c - center - centre - r - right mutable: yes widget: combo - identifier: valign title: Vertical alignment description: > Set the vertical alignment within the geometry rectangle. type: string default: top values: - t - top - m - middle - b - bottom mutable: yes widget: combo - identifier: html title: HTML String type: string description: > Render rich text from a string containing a subset of HTML 4. The only other properties that can be used with this are geometry and bgcolour. The geometry width and height defines the page or block size while its X and Y coordinates determine its position. This property has a higher priority than argument; argument is ignored if this property is set. mutable: yes - identifier: resource title: HTML File type: string description: > Render rich text from a file containing a subset of HTML 4. The only other properties that can be used with this are geometry and bgcolour. The geometry width and height defines the page or block size while its X and Y coordinates determine its position. This property has a higher priority than both argument and html; argument and html are ignored if this property is set. mutable: yes - identifier: _hide title: Hide type: boolean description: > Setting this property will not be serialized (unlike "disable"). When set true (1), the filter does not render. This allows an authoring tool to provide its own rendering while editing and then let the filter render outside the tool UI. mutable: yes default: 0 - identifier: overflow-y title: Vertical Overflow type: boolean description: > This option applies only when using html or resource properties. It controls whether the text will be cropped to the geometry property or allowed to overflow. When not set, it is automatic based on whether the geometry height is greater than or equal to the profile height. The default is unset/automatic. mutable: yes - identifier: pixel_ratio title: Pixel Ratio type: float description: > This option applies only when using html or resource properties. It adds a scaling factor to the rendering. This can be used to help to make MLT's output match a user interface. NOTE: this property is only used on Windows because it is the only platform found thus far that has different rendering behavior relative to device pixel ratio. minimum: 1.0 default: 1.0 mutable: yes - identifier: opacity title: Opacity type: float description: Opacity of all elements - text, outline, and background readonly: no default: 1.0 minimum: 0 maximum: 1.0 mutable: yes widget: slider mlt-7.22.0/src/modules/qt/filter_typewriter.cpp000664 000000 000000 00000022200 14531534050 021524 0ustar00rootroot000000 000000 /* * filter_typewriter.cpp -- typewriter filter * Copyright (c) 2021 * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied wrenderedanty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include #include #include "kdenlivetitle_wrapper.h" #include "typewriter.h" struct FilterContainer { XmlParser xp; std::vector renders; // rendered data [array] bool init; // 1 if initialized int current_frame; // currently parsed frame std::string xml_data; // data field content (xml data) bool is_template; int step_length; // frame step value float sigma; // sigma of fluctuations int seed; // seed for random fluctuations int macro; // macro type: 0 - custom, 1 - char, 2 - word, 3 - line int producer_type; // 1 - kdenlivetitle mlt_producer producer; // hold producer pointer FilterContainer() { clean(); } void clean() { renders.clear(); init = false; current_frame = -1; xml_data.clear(); is_template = false; step_length = 0; sigma = 0; seed = 0; macro = 0; producer_type = 0; producer = nullptr; } }; /* * Get data for display. */ static int get_producer_data(mlt_properties filter_p, mlt_properties frame_p, FilterContainer *cont) { if (cont == nullptr) return 0; char *d = nullptr; int step_length = 0; int sigma = 0; int seed = 0; int macro = 0; mlt_producer producer = nullptr; mlt_properties producer_properties = nullptr; unsigned int update_mask = 0; /* Try with kdenlivetitle */ producer_ktitle kt = static_cast( mlt_properties_get_data(frame_p, "producer_kdenlivetitle", NULL)); if (kt != nullptr) { /* Obtain properties of producer */ producer = &kt->parent; producer_properties = MLT_PRODUCER_PROPERTIES(producer); if (producer == nullptr || producer_properties == nullptr) return 0; d = mlt_properties_get(producer_properties, "resource"); cont->is_template = (d && d[0] != '\0'); if (cont->is_template) d = mlt_properties_get(producer_properties, "_xmldata"); else d = mlt_properties_get(producer_properties, "xmldata"); if (d == nullptr) return 0; step_length = mlt_properties_get_int(filter_p, "step_length"); sigma = mlt_properties_get_int(filter_p, "step_sigma"); seed = mlt_properties_get_int(filter_p, "random_seed"); macro = mlt_properties_get_int(filter_p, "macro_type"); // if xml data changed, set update mask 0x1 if (cont->xml_data != d || macro != cont->macro) update_mask = 0x3; if (step_length != cont->step_length || sigma != cont->sigma || seed != cont->seed) update_mask |= 0x2; // clear and prepare for new parsing if (0 == update_mask) return 1; } else { return 0; } if (update_mask & 0x1) { cont->clean(); // save new data field name cont->xml_data = d; // Get content data and backup in the tw container. cont->xp.setDocument(d); cont->xp.parse(); unsigned int n = cont->xp.getContentNodesNumber(); for (uint i = 0; i < n; ++i) { std::string key = cont->xp.getNodeContent(i).toStdString(); TypeWriter data; if (macro) { char *buff = new char[key.length() + 5]; char c = 0; switch (macro) { case 1: c = 'c'; break; case 2: c = 'w'; break; case 3: c = 'l'; break; default: break; } sprintf(buff, ":%c{%s}", c, key.c_str()); data.setPattern(buff); delete[] buff; } else { data.setPattern(key); } cont->renders.push_back(data); } cont->macro = macro; cont->producer_type = 1; cont->producer = producer; // mark as inited cont->init = true; } if (update_mask & 0x2) { for (auto &render : cont->renders) { render.setFrameStep(step_length); render.setStepSigma(sigma); render.setStepSeed(seed); render.parse(); } cont->step_length = step_length; cont->sigma = sigma; cont->seed = seed; } return 1; } static int update_producer(mlt_frame frame, mlt_properties /*frame_p*/, FilterContainer *cont, bool restore) { if (cont->init == false) return 0; mlt_position pos = mlt_frame_original_position(frame); mlt_properties producer_properties = nullptr; if (cont->producer_type == 1) { producer_properties = MLT_PRODUCER_PROPERTIES(cont->producer); if (restore) mlt_properties_set_int(producer_properties, "force_reload", 0); else mlt_properties_set_int(producer_properties, "force_reload", 1); } if (producer_properties == nullptr) return 0; if (restore == true) { if (cont->is_template) mlt_properties_set(producer_properties, "_xmldata", cont->xml_data.c_str()); else mlt_properties_set(producer_properties, "xmldata", cont->xml_data.c_str()); return 1; } assert((cont->xp.getContentNodesNumber() == cont->renders.size())); // render the string and set as a content value unsigned int n = cont->xp.getContentNodesNumber(); for (uint i = 0; i < n; ++i) { cont->xp.setNodeContent(i, cont->renders[i].render(pos).c_str()); } // update producer for rest of the frame QString dom = cont->xp.getDocument(); if (cont->is_template) mlt_properties_set(producer_properties, "_xmldata", dom.toStdString().c_str()); else mlt_properties_set(producer_properties, "xmldata", dom.toStdString().c_str()); cont->current_frame = pos; return 1; } static int filter_get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int /*writable*/) { int error = 0; mlt_filter filter = (mlt_filter) mlt_frame_pop_service(frame); mlt_properties frame_properties = MLT_FRAME_PROPERTIES(frame); mlt_properties properties = MLT_FILTER_PROPERTIES(filter); FilterContainer *cont = (FilterContainer *) filter->child; mlt_service_lock(MLT_FILTER_SERVICE(filter)); int res = get_producer_data(properties, frame_properties, cont); if (res == 0) return mlt_frame_get_image(frame, image, format, width, height, 1); update_producer(frame, frame_properties, cont, false); error = mlt_frame_get_image(frame, image, format, width, height, 1); update_producer(frame, frame_properties, cont, true); mlt_service_unlock(MLT_FILTER_SERVICE(filter)); return error; } static mlt_frame filter_process(mlt_filter filter, mlt_frame frame) { mlt_frame_push_service(frame, filter); mlt_frame_push_get_image(frame, filter_get_image); return frame; } static void filter_close(mlt_filter filter) { FilterContainer *cont = (FilterContainer *) filter->child; cont->clean(); } extern "C" { mlt_filter filter_typewriter_init(mlt_profile /*profile*/, mlt_service_type /*type*/, const char * /*id*/, char * /*arg*/) { mlt_filter filter = mlt_filter_new(); FilterContainer *cont = new FilterContainer; if (filter != nullptr && cont != nullptr) { filter->process = filter_process; filter->child = cont; filter->close = filter_close; } mlt_properties properties = MLT_FILTER_PROPERTIES(filter); mlt_properties_set_int(properties, "step_length", 25); mlt_properties_set_int(properties, "step_sigma", 0); mlt_properties_set_int(properties, "random_seed", 0); mlt_properties_set_int(properties, "macro_type", 1); return filter; } } mlt-7.22.0/src/modules/qt/filter_typewriter.yml000664 000000 000000 00000002477 14531534050 021561 0ustar00rootroot000000 000000 schema_version: 0.1 type: filter # consumer, filter, producer, or transition identifier: typewriter title: TypeWriter version: 0.3.5 copyright: Copyright (C) Rafal Lalik license: GPL language: en creator: Rafal Lalik tags: - Video description: | Typewriter effect applied to kdenlivetitler producer notes: > Original development: https://github.com/rlalik/mlt_extra_modules parameters: - identifier: step_length title: Distance between basic steps type: integer description: Defines how many frames it takes to display next character readonly: no mutable: yes default: 25 - identifier: step_sigma title: Fluctuation of step length type: integer description: Varies the step position by random value following normal distribution readonly: no mutable: yes default: 0 - identifier: random_seed title: Seed value type: integer description: Seed value for the random generator readonly: no mutable: yes default: 0 - identifier: macro_type title: Macro expansion type type: integer description: | Defines type of macro expansion: 0 - custom macro, 1 - expansion char-by-char 2 - expansion word-by-word 3 - expansion line-by-line readonly: no mutable: yes default: 1 mlt-7.22.0/src/modules/qt/gpl000664 000000 000000 00000000000 14531534050 015734 0ustar00rootroot000000 000000 mlt-7.22.0/src/modules/qt/gps_drawing.cpp000664 000000 000000 00000104344 14531534050 020257 0ustar00rootroot000000 000000 /* * filter_drawing.cpp -- draws gps related graphics * Copyright (c) 2015-2022 Meltytech, LLC * Original author: Daniel F * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "filter_gpsgraphic.h" //apply crop percentages to [min, max] then compute value at specific percentage template static double get_value_from_percent_after_crop(double perc, T min, T max, double p_min, double p_max) { double ret = 0; T delta = max - min; T new_min = min + p_min * delta / 100.0; T new_max = min + delta * p_max / 100.0; ret = (double) (new_min + perc * (new_max - new_min)); // mlt_log_info(NULL, "get_value_from_percent_after_crop (perc=%f, min=%f, max=%f, p_min=%f, p_max=%f) rezult=%f [restricted to 0..1]\n", perc, min, max, p_min, p_max, ret); return ret; } //returns a 2d point of current source-type from gp, scaled to the rect area static point_2d get_gpspoint_to_rect( mlt_filter filter, mlt_frame frame, gps_point_proc *gp, mlt_rect rect, s_base_crops used_crops) { private_data *pdata = (private_data *) filter->child; point_2d tmp = {-1, -1}; tmp.y = crop_and_normalize(get_crtval_bysrc(filter, 0, 0, gp), get_min_bysrc(filter), get_max_bysrc(filter), used_crops.bot, used_crops.top); if (pdata->graph_data_source == gspg_location_src) //if location, x-axis is longitude, else time tmp.x = crop_and_normalize(get_crtval_bysrc(filter, 0, gpsg_longitude_id, gp), get_min_bysrc(filter, gpsg_longitude_id), get_max_bysrc(filter, gpsg_longitude_id), used_crops.left, used_crops.right); else tmp.x = crop_and_normalize(gp->time, pdata->ui_crops.min_crop_time, pdata->ui_crops.max_crop_time, used_crops.left, used_crops.right); //scale point to rect area tmp.x = rect.x + tmp.x * rect.w; tmp.y = rect.y + rect.h - tmp.y * rect.h; return tmp; } //there isn't a Qt function for rect - line intersection check :( static bool rect_intersects_line(QRectF rect, point_2d pt1, point_2d pt2) { //if one point is inside the rect -> true if (rect.contains(pt1.x, pt1.y) || rect.contains(pt2.x, pt2.y)) return true; #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) //check if each side of the rect intersects (bounded) with the line QLineF line = QLineF(pt1.x, pt1.y, pt2.x, pt2.y); if (line.intersects(QLineF(rect.topLeft(), rect.topRight()), NULL) == QLineF::BoundedIntersection) return true; if (line.intersects(QLineF(rect.bottomLeft(), rect.bottomRight()), NULL) == QLineF::BoundedIntersection) return true; if (line.intersects(QLineF(rect.topLeft(), rect.bottomLeft()), NULL) == QLineF::BoundedIntersection) return true; if (line.intersects(QLineF(rect.topRight(), rect.bottomRight()), NULL) == QLineF::BoundedIntersection) return true; #else return true; //this is ok because rect clipping is enabled, it just skips the optimisation #endif return false; } //get a smooth change in color when drawing individual lines for maps //percentage is of the entire colors array, interpolation is done for the 2 nearby colors static QColor interpolate_color_from_gradient(double p, QVector &colors) { QColor ret = Qt::black; p = CLAMP(p, 0, 1); if (colors.size() == 0) return ret; if (p == 1 || colors.size() == 1) return colors[colors.size() - 1]; //the 2 colors we need to interpolate from int c1 = p * (colors.size() - 1); c1 = CLAMP(c1, 0, colors.size() - 1); int c2 = c1 + 1; c2 = CLAMP(c2, 0, colors.size() - 1); double ratio = (p * (colors.size() - 1)) - c1; //=the fractional part ratio = CLAMP(ratio, 0, 1); //Result = (color2 - color1) * ratio + color1 #define interp(v1, v2, r) (v2 - v1) * r + v1 ret.setRed(interp(colors[c1].red(), colors[c2].red(), ratio)); ret.setGreen(interp(colors[c1].green(), colors[c2].green(), ratio)); ret.setBlue(interp(colors[c1].blue(), colors[c2].blue(), ratio)); ret.setAlpha(interp(colors[c1].alpha(), colors[c2].alpha(), ratio)); #undef interp return ret; } //draws 5 horizontal (+5 vertical for 2D map) with small text for each line showing the graph value at that point void draw_legend_grid(mlt_filter filter, mlt_frame frame, QPainter &p, s_base_crops &used_crops) { private_data *pdata = (private_data *) filter->child; mlt_rect rect = pdata->img_rect; mlt_properties properties = MLT_FILTER_PROPERTIES(filter); char *legend_unit = mlt_properties_get(properties, "legend_unit"); int nr_legend_lines = 5; QPainterPath path_grid_lines; QPen pen_5lines; pen_5lines.setWidth(1); pen_5lines.setColor(Qt::white); QFont font = p.font(); int text_size = MIN(rect.w, rect.h) / 25; font.setPixelSize(text_size); p.setFont(font); p.setPen(pen_5lines); p.setClipping(false); //legend text goes a bit outside the rect for (int i = 0; i < nr_legend_lines; i++) { path_grid_lines.moveTo(rect.x, rect.y + rect.h - (rect.h / (nr_legend_lines - 1) * i)); //for each of the nr_legend_lines compute it's absolute value = percentage of min_max (after applying crop bot/top) double crt_val = get_value_from_percent_after_crop((double) i / (nr_legend_lines - 1), get_min_bysrc(filter), get_max_bysrc(filter), used_crops.bot, used_crops.top); crt_val = convert_bysrc_to_format(filter, crt_val); p.drawText(path_grid_lines.currentPosition().x() + 3, path_grid_lines.currentPosition().y() - 3, QString::number(crt_val, 'f', decimals_needed_bysrc(filter, crt_val)) + legend_unit); path_grid_lines.lineTo(rect.x + rect.w, rect.y + rect.h - (rect.h / 4 * i)); } //+vertical lines only for the map (longitude) if (pdata->graph_data_source == gspg_location_src) { for (int i = 0; i < nr_legend_lines; i++) { path_grid_lines.moveTo(rect.x + rect.w / (nr_legend_lines - 1) * i, rect.y); double crt_val = get_value_from_percent_after_crop((double) i / (nr_legend_lines - 1), get_min_bysrc(filter, gpsg_longitude_id), get_max_bysrc(filter, gpsg_longitude_id), used_crops.left, used_crops.right); crt_val = swap_180_if_needed(crt_val); p.drawText(path_grid_lines.currentPosition().x() + 3, path_grid_lines.currentPosition().y() + text_size + 3, QString::number(crt_val, 'f', 6)); path_grid_lines.lineTo(rect.x + rect.w / (nr_legend_lines - 1) * i, rect.y + rect.h); } } p.drawPath(path_grid_lines); p.setClipping(true); } //draws a small dot/disc on the map/graph according to the current video time + sync void draw_now_dot(mlt_filter filter, mlt_frame frame, QPainter &p, s_base_crops &used_crops) { private_data *pdata = (private_data *) filter->child; mlt_rect rect = pdata->img_rect; mlt_properties properties = MLT_FILTER_PROPERTIES(filter); mlt_position position = mlt_filter_get_position(filter, frame); mlt_position length = mlt_filter_get_length2(filter, frame); int thickness = mlt_properties_get_int(properties, "thickness"); mlt_color dot_color = mlt_properties_anim_get_color(properties, "now_dot_color", position, length); //disc with internal color = white and outer color=now dot color or last used for graph line QPen dot_pen = p.pen(); dot_pen.setWidth(thickness / 1.5); //or fixed to 3px ? int disc_size = thickness * 1.5; if (dot_color.a != 0) dot_pen.setColor(QColor(dot_color.r, dot_color.g, dot_color.b, dot_color.a)); p.setBrush(Qt::white); p.setPen(dot_pen); double px, py; bool inside_only = true; //the now_dot will never leave the rect area to help in visual sync gps_point_proc crt = get_now_weighted_gpspoint(filter, frame); if (get_crtval_bysrc(filter, 0, 0, &crt) != GPS_UNINIT) { py = crop_and_normalize(get_crtval_bysrc(filter, 0, 0, &crt), get_min_bysrc(filter), get_max_bysrc(filter), used_crops.bot, used_crops.top, inside_only); if (pdata->graph_data_source == gspg_location_src) px = crop_and_normalize(get_crtval_bysrc(filter, 0, gpsg_longitude_id, &crt), get_min_bysrc(filter, gpsg_longitude_id), get_max_bysrc(filter, gpsg_longitude_id), used_crops.left, used_crops.right, inside_only); else //time px = crop_and_normalize(crt.time, pdata->ui_crops.min_crop_time, pdata->ui_crops.max_crop_time, used_crops.left, used_crops.right, inside_only); QPointF disc; disc.setX(rect.x + px * rect.w); disc.setY(rect.y + rect.h - py * rect.h); p.setClipping(false); p.drawEllipse(disc, disc_size, disc_size); p.setClipping(true); } } //draws a 2d graph of the chosen graph source (for non-location, second axis is time) void draw_main_line_graph(mlt_filter filter, mlt_frame frame, QPainter &p, s_base_crops &used_crops) { private_data *pdata = (private_data *) filter->child; mlt_rect rect = pdata->img_rect; QRectF qrect = QRectF(rect.x, rect.y, rect.w, rect.h); mlt_properties properties = MLT_FILTER_PROPERTIES(filter); mlt_position position = mlt_filter_get_position(filter, frame); mlt_position length = mlt_filter_get_length2(filter, frame); int color_style = mlt_properties_get_int(properties, "color_style"); int thickness = qAbs(mlt_properties_get_int(properties, "thickness")); int dots_only = mlt_properties_get_int(properties, "draw_individual_dots"); char *legend_unit = mlt_properties_get(properties, "legend_unit"); QVector colors = get_graph_colors(properties, position, length); QPen last_graph_pen; if (colors.size() < 2) colors.append(colors[0]); //original state p.save(); //draw legend grid lines + text if enabled if ((pdata->graph_type == gpsg_2d_map_graph || pdata->graph_type == gpsg_crop_center_graph) && mlt_properties_get_int(properties, "show_grid")) draw_legend_grid(filter, frame, p, used_crops); int i_now = get_now_gpspoint_index(filter, frame); gps_point_proc gps_now = get_now_weighted_gpspoint(filter, frame); point_2d crt_pt = {-1, -1}, next_pt = {-1, -1}; QPen pen_solid_color0; pen_solid_color0.setWidth(thickness); pen_solid_color0.setColor(colors[0]); pen_solid_color0.setCapStyle(Qt::RoundCap); last_graph_pen = pen_solid_color0; QPen pen_solid_color1; pen_solid_color1.setWidth(thickness); pen_solid_color1.setColor(colors[1]); pen_solid_color1.setCapStyle(Qt::RoundCap); QPen pen_thin_color1; //1px dots are too small, 1px line feels just too small and sometimes aliaseses weirdly at an angle //meanwhile, I've added the "Two colors" style if the future points/lines are too small, might reconsider this if (thickness > 2) pen_thin_color1.setWidth(2); else pen_thin_color1.setWidth(1); pen_thin_color1.setColor(colors[1]); pen_thin_color1.setCapStyle( Qt::RoundCap); //( Qt::SquareCap ); // I don't remember what issue I had that made me use this, keeping it for now QPen pen_gradients; pen_gradients.setWidth(thickness); pen_gradients.setCapStyle(Qt::RoundCap); //enhancement TODO: if detected gap in GPS data draw a dashed line (but needs a bunch of extra changes) //pen_dash.setStyle( Qt::DashLine ); //go through the [start_index..end_index] interval of gps_points_p and draw each pair of valid points for (int i = pdata->ui_crops.start_index; i < pdata->ui_crops.end_index; i++) { int next_i = get_next_valid_gpspoint_index(filter, i); if (i == next_i || get_crtval_bysrc(filter, i) == GPS_UNINIT || get_crtval_bysrc(filter, next_i) == GPS_UNINIT) { // mlt_log_info(filter, "incomplete pair (i=%d, %d) GPS_UNINIT, skipping drawing", i, next_i); continue; } crt_pt = get_gpspoint_to_rect(filter, frame, &pdata->gps_points_p[i], rect, used_crops); next_pt = get_gpspoint_to_rect(filter, frame, &pdata->gps_points_p[next_i], rect, used_crops); //don't draw line at all if it is completely outside the rect if (!rect_intersects_line(qrect, crt_pt, next_pt)) continue; //apply color style if (color_style == gpsg_color_by_solid) { p.setPen(pen_solid_color0); } else if (color_style == gpsg_color_by_solid_past_future) { if (i <= i_now) p.setPen(pen_solid_color0); else if (i > i_now) p.setPen(pen_solid_color1); } else if ((color_style == gpsg_color_by_solid_past && i <= i_now) || (color_style == gpsg_color_by_solid_future && i > i_now)) { p.setPen(pen_solid_color0); } else if ((color_style == gpsg_color_by_solid_past && i > i_now) || (color_style == gpsg_color_by_solid_future && i <= i_now)) { p.setPen(pen_thin_color1); } else if (color_style == gpsg_color_by_vertical_gradient || color_style == gpsg_color_by_horizontal_gradient) { QLinearGradient gradient; gradient.setStart(rect.x, rect.y); if (color_style == gpsg_color_by_vertical_gradient) gradient.setFinalStop(rect.x, rect.y + rect.h); else gradient.setFinalStop(rect.x + rect.w, rect.y); qreal step = 1.0 / (colors.size() - 1); for (int i = 0; i < colors.size(); i++) gradient.setColorAt((qreal) i * step, colors[i]); pen_gradients.setBrush(gradient); p.setPen(pen_gradients); } else if (color_style >= gpsg_color_by_duration && color_style <= gpsg_color_by_grade_max20) { //compute current value as a percentage of min..max #define calc_perc(v, min, max) \ (double) (v - min) / ((max - min) != 0 ? (max - min) : ((v - min) ? (v - min) : 1)) double perc = 0; if (color_style == gpsg_color_by_duration) { //this one is relative to trim, not entire gps track perc = calc_perc(pdata->gps_points_p[i].time, pdata->ui_crops.min_crop_time, pdata->ui_crops.max_crop_time); } else if (color_style == gpsg_color_by_altitude) { perc = calc_perc(pdata->gps_points_p[i].ele, pdata->minmax.min_ele, pdata->minmax.max_ele); } else if (color_style == gpsg_color_by_hr) { perc = calc_perc(pdata->gps_points_p[i].hr, pdata->minmax.min_hr, pdata->minmax.max_hr); } else if (color_style == gpsg_color_by_speed || color_style == gpsg_color_by_speed_max100) { //max 100km/h (27.777 m/s) variant to cover for bad GPS errors double used_max_speed = pdata->minmax.max_speed; if (color_style == gpsg_color_by_speed_max100 && used_max_speed > 27.777) used_max_speed = 27.777; perc = calc_perc(pdata->gps_points_p[i].speed, pdata->minmax.min_speed, used_max_speed); } else if (color_style == gpsg_color_by_grade_max90 || color_style == gpsg_color_by_grade_max20) { //limit to 90* (100%) or 20* (36.397%) - only if max is over this value double max_allowed_percentage = MAX(abs(pdata->minmax.min_grade_p), abs(pdata->minmax.max_grade_p)); max_allowed_percentage = MIN(max_allowed_percentage, (color_style == gpsg_color_by_grade_max20 ? 36.397 : 100)); double safe_grade_p = CLAMP(pdata->gps_points_p[i].grade_p, -max_allowed_percentage, max_allowed_percentage); //this one is special because middle color is always for value 0; if (pdata->gps_points_p[i].grade_p < 0) perc = calc_perc(safe_grade_p, -max_allowed_percentage, 0) / 2.0; else perc = calc_perc(safe_grade_p, 0, max_allowed_percentage) / 2.0 + 0.5; } //assign the interpolated color at p% in the colors array pen_gradients.setColor(interpolate_color_from_gradient(perc, colors)); p.setPen(pen_gradients); } else { p.setPen(pen_solid_color0); } if (i == i_now) last_graph_pen = p.pen(); //for the past/future segment we need to split it exactly at the now point into 2 different colors or it will look horrible if zoomed in enough if ((i == i_now) && (color_style == gpsg_color_by_solid_past || color_style == gpsg_color_by_solid_future || color_style == gpsg_color_by_solid_past_future)) { point_2d now_pt = get_gpspoint_to_rect(filter, frame, &gps_now, rect, used_crops); //if we got a valid intermediary point for the current location, we'll draw the past/future with different styles if (get_crtval_bysrc(filter, 0, 0, &gps_now) != GPS_UNINIT) { //"past" sub-segment if (color_style == gpsg_color_by_solid_past || color_style == gpsg_color_by_solid_past_future) p.setPen(pen_solid_color0); else if (color_style == gpsg_color_by_solid_future) p.setPen(pen_thin_color1); if (dots_only) p.drawPoint(QPointF(crt_pt.x, crt_pt.y)); else p.drawLine(QPointF(crt_pt.x, crt_pt.y), QPointF(now_pt.x, now_pt.y)); //"future" sub-segment if (color_style == gpsg_color_by_solid_past) p.setPen(pen_thin_color1); else if (color_style == gpsg_color_by_solid_future) p.setPen(pen_solid_color0); else if (color_style == gpsg_color_by_solid_past_future) p.setPen(pen_solid_color1); if (!dots_only) p.drawLine(QPointF(now_pt.x, now_pt.y), QPointF(next_pt.x, next_pt.y)); } else { //if invalid point, consider the entire line "future" if (color_style == gpsg_color_by_solid_past) p.setPen(pen_thin_color1); else if (color_style == gpsg_color_by_solid_future) p.setPen(pen_solid_color0); else if (color_style == gpsg_color_by_solid_past_future) p.setPen(pen_solid_color1); if (dots_only) p.drawPoint(QPointF(crt_pt.x, crt_pt.y)); else p.drawLine(QPointF(crt_pt.x, crt_pt.y), QPointF(next_pt.x, next_pt.y)); } } else //= full segment lines not intersecting now_dot { //IMPORTANT: the function call without QPointF() cast loses precision due to int!! fun times debugging the small random wiggles from this one if (dots_only) p.drawPoint(QPointF(crt_pt.x, crt_pt.y)); else p.drawLine(QPointF(crt_pt.x, crt_pt.y), QPointF(next_pt.x, next_pt.y)); } } //draw the current value in the bot-right corner, big bold white text if (mlt_properties_get_int(properties, "show_now_text")) { double now_val = get_crtval_bysrc(filter, 0, 0, &gps_now); if (now_val != GPS_UNINIT) { QRectF now_text_rect = QRectF(rect.x, rect.y + rect.h / 2, rect.w, rect.h / 2); QFont font = p.font(); font.setPixelSize(rect.w / 15); font.setWeight(QFont::Bold); p.setFont(font); p.setPen(Qt::white); now_val = convert_bysrc_to_format(filter, now_val); QString now_text = QString::number(now_val, 'f', decimals_needed_bysrc(filter, now_val)); if (pdata->graph_data_source == gspg_location_src) { now_val = get_crtval_bysrc(filter, 0, gpsg_longitude_id, &gps_now); now_val = swap_180_if_needed(now_val); now_text += ", " + QString::number(now_val, 'f', decimals_needed_bysrc(filter, now_val)); } now_text += QString(legend_unit); p.drawText(now_text_rect, Qt::AlignBottom | Qt::AlignRight, now_text); } } //restore to before legend + main_graph p.restore(); //set color so by default the now_dot will be the same color as nearby graph p.setPen(last_graph_pen); //draw a circle for current point if (mlt_properties_get_int(properties, "show_now_dot") && pdata->graph_type <= 1) draw_now_dot(filter, frame, p, used_crops); } //draws a speedometer from the current data source (for map it displays % of total) void draw_main_speedometer(mlt_filter filter, mlt_frame frame, QPainter &p, s_base_crops &used_crops) { private_data *pdata = (private_data *) filter->child; mlt_rect rect = pdata->img_rect; mlt_properties properties = MLT_FILTER_PROPERTIES(filter); mlt_position position = mlt_filter_get_position(filter, frame); mlt_position length = mlt_filter_get_length2(filter, frame); // int color_style = mlt_properties_get_int( properties, "color_style" ) ; // int thickness = qAbs( mlt_properties_get_int( properties, "thickness" ) ); int show_grid = mlt_properties_get_int(properties, "show_grid"); QVector colors = get_graph_colors(properties, position, length); if (colors.size() == 1) colors.append(colors[0]); //draw the divisions double canvas_angle = mlt_properties_get_double(properties, "angle"); const double max_angle = 240.0; const double start_angle = 90 + 30; const int nr_big_lines = 6; const int nr_small_lines = 3; int small_side = MIN(rect.w, rect.h); double big_line_width = small_side / 10.0; double big_line_height = big_line_width / 5.0; double big_angle = max_angle / nr_big_lines; double small_angle = big_angle / (nr_small_lines + 1); QRectF big_line(0, -big_line_height / 2, -big_line_width, big_line_height); QRectF small_line(0, big_line.y() / 2, big_line.width() / 2, big_line.height() / 2); //legend = big line numbers QPen pen_speedo_nr; pen_speedo_nr.setWidth(1); pen_speedo_nr.setColor(Qt::white); QFont font = p.font(); int text_size = big_line_height * 3; font.setPixelSize(text_size); font.setWeight(QFont::Bold); p.setFont(font); p.setPen(pen_speedo_nr); p.translate(rect.x + rect.w / 2, rect.y + rect.h / 2); p.rotate(start_angle); for (int i = 0; i <= nr_big_lines; i++) { //big lines p.rotate(i * big_angle); p.translate(small_side / 2, 0); p.fillRect(big_line, colors[0]); //legend values if (show_grid) { p.save(); double crt_val = 0; if (pdata->graph_data_source != gspg_location_src) { crt_val = get_value_from_percent_after_crop((double) i / nr_big_lines, get_min_bysrc(filter), get_max_bysrc(filter), used_crops.bot, used_crops.top); crt_val = convert_bysrc_to_format(filter, crt_val); } else //just show a percentage of total for map { crt_val = 100.0 * i / nr_big_lines; } //center & rotate the text: compute the text bounding box, translate to it's center, rotate, translate back and drawtext within the rect bounding box QString l_text = QString::number(crt_val, 'f', 0); int text_width = p.fontMetrics().horizontalAdvance(l_text) + p.fontMetrics().horizontalAdvance(" "); int text_height = p.fontMetrics().height(); QRectF text_rect = QRectF(-text_width, -text_height / 2, text_width, text_height); p.translate(-big_line_width, 0); p.translate(text_rect.center()); p.rotate(-(canvas_angle + start_angle + i * big_angle)); p.translate(-text_rect.center()); p.drawText(text_rect, Qt::AlignCenter, l_text); p.restore(); } // small lines p.translate(-small_side / 2, 0); for (int j = 1; j <= nr_small_lines && i != nr_big_lines; j++) { p.rotate(j * small_angle); p.translate(small_side / 2, 0); p.fillRect(small_line, colors[1]); p.translate(-small_side / 2, 0); p.rotate(-j * small_angle); } p.rotate(-i * big_angle); } //find out the needle's angle gps_point_proc gps_now = get_now_weighted_gpspoint(filter, frame); double now_value = 0, n_angle = 0; if (pdata->graph_data_source != gspg_location_src) { if (get_crtval_bysrc(filter, 0, 0, &gps_now) != GPS_UNINIT) { n_angle = crop_and_normalize(get_crtval_bysrc(filter, 0, 0, &gps_now), get_min_bysrc(filter), get_max_bysrc(filter), used_crops.bot, used_crops.top, true); now_value = convert_bysrc_to_format(filter, get_crtval_bysrc(filter, 0, 0, &gps_now)); } } else //just show a percentage of total { n_angle = crop_and_normalize(gps_now.time, pdata->ui_crops.min_crop_time, pdata->ui_crops.max_crop_time, used_crops.left, used_crops.right, true); now_value = n_angle * 100; } p.rotate(n_angle * max_angle); //the needle considered as a "Now dot in UI"; just a long triangle if (mlt_properties_get_int(properties, "show_now_dot")) { mlt_color dot_color = mlt_properties_anim_get_color(properties, "now_dot_color", position, length); if (dot_color.a == 0) // if transparent -> use main color { p.setBrush(colors[0]); p.setPen(colors[0]); } else { p.setBrush(QColor(dot_color.r, dot_color.g, dot_color.b, dot_color.a)); p.setPen(QColor(dot_color.r, dot_color.g, dot_color.b, dot_color.a)); } double needle_base_len = big_line_width / 8; QPointF needle[3] = {QPointF(-needle_base_len, -needle_base_len), QPointF(-needle_base_len, needle_base_len), QPointF(small_side / 2.0, 0)}; p.drawConvexPolygon(needle, 3); //small dot in center double needle_circle_len = big_line_width / 2; p.drawEllipse(-needle_circle_len / 2, -needle_circle_len / 2, needle_circle_len, needle_circle_len); } p.rotate(-n_angle * max_angle); p.rotate(-start_angle); //draw big bold text in white here if (mlt_properties_get_int(properties, "show_now_text")) { QRectF now_text_rect = QRectF(-small_side * 0.25, 0, small_side * 0.75, small_side / 2 - text_size * 2); QString now_text = QString::number(now_value, 'f', decimals_needed(now_value)); int now_text_size = text_size * 3; font.setPixelSize(now_text_size); p.setFont(font); p.setPen(Qt::white); p.translate(now_text_rect.center()); p.rotate(-canvas_angle); p.translate(-now_text_rect.center()); p.drawText(now_text_rect, Qt::AlignBottom | Qt::AlignHCenter, now_text); //print the legend_unit with 1/3 smaller text size below QRectF now_text_unit_rect = QRectF(-small_side * 0.25, small_side / 2 - 2 * text_size, small_side * 0.75, text_size * 2); QString now_text_unit = mlt_properties_get(properties, "legend_unit"); font.setPixelSize(text_size); p.setFont(font); p.setPen(Qt::white); p.drawText(now_text_unit_rect, Qt::AlignTop | Qt::AlignHCenter, now_text_unit); } } //inits drawing rect +clipping, antialiasing, and applies rotate void prepare_canvas(mlt_filter filter, mlt_frame frame, QImage *qimg, QPainter &p, int width, int height, s_base_crops &used_crops) { private_data *pdata = (private_data *) filter->child; mlt_properties properties = MLT_FILTER_PROPERTIES(filter); mlt_position position = mlt_filter_get_position(filter, frame); mlt_position length = mlt_filter_get_length2(filter, frame); mlt_profile profile = mlt_service_profile(MLT_FILTER_SERVICE(filter)); mlt_rect rect = mlt_properties_anim_get_rect(properties, "rect", position, length); //rect area and scaling stuff if (strchr(mlt_properties_get(properties, "rect"), '%')) { rect.x *= qimg->width(); rect.w *= qimg->width(); rect.y *= qimg->height(); rect.h *= qimg->height(); } double scale = mlt_profile_scale_width(profile, width); rect.x *= scale; rect.w *= scale; scale = mlt_profile_scale_height(profile, height); rect.y *= scale; rect.h *= scale; pdata->img_rect = rect; QRectF r(rect.x, rect.y, rect.w, rect.h); // Apply rotation double angle = mlt_properties_get_double(properties, "angle"); if (angle) { p.translate(r.x() + r.width() / 2, r.y() + r.height() / 2); p.rotate(angle); p.translate(-(r.x() + r.width() / 2), -(r.y() + r.height() / 2)); } p.setClipRect(r); //this almost doubles processing time if using large backgrounds (from 6-7ms to 10-11ms/frame) but massively improves drawImage smoothness (visible neighbor pixel jumps from frame to frame if disabled) p.setRenderHint(QPainter::SmoothPixmapTransform, true); //draw background image (cropped and matched) if (pdata->bg_img_scaled_width != 0 && strlen(pdata->last_bg_img_path) > 0 && !pdata->bg_img.isNull()) { //step 1 scales image to map aspect ratio (in process_filter_properties()) //step 2) apply bg_scale_w/h to correctly match img to GPS track -> create rescaled_bg_rect instead of modifying image here double bg_width = pdata->bg_img_scaled.width(); double bg_height = pdata->bg_img_scaled.height(); double new_bg_width = bg_width * pdata->bg_img_scaled_width; double new_bg_height = bg_height * pdata->bg_img_scaled_height; double new_bg_x = (bg_width - new_bg_width) / 2; double new_bg_y = (bg_height - new_bg_height) / 2; QRectF rescaled_bg_rect = QRectF(new_bg_x, new_bg_y, new_bg_width, new_bg_height); //step 3) apply crop percentages to the rescaled_bg_rect to follow now_dot or user input QPointF top_left = QPointF(rescaled_bg_rect.x() + rescaled_bg_rect.width() * used_crops.left / 100.0, rescaled_bg_rect.y() + rescaled_bg_rect.height() * (1 - used_crops.top / 100.0)); QPointF bot_right = QPointF(rescaled_bg_rect.bottomRight().x() - rescaled_bg_rect.width() * (1 - used_crops.right / 100.0), rescaled_bg_rect.bottomRight().y() - rescaled_bg_rect.height() * used_crops.bot / 100.0); QRectF crop_rect = QRectF(top_left, bot_right); // mlt_log_info(filter, "crop rect: x,y w,h= %f,%f %f,%f", crop_rect.x(), crop_rect.y(), crop_rect.width(), crop_rect.height()); //step 4) crop the computed rect from the step 1) scaled image p.setOpacity(mlt_properties_get_double(properties, "bg_opacity")); p.drawImage(r, pdata->bg_img_scaled, crop_rect); p.setOpacity(1); } p.setRenderHint(QPainter::Antialiasing, true); } mlt-7.22.0/src/modules/qt/gps_parser.cpp000664 000000 000000 00000165517 14531534050 020131 0ustar00rootroot000000 000000 /* * gps_parser.h -- Contains gps parsing (.gpx and .tcx) and processing code * Copyright (C) 2011-2022 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #define __USE_XOPEN #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #include "gps_parser.h" #define _x (const xmlChar *) #define _s (const char *) #define _qxml_getthestring(s) qUtf8Printable((s).toString()) //shifts all (longitude) values from near 180 to 0 double get_180_swapped(double lon) { // mlt_log_info(NULL, "get_180_swapped(%f) -> %f\n", lon, lon + ( lon>0 ? -180 : 180)); return lon + (lon > 0 ? -180 : 180); } //testing max valid time between 2 points = 10 * avg_gps_time, better option would be a user entered value int get_max_gps_diff_ms(gps_private_data gdata) { return (10 * get_avg_gps_time_ms(gdata)) * 1000; } /** Computes the average time between recorded gps points * Note: needs first/last gps time already computed */ double get_avg_gps_time_ms(gps_private_data gdata) { int64_t tdiff = *gdata.last_gps_time - *gdata.first_gps_time; int gp_size = *gdata.gps_points_size; if (gp_size == 0) return 0; return (double) tdiff / gp_size; } /* Converts the datetime string from gps file into seconds since epoch * Note: assumes UTC */ int64_t datetimeXMLstring_to_mseconds(const char *text, char *format /* = NULL*/) { char def_format[] = "%Y-%m-%dT%H:%M:%S"; int64_t ret = 0; int ms = 0; struct tm tm_time; //samples: 2020-07-11T09:03:23.000Z or 2021-02-27T12:10:00+00:00 tm_time.tm_isdst = -1; //force dst detection if (format == NULL) format = def_format; if (strptime(text, format, &tm_time) == NULL) { mlt_log_warning( NULL, "filter_gpsText.c datetimeXMLstring_to_seconds strptime failed on string: %.25s", text); return 0; } ret = internal_timegm(&tm_time); //check if we have miliseconds, 3 digits only const char *ms_part = strchr(text, '.'); if (ms_part != NULL) { ms = strtol(ms_part + 1, NULL, 10); while (abs(ms) > 999) ms /= 10; } ret = ret * 1000 + ms; // mlt_log_info(NULL, "datetimeXMLstring_to_mseconds: text:%s, ms:%d (/1000)", text, ret/1000); return ret; } //checks if provided char* is only made of whitespace chars static int is_whitespace_string(char *str) { unsigned i; for (i = 0; i < strlen(str); i++) { if (!isspace(str[i])) return 0; } return 1; } //Converts miliseconds to a date-time with optional format (no miliesconds in output) void mseconds_to_timestring(int64_t seconds, char *format, char *result) { time_t secs = llabs(seconds) / 1000; struct tm *ptm = gmtime(&secs); if (!format || is_whitespace_string(format)) strftime(result, 25, "%Y-%m-%d %H:%M:%S", ptm); else strftime(result, 50, format, ptm); } //returns the distance between 2 gps points (accurate for very large distances) double distance_haversine_2p(double p1_lat, double p1_lon, double p2_lat, double p2_lon) { const int R = 6371000; double dlat = to_rad(p2_lat - p1_lat); double dlon = to_rad(p2_lon - p1_lon); double a = sin(dlat / 2.0) * sin(dlat / 2.0) + cos(to_rad(p1_lat)) * cos(to_rad(p2_lat)) * sin(dlon / 2.0) * sin(dlon / 2.0); double c = 2.0 * atan2(sqrt(a), sqrt(1 - a)); return R * c; //return is in meters because radius is in meters } // Returns the distance between 2 gps points (uses haversine formula if needed) double distance_equirectangular_2p(double p1_lat, double p1_lon, double p2_lat, double p2_lon) { //use the haversine formula for very far points if (fabs(p1_lat - p2_lat) > 0.05 || fabs(p1_lat - p2_lat) > 0.05) { //~5.5km at equator to ~2km at arctic circle mlt_log_info(NULL, "distance_equirectangular_2p: points are too far away, doing haversine (%f,%f " "to %f,%f)\n", p1_lat, p1_lon, p2_lat, p2_lon); return distance_haversine_2p(p1_lat, p1_lon, p2_lat, p2_lon); } const int R = 6371000; double x = (to_rad(p2_lon) - to_rad(p1_lon)) * cos((to_rad(p2_lat) + to_rad(p1_lat)) / 2.0); double y = to_rad(p1_lat) - to_rad(p2_lat); return sqrt(x * x + y * y) * R; //NOTE: adding altitude is very risky, GPS altitude seems pretty bad and with sudden large jumps //maybe consider adding distance_2d and distance_3d fields //sqrt(2D_distance^2 + delta_alt^2) } // Computes bearing from 2 gps points double bearing_2p(double p1_lat, double p1_lon, double p2_lat, double p2_lon) { double lat1 = to_rad(p1_lat); double lat2 = to_rad(p2_lat); double dlon = to_rad(p2_lon - p1_lon); double y = sin(dlon) * cos(lat2); double x = cos(lat1) * sin(lat2) - sin(lat1) * cos(lat2) * cos(dlon); double bearing = to_deg(atan2(y, x)); //normalize to 0-360 return fmod(bearing + 360, 360); } /** Searches for and returns the first valid time with locaton in the gps_points_r array * Returns in miliesconds, 0 on error */ void get_first_gps_time(gps_private_data gdata) { gps_point_raw *gps_points = gdata.gps_points_r; if (!gps_points) { *gdata.first_gps_time = 0; return; } int i = 0; while (i < *gdata.gps_points_size) { if (gps_points[i].time && has_valid_location(gps_points[i])) { //mlt_log_info(gdata.filter, "get_first_gps_time found: %d", gps_points[i].time); *gdata.first_gps_time = gps_points[i].time; return; } i++; } *gdata.first_gps_time = 0; } /** Searches for and returns the last valid time with location in the gps_points_r array * returns in miliesconds, returns 0 on error */ void get_last_gps_time(gps_private_data gdata) { gps_point_raw *gps_points = gdata.gps_points_r; if (!gps_points) { *gdata.last_gps_time = 0; return; } int i = *gdata.gps_points_size - 1; while (i >= 0) { if (gps_points[i].time && has_valid_location(gps_points[i])) { //mlt_log_info(gdata.filter, "get_last_gps_time found: %d", gps_points[i].time); *gdata.last_gps_time = gps_points[i].time; return; } i--; } *gdata.last_gps_time = 0; } //checks if time value val is between gps_points[i] and gps_points[i+1] with size checks //note: size must be inclusive (ie: gps_points[size] valid!) int time_val_between_indices_raw( int64_t time_val, gps_point_raw *gp, int i, int size, int max_gps_diff_ms, bool force_result) { if (i < 0 || i > size) return 0; else if (time_val == gp[i].time) return 1; else if (i + 1 <= size && gp[i].time <= time_val && time_val < gp[i + 1].time) { if (force_result) return 1; else if (llabs(gp[i + 1].time - gp[i].time) <= max_gps_diff_ms) return 1; } return 0; } //checks if time value val is between gps_points[i] and gps_points[i+1] with size checks //note: size must be inclusive (ie: gps_points[size] valid) int time_val_between_indices_proc( int64_t time_val, gps_point_proc *gp, int i, int size, int max_gps_diff_ms, bool force_result) { if (i < 0 || i > size) return 0; else if (time_val == gp[i].time) return 1; else if (i + 1 <= size && gp[i].time <= time_val && time_val < gp[i + 1].time) { if (force_result) return 1; else if (llabs(gp[i + 1].time - gp[i].time) <= max_gps_diff_ms) return 1; } return 0; } /** Returns the [index of] nearest gps point in time, but not farther than MAX_GPS_DIFF (10 seconds) * or -1 if search fails * Searches in raw values directly * If force_result is nonzero, it will ignore MAX_GPS_DIFF restriction */ int binary_search_gps(gps_private_data gdata, int64_t video_time, bool force_result /* = false */) { gps_point_raw *gps_points = gdata.gps_points_r; const int gps_points_size = *gdata.gps_points_size - 1; //size-1 !! int last_index = *gdata.last_searched_index; int max_gps_diff_ms = get_max_gps_diff_ms(gdata); int il = 0; int ir = gps_points_size; int mid = 0; if (!gps_points || gps_points_size <= 0) return -1; //optimize repeated calls (exact match or in between points) if (time_val_between_indices_raw(video_time, gps_points, last_index, gps_points_size, max_gps_diff_ms, force_result)) { // printf("_binary_search_gps, last_index(%d) v1 video_time: %I64d match: %I64d\n", last_index, video_time, gps_points[last_index].time); return last_index; } //optimize consecutive playback calls last_index++; if (time_val_between_indices_raw(video_time, gps_points, last_index, gps_points_size, max_gps_diff_ms, force_result)) { *gdata.last_searched_index = last_index; return last_index; } //optimize for the previous index last_index -= 2; //cancel the +1 above if (last_index >= 0 && time_val_between_indices_raw(video_time, gps_points, last_index, gps_points_size, max_gps_diff_ms, force_result)) { *gdata.last_searched_index = last_index; return last_index; } //optimize for outside values, if force result it will return 0 or (size-1) if (video_time < *gdata.first_gps_time - max_gps_diff_ms) return (force_result ? 0 : -1); if (video_time > *gdata.last_gps_time + max_gps_diff_ms) return (force_result ? gps_points_size : -1); //binary search while (il < ir) { mid = (ir + il) / 2; //mlt_log_info(gdata.filter, "binary_search_gps: mid=%d, l=%d, r=%d, mid-time=%d (s)", mid, il, ir, gps_points[mid].time/1000); if (time_val_between_indices_raw(video_time, gps_points, mid, gps_points_size, max_gps_diff_ms, force_result)) { *gdata.last_searched_index = mid; break; } else if (gps_points[mid].time > video_time) ir = mid; else il = mid + 1; } //don't return the closest gps point if time difference is too large (unless force_result is 1) if (llabs(video_time - gps_points[mid].time) > max_gps_diff_ms) return (force_result ? mid : -1); else return mid; } /** Returns a nicer number of decimal values for floats * [ 1.23m | 12.3m | 123m ] */ int decimals_needed(double x) { if (fabs(x) < 10) return 2; if (fabs(x) < 100) return 1; return 0; } /** Returns a nicer number of decimal values for floats, max 1 digit after . * [ 1.2% | 12% ] */ int decimals_needed_maxone(double x) { if (fabs(x) < 10) return 1; return 0; } /** Converts the distance (stored in meters) to the unit requested in extended keyword */ double convert_distance_to_format(double x, const char *format) { if (format == NULL) return x; if (strstr(format, "km") || strstr(format, "kilometer")) return x / 1000.0; else if (strstr(format, "mi") || strstr(format, "mile")) return x * 0.00062137; else if (strstr(format, "nm") || strstr(format, "nautical")) return x * 0.0005399568; else if (strstr(format, "ft") || strstr(format, "feet")) return x * 3.2808399; return x; } /** Converts the speed (stored in meters/second) to the unit requested in extended keyword */ double convert_speed_to_format(double x, const char *format) { //order is important as short keywords will match anywhere (ms in kms and mi in min[utes]) if (format == NULL || strstr(format, "kms") || strstr(format, "km/s") || strstr(format, "kilometer")) return x * 3.6; //default km/h if (strstr(format, "ms") || strstr(format, "m/s") || strstr(format, "meter")) return x; if (strstr(format, "mmin") || strstr(format, "m/min")) return x * 60; if (strstr(format, "ftmin") || strstr(format, "ft/min")) return x * 196.850393; if (strstr(format, "mi") || strstr(format, "mi/h") || strstr(format, "mile")) return x * 2.23693629; if (strstr(format, "kn") || strstr(format, "nm/h") || strstr(format, "knots")) return x * 1.94384449; if (strstr(format, "ft") || strstr(format, "ft/s") || strstr(format, "feet")) return x * 3.2808399; return x * 3.6; } /* Converts the bearing angle (0-360) to a cardinal direction * 8 sub-divisions */ const char *bearing_to_compass(double x) { if (x <= 22.5 || x >= 360 - 22.5) return "N"; else if (x < 45 + 22.5) return "NE"; else if (x <= 90 + 22.5) return "E"; else if (x < 90 + 45 + 22.5) return "SE"; else if (x <= 180 + 22.5) return "S"; else if (x < 180 + 45 + 22.5) return "SW"; else if (x <= 270 + 22.5) return "W"; else if (x < 270 + 45 + 22.5) return "NW"; return "-"; } // Updates gps-coords-based data in gps_points_p using already smoothed lat/lon void recalculate_gps_data(gps_private_data gdata) { int i; int req_smooth = gdata.last_smooth_lvl; if (req_smooth == 0) return; if (gdata.gps_points_r == NULL) { mlt_log_warning(gdata.filter, "recalculate_gps_data - gps_points_r is null!"); return; } if (gdata.gps_points_p == NULL) { if ((*gdata.ptr_to_gps_points_p = (gps_point_proc *) calloc(*gdata.gps_points_size, sizeof(gps_point_proc))) == NULL) { mlt_log_warning(gdata.filter, "calloc error, size=%u\n", (unsigned) (*gdata.gps_points_size * sizeof(gps_point_proc))); return; } else { //alloc ok gdata.gps_points_p = *gdata.ptr_to_gps_points_p; process_gps_smoothing(gdata, 0); } } gps_point_proc *gps_points = gdata.gps_points_p; const int gps_points_size = *gdata.gps_points_size; //compute gps_start_time actual offset int offset_start = 0; if (gdata.gps_proc_start_t != 0) offset_start = binary_search_gps(gdata, gdata.gps_proc_start_t, true) + 1; //mlt_log_info(gdata.filter, "recalculate_gps_data, offset=%d, points=%p, new:%p, size:%d, newSize:%d", offset, gdata.gps_points, gps_points, *gdata.gps_points_size, gps_points_size); int ignore_points_before = 0; double total_dist = 0, total_d_elev = 0, total_elev_up = 0, total_elev_down = 0, total_dist_up = 0, total_dist_down = 0, total_dist_flat = 0; double start_dist = 0, start_d_elev = 0, start_elev_up = 0, start_elev_down = 0, start_dist_up = 0, start_dist_down = 0, start_dist_flat = 0; gps_point_proc *crt_point = NULL, *prev_point = NULL, *prev_nrsmooth_point = NULL; int grade_bucket[100], speed_avg_count = 0; double speed_avg = 0, last_grade = 0; memset(&grade_bucket, 0, 100 * sizeof(int)); for (i = 0; i < gps_points_size; i++) { //store values at processing_start_time to substract them at the end if (i - 1 == offset_start) { start_dist = total_dist; start_d_elev = total_d_elev; start_elev_up = total_elev_up; start_elev_down = total_elev_down; start_dist_up = total_dist_up; start_dist_down = total_dist_down; start_dist_flat = total_dist_flat; } crt_point = &gps_points[i]; //this is the farthest valid point (behind current) that can be used for smoothing, limited by start of array or big gap in time int smooth_index = MAX(ignore_points_before, MAX(0, i - req_smooth)); //ignore points with missing lat/lon, some devices use 0,0 for no fix so we ignore those too if (!has_valid_location_ptr(crt_point) || (!crt_point->lat && !crt_point->lon)) { //set the last valid values to these points so output won't be "--" crt_point->total_dist = total_dist; crt_point->d_elev = 0; crt_point->elev_up = total_elev_up; crt_point->elev_down = total_elev_down; crt_point->dist_up = total_dist_up; crt_point->dist_down = total_dist_down; crt_point->dist_flat = total_dist_flat; continue; } //previous valid point must exist for math to happen if (prev_point == NULL) { prev_point = crt_point; //first local valid point, just set its distance to 0, other stuff can't be calculated crt_point->total_dist = total_dist; continue; } //find a valid point from i-req_smooth to i (to use in smoothing calc) while (smooth_index < i && !has_valid_location(gps_points[smooth_index])) smooth_index++; if (smooth_index < i) prev_nrsmooth_point = &gps_points[smooth_index]; else prev_nrsmooth_point = NULL; //1) get distance difference between last 2 valid points double d_dist = distance_equirectangular_2p(prev_point->lat, prev_point->lon, crt_point->lat, crt_point->lon); double d_elev = 0, d_dist_smoothed = 0, d_elev_smoothed = 0; //the int64 diff will be small enough for double double d_time_sec = (crt_point->time - prev_point->time) / 1000.0; double d_time_smoothed_sec = d_time_sec; //if time difference is way longer for one point than usual, don't do math on that gap (treat recording paused, move far, then resume) if (d_time_sec > 10.0 * (*gdata.last_gps_time - *gdata.first_gps_time) / *gdata.gps_points_size) { prev_nrsmooth_point = NULL; ignore_points_before = i; crt_point->total_dist = total_dist; prev_point = crt_point; continue; } //this is the total distance since beginning of gps processing time total_dist += d_dist; crt_point->total_dist = total_dist; //distance and height difference if smoothed point can be used if (prev_nrsmooth_point) { d_time_smoothed_sec = (crt_point->time - prev_nrsmooth_point->time) / 1000.0; d_dist_smoothed = crt_point->total_dist - prev_nrsmooth_point->total_dist; if (crt_point->ele != GPS_UNINIT && prev_nrsmooth_point->ele != GPS_UNINIT) d_elev_smoothed = crt_point->ele - prev_nrsmooth_point->ele; } //2)+3) speed and bearing if (req_smooth < 2) { crt_point->speed = d_dist / d_time_sec; //in m/s crt_point->bearing = bearing_2p(prev_point->lat, prev_point->lon, crt_point->lat, crt_point->lon); } else if (prev_nrsmooth_point) { //for "smoothing" we calculate distance between 2 points "nr_smoothing" distance behind crt_point->speed = d_dist_smoothed / d_time_smoothed_sec; crt_point->bearing = bearing_2p(prev_nrsmooth_point->lat, prev_nrsmooth_point->lon, crt_point->lat, crt_point->lon); } //continuously compute speed average if (crt_point->speed > 0.27) { speed_avg = (speed_avg * speed_avg_count + crt_point->speed) / (speed_avg_count + 1); ++speed_avg_count; } //4) altitude stuff if (crt_point->ele != GPS_UNINIT && prev_point->ele != GPS_UNINIT) { d_elev = crt_point->ele - prev_point->ele; total_d_elev += d_elev; if (crt_point->ele > prev_point->ele) { total_elev_up += d_elev; total_dist_up += d_dist; } else if (crt_point->ele < prev_point->ele) { total_elev_down += d_elev; total_dist_down += d_dist; } else { total_dist_flat += d_dist; } crt_point->d_elev = total_d_elev; crt_point->elev_up = total_elev_up; crt_point->elev_down = total_elev_down; crt_point->dist_up = total_dist_up; crt_point->dist_down = total_dist_down; crt_point->dist_flat = total_dist_flat; } //5) grade, vertical and 3d speed (if altitude present) if (crt_point->ele != GPS_UNINIT) { bool ok = 1; double used_d_elev = d_elev, used_d_dist = d_dist, used_d_time_sec = d_time_sec; if (prev_nrsmooth_point && prev_nrsmooth_point->ele != GPS_UNINIT) { used_d_elev = d_elev_smoothed; used_d_dist = d_dist_smoothed; used_d_time_sec = d_time_smoothed_sec; } else if (prev_point->ele == GPS_UNINIT) { //this looks cleaner; no previous elevation available ok = 0; } if (ok) { //cut-off grade computation (->just duplicate it) under 2km/h and half the avg speed (but don't cut-off over 15km/h) if (used_d_dist > 0.28 * (req_smooth + 1) //this covers division by 0 && used_d_dist > MIN(speed_avg / 2, 4) * req_smooth) { crt_point->grade_p = 100.0 * used_d_elev / used_d_dist; int indx = CLAMP(abs(crt_point->grade_p) / 10, 0, 99); grade_bucket[indx]++; last_grade = crt_point->grade_p; } else { //better option: set this to GPS_UNINIT and run an interpolation at the end to re-fill crt_point->grade_p = last_grade / 2; } //vertical + 3d speed crt_point->speed_vertical = used_d_elev / used_d_time_sec; crt_point->speed_3d = sqrt(used_d_dist * used_d_dist + used_d_elev * used_d_elev) / used_d_time_sec; } } prev_point = crt_point; } //for gps grade only: trim down the highest 1st percentile of values if (req_smooth > 1) { int nr_1p = gps_points_size / 100; int max_grade_1p = -1; for (i = 99; i > 1; --i) { if (grade_bucket[i] > nr_1p) { max_grade_1p = (i + 1) * 10; break; } } if (max_grade_1p > 1) { for (i = 0; i < gps_points_size; i++) { if (gps_points[i].grade_p != GPS_UNINIT && abs(gps_points[i].grade_p) > max_grade_1p) { //if speed is over 15km/h assume grade is correct if (gps_points[i].speed < MAX(speed_avg / 2, 4)) { // mlt_log_info(NULL, // "clamping [%d].grade_p from %f to %d; speed = %f", // i, // gps_points[i].grade_p, // max_grade_1p, // gps_points[i].speed); gps_points[i].grade_p = (gps_points[i].grade_p >= 0 ? max_grade_1p : -max_grade_1p); } } } } } //clean up computed values for relative stuff (distances mostly) before gps_processing_start time if (gdata.gps_proc_start_t != 0 && offset_start > 0 && offset_start < gps_points_size) { //mlt_log_info(gdata.filter, "recalculate_gps_data: clearing gps data from 0 to %d due to set GPS start time:%d s", offset_start, gdata.gps_proc_start_t/1000); for (i = 0; i < offset_start; i++) { gps_point_proc *crt_point = &(gdata.gps_points_p[i]); if (crt_point->total_dist != 0) start_dist = crt_point->total_dist; crt_point->total_dist = 0; crt_point->d_elev = 0; crt_point->elev_up = 0; crt_point->elev_down = 0; crt_point->dist_up = 0; crt_point->dist_down = 0; crt_point->dist_flat = 0; } //remove the distances from before //mlt_log_info(gdata.filter, "recalculate_gps_data: substracting values at start time! (start_dist=%f @ start index=%d)", start_dist, offset_start); for (i = offset_start; i < gps_points_size; i++) { gps_point_proc *crt_point = &(gdata.gps_points_p[i]); crt_point->total_dist -= start_dist; crt_point->d_elev -= start_d_elev; crt_point->elev_up -= start_elev_up; crt_point->elev_down -= start_elev_down; crt_point->dist_up -= start_dist_up; crt_point->dist_down -= start_dist_down; crt_point->dist_flat -= start_dist_flat; } } } /* Returns a weighted (type:double) value for an intermediary time * Notes: time limit 10s, if one value is GPS_UNINIT, returns the other */ double weighted_middle_double( double v1, int64_t t1, double v2, int64_t t2, int64_t new_t, int max_gps_diff_ms) { int64_t d_time = t2 - t1; if (v1 == GPS_UNINIT) return v2; if (v2 == GPS_UNINIT) return v1; if (d_time > max_gps_diff_ms || d_time == 0) return v1; double prev_weight = 1 - (double) (new_t - t1) / d_time; double next_weight = 1 - (double) (t2 - new_t) / d_time; double rez = v1 * prev_weight + v2 * next_weight; //mlt_log_info(NULL, "weighted_middle_double in: v:%f-%f, %d-%d %d out: %f ", v1, v2, t1/1000, t2/1000, new_t/1000, rez); return rez; } /* Returns a weighted (type:int64_t) value for an intermediary time * Notes: time limit 10s, if one value is GPS_UNINIT, returns the other */ int64_t weighted_middle_int64( int64_t v1, int64_t t1, int64_t v2, int64_t t2, int64_t new_t, int max_gps_diff_ms) { int64_t d_time = t2 - t1; if (v1 == GPS_UNINIT) return v2; if (v2 == GPS_UNINIT) return v1; if (d_time > max_gps_diff_ms || d_time == 0) return v1; double prev_weight = 1 - (double) (new_t - t1) / d_time; double next_weight = 1 - (double) (t2 - new_t) / d_time; int64_t rez = v1 * prev_weight + v2 * next_weight; //mlt_log_info(NULL, "weighted_middle_int64 in: v:%d-%d, %d-%d %d out: %d (weights: prev=%f, next=%f)", v1%1000000, v2%1000000, t1/1000, t2/1000, new_t/1000, rez%1000000, prev_weight, next_weight); return rez; } //compute a virtual point at a specific time between 2 real points gps_point_proc weighted_middle_point_proc(gps_point_proc *p1, gps_point_proc *p2, int64_t new_t, int max_gps_diff_ms) { if (p1 == p2) return *p1; if (llabs(p2->time - p1->time) > max_gps_diff_ms) return *p1; if (new_t < MIN(p1->time, p2->time)) return *p1; if (new_t > MAX(p1->time, p2->time)) return *p2; gps_point_proc crt_point = uninit_gps_proc_point; crt_point.lat = weighted_middle_double(p1->lat, p1->time, p2->lat, p2->time, new_t, max_gps_diff_ms); crt_point.lon = weighted_middle_double(p1->lon, p1->time, p2->lon, p2->time, new_t, max_gps_diff_ms); crt_point.speed = weighted_middle_double(p1->speed, p1->time, p2->speed, p2->time, new_t, max_gps_diff_ms); crt_point.speed_vertical = weighted_middle_double(p1->speed_vertical, p1->time, p2->speed_vertical, p2->time, new_t, max_gps_diff_ms); crt_point.speed_3d = weighted_middle_double(p1->speed_3d, p1->time, p2->speed_3d, p2->time, new_t, max_gps_diff_ms); crt_point.total_dist = weighted_middle_double(p1->total_dist, p1->time, p2->total_dist, p2->time, new_t, max_gps_diff_ms); crt_point.ele = weighted_middle_double(p1->ele, p1->time, p2->ele, p2->time, new_t, max_gps_diff_ms); crt_point.time = weighted_middle_int64(p1->time, p1->time, p2->time, p2->time, new_t, max_gps_diff_ms); crt_point.d_elev = weighted_middle_double(p1->d_elev, p1->time, p2->d_elev, p2->time, new_t, max_gps_diff_ms); crt_point.elev_up = weighted_middle_double(p1->elev_up, p1->time, p2->elev_up, p2->time, new_t, max_gps_diff_ms); crt_point.elev_down = weighted_middle_double(p1->elev_down, p1->time, p2->elev_down, p2->time, new_t, max_gps_diff_ms); crt_point.dist_up = weighted_middle_double(p1->dist_up, p1->time, p2->dist_up, p2->time, new_t, max_gps_diff_ms); crt_point.dist_down = weighted_middle_double(p1->dist_down, p1->time, p2->dist_down, p2->time, new_t, max_gps_diff_ms); crt_point.dist_flat = weighted_middle_double(p1->dist_flat, p1->time, p2->dist_flat, p2->time, new_t, max_gps_diff_ms); crt_point.bearing = weighted_middle_double(p1->bearing, p1->time, p2->bearing, p2->time, new_t, max_gps_diff_ms); crt_point.hr = weighted_middle_double(p1->hr, p1->time, p2->hr, p2->time, new_t, max_gps_diff_ms); crt_point.cad = weighted_middle_double(p1->cad, p1->time, p2->cad, p2->time, new_t, max_gps_diff_ms); crt_point.grade_p = weighted_middle_double(p1->grade_p, p1->time, p2->grade_p, p2->time, new_t, max_gps_diff_ms); crt_point.atemp = weighted_middle_double(p1->atemp, p1->time, p2->atemp, p2->time, new_t, max_gps_diff_ms); return crt_point; } //checks whether 2 points (not necessarily consecutive) are not after a long pause in tracking int in_gps_time_window(gps_private_data gdata, int crt, int next) { gps_point_raw *gp = gdata.gps_points_r; int64_t d_time = llabs(gp[next].time - gp[crt].time); int d_indices = abs(next - crt); return d_time <= (get_avg_gps_time_ms(gdata) * d_indices + get_max_gps_diff_ms(gdata)); } /* Processes the entire gps_points_p array to fill the lat, lon values * Also does linear interpolation of HR, altitude (+lat/lon*) if necessary to fill missing values * After this, if do_processing is 1, calls recalculate_gps_data to update distance, speed + other fields for all points * Returns without doing anything if smoothing level is 0 */ void process_gps_smoothing(gps_private_data gdata, char do_processing) { int req_smooth = gdata.last_smooth_lvl; if (gdata.last_smooth_lvl == 0) return; if (gdata.gps_points_r == NULL) { mlt_log_warning(gdata.filter, "process_gps_smoothing - gps_points_r is null!\n"); return; } if (gdata.gps_points_p == NULL) { if ((*gdata.ptr_to_gps_points_p = (gps_point_proc *) calloc(*gdata.gps_points_size, sizeof(gps_point_proc))) == NULL) { mlt_log_warning(gdata.filter, "calloc failed, size = %u\n", (unsigned) (*gdata.gps_points_size * sizeof(gps_point_proc))); return; } else gdata.gps_points_p = *gdata.ptr_to_gps_points_p; } int max_gps_diff_ms = get_max_gps_diff_ms(gdata); int i, j, nr_hr = 0, nr_ele = 0, nr_cad = 0, nr_atemp = 0; double hr = GPS_UNINIT, ele = GPS_UNINIT, cad = GPS_UNINIT, atemp = GPS_UNINIT; //linear interpolation for heart rate, elevation, cadence and temperature, one time per file, ignores start offset if (*gdata.interpolated == 0) { //figure out how many seconds are between 2 average fixes so we can set a limit (in time) to interpolation double avg_time = (*gdata.last_gps_time - *gdata.first_gps_time) / 1000 / (double) *gdata.gps_points_size; double nr_one_minute = 60.0 / (avg_time ? avg_time : 1); gps_point_raw *gp_r = gdata.gps_points_r; gps_point_proc *gp_p = gdata.gps_points_p; for (i = 0; i < *gdata.gps_points_size; i++) { //calloc made everything 0, fill back with GPS_UNINIT if needed gp_p[i].hr = gp_r[i].hr; gp_p[i].ele = gp_r[i].ele; gp_p[i].cad = gp_r[i].cad; gp_p[i].atemp = gp_r[i].atemp; //heart rate if (gp_r[i].hr != GPS_UNINIT) { //found valid hr if (hr != GPS_UNINIT && nr_hr > 0 && nr_hr <= nr_one_minute) { //there were missing values and had a hr before nr_hr++; for (j = i; j > i - nr_hr; j--) { //go backwards and fill values gp_p[j].hr = hr + (gp_r[i].hr - hr) * (1.0 * (j - (i - nr_hr)) / nr_hr); //printf("_i=%d, j=%d, nr_hr=%d hr = %d; gp_r[i].hr=%f, gp_p[j].hr=%f \n", i,j,nr_hr, hr, gp_r[i].hr, gp_p[j].hr); } } hr = gp_r[i].hr; nr_hr = 0; } else nr_hr++; //altitude if (gp_r[i].ele != GPS_UNINIT) { if (ele != GPS_UNINIT && nr_ele > 0 && nr_ele <= nr_one_minute * 10) { nr_ele++; for (j = i; j > i - nr_ele; j--) { gp_p[j].ele = ele + 1.0 * (gp_r[i].ele - ele) * (1.0 * (j - (i - nr_ele)) / nr_ele); //printf("_i=%d, j=%d, nr_ele=%d ele = %f; gp_r[i].ele=%f, gp_p[j].ele=%f \n", i,j,nr_ele, ele, gp_r[i].ele, gp_p[j].ele); } } ele = gp_r[i].ele; nr_ele = 0; } else nr_ele++; //cadence if (gp_r[i].cad != GPS_UNINIT) { if (cad != GPS_UNINIT && nr_cad > 0 && nr_cad <= nr_one_minute) { nr_cad++; for (j = i; j > i - nr_cad; j--) { gp_p[j].cad = cad + 1.0 * (gp_r[i].cad - cad) * (1.0 * (j - (i - nr_cad)) / nr_cad); } } cad = gp_r[i].cad; nr_cad = 0; } else nr_cad++; //temperature if (gp_r[i].atemp != GPS_UNINIT) { if (atemp != GPS_UNINIT && nr_atemp > 0 && nr_atemp <= nr_one_minute * 60) { nr_atemp++; for (j = i; j > i - nr_atemp; j--) { gp_p[j].atemp = atemp + 1.0 * (gp_r[i].atemp - atemp) * (1.0 * (j - (i - nr_atemp)) / nr_atemp); } } atemp = gp_r[i].atemp; nr_atemp = 0; } else { nr_atemp++; } //these are not interpolated but as long as we're iterating we can copy them now gp_p[i].time = gp_r[i].time; gp_p[i].lat = gp_r[i].lat; gp_p[i].lon = gp_r[i].lon; } } gps_point_raw *gps_points_r = gdata.gps_points_r; gps_point_proc *gps_points_p = gdata.gps_points_p; const int gps_points_size = *gdata.gps_points_size; for (i = 0; i < gps_points_size; i++) { if (req_smooth == 1) { //copy raw lat/lon to calc lat/lon and interpolate 1 location if necessary gps_points_p[i].lat = gps_points_r[i].lat; gps_points_p[i].lon = gps_points_r[i].lon; //this can happen often if location and altitude are stored at different time intervals (every 3s vs every 10s) if (i - 1 >= 0 && i + 1 < gps_points_size //if we're not at start/end && !has_valid_location(gps_points_r[i]) && has_valid_location(gps_points_r[i - 1]) && has_valid_location( gps_points_r[i + 1]) //if current point has no lat/lon but nearby ones do && llabs(gps_points_r[i + 1].time - gps_points_r[i - 1].time) < max_gps_diff_ms) //if time difference is lower than max_gps_diff_ms { //place a weighted "middle" point here depending on time difference gps_points_p[i].lat = weighted_middle_double(gps_points_r[i - 1].lat, gps_points_r[i - 1].time, gps_points_r[i + 1].lat, gps_points_r[i + 1].time, gps_points_r[i].time, max_gps_diff_ms); gps_points_p[i].lon = weighted_middle_double(gps_points_r[i - 1].lon, gps_points_r[i - 1].time, gps_points_r[i + 1].lon, gps_points_r[i + 1].time, gps_points_r[i].time, max_gps_diff_ms); //mlt_log_info(gdata.filter, "interpolating position for smooth=1, new point[%d]:%f,%f @time=%d s", i, gps_points_p[i].lat, gps_points_p[i].lon, gps_points_r[i].time/1000); } } else if (req_smooth > 1) { //for each point average "req_smooth/2" values before and after double lat_sum = 0, lon_sum = 0; int nr_div = 0; //passing 180 meridian is ok, but passing both 180 and 0 (so more than one full circle around the earth) will smooth out badly; no fix planned for this for (j = MAX(0, i - req_smooth / 2); j < MIN(i + req_smooth / 2, gps_points_size); j++) { if (has_valid_location(gps_points_r[j]) && in_gps_time_window(gdata, i, j)) { lat_sum += gps_points_r[j].lat; lon_sum += gps_points_r[j].lon; nr_div++; } } if (nr_div != 0) { gps_points_p[i].lat = lat_sum / nr_div; gps_points_p[i].lon = lon_sum / nr_div; } else { gps_points_p[i].lat = gps_points_r[i].lat; gps_points_p[i].lon = gps_points_r[i].lon; } //mlt_log_info(filter, "i=%d, lat_sum=%f, lon_sum=%f, nr_div=%d, time=%d", i, lat_sum, lon_sum, nr_div, gps_points[i].time); } } *gdata.interpolated = 1; if (req_smooth != 0 && do_processing == 1) recalculate_gps_data(gdata); } //File parsing with QT's XML stream reader // Parses a .gpx file into a gps_point_raw linked list void qxml_parse_gpx(QXmlStreamReader &reader, gps_point_ll **gps_list, int *count_pts) { int64_t last_time = -1; //support no time in file because many services (Strava included) do this for exported routes gps_point_ll *no_time_head = NULL, *no_time_prev = NULL; int no_time_count = 0; /* // sample point from .GPX file (version 1.0) + TrackPointExtension 868.3005248873908 0.0 337.1557 //official garmin extension 132 //heart rate in beats per minute 75 //cadence in revolutions per minute 27.5 //ambient temperature in degrees celsius */ while (!reader.atEnd() && !reader.hasError()) { reader.readNext(); // mlt_log_info(NULL, " readNext tag: %s type = %d\n", _qxml_getthestring(reader.name()), reader.tokenType()); if (reader.isStartElement() && reader.name() == QString("trkpt")) { gps_point_raw crt_point = uninit_gps_raw_point; QXmlStreamAttributes attributes = reader.attributes(); if (attributes.hasAttribute("lat")) crt_point.lat = attributes.value("lat").toDouble(); if (attributes.hasAttribute("lon")) crt_point.lon = attributes.value("lon").toDouble(); while (reader.readNext() && !(reader.name() == QString("trkpt") && reader.tokenType() == QXmlStreamReader::EndElement)) //until closing trkpt { // mlt_log_info(NULL, "[trkpt->1] readNext tag: %s, type=%d \n", _qxml_getthestring(reader.name()), reader.tokenType()); if (!reader.isStartElement()) continue; if (reader.name() == QString("ele")) crt_point.ele = reader.readElementText().toDouble(); else if (reader.name() == QString("time")) crt_point.time = datetimeXMLstring_to_mseconds( qUtf8Printable(reader.readElementText())); else if (reader.name() == QString("speed")) crt_point.speed = reader.readElementText().toDouble(); else if (reader.name() == QString("course")) crt_point.bearing = reader.readElementText().toDouble(); else if (reader.name() == QString("extensions")) { reader.readNextStartElement(); if (reader.name() == QString("TrackPointExtension")) { while (reader.readNext() && !(reader.name() == QString("TrackPointExtension") && reader.tokenType() == QXmlStreamReader::EndElement)) { if (reader.name() == QString("hr")) crt_point.hr = reader.readElementText().toDouble(); else if (reader.name() == QString("cad")) crt_point.cad = reader.readElementText().toDouble(); else if (reader.name() == QString("atemp")) crt_point.atemp = reader.readElementText().toDouble(); } } } } //now add the point to linked list (but only if increasing time) if (crt_point.time != GPS_UNINIT && crt_point.time > last_time) { *gps_list = (gps_point_ll *) calloc(1, sizeof(gps_point_ll)); if (*gps_list == NULL) return; *count_pts += 1; (*gps_list)->gp = crt_point; (*gps_list)->next = NULL; gps_list = &(*gps_list)->next; last_time = crt_point.time; } else if (crt_point.time == GPS_UNINIT) { //if no time is stored, we make it up starting from 1 gps_point_ll *tmp = (gps_point_ll *) calloc(1, sizeof(gps_point_ll)); if (tmp == NULL) return; if (no_time_head == NULL) { no_time_head = tmp; no_time_prev = tmp; } else { no_time_prev->next = tmp; no_time_prev = tmp; } no_time_count += 1; crt_point.time = no_time_count * 1000; tmp->gp = crt_point; tmp->next = NULL; } else { mlt_log_info(NULL, "qxml_parse_gpx: skipping point due to time [%d] %f,%f - crt:%u.%u, " "last:%u.%u\n", *count_pts, crt_point.lat, crt_point.lon, (unsigned) (crt_point.time / 1000), (unsigned) (crt_point.time % 1000), (unsigned) (last_time / 1000), (unsigned) (last_time % 1000)); } } } //if no points had time, we replace the real list with the no_time one if (*count_pts == 0 && no_time_count > 0) { mlt_log_info(NULL, "qxml_parse_gpx: all GPS points are missing time values, inserting each " "point at 1 second interval!"); *gps_list = no_time_head; *count_pts = no_time_count; } else if (*count_pts > 0 && no_time_count > 0) { mlt_log_info(NULL, "qxml_parse_gpx: %d GPS points have no time in original file and have been " "discarded (total valid points kept: %d)!", no_time_count, *count_pts); //cleanup the no_time list as we're not using it while (no_time_head) { gps_point_ll *tmp = no_time_head; no_time_head = no_time_head->next; free(tmp); } } } // Parses a .tcx file into a gps_point_raw linked list void qxml_parse_tcx(QXmlStreamReader &reader, gps_point_ll **gps_list, int *count_pts) { int64_t last_time = -1; //support no time in file because many services (Strava included) do this for exported routes gps_point_ll *no_time_head = NULL, *no_time_prev = NULL; int no_time_count = 0; /* //sample point from .TCX file 44.48205950925148 26.1843781367422 65.0 25919.213486999884 124 78 //unofficial, but some popular converters use it 25 //unofficial/not widely used */ while (!reader.atEnd() && !reader.hasError()) { reader.readNext(); // mlt_log_info(NULL, " readNextStartElement tag: %s \n", _qxml_getthestring(reader.name())); if (reader.isStartElement() && reader.name() == QString("Trackpoint")) { gps_point_raw crt_point = uninit_gps_raw_point; while ( reader.readNext() && !(reader.name() == QString("Trackpoint") && reader.tokenType() == QXmlStreamReader::EndElement)) //loop until we hit the closing { // mlt_log_info(NULL, "[Trackpoint->1] readNext tag: %s, type=%d \n", _qxml_getthestring(reader.name()), reader.tokenType()); if (!reader.isStartElement()) continue; if (reader.name() == QString("Time")) crt_point.time = datetimeXMLstring_to_mseconds( qUtf8Printable(reader.readElementText())); else if (reader.name() == QString("Position")) { reader.readNextStartElement(); if (reader.name() == QString("LatitudeDegrees")) crt_point.lat = reader.readElementText().toDouble(); reader.readNextStartElement(); if (reader.name() == QString("LongitudeDegrees")) crt_point.lon = reader.readElementText().toDouble(); } else if (reader.name() == QString("AltitudeMeters")) { crt_point.ele = reader.readElementText().toDouble(); } else if (reader.name() == QString("DistanceMeters")) { crt_point.total_dist = reader.readElementText().toDouble(); } else if (reader.name() == QString("HeartRateBpm")) { reader.readNextStartElement(); if (reader.name() == QString("Value")) crt_point.hr = reader.readElementText().toDouble(); } else if (reader.name() == QString("Cadence")) { crt_point.cad = reader.readElementText().toDouble(); } else if (reader.name() == QString("Temperature")) { crt_point.atemp = reader.readElementText().toDouble(); } } //now add the point to linked list (but only if increasing time) if (crt_point.time != GPS_UNINIT && crt_point.time > last_time) { *gps_list = (gps_point_ll *) calloc(1, sizeof(gps_point_ll)); if (*gps_list == NULL) return; *count_pts += 1; (*gps_list)->gp = crt_point; (*gps_list)->next = NULL; gps_list = &(*gps_list)->next; last_time = crt_point.time; } else if (crt_point.time == GPS_UNINIT) { //if no time is stored, we make it up starting from 1 gps_point_ll *tmp = (gps_point_ll *) calloc(1, sizeof(gps_point_ll)); if (tmp == NULL) return; if (no_time_head == NULL) { no_time_head = tmp; no_time_prev = tmp; } else { no_time_prev->next = tmp; no_time_prev = tmp; } no_time_count += 1; crt_point.time = no_time_count * 1000; tmp->gp = crt_point; tmp->next = NULL; } else mlt_log_info(NULL, "qxml_parse_tcx: skipping point due to time [%d] %f,%f - crt:%u.%u, " "last:%u.%u\n", *count_pts, crt_point.lat, crt_point.lon, (unsigned) (crt_point.time / 1000), (unsigned) (crt_point.time % 1000), (unsigned) (last_time / 1000), (unsigned) (last_time % 1000)); } } //if no points had time, we replace the real list with the no_time one if (*count_pts == 0 && no_time_count > 0) { mlt_log_info(NULL, "qxml_parse_gpx: all GPS points are missing time values, inserting each " "point at 1 second interval!"); *gps_list = no_time_head; *count_pts = no_time_count; } else if (*count_pts > 0 && no_time_count > 0) { //cleanup the no_time list as we're not using it mlt_log_info(NULL, "qxml_parse_gpx: %d GPS points have no time in original file and have been " "discarded (total valid points kept: %d)!", no_time_count, *count_pts); while (no_time_head) { gps_point_ll *tmp = no_time_head; no_time_head = no_time_head->next; free(tmp); } } } /* Reads file, parses it and stores the gps values in a gps_point_raw array (allocated inside) * and its size in gps_points_size. * swap_to_180 -> shifts all longitudes around with 180 becoming 0, only use it if user requests it * Returns 0 on failure, 1 on success. */ int qxml_parse_file(gps_private_data gdata) { gps_point_ll *gps_list_head = NULL, *tmp = NULL; int count_pts = 0, nr = 0; char *filename = gdata.last_filename; gps_point_raw *gps_array = NULL; int &swap_to_180 = *gdata.swap180; // mlt_log_info(gdata.filter, "in qxml_parse_file, filename: %s, swap_to_180=%d\n", filename, swap_to_180); QFile gps_file(filename); if (!gps_file.open(QFile::ReadOnly | QFile::Text)) { mlt_log_warning(gdata.filter, "qxml_parse_file couldn't read file: %s", filename); return 0; } QXmlStreamReader qxml_reader(&gps_file); qxml_reader.setNamespaceProcessing(false); while (!qxml_reader.atEnd() && !qxml_reader.hasError()) { qxml_reader.readNextStartElement(); if (qxml_reader.isStartDocument()) { // mlt_log_info(NULL, "qxml_parse_file: StartDocument found. Encoding=%s\n", qUtf8Printable(qxml_reader.documentEncoding().toString())); continue; } // mlt_log_info(NULL, " readNext element tag: %s, type=%d \n", qUtf8Printable(qxml_reader.name().toString()), qxml_reader.tokenType()); if (qxml_reader.name() == QString("TrainingCenterDatabase")) { qxml_parse_tcx(qxml_reader, &gps_list_head, &count_pts); } else if (qxml_reader.name() == QString("gpx")) { qxml_parse_gpx(qxml_reader, &gps_list_head, &count_pts); } else { mlt_log_warning( gdata.filter, "qxml_parse_file: fail to parse file: %s, unknown root element: %s. Aborting. \n", filename, qUtf8Printable(qxml_reader.name().toString())); return 0; } } if (qxml_reader.hasError()) { mlt_log_info(NULL, "qxml_reader.hasError! line:%u, errString:%s\n", (unsigned) qxml_reader.lineNumber(), qUtf8Printable(qxml_reader.errorString())); return 0; } qxml_reader.clear(); //not sure if really needed if not reused if (count_pts < 2) { mlt_log_warning(gdata.filter, "qxml_parse_file: less than 2 gps points read (%d). Aborting. \n", count_pts); return 0; } *gdata.ptr_to_gps_points_r = (gps_point_raw *) calloc(count_pts, sizeof(gps_point_raw)); gps_array = *gdata.ptr_to_gps_points_r; //just an alias if (gps_array == NULL) { mlt_log_error(gdata.filter, "malloc error (size=%u)\n", (unsigned) (count_pts * sizeof(gps_point_raw))); } *gdata.gps_points_size = count_pts; //copy points to private array and free list while (gps_list_head) { gps_array[nr++] = gps_list_head->gp; tmp = gps_list_head; gps_list_head = gps_list_head->next; free(tmp); } //search to find if we cross the 180 and/or the 0 meridian bool crosses0 = false, crosses180 = false; for (int i = 0; i < *gdata.gps_points_size - 1; i++) { double crt = gps_array[i].lon; double next = gps_array[i + 1].lon; if (crt == GPS_UNINIT || next == GPS_UNINIT) continue; if ((crt < 0 && next > 0) || (crt > 0 && next < 0)) { if (crt - next > 180 || next - crt > 180) crosses180 = true; else crosses0 = true; } } mlt_log_info( NULL, "_automatic 180 meridian detection: crosses180=%d, crosses0=%d --> swapping180=%d\n", crosses180, crosses0, (crosses180 && !crosses0)); //if only 180 is crossed, we swap, otherswise we don't do anything if (crosses180 && !crosses0) { swap_to_180 = 1; for (int i = 0; i < *gdata.gps_points_size; i++) gps_array[i].lon = get_180_swapped(gps_array[i].lon); } else swap_to_180 = 0; // //debug result: // for (int i = 0; i < *gdata.gps_points_size; i += 100) { // gps_point_raw *crt_point = &gps_array[i]; // mlt_log_info(NULL, // "_xml_parse_file read point[%d]: time:%d, lat:%f, lon:%f, ele:%f, " // "distance:%f, hr:%f, bearing:%f, cadence:%f, temp:%f\n", // i, // crt_point->time / 1000, // crt_point->lat, // crt_point->lon, // crt_point->ele, // crt_point->total_dist, // crt_point->hr, // crt_point->bearing, // crt_point->cad, // crt_point->atemp); // } return 1; }mlt-7.22.0/src/modules/qt/gps_parser.h000664 000000 000000 00000021000 14531534050 017550 0ustar00rootroot000000 000000 /* * gps_parser.h -- Header for gps parsing (.gpx and .tcx) and processing * Copyright (C) 2011-2021 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef GPS_PARSER_H #define GPS_PARSER_H #include #include #include #include #define GPS_UNINIT -9999 typedef struct { double lat, lon, speed, total_dist, ele, hr, bearing, cad, atemp; int64_t time; //epoch milliseconds } gps_point_raw; typedef struct gps_point_raw_list { gps_point_raw gp; struct gps_point_raw_list *next; } gps_point_ll; typedef struct { double lat, lon, speed, speed_vertical, speed_3d, total_dist, ele, hr, bearing, cad, atemp; int64_t time; double d_elev, elev_up, elev_down, dist_up, dist_down, dist_flat, grade_p; } gps_point_proc; //0 is a valid value for many fields, use GPS_UNINIT (-9999) to differentiate missing values static const gps_point_raw uninit_gps_raw_point = {.lat = GPS_UNINIT, .lon = GPS_UNINIT, .speed = GPS_UNINIT, .total_dist = GPS_UNINIT, .ele = GPS_UNINIT, .hr = GPS_UNINIT, .bearing = GPS_UNINIT, .cad = GPS_UNINIT, .atemp = GPS_UNINIT, .time = GPS_UNINIT}; static const gps_point_proc uninit_gps_proc_point = {.lat = GPS_UNINIT, .lon = GPS_UNINIT, .speed = GPS_UNINIT, .speed_vertical = GPS_UNINIT, .speed_3d = GPS_UNINIT, .total_dist = GPS_UNINIT, .ele = GPS_UNINIT, .hr = GPS_UNINIT, .bearing = GPS_UNINIT, .cad = GPS_UNINIT, .atemp = GPS_UNINIT, .time = GPS_UNINIT, .d_elev = GPS_UNINIT, .elev_up = GPS_UNINIT, .elev_down = GPS_UNINIT, .dist_up = GPS_UNINIT, .dist_down = GPS_UNINIT, .dist_flat = GPS_UNINIT, .grade_p = GPS_UNINIT}; //structure used to ease argument passing between filter and GPS parser api typedef struct { gps_point_raw *gps_points_r; //raw gps data from file gps_point_proc *gps_points_p; //processed gps data gps_point_raw **ptr_to_gps_points_r; //if malloc is needed gps_point_proc **ptr_to_gps_points_p; int *gps_points_size; int *last_searched_index; //used to cache repeated searches int64_t *first_gps_time; int64_t *last_gps_time; char *interpolated; int *swap180; //read only: int64_t gps_proc_start_t; //process only points after this time (epoch miliseconds) int last_smooth_lvl; char *last_filename; //gps file fullpath mlt_filter filter; } gps_private_data; #define has_valid_location(x) (((x).lat) != GPS_UNINIT && ((x).lon) != GPS_UNINIT) #define has_valid_location_ptr(x) ((x) && ((x)->lat) != GPS_UNINIT && ((x)->lon) != GPS_UNINIT) #define MATH_PI 3.14159265358979323846 #define to_rad(x) ((x) *MATH_PI / 180.0) #define to_deg(x) ((x) *180.0 / MATH_PI) int64_t datetimeXMLstring_to_mseconds(const char *text, char *format = NULL); void mseconds_to_timestring(int64_t seconds, char *format, char *result); double distance_haversine_2p(double p1_lat, double p1_lon, double p2_lat, double p2_lon); double distance_equirectangular_2p(double p1_lat, double p1_lon, double p2_lat, double p2_lon); double bearing_2p(double p1_lat, double p1_lon, double p2_lat, double p2_lon); void get_first_gps_time(gps_private_data gdata); void get_last_gps_time(gps_private_data gdata); double get_avg_gps_time_ms(gps_private_data gdata); int get_max_gps_diff_ms(gps_private_data gdata); int binary_search_gps(gps_private_data gdata, int64_t video_time, bool force_result = false); int decimals_needed(double x); int decimals_needed_maxone(double x); double convert_distance_to_format(double x, const char *format); double convert_speed_to_format(double x, const char *format); const char *bearing_to_compass(double x); void recalculate_gps_data(gps_private_data gdata); void process_gps_smoothing(gps_private_data gdata, char do_processing); double weighted_middle_double( double v1, int64_t t1, double v2, int64_t t2, int64_t new_t, int max_gps_diff_ms); int64_t weighted_middle_int64( int64_t v1, int64_t t1, int64_t v2, int64_t t2, int64_t new_t, int max_gps_diff_ms); gps_point_proc weighted_middle_point_proc(gps_point_proc *p1, gps_point_proc *p2, int64_t new_t, int max_gps_diff_ms); void qxml_parse_gpx(QXmlStreamReader &reader, gps_point_ll **gps_list, int *count_pts); void qxml_parse_tcx(QXmlStreamReader &reader, gps_point_ll **gps_list, int *count_pts); int qxml_parse_file(gps_private_data gdata); #define swap_180_if_needed(lon) (pdata->swap_180 ? get_180_swapped(lon) : lon) double get_180_swapped(double lon); int time_val_between_indices_proc( int64_t time_val, gps_point_proc *gp, int i, int size, int max_gps_diff_ms, bool force_result); int time_val_between_indices_raw( int64_t time_val, gps_point_raw *gp, int i, int size, int max_gps_diff_ms, bool force_result); //TODO: this is copied from src\framework\mlt_producer.c /* * Boost implementation of timegm() * (C) Copyright Howard Hinnant * (C) Copyright 2010-2011 Vicente J. Botet Escriba */ static inline int32_t is_leap(int32_t year) { if (year % 400 == 0) return 1; if (year % 100 == 0) return 0; if (year % 4 == 0) return 1; return 0; } static inline int32_t days_from_0(int32_t year) { year--; return 365 * year + (year / 400) - (year / 100) + (year / 4); } static inline int32_t days_from_1970(int32_t year) { const int days_from_0_to_1970 = days_from_0(1970); return days_from_0(year) - days_from_0_to_1970; } static inline int32_t days_from_1jan(int32_t year, int32_t month, int32_t day) { static const int32_t days[2][12] = {{0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334}, {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335}}; return days[is_leap(year)][month - 1] + day - 1; } static inline time_t internal_timegm(struct tm const *t) { int year = t->tm_year + 1900; int month = t->tm_mon; if (month > 11) { year += month / 12; month %= 12; } else if (month < 0) { int years_diff = (-month + 11) / 12; year -= years_diff; month += 12 * years_diff; } month++; int day = t->tm_mday; int day_of_year = days_from_1jan(year, month, day); int days_since_epoch = days_from_1970(year) + day_of_year; time_t seconds_in_day = 3600 * 24; time_t result = seconds_in_day * days_since_epoch + 3600 * t->tm_hour + 60 * t->tm_min + t->tm_sec; return result; } /* End of Boost implementation of timegm(). */ #endif /* GPS_PARSER_H */ mlt-7.22.0/src/modules/qt/graph.cpp000664 000000 000000 00000022664 14531534050 017060 0ustar00rootroot000000 000000 /* * Copyright (c) 2015-2022 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "graph.h" #include #include #include QVector get_graph_colors(mlt_properties filter_properties, int position, int length) { QVector colors; // Find user specified colors for the gradient while (true) { QString prop_name = QString("color.") + QString::number(colors.size() + 1); if (mlt_properties_exists(filter_properties, prop_name.toUtf8().constData())) { mlt_color mcolor = mlt_properties_anim_get_color(filter_properties, prop_name.toUtf8().constData(), position, length); colors.append(QColor(mcolor.r, mcolor.g, mcolor.b, mcolor.a)); } else { break; } } if (colors.size() == 0) { // No colors found - use a default. colors.append(QColor(Qt::white)); } return colors; } /* * Apply the "bgcolor" and "angle" parameters to the QPainter */ void setup_graph_painter( QPainter &p, QRectF &r, mlt_properties filter_properties, int position, int length) { mlt_color bg_color = mlt_properties_anim_get_color(filter_properties, "bgcolor", position, length); double angle = mlt_properties_anim_get_double(filter_properties, "angle", position, length); p.setRenderHint(QPainter::Antialiasing); // Fill background if (bg_color.r || bg_color.g || bg_color.g || bg_color.a) { QColor qbgcolor(bg_color.r, bg_color.g, bg_color.b, bg_color.a); p.fillRect(0, 0, p.device()->width(), p.device()->height(), qbgcolor); } // Apply rotation if (angle) { p.translate(r.x() + r.width() / 2, r.y() + r.height() / 2); p.rotate(angle); p.translate(-(r.x() + r.width() / 2), -(r.y() + r.height() / 2)); } } /* * Apply the "thickness", "gorient" and "color.x" parameters to the QPainter */ void setup_graph_pen( QPainter &p, QRectF &r, mlt_properties filter_properties, double scale, int position, int length) { int thickness = mlt_properties_anim_get_int(filter_properties, "thickness", position, length) * scale; QString gorient = mlt_properties_get(filter_properties, "gorient"); QVector colors = get_graph_colors(filter_properties, position, length); QPen pen; pen.setWidth(qAbs(thickness)); if (colors.size() == 1) { // Only use one color pen.setBrush(colors[0]); } else { QLinearGradient gradient; if (gorient.startsWith("h", Qt::CaseInsensitive)) { gradient.setStart(r.x(), r.y()); gradient.setFinalStop(r.x() + r.width(), r.y()); } else { // Vertical gradient.setStart(r.x(), r.y()); gradient.setFinalStop(r.x(), r.y() + r.height()); } qreal step = 1.0 / (colors.size() - 1); for (int i = 0; i < colors.size(); i++) { gradient.setColorAt((qreal) i * step, colors[i]); } pen.setBrush(gradient); } p.setPen(pen); } static inline void fix_point(QPointF &point, QRectF &rect) { if (point.x() < rect.x()) { point.setX(rect.x()); } else if (point.x() > rect.x() + rect.width()) { point.setX(rect.x() + rect.width()); } if (point.y() < rect.y()) { point.setY(rect.y()); } else if (point.y() > rect.y() + rect.height()) { point.setY(rect.y() + rect.height()); } } void paint_line_graph(QPainter &p, QRectF &rect, int points, float *values, double tension, int fill) { double width = rect.width(); double height = rect.height(); double pixelsPerPoint = width / (double) (points - 1); // Calculate cubic control points QVector controlPoints((points - 1) * 2); int cpi = 0; // First control point is equal to first point controlPoints[cpi++] = QPointF(rect.x(), rect.y() + height - values[0] * height); // Calculate control points between data points // Based on ideas from: // http://scaledinnovation.com/analytics/splines/aboutSplines.html for (int i = 0; i < (points - 2); i++) { double x0 = rect.x() + (double) i * pixelsPerPoint; double x1 = rect.x() + (double) (i + 1) * pixelsPerPoint; double x2 = rect.x() + (double) (i + 2) * pixelsPerPoint; double y0 = rect.y() + height - values[i] * height; double y1 = rect.y() + height - values[i + 1] * height; double y2 = rect.y() + height - values[i + 2] * height; double d01 = sqrt(pow(x1 - x0, 2) + pow(y1 - y0, 2)); double d12 = sqrt(pow(x2 - x1, 2) + pow(y2 - y1, 2)); double fa = tension * d01 / (d01 + d12); double fb = tension * d12 / (d01 + d12); double p1x = x1 - fa * (x2 - x0); double p1y = y1 - fa * (y2 - y0); QPointF c1(p1x, p1y); fix_point(c1, rect); double p2x = x1 + fb * (x2 - x0); double p2y = y1 + fb * (y2 - y0); QPointF c2(p2x, p2y); fix_point(c2, rect); controlPoints[cpi++] = c1; controlPoints[cpi++] = c2; } // Last control point is equal to last point controlPoints[cpi++] = QPointF(rect.x() + width, rect.y() + height - values[points - 1] * height); // Draw a sequence of bezier curves to interpolate between points. QPainterPath curvePath; // Begin at the first data point. curvePath.moveTo(rect.x(), rect.y() + height - values[0] * height); cpi = 0; for (int i = 1; i < points; i++) { QPointF c1 = controlPoints[cpi++]; QPointF c2 = controlPoints[cpi++]; double endX = rect.x() + (double) i * pixelsPerPoint; double endY = rect.y() + height - values[i] * height; QPointF end(endX, endY); curvePath.cubicTo(c1, c2, end); } if (fill) { curvePath.lineTo(rect.x() + width, rect.y() + height); curvePath.lineTo(rect.x(), rect.y() + height); curvePath.closeSubpath(); p.fillPath(curvePath, p.pen().brush()); } else { p.drawPath(curvePath); } } void paint_bar_graph(QPainter &p, QRectF &rect, int points, float *values) { double width = rect.width(); double height = rect.height(); double pixelsPerPoint = width / (double) points; QPointF bottomPoint(rect.x() + pixelsPerPoint / 2, rect.y() + height); QPointF topPoint(rect.x() + pixelsPerPoint / 2, rect.y()); for (int i = 0; i < points; i++) { topPoint.setY(rect.y() + height - values[i] * height); p.drawLine(bottomPoint, topPoint); bottomPoint.setX(bottomPoint.x() + pixelsPerPoint); topPoint.setX(bottomPoint.x()); } } void paint_segment_graph(QPainter &p, const QRectF &rect, int points, const float *values, const QVector &colors, int segments, int segment_gap, int segment_width) { double pixelsPerPoint = rect.width() / (double) points; if (segment_width > pixelsPerPoint) { segment_width = pixelsPerPoint; } double segment_space = pixelsPerPoint - segment_width; double segmentSize = rect.height() / (double) segments; if (segment_gap >= segmentSize) { segment_gap = segmentSize - 1; } segmentSize = (rect.height() - ((double) segment_gap * (double) (segments - 1))) / (double) segments; for (int i = 0; i < points; i++) { QPointF bottomPoint(rect.x() + (segment_space / 2.0) + (pixelsPerPoint * (double) i), rect.y() + rect.height()); QPointF topPoint(bottomPoint.x() + segment_width, bottomPoint.y() - segmentSize); qreal segmentRatio = 1.0 / (qreal) segments; for (int s = 0; s < segments; s++) { int colorIndex = colors.size() - qRound((qreal) s / (qreal) segments * (qreal) colors.size()) - 1; colorIndex = qBound(0, colorIndex, colors.size()); QColor segmentColor = colors[colorIndex]; qreal minSegmentValue = segmentRatio * (qreal) s; qreal maxSegmentValue = segmentRatio * (qreal) (s + 1); if (values[i] < minSegmentValue) { break; } else if (values[i] < maxSegmentValue) { qreal opacity = (values[i] - minSegmentValue) / segmentRatio; segmentColor.setAlphaF(opacity); } p.fillRect(QRectF(topPoint, bottomPoint), segmentColor); bottomPoint.setY(topPoint.y() - segment_gap); topPoint.setY(bottomPoint.y() - segmentSize); } } } mlt-7.22.0/src/modules/qt/graph.h000664 000000 000000 00000003574 14531534050 016524 0ustar00rootroot000000 000000 /* * Copyright (c) 2015-2022 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef GRAPH_H #define GRAPH_H #include #include #include #include #include QVector get_graph_colors(mlt_properties filter_properties, int position, int length); void setup_graph_painter( QPainter &p, QRectF &rect, mlt_properties filter_properties, int position, int length); void setup_graph_pen(QPainter &p, QRectF &rect, mlt_properties filter_properties, double scale, int position, int length); void paint_line_graph(QPainter &p, QRectF &rect, int points, float *values, double tension, int fill); void paint_bar_graph(QPainter &p, QRectF &rect, int points, float *values); void paint_segment_graph(QPainter &p, const QRectF &rect, int points, const float *values, const QVector &colors, int segments, int segment_gap, int segment_width); #endif // GRAPH_H mlt-7.22.0/src/modules/qt/kdenlivetitle_wrapper.cpp000664 000000 000000 00000121633 14531534050 022356 0ustar00rootroot000000 000000 /* * kdenlivetitle_wrapper.cpp -- kdenlivetitle wrapper * Copyright (c) 2009 Marco Gittler * Copyright (c) 2009 Jean-Baptiste Mardelle * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "kdenlivetitle_wrapper.h" #include "typewriter.h" #include "common.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include Q_DECLARE_METATYPE(QTextCursor); Q_DECLARE_METATYPE(std::shared_ptr); // Private Constants static const double PI = 3.14159265358979323846; class ImageItem : public QGraphicsItem { public: ImageItem(QImage img) { m_img = img; } virtual QRectF boundingRect() const { return QRectF(0, 0, m_img.width(), m_img.height()); } virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget *) { painter->setRenderHint(QPainter::SmoothPixmapTransform, true); painter->drawImage(QPoint(), m_img); } private: QImage m_img; }; void blur(QImage &image, int radius) { int tab[] = {14, 10, 8, 6, 5, 5, 4, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2}; int alpha = (radius < 1) ? 16 : (radius > 17) ? 1 : tab[radius - 1]; int r1 = 0; int r2 = image.height() - 1; int c1 = 0; int c2 = image.width() - 1; int bpl = image.bytesPerLine(); int rgba[4]; unsigned char *p; int i1 = 0; int i2 = 3; for (int col = c1; col <= c2; col++) { p = image.scanLine(r1) + col * 4; for (int i = i1; i <= i2; i++) rgba[i] = p[i] << 4; p += bpl; for (int j = r1; j < r2; j++, p += bpl) for (int i = i1; i <= i2; i++) p[i] = (rgba[i] += ((p[i] << 4) - rgba[i]) * alpha / 16) >> 4; } for (int row = r1; row <= r2; row++) { p = image.scanLine(row) + c1 * 4; for (int i = i1; i <= i2; i++) rgba[i] = p[i] << 4; p += 4; for (int j = c1; j < c2; j++, p += 4) for (int i = i1; i <= i2; i++) p[i] = (rgba[i] += ((p[i] << 4) - rgba[i]) * alpha / 16) >> 4; } for (int col = c1; col <= c2; col++) { p = image.scanLine(r2) + col * 4; for (int i = i1; i <= i2; i++) rgba[i] = p[i] << 4; p -= bpl; for (int j = r1; j < r2; j++, p -= bpl) for (int i = i1; i <= i2; i++) p[i] = (rgba[i] += ((p[i] << 4) - rgba[i]) * alpha / 16) >> 4; } for (int row = r1; row <= r2; row++) { p = image.scanLine(row) + c2 * 4; for (int i = i1; i <= i2; i++) rgba[i] = p[i] << 4; p -= 4; for (int j = c1; j < c2; j++, p -= 4) for (int i = i1; i <= i2; i++) p[i] = (rgba[i] += ((p[i] << 4) - rgba[i]) * alpha / 16) >> 4; } } class PlainTextItem : public QGraphicsItem { public: PlainTextItem(QString text, QFont font, double width, double height, QBrush brush, QColor outlineColor, double outline, int align, int lineSpacing) : m_metrics(QFontMetrics(font)) { m_boundingRect = QRectF(0, 0, width, height); m_brush = brush; m_outline = outline; m_pen = QPen(outlineColor); m_pen.setWidthF(outline); m_font = font; m_lineSpacing = lineSpacing + m_metrics.lineSpacing(); m_align = align; m_width = width; updateText(text); } void updateText(QString text) { #if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0) m_path.clear(); #else m_path = QPainterPath(); #endif // Calculate line width QStringList lines = text.split('\n'); double linePos = m_metrics.ascent(); foreach (const QString &line, lines) { QPainterPath linePath; linePath.addText(0, linePos, m_font, line); linePos += m_lineSpacing; if (m_align == Qt::AlignHCenter) { #if (QT_VERSION > QT_VERSION_CHECK(5, 11, 0)) double offset = (m_width - m_metrics.horizontalAdvance(line)) / 2; #else double offset = (m_width - m_metrics.width(line)) / 2; #endif linePath.translate(offset, 0); } else if (m_align == Qt::AlignRight) { #if (QT_VERSION > QT_VERSION_CHECK(5, 11, 0)) double offset = (m_width - m_metrics.horizontalAdvance(line)); #else double offset = (m_width - m_metrics.width(line)); #endif linePath.translate(offset, 0); } m_path.addPath(linePath); } m_path.setFillRule(Qt::WindingFill); } virtual QRectF boundingRect() const { return m_boundingRect; } virtual void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *w) { if (!m_shadow.isNull()) { painter->drawImage(m_shadowOffset, m_shadow); } if (m_outline > 0) { painter->strokePath(m_path.simplified(), m_pen); } painter->fillPath(m_path, m_brush); } void addShadow(QStringList params) { m_params = params; updateShadows(); } void updateShadows() { if (m_params.count() < 5 || m_params.at(0).toInt() == false) { // Invalid or no shadow wanted return; } // Build shadow image QColor shadowColor = QColor(m_params.at(1)); int blurRadius = m_params.at(2).toInt(); int offsetX = m_params.at(3).toInt(); int offsetY = m_params.at(4).toInt(); m_shadow = QImage(m_boundingRect.width() + abs(offsetX) + 4 * blurRadius, m_boundingRect.height() + abs(offsetY) + 4 * blurRadius, QImage::Format_ARGB32_Premultiplied); m_shadow.fill(Qt::transparent); QPainterPath shadowPath = m_path; offsetX -= 2 * blurRadius; offsetY -= 2 * blurRadius; m_shadowOffset = QPoint(offsetX, offsetY); shadowPath.translate(2 * blurRadius, 2 * blurRadius); QPainter shadowPainter(&m_shadow); if (m_outline > 0) { QPainterPathStroker strokePath; strokePath.setWidth(m_outline); QPainterPath stroke = strokePath.createStroke(shadowPath); shadowPath.addPath(stroke); } shadowPainter.fillPath(shadowPath, QBrush(shadowColor)); shadowPainter.end(); blur(m_shadow, blurRadius); } private: QRectF m_boundingRect; QImage m_shadow; QPoint m_shadowOffset; QPainterPath m_path; QBrush m_brush; QPen m_pen; QFont m_font; int m_lineSpacing; int m_align; double m_width; QFontMetrics m_metrics; double m_outline; QStringList m_params; }; QRectF stringToRect(const QString &s) { QStringList l = s.split(','); if (l.size() < 4) return QRectF(); return QRectF(l.at(0).toDouble(), l.at(1).toDouble(), l.at(2).toDouble(), l.at(3).toDouble()) .normalized(); } QColor stringToColor(const QString &s) { QStringList l = s.split(','); if (l.size() < 4) return QColor(); return QColor(l.at(0).toInt(), l.at(1).toInt(), l.at(2).toInt(), l.at(3).toInt()); ; } QTransform stringToTransform(const QString &s) { QStringList l = s.split(','); if (l.size() < 9) return QTransform(); return QTransform(l.at(0).toDouble(), l.at(1).toDouble(), l.at(2).toDouble(), l.at(3).toDouble(), l.at(4).toDouble(), l.at(5).toDouble(), l.at(6).toDouble(), l.at(7).toDouble(), l.at(8).toDouble()); } static void qscene_delete(void *data) { QGraphicsScene *scene = (QGraphicsScene *) data; if (scene) delete scene; scene = NULL; } void loadFromXml(producer_ktitle self, QGraphicsScene *scene, const char *templateXml, const char *templateText) { scene->clear(); mlt_producer producer = &self->parent; self->has_alpha = true; mlt_properties producer_props = MLT_PRODUCER_PROPERTIES(producer); QDomDocument doc; QString data = QString::fromUtf8(templateXml); QString replacementText = QString::fromUtf8(templateText); doc.setContent(data); QDomElement title = doc.documentElement(); // Check for invalid title if (title.isNull() || title.tagName() != "kdenlivetitle") return; // Check title locale if (title.hasAttribute("LC_NUMERIC")) { QString locale = title.attribute("LC_NUMERIC"); QLocale::setDefault(QLocale(locale)); } int originalWidth; int originalHeight; if (title.hasAttribute("width")) { originalWidth = title.attribute("width").toInt(); originalHeight = title.attribute("height").toInt(); scene->setSceneRect(0, 0, originalWidth, originalHeight); } else { originalWidth = scene->sceneRect().width(); originalHeight = scene->sceneRect().height(); } if (title.hasAttribute("out")) { mlt_properties_set_position(producer_props, "_animation_out", title.attribute("out").toDouble()); } else { mlt_properties_set_position(producer_props, "_animation_out", mlt_producer_get_out(producer)); } mlt_properties_set_int(producer_props, "meta.media.width", originalWidth); mlt_properties_set_int(producer_props, "meta.media.height", originalHeight); QDomNode node; QDomNodeList items = title.elementsByTagName("item"); for (int i = 0; i < items.count(); i++) { QGraphicsItem *gitem = NULL; node = items.item(i); QDomNamedNodeMap nodeAttributes = node.attributes(); int zValue = nodeAttributes.namedItem("z-index").nodeValue().toInt(); if (zValue > -1000) { if (nodeAttributes.namedItem("type").nodeValue() == "QGraphicsTextItem") { QDomNamedNodeMap txtProperties = node.namedItem("content").attributes(); QFont font(txtProperties.namedItem("font").nodeValue()); QDomNode propsNode = txtProperties.namedItem("font-bold"); if (!propsNode.isNull()) { // Old: Bold/Not bold. font.setBold(propsNode.nodeValue().toInt()); } else { // New: Font weight (QFont::) font.setWeight( QFont::Weight(txtProperties.namedItem("font-weight").nodeValue().toInt())); } font.setItalic(txtProperties.namedItem("font-italic").nodeValue().toInt()); font.setUnderline(txtProperties.namedItem("font-underline").nodeValue().toInt()); int letterSpacing = txtProperties.namedItem("font-spacing").nodeValue().toInt(); if (letterSpacing != 0) { font.setLetterSpacing(QFont::AbsoluteSpacing, letterSpacing); } // Older Kdenlive version did not store pixel size but point size if (txtProperties.namedItem("font-pixel-size").isNull()) { QFont f2; f2.setPointSize(txtProperties.namedItem("font-size").nodeValue().toInt()); font.setPixelSize(QFontInfo(f2).pixelSize()); } else { font.setPixelSize( txtProperties.namedItem("font-pixel-size").nodeValue().toInt()); } if (!txtProperties.namedItem("letter-spacing").isNull()) { font.setLetterSpacing(QFont::AbsoluteSpacing, txtProperties.namedItem("letter-spacing") .nodeValue() .toInt()); } QColor col(stringToColor(txtProperties.namedItem("font-color").nodeValue())); QString text = node.namedItem("content").firstChild().nodeValue(); if (!replacementText.isEmpty()) { text = text.replace("%s", replacementText); } QColor outlineColor( stringToColor(txtProperties.namedItem("font-outline-color").nodeValue())); int align = 1; if (txtProperties.namedItem("alignment").isNull() == false) { align = txtProperties.namedItem("alignment").nodeValue().toInt(); } double boxWidth = 0; double boxHeight = 0; if (txtProperties.namedItem("box-width").isNull()) { // This is an old version title, find out dimensions from QGraphicsTextItem QGraphicsTextItem *txt = scene->addText(text, font); QRectF br = txt->boundingRect(); boxWidth = br.width(); boxHeight = br.height(); scene->removeItem(txt); delete txt; } else { boxWidth = txtProperties.namedItem("box-width").nodeValue().toDouble(); boxHeight = txtProperties.namedItem("box-height").nodeValue().toDouble(); } QBrush brush; if (txtProperties.namedItem("gradient").isNull() == false) { // Calculate gradient QString gradientData = txtProperties.namedItem("gradient").nodeValue(); QStringList values = gradientData.split(";"); if (values.count() < 5) { // invalid gradient, use default values = QStringList() << "#ff0000" << "#2e0046" << "0" << "100" << "90"; } QLinearGradient gr; gr.setColorAt(values.at(2).toDouble() / 100, values.at(0)); gr.setColorAt(values.at(3).toDouble() / 100, values.at(1)); double angle = values.at(4).toDouble(); if (angle <= 90) { gr.setStart(0, 0); gr.setFinalStop(boxWidth * cos(angle * PI / 180), boxHeight * sin(angle * PI / 180)); } else { gr.setStart(boxWidth, 0); gr.setFinalStop(boxWidth + boxWidth * cos(angle * PI / 180), boxHeight * sin(angle * PI / 180)); } brush = QBrush(gr); } else { brush = QBrush(col); } if (txtProperties.namedItem("compatibility").isNull()) { // Workaround Qt5 crash in threaded drawing of QGraphicsTextItem, paint by ourselves PlainTextItem *txt = new PlainTextItem( text, font, boxWidth, boxHeight, brush, outlineColor, txtProperties.namedItem("font-outline").nodeValue().toDouble(), align, txtProperties.namedItem("line-spacing").nodeValue().toInt()); if (txtProperties.namedItem("shadow").isNull() == false) { QStringList values = txtProperties.namedItem("shadow").nodeValue().split( ";"); txt->addShadow(values); } if (!txtProperties.namedItem("typewriter").isNull()) { // typewriter effect QStringList values = txtProperties.namedItem("typewriter").nodeValue().split(";"); int enabled = (static_cast(values.at(0).toInt())); if (enabled and values.count() >= 5) { mlt_properties_set_int(producer_props, "_animated", 1); std::shared_ptr tw(new TypeWriter); tw->setFrameStep(values.at(1).toInt()); int macro = values.at(2).toInt(); tw->setStepSigma(values.at(3).toInt()); tw->setStepSeed(values.at(4).toInt()); QString pattern; if (macro) { char c = 0; switch (macro) { case 1: c = 'c'; break; case 2: c = 'w'; break; case 3: c = 'l'; break; default: break; } pattern = QString(":%1{%2}").arg(c).arg(text); } else { pattern = text; } tw->setPattern(pattern.toStdString()); tw->parse(); tw->printParseResult(); txt->setData(0, QVariant::fromValue>(tw)); } else { txt->setData(0, QVariant()); } } scene->addItem(txt); gitem = txt; } else { QGraphicsTextItem *txt = scene->addText(text, font); gitem = txt; if (txtProperties.namedItem("font-outline").nodeValue().toDouble() > 0.0) { QTextDocument *doc = txt->document(); // Make sure some that the text item does not request refresh by itself doc->blockSignals(true); QTextCursor cursor(doc); cursor.select(QTextCursor::Document); QTextCharFormat format; format.setTextOutline( QPen(QColor(stringToColor( txtProperties.namedItem("font-outline-color").nodeValue())), txtProperties.namedItem("font-outline").nodeValue().toDouble(), Qt::SolidLine, Qt::RoundCap, Qt::RoundJoin)); format.setForeground(QBrush(col)); cursor.mergeCharFormat(format); } else { txt->setDefaultTextColor(col); } // Effects if (!txtProperties.namedItem("typewriter").isNull()) { // typewriter effect mlt_properties_set_int(producer_props, "_animated", 1); QStringList effetData = QStringList() << "typewriter" << text << txtProperties.namedItem("typewriter").nodeValue(); txt->setData(0, effetData); if (!txtProperties.namedItem("textwidth").isNull()) txt->setData(1, txtProperties.namedItem("textwidth").nodeValue()); } if (txtProperties.namedItem("alignment").isNull() == false) { txt->setTextWidth(txt->boundingRect().width()); QTextOption opt = txt->document()->defaultTextOption(); opt.setAlignment( (Qt::Alignment) txtProperties.namedItem("alignment").nodeValue().toInt()); txt->document()->setDefaultTextOption(opt); } if (!txtProperties.namedItem("kdenlive-axis-x-inverted").isNull()) { //txt->setData(OriginXLeft, txtProperties.namedItem("kdenlive-axis-x-inverted").nodeValue().toInt()); } if (!txtProperties.namedItem("kdenlive-axis-y-inverted").isNull()) { //txt->setData(OriginYTop, txtProperties.namedItem("kdenlive-axis-y-inverted").nodeValue().toInt()); } if (!txtProperties.namedItem("preferred-width").isNull()) { txt->setTextWidth( txtProperties.namedItem("preferred-width").nodeValue().toInt()); } } } else if (nodeAttributes.namedItem("type").nodeValue() == "QGraphicsRectItem" || nodeAttributes.namedItem("type").nodeValue() == "QGraphicsEllipseItem") { QDomNamedNodeMap rectProperties = node.namedItem("content").attributes(); QRectF rect = stringToRect(rectProperties.namedItem("rect").nodeValue()); QString pen_str = rectProperties.namedItem("pencolor").nodeValue(); double penwidth = rectProperties.namedItem("penwidth").nodeValue().toDouble(); QBrush brush; if (!rectProperties.namedItem("gradient").isNull()) { // Calculate gradient QString gradientData = rectProperties.namedItem("gradient").nodeValue(); QStringList values = gradientData.split(";"); if (values.count() < 5) { // invalid gradient, use default values = QStringList() << "#ff0000" << "#2e0046" << "0" << "100" << "90"; } QLinearGradient gr; gr.setColorAt(values.at(2).toDouble() / 100, values.at(0)); gr.setColorAt(values.at(3).toDouble() / 100, values.at(1)); double angle = values.at(4).toDouble(); if (angle <= 90) { gr.setStart(0, 0); gr.setFinalStop(rect.width() * cos(angle * PI / 180), rect.height() * sin(angle * PI / 180)); } else { gr.setStart(rect.width(), 0); gr.setFinalStop(rect.width() + rect.width() * cos(angle * PI / 180), rect.height() * sin(angle * PI / 180)); } brush = QBrush(gr); } else { brush = QBrush( stringToColor(rectProperties.namedItem("brushcolor").nodeValue())); } QPen pen; if (penwidth == 0) { pen = QPen(Qt::NoPen); } else { pen = QPen(QBrush(stringToColor(pen_str)), penwidth, Qt::SolidLine, Qt::SquareCap, Qt::RoundJoin); } if (nodeAttributes.namedItem("type").nodeValue() == "QGraphicsEllipseItem") { QGraphicsEllipseItem *ellipse = scene->addEllipse(rect, pen, brush); gitem = ellipse; } else { // QGraphicsRectItem QGraphicsRectItem *rec = scene->addRect(rect, pen, brush); gitem = rec; } } else if (nodeAttributes.namedItem("type").nodeValue() == "QGraphicsPixmapItem") { const QString url = node.namedItem("content").attributes().namedItem("url").nodeValue(); const QString base64 = items.item(i).namedItem("content").attributes().namedItem("base64").nodeValue(); QImage img; if (base64.isEmpty()) { img.load(url); } else { img.loadFromData(QByteArray::fromBase64(base64.toLatin1())); } ImageItem *rec = new ImageItem(img); scene->addItem(rec); gitem = rec; } else if (nodeAttributes.namedItem("type").nodeValue() == "QGraphicsSvgItem") { QString url = items.item(i).namedItem("content").attributes().namedItem("url").nodeValue(); QString base64 = items.item(i).namedItem("content").attributes().namedItem("base64").nodeValue(); QGraphicsSvgItem *rec = NULL; if (base64.isEmpty()) { rec = new QGraphicsSvgItem(url); } else { rec = new QGraphicsSvgItem(); QSvgRenderer *renderer = new QSvgRenderer(QByteArray::fromBase64(base64.toLatin1()), rec); rec->setSharedRenderer(renderer); } if (rec) { scene->addItem(rec); gitem = rec; } } } //pos and transform if (gitem) { QPointF p(node.namedItem("position").attributes().namedItem("x").nodeValue().toDouble(), node.namedItem("position").attributes().namedItem("y").nodeValue().toDouble()); gitem->setPos(p); gitem->setTransform(stringToTransform( node.namedItem("position").firstChild().firstChild().nodeValue())); int zValue = nodeAttributes.namedItem("z-index").nodeValue().toInt(); gitem->setZValue(zValue); // effects QDomNode eff = items.item(i).namedItem("effect"); if (!eff.isNull()) { QDomElement e = eff.toElement(); if (e.attribute("type") == "blur") { QGraphicsBlurEffect *blur = new QGraphicsBlurEffect(); blur->setBlurRadius(e.attribute("blurradius").toInt()); gitem->setGraphicsEffect(blur); } else if (e.attribute("type") == "shadow") { QGraphicsDropShadowEffect *shadow = new QGraphicsDropShadowEffect(); shadow->setBlurRadius(e.attribute("blurradius").toInt()); shadow->setOffset(e.attribute("xoffset").toInt(), e.attribute("yoffset").toInt()); gitem->setGraphicsEffect(shadow); } } } } QDomNode n = title.firstChildElement("background"); if (!n.isNull()) { QColor color = QColor(stringToColor(n.attributes().namedItem("color").nodeValue())); self->has_alpha = color.alpha() != 255; if (color.alpha() > 0) { QGraphicsRectItem *rec = scene->addRect(0, 0, scene->width(), scene->height(), QPen(Qt::NoPen), QBrush(color)); rec->setZValue(-1100); } } QString startRect; n = title.firstChildElement("startviewport"); // Check if node exists, if it has an x attribute, it is an old version title, don't use viewport if (!n.isNull() && !n.toElement().hasAttribute("x")) { startRect = n.attributes().namedItem("rect").nodeValue(); } n = title.firstChildElement("endviewport"); // Check if node exists, if it has an x attribute, it is an old version title, don't use viewport if (!n.isNull() && !n.toElement().hasAttribute("x")) { QString rect = n.attributes().namedItem("rect").nodeValue(); if (startRect != rect) mlt_properties_set(producer_props, "_endrect", rect.toUtf8().data()); } if (!startRect.isEmpty()) { mlt_properties_set(producer_props, "_startrect", startRect.toUtf8().data()); } return; } int initTitleProducer(mlt_producer producer) { if (!createQApplicationIfNeeded(MLT_PRODUCER_SERVICE(producer))) { return false; } if (!QMetaType::type("QTextCursor")) { qRegisterMetaType("QTextCursor"); } return true; } void drawKdenliveTitle(producer_ktitle self, mlt_frame frame, mlt_image_format format, int width, int height, double position, int force_refresh) { // Obtain the producer mlt_producer producer = &self->parent; mlt_profile profile = mlt_service_profile(MLT_PRODUCER_SERVICE(producer)); mlt_properties producer_props = MLT_PRODUCER_PROPERTIES(producer); // Obtain properties of frame mlt_properties properties = MLT_FRAME_PROPERTIES(frame); pthread_mutex_lock(&self->mutex); // Check if user wants us to reload the image or if we need animation bool animated = mlt_properties_get(producer_props, "_endrect") != NULL; if (mlt_properties_get(producer_props, "_animated") != NULL || force_refresh == 1 || width != self->current_width || height != self->current_height || animated) { if (!animated) { // Cache image only if no animation self->current_image = NULL; mlt_properties_set_data(producer_props, "_cached_image", NULL, 0, NULL, NULL); } mlt_properties_set_int(producer_props, "force_reload", 0); } int image_size = width * height * 4; if (self->current_image == NULL || animated) { // restore QGraphicsScene QGraphicsScene *scene = static_cast( mlt_properties_get_data(producer_props, "qscene", NULL)); self->current_alpha = NULL; if (force_refresh == 1 && scene) { scene = NULL; mlt_properties_set_data(producer_props, "qscene", NULL, 0, NULL, NULL); } if (scene == NULL) { scene = new QGraphicsScene(); scene->setItemIndexMethod(QGraphicsScene::NoIndex); scene->setSceneRect(0, 0, mlt_properties_get_int(properties, "width"), mlt_properties_get_int(properties, "height")); if (mlt_properties_get(producer_props, "resource") && mlt_properties_get(producer_props, "resource")[0] != '\0') { // The title has a resource property, so we read all properties from the resource. // Do not serialize the xmldata loadFromXml(self, scene, mlt_properties_get(producer_props, "_xmldata"), mlt_properties_get(producer_props, "templatetext")); } else { // The title has no resource, all data should be serialized loadFromXml(self, scene, mlt_properties_get(producer_props, "xmldata"), mlt_properties_get(producer_props, "templatetext")); } mlt_properties_set_data(producer_props, "qscene", scene, 0, (mlt_destructor) qscene_delete, NULL); } QRectF start = stringToRect(QString(mlt_properties_get(producer_props, "_startrect"))); QRectF end = stringToRect(QString(mlt_properties_get(producer_props, "_endrect"))); const QRectF source(0, 0, width, height); if (start.isNull()) { start = QRectF(0, 0, mlt_properties_get_int(producer_props, "meta.media.width"), mlt_properties_get_int(producer_props, "meta.media.height")); } // Effects QList items = scene->items(); PlainTextItem *titem = NULL; for (int i = 0; i < items.count(); i++) { titem = dynamic_cast(items.at(i)); if (titem && !titem->data(0).isNull()) { std::shared_ptr ptr = titem->data(0).value>(); titem->updateText(ptr->render(position).c_str()); titem->updateShadows(); } } //must be extracted from kdenlive title self->rgba_image = (uint8_t *) mlt_pool_alloc(image_size); #if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0) // QImage::Format_RGBA8888 was added in Qt5.2 // Initialize the QImage with the MLT image because the data formats match. QImage img(self->rgba_image, width, height, QImage::Format_RGBA8888); #else QImage img(width, height, QImage::Format_ARGB32); #endif img.fill(0); QPainter p1; p1.begin(&img); p1.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) | QPainter::HighQualityAntialiasing #endif ); //| QPainter::SmoothPixmapTransform ); mlt_position anim_out = mlt_properties_get_position(producer_props, "_animation_out"); if (end.isNull()) { scene->render(&p1, source, start, Qt::IgnoreAspectRatio); } else if (position > anim_out) { scene->render(&p1, source, end, Qt::IgnoreAspectRatio); } else { double percentage = 0; if (position && anim_out) percentage = position / anim_out; QPointF topleft = start.topLeft() + (end.topLeft() - start.topLeft()) * percentage; QPointF bottomRight = start.bottomRight() + (end.bottomRight() - start.bottomRight()) * percentage; const QRectF r1(topleft, bottomRight); scene->render(&p1, source, r1, Qt::IgnoreAspectRatio); if (profile && !profile->progressive) { int line = 0; double percentage_next_filed = (position + 0.5) / anim_out; QPointF topleft_next_field = start.topLeft() + (end.topLeft() - start.topLeft()) * percentage_next_filed; QPointF bottomRight_next_field = start.bottomRight() + (end.bottomRight() - start.bottomRight()) * percentage_next_filed; const QRectF r2(topleft_next_field, bottomRight_next_field); QImage img1(width, height, QImage::Format_ARGB32); img1.fill(0); QPainter p2; p2.begin(&img1); p2.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) | QPainter::HighQualityAntialiasing #endif ); scene->render(&p2, source, r2, Qt::IgnoreAspectRatio); p2.end(); int next_field_line = (mlt_properties_get_int(producer_props, "top_field_first") ? 1 : 0); for (line = next_field_line; line < height; line += 2) { memcpy(img.scanLine(line), img1.scanLine(line), img.bytesPerLine()); } } } p1.end(); self->format = mlt_image_rgba; convert_qimage_to_mlt_rgba(&img, self->rgba_image, width, height); self->current_image = (uint8_t *) mlt_pool_alloc(image_size); memcpy(self->current_image, self->rgba_image, image_size); mlt_properties_set_data(producer_props, "_cached_buffer", self->rgba_image, image_size, mlt_pool_release, NULL); mlt_properties_set_data(producer_props, "_cached_image", self->current_image, image_size, mlt_pool_release, NULL); self->current_width = width; self->current_height = height; uint8_t *alpha = NULL; if ((alpha = mlt_frame_get_alpha(frame))) { self->current_alpha = (uint8_t *) mlt_pool_alloc(width * height); memcpy(self->current_alpha, alpha, width * height); mlt_properties_set_data(producer_props, "_cached_alpha", self->current_alpha, width * height, mlt_pool_release, NULL); } } // Convert image to requested format if (format != mlt_image_none && format != mlt_image_movit && format != self->format) { uint8_t *buffer = NULL; if (self->format != mlt_image_rgba) { // Image buffer was previously converted, revert to original rgba buffer self->current_image = (uint8_t *) mlt_pool_alloc(image_size); memcpy(self->current_image, self->rgba_image, image_size); mlt_properties_set_data(producer_props, "_cached_image", self->current_image, image_size, mlt_pool_release, NULL); self->format = mlt_image_rgba; } // First, set the image so it can be converted when we get it mlt_frame_replace_image(frame, self->current_image, self->format, width, height); mlt_frame_set_image(frame, self->current_image, image_size, NULL); self->format = format; // get_image will do the format conversion mlt_frame_get_image(frame, &buffer, &format, &width, &height, 0); // cache copies of the image and alpha buffers if (buffer) { image_size = mlt_image_format_size(format, width, height, NULL); self->current_image = (uint8_t *) mlt_pool_alloc(image_size); memcpy(self->current_image, buffer, image_size); mlt_properties_set_data(producer_props, "_cached_image", self->current_image, image_size, mlt_pool_release, NULL); } if ((buffer = mlt_frame_get_alpha(frame))) { self->current_alpha = (uint8_t *) mlt_pool_alloc(width * height); memcpy(self->current_alpha, buffer, width * height); mlt_properties_set_data(producer_props, "_cached_alpha", self->current_alpha, width * height, mlt_pool_release, NULL); } } pthread_mutex_unlock(&self->mutex); mlt_properties_set_int(properties, "width", self->current_width); mlt_properties_set_int(properties, "height", self->current_height); } mlt-7.22.0/src/modules/qt/kdenlivetitle_wrapper.h000664 000000 000000 00000003124 14531534050 022015 0ustar00rootroot000000 000000 /* * kdenlivetitle_wrapper.h -- kdenlivetitle wrapper * Copyright (c) 2009 Marco Gittler * Copyright (c) 2009 Jean-Baptiste Mardelle * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #ifdef __cplusplus extern "C" { #endif #include #include #include struct producer_ktitle_s { struct mlt_producer_s parent; uint8_t *rgba_image; uint8_t *current_image; uint8_t *current_alpha; mlt_image_format format; int current_width; int current_height; int has_alpha; pthread_mutex_t mutex; }; typedef struct producer_ktitle_s *producer_ktitle; extern void drawKdenliveTitle( producer_ktitle self, mlt_frame frame, mlt_image_format format, int, int, double, int); extern int initTitleProducer(mlt_producer producer); #ifdef __cplusplus } #endif mlt-7.22.0/src/modules/qt/producer_kdenlivetitle.c000664 000000 000000 00000020115 14531534050 022152 0ustar00rootroot000000 000000 /* * producer_kdenlivetitle.c -- kdenlive producer * Copyright (c) 2009 Marco Gittler * Copyright (c) 2009 Jean-Baptiste Mardelle * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "kdenlivetitle_wrapper.h" #include #include #include void read_xml(mlt_properties properties) { const char *resource = mlt_properties_get(properties, "resource"); FILE *f = mlt_fopen(resource, "r"); if (f != NULL) { int size = 0; long lSize; if (fseek(f, 0, SEEK_END) < 0) goto error; lSize = ftell(f); if (lSize <= 0) goto error; rewind(f); char *infile = (char *) mlt_pool_alloc(lSize + 1); if (infile) { size = fread(infile, 1, lSize, f); if (size) { infile[size] = '\0'; mlt_properties_set(properties, "_xmldata", infile); } mlt_pool_release(infile); } error: fclose(f); } } static int producer_get_image(mlt_frame frame, uint8_t **buffer, mlt_image_format *format, int *width, int *height, int writable) { int error = 0; /* Obtain properties of frame */ mlt_properties properties = MLT_FRAME_PROPERTIES(frame); /* Obtain the producer for this frame */ producer_ktitle self = mlt_properties_get_data(properties, "producer_kdenlivetitle", NULL); /* Obtain properties of producer */ mlt_producer producer = &self->parent; mlt_properties producer_props = MLT_PRODUCER_PROPERTIES(producer); if (mlt_properties_get_int(properties, "rescale_width") > 0) *width = mlt_properties_get_int(properties, "rescale_width"); if (mlt_properties_get_int(properties, "rescale_height") > 0) *height = mlt_properties_get_int(properties, "rescale_height"); mlt_service_lock(MLT_PRODUCER_SERVICE(producer)); /* Allocate the image */ if (mlt_properties_get_int(producer_props, "force_reload")) { if (mlt_properties_get_int(producer_props, "force_reload") > 1) read_xml(producer_props); mlt_properties_set_int(producer_props, "force_reload", 0); drawKdenliveTitle(self, frame, *format, *width, *height, mlt_frame_original_position(frame), 1); } else { drawKdenliveTitle(self, frame, *format, *width, *height, mlt_frame_original_position(frame), 0); } // Get width and height (may have changed during the refresh) *width = mlt_properties_get_int(properties, "width"); *height = mlt_properties_get_int(properties, "height"); *format = self->format; if (self->current_image) { // Clone the image and the alpha int image_size = mlt_image_format_size(self->format, self->current_width, self->current_height, NULL); uint8_t *image_copy = mlt_pool_alloc(image_size); memcpy(image_copy, self->current_image, mlt_image_format_size(self->format, self->current_width, self->current_height, NULL)); // Now update properties so we free the copy after mlt_frame_set_image(frame, image_copy, image_size, mlt_pool_release); // We're going to pass the copy on *buffer = image_copy; // Clone the alpha channel if (self->current_alpha) { image_copy = mlt_pool_alloc(self->current_width * self->current_height); memcpy(image_copy, self->current_alpha, self->current_width * self->current_height); mlt_frame_set_alpha(frame, image_copy, self->current_width * self->current_height, mlt_pool_release); } } else { error = 1; } mlt_service_unlock(MLT_PRODUCER_SERVICE(producer)); return error; } static int producer_get_frame(mlt_producer producer, mlt_frame_ptr frame, int index) { // Get the real structure for this producer producer_ktitle this = producer->child; /* Generate a frame */ *frame = mlt_frame_init(MLT_PRODUCER_SERVICE(producer)); if (*frame != NULL) { /* Obtain properties of frame and producer */ mlt_properties properties = MLT_FRAME_PROPERTIES(*frame); /* Obtain properties of producer */ mlt_properties producer_props = MLT_PRODUCER_PROPERTIES(producer); /* Set the producer on the frame properties */ mlt_properties_set_data(properties, "producer_kdenlivetitle", this, 0, NULL, NULL); /* Update timecode on the frame we're creating */ mlt_frame_set_position(*frame, mlt_producer_position(producer)); // Set producer-specific frame properties mlt_properties_set_int(properties, "progressive", 1); // Inform framework that this producer creates rgba frames by default // TODO: read the producer's xml on opening so we know if we have RGB or RGBA data mlt_properties_set_int(properties, "format", mlt_image_rgba); double force_ratio = mlt_properties_get_double(producer_props, "force_aspect_ratio"); if (force_ratio > 0.0) mlt_properties_set_double(properties, "aspect_ratio", force_ratio); else mlt_properties_set_double(properties, "aspect_ratio", mlt_properties_get_double(producer_props, "aspect_ratio")); /* Push the get_image method */ mlt_frame_push_get_image(*frame, producer_get_image); } /* Calculate the next timecode */ mlt_producer_prepare_next(producer); return 0; } static void producer_close(mlt_producer producer) { producer_ktitle self = producer->child; producer->close = NULL; mlt_service_cache_purge(MLT_PRODUCER_SERVICE(producer)); mlt_producer_close(producer); free(self); } mlt_producer producer_kdenlivetitle_init(mlt_profile profile, mlt_service_type type, const char *id, char *filename) { /* Create a new producer object */ producer_ktitle self = calloc(1, sizeof(struct producer_ktitle_s)); if (self != NULL && mlt_producer_init(&self->parent, self) == 0) { mlt_producer producer = &self->parent; /* Get the properties interface */ mlt_properties properties = MLT_PRODUCER_PROPERTIES(producer); /* Callback registration */ producer->get_frame = producer_get_frame; producer->close = (mlt_destructor) producer_close; mlt_properties_set(properties, "resource", filename); mlt_properties_set_int(properties, "meta.media.progressive", 1); mlt_properties_set_int(properties, "aspect_ratio", 1); mlt_properties_set_int(properties, "seekable", 1); if (!initTitleProducer(producer)) { mlt_producer_close(producer); return NULL; } read_xml(properties); return producer; } free(self); return NULL; } mlt-7.22.0/src/modules/qt/producer_kdenlivetitle.yml000664 000000 000000 00000000356 14531534050 022536 0ustar00rootroot000000 000000 schema_version: 0.1 type: producer identifier: kdenlivetitle title: Kdenlive Titler version: 3 copyright: Marco Gittler, Jean-Baptiste Mardelle creator: Marco Gittler, Jean-Baptiste Mardelle license: LGPLv2.1 language: en tags: - Video mlt-7.22.0/src/modules/qt/producer_qimage.c000664 000000 000000 00000035632 14531534050 020564 0ustar00rootroot000000 000000 /* * producer_image.c -- a Qt/QImage based producer for MLT * Copyright (C) 2006-2023 Meltytech, LLC * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "qimage_wrapper.h" #include #include #include #include #include #include #include #include #include #include static void load_filenames(producer_qimage self, mlt_properties producer_properties); static int producer_get_frame(mlt_producer parent, mlt_frame_ptr frame, int index); static void producer_close(mlt_producer parent); static void refresh_length(mlt_properties properties, producer_qimage self) { if (self->count > mlt_properties_get_int(properties, "length") || mlt_properties_get_int(properties, "autolength")) { int ttl = mlt_properties_get_int(properties, "ttl"); mlt_position length = self->count * ttl; mlt_properties_set_position(properties, "length", length); mlt_properties_set_position(properties, "out", length - 1); } } static void on_property_changed(mlt_service owner, mlt_producer producer, mlt_event_data event_data) { const char *name = mlt_event_data_to_string(event_data); if (name && !strcmp(name, "ttl")) refresh_length(MLT_PRODUCER_PROPERTIES(producer), producer->child); } mlt_producer producer_qimage_init(mlt_profile profile, mlt_service_type type, const char *id, char *filename) { producer_qimage self = calloc(1, sizeof(struct producer_qimage_s)); if (self != NULL && mlt_producer_init(&self->parent, self) == 0) { mlt_producer producer = &self->parent; // Get the properties interface mlt_properties properties = MLT_PRODUCER_PROPERTIES(&self->parent); // Initialize KDE image plugins and get supported animation frame count self->count = init_qimage(producer, filename); if (!self->count) { mlt_producer_close(producer); free(self); return NULL; } // Callback registration producer->get_frame = producer_get_frame; producer->close = (mlt_destructor) producer_close; // Set the default properties mlt_properties_set(properties, "resource", filename); mlt_properties_set_int(properties, "ttl", self->count > 1 ? 1 : 25); mlt_properties_set_int(properties, "aspect_ratio", 1); mlt_properties_set_int(properties, "meta.media.progressive", 1); mlt_properties_set_int(properties, "seekable", 1); // Validate the resource if (self->count == 1 && filename) { load_filenames(self, properties); } else { refresh_length(properties, self); } if (self->count) { mlt_frame frame = mlt_frame_init(MLT_PRODUCER_SERVICE(producer)); if (frame) { mlt_properties frame_properties = MLT_FRAME_PROPERTIES(frame); mlt_properties_set_data(frame_properties, "producer_qimage", self, 0, NULL, NULL); mlt_frame_set_position(frame, mlt_producer_position(producer)); int enable_caching = self->count == 1; refresh_qimage(self, frame, enable_caching); if (enable_caching) { mlt_cache_item_close(self->qimage_cache); } mlt_frame_close(frame); } } if (self->current_width == 0) { producer_close(producer); producer = NULL; } else { mlt_events_listen(properties, self, "property-changed", (mlt_listener) on_property_changed); } return producer; } free(self); return NULL; } static int load_svg(producer_qimage self, mlt_properties properties, const char *filename) { int result = 0; // Read xml string if (strstr(filename, " start && (end[0] == 'd' || end[0] == 'i' || end[0] == 'u')) { int n = end - start; char *s = calloc(1, n + 1); strncpy(s, start, n); mlt_properties_set(properties, "begin", s); free(s); s = calloc(1, strlen(filename) + 2); strncpy(s, filename, start - filename); sprintf(s + (start - filename), ".%d%s", n, end); result = load_sequence_sprintf(self, properties, s); free(s); } } return result; } static int load_sequence_querystring(producer_qimage self, mlt_properties properties, const char *filename) { int result = 0; // Obtain filenames with pattern and begin value in query string if (strchr(filename, '%') && strchr(filename, '?')) { // Split filename into pattern and query string char *s = strdup(filename); char *querystring = strrchr(s, '?'); *querystring++ = '\0'; if (strstr(filename, "begin=")) mlt_properties_set(properties, "begin", strstr(querystring, "begin=") + 6); else if (strstr(filename, "begin:")) mlt_properties_set(properties, "begin", strstr(querystring, "begin:") + 6); // Coerce to an int value so serialization does not have any extra query string cruft mlt_properties_set_int(properties, "begin", mlt_properties_get_int(properties, "begin")); result = load_sequence_sprintf(self, properties, s); free(s); } return result; } static void load_filenames(producer_qimage self, mlt_properties properties) { char *filename = mlt_properties_get(properties, "resource"); self->filenames = mlt_properties_new(); if (!load_svg(self, properties, filename) && !load_sequence_querystring(self, properties, filename) && !load_sequence_sprintf(self, properties, filename) && !load_sequence_deprecated(self, properties, filename) && !load_folder(self, filename)) { mlt_properties_set(self->filenames, "0", filename); } self->count = mlt_properties_count(self->filenames); refresh_length(properties, self); } static int producer_get_image(mlt_frame frame, uint8_t **buffer, mlt_image_format *format, int *width, int *height, int writable) { int error = 0; // Obtain properties of frame and producer mlt_properties properties = MLT_FRAME_PROPERTIES(frame); producer_qimage self = mlt_properties_get_data(properties, "producer_qimage", NULL); mlt_producer producer = &self->parent; // Use the width and height suggested by the rescale filter because we can do our own scaling. if (mlt_properties_get_int(properties, "rescale_width") > 0) *width = mlt_properties_get_int(properties, "rescale_width"); if (mlt_properties_get_int(properties, "rescale_height") > 0) *height = mlt_properties_get_int(properties, "rescale_height"); mlt_service_lock(MLT_PRODUCER_SERVICE(&self->parent)); int enable_caching = (self->count <= 1 || mlt_properties_get_int(MLT_PRODUCER_PROPERTIES(producer), "ttl") > 1); // Refresh the image if (enable_caching) { self->qimage_cache = mlt_service_cache_get(MLT_PRODUCER_SERVICE(producer), "qimage.qimage"); self->qimage = mlt_cache_item_data(self->qimage_cache, NULL); self->image_cache = mlt_service_cache_get(MLT_PRODUCER_SERVICE(producer), "qimage.image"); self->current_image = mlt_cache_item_data(self->image_cache, NULL); self->alpha_cache = mlt_service_cache_get(MLT_PRODUCER_SERVICE(producer), "qimage.alpha"); self->current_alpha = mlt_cache_item_data(self->alpha_cache, &self->alpha_size); const char *dst_color_range = mlt_properties_get(properties, "consumer.color_range"); int dst_full_range = dst_color_range && (!strcmp("pc", dst_color_range) || !strcmp("jpeg", dst_color_range)); if (dst_full_range) mlt_properties_set_int(properties, "full_range", 1); } refresh_image(self, frame, *format, *width, *height, enable_caching); // Get width and height (may have changed during the refresh) *width = mlt_properties_get_int(properties, "width"); *height = mlt_properties_get_int(properties, "height"); *format = self->format; // NB: Cloning is necessary with this producer (due to processing of images ahead of use) // The fault is not in the design of mlt, but in the implementation of the qimage producer... if (self->current_image) { int image_size = mlt_image_format_size(self->format, self->current_width, self->current_height, NULL); if (enable_caching) { // Clone the image and the alpha uint8_t *image_copy = mlt_pool_alloc(image_size); memcpy(image_copy, self->current_image, image_size); // Now update properties so we free the copy after mlt_frame_set_image(frame, image_copy, image_size, mlt_pool_release); // We're going to pass the copy on *buffer = image_copy; mlt_log_debug(MLT_PRODUCER_SERVICE(&self->parent), "%dx%d (%s)\n", self->current_width, self->current_height, mlt_image_format_name(*format)); // Clone the alpha channel if (self->current_alpha) { if (!self->alpha_size) self->alpha_size = self->current_width * self->current_height; uint8_t *alpha_copy = mlt_pool_alloc(self->alpha_size); memcpy(alpha_copy, self->current_alpha, self->alpha_size); mlt_frame_set_alpha(frame, alpha_copy, self->alpha_size, mlt_pool_release); } } else { // For image sequences with ttl = 1 we recreate self->current_image on each frame, no need to clone mlt_frame_set_image(frame, self->current_image, image_size, mlt_pool_release); *buffer = self->current_image; if (self->current_alpha) { if (!self->alpha_size) self->alpha_size = self->current_width * self->current_height; mlt_frame_set_alpha(frame, self->current_alpha, self->alpha_size, mlt_pool_release); } } } else { error = 1; } if (enable_caching) { // Release references and locks mlt_cache_item_close(self->qimage_cache); mlt_cache_item_close(self->image_cache); mlt_cache_item_close(self->alpha_cache); } mlt_service_unlock(MLT_PRODUCER_SERVICE(&self->parent)); return error; } static int producer_get_frame(mlt_producer producer, mlt_frame_ptr frame, int index) { // Get the real structure for this producer producer_qimage self = producer->child; // Fetch the producers properties mlt_properties producer_properties = MLT_PRODUCER_PROPERTIES(producer); // Cache miss if (!self->filenames && !self->count && mlt_properties_get(producer_properties, "resource")) { self->count = init_qimage(producer, mlt_properties_get(producer_properties, "resource")); if (!self->count) { return 1; } if (self->count == 1) { load_filenames(self, producer_properties); } else { refresh_length(producer_properties, self); } } // Generate a frame *frame = mlt_frame_init(MLT_PRODUCER_SERVICE(producer)); if (*frame != NULL && self->count > 0) { // Obtain properties of frame and producer mlt_properties properties = MLT_FRAME_PROPERTIES(*frame); // Set the producer on the frame properties mlt_properties_set_data(properties, "producer_qimage", self, 0, NULL, NULL); // Update timecode on the frame we're creating mlt_frame_set_position(*frame, mlt_producer_position(producer)); // Refresh the image if (self->count == 1 || mlt_properties_get_int(producer_properties, "ttl") > 1) { self->qimage_cache = mlt_service_cache_get(MLT_PRODUCER_SERVICE(producer), "qimage.qimage"); self->qimage = mlt_cache_item_data(self->qimage_cache, NULL); refresh_qimage(self, *frame, 1); mlt_cache_item_close(self->qimage_cache); } // Set producer-specific frame properties mlt_properties_set_int(properties, "progressive", 1); mlt_properties_set_int(properties, "format", mlt_properties_get_int(producer_properties, "format")); double force_ratio = mlt_properties_get_double(producer_properties, "force_aspect_ratio"); if (force_ratio > 0.0) mlt_properties_set_double(properties, "aspect_ratio", force_ratio); else mlt_properties_set_double(properties, "aspect_ratio", mlt_properties_get_double(producer_properties, "aspect_ratio")); // Push the get_image method mlt_frame_push_get_image(*frame, producer_get_image); } // Calculate the next timecode mlt_producer_prepare_next(producer); return 0; } static void producer_close(mlt_producer parent) { producer_qimage self = parent->child; parent->close = NULL; mlt_service_cache_purge(MLT_PRODUCER_SERVICE(parent)); mlt_producer_close(parent); mlt_properties_close(self->filenames); free(self); } mlt-7.22.0/src/modules/qt/producer_qimage.yml000664 000000 000000 00000006222 14531534050 021134 0ustar00rootroot000000 000000 schema_version: 0.3 type: producer identifier: qimage title: Qt QImage version: 2 creator: Charles Yates license: GPLv2 language: en tags: - Video description: > A still graphics to video generator using Qt QImage notes: > QImage has builtin scaling. It will rescale the originally rendered title to whatever the consumer requests. Therefore, it will lose its aspect ratio if so requested, and it is up to the consumer to request a proper width and height that maintains the image aspect. parameters: - identifier: resource title: File type: string description: > The name of a graphics file loadable by Qt. If "%" in filename, the filename is used with sprintf to generate a filename from a counter for multi-file/flipbook animation. The file sequence ends when numeric discontinuity exceeds 100. If the file sequence does not begin within the count of 100 you can pass the begin property like a query string parameter, for example: anim-%04d.png?begin=1000. If filename contains "/.all.", suffix with an extension to load all pictures with matching extension from a directory. If filename contains the string " Reload the file instead of using its cached image. This property automatically resets itself once it has been set 1 and processed. minimum: 0 maximum: 1 mutable: yes - identifier: disable_exif title: Disable auto-rotation type: boolean default: 0 widget: checkbox - identifier: force_aspect_ratio title: Sample aspect ratio type: float description: Optionally override a (mis)detected aspect ratio mutable: yes - identifier: autolength title: Automatically compute length description: Whether to automatically compute the length and out point for an image sequence. type: boolean default: 0 widget: checkbox mlt-7.22.0/src/modules/qt/producer_qtext.cpp000664 000000 000000 00000050335 14531534050 021023 0ustar00rootroot000000 000000 /* * producer_qtext.c -- text generating producer * Copyright (C) 2013 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "common.h" #include #include #include #include #include #include #include #include #include #include #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) #include #include #else #include #include #endif static void close_qimg(void *qimg) { delete static_cast(qimg); } static void close_qpath(void *qpath) { delete static_cast(qpath); } static void copy_qimage_to_mlt_image(QImage *qImg, uint8_t *mImg) { int height = qImg->height(); int width = qImg->width(); int y = 0; // convert qimage to mlt y = height + 1; while (--y) { QRgb *src = (QRgb *) qImg->scanLine(height - y); int x = width + 1; while (--x) { *mImg++ = qRed(*src); *mImg++ = qGreen(*src); *mImg++ = qBlue(*src); *mImg++ = qAlpha(*src); src++; } } } static void copy_image_to_alpha(uint8_t *image, uint8_t *alpha, int width, int height) { int len = width * height; // Extract the alpha mask from the RGBA image using Duff's Device uint8_t *s = image + 3; // start on the alpha component uint8_t *d = alpha; int n = (len + 7) / 8; switch (len % 8) { case 0: do { *d++ = *s; s += 4; case 7: *d++ = *s; s += 4; case 6: *d++ = *s; s += 4; case 5: *d++ = *s; s += 4; case 4: *d++ = *s; s += 4; case 3: *d++ = *s; s += 4; case 2: *d++ = *s; s += 4; case 1: *d++ = *s; s += 4; } while (--n > 0); } } /** Check if the qpath needs to be regenerated. Return true if regeneration is required. */ static bool check_qpath(mlt_properties producer_properties) { #define MAX_SIG 1000 bool result = false; char *new_path_sig = new char[MAX_SIG]; // Generate a signature that represents the current properties snprintf(new_path_sig, MAX_SIG, "%s%s%s%s%s%s%s%s%s%s%s%s", mlt_properties_get(producer_properties, "text"), mlt_properties_get(producer_properties, "fgcolour"), mlt_properties_get(producer_properties, "bgcolour"), mlt_properties_get(producer_properties, "olcolour"), mlt_properties_get(producer_properties, "outline"), mlt_properties_get(producer_properties, "align"), mlt_properties_get(producer_properties, "pad"), mlt_properties_get(producer_properties, "family"), mlt_properties_get(producer_properties, "size"), mlt_properties_get(producer_properties, "style"), mlt_properties_get(producer_properties, "weight"), mlt_properties_get(producer_properties, "encoding")); new_path_sig[MAX_SIG - 1] = '\0'; // Check if the properties have changed by comparing this signature with the // last one. char *last_path_sig = mlt_properties_get(producer_properties, "_path_sig"); if (!last_path_sig || strcmp(new_path_sig, last_path_sig)) { mlt_properties_set(producer_properties, "_path_sig", new_path_sig); result = true; } delete[] new_path_sig; return result; } static void generate_qpath(mlt_properties producer_properties) { QPainterPath *qPath = static_cast( mlt_properties_get_data(producer_properties, "_qpath", NULL)); int outline = mlt_properties_get_int(producer_properties, "outline"); char *align = mlt_properties_get(producer_properties, "align"); char *style = mlt_properties_get(producer_properties, "style"); char *text = mlt_properties_get(producer_properties, "text"); char *encoding = mlt_properties_get(producer_properties, "encoding"); int pad = mlt_properties_get_int(producer_properties, "pad"); int offset = pad + (outline / 2); int width = 0; int height = 0; // Make the path empty *qPath = QPainterPath(); qPath->setFillRule(Qt::WindingFill); // Get the strings to display QTextCodec *codec = QTextCodec::codecForName(encoding); QTextDecoder *decoder = codec->makeDecoder(); QString s = decoder->toUnicode(text); delete decoder; QStringList lines = s.split("\n"); // Configure the font QFont font; font.setPixelSize(mlt_properties_get_int(producer_properties, "size")); font.setFamily(mlt_properties_get(producer_properties, "family")); font.setWeight(QFont::Weight((mlt_properties_get_int(producer_properties, "weight") / 10) - 1)); switch (style[0]) { case 'i': case 'I': font.setStyle(QFont::StyleItalic); break; } QFontMetrics fm(font); // Determine the text rectangle size height = fm.lineSpacing() * lines.size(); for (int i = 0; i < lines.size(); ++i) { const QString line = lines[i]; #if (QT_VERSION > QT_VERSION_CHECK(5, 11, 0)) int line_width = fm.horizontalAdvance(line); #else int line_width = fm.width(line); #endif int bearing = (line.size() > 0) ? fm.leftBearing(line.at(0)) : 0; if (bearing < 0) line_width -= bearing; bearing = (line.size() > 0) ? fm.rightBearing(line.at(line.size() - 1)) : 0; if (bearing < 0) line_width -= bearing; if (line_width > width) width = line_width; } // Lay out the text in the path int x = 0; int y = fm.ascent() + offset; for (int i = 0; i < lines.size(); ++i) { QString line = lines.at(i); x = offset; #if (QT_VERSION > QT_VERSION_CHECK(5, 11, 0)) int line_width = fm.horizontalAdvance(line); #else int line_width = fm.width(line); #endif int bearing = (line.size() > 0) ? fm.leftBearing(line.at(0)) : 0; if (bearing < 0) { line_width -= bearing; x -= bearing; } bearing = (line.size() > 0) ? fm.rightBearing(line.at(line.size() - 1)) : 0; if (bearing < 0) line_width -= bearing; switch (align[0]) { default: case 'l': case 'L': break; case 'c': case 'C': x += (width - line_width) / 2; break; case 'r': case 'R': x += width - line_width; break; } qPath->addText(x, y, font, line); y += fm.lineSpacing(); } // Account for outline and pad width += offset * 2; height += offset * 2; // Sanity check if (width == 0) width = 1; height += 2; // I found some fonts whose descenders get cut off. mlt_properties_set_int(producer_properties, "meta.media.width", width); mlt_properties_set_int(producer_properties, "meta.media.height", height); } static bool check_qimage(mlt_properties frame_properties) { mlt_producer producer = static_cast( mlt_properties_get_data(frame_properties, "_producer_qtext", NULL)); mlt_properties producer_properties = MLT_PRODUCER_PROPERTIES(producer); QImage *qImg = static_cast( mlt_properties_get_data(producer_properties, "_qimg", NULL)); QSize target_size(mlt_properties_get_int(frame_properties, "rescale_width"), mlt_properties_get_int(frame_properties, "rescale_height")); QSize native_size(mlt_properties_get_int(frame_properties, "meta.media.width"), mlt_properties_get_int(frame_properties, "meta.media.height")); // Check if the last image signature is different from the path signature // for this frame. char *last_img_sig = mlt_properties_get(producer_properties, "_img_sig"); char *path_sig = mlt_properties_get(frame_properties, "_path_sig"); if (!last_img_sig || strcmp(path_sig, last_img_sig)) { mlt_properties_set(producer_properties, "_img_sig", path_sig); return true; } // Check if the last image size matches the requested image size QSize output_size = target_size; if (output_size.isEmpty()) { output_size = native_size; } if (output_size != qImg->size()) { return true; } return false; } static void generate_qimage(mlt_properties frame_properties) { mlt_producer producer = static_cast( mlt_properties_get_data(frame_properties, "_producer_qtext", NULL)); mlt_properties producer_properties = MLT_PRODUCER_PROPERTIES(producer); QImage *qImg = static_cast( mlt_properties_get_data(producer_properties, "_qimg", NULL)); QSize target_size(mlt_properties_get_int(frame_properties, "rescale_width"), mlt_properties_get_int(frame_properties, "rescale_height")); QSize native_size(mlt_properties_get_int(frame_properties, "meta.media.width"), mlt_properties_get_int(frame_properties, "meta.media.height")); QPainterPath *qPath = static_cast( mlt_properties_get_data(frame_properties, "_qpath", NULL)); mlt_color bg_color = mlt_properties_get_color(frame_properties, "_bgcolour"); mlt_color fg_color = mlt_properties_get_color(frame_properties, "_fgcolour"); mlt_color ol_color = mlt_properties_get_color(frame_properties, "_olcolour"); int outline = mlt_properties_get_int(frame_properties, "_outline"); qreal sx = 1.0; qreal sy = 1.0; // Create a new image and set up scaling if (!target_size.isEmpty() && target_size != native_size) { *qImg = QImage(target_size, QImage::Format_ARGB32); sx = (qreal) target_size.width() / (qreal) native_size.width(); sy = (qreal) target_size.height() / (qreal) native_size.height(); } else { *qImg = QImage(native_size, QImage::Format_ARGB32); } qImg->fill(QColor(bg_color.r, bg_color.g, bg_color.b, bg_color.a).rgba()); // Draw the text QPainter painter(qImg); // Scale the painter rather than the image for better looking results. painter.scale(sx, sy); painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) | QPainter::HighQualityAntialiasing #endif ); QPen pen; pen.setWidth(outline); if (outline) { pen.setColor(QColor(ol_color.r, ol_color.g, ol_color.b, ol_color.a)); } else { pen.setColor(QColor(bg_color.r, bg_color.g, bg_color.b, bg_color.a)); } painter.setPen(pen); QBrush brush(QColor(fg_color.r, fg_color.g, fg_color.b, fg_color.a)); painter.setBrush(brush); painter.drawPath(*qPath); } static int producer_get_image(mlt_frame frame, uint8_t **buffer, mlt_image_format *format, int *width, int *height, int writable) { mlt_properties frame_properties = MLT_FRAME_PROPERTIES(frame); mlt_producer producer = static_cast( mlt_properties_get_data(frame_properties, "_producer_qtext", NULL)); mlt_properties producer_properties = MLT_PRODUCER_PROPERTIES(producer); int img_size = 0; int alpha_size = 0; uint8_t *alpha = NULL; QImage *qImg = static_cast( mlt_properties_get_data(producer_properties, "_qimg", NULL)); mlt_service_lock(MLT_PRODUCER_SERVICE(producer)); // Regenerate the qimage if necessary if (check_qimage(frame_properties) == true) { generate_qimage(frame_properties); } *format = mlt_image_rgba; *width = qImg->width(); *height = qImg->height(); // Allocate and fill the image buffer img_size = mlt_image_format_size(*format, *width, *height, NULL); *buffer = static_cast(mlt_pool_alloc(img_size)); copy_qimage_to_mlt_image(qImg, *buffer); mlt_service_unlock(MLT_PRODUCER_SERVICE(producer)); // Allocate and fill the alpha buffer alpha_size = *width * *height; alpha = static_cast(mlt_pool_alloc(alpha_size)); copy_image_to_alpha(*buffer, alpha, *width, *height); // Update the frame mlt_frame_set_image(frame, *buffer, img_size, mlt_pool_release); mlt_frame_set_alpha(frame, alpha, alpha_size, mlt_pool_release); mlt_properties_set_int(frame_properties, "width", *width); mlt_properties_set_int(frame_properties, "height", *height); return 0; } static int producer_get_frame(mlt_producer producer, mlt_frame_ptr frame, int index) { // Generate a frame *frame = mlt_frame_init(MLT_PRODUCER_SERVICE(producer)); if (*frame != NULL) { mlt_properties frame_properties = MLT_FRAME_PROPERTIES(*frame); mlt_properties producer_properties = MLT_PRODUCER_PROPERTIES(producer); // Regenerate the QPainterPath if necessary if (check_qpath(producer_properties)) { generate_qpath(producer_properties); } // Give the frame a copy of the painter path QPainterPath *prodPath = static_cast( mlt_properties_get_data(producer_properties, "_qpath", NULL)); QPainterPath *framePath = new QPainterPath(*prodPath); mlt_properties_set_data(frame_properties, "_qpath", static_cast(framePath), 0, close_qpath, NULL); // Pass properties to the frame that will be needed to render the path mlt_properties_set(frame_properties, "_path_sig", mlt_properties_get(producer_properties, "_path_sig")); mlt_properties_set(frame_properties, "_bgcolour", mlt_properties_get(producer_properties, "bgcolour")); mlt_properties_set(frame_properties, "_fgcolour", mlt_properties_get(producer_properties, "fgcolour")); mlt_properties_set(frame_properties, "_olcolour", mlt_properties_get(producer_properties, "olcolour")); mlt_properties_set(frame_properties, "_outline", mlt_properties_get(producer_properties, "outline")); mlt_properties_set_data(frame_properties, "_producer_qtext", static_cast(producer), 0, NULL, NULL); // Inform framework that this producer creates rgba frames by default mlt_properties_set_int(frame_properties, "format", mlt_image_rgba); // Set frame properties mlt_properties_set_int(frame_properties, "progressive", 1); double force_ratio = mlt_properties_get_double(producer_properties, "force_aspect_ratio"); if (force_ratio > 0.0) mlt_properties_set_double(frame_properties, "aspect_ratio", force_ratio); else mlt_properties_set_double(frame_properties, "aspect_ratio", 1.0); // Update time code on the frame mlt_frame_set_position(*frame, mlt_producer_position(producer)); // Configure callbacks mlt_frame_push_get_image(*frame, producer_get_image); } // Calculate the next time code mlt_producer_prepare_next(producer); return 0; } static void producer_close(mlt_producer producer) { producer->close = NULL; mlt_producer_close(producer); free(producer); } /** Initialize. */ extern "C" { mlt_producer producer_qtext_init(mlt_profile profile, mlt_service_type type, const char *id, char *filename) { // Create a new producer object mlt_producer producer = mlt_producer_new(profile); mlt_properties producer_properties = MLT_PRODUCER_PROPERTIES(producer); // Initialize the producer if (producer) { if (!createQApplicationIfNeeded(MLT_PRODUCER_SERVICE(producer))) { mlt_producer_close(producer); return NULL; } mlt_properties_set(producer_properties, "text", ""); mlt_properties_set(producer_properties, "fgcolour", "0xffffffff"); mlt_properties_set(producer_properties, "bgcolour", "0x00000000"); mlt_properties_set(producer_properties, "olcolour", "0x00000000"); mlt_properties_set(producer_properties, "outline", "0"); mlt_properties_set(producer_properties, "align", "left"); mlt_properties_set(producer_properties, "pad", "0"); mlt_properties_set(producer_properties, "family", "Sans"); mlt_properties_set(producer_properties, "size", "48"); mlt_properties_set(producer_properties, "style", "normal"); mlt_properties_set(producer_properties, "weight", "400"); mlt_properties_set(producer_properties, "encoding", "UTF-8"); mlt_properties_set_int(producer_properties, "meta.media.progressive", 1); // Parse the filename argument if (filename == NULL || !strcmp(filename, "") || strstr(filename, "")) { } else if (filename[0] == '+' || strstr(filename, "/+")) { char *copy = strdup(filename + 1); char *tmp = copy; if (strstr(tmp, "/+")) tmp = strstr(tmp, "/+") + 2; if (strrchr(tmp, '.')) (*strrchr(tmp, '.')) = '\0'; while (strchr(tmp, '~')) (*strchr(tmp, '~')) = '\n'; mlt_properties_set(producer_properties, "text", tmp); mlt_properties_set(producer_properties, "resource", filename); free(copy); } else { mlt_properties_set(producer_properties, "resource", filename); FILE *f = mlt_fopen(filename, "r"); if (f != NULL) { char line[81]; char *tmp = NULL; size_t size = 0; line[80] = '\0'; while (fgets(line, 80, f)) { size += strlen(line) + 1; if (tmp) { tmp = (char *) realloc(tmp, size); if (tmp) strcat(tmp, line); } else { tmp = strdup(line); } } fclose(f); if (tmp && tmp[strlen(tmp) - 1] == '\n') tmp[strlen(tmp) - 1] = '\0'; if (tmp) mlt_properties_set(producer_properties, "text", tmp); free(tmp); } } // Create QT objects to be reused. mlt_properties_set_data(producer_properties, "_qimg", static_cast(new QImage()), 0, close_qimg, NULL); mlt_properties_set_data(producer_properties, "_qpath", static_cast(new QPainterPath()), 0, close_qpath, NULL); // Callback registration producer->get_frame = producer_get_frame; producer->close = (mlt_destructor) producer_close; } return producer; } } mlt-7.22.0/src/modules/qt/producer_qtext.yml000664 000000 000000 00000011422 14531534050 021034 0ustar00rootroot000000 000000 schema_version: 0.3 type: producer identifier: qtext title: Qt Titler version: 1 copyright: Meltytech, LLC license: LGPLv2.1 language: en tags: - Video description: > A title generator that uses the Qt framework to render text. notes: > qtext accepts a file name with at ".txt" extension. If the filename begins with "+" the qtext producer interprets the filename as text. This is a shortcut to embed titles in melt commands. For MLT XML, it is recommended that you embed the title text in the "text" property value. qtext has builtin scaling. It will rescale the originally rendered title to whatever the consumer requests. Therefore, it will lose its aspect ratio if so requested, and it is up to the consumer to request a proper width and height that maintains the image aspect. parameters: - identifier: resource title: File type: string description: | A text file containing text to be rendered. The text file contents initialize the value of the "text" parameter. readonly: no argument: yes mutable: no widget: fileopen - identifier: text title: Text type: string description: | A text string to be rendered. readonly: no argument: yes mutable: yes widget: textbox - identifier: fgcolour title: Foreground color type: string description: > A color value is a hexadecimal representation of RGB plus alpha channel as 0xrrggbbaa. Colors can also be the words: white, black, red, green, or blue. You can also use a HTML-style color values #rrggbb or #aarrggbb. readonly: no mutable: yes widget: color - identifier: bgcolour title: Background color type: string description: > A color value is a hexadecimal representation of RGB plus alpha channel as 0xrrggbbaa. Colors can also be the words: white, black, red, green, or blue. You can also use a HTML-style color values #rrggbb or #aarrggbb. readonly: no mutable: yes widget: color - identifier: olcolour title: Outline color type: string description: > A color value is a hexadecimal representation of RGB plus alpha channel as 0xrrggbbaa. Colors can also be the words: white, black, red, green, or blue. You can also use a HTML-style color values #rrggbb or #aarrggbb. readonly: no mutable: yes widget: color - identifier: outline title: Outline Width type: string description: > The width of the outline in pixels. readonly: no default: 0 minimum: 0 maximum: 3 mutable: yes widget: spinner - identifier: align title: Paragraph alignment type: string description: > left, center, right readonly: no default: left mutable: yes widget: combo - identifier: pad title: Padding type: integer description: > The number of pixels to pad the background rectangle beyond edges of text. readonly: no default: 0 mutable: yes widget: spinner - identifier: family title: Font family type: string description: > The font typeface. default: Sans readonly: no mutable: yes widget: combo - identifier: size title: Font size type: integer description: > The size in pixels of the font. default: 48 readonly: no mutable: yes widget: spinner - identifier: style title: Font style type: string description: > The style of the font. values: - normal - italic default: normal readonly: no mutable: yes widget: combo - identifier: weight title: Font weight type: integer description: The weight of the font. minimum: 100 maximum: 1000 default: 400 readonly: no mutable: yes widget: spinner - identifier: encoding title: Encoding type: string description: > The text encoding type of the "text" parameter. default: UTF-8 readonly: no mutable: yes widget: combo - identifier: force_aspect_ratio title: Sample aspect ratio type: float description: Optionally override a (mis)detected aspect ratio mutable: yes - identifier: meta.media.width title: Real width type: integer description: The original, unscaled width of the rendered image. readonly: yes - identifier: meta.media.height title: Real height type: integer description: The original, unscaled height of the rendered image. readonly: yes - identifier: width title: Width type: integer description: The last requested scaled image width. readonly: yes - identifier: height title: Height type: integer description: The last requested scaled image height. readonly: yes mlt-7.22.0/src/modules/qt/qimage_wrapper.cpp000664 000000 000000 00000047273 14531534050 020765 0ustar00rootroot000000 000000 /* * qimage_wrapper.cpp -- a Qt/QImage based producer for MLT * Copyright (C) 2006-2022 Meltytech, LLC * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "qimage_wrapper.h" #include "common.h" #ifdef USE_KDE4 #include #endif #include #include #include #include #include #include #include #include #include #ifdef USE_EXIF #include #include #endif #include #include #include #include extern "C" { #include #include #ifdef USE_KDE4 static KComponentData *instance = 0L; #endif static void qimage_delete(void *data) { QImage *image = (QImage *) data; delete image; image = NULL; #if defined(USE_KDE4) delete instance; instance = 0L; #endif } /// Returns frame count or 0 on error int init_qimage(mlt_producer producer, const char *filename) { if (!createQApplicationIfNeeded(MLT_PRODUCER_SERVICE(producer))) { return 0; } QImageReader reader; reader.setDecideFormatFromContent(true); reader.setFileName(filename); if (reader.canRead() && reader.imageCount() > 1) { return reader.format() == "webp" ? reader.imageCount() : 0; } #ifdef USE_KDE4 if (!instance) { instance = new KComponentData("qimage_prod"); } #endif return 1; } #if QT_VERSION < QT_VERSION_CHECK(5, 5, 0) static QImage *reorient_with_exif(producer_qimage self, int image_idx, QImage *qimage) { #ifdef USE_EXIF mlt_properties producer_props = MLT_PRODUCER_PROPERTIES(&self->parent); ExifData *d = exif_data_new_from_file(mlt_properties_get_value(self->filenames, image_idx)); ExifEntry *entry; int exif_orientation = 0; /* get orientation and rotate image accordingly if necessary */ if (d) { if ((entry = exif_content_get_entry(d->ifd[EXIF_IFD_0], EXIF_TAG_ORIENTATION))) exif_orientation = exif_get_short(entry->data, exif_data_get_byte_order(d)); /* Free the EXIF data */ exif_data_unref(d); } // Remember EXIF value, might be useful for someone mlt_properties_set_int(producer_props, "_exif_orientation", exif_orientation); if (exif_orientation > 1) { // Rotate image according to exif data QImage processed; QTransform matrix; switch (exif_orientation) { case 2: matrix.scale(-1, 1); break; case 3: matrix.rotate(180); break; case 4: matrix.scale(1, -1); break; case 5: matrix.rotate(270); matrix.scale(-1, 1); break; case 6: matrix.rotate(90); break; case 7: matrix.rotate(90); matrix.scale(-1, 1); break; case 8: matrix.rotate(270); break; } processed = qimage->transformed(matrix); delete qimage; qimage = new QImage(processed); } #endif return qimage; } #endif int refresh_qimage(producer_qimage self, mlt_frame frame, int enable_caching) { // Obtain properties of frame and producer mlt_properties properties = MLT_FRAME_PROPERTIES(frame); mlt_producer producer = &self->parent; mlt_properties producer_props = MLT_PRODUCER_PROPERTIES(producer); // Check if user wants us to reload the image if (mlt_properties_get_int(producer_props, "force_reload")) { self->qimage = NULL; self->current_image = NULL; mlt_properties_set_int(producer_props, "force_reload", 0); } // Get the original position of this frame mlt_position position = mlt_frame_original_position(frame); position += mlt_producer_get_in(producer); // Image index int image_idx = (int) floor((double) position / mlt_properties_get_int(producer_props, "ttl")) % self->count; int disable_exif = mlt_properties_get_int(producer_props, "disable_exif"); if (image_idx != self->qimage_idx) { self->qimage = NULL; } if (!self->qimage || mlt_properties_get_int(producer_props, "_disable_exif") != disable_exif) { self->current_image = NULL; QImageReader reader; QImage *qimage; #if QT_VERSION >= QT_VERSION_CHECK(5, 5, 0) // Use Qt's orientation detection reader.setAutoTransform(!disable_exif); #endif QString filename = QString::fromUtf8(mlt_properties_get_value(self->filenames, image_idx)); if (filename.isEmpty()) { filename = QString::fromUtf8(mlt_properties_get(producer_props, "resource")); } // First try to detect the file type based on the content // in case the file extension is incorrect. reader.setDecideFormatFromContent(true); reader.setFileName(filename); if (reader.imageCount() > 1) { QMovie movie(filename); movie.setCacheMode(QMovie::CacheAll); movie.jumpToFrame(image_idx); qimage = new QImage(movie.currentImage()); } else { qimage = new QImage(reader.read()); } if (qimage->isNull()) { mlt_log_info(MLT_PRODUCER_SERVICE(&self->parent), "QImage retry: %d - %s\n", reader.error(), reader.errorString().toLatin1().data()); delete qimage; // If detection fails, try a more comprehensive detection including file extension reader.setDecideFormatFromContent(false); reader.setFileName(filename); qimage = new QImage(reader.read()); if (qimage->isNull()) { mlt_log_info(MLT_PRODUCER_SERVICE(&self->parent), "QImage fail: %d - %s\n", reader.error(), reader.errorString().toLatin1().data()); } } self->qimage = qimage; if (!qimage->isNull()) { #if QT_VERSION < QT_VERSION_CHECK(5, 5, 0) // Read the exif value for this file if (!disable_exif) { qimage = reorient_with_exif(self, image_idx, qimage); self->qimage = qimage; } #endif if (enable_caching) { // Register qimage for destruction and reuse mlt_cache_item_close(self->qimage_cache); mlt_service_cache_put(MLT_PRODUCER_SERVICE(producer), "qimage.qimage", qimage, 0, (mlt_destructor) qimage_delete); self->qimage_cache = mlt_service_cache_get(MLT_PRODUCER_SERVICE(producer), "qimage.qimage"); } else { // Ensure original image data will be deleted mlt_properties_set_data(producer_props, "qimage.qimage", qimage, 0, (mlt_destructor) qimage_delete, NULL); } self->qimage_idx = image_idx; // Store the width/height of the qimage self->current_width = qimage->width(); self->current_height = qimage->height(); mlt_events_block(producer_props, NULL); mlt_properties_set_int(producer_props, "format", qimage->hasAlphaChannel() ? mlt_image_rgba : mlt_image_rgb); mlt_properties_set_int(producer_props, "meta.media.width", self->current_width); mlt_properties_set_int(producer_props, "meta.media.height", self->current_height); mlt_properties_set_int(producer_props, "_disable_exif", disable_exif); mlt_events_unblock(producer_props, NULL); } else { delete qimage; self->qimage = NULL; } } // Set width/height of frame mlt_properties_set_int(properties, "width", self->current_width); mlt_properties_set_int(properties, "height", self->current_height); return image_idx; } void refresh_image(producer_qimage self, mlt_frame frame, mlt_image_format format, int width, int height, int enable_caching) { // Obtain properties of frame and producer mlt_properties properties = MLT_FRAME_PROPERTIES(frame); mlt_producer producer = &self->parent; // Get index and qimage int image_idx = refresh_qimage(self, frame, enable_caching); // optimization for subsequent iterations on single picture if (!enable_caching || image_idx != self->image_idx || width != self->current_width || height != self->current_height) self->current_image = NULL; // If we have a qimage and need a new scaled image if (self->qimage && (!self->current_image || (format != mlt_image_none && format != mlt_image_movit && format != self->format))) { QString interps = mlt_properties_get(properties, "consumer.rescale"); bool interp = (interps != "nearest") && (interps != "none"); QImage *qimage = static_cast(self->qimage); int has_alpha = qimage->hasAlphaChannel(); QImage::Format qimageFormat = has_alpha ? QImage::Format_ARGB32 : QImage::Format_RGB32; // Note - the original qimage is already safe and ready for destruction if (enable_caching && qimage->format() != qimageFormat) { QImage temp = qimage->convertToFormat(qimageFormat); qimage = new QImage(temp); self->qimage = qimage; mlt_cache_item_close(self->qimage_cache); mlt_service_cache_put(MLT_PRODUCER_SERVICE(producer), "qimage.qimage", qimage, 0, (mlt_destructor) qimage_delete); self->qimage_cache = mlt_service_cache_get(MLT_PRODUCER_SERVICE(producer), "qimage.qimage"); } QImage scaled = interp ? qimage->scaled(QSize(width, height), Qt::IgnoreAspectRatio, Qt::SmoothTransformation) : qimage->scaled(QSize(width, height)); // Store width and height self->current_width = width; self->current_height = height; // Allocate/define image self->current_alpha = NULL; self->alpha_size = 0; // Convert scaled image to target format (it might be premultiplied after scaling). scaled = scaled.convertToFormat(qimageFormat); // Copy the image int image_size; #if QT_VERSION >= QT_VERSION_CHECK(5, 2, 0) if (has_alpha) { self->format = mlt_image_rgba; scaled = scaled.convertToFormat(QImage::Format_RGBA8888); image_size = mlt_image_format_size(self->format, width, height, NULL); self->current_image = (uint8_t *) mlt_pool_alloc(image_size); memcpy(self->current_image, scaled.constBits(), scaled.sizeInBytes()); } else { self->format = mlt_image_rgb; scaled = scaled.convertToFormat(QImage::Format_RGB888); image_size = mlt_image_format_size(self->format, width, height, NULL); self->current_image = (uint8_t *) mlt_pool_alloc(image_size); for (int y = 0; y < height; y++) { QRgb *values = reinterpret_cast(scaled.scanLine(y)); memcpy(&self->current_image[3 * y * width], values, 3 * width); } } #else self->format = has_alpha ? mlt_image_rgba : mlt_image_rgb; image_size = mlt_image_format_size(self->format, self->current_width, self->current_height, NULL); self->current_image = (uint8_t *) mlt_pool_alloc(image_size); int y = self->current_height + 1; uint8_t *dst = self->current_image; if (has_alpha) { while (--y) { QRgb *src = (QRgb *) scaled.scanLine(self->current_height - y); int x = self->current_width + 1; while (--x) { *dst++ = qRed(*src); *dst++ = qGreen(*src); *dst++ = qBlue(*src); *dst++ = qAlpha(*src); ++src; } } } else { while (--y) { QRgb *src = (QRgb *) scaled.scanLine(self->current_height - y); int x = self->current_width + 1; while (--x) { *dst++ = qRed(*src); *dst++ = qGreen(*src); *dst++ = qBlue(*src); ++src; } } } #endif // Convert image to requested format if (format != mlt_image_none && format != mlt_image_movit && format != self->format && enable_caching) { uint8_t *buffer = NULL; // First, set the image so it can be converted when we get it mlt_frame_replace_image(frame, self->current_image, self->format, width, height); mlt_frame_set_image(frame, self->current_image, image_size, mlt_pool_release); // get_image will do the format conversion mlt_frame_get_image(frame, &buffer, &format, &width, &height, 0); // cache copies of the image and alpha buffers if (buffer) { self->current_width = width; self->current_height = height; self->format = format; image_size = mlt_image_format_size(format, width, height, NULL); self->current_image = (uint8_t *) mlt_pool_alloc(image_size); memcpy(self->current_image, buffer, image_size); } if ((buffer = (uint8_t *) mlt_frame_get_alpha_size(frame, &self->alpha_size))) { if (!self->alpha_size) self->alpha_size = self->current_width * self->current_height; self->current_alpha = (uint8_t *) mlt_pool_alloc(self->alpha_size); memcpy(self->current_alpha, buffer, self->alpha_size); } } self->image_idx = image_idx; if (enable_caching) { // Update the cache mlt_cache_item_close(self->image_cache); mlt_service_cache_put(MLT_PRODUCER_SERVICE(producer), "qimage.image", self->current_image, image_size, mlt_pool_release); self->image_cache = mlt_service_cache_get(MLT_PRODUCER_SERVICE(producer), "qimage.image"); mlt_cache_item_close(self->alpha_cache); self->alpha_cache = NULL; if (self->current_alpha) { mlt_service_cache_put(MLT_PRODUCER_SERVICE(producer), "qimage.alpha", self->current_alpha, self->alpha_size, mlt_pool_release); self->alpha_cache = mlt_service_cache_get(MLT_PRODUCER_SERVICE(producer), "qimage.alpha"); } } } // Set width/height of frame mlt_properties_set_int(properties, "width", self->current_width); mlt_properties_set_int(properties, "height", self->current_height); } extern void make_tempfile(producer_qimage self, const char *xml) { // Generate a temporary file for the svg QTemporaryFile tempFile("mlt.XXXXXX"); tempFile.setAutoRemove(false); if (tempFile.open()) { // Write the svg into the temp file QByteArray fullname = tempFile.fileName().toUtf8(); // Strip leading crap while (xml[0] != '<') xml++; qint64 remaining_bytes = strlen(xml); while (remaining_bytes > 0) remaining_bytes -= tempFile.write(xml + strlen(xml) - remaining_bytes, remaining_bytes); tempFile.close(); mlt_properties_set(self->filenames, "0", fullname.data()); mlt_properties_set_data(MLT_PRODUCER_PROPERTIES(&self->parent), "__temporary_file__", fullname.data(), 0, (mlt_destructor) unlink, NULL); } } int load_sequence_sprintf(producer_qimage self, mlt_properties properties, const char *filename) { int result = 0; // Obtain filenames with pattern if (filename && strchr(filename, '%')) { // handle picture sequences int i = mlt_properties_get_int(properties, "begin"); int keyvalue = 0; #if QT_VERSION >= QT_VERSION_CHECK(5, 5, 0) for (int gap = 0; gap < 100;) { QString full = QString::asprintf(filename, i++); if (QFile::exists(full)) { QString key = QString::asprintf("%d", keyvalue++); mlt_properties_set(self->filenames, key.toLatin1().constData(), full.toUtf8().constData()); gap = 0; } else { gap++; } } #else char full[1024]; char key[50]; for (int gap = 0; gap < 100;) { struct stat buf; snprintf(full, 1023, filename, i++); if (stat(full, &buf) == 0) { sprintf(key, "%d", keyvalue++); mlt_properties_set(self->filenames, key, full); gap = 0; } else { gap++; } } #endif if (mlt_properties_count(self->filenames) > 0) { mlt_properties_set_int(properties, "ttl", 1); result = 1; } } return result; } int load_folder(producer_qimage self, const char *filename) { int result = 0; // Obtain filenames within folder if (strstr(filename, "/.all.") != NULL) { mlt_properties filename_property = self->filenames; QFileInfo info(filename); QDir dir = info.absoluteDir(); QStringList filters = {QString("*.%1").arg(info.suffix())}; QStringList files = dir.entryList(filters, QDir::Files, QDir::Name); int key; for (auto &f : files) { key = mlt_properties_count(filename_property); mlt_properties_set_string(filename_property, QString::number(key).toLatin1(), dir.absoluteFilePath(f).toUtf8().constData()); } result = 1; } return result; } } // extern "C" mlt-7.22.0/src/modules/qt/qimage_wrapper.h000664 000000 000000 00000004010 14531534050 020410 0ustar00rootroot000000 000000 /* * qimage_wrapper.h -- a QT/QImage based producer for MLT * Copyright (C) 2006-2021 Meltytech, LLC * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef MLT_QIMAGE_WRAPPER #define MLT_QIMAGE_WRAPPER #include #include #ifdef __cplusplus extern "C" { #endif struct producer_qimage_s { struct mlt_producer_s parent; mlt_properties filenames; int count; int image_idx; int qimage_idx; uint8_t *current_image; uint8_t *current_alpha; int current_width; int current_height; int alpha_size; mlt_cache_item image_cache; mlt_cache_item alpha_cache; mlt_cache_item qimage_cache; void *qimage; mlt_image_format format; }; typedef struct producer_qimage_s *producer_qimage; extern int refresh_qimage(producer_qimage self, mlt_frame frame, int enable_caching); extern void refresh_image( producer_qimage, mlt_frame, mlt_image_format, int width, int height, int enable_caching); extern void make_tempfile(producer_qimage, const char *xml); extern int init_qimage(mlt_producer producer, const char *filename); extern int load_sequence_sprintf(producer_qimage self, mlt_properties properties, const char *filename); extern int load_folder(producer_qimage self, const char *filename); #ifdef __cplusplus } #endif #endif mlt-7.22.0/src/modules/qt/transition_qtblend.cpp000664 000000 000000 00000027121 14531534050 021653 0ustar00rootroot000000 000000 /* * transition_qtblend.cpp -- Qt composite transition * Copyright (c) 2016 Jean-Baptiste Mardelle * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "common.h" #include #include #include #include #include #include static int get_image(mlt_frame a_frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { int error = 0; mlt_frame b_frame = mlt_frame_pop_frame(a_frame); mlt_properties b_properties = MLT_FRAME_PROPERTIES(b_frame); mlt_properties properties = MLT_FRAME_PROPERTIES(a_frame); mlt_transition transition = MLT_TRANSITION(mlt_frame_pop_service(a_frame)); mlt_properties transition_properties = MLT_TRANSITION_PROPERTIES(transition); uint8_t *b_image = NULL; // hasAlpha indicates whether the source material has an alpha channel bool hasAlpha = *format == mlt_image_rgba; // forceAlpha is true if some operation makes it mandatory to perform the alpha compositing, like padding or scaling bool forceAlpha = false; double opacity = 1.0; QTransform transform; // reference rect mlt_rect rect; // Determine length mlt_position length = mlt_transition_get_length(transition); // Get current position mlt_position position = mlt_transition_get_position(transition, a_frame); // Obtain the normalized width and height from the a_frame mlt_profile profile = mlt_service_profile(MLT_TRANSITION_SERVICE(transition)); int b_width = mlt_properties_get_int(b_properties, "meta.media.width"); int b_height = mlt_properties_get_int(b_properties, "meta.media.height"); bool distort = mlt_properties_get_int(transition_properties, "distort"); // Check the producer's native format before fetching image int sourceFormat = mlt_properties_get_int(b_properties, "format"); if (sourceFormat == mlt_image_rgba || sourceFormat == mlt_image_rgb) { hasAlpha = sourceFormat == mlt_image_rgba; *format = mlt_image_rgba; } if (b_height == 0) { b_width = *width; b_height = *height; } double b_ar = mlt_frame_get_aspect_ratio(b_frame); double b_dar = b_ar * b_width / b_height; rect.w = -1; rect.h = -1; if (!distort && (b_height < *height || b_width < *width)) { b_width = *width; b_height = *height; } // Check transform if (mlt_properties_get(transition_properties, "rect")) { rect = mlt_properties_anim_get_rect(transition_properties, "rect", position, length); if (::strchr(mlt_properties_get(transition_properties, "rect"), '%')) { // We have percentage values, scale to frame size rect.x *= *width; rect.y *= *height; rect.w *= *width; rect.h *= *height; } else { // Adjust to preview scaling double scale = mlt_profile_scale_width(profile, *width); if (scale != 1.0) { rect.x *= scale; rect.w *= scale; if (distort) { b_width *= scale; } } scale = mlt_profile_scale_height(profile, *height); if (scale != 1.0) { rect.y *= scale; rect.h *= scale; if (distort) { b_height *= scale; } } } transform.translate(rect.x, rect.y); opacity = rect.o; if (!distort) { b_width = qMin((int) rect.w, b_width); b_height = qMin((int) rect.h, b_height); transform.translate((rect.w - b_width) / 2.0, (rect.h - b_height) / 2.0); } if (opacity < 1 || rect.x != 0 || rect.y != 0 || (rect.x + rect.w != *width) || (rect.y + rect.h != *height)) { // we will process operations on top frame, so also process b_frame forceAlpha = true; } // Ensure we don't request an image with a 0 width or height b_width = qMax(1, b_width); b_height = qMax(1, b_height); } else { b_height = *height; b_width = *width; } double output_ar = mlt_profile_sar(profile); if (mlt_frame_get_aspect_ratio(b_frame) == 0) { mlt_frame_set_aspect_ratio(b_frame, output_ar); } if (mlt_properties_get(transition_properties, "rotation")) { double angle = mlt_properties_anim_get_double(transition_properties, "rotation", position, length); if (angle != 0.0) { if (mlt_properties_get_int(transition_properties, "rotate_center")) { transform.translate(rect.w / 2.0, rect.h / 2.0); transform.rotate(angle); transform.translate(-rect.w / 2.0, -rect.h / 2.0); } else { transform.rotate(angle); } forceAlpha = true; } } // This is not a field-aware transform. mlt_properties_set_int(b_properties, "consumer.progressive", 1); // Suppress padding and aspect normalization. char *interps = mlt_properties_get(properties, "consumer.rescale"); if (interps) interps = strdup(interps); if (error) { return error; } if (distort && b_width != 0 && b_height != 0) { transform.scale(rect.w / b_width, rect.h / b_height); } // Check profile dar vs image dar image if (!forceAlpha && rect.w == -1 && b_dar != mlt_profile_dar(profile)) { // Activate transparency if the clips don't have the same aspect ratio forceAlpha = true; } if (!forceAlpha && (mlt_properties_get_int(transition_properties, "compositing") != 0 || b_width < *width || b_height < *height)) { forceAlpha = true; } // Check if we have transparency int request_width = b_width; int request_height = b_height; bool imageFetched = false; if (!forceAlpha) { if (!hasAlpha || *format == mlt_image_rgba) { // fetch image in native format error = mlt_frame_get_image(b_frame, &b_image, format, &b_width, &b_height, 0); imageFetched = true; if (!hasAlpha && (*format == mlt_image_rgba || mlt_frame_get_alpha(b_frame))) { hasAlpha = true; } if (hasAlpha && *format == mlt_image_rgba) { hasAlpha = !mlt_image_rgba_opaque(b_image, b_width, b_height); } } if (!hasAlpha) { // Pass all required frame properties mlt_properties_pass_list(properties, b_properties, "progressive,distort,colorspace,full_range,force_full_luma," "top_field_first,color_trc"); // Prepare output image if (b_frame->convert_image && (b_width != request_width || b_height != request_height)) { mlt_properties_set_int(b_properties, "convert_image_width", request_width); mlt_properties_set_int(b_properties, "convert_image_height", request_height); b_frame->convert_image(b_frame, &b_image, format, *format); *width = request_width; *height = request_height; } else { *width = b_width; *height = b_height; } *image = b_image; free(interps); return 0; } } if (!imageFetched) { *format = mlt_image_rgba; error = mlt_frame_get_image(b_frame, &b_image, format, &b_width, &b_height, 0); } if (b_frame->convert_image && (*format != mlt_image_rgba || b_width != request_width || b_height != request_height)) { mlt_properties_set_int(b_properties, "convert_image_width", request_width); mlt_properties_set_int(b_properties, "convert_image_height", request_height); b_frame->convert_image(b_frame, &b_image, format, mlt_image_rgba); b_width = request_width; b_height = request_height; } *format = mlt_image_rgba; // Get bottom frame uint8_t *a_image = NULL; error = mlt_frame_get_image(a_frame, &a_image, format, width, height, 0); if (error) { free(interps); return error; } // Prepare output image int image_size = mlt_image_format_size(*format, *width, *height, NULL); *image = (uint8_t *) mlt_pool_alloc(image_size); // Copy bottom frame in output memcpy(*image, a_image, image_size); bool hqPainting = false; if (interps) { if (strcmp(interps, "bilinear") == 0 || strcmp(interps, "bicubic") == 0) { hqPainting = true; } } // convert bottom mlt image to qimage QImage bottomImg; convert_mlt_to_qimage_rgba(*image, &bottomImg, *width, *height); // convert top mlt image to qimage QImage topImg; convert_mlt_to_qimage_rgba(b_image, &topImg, b_width, b_height); // setup Qt drawing QPainter painter(&bottomImg); painter.setCompositionMode( (QPainter::CompositionMode) mlt_properties_get_int(transition_properties, "compositing")); painter.setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform, hqPainting); painter.setTransform(transform); painter.setOpacity(opacity); // Composite top frame painter.drawImage(0, 0, topImg); // finish Qt drawing painter.end(); convert_qimage_to_mlt_rgba(&bottomImg, *image, *width, *height); mlt_frame_set_image(a_frame, *image, image_size, mlt_pool_release); // Remove potentially large image on the B frame. mlt_frame_set_image(b_frame, NULL, 0, NULL); free(interps); return error; } static mlt_frame process(mlt_transition transition, mlt_frame a_frame, mlt_frame b_frame) { mlt_frame_push_service(a_frame, transition); mlt_frame_push_frame(a_frame, b_frame); mlt_frame_push_get_image(a_frame, get_image); return a_frame; } extern "C" { mlt_transition transition_qtblend_init(mlt_profile profile, mlt_service_type type, const char *id, void *arg) { mlt_transition transition = mlt_transition_new(); if (transition) { mlt_properties properties = MLT_TRANSITION_PROPERTIES(transition); if (!createQApplicationIfNeeded(MLT_TRANSITION_SERVICE(transition))) { mlt_transition_close(transition); return NULL; } transition->process = process; mlt_properties_set_int(properties, "_transition_type", 1); // video only mlt_properties_set(properties, "rect", (char *) arg); mlt_properties_set_int(properties, "compositing", 0); mlt_properties_set_int(properties, "distort", 0); mlt_properties_set_int(properties, "rotate_center", 0); } return transition; } } // extern "C" mlt-7.22.0/src/modules/qt/transition_qtblend.yml000664 000000 000000 00000002572 14531534050 021675 0ustar00rootroot000000 000000 schema_version: 0.1 type: transition identifier: qtblend title: Composite and transform version: 3 copyright: Meltytech, LLC creator: Jean-Baptiste Mardelle license: LGPLv2.1 language: en tags: - Video description: > A transition allowing compositing and transform. parameters: - identifier: rect title: Rectangle type: rect description: > Keyframable rectangle specification. mutable: yes - identifier: distort title: Ignore aspect ratio description: > Determines whether the image aspect ratio will be distorted while scaling to completely fill the geometry rectangle. type: boolean default: 0 mutable: yes widget: checkbox - identifier: compositing title: Composition mode description: > Defines which composition operation will be performed (see QPainter CompositionMode for doc). type: integer default: 0 minimum: 0 maximum: 40 mutable: yes widget: spinner - identifier: rotation title: Rotation angle description: > Angle for rotation. type: float default: 1 minimum: 0 maximum: 360 mutable: yes unit: degrees - identifier: rotate_center title: Rotate from center type: integer description: Process the rotation from center if set, otherwise from top left corner minimum: 0 maximum: 1 mutable: yes widget: checkbox mlt-7.22.0/src/modules/qt/transition_vqm.cpp000664 000000 000000 00000022231 14531534050 021022 0ustar00rootroot000000 000000 /* * transition_vqm.c -- video quality measurement * Copyright (c) 2012 Dan Dennedy * Core psnr and ssim routines based on code from * qsnr (C) 2010 E. Oriani, ema fastwebnet it * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see */ #include "common.h" #include #include #include #include #include #include #include #include #include #include static double calc_psnr(const uint8_t *a, const uint8_t *b, int size, int bpp) { double mse = 0.0; int n = size + 1; while (--n) { int diff = *a - *b; mse += diff * diff; a += bpp; b += bpp; } return 10.0 * log10(255.0 * 255.0 / (mse == 0 ? 1e-10 : mse / size)); } static double calc_ssim( const uint8_t *a, const uint8_t *b, int width, int height, int window_size, int bpp) { int windows_x = width / window_size; int windows_y = height / window_size; double avg = 0.0; if (!windows_x || !windows_y) return 0.0; // for each window for (int y = 0; y < windows_y; ++y) for (int x = 0; x < windows_x; ++x) { int base_offset = x * window_size + y * window_size * width; double ref_acc = 0.0, ref_acc_2 = 0.0, cmp_acc = 0.0, cmp_acc_2 = 0.0, ref_cmp_acc = 0.0; // accumulate the pixel values for this window for (int j = 0; j < window_size; ++j) for (int i = 0; i < window_size; ++i) { uint8_t c_a = a[bpp * (base_offset + j * width + i)]; uint8_t c_b = b[bpp * (base_offset + j * width + i)]; ref_acc += c_a; ref_acc_2 += c_a * c_a; cmp_acc += c_b; cmp_acc_2 += c_b * c_b; ref_cmp_acc += c_a * c_b; } // compute the SSIM for this window // http://en.wikipedia.org/wiki/SSIM // http://en.wikipedia.org/wiki/Variance // http://en.wikipedia.org/wiki/Covariance double n_samples = window_size * window_size, ref_avg = ref_acc / n_samples, ref_var = ref_acc_2 / n_samples - ref_avg * ref_avg, cmp_avg = cmp_acc / n_samples, cmp_var = cmp_acc_2 / n_samples - cmp_avg * cmp_avg, ref_cmp_cov = ref_cmp_acc / n_samples - ref_avg * cmp_avg, c1 = 6.5025, // (0.01*255.0)^2 c2 = 58.5225, // (0.03*255)^2 ssim_num = (2.0 * ref_avg * cmp_avg + c1) * (2.0 * ref_cmp_cov + c2), ssim_den = (ref_avg * ref_avg + cmp_avg * cmp_avg + c1) * (ref_var + cmp_var + c2); // accumulate the SSIM avg += ssim_num / ssim_den; } // return the average SSIM return avg / windows_x / windows_y; } static int get_image(mlt_frame a_frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { mlt_frame b_frame = mlt_frame_pop_frame(a_frame); mlt_properties properties = MLT_FRAME_PROPERTIES(a_frame); mlt_transition transition = MLT_TRANSITION(mlt_frame_pop_service(a_frame)); uint8_t *b_image; int window_size = mlt_properties_get_int(MLT_TRANSITION_PROPERTIES(transition), "window_size"); double psnr[3], ssim[3]; *format = mlt_image_yuv422; mlt_frame_get_image(b_frame, &b_image, format, width, height, writable); mlt_frame_get_image(a_frame, image, format, width, height, writable); psnr[0] = calc_psnr(*image, b_image, *width * *height, 2); psnr[1] = calc_psnr(*image + 1, b_image + 1, *width * *height / 2, 4); psnr[2] = calc_psnr(*image + 3, b_image + 3, *width * *height / 2, 4); ssim[0] = calc_ssim(*image, b_image, *width, *height, window_size, 2); ssim[1] = calc_ssim(*image + 1, b_image + 1, *width / 2, *height, window_size, 4); ssim[2] = calc_ssim(*image + 3, b_image + 3, *width / 2, *height, window_size, 4); mlt_properties_set_double(properties, "meta.vqm.psnr.y", psnr[0]); mlt_properties_set_double(properties, "meta.vqm.psnr.cb", psnr[1]); mlt_properties_set_double(properties, "meta.vqm.psnr.cr", psnr[2]); mlt_properties_set_double(properties, "meta.vqm.ssim.y", ssim[0]); mlt_properties_set_double(properties, "meta.vqm.ssim.cb", ssim[1]); mlt_properties_set_double(properties, "meta.vqm.ssim.cr", ssim[2]); printf("%05d %05.2f %05.2f %05.2f %5.3f %5.3f %5.3f\n", mlt_frame_get_position(a_frame), psnr[0], psnr[1], psnr[2], ssim[0], ssim[1], ssim[2]); // copy the B frame to the bottom of the A frame for comparison window_size = mlt_image_format_size(*format, *width, *height, NULL) / 2; memcpy(*image + window_size, b_image + window_size, window_size); if (!mlt_properties_get_int(MLT_TRANSITION_PROPERTIES(transition), "render")) return 0; // get RGBA image for Qt drawing *format = mlt_image_rgba; mlt_frame_get_image(a_frame, image, format, width, height, 1); // convert mlt image to qimage QImage img(*width, *height, QImage::Format_ARGB32); int y = *height + 1; uint8_t *src = *image; while (--y) { QRgb *dst = (QRgb *) img.scanLine(*height - y); int x = *width + 1; while (--x) { *dst++ = qRgba(src[0], src[1], src[2], 255); src += 4; } } // setup Qt drawing QPainter painter; painter.begin(&img); painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) | QPainter::HighQualityAntialiasing #endif ); // draw some stuff with Qt QPalette palette; QFont font; QString s; font.setBold(true); font.setPointSize(30 * *height / 1080); painter.setPen(QColor("black")); painter.drawLine(0, *height / 2 + 1, *width, *height / 2); painter.setPen(QColor("white")); painter.drawLine(0, *height / 2 - 1, *width, *height / 2); painter.setFont(font); s.asprintf("Frame: %05d\nPSNR: %05.2f (Y) %05.2f (Cb) %05.2f (Cr)\nSSIM: %5.3f (Y) %5.3f " "(Cb) %5.3f (Cr)", mlt_frame_get_position(a_frame), psnr[0], psnr[1], psnr[2], ssim[0], ssim[1], ssim[2]); painter.setPen(QColor("black")); painter.drawText(52, *height * 8 / 10 + 2, *width, *height, 0, s); painter.setPen(QColor("white")); painter.drawText(50, *height * 8 / 10, *width, *height, 0, s); // finish Qt drawing painter.end(); window_size = mlt_image_format_size(*format, *width, *height, NULL); uint8_t *dst = (uint8_t *) mlt_pool_alloc(window_size); mlt_properties_set_data(MLT_FRAME_PROPERTIES(a_frame), "image", dst, window_size, mlt_pool_release, NULL); *image = dst; // convert qimage to mlt y = *height + 1; while (--y) { QRgb *src = (QRgb *) img.scanLine(*height - y); int x = *width + 1; while (--x) { *dst++ = qRed(*src); *dst++ = qGreen(*src); *dst++ = qBlue(*src); *dst++ = qAlpha(*src); src++; } } return 0; } static mlt_frame process(mlt_transition transition, mlt_frame a_frame, mlt_frame b_frame) { mlt_frame_push_service(a_frame, transition); mlt_frame_push_frame(a_frame, b_frame); mlt_frame_push_get_image(a_frame, get_image); return a_frame; } extern "C" { mlt_transition transition_vqm_init(mlt_profile profile, mlt_service_type type, const char *id, void *arg) { mlt_transition transition = mlt_transition_new(); if (transition) { mlt_properties properties = MLT_TRANSITION_PROPERTIES(transition); if (!createQApplicationIfNeeded(MLT_TRANSITION_SERVICE(transition))) { mlt_transition_close(transition); return NULL; } transition->process = process; mlt_properties_set_int(properties, "_transition_type", 1); // video only mlt_properties_set_int(properties, "window_size", 8); printf("frame psnr[Y] psnr[Cb] psnr[Cr] ssim[Y] ssim[Cb] ssim[Cr]\n"); } return transition; } } // extern "C" mlt-7.22.0/src/modules/qt/transition_vqm.yml000664 000000 000000 00000001347 14531534050 021046 0ustar00rootroot000000 000000 schema_version: 0.1 type: transition identifier: vqm title: Video Quality Measurement version: 1 copyright: Dan Dennedy creator: Dan Dennedy license: GPLv3 language: en description: > This performs the PSNR and SSIM video quality measurements by comparing the B frames to the reference frame A. It outputs the numbers to stdout in space-delimited format for easy by another tool. The bottom half of the B frame is placed below the top half of the A frame for visual comparison. tags: - Video parameters: - identifier: render title: Render description: > Render a line between top and bottom halves and the values atop the video. type: integer default: 0 minimum: 0 maximum: 1 widget: checkbox mlt-7.22.0/src/modules/qt/typewriter.cpp000664 000000 000000 00000037644 14531534050 020201 0ustar00rootroot000000 000000 /* * Copyright (c) 2017 Rafal Lalik rafallalik@gmail.com * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #include "typewriter.h" #include #include #include #include using namespace std; /*======================================================================* TypeWriter parser related stuff *======================================================================*/ const char macro_char = ':'; const char nextframe_char = ','; const char nextstep_char = '>'; const char delkey_char = '<'; const char optbeg_char = '['; const char optend_char = ']'; const char rangebeg_char = '{'; const char rangeend_char = '}'; const char escape_char = '\\'; std::string null_string; TypeWriter::TypeWriter() : frame_rate(25) , frame_step(1) , parsing_err(0) , last_used_idx(-1) {} void TypeWriter::clear() { frames.clear(); } void TypeWriter::setPattern(const std::string &str) { raw_string = str; frames.reserve(raw_string.length()); } int TypeWriter::parse() { clear(); gen.seed(step_seed); if (step_sigma > 0.) d = std::normal_distribution<>{0, step_sigma}; previous_total_frame = -1; int start_frame = 0; parsing_err = parseString(raw_string, start_frame); return parsing_err; } void TypeWriter::printParseResult() { if (parsing_err < 0) { fprintf(stderr, "Parsing error:\n%.*s\n", -parsing_err - 1, raw_string.c_str()); fprintf(stderr, "%*c%c\n", -parsing_err - 2, ' ', '^'); } else { printf("Parsing OK: frames=%u strings=%zu\n", count(), frames.size()); } } uint TypeWriter::count() const { return frames.back().frame; } uint TypeWriter::getOrInsertFrame(uint frame) { // create new or reuse old frame // by design last->frame cannot be larger than frame // take the last frame then FIXME: should we break parser here? int real_frame = frame * frame_step; uint n = frames.size(); if (!n) { int s = step_sigma > 0. ? std::round(d(gen)) : 0; if ((s + real_frame) > 0) real_frame += s; if (real_frame <= previous_total_frame) real_frame = previous_total_frame + 1; previous_total_frame = real_frame; frames.push_back(Frame(frame, real_frame)); return 0; } if (frames[n - 1].frame >= frame) return n - 1; int s = step_sigma > 0. ? std::round(d(gen)) : 0; if ((s + real_frame) > 0) real_frame += s; if (real_frame <= previous_total_frame) real_frame = previous_total_frame + 1; previous_total_frame = real_frame; Frame f = Frame(frame, real_frame); f.s = frames[n - 1].s; frames.push_back(f); return n; } void TypeWriter::insertChar(char c, uint frame) { char buff[2] = "\0"; buff[0] = c; insertString(buff, frame); } void TypeWriter::insertString(const std::string &str, uint frame) { uint n = getOrInsertFrame(frame); Frame &f = frames[n]; f.s.append(str); } void TypeWriter::insertBypass(uint frame) { uint n = getOrInsertFrame(frame); addBypass(n); } const std::string &TypeWriter::render(uint frame) { uint n = frames.size(); if (!n) return null_string; if (last_used_idx == -1) last_used_idx = 0; // start with current frame Frame f = frames[last_used_idx]; // but if current is ahead 'frame', start from beginning if (f.real_frame > frame) last_used_idx = 0; if (frames[last_used_idx].real_frame > frame) return null_string; for (; last_used_idx < (int) n - 1; ++last_used_idx) { f = frames[last_used_idx + 1]; if (f.real_frame > frame) return frames[last_used_idx].s; } return frames[last_used_idx].s; } void TypeWriter::addBypass(uint idx) { if (idx == 0) { frames[idx].s.clear(); return; } int pidx = -1; if (frames[idx].bypass == -2) pidx = idx - 1; else pidx = frames[idx].bypass; if (pidx == -1) return; while (true) { if (frames[pidx].bypass != -2) { pidx = frames[pidx].bypass; } else { --pidx; break; } } frames[idx].bypass = pidx; if (frames[idx].bypass >= 0) frames[idx].s = frames[frames[idx].bypass].s; else frames[idx].s.clear(); } Frame::Frame(uint frame, uint real_frame) : frame(frame) , real_frame(real_frame) , bypass(-2) {} void Frame::print() { printf("%c [%d] (%d) %s %c\n", true ? '-' : '|', frame, real_frame, s.c_str(), true ? '-' : '|'); } std::string TypeWriter::detectUtf8(const std::string &str, size_t pos) { /* * 0x00 do 0x7F – bits 0xxxxxxx * 0x80 do 0x7FF – bits 110xxxxx 10xxxxxx * 0x800 do 0xFFFF – bits 1110xxxx 10xxxxxx 10xxxxxx * 0x10000 do 0x1FFFFF – bits 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx * 0x200000 do 0x3FFFFFF – bits 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx * 0x4000000 do 0x7FFFFFFF – bits 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx */ unsigned char c = str[pos]; const unsigned char mask = 0xfc; // largest possible utf char // five patterns possible for (int i = 0; i < 5; ++i) { unsigned char m = mask << i; if ((c & m) == m) return str.substr(pos, 6 - i); } return str.substr(pos, 1); } struct TypeWriter::ParseOptions { ParseOptions() : n(0) , fskip(0) , sskip(0) {} uint n; uint fskip; uint sskip; }; uint TypeWriter::getFrameSkipFromOptions(const ParseOptions &po, bool steps) { if (steps) return (po.n + po.sskip) * frame_rate; else return po.sskip * frame_rate + po.fskip + po.n; } int TypeWriter::parseString(const std::string &line, int start_frame) { size_t limit = line.length(); uint frame = start_frame; std::string frame_text; char check_for_options = 0; bool was_escaped = false; uint i = 0; while (i < limit) { was_escaped = false; check_for_options = 0; char c = line[i]; if (c == escape_char) { ++i; c = line[i]; if (!c) return -i - 1; was_escaped = true; } else if (c == nextframe_char) { ++frame; // increase frame number check_for_options = nextframe_char; } else if (c == nextstep_char) { frame += frame_rate; check_for_options = nextstep_char; } else if (c == macro_char) { ++i; int ret = parseMacro(line, i, frame); if (ret < 0) return ret; continue; } if (check_for_options) { // get next char and check whether it is an option ++i; c = line[i]; ParseOptions po; int ret = parseOptions(line, i, po); if (ret < 0) { return ret; } uint n = getFrameSkipFromOptions(po, check_for_options == nextstep_char); if (check_for_options == nextframe_char) { if (n > 0) frame += (n - 1); } else if (check_for_options == nextstep_char) { if (n > 0) frame += (n - frame_rate); } continue; } // append values if (!was_escaped and c == delkey_char) { // get next char and check whether it is an option ++i; c = line[i]; ParseOptions po; po.n = 1; int ret = parseOptions(line, i, po); if (ret < 0) { return ret; } for (uint i = 0; i < po.n; ++i) insertBypass(frame); } else { if (was_escaped) { if (c == 'n') c = '\n'; else if (c == 't') c = '\t'; } std::string test_str = detectUtf8(line, i); insertString(test_str, frame); i += test_str.length(); } } return frame; } int TypeWriter::parseOptions(const std::string &line, uint &i, ParseOptions &po) { char c = line[i]; if (c != optbeg_char) return i; ++i; c = line[i]; int n = 0; // stores number of frames to skip while (c != optend_char and c) { // if is digit then add to frames skip number if (isdigit(c)) { int v = c - 48; // quick conv from char to int n = n * 10 + v; } // s if for seconds: mult frames by f. rate else if (c == 's') { po.sskip = n; n = 0; } // just frames else if (c == 'f') { po.fskip = n; n = 0; } else if (c == ',') { if (n) po.n = n; } else { // unknown char, return error return -i - 1; } ++i; c = line[i]; } if (n) po.n = n; ++i; return i; } int TypeWriter::parseMacro(const std::string &line, uint &i, uint &frame) { std::vector string_list; uint n = 0; char c = line[i]; if (c == 'c') // split by characters { ++i; // calculate skip from options ParseOptions po; int ret = parseOptions(line, i, po); if (ret < 0) { return ret; } n = getFrameSkipFromOptions(po); if (n == 0) n = 1; c = line[i]; if (c != rangebeg_char) return -i - 1; ++i; c = line[i]; while (c != rangeend_char and c) { if (c == escape_char) { ++i; c = line[i]; if (!c) return -i - 1; } std::string test_str = detectUtf8(line, i); insertString(test_str, frame); frame += n; i += test_str.length(); c = line[i]; } } else if (c == 'w') // split by words { ++i; // calculate skip from options ParseOptions po; int ret = parseOptions(line, i, po); if (ret < 0) { return ret; } n = getFrameSkipFromOptions(po); if (n == 0) n = 1; c = line[i]; if (c != rangebeg_char) return -i - 1; ++i; c = line[i]; size_t i_end = i; while (true) { // search for range end i_end = line.find_first_of(rangeend_char, i_end); // if end of string, error if (i_end == line.npos) return -i_end - 1; // check if endrange char is not escaped if (line[i_end - 1] != escape_char or line[i_end - 2] == escape_char) break; ++i_end; } std::string substr = line.substr(i, i_end - i); size_t pos = 0; while (true) { pos = substr.find_first_of(escape_char, pos); if (pos == substr.npos) break; substr.replace(pos, 1, ""); } pos = 0; size_t pos2 = 0; std::string s; while (pos2 != substr.npos) { pos2 = substr.find_first_of(" \t\n", pos); if (pos2 != substr.npos) { pos2 = substr.find_first_not_of(" \t\n", pos2); if (pos2 != substr.npos) s = substr.substr(pos, pos2 - pos); else s = substr.substr(pos, -1); } else { s = substr.substr(pos, -1); } insertString(s, frame); frame += n; pos = pos2; } i = i_end; ++i; } else if (c == 'l') // split by lines { ++i; // calculate skip from options ParseOptions po; int ret = parseOptions(line, i, po); if (ret < 0) { return ret; } n = getFrameSkipFromOptions(po); if (n == 0) n = 1; c = line[i]; if (c != rangebeg_char) return -i - 1; ++i; c = line[i]; size_t i_end = i; while (true) { // search for range end i_end = line.find_first_of(rangeend_char, i_end); // if end of string, error if (i_end == line.npos) return -i_end - 1; // check if endrange char is not escaped if (line[i_end - 1] != escape_char or line[i_end - 2] == escape_char) break; ++i_end; } std::string substr = line.substr(i, i_end - i); size_t pos = 0; while (true) { pos = substr.find_first_of(escape_char, pos); if (pos == substr.npos) break; substr.replace(pos, 1, ""); } pos = 0; size_t pos2 = 0; std::string s; while (pos2 != substr.npos) { pos2 = substr.find_first_of("\n", pos); if (pos2 != substr.npos) { pos2 = substr.find_first_not_of("\n", pos2); if (pos2 != substr.npos) s = substr.substr(pos, pos2 - pos); else s = substr.substr(pos, -1); } else { s = substr.substr(pos, -1); } insertString(s, frame); frame += n; pos = pos2; } i = i_end; ++i; } else // error { return -i - 1; } ++i; return i; } /*======================================================================* XML Parser related stuff *======================================================================*/ char *clone_string(const char *string) { char *new_string = new char[strlen(string) + 1]; if (new_string) strcpy(new_string, string); return new_string; } XmlParser::XmlParser() {} XmlParser::~XmlParser() {} void XmlParser::clear() {} void XmlParser::setDocument(const char *xml) { clear(); doc = QString::fromUtf8(xml); dom.setContent(doc); QDomElement title = dom.documentElement(); items = title.elementsByTagName("item"); } int XmlParser::parse() { node_vec.clear(); for (int i = 0; i < items.count(); ++i) { QDomNode node = items.item(i); QDomNamedNodeMap nodeAttributes = node.attributes(); if (nodeAttributes.namedItem("type").nodeValue() == "QGraphicsTextItem") { QDomNode lnode = node.namedItem("content").firstChild(); node_vec.push_back(lnode); } } return 1; } QString XmlParser::getNodeContent(uint i) const { if (i >= node_vec.size()) return nullptr; return node_vec[i].nodeValue(); } void XmlParser::setNodeContent(uint i, const QString &content) { if (i >= node_vec.size()) return; node_vec[i].setNodeValue(content); } QString XmlParser::getDocument() const { return dom.toString(); } mlt-7.22.0/src/modules/qt/typewriter.h000664 000000 000000 00000007221 14531534050 017632 0ustar00rootroot000000 000000 /* * Copyright (c) 2017 Rafal Lalik rafallalik@gmail.com * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ #ifndef TYPEWRITER_H #define TYPEWRITER_H #include #include #include #include using namespace std; struct Frame { Frame(uint frame, uint real_frame); uint frame; uint real_frame; std::string s; int bypass; void print(); }; class TypeWriter { public: TypeWriter(); virtual ~TypeWriter() = default; void setFrameRate(uint fr) { frame_rate = fr; } uint getFrameRate() const { return frame_rate; } void setFrameStep(uint fs) { frame_step = fs; } uint getFrameStep() const { return frame_step; } void setStepSigma(float ss) { step_sigma = ss; } uint getStepSigma() const { return step_sigma; } void setStepSeed(float ss) { step_seed = ss; } uint getStepSeed() const { return step_seed; } void setPattern(const std::string &str); const std::string &getPattern() const { return raw_string; } int parse(); void printParseResult(); const std::string &render(uint frame); uint count() const; bool isEnd() const { return last_used_idx == (int) frames.size() - 1; } void clear(); void debug() const { for (Frame f : frames) f.print(); } private: int parseString(const std::string &line, int start_frame); struct ParseOptions; int parseOptions(const std::string &line, uint &i, ParseOptions &po); int parseMacro(const std::string &line, uint &i, uint &frame); std::string detectUtf8(const std::string &str, size_t pos); void insertChar(char c, uint frame); void insertString(const std::string &str, uint frame); void insertBypass(uint frame); uint getFrameSkipFromOptions(const ParseOptions &po, bool steps = false); uint getOrInsertFrame(uint frame); void addBypass(uint idx); private: size_t frame_rate; size_t frame_step; float step_sigma; size_t step_seed; int parsing_err; int previous_total_frame; std::string raw_string; std::vector frames; int last_used_idx; std::mt19937 gen; std::normal_distribution<> d; }; class XmlParser { public: XmlParser(); virtual ~XmlParser(); void setDocument(const char *xml); int parse(); uint getContentNodesNumber() const { return node_vec.size(); } QString getNodeContent(uint i) const; void setNodeContent(uint i, const QString &content); void clear(); QString getDocument() const; private: QString doc; QDomDocument dom; QDomNodeList items; std::vector node_vec; }; #endif /* TYPEWRITER_H */ mlt-7.22.0/src/modules/resample/000775 000000 000000 00000000000 14531534050 016425 5ustar00rootroot000000 000000 mlt-7.22.0/src/modules/resample/CMakeLists.txt000664 000000 000000 00000001107 14531534050 021164 0ustar00rootroot000000 000000 add_library(mltresample MODULE factory.c filter_resample.c link_resample.c) file(GLOB YML "*.yml") add_custom_target(Other_resample_Files SOURCES ${YML} ) target_compile_options(mltresample PRIVATE ${MLT_COMPILE_OPTIONS}) target_link_libraries(mltresample PRIVATE mlt PkgConfig::samplerate) set_target_properties(mltresample PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${MLT_MODULE_OUTPUT_DIRECTORY}") install(TARGETS mltresample LIBRARY DESTINATION ${MLT_INSTALL_MODULE_DIR}) install(FILES filter_resample.yml link_resample.yml DESTINATION ${MLT_INSTALL_DATA_DIR}/resample ) mlt-7.22.0/src/modules/resample/factory.c000664 000000 000000 00000003617 14531534050 020247 0ustar00rootroot000000 000000 /* * factory.c -- the factory method interfaces * Copyright (C) 2003-2014 Meltytech, LLC * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include extern mlt_filter filter_resample_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_link link_resample_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); static mlt_properties metadata(mlt_service_type type, const char *id, void *data) { char file[PATH_MAX]; snprintf(file, PATH_MAX, "%s/resample/%s", mlt_environment("MLT_DATA"), (char *) data); return mlt_properties_parse_yaml(file); } MLT_REPOSITORY { MLT_REGISTER(mlt_service_filter_type, "resample", filter_resample_init); MLT_REGISTER(mlt_service_link_type, "resample", link_resample_init); MLT_REGISTER_METADATA(mlt_service_filter_type, "resample", metadata, "filter_resample.yml"); MLT_REGISTER_METADATA(mlt_service_link_type, "resample", metadata, "link_resample.yml"); } mlt-7.22.0/src/modules/resample/filter_resample.c000664 000000 000000 00000022352 14531534050 021752 0ustar00rootroot000000 000000 /* * filter_resample.c -- adjust audio sample frequency * Copyright (C) 2003-2021 Meltytech, LLC * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include #include #define PROCESS_BUFF_SIZE (10000 * sizeof(float)) // Private Types typedef struct { SRC_STATE *s; int error; int channels; float buff[PROCESS_BUFF_SIZE]; int leftover_samples; } private_data; /** Get the audio. */ static int resample_get_audio(mlt_frame frame, void **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples) { mlt_filter filter = mlt_frame_pop_audio(frame); private_data *pdata = (private_data *) filter->child; struct mlt_audio_s in; struct mlt_audio_s out; mlt_audio_set_values(&out, NULL, *frequency, *format, *samples, *channels); // Apply user requested rate - else will normalize to consumer requested rate; if (mlt_properties_get_int(MLT_FILTER_PROPERTIES(filter), "frequency")) { out.frequency = mlt_properties_get_int(MLT_FILTER_PROPERTIES(filter), "frequency"); } // Get the producer's audio int error = mlt_frame_get_audio(frame, buffer, format, frequency, channels, samples); if (error || *format == mlt_audio_none || *frequency <= 0 || out.frequency <= 0 || *channels <= 0) { // Error situation. Do not attempt to convert. mlt_log_error(MLT_FILTER_SERVICE(filter), "Invalid Parameters: %dS - %dHz %dC %s -> %dHz %dC %s\n", *samples, *frequency, *channels, mlt_audio_format_name(*format), out.frequency, out.channels, mlt_audio_format_name(out.format)); return error; } if (*samples == 0) { // Noting to convert. No error message needed. return error; } if (*frequency == out.frequency && !pdata) { // No frequency change, and there is no stored state. return error; } // *Proceed to convert the sampling frequency* // The converter operates on interleaved float. Convert the samples if necessary if (*format != mlt_audio_f32le) { // Do not convert to float unless we need to change the rate frame->convert_audio(frame, buffer, format, mlt_audio_f32le); } // Set up audio structures and allocate output buffer mlt_audio_set_values(&in, *buffer, *frequency, *format, *samples, *channels); out.format = in.format; out.channels = in.channels; mlt_audio_alloc_data(&out); mlt_log_debug(MLT_FILTER_SERVICE(filter), "%dHz -> %dHz\n", in.frequency, out.frequency); mlt_service_lock(MLT_FILTER_SERVICE(filter)); // Create the private data if it does not exist if (!pdata) { pdata = (private_data *) calloc(1, sizeof(private_data)); pdata->s = NULL; pdata->channels = 0; pdata->leftover_samples = 0; filter->child = pdata; } // Recreate the resampler if necessary if (!pdata->s || pdata->channels != in.channels) { mlt_log_debug(MLT_FILTER_SERVICE(filter), "Create resample state %d channels\n", in.channels); pdata->s = src_delete(pdata->s); pdata->s = src_new(SRC_SINC_BEST_QUALITY, in.channels, &pdata->error); pdata->channels = in.channels; } int total_consumed_samples = 0; int consumed_samples = 0; int received_samples = 0; int process_buff_samples = PROCESS_BUFF_SIZE / sizeof(float) / in.channels; // First copy samples that are leftover from the previous frame if (pdata->leftover_samples) { int samples_to_copy = pdata->leftover_samples; if (samples_to_copy > out.samples) { samples_to_copy = out.samples; } memcpy(out.data, pdata->buff, samples_to_copy * out.channels * sizeof(float)); received_samples += samples_to_copy; pdata->leftover_samples -= samples_to_copy; } // Process all input samples while (total_consumed_samples < in.samples || received_samples < out.samples) { if (pdata->leftover_samples) { mlt_log_error(MLT_FILTER_SERVICE(filter), "Discard leftover samples %d\n", pdata->leftover_samples); pdata->leftover_samples = 0; } if (consumed_samples >= in.samples) { // Continue to repeat input samples into the resampler until it // provides the desired number of samples out. consumed_samples = 0; mlt_log_debug(MLT_FILTER_SERVICE(filter), "Repeat samples\n"); } SRC_DATA data; data.end_of_input = 0; data.src_ratio = (double) out.frequency / (double) in.frequency; data.data_in = (float *) in.data + (consumed_samples * in.channels); data.input_frames = in.samples - consumed_samples; data.data_out = pdata->buff; data.output_frames = process_buff_samples; if (total_consumed_samples >= in.samples) { // All input samples have been read once. // Limit the output frames to the minimum necessary to fill the output frame. // Limit the input frames to 1 at a time to minimize the duplicated samples. // Sometimes one input frame can cause many frames to be output from the resampler. data.input_frames = 1; if (data.output_frames > (out.samples - received_samples)) { data.output_frames = out.samples - received_samples; } } // Resample the audio src_set_ratio(pdata->s, data.src_ratio); error = src_process(pdata->s, &data); if (error) { mlt_log_error(MLT_FILTER_SERVICE(filter), "%s %d,%d,%d\n", src_strerror(error), in.frequency, in.samples, out.frequency); break; } // Copy resampled samples from buff to output if (data.output_frames_gen) { float *dst = (float *) out.data + (received_samples * out.channels); int samples_to_copy = data.output_frames_gen; if (samples_to_copy > (out.samples - received_samples)) { samples_to_copy = out.samples - received_samples; } int bytes_to_copy = samples_to_copy * out.channels * sizeof(float); memcpy(dst, pdata->buff, bytes_to_copy); if (samples_to_copy < data.output_frames_gen) { // Move leftover samples to the beginning of buff to use next time pdata->leftover_samples = data.output_frames_gen - samples_to_copy; float *src = pdata->buff + (samples_to_copy * out.channels); memmove(pdata->buff, src, pdata->leftover_samples * out.channels * sizeof(float)); } received_samples += samples_to_copy; } consumed_samples += data.input_frames_used; total_consumed_samples += data.input_frames_used; } mlt_frame_set_audio(frame, out.data, out.format, 0, out.release_data); mlt_audio_get_values(&out, buffer, frequency, format, samples, channels); mlt_service_unlock(MLT_FILTER_SERVICE(filter)); return error; } /** Filter processing. */ static mlt_frame filter_process(mlt_filter filter, mlt_frame frame) { if (mlt_frame_is_test_audio(frame) == 0) { mlt_frame_push_audio(frame, filter); mlt_frame_push_audio(frame, resample_get_audio); } return frame; } static void close_filter(mlt_filter filter) { private_data *pdata = (private_data *) filter->child; if (pdata) { if (pdata->s) { src_delete(pdata->s); } free(pdata); filter->child = NULL; } } /** Constructor for the filter. */ mlt_filter filter_resample_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = mlt_filter_new(); if (filter) { filter->process = filter_process; filter->close = close_filter; filter->child = NULL; } else { mlt_log_error(MLT_FILTER_SERVICE(filter), "Failed to initialize\n"); } return filter; } mlt-7.22.0/src/modules/resample/filter_resample.yml000664 000000 000000 00000001233 14531534050 022324 0ustar00rootroot000000 000000 schema_version: 0.1 type: filter identifier: resample title: Resample version: 1 copyright: Meltytech, LLC creator: Dan Dennedy license: LGPLv2.1 language: en tags: - Audio - Hidden description: > Adjust an audio stream's sampling rate. This filter is automatically invoked by the loader producer to normalize audio from the producer to provide the rate requested by the consumer. parameters: - identifier: frequency title: Frequency type: integer description: > The target sample rate. If not set, the filter will convert to the sample rate requested by the consumer. required: no readonly: no mlt-7.22.0/src/modules/resample/gpl000664 000000 000000 00000000000 14531534050 017120 0ustar00rootroot000000 000000 mlt-7.22.0/src/modules/resample/link_resample.c000664 000000 000000 00000027124 14531534050 021424 0ustar00rootroot000000 000000 /* * link_resample.c * Copyright (C) 2022 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include // Private Types #define FUTURE_FRAMES 1 typedef struct { // Used by get_audio mlt_position expected_frame; mlt_position continuity_frame; int continuity_sample; SRC_STATE *s; int channels; } private_data; static void link_configure(mlt_link self, mlt_profile chain_profile) { // Operate at the same frame rate as the next link mlt_service_set_profile(MLT_LINK_SERVICE(self), mlt_service_profile(MLT_PRODUCER_SERVICE(self->next))); } static int link_get_audio(mlt_frame frame, void **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples) { int requested_frequency = *frequency <= 0 ? 48000 : *frequency; int requested_samples = *samples; mlt_link self = (mlt_link) mlt_frame_pop_audio(frame); private_data *pdata = (private_data *) self->child; int src_frequency = mlt_properties_get_int(MLT_FRAME_PROPERTIES(frame), "audio_frequency"); src_frequency = src_frequency <= 0 ? *frequency : src_frequency; int src_samples = mlt_audio_calculate_frame_samples(mlt_producer_get_fps( MLT_LINK_PRODUCER(self)), src_frequency, mlt_frame_get_position(frame)); mlt_properties frame_properties = MLT_FRAME_PROPERTIES(frame); struct mlt_audio_s in; struct mlt_audio_s out; mlt_audio_set_values(&in, *buffer, src_frequency, *format, src_samples, *channels); mlt_audio_set_values(&out, NULL, requested_frequency, *format, requested_samples, *channels); // Get the producer's audio int error = mlt_frame_get_audio(frame, &in.data, &in.format, &in.frequency, &in.channels, &in.samples); if (error || in.format == mlt_audio_none || out.format == mlt_audio_none || in.frequency <= 0 || out.frequency <= 0 || in.channels <= 0 || out.channels <= 0) { // Error situation. Do not attempt to convert. mlt_audio_get_values(&in, buffer, frequency, format, samples, channels); mlt_log_error(MLT_LINK_SERVICE(self), "Invalid Parameters: %dS - %dHz %dC %s -> %dHz %dC %s\n", in.samples, in.frequency, in.channels, mlt_audio_format_name(in.format), out.frequency, out.channels, mlt_audio_format_name(out.format)); return error; } if (in.samples == 0) { // Noting to convert. return error; } if (in.frequency == requested_frequency && !pdata->s) { // No change necessary mlt_audio_get_values(&in, buffer, frequency, format, samples, channels); return error; } // Set up audio structures and allocate output buffer in.format = mlt_audio_f32le; out.format = mlt_audio_f32le; out.channels = in.channels; mlt_audio_alloc_data(&out); mlt_log_debug(MLT_LINK_SERVICE(self), "%dHz -> %dHz\n", in.frequency, out.frequency); mlt_service_lock(MLT_LINK_SERVICE(self)); // Recreate the resampler if necessary if (!pdata->s || pdata->channels != in.channels || pdata->expected_frame != mlt_frame_get_position(frame)) { mlt_log_info(MLT_LINK_SERVICE(self), "%dHz -> %dHz\n", in.frequency, out.frequency); pdata->s = src_delete(pdata->s); pdata->s = src_new(SRC_SINC_BEST_QUALITY, in.channels, &error); pdata->channels = in.channels; pdata->expected_frame = mlt_frame_get_position(frame); pdata->continuity_frame = mlt_frame_get_position(frame); pdata->continuity_sample = 0; } int received_samples = 0; while (received_samples < out.samples && !error) { mlt_frame src_frame = NULL; if (pdata->continuity_frame == mlt_frame_get_position(frame)) { src_frame = frame; } else { // The input frame is insufficient to fill the output frame. // Request audio from future frames. mlt_properties unique_properties = mlt_frame_get_unique_properties(frame, MLT_LINK_SERVICE(self)); if (!unique_properties) { error = 1; break; } char key[19]; int frame_delta = mlt_frame_get_position(frame) - mlt_frame_original_position(frame); sprintf(key, "%d", pdata->continuity_frame - frame_delta); src_frame = (mlt_frame) mlt_properties_get_data(unique_properties, key, NULL); } if (!src_frame) { mlt_log_error(MLT_LINK_SERVICE(self), "Frame not found: %d\n", pdata->continuity_frame); error = 1; break; } in.samples = mlt_audio_calculate_frame_samples(mlt_producer_get_fps(MLT_LINK_PRODUCER(self)), in.frequency, pdata->continuity_frame); error = mlt_frame_get_audio(src_frame, &in.data, &in.format, &in.frequency, &in.channels, &in.samples); if (error) { mlt_log_error(MLT_LINK_SERVICE(self), "Unable to get audio for %d\n", pdata->continuity_frame); break; } while (pdata->continuity_sample < in.samples && received_samples < out.samples) { SRC_DATA data; data.end_of_input = 0; data.src_ratio = (double) out.frequency / (double) in.frequency; data.data_out = (float *) out.data + (received_samples * out.channels); data.output_frames = out.samples - received_samples; data.data_in = (float *) in.data + (pdata->continuity_sample * in.channels); // Calculate the fewest samples that can be sent to the resampler to get the needed output. // Round down just to be sure. This is done to reduce the latency through the resampler and // to borrow as few samples from future frames as possible. data.input_frames = ((data.output_frames * in.frequency) / out.frequency) - 1; if (data.input_frames > (in.samples - pdata->continuity_sample)) { data.input_frames = in.samples - pdata->continuity_sample; } if (data.input_frames <= 0) { data.input_frames = 1; } // Resample the audio src_set_ratio(pdata->s, data.src_ratio); error = src_process(pdata->s, &data); if (error) { mlt_log_error(MLT_LINK_SERVICE(self), "%s %d,%d,%d\n", src_strerror(error), in.frequency, in.samples, out.frequency); break; } received_samples += data.output_frames_gen; pdata->continuity_sample += data.input_frames_used; } if (pdata->continuity_sample >= in.samples) { // All the samples from this frame are used. pdata->continuity_sample = 0; pdata->continuity_frame++; } } if (received_samples == 0) { mlt_log_info(MLT_LINK_SERVICE(self), "Failed to get any samples - return silence\n"); mlt_audio_silence(&out, out.samples, 0); } else if (received_samples < out.samples) { // Duplicate samples to return the exact number requested. mlt_audio_copy(&out, &out, received_samples, 0, out.samples - received_samples); } mlt_frame_set_audio(frame, out.data, out.format, 0, out.release_data); mlt_audio_get_values(&out, buffer, frequency, format, samples, channels); mlt_properties_set(frame_properties, "channel_layout", mlt_audio_channel_layout_name(out.layout)); pdata->expected_frame = mlt_frame_get_position(frame) + 1; mlt_service_unlock(MLT_LINK_SERVICE(self)); return error; } static int link_get_frame(mlt_link self, mlt_frame_ptr frame, int index) { int error = 0; mlt_position frame_pos = mlt_producer_position(MLT_LINK_PRODUCER(self)); mlt_producer_seek(self->next, frame_pos); error = mlt_service_get_frame(MLT_PRODUCER_SERVICE(self->next), frame, index); if (error) { return error; } mlt_properties unique_properties = mlt_frame_unique_properties(*frame, MLT_LINK_SERVICE(self)); // Pass future frames int i = 0; for (i = 0; i < FUTURE_FRAMES; i++) { mlt_position future_pos = frame_pos + i + 1; mlt_frame future_frame = NULL; mlt_producer_seek(self->next, future_pos); error = mlt_service_get_frame(MLT_PRODUCER_SERVICE(self->next), &future_frame, index); if (error) { mlt_log_error(MLT_LINK_SERVICE(self), "Error getting frame: %d\n", (int) future_pos); } char key[19]; sprintf(key, "%d", (int) future_pos); mlt_properties_set_data(unique_properties, key, future_frame, 0, (mlt_destructor) mlt_frame_close, NULL); } mlt_frame_push_audio(*frame, (void *) self); mlt_frame_push_audio(*frame, link_get_audio); mlt_producer_prepare_next(MLT_LINK_PRODUCER(self)); return error; } static void link_close(mlt_link self) { if (self) { private_data *pdata = (private_data *) self->child; if (pdata) { src_delete(pdata->s); } free(pdata); self->close = NULL; self->child = NULL; mlt_link_close(self); free(self); } } mlt_link link_resample_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_link self = mlt_link_init(); private_data *pdata = (private_data *) calloc(1, sizeof(private_data)); if (self && pdata) { pdata->continuity_frame = -1; pdata->expected_frame = -1; self->child = pdata; // Callback registration self->configure = link_configure; self->get_frame = link_get_frame; self->close = link_close; } else { if (pdata) { free(pdata); } if (self) { mlt_link_close(self); self = NULL; } } return self; } mlt-7.22.0/src/modules/resample/link_resample.yml000664 000000 000000 00000000570 14531534050 021777 0ustar00rootroot000000 000000 schema_version: 7.0 type: link identifier: resample title: SRC Resampler version: 1 copyright: Meltytech, LLC license: LGPLv2.1 language: en url: http://www.mega-nerd.com/SRC/ tags: - Audio - Hidden description: > Change the audio sampling rate. This link can be added to a chain to normalize audio from the producer to provide the rate requested by the consumer.mlt-7.22.0/src/modules/rtaudio/000775 000000 000000 00000000000 14531534050 016264 5ustar00rootroot000000 000000 mlt-7.22.0/src/modules/rtaudio/CMakeLists.txt000664 000000 000000 00000003276 14531534050 021034 0ustar00rootroot000000 000000 add_library(mltrtaudio MODULE consumer_rtaudio.cpp ) file(GLOB YML "*.yml") add_custom_target(Other_rtaudio_Files SOURCES ${YML} ) target_compile_options(mltrtaudio PRIVATE ${MLT_COMPILE_OPTIONS}) target_link_libraries(mltrtaudio PRIVATE mlt Threads::Threads) if(TARGET PkgConfig::rtaudio) target_link_libraries(mltrtaudio PRIVATE PkgConfig::rtaudio) else() target_sources(mltrtaudio PRIVATE RtAudio.cpp RtAudio.h) target_include_directories(mltrtaudio PRIVATE .) if(APPLE) target_link_libraries(mltrtaudio PRIVATE "-framework CoreAudio" "-framework CoreFoundation") target_compile_definitions(mltrtaudio PRIVATE __MACOSX_CORE__) elseif(WIN32) target_link_libraries(mltrtaudio PRIVATE ole32 dsound winmm ksuser mfplat mfuuid wmcodecdspuuid) target_compile_definitions(mltrtaudio PRIVATE __WINDOWS_DS__ __WINDOWS_WASAPI__) else() if(TARGET PkgConfig::alsa) target_link_libraries(mltrtaudio PRIVATE PkgConfig::alsa) target_compile_definitions(mltrtaudio PRIVATE __LINUX_ALSA__) endif() if(TARGET PkgConfig::libpulse-simple) target_link_libraries(mltrtaudio PRIVATE PkgConfig::libpulse-simple) target_compile_definitions(mltrtaudio PRIVATE __LINUX_PULSE__) endif() if(NOT (TARGET PkgConfig::alsa OR TARGET PkgConfig::libpulse-simple)) target_link_libraries(mltrtaudio PRIVATE ossaudio) target_compile_definitions(mltrtaudio PRIVATE __LINUX_OSS__) endif() endif() endif() set_target_properties(mltrtaudio PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${MLT_MODULE_OUTPUT_DIRECTORY}") install(TARGETS mltrtaudio LIBRARY DESTINATION ${MLT_INSTALL_MODULE_DIR}) install(FILES consumer_rtaudio.yml DESTINATION ${MLT_INSTALL_DATA_DIR}/rtaudio) mlt-7.22.0/src/modules/rtaudio/LICENSE000664 000000 000000 00000002520 14531534050 017270 0ustar00rootroot000000 000000 RtAudio: a set of realtime audio i/o C++ classes Copyright (c) 2001-2023 Gary P. Scavone Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. Any person wishing to distribute modifications to the Software is asked to send the modifications to the original developer so that they can be incorporated into the canonical version. This is, however, not a binding provision of this license. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. mlt-7.22.0/src/modules/rtaudio/RtAudio.cpp000664 000000 000000 00001443330 14531534050 020347 0ustar00rootroot000000 000000 /************************************************************************/ /*! \class RtAudio \brief Realtime audio i/o C++ classes. RtAudio provides a common API (Application Programming Interface) for realtime audio input/output across Linux (native ALSA, Jack, and OSS), Macintosh OS X (CoreAudio and Jack), and Windows (DirectSound, ASIO and WASAPI) operating systems. RtAudio GitHub site: https://github.com/thestk/rtaudio RtAudio WWW site: http://www.music.mcgill.ca/~gary/rtaudio/ RtAudio: realtime audio i/o C++ classes Copyright (c) 2001-2023 Gary P. Scavone Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. Any person wishing to distribute modifications to the Software is asked to send the modifications to the original developer so that they can be incorporated into the canonical version. This is, however, not a binding provision of this license. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /************************************************************************/ // RtAudio: Version 6.0.1 #include "RtAudio.h" #include #include #include #include #include #include #include #include #if defined(_WIN32) #include #endif // Static variable definitions. const unsigned int RtApi::MAX_SAMPLE_RATES = 14; const unsigned int RtApi::SAMPLE_RATES[] = { 4000, 5512, 8000, 9600, 11025, 16000, 22050, 32000, 44100, 48000, 88200, 96000, 176400, 192000 }; template inline std::string convertCharPointerToStdString(const T *text); template<> inline std::string convertCharPointerToStdString(const char *text) { return text; } template<> inline std::string convertCharPointerToStdString(const wchar_t *text) { return std::wstring_convert>{}.to_bytes(text); } #if defined(_MSC_VER) #define MUTEX_INITIALIZE(A) InitializeCriticalSection(A) #define MUTEX_DESTROY(A) DeleteCriticalSection(A) #define MUTEX_LOCK(A) EnterCriticalSection(A) #define MUTEX_UNLOCK(A) LeaveCriticalSection(A) #else #define MUTEX_INITIALIZE(A) pthread_mutex_init(A, NULL) #define MUTEX_DESTROY(A) pthread_mutex_destroy(A) #define MUTEX_LOCK(A) pthread_mutex_lock(A) #define MUTEX_UNLOCK(A) pthread_mutex_unlock(A) #endif // *************************************************** // // // RtApi subclass prototypes. // // *************************************************** // #if defined(__MACOSX_CORE__) #include class RtApiCore: public RtApi { public: RtApiCore(); ~RtApiCore(); RtAudio::Api getCurrentApi( void ) override { return RtAudio::MACOSX_CORE; } unsigned int getDefaultOutputDevice( void ) override; unsigned int getDefaultInputDevice( void ) override; void closeStream( void ) override; RtAudioErrorType startStream( void ) override; RtAudioErrorType stopStream( void ) override; RtAudioErrorType abortStream( void ) override; // This function is intended for internal use only. It must be // public because it is called by an internal callback handler, // which is not a member of RtAudio. External use of this function // will most likely produce highly undesirable results! bool callbackEvent( AudioDeviceID deviceId, const AudioBufferList *inBufferList, const AudioBufferList *outBufferList ); private: void probeDevices( void ) override; bool probeDeviceInfo( AudioDeviceID id, RtAudio::DeviceInfo &info ); bool probeDeviceOpen( unsigned int deviceId, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, RtAudio::StreamOptions *options ) override; static const char* getErrorCode( OSStatus code ); std::vector< AudioDeviceID > deviceIds_; }; #endif #if defined(__UNIX_JACK__) #include class RtApiJack: public RtApi { public: RtApiJack(); ~RtApiJack(); RtAudio::Api getCurrentApi( void ) override { return RtAudio::UNIX_JACK; } void closeStream( void ) override; RtAudioErrorType startStream( void ) override; RtAudioErrorType stopStream( void ) override; RtAudioErrorType abortStream( void ) override; // This function is intended for internal use only. It must be // public because it is called by the internal callback handler, // which is not a member of RtAudio. External use of this function // will most likely produce highly undesirable results! bool callbackEvent( unsigned long nframes ); private: void probeDevices( void ) override; bool probeDeviceInfo( RtAudio::DeviceInfo &info, jack_client_t *client ); bool probeDeviceOpen( unsigned int deviceId, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, RtAudio::StreamOptions *options ) override; bool shouldAutoconnect_; }; #endif #if defined(__WINDOWS_ASIO__) class RtApiAsio: public RtApi { public: RtApiAsio(); ~RtApiAsio(); RtAudio::Api getCurrentApi( void ) override { return RtAudio::WINDOWS_ASIO; } void closeStream( void ) override; RtAudioErrorType startStream( void ) override; RtAudioErrorType stopStream( void ) override; RtAudioErrorType abortStream( void ) override; // This function is intended for internal use only. It must be // public because it is called by the internal callback handler, // which is not a member of RtAudio. External use of this function // will most likely produce highly undesirable results! bool callbackEvent( long bufferIndex ); private: bool coInitialized_; void probeDevices( void ) override; bool probeDeviceInfo( RtAudio::DeviceInfo &info ); bool probeDeviceOpen( unsigned int device, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, RtAudio::StreamOptions *options ) override; }; #endif #if defined(__WINDOWS_DS__) class RtApiDs: public RtApi { public: RtApiDs(); ~RtApiDs(); RtAudio::Api getCurrentApi( void ) override { return RtAudio::WINDOWS_DS; } void closeStream( void ) override; RtAudioErrorType startStream( void ) override; RtAudioErrorType stopStream( void ) override; RtAudioErrorType abortStream( void ) override; // This function is intended for internal use only. It must be // public because it is called by the internal callback handler, // which is not a member of RtAudio. External use of this function // will most likely produce highly undesirable results! void callbackEvent( void ); private: bool coInitialized_; bool buffersRolling; long duplexPrerollBytes; std::vector dsDevices_; void probeDevices( void ) override; bool probeDeviceInfo( RtAudio::DeviceInfo &info, DsDevice &dsDevice ); bool probeDeviceOpen( unsigned int deviceId, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, RtAudio::StreamOptions *options ) override; }; #endif #if defined(__WINDOWS_WASAPI__) struct IMMDeviceEnumerator; class RtApiWasapi : public RtApi { public: RtApiWasapi(); virtual ~RtApiWasapi(); RtAudio::Api getCurrentApi( void ) override { return RtAudio::WINDOWS_WASAPI; } unsigned int getDefaultOutputDevice( void ) override; unsigned int getDefaultInputDevice( void ) override; void closeStream( void ) override; RtAudioErrorType startStream( void ) override; RtAudioErrorType stopStream( void ) override; RtAudioErrorType abortStream( void ) override; private: bool coInitialized_; IMMDeviceEnumerator* deviceEnumerator_; std::vector< std::pair< std::string, bool> > deviceIds_; void probeDevices( void ) override; bool probeDeviceInfo( RtAudio::DeviceInfo &info, LPWSTR deviceId, bool isCaptureDevice ); bool probeDeviceOpen( unsigned int deviceId, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int* bufferSize, RtAudio::StreamOptions* options ) override; static DWORD WINAPI runWasapiThread( void* wasapiPtr ); static DWORD WINAPI stopWasapiThread( void* wasapiPtr ); static DWORD WINAPI abortWasapiThread( void* wasapiPtr ); void wasapiThread(); }; #endif #if defined(__LINUX_ALSA__) class RtApiAlsa: public RtApi { public: RtApiAlsa(); ~RtApiAlsa(); RtAudio::Api getCurrentApi() override { return RtAudio::LINUX_ALSA; } void closeStream( void ) override; RtAudioErrorType startStream( void ) override; RtAudioErrorType stopStream( void ) override; RtAudioErrorType abortStream( void ) override; // This function is intended for internal use only. It must be // public because it is called by the internal callback handler, // which is not a member of RtAudio. External use of this function // will most likely produce highly undesirable results! void callbackEvent( void ); private: std::vector> deviceIdPairs_; void probeDevices( void ) override; bool probeDeviceInfo( RtAudio::DeviceInfo &info, std::string name ); bool probeDeviceOpen( unsigned int deviceId, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, RtAudio::StreamOptions *options ) override; }; #endif #if defined(__LINUX_PULSE__) #include class RtApiPulse: public RtApi { public: ~RtApiPulse(); RtAudio::Api getCurrentApi() override { return RtAudio::LINUX_PULSE; } void closeStream( void ) override; RtAudioErrorType startStream( void ) override; RtAudioErrorType stopStream( void ) override; RtAudioErrorType abortStream( void ) override; // This function is intended for internal use only. It must be // public because it is called by the internal callback handler, // which is not a member of RtAudio. External use of this function // will most likely produce highly undesirable results! void callbackEvent( void ); struct PaDeviceInfo { std::string sinkName; std::string sourceName; }; private: std::vector< PaDeviceInfo > paDeviceList_; void probeDevices( void ) override; bool probeDeviceOpen( unsigned int deviceId, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, RtAudio::StreamOptions *options ) override; }; #endif #if defined(__LINUX_OSS__) #include class RtApiOss: public RtApi { public: RtApiOss(); ~RtApiOss(); RtAudio::Api getCurrentApi() override { return RtAudio::LINUX_OSS; } void closeStream( void ) override; RtAudioErrorType startStream( void ) override; RtAudioErrorType stopStream( void ) override; RtAudioErrorType abortStream( void ) override; // This function is intended for internal use only. It must be // public because it is called by the internal callback handler, // which is not a member of RtAudio. External use of this function // will most likely produce highly undesirable results! void callbackEvent( void ); private: void probeDevices( void ) override; bool probeDeviceInfo( RtAudio::DeviceInfo &info, oss_audioinfo &ainfo ); bool probeDeviceOpen( unsigned int deviceId, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, RtAudio::StreamOptions *options ) override; }; #endif #if defined(__RTAUDIO_DUMMY__) class RtApiDummy: public RtApi { public: RtApiDummy() { errorText_ = "RtApiDummy: This class provides no functionality."; error( RTAUDIO_WARNING ); } RtAudio::Api getCurrentApi( void ) override { return RtAudio::RTAUDIO_DUMMY; } void closeStream( void ) override {} RtAudioErrorType startStream( void ) override { return RTAUDIO_NO_ERROR; } RtAudioErrorType stopStream( void ) override { return RTAUDIO_NO_ERROR; } RtAudioErrorType abortStream( void ) override { return RTAUDIO_NO_ERROR; } private: bool probeDeviceOpen( unsigned int /*deviceId*/, StreamMode /*mode*/, unsigned int /*channels*/, unsigned int /*firstChannel*/, unsigned int /*sampleRate*/, RtAudioFormat /*format*/, unsigned int * /*bufferSize*/, RtAudio::StreamOptions * /*options*/ ) override { return false; } }; #endif // *************************************************** // // // RtAudio definitions. // // *************************************************** // std::string RtAudio :: getVersion( void ) { return RTAUDIO_VERSION; } // Define API names and display names. // Must be in same order as API enum. extern "C" { const char* rtaudio_api_names[][2] = { { "unspecified" , "Unknown" }, { "core" , "CoreAudio" }, { "alsa" , "ALSA" }, { "jack" , "Jack" }, { "pulse" , "Pulse" }, { "oss" , "OpenSoundSystem" }, { "asio" , "ASIO" }, { "wasapi" , "WASAPI" }, { "ds" , "DirectSound" }, { "dummy" , "Dummy" }, }; const unsigned int rtaudio_num_api_names = sizeof(rtaudio_api_names)/sizeof(rtaudio_api_names[0]); // The order here will control the order of RtAudio's API search in // the constructor. extern "C" const RtAudio::Api rtaudio_compiled_apis[] = { #if defined(__MACOSX_CORE__) RtAudio::MACOSX_CORE, #endif #if defined(__LINUX_ALSA__) RtAudio::LINUX_ALSA, #endif #if defined(__UNIX_JACK__) RtAudio::UNIX_JACK, #endif #if defined(__LINUX_PULSE__) RtAudio::LINUX_PULSE, #endif #if defined(__LINUX_OSS__) RtAudio::LINUX_OSS, #endif #if defined(__WINDOWS_ASIO__) RtAudio::WINDOWS_ASIO, #endif #if defined(__WINDOWS_WASAPI__) RtAudio::WINDOWS_WASAPI, #endif #if defined(__WINDOWS_DS__) RtAudio::WINDOWS_DS, #endif #if defined(__RTAUDIO_DUMMY__) RtAudio::RTAUDIO_DUMMY, #endif RtAudio::UNSPECIFIED, }; extern "C" const unsigned int rtaudio_num_compiled_apis = sizeof(rtaudio_compiled_apis)/sizeof(rtaudio_compiled_apis[0])-1; } // This is a compile-time check that rtaudio_num_api_names == RtAudio::NUM_APIS. // If the build breaks here, check that they match. template class StaticAssert { private: StaticAssert() {} }; template<> class StaticAssert{ public: StaticAssert() {} }; class StaticAssertions { StaticAssertions() { StaticAssert(); }}; void RtAudio :: getCompiledApi( std::vector &apis ) { apis = std::vector(rtaudio_compiled_apis, rtaudio_compiled_apis + rtaudio_num_compiled_apis); } std::string RtAudio :: getApiName( RtAudio::Api api ) { if (api < 0 || api >= RtAudio::NUM_APIS) return ""; return rtaudio_api_names[api][0]; } std::string RtAudio :: getApiDisplayName( RtAudio::Api api ) { if (api < 0 || api >= RtAudio::NUM_APIS) return "Unknown"; return rtaudio_api_names[api][1]; } RtAudio::Api RtAudio :: getCompiledApiByName( const std::string &name ) { unsigned int i=0; for (i = 0; i < rtaudio_num_compiled_apis; ++i) if (name == rtaudio_api_names[rtaudio_compiled_apis[i]][0]) return rtaudio_compiled_apis[i]; return RtAudio::UNSPECIFIED; } RtAudio::Api RtAudio :: getCompiledApiByDisplayName( const std::string &name ) { unsigned int i=0; for (i = 0; i < rtaudio_num_compiled_apis; ++i) if (name == rtaudio_api_names[rtaudio_compiled_apis[i]][1]) return rtaudio_compiled_apis[i]; return RtAudio::UNSPECIFIED; } void RtAudio :: openRtApi( RtAudio::Api api ) { if ( rtapi_ ) delete rtapi_; rtapi_ = 0; #if defined(__UNIX_JACK__) if ( api == UNIX_JACK ) rtapi_ = new RtApiJack(); #endif #if defined(__LINUX_ALSA__) if ( api == LINUX_ALSA ) rtapi_ = new RtApiAlsa(); #endif #if defined(__LINUX_PULSE__) if ( api == LINUX_PULSE ) rtapi_ = new RtApiPulse(); #endif #if defined(__LINUX_OSS__) if ( api == LINUX_OSS ) rtapi_ = new RtApiOss(); #endif #if defined(__WINDOWS_ASIO__) if ( api == WINDOWS_ASIO ) rtapi_ = new RtApiAsio(); #endif #if defined(__WINDOWS_WASAPI__) if ( api == WINDOWS_WASAPI ) rtapi_ = new RtApiWasapi(); #endif #if defined(__WINDOWS_DS__) if ( api == WINDOWS_DS ) rtapi_ = new RtApiDs(); #endif #if defined(__MACOSX_CORE__) if ( api == MACOSX_CORE ) rtapi_ = new RtApiCore(); #endif #if defined(__RTAUDIO_DUMMY__) if ( api == RTAUDIO_DUMMY ) rtapi_ = new RtApiDummy(); #endif } RtAudio :: RtAudio( RtAudio::Api api, RtAudioErrorCallback&& errorCallback ) { rtapi_ = 0; std::string errorMessage; if ( api != UNSPECIFIED ) { // Attempt to open the specified API. openRtApi( api ); if ( rtapi_ ) { if ( errorCallback ) rtapi_->setErrorCallback( errorCallback ); return; } // No compiled support for specified API value. Issue a warning // and continue as if no API was specified. errorMessage = "RtAudio: no compiled support for specified API argument!"; if ( errorCallback ) errorCallback( RTAUDIO_INVALID_USE, errorMessage ); else std::cerr << '\n' << errorMessage << '\n' << std::endl; } // Iterate through the compiled APIs and return as soon as we find // one with at least one device or we reach the end of the list. std::vector< RtAudio::Api > apis; getCompiledApi( apis ); for ( unsigned int i=0; igetDeviceNames()).size() > 0 ) break; } if ( rtapi_ ) { if ( errorCallback ) rtapi_->setErrorCallback( errorCallback ); return; } // It should not be possible to get here because the preprocessor // definition __RTAUDIO_DUMMY__ is automatically defined in RtAudio.h // if no API-specific definitions are passed to the compiler. But just // in case something weird happens, issue an error message and abort. errorMessage = "RtAudio: no compiled API support found ... critical error!"; if ( errorCallback ) errorCallback( RTAUDIO_INVALID_USE, errorMessage ); else std::cerr << '\n' << errorMessage << '\n' << std::endl; abort(); } RtAudio :: ~RtAudio() { if ( rtapi_ ) delete rtapi_; } RtAudioErrorType RtAudio :: openStream( RtAudio::StreamParameters *outputParameters, RtAudio::StreamParameters *inputParameters, RtAudioFormat format, unsigned int sampleRate, unsigned int *bufferFrames, RtAudioCallback callback, void *userData, RtAudio::StreamOptions *options ) { return rtapi_->openStream( outputParameters, inputParameters, format, sampleRate, bufferFrames, callback, userData, options ); } // *************************************************** // // // Public RtApi definitions (see end of file for // private or protected utility functions). // // *************************************************** // RtApi :: RtApi() { clearStreamInfo(); MUTEX_INITIALIZE( &stream_.mutex ); errorCallback_ = 0; showWarnings_ = true; currentDeviceId_ = 129; } RtApi :: ~RtApi() { MUTEX_DESTROY( &stream_.mutex ); } RtAudioErrorType RtApi :: openStream( RtAudio::StreamParameters *oParams, RtAudio::StreamParameters *iParams, RtAudioFormat format, unsigned int sampleRate, unsigned int *bufferFrames, RtAudioCallback callback, void *userData, RtAudio::StreamOptions *options ) { if ( stream_.state != STREAM_CLOSED ) { errorText_ = "RtApi::openStream: a stream is already open!"; return error( RTAUDIO_INVALID_USE ); } // Clear stream information potentially left from a previously open stream. clearStreamInfo(); if ( oParams && oParams->nChannels < 1 ) { errorText_ = "RtApi::openStream: a non-NULL output StreamParameters structure cannot have an nChannels value less than one."; return error( RTAUDIO_INVALID_PARAMETER ); } if ( iParams && iParams->nChannels < 1 ) { errorText_ = "RtApi::openStream: a non-NULL input StreamParameters structure cannot have an nChannels value less than one."; return error( RTAUDIO_INVALID_PARAMETER ); } if ( oParams == NULL && iParams == NULL ) { errorText_ = "RtApi::openStream: input and output StreamParameters structures are both NULL!"; return error( RTAUDIO_INVALID_PARAMETER ); } if ( formatBytes(format) == 0 ) { errorText_ = "RtApi::openStream: 'format' parameter value is undefined."; return error( RTAUDIO_INVALID_PARAMETER ); } // Scan devices if none currently listed. if ( deviceList_.size() == 0 ) probeDevices(); unsigned int m, oChannels = 0; if ( oParams ) { oChannels = oParams->nChannels; // Verify that the oParams->deviceId is found in our list for ( m=0; mdeviceId ) break; } if ( m == deviceList_.size() ) { errorText_ = "RtApi::openStream: output device ID is invalid."; return error( RTAUDIO_INVALID_PARAMETER ); } } unsigned int iChannels = 0; if ( iParams ) { iChannels = iParams->nChannels; for ( m=0; mdeviceId ) break; } if ( m == deviceList_.size() ) { errorText_ = "RtApi::openStream: input device ID is invalid."; return error( RTAUDIO_INVALID_PARAMETER ); } } bool result; if ( oChannels > 0 ) { result = probeDeviceOpen( oParams->deviceId, OUTPUT, oChannels, oParams->firstChannel, sampleRate, format, bufferFrames, options ); if ( result == false ) return error( RTAUDIO_SYSTEM_ERROR ); } if ( iChannels > 0 ) { result = probeDeviceOpen( iParams->deviceId, INPUT, iChannels, iParams->firstChannel, sampleRate, format, bufferFrames, options ); if ( result == false ) return error( RTAUDIO_SYSTEM_ERROR ); } stream_.callbackInfo.callback = (void *) callback; stream_.callbackInfo.userData = userData; if ( options ) options->numberOfBuffers = stream_.nBuffers; stream_.state = STREAM_STOPPED; return RTAUDIO_NO_ERROR; } void RtApi :: probeDevices( void ) { // This function MUST be implemented in all subclasses! Within each // API, this function will be used to: // - enumerate the devices and fill or update our // std::vector< RtAudio::DeviceInfo> deviceList_ class variable // - store corresponding (usually API-specific) identifiers that // are needed to open each device // - make sure that the default devices are properly identified // within the deviceList_ (unless API-specific functions are // available for this purpose). // // The function should not reprobe devices that have already been // found. The function must properly handle devices that are removed // or added. // // Ideally, we would also configure callback functions to be invoked // when devices are added or removed (which could be used to inform // clients about changes). However, none of the APIs currently // support notification of _new_ devices and I don't see the // usefulness of having this work only for device removal. return; } unsigned int RtApi :: getDeviceCount( void ) { probeDevices(); return (unsigned int)deviceList_.size(); } std::vector RtApi :: getDeviceIds( void ) { probeDevices(); // Copy device IDs into output vector. std::vector deviceIds; for ( unsigned int m=0; m RtApi :: getDeviceNames( void ) { probeDevices(); // Copy device names into output vector. std::vector deviceNames; for ( unsigned int m=0; m 0 ) { deviceList_[i].isDefaultInput = true; return deviceList_[i].ID; } } return 0; } unsigned int RtApi :: getDefaultOutputDevice( void ) { // Should be reimplemented in subclasses if necessary. if ( deviceList_.size() == 0 ) probeDevices(); for ( unsigned int i = 0; i < deviceList_.size(); i++ ) { if ( deviceList_[i].isDefaultOutput ) return deviceList_[i].ID; } // If not found, find the first device with output channels, set it // as the default, and return the ID. for ( unsigned int i = 0; i < deviceList_.size(); i++ ) { if ( deviceList_[i].outputChannels > 0 ) { deviceList_[i].isDefaultOutput = true; return deviceList_[i].ID; } } return 0; } RtAudio::DeviceInfo RtApi :: getDeviceInfo( unsigned int deviceId ) { if ( deviceList_.size() == 0 ) probeDevices(); for ( unsigned int m=0; m= 0.0 ) stream_.streamTime = time; /* #if defined( HAVE_GETTIMEOFDAY ) gettimeofday( &stream_.lastTickTimestamp, NULL ); #endif */ } unsigned int RtApi :: getStreamSampleRate( void ) { if ( isStreamOpen() ) return stream_.sampleRate; else return 0; } // *************************************************** // // // OS/API-specific methods. // // *************************************************** // #if defined(__MACOSX_CORE__) #include // The OS X CoreAudio API is designed to use a separate callback // procedure for each of its audio devices. A single RtAudio duplex // stream using two different devices is supported here, though it // cannot be guaranteed to always behave correctly because we cannot // synchronize these two callbacks. // // A property listener is installed for over/underrun information. // However, no functionality is currently provided to allow property // listeners to trigger user handlers because it is unclear what could // be done if a critical stream parameter (buffer size, sample rate, // device disconnect) notification arrived. The listeners entail // quite a bit of extra code and most likely, a user program wouldn't // be prepared for the result anyway. However, we do provide a flag // to the client callback function to inform of an over/underrun. // A structure to hold various information related to the CoreAudio API // implementation. struct CoreHandle { AudioDeviceID id[2]; // device ids #if defined( MAC_OS_X_VERSION_10_5 ) && ( MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 ) AudioDeviceIOProcID procId[2]; #endif UInt32 iStream[2]; // device stream index (or first if using multiple) UInt32 nStreams[2]; // number of streams to use bool xrun[2]; char *deviceBuffer; pthread_cond_t condition; int drainCounter; // Tracks callback counts when draining bool internalDrain; // Indicates if stop is initiated from callback or not. bool xrunListenerAdded[2]; bool disconnectListenerAdded[2]; CoreHandle() :deviceBuffer(0), drainCounter(0), internalDrain(false) { nStreams[0] = 1; nStreams[1] = 1; id[0] = 0; id[1] = 0; procId[0] = 0; procId[1] = 0; xrun[0] = false; xrun[1] = false; xrunListenerAdded[0] = false; xrunListenerAdded[1] = false; disconnectListenerAdded[0] = false; disconnectListenerAdded[1] = false; } }; #if defined( MAC_OS_VERSION_12_0 ) && ( MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_VERSION_12_0 ) #define KAUDIOOBJECTPROPERTYELEMENT kAudioObjectPropertyElementMain #else #define KAUDIOOBJECTPROPERTYELEMENT kAudioObjectPropertyElementMaster // deprecated with macOS 12 #endif RtApiCore:: RtApiCore() { #if defined( AVAILABLE_MAC_OS_X_VERSION_10_6_AND_LATER ) // This is a largely undocumented but absolutely necessary // requirement starting with OS-X 10.6. If not called, queries and // updates to various audio device properties are not handled // correctly. CFRunLoopRef theRunLoop = NULL; AudioObjectPropertyAddress property = { kAudioHardwarePropertyRunLoop, kAudioObjectPropertyScopeGlobal, KAUDIOOBJECTPROPERTYELEMENT }; OSStatus result = AudioObjectSetPropertyData( kAudioObjectSystemObject, &property, 0, NULL, sizeof(CFRunLoopRef), &theRunLoop); if ( result != noErr ) { errorText_ = "RtApiCore::RtApiCore: error setting run loop property!"; error( RTAUDIO_SYSTEM_ERROR ); } #endif } RtApiCore :: ~RtApiCore() { // The subclass destructor gets called before the base class // destructor, so close an existing stream before deallocating // apiDeviceId memory. if ( stream_.state != STREAM_CLOSED ) closeStream(); } unsigned int RtApiCore :: getDefaultOutputDevice( void ) { AudioDeviceID id; UInt32 dataSize = sizeof( AudioDeviceID ); AudioObjectPropertyAddress property = { kAudioHardwarePropertyDefaultOutputDevice, kAudioObjectPropertyScopeGlobal, KAUDIOOBJECTPROPERTYELEMENT }; OSStatus result = AudioObjectGetPropertyData( kAudioObjectSystemObject, &property, 0, NULL, &dataSize, &id ); if ( result != noErr ) { errorText_ = "RtApiCore::getDefaultOutputDevice: OS-X system error getting device."; error( RTAUDIO_SYSTEM_ERROR ); return 0; } for ( unsigned int m=0; mobject; info->deviceDisconnected = true; object->closeStream(); return kAudioHardwareUnspecifiedError; } } return kAudioHardwareNoError; } void RtApiCore :: probeDevices( void ) { // See list of required functionality in RtApi::probeDevices(). // Find out how many audio devices there are. UInt32 dataSize; AudioObjectPropertyAddress property = { kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal, KAUDIOOBJECTPROPERTYELEMENT }; OSStatus result = AudioObjectGetPropertyDataSize( kAudioObjectSystemObject, &property, 0, NULL, &dataSize ); if ( result != noErr ) { errorText_ = "RtApiCore::probeDevices: OS-X system error getting device info!"; error( RTAUDIO_SYSTEM_ERROR ); return; } unsigned int nDevices = dataSize / sizeof( AudioDeviceID ); if ( nDevices == 0 ) { deviceList_.clear(); deviceIds_.clear(); return; } AudioDeviceID ids[ nDevices ]; property.mSelector = kAudioHardwarePropertyDevices; result = AudioObjectGetPropertyData( kAudioObjectSystemObject, &property, 0, NULL, &dataSize, (void *) &ids ); if ( result != noErr ) { errorText_ = "RtApiCore::probeDevices: OS-X system error getting device IDs."; error( RTAUDIO_SYSTEM_ERROR ); return; } // Fill or update the deviceList_ and also save a corresponding list of Ids. for ( unsigned int n=0; n::iterator it=deviceIds_.begin(); it!=deviceIds_.end(); ) { for ( m=0; mmNumberBuffers; for ( i=0; imBuffers[i].mNumberChannels; free( bufferList ); // Get the input stream "configuration". property.mScope = kAudioDevicePropertyScopeInput; result = AudioObjectGetPropertyDataSize( id, &property, 0, NULL, &dataSize ); if ( result != noErr || dataSize == 0 ) { errorStream_ << "RtApiCore::probeDeviceInfo: system error (" << getErrorCode( result ) << ") getting input stream configuration info for device (" << info.name << ")."; errorText_ = errorStream_.str(); error( RTAUDIO_WARNING ); return false; } // Allocate the AudioBufferList. bufferList = (AudioBufferList *) malloc( dataSize ); if ( bufferList == NULL ) { errorText_ = "RtApiCore::probeDeviceInfo: memory error allocating input AudioBufferList."; error( RTAUDIO_WARNING ); return false; } result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, bufferList ); if (result != noErr || dataSize == 0) { free( bufferList ); errorStream_ << "RtApiCore::probeDeviceInfo: system error (" << getErrorCode( result ) << ") getting input stream configuration for device (" << info.name << ")."; errorText_ = errorStream_.str(); error( RTAUDIO_WARNING ); return false; } // Get input channel information. nStreams = bufferList->mNumberBuffers; for ( i=0; imBuffers[i].mNumberChannels; free( bufferList ); // If device opens for both playback and capture, we determine the channels. if ( info.outputChannels > 0 && info.inputChannels > 0 ) info.duplexChannels = (info.outputChannels > info.inputChannels) ? info.inputChannels : info.outputChannels; // Probe the device sample rates. bool isInput = false; if ( info.outputChannels == 0 ) isInput = true; // Determine the supported sample rates. property.mSelector = kAudioDevicePropertyAvailableNominalSampleRates; if ( isInput == false ) property.mScope = kAudioDevicePropertyScopeOutput; result = AudioObjectGetPropertyDataSize( id, &property, 0, NULL, &dataSize ); if ( result != kAudioHardwareNoError || dataSize == 0 ) { errorStream_ << "RtApiCore::probeDeviceInfo: system error (" << getErrorCode( result ) << ") getting sample rate info."; errorText_ = errorStream_.str(); error( RTAUDIO_WARNING ); return false; } UInt32 nRanges = dataSize / sizeof( AudioValueRange ); AudioValueRange rangeList[ nRanges ]; result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, &rangeList ); if ( result != kAudioHardwareNoError ) { errorStream_ << "RtApiCore::probeDeviceInfo: system error (" << getErrorCode( result ) << ") getting sample rates."; errorText_ = errorStream_.str(); error( RTAUDIO_WARNING ); return false; } // The sample rate reporting mechanism is a bit of a mystery. It // seems that it can either return individual rates or a range of // rates. I assume that if the min / max range values are the same, // then that represents a single supported rate and if the min / max // range values are different, the device supports an arbitrary // range of values (though there might be multiple ranges, so we'll // use the most conservative range). Float64 minimumRate = 1.0, maximumRate = 10000000000.0; bool haveValueRange = false; info.sampleRates.clear(); for ( UInt32 i=0; i info.preferredSampleRate ) ) info.preferredSampleRate = tmpSr; } else { haveValueRange = true; if ( rangeList[i].mMinimum > minimumRate ) minimumRate = rangeList[i].mMinimum; if ( rangeList[i].mMaximum < maximumRate ) maximumRate = rangeList[i].mMaximum; } } if ( haveValueRange ) { for ( unsigned int k=0; k= (unsigned int) minimumRate && SAMPLE_RATES[k] <= (unsigned int) maximumRate ) { info.sampleRates.push_back( SAMPLE_RATES[k] ); if ( !info.preferredSampleRate || ( SAMPLE_RATES[k] <= 48000 && SAMPLE_RATES[k] > info.preferredSampleRate ) ) info.preferredSampleRate = SAMPLE_RATES[k]; } } } // Sort and remove any redundant values std::sort( info.sampleRates.begin(), info.sampleRates.end() ); info.sampleRates.erase( unique( info.sampleRates.begin(), info.sampleRates.end() ), info.sampleRates.end() ); if ( info.sampleRates.size() == 0 ) { errorStream_ << "RtApiCore::probeDeviceInfo: No supported sample rates found for device (" << info.name << ")."; errorText_ = errorStream_.str(); error( RTAUDIO_WARNING ); return false; } // Probe the currently configured sample rate Float64 nominalRate; dataSize = sizeof( Float64 ); property.mSelector = kAudioDevicePropertyNominalSampleRate; result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, &nominalRate ); if ( result == noErr ) info.currentSampleRate = (unsigned int) nominalRate; // CoreAudio always uses 32-bit floating point data for PCM streams. // Thus, any other "physical" formats supported by the device are of // no interest to the client. info.nativeFormats = RTAUDIO_FLOAT32; return true; } static OSStatus callbackHandler( AudioDeviceID inDevice, const AudioTimeStamp* /*inNow*/, const AudioBufferList* inInputData, const AudioTimeStamp* /*inInputTime*/, AudioBufferList* outOutputData, const AudioTimeStamp* /*inOutputTime*/, void* infoPointer ) { CallbackInfo *info = (CallbackInfo *) infoPointer; if(info == NULL || info->object == NULL) return kAudioHardwareUnspecifiedError; RtApiCore *object = (RtApiCore *) info->object; if ( object->callbackEvent( inDevice, inInputData, outOutputData ) == false ) return kAudioHardwareUnspecifiedError; else return kAudioHardwareNoError; } static OSStatus xrunListener( AudioObjectID /*inDevice*/, UInt32 nAddresses, const AudioObjectPropertyAddress properties[], void* handlePointer ) { CoreHandle *handle = (CoreHandle *) handlePointer; for ( UInt32 i=0; ixrun[1] = true; else handle->xrun[0] = true; } } return kAudioHardwareNoError; } bool RtApiCore :: probeDeviceOpen( unsigned int deviceId, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, RtAudio::StreamOptions *options ) { AudioDeviceID id = 0; for ( unsigned int m=0; mmNumberBuffers; bool monoMode = false; bool foundStream = false; // First check that the device supports the requested number of // channels. UInt32 deviceChannels = 0; for ( iStream=0; iStreammBuffers[iStream].mNumberChannels; if ( deviceChannels < ( channels + firstChannel ) ) { free( bufferList ); errorStream_ << "RtApiCore::probeDeviceOpen: the device (" << deviceId << ") does not support the requested channel count."; errorText_ = errorStream_.str(); return FAILURE; } // Look for a single stream meeting our needs. UInt32 firstStream = 0, streamCount = 1, streamChannels = 0, channelOffset = 0; for ( iStream=0; iStreammBuffers[iStream].mNumberChannels; if ( streamChannels >= channels + offsetCounter ) { firstStream = iStream; channelOffset = offsetCounter; foundStream = true; break; } if ( streamChannels > offsetCounter ) break; offsetCounter -= streamChannels; } // If we didn't find a single stream above, then we should be able // to meet the channel specification with multiple streams. if ( foundStream == false ) { monoMode = true; offsetCounter = firstChannel; for ( iStream=0; iStreammBuffers[iStream].mNumberChannels; if ( streamChannels > offsetCounter ) break; offsetCounter -= streamChannels; } firstStream = iStream; channelOffset = offsetCounter; Int32 channelCounter = channels + offsetCounter - streamChannels; if ( streamChannels > 1 ) monoMode = false; while ( channelCounter > 0 ) { streamChannels = bufferList->mBuffers[++iStream].mNumberChannels; if ( streamChannels > 1 ) monoMode = false; channelCounter -= streamChannels; streamCount++; } } free( bufferList ); // Determine the buffer size. AudioValueRange bufferRange; dataSize = sizeof( AudioValueRange ); property.mSelector = kAudioDevicePropertyBufferFrameSizeRange; result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, &bufferRange ); if ( result != noErr ) { errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") getting buffer size range for device (" << deviceId << ")."; errorText_ = errorStream_.str(); return FAILURE; } if ( bufferRange.mMinimum > *bufferSize ) *bufferSize = (unsigned int) bufferRange.mMinimum; else if ( bufferRange.mMaximum < *bufferSize ) *bufferSize = (unsigned int) bufferRange.mMaximum; if ( options && options->flags & RTAUDIO_MINIMIZE_LATENCY ) *bufferSize = (unsigned int) bufferRange.mMinimum; // Set the buffer size. For multiple streams, I'm assuming we only // need to make this setting for the master channel. UInt32 theSize = (UInt32) *bufferSize; dataSize = sizeof( UInt32 ); property.mSelector = kAudioDevicePropertyBufferFrameSize; result = AudioObjectSetPropertyData( id, &property, 0, NULL, dataSize, &theSize ); if ( result != noErr ) { errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") setting the buffer size for device (" << deviceId << ")."; errorText_ = errorStream_.str(); return FAILURE; } // If attempting to setup a duplex stream, the bufferSize parameter // MUST be the same in both directions! *bufferSize = theSize; if ( stream_.mode == OUTPUT && mode == INPUT && *bufferSize != stream_.bufferSize ) { errorStream_ << "RtApiCore::probeDeviceOpen: system error setting buffer size for duplex stream on device (" << deviceId << ")."; errorText_ = errorStream_.str(); return FAILURE; } stream_.bufferSize = *bufferSize; stream_.nBuffers = 1; // Try to set "hog" mode ... it's not clear to me this is working. if ( options && options->flags & RTAUDIO_HOG_DEVICE ) { pid_t hog_pid; dataSize = sizeof( hog_pid ); property.mSelector = kAudioDevicePropertyHogMode; result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, &hog_pid ); if ( result != noErr ) { errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") getting 'hog' state!"; errorText_ = errorStream_.str(); return FAILURE; } if ( hog_pid != getpid() ) { hog_pid = getpid(); result = AudioObjectSetPropertyData( id, &property, 0, NULL, dataSize, &hog_pid ); if ( result != noErr ) { errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") setting 'hog' state!"; errorText_ = errorStream_.str(); return FAILURE; } } } // Check and if necessary, change the sample rate for the device. Float64 nominalRate; dataSize = sizeof( Float64 ); property.mSelector = kAudioDevicePropertyNominalSampleRate; result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, &nominalRate ); if ( result != noErr ) { errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") getting current sample rate."; errorText_ = errorStream_.str(); return FAILURE; } // Only try to change the sample rate if off by more than 1 Hz. if ( fabs( nominalRate - (double)sampleRate ) > 1.0 ) { nominalRate = (Float64) sampleRate; result = AudioObjectSetPropertyData( id, &property, 0, NULL, dataSize, &nominalRate ); if ( result != noErr ) { errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") setting sample rate for device (" << deviceId << ")."; errorText_ = errorStream_.str(); return FAILURE; } // Now wait until the reported nominal rate is what we just set. UInt32 microCounter = 0; Float64 reportedRate = 0.0; while ( reportedRate != nominalRate ) { microCounter += 5000; if ( microCounter > 2000000 ) break; usleep( 5000 ); result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, &reportedRate ); } if ( microCounter > 2000000 ) { errorStream_ << "RtApiCore::probeDeviceOpen: timeout waiting for sample rate update for device (" << deviceId << ")."; errorText_ = errorStream_.str(); return FAILURE; } } // Now set the stream format for all streams. Also, check the // physical format of the device and change that if necessary. AudioStreamBasicDescription description; dataSize = sizeof( AudioStreamBasicDescription ); property.mSelector = kAudioStreamPropertyVirtualFormat; result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, &description ); if ( result != noErr ) { errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") getting stream format for device (" << deviceId << ")."; errorText_ = errorStream_.str(); return FAILURE; } // Set the sample rate and data format id. However, only make the // change if the sample rate is not within 1.0 of the desired // rate and the format is not linear pcm. bool updateFormat = false; if ( fabs( description.mSampleRate - (Float64)sampleRate ) > 1.0 ) { description.mSampleRate = (Float64) sampleRate; updateFormat = true; } if ( description.mFormatID != kAudioFormatLinearPCM ) { description.mFormatID = kAudioFormatLinearPCM; updateFormat = true; } if ( updateFormat ) { result = AudioObjectSetPropertyData( id, &property, 0, NULL, dataSize, &description ); if ( result != noErr ) { errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") setting sample rate or data format for device (" << deviceId << ")."; errorText_ = errorStream_.str(); return FAILURE; } } // Now check the physical format. property.mSelector = kAudioStreamPropertyPhysicalFormat; result = AudioObjectGetPropertyData( id, &property, 0, NULL, &dataSize, &description ); if ( result != noErr ) { errorStream_ << "RtApiCore::probeDeviceOpen: system error (" << getErrorCode( result ) << ") getting stream physical format for device (" << deviceId << ")."; errorText_ = errorStream_.str(); return FAILURE; } //std::cout << "Current physical stream format:" << std::endl; //std::cout << " mBitsPerChan = " << description.mBitsPerChannel << std::endl; //std::cout << " aligned high = " << (description.mFormatFlags & kAudioFormatFlagIsAlignedHigh) << ", isPacked = " << (description.mFormatFlags & kAudioFormatFlagIsPacked) << std::endl; //std::cout << " bytesPerFrame = " << description.mBytesPerFrame << std::endl; //std::cout << " sample rate = " << description.mSampleRate << std::endl; if ( description.mFormatID != kAudioFormatLinearPCM || description.mBitsPerChannel < 16 ) { description.mFormatID = kAudioFormatLinearPCM; //description.mSampleRate = (Float64) sampleRate; AudioStreamBasicDescription testDescription = description; UInt32 formatFlags; // We'll try higher bit rates first and then work our way down. std::vector< std::pair > physicalFormats; formatFlags = (description.mFormatFlags | kLinearPCMFormatFlagIsFloat) & ~kLinearPCMFormatFlagIsSignedInteger; physicalFormats.push_back( std::pair( 32, formatFlags ) ); formatFlags = (description.mFormatFlags | kLinearPCMFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked) & ~kLinearPCMFormatFlagIsFloat; physicalFormats.push_back( std::pair( 32, formatFlags ) ); physicalFormats.push_back( std::pair( 24, formatFlags ) ); // 24-bit packed formatFlags &= ~( kAudioFormatFlagIsPacked | kAudioFormatFlagIsAlignedHigh ); physicalFormats.push_back( std::pair( 24.2, formatFlags ) ); // 24-bit in 4 bytes, aligned low formatFlags |= kAudioFormatFlagIsAlignedHigh; physicalFormats.push_back( std::pair( 24.4, formatFlags ) ); // 24-bit in 4 bytes, aligned high formatFlags = (description.mFormatFlags | kLinearPCMFormatFlagIsSignedInteger | kAudioFormatFlagIsPacked) & ~kLinearPCMFormatFlagIsFloat; physicalFormats.push_back( std::pair( 16, formatFlags ) ); physicalFormats.push_back( std::pair( 8, formatFlags ) ); bool setPhysicalFormat = false; for( unsigned int i=0; iflags & RTAUDIO_NONINTERLEAVED ) stream_.userInterleaved = false; else stream_.userInterleaved = true; stream_.deviceInterleaved[mode] = true; if ( monoMode == true ) stream_.deviceInterleaved[mode] = false; // Set flags for buffer conversion. stream_.doConvertBuffer[mode] = false; if ( stream_.userFormat != stream_.deviceFormat[mode] ) stream_.doConvertBuffer[mode] = true; if ( stream_.nUserChannels[mode] < stream_.nDeviceChannels[mode] ) stream_.doConvertBuffer[mode] = true; if ( streamCount == 1 ) { if ( stream_.nUserChannels[mode] > 1 && stream_.userInterleaved != stream_.deviceInterleaved[mode] ) stream_.doConvertBuffer[mode] = true; } else if ( monoMode && stream_.userInterleaved ) stream_.doConvertBuffer[mode] = true; // Allocate our CoreHandle structure for the stream. CoreHandle *handle = 0; if ( stream_.apiHandle == 0 ) { try { handle = new CoreHandle; } catch ( std::bad_alloc& ) { errorText_ = "RtApiCore::probeDeviceOpen: error allocating CoreHandle memory."; goto error; } if ( pthread_cond_init( &handle->condition, NULL ) ) { errorText_ = "RtApiCore::probeDeviceOpen: error initializing pthread condition variable."; goto error; } stream_.apiHandle = (void *) handle; } else handle = (CoreHandle *) stream_.apiHandle; handle->iStream[mode] = firstStream; handle->nStreams[mode] = streamCount; handle->id[mode] = id; // Allocate necessary internal buffers. unsigned long bufferBytes; bufferBytes = stream_.nUserChannels[mode] * *bufferSize * formatBytes( stream_.userFormat ); stream_.userBuffer[mode] = (char *) calloc( bufferBytes, 1 ); if ( stream_.userBuffer[mode] == NULL ) { errorText_ = "RtApiCore::probeDeviceOpen: error allocating user buffer memory."; goto error; } // If possible, we will make use of the CoreAudio stream buffers as // "device buffers". However, we can't do this if using multiple // streams. if ( stream_.doConvertBuffer[mode] && handle->nStreams[mode] > 1 ) { bool makeBuffer = true; bufferBytes = stream_.nDeviceChannels[mode] * formatBytes( stream_.deviceFormat[mode] ); if ( mode == INPUT ) { if ( stream_.mode == OUTPUT && stream_.deviceBuffer ) { unsigned long bytesOut = stream_.nDeviceChannels[0] * formatBytes( stream_.deviceFormat[0] ); if ( bufferBytes <= bytesOut ) makeBuffer = false; } } if ( makeBuffer ) { bufferBytes *= *bufferSize; if ( stream_.deviceBuffer ) free( stream_.deviceBuffer ); stream_.deviceBuffer = (char *) calloc( bufferBytes, 1 ); if ( stream_.deviceBuffer == NULL ) { errorText_ = "RtApiCore::probeDeviceOpen: error allocating device buffer memory."; goto error; } } } stream_.sampleRate = sampleRate; stream_.deviceId[mode] = deviceId; stream_.state = STREAM_STOPPED; stream_.callbackInfo.object = (void *) this; // Setup the buffer conversion information structure. if ( stream_.doConvertBuffer[mode] ) { if ( streamCount > 1 ) setConvertInfo( mode, 0 ); else setConvertInfo( mode, channelOffset ); } if ( mode == INPUT && stream_.mode == OUTPUT && stream_.deviceId[0] == deviceId ) // Only one callback procedure and property listener per device. stream_.mode = DUPLEX; else { #if defined( MAC_OS_X_VERSION_10_5 ) && ( MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 ) result = AudioDeviceCreateIOProcID( id, callbackHandler, (void *) &stream_.callbackInfo, &handle->procId[mode] ); #else // deprecated in favor of AudioDeviceCreateIOProcID() result = AudioDeviceAddIOProc( id, callbackHandler, (void *) &stream_.callbackInfo ); #endif if ( result != noErr ) { errorStream_ << "RtApiCore::probeDeviceOpen: system error setting callback for device (" << deviceId << ")."; errorText_ = errorStream_.str(); goto error; } if ( stream_.mode == OUTPUT && mode == INPUT ) stream_.mode = DUPLEX; else stream_.mode = mode; // Setup the device property listener for over/underload. property.mSelector = kAudioDeviceProcessorOverload; property.mScope = kAudioObjectPropertyScopeGlobal; result = AudioObjectAddPropertyListener( id, &property, xrunListener, (void *) handle ); if ( result != noErr ) { errorStream_ << "RtApiCore::probeDeviceOpen: system error setting xrun listener for device (" << deviceId << ")."; errorText_ = errorStream_.str(); goto error; } handle->xrunListenerAdded[mode] = true; // Setup a listener to detect a possible device disconnect. property.mSelector = kAudioDevicePropertyDeviceIsAlive; result = AudioObjectAddPropertyListener( id , &property, streamDisconnectListener, (void *) &stream_.callbackInfo ); if ( result != noErr ) { AudioObjectRemovePropertyListener( id, &property, xrunListener, (void *) handle ); errorStream_ << "RtApiCore::probeDeviceOpen: system error setting disconnect listener for device (" << deviceId << ")."; errorText_ = errorStream_.str(); goto error; } handle->disconnectListenerAdded[mode] = true; } return SUCCESS; error: closeStream(); // this should safely clear out procedures, listeners and memory, even for duplex stream return FAILURE; } void RtApiCore :: closeStream( void ) { if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiCore::closeStream(): no open stream to close!"; error( RTAUDIO_WARNING ); return; } CoreHandle *handle = (CoreHandle *) stream_.apiHandle; if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { if ( handle ) { AudioObjectPropertyAddress property = { kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal, KAUDIOOBJECTPROPERTYELEMENT }; if ( handle->xrunListenerAdded[0] ) { property.mSelector = kAudioDeviceProcessorOverload; if (AudioObjectRemovePropertyListener( handle->id[0], &property, xrunListener, (void *) handle ) != noErr) { errorText_ = "RtApiCore::closeStream(): error removing xrun property listener!"; error( RTAUDIO_WARNING ); } } if ( handle->disconnectListenerAdded[0] ) { property.mSelector = kAudioDevicePropertyDeviceIsAlive; if (AudioObjectRemovePropertyListener( handle->id[0], &property, streamDisconnectListener, (void *) &stream_.callbackInfo ) != noErr) { errorText_ = "RtApiCore::closeStream(): error removing disconnect property listener!"; error( RTAUDIO_WARNING ); } } #if defined( MAC_OS_X_VERSION_10_5 ) && ( MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 ) if ( handle->procId[0] ) { if ( stream_.state == STREAM_RUNNING ) AudioDeviceStop( handle->id[0], handle->procId[0] ); AudioDeviceDestroyIOProcID( handle->id[0], handle->procId[0] ); } #else // deprecated behaviour if ( stream_.state == STREAM_RUNNING ) AudioDeviceStop( handle->id[0], callbackHandler ); AudioDeviceRemoveIOProc( handle->id[0], callbackHandler ); #endif } } if ( stream_.mode == INPUT || ( stream_.mode == DUPLEX && stream_.deviceId[0] != stream_.deviceId[1] ) ) { if ( handle ) { AudioObjectPropertyAddress property = { kAudioHardwarePropertyDevices, kAudioObjectPropertyScopeGlobal, KAUDIOOBJECTPROPERTYELEMENT }; if ( handle->xrunListenerAdded[1] ) { property.mSelector = kAudioDeviceProcessorOverload; if (AudioObjectRemovePropertyListener( handle->id[1], &property, xrunListener, (void *) handle ) != noErr) { errorText_ = "RtApiCore::closeStream(): error removing xrun property listener!"; error( RTAUDIO_WARNING ); } } if ( handle->disconnectListenerAdded[0] ) { property.mSelector = kAudioDevicePropertyDeviceIsAlive; if (AudioObjectRemovePropertyListener( handle->id[1], &property, streamDisconnectListener, (void *) &stream_.callbackInfo ) != noErr) { errorText_ = "RtApiCore::closeStream(): error removing disconnect property listener!"; error( RTAUDIO_WARNING ); } } #if defined( MAC_OS_X_VERSION_10_5 ) && ( MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 ) if ( handle->procId[1] ) { if ( stream_.state == STREAM_RUNNING ) AudioDeviceStop( handle->id[1], handle->procId[1] ); AudioDeviceDestroyIOProcID( handle->id[1], handle->procId[1] ); } #else // deprecated behaviour if ( stream_.state == STREAM_RUNNING ) AudioDeviceStop( handle->id[1], callbackHandler ); AudioDeviceRemoveIOProc( handle->id[1], callbackHandler ); #endif } } for ( int i=0; i<2; i++ ) { if ( stream_.userBuffer[i] ) { free( stream_.userBuffer[i] ); stream_.userBuffer[i] = 0; } } if ( stream_.deviceBuffer ) { free( stream_.deviceBuffer ); stream_.deviceBuffer = 0; } // Destroy pthread condition variable. pthread_cond_signal( &handle->condition ); // signal condition variable in case stopStream is blocked pthread_cond_destroy( &handle->condition ); delete handle; stream_.apiHandle = 0; CallbackInfo *info = (CallbackInfo *) &stream_.callbackInfo; if ( info->deviceDisconnected ) { errorText_ = "RtApiCore: the stream device was disconnected (and closed)!"; error( RTAUDIO_DEVICE_DISCONNECT ); } clearStreamInfo(); } RtAudioErrorType RtApiCore :: startStream( void ) { if ( stream_.state != STREAM_STOPPED ) { if ( stream_.state == STREAM_RUNNING ) errorText_ = "RtApiCore::startStream(): the stream is already running!"; else if ( stream_.state == STREAM_STOPPING || stream_.state == STREAM_CLOSED ) errorText_ = "RtApiCore::startStream(): the stream is stopping or closed!"; return error( RTAUDIO_WARNING ); } /* #if defined( HAVE_GETTIMEOFDAY ) gettimeofday( &stream_.lastTickTimestamp, NULL ); #endif */ OSStatus result = noErr; CoreHandle *handle = (CoreHandle *) stream_.apiHandle; if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { #if defined( MAC_OS_X_VERSION_10_5 ) && ( MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 ) result = AudioDeviceStart( handle->id[0], handle->procId[0] ); #else // deprecated behaviour result = AudioDeviceStart( handle->id[0], callbackHandler ); #endif if ( result != noErr ) { errorStream_ << "RtApiCore::startStream: system error (" << getErrorCode( result ) << ") starting callback procedure on device (" << stream_.deviceId[0] << ")."; errorText_ = errorStream_.str(); goto unlock; } } if ( stream_.mode == INPUT || ( stream_.mode == DUPLEX && stream_.deviceId[0] != stream_.deviceId[1] ) ) { // Clear user input buffer unsigned long bufferBytes; bufferBytes = stream_.nUserChannels[1] * stream_.bufferSize * formatBytes( stream_.userFormat ); memset( stream_.userBuffer[1], 0, bufferBytes * sizeof(char) ); #if defined( MAC_OS_X_VERSION_10_5 ) && ( MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 ) result = AudioDeviceStart( handle->id[1], handle->procId[1] ); #else // deprecated behaviour result = AudioDeviceStart( handle->id[1], callbackHandler ); #endif if ( result != noErr ) { errorStream_ << "RtApiCore::startStream: system error starting input callback procedure on device (" << stream_.deviceId[1] << ")."; errorText_ = errorStream_.str(); goto unlock; } } handle->drainCounter = 0; handle->internalDrain = false; stream_.state = STREAM_RUNNING; unlock: if ( result == noErr ) return RTAUDIO_NO_ERROR; return error( RTAUDIO_SYSTEM_ERROR ); } RtAudioErrorType RtApiCore :: stopStream( void ) { if ( stream_.state != STREAM_RUNNING && stream_.state != STREAM_STOPPING ) { if ( stream_.state == STREAM_STOPPED ) errorText_ = "RtApiCore::stopStream(): the stream is already stopped!"; else if ( stream_.state == STREAM_CLOSED ) errorText_ = "RtApiCore::stopStream(): the stream is closed!"; return error( RTAUDIO_WARNING ); } OSStatus result = noErr; CoreHandle *handle = (CoreHandle *) stream_.apiHandle; if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { if ( handle->drainCounter == 0 ) { handle->drainCounter = 2; pthread_cond_wait( &handle->condition, &stream_.mutex ); // block until signaled } #if defined( MAC_OS_X_VERSION_10_5 ) && ( MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 ) result = AudioDeviceStop( handle->id[0], handle->procId[0] ); #else // deprecated behaviour result = AudioDeviceStop( handle->id[0], callbackHandler ); #endif if ( result != noErr ) { errorStream_ << "RtApiCore::stopStream: system error (" << getErrorCode( result ) << ") stopping callback procedure on device (" << stream_.deviceId[0] << ")."; errorText_ = errorStream_.str(); goto unlock; } } if ( stream_.mode == INPUT || ( stream_.mode == DUPLEX && stream_.deviceId[0] != stream_.deviceId[1] ) ) { #if defined( MAC_OS_X_VERSION_10_5 ) && ( MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 ) result = AudioDeviceStop( handle->id[1], handle->procId[1] ); #else // deprecated behaviour result = AudioDeviceStop( handle->id[1], callbackHandler ); #endif if ( result != noErr ) { errorStream_ << "RtApiCore::stopStream: system error (" << getErrorCode( result ) << ") stopping input callback procedure on device (" << stream_.deviceId[1] << ")."; errorText_ = errorStream_.str(); goto unlock; } } stream_.state = STREAM_STOPPED; unlock: if ( result == noErr ) return RTAUDIO_NO_ERROR; return error( RTAUDIO_SYSTEM_ERROR ); } RtAudioErrorType RtApiCore :: abortStream( void ) { if ( stream_.state != STREAM_RUNNING ) { if ( stream_.state == STREAM_STOPPED ) errorText_ = "RtApiCore::abortStream(): the stream is already stopped!"; else if ( stream_.state == STREAM_STOPPING || stream_.state == STREAM_CLOSED ) errorText_ = "RtApiCore::abortStream(): the stream is stopping or closed!"; return error( RTAUDIO_WARNING ); } CoreHandle *handle = (CoreHandle *) stream_.apiHandle; handle->drainCounter = 2; stream_.state = STREAM_STOPPING; return stopStream(); } // This function will be called by a spawned thread when the user // callback function signals that the stream should be stopped or // aborted. It is better to handle it this way because the // callbackEvent() function probably should return before the // AudioDeviceStop() function is called. static void *coreStopStream( void *ptr ) { CallbackInfo *info = (CallbackInfo *) ptr; RtApiCore *object = (RtApiCore *) info->object; object->stopStream(); pthread_exit( NULL ); } bool RtApiCore :: callbackEvent( AudioDeviceID deviceId, const AudioBufferList *inBufferList, const AudioBufferList *outBufferList ) { if ( stream_.state == STREAM_STOPPED || stream_.state == STREAM_STOPPING ) return SUCCESS; if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiCore::callbackEvent(): the stream is closed ... this shouldn't happen!"; error( RTAUDIO_WARNING ); return FAILURE; } CallbackInfo *info = (CallbackInfo *) &stream_.callbackInfo; CoreHandle *handle = (CoreHandle *) stream_.apiHandle; // Check if we were draining the stream and signal is finished. if ( handle->drainCounter > 3 ) { ThreadHandle threadId; stream_.state = STREAM_STOPPING; if ( handle->internalDrain == true ) pthread_create( &threadId, NULL, coreStopStream, info ); else // external call to stopStream() pthread_cond_signal( &handle->condition ); return SUCCESS; } AudioDeviceID outputDevice = handle->id[0]; // Invoke user callback to get fresh output data UNLESS we are // draining stream or duplex mode AND the input/output devices are // different AND this function is called for the input device. if ( handle->drainCounter == 0 && ( stream_.mode != DUPLEX || deviceId == outputDevice ) ) { RtAudioCallback callback = (RtAudioCallback) info->callback; double streamTime = getStreamTime(); RtAudioStreamStatus status = 0; if ( stream_.mode != INPUT && handle->xrun[0] == true ) { status |= RTAUDIO_OUTPUT_UNDERFLOW; handle->xrun[0] = false; } if ( stream_.mode != OUTPUT && handle->xrun[1] == true ) { status |= RTAUDIO_INPUT_OVERFLOW; handle->xrun[1] = false; } int cbReturnValue = callback( stream_.userBuffer[0], stream_.userBuffer[1], stream_.bufferSize, streamTime, status, info->userData ); if ( cbReturnValue == 2 ) { abortStream(); return SUCCESS; } else if ( cbReturnValue == 1 ) { handle->drainCounter = 1; handle->internalDrain = true; } } if ( stream_.mode == OUTPUT || ( stream_.mode == DUPLEX && deviceId == outputDevice ) ) { if ( handle->drainCounter > 1 ) { // write zeros to the output stream if ( handle->nStreams[0] == 1 ) { memset( outBufferList->mBuffers[handle->iStream[0]].mData, 0, outBufferList->mBuffers[handle->iStream[0]].mDataByteSize ); } else { // fill multiple streams with zeros for ( unsigned int i=0; inStreams[0]; i++ ) { memset( outBufferList->mBuffers[handle->iStream[0]+i].mData, 0, outBufferList->mBuffers[handle->iStream[0]+i].mDataByteSize ); } } } else if ( handle->nStreams[0] == 1 ) { if ( stream_.doConvertBuffer[0] ) { // convert directly to CoreAudio stream buffer convertBuffer( (char *) outBufferList->mBuffers[handle->iStream[0]].mData, stream_.userBuffer[0], stream_.convertInfo[0] ); } else { // copy from user buffer memcpy( outBufferList->mBuffers[handle->iStream[0]].mData, stream_.userBuffer[0], outBufferList->mBuffers[handle->iStream[0]].mDataByteSize ); } } else { // fill multiple streams Float32 *inBuffer = (Float32 *) stream_.userBuffer[0]; if ( stream_.doConvertBuffer[0] ) { convertBuffer( stream_.deviceBuffer, stream_.userBuffer[0], stream_.convertInfo[0] ); inBuffer = (Float32 *) stream_.deviceBuffer; } if ( stream_.deviceInterleaved[0] == false ) { // mono mode UInt32 bufferBytes = outBufferList->mBuffers[handle->iStream[0]].mDataByteSize; for ( unsigned int i=0; imBuffers[handle->iStream[0]+i].mData, (void *)&inBuffer[i*stream_.bufferSize], bufferBytes ); } } else { // fill multiple multi-channel streams with interleaved data UInt32 streamChannels, channelsLeft, inJump, outJump, inOffset; Float32 *out, *in; bool inInterleaved = ( stream_.userInterleaved ) ? true : false; UInt32 inChannels = stream_.nUserChannels[0]; if ( stream_.doConvertBuffer[0] ) { inInterleaved = true; // device buffer will always be interleaved for nStreams > 1 and not mono mode inChannels = stream_.nDeviceChannels[0]; } if ( inInterleaved ) inOffset = 1; else inOffset = stream_.bufferSize; channelsLeft = inChannels; for ( unsigned int i=0; inStreams[0]; i++ ) { in = inBuffer; out = (Float32 *) outBufferList->mBuffers[handle->iStream[0]+i].mData; streamChannels = outBufferList->mBuffers[handle->iStream[0]+i].mNumberChannels; outJump = 0; // Account for possible channel offset in first stream if ( i == 0 && stream_.channelOffset[0] > 0 ) { streamChannels -= stream_.channelOffset[0]; outJump = stream_.channelOffset[0]; out += outJump; } // Account for possible unfilled channels at end of the last stream if ( streamChannels > channelsLeft ) { outJump = streamChannels - channelsLeft; streamChannels = channelsLeft; } // Determine input buffer offsets and skips if ( inInterleaved ) { inJump = inChannels; in += inChannels - channelsLeft; } else { inJump = 1; in += (inChannels - channelsLeft) * inOffset; } for ( unsigned int i=0; idrainCounter ) { handle->drainCounter++; goto unlock; } AudioDeviceID inputDevice; inputDevice = handle->id[1]; if ( stream_.mode == INPUT || ( stream_.mode == DUPLEX && deviceId == inputDevice ) ) { if ( handle->nStreams[1] == 1 ) { if ( stream_.doConvertBuffer[1] ) { // convert directly from CoreAudio stream buffer convertBuffer( stream_.userBuffer[1], (char *) inBufferList->mBuffers[handle->iStream[1]].mData, stream_.convertInfo[1] ); } else { // copy to user buffer memcpy( stream_.userBuffer[1], inBufferList->mBuffers[handle->iStream[1]].mData, inBufferList->mBuffers[handle->iStream[1]].mDataByteSize ); } } else { // read from multiple streams Float32 *outBuffer = (Float32 *) stream_.userBuffer[1]; if ( stream_.doConvertBuffer[1] ) outBuffer = (Float32 *) stream_.deviceBuffer; if ( stream_.deviceInterleaved[1] == false ) { // mono mode UInt32 bufferBytes = inBufferList->mBuffers[handle->iStream[1]].mDataByteSize; for ( unsigned int i=0; imBuffers[handle->iStream[1]+i].mData, bufferBytes ); } } else { // read from multiple multi-channel streams UInt32 streamChannels, channelsLeft, inJump, outJump, outOffset; Float32 *out, *in; bool outInterleaved = ( stream_.userInterleaved ) ? true : false; UInt32 outChannels = stream_.nUserChannels[1]; if ( stream_.doConvertBuffer[1] ) { outInterleaved = true; // device buffer will always be interleaved for nStreams > 1 and not mono mode outChannels = stream_.nDeviceChannels[1]; } if ( outInterleaved ) outOffset = 1; else outOffset = stream_.bufferSize; channelsLeft = outChannels; for ( unsigned int i=0; inStreams[1]; i++ ) { out = outBuffer; in = (Float32 *) inBufferList->mBuffers[handle->iStream[1]+i].mData; streamChannels = inBufferList->mBuffers[handle->iStream[1]+i].mNumberChannels; inJump = 0; // Account for possible channel offset in first stream if ( i == 0 && stream_.channelOffset[1] > 0 ) { streamChannels -= stream_.channelOffset[1]; inJump = stream_.channelOffset[1]; in += inJump; } // Account for possible unread channels at end of the last stream if ( streamChannels > channelsLeft ) { inJump = streamChannels - channelsLeft; streamChannels = channelsLeft; } // Determine output buffer offsets and skips if ( outInterleaved ) { outJump = outChannels; out += outChannels - channelsLeft; } else { outJump = 1; out += (outChannels - channelsLeft) * outOffset; } for ( unsigned int i=0; iid[0] == handle->id[1] ) // same device, only one callback RtApi::tickStreamTime(); else if ( deviceId == handle->id[0] ) RtApi::tickStreamTime(); // two devices, only tick on the output callback } else RtApi::tickStreamTime(); // input or output stream only return SUCCESS; } const char* RtApiCore :: getErrorCode( OSStatus code ) { switch( code ) { case kAudioHardwareNotRunningError: return "kAudioHardwareNotRunningError"; case kAudioHardwareUnspecifiedError: return "kAudioHardwareUnspecifiedError"; case kAudioHardwareUnknownPropertyError: return "kAudioHardwareUnknownPropertyError"; case kAudioHardwareBadPropertySizeError: return "kAudioHardwareBadPropertySizeError"; case kAudioHardwareIllegalOperationError: return "kAudioHardwareIllegalOperationError"; case kAudioHardwareBadObjectError: return "kAudioHardwareBadObjectError"; case kAudioHardwareBadDeviceError: return "kAudioHardwareBadDeviceError"; case kAudioHardwareBadStreamError: return "kAudioHardwareBadStreamError"; case kAudioHardwareUnsupportedOperationError: return "kAudioHardwareUnsupportedOperationError"; case kAudioDeviceUnsupportedFormatError: return "kAudioDeviceUnsupportedFormatError"; case kAudioDevicePermissionsError: return "kAudioDevicePermissionsError"; default: return "CoreAudio unknown error"; } } //******************** End of __MACOSX_CORE__ *********************// #endif #if defined(__UNIX_JACK__) // JACK is a low-latency audio server, originally written for the // GNU/Linux operating system and now also ported to OS-X and // Windows. It can connect a number of different applications to an // audio device, as well as allowing them to share audio between // themselves. // // When using JACK with RtAudio, "devices" refer to JACK clients that // have ports connected to the server, while ports correspond to device // channels. The JACK server is typically started in a terminal as // follows: // // .jackd -d alsa -d hw:0 // // or through an interface program such as qjackctl. Many of the // parameters normally set for a stream are fixed by the JACK server // and can be specified when the JACK server is started. In // particular, // // .jackd -d alsa -d hw:0 -r 44100 -p 512 -n 4 // // specifies a sample rate of 44100 Hz, a buffer size of 512 sample // frames, and number of buffers = 4. Once the server is running, it // is not possible to override these values. If the values are not // specified in the command-line, the JACK server uses default values. // // The JACK server does not have to be running when an instance of // RtApiJack is created, though the function getDeviceCount() will // report 0 devices found until JACK has been started. When no // devices are available (i.e., the JACK server is not running), a // stream cannot be opened. #include #include // A structure to hold various information related to the Jack API // implementation. struct JackHandle { jack_client_t *client; jack_port_t **ports[2]; std::string deviceName[2]; bool xrun[2]; pthread_cond_t condition; int drainCounter; // Tracks callback counts when draining bool internalDrain; // Indicates if stop is initiated from callback or not. JackHandle() :client(0), drainCounter(0), internalDrain(false) { ports[0] = 0; ports[1] = 0; xrun[0] = false; xrun[1] = false; } }; std::string escapeJackPortRegex(std::string &str) { const std::string need_escaping = "()[]{}*+?$^.|\\"; std::string escaped_string; for (auto c : str) { if (need_escaping.find(c) != std::string::npos) escaped_string.push_back('\\'); escaped_string.push_back(c); } return escaped_string; } #if !defined(__RTAUDIO_DEBUG__) static void jackSilentError( const char * ) {}; #endif RtApiJack :: RtApiJack() :shouldAutoconnect_(true) { // Nothing to do here. #if !defined(__RTAUDIO_DEBUG__) // Turn off Jack's internal error reporting. jack_set_error_function( &jackSilentError ); #endif } RtApiJack :: ~RtApiJack() { if ( stream_.state != STREAM_CLOSED ) closeStream(); } void RtApiJack :: probeDevices( void ) { // See list of required functionality in RtApi::probeDevices(). // See if we can become a jack client. jack_options_t options = (jack_options_t) ( JackNoStartServer ); //JackNullOption; jack_status_t *status = NULL; jack_client_t *client = jack_client_open( "RtApiJackProbe", options, status ); if ( client == 0 ) { deviceList_.clear(); // in case the server is shutdown after a previous successful probe errorText_ = "RtApiJack::probeDevices: Jack server not found or connection error!"; //error( RTAUDIO_SYSTEM_ERROR ); error( RTAUDIO_WARNING ); return; } const char **ports; std::string port, previousPort; unsigned int nChannels = 0, nDevices = 0; std::vector portNames; ports = jack_get_ports( client, NULL, JACK_DEFAULT_AUDIO_TYPE, 0 ); if ( ports ) { // Parse the port names up to the first colon (:). size_t iColon = 0; do { port = (char *) ports[ nChannels ]; iColon = port.find(":"); if ( iColon != std::string::npos ) { port = port.substr( 0, iColon ); if ( port != previousPort ) { portNames.push_back( port ); nDevices++; previousPort = port; } } } while ( ports[++nChannels] ); free( ports ); } // Fill or update the deviceList_. unsigned int m, n; for ( n=0; n::iterator it=deviceList_.begin(); it!=deviceList_.end(); ) { for ( m=0; m 0 && info.inputChannels > 0 ) info.duplexChannels = (info.outputChannels > info.inputChannels) ? info.inputChannels : info.outputChannels; // Jack always uses 32-bit floats. info.nativeFormats = RTAUDIO_FLOAT32; return true; } static int jackCallbackHandler( jack_nframes_t nframes, void *infoPointer ) { CallbackInfo *info = (CallbackInfo *) infoPointer; RtApiJack *object = (RtApiJack *) info->object; if ( object->callbackEvent( (unsigned long) nframes ) == false ) return 1; return 0; } // This function will be called by a spawned thread when the Jack // server signals that it is shutting down. It is necessary to handle // it this way because the jackShutdown() function must return before // the jack_deactivate() function (in closeStream()) will return. static void *jackCloseStream( void *ptr ) { CallbackInfo *info = (CallbackInfo *) ptr; RtApiJack *object = (RtApiJack *) info->object; info->deviceDisconnected = true; object->closeStream(); pthread_exit( NULL ); } /* // Could be used to catch client connections but requires open client. static void jackClientChange( const char *name, int registered, void *infoPointer ) { std::cout << "in jackClientChange, name = " << name << ", registered = " << registered << std::endl; } */ static void jackShutdown( void *infoPointer ) { CallbackInfo *info = (CallbackInfo *) infoPointer; RtApiJack *object = (RtApiJack *) info->object; // Check current stream state. If stopped, then we'll assume this // was called as a result of a call to RtApiJack::stopStream (the // deactivation of a client handle causes this function to be called). // If not, we'll assume the Jack server is shutting down or some // other problem occurred and we should close the stream. if ( object->isStreamRunning() == false ) return; ThreadHandle threadId; pthread_create( &threadId, NULL, jackCloseStream, info ); } static int jackXrun( void *infoPointer ) { JackHandle *handle = *((JackHandle **) infoPointer); if ( handle->ports[0] ) handle->xrun[0] = true; if ( handle->ports[1] ) handle->xrun[1] = true; return 0; } bool RtApiJack :: probeDeviceOpen( unsigned int deviceId, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, RtAudio::StreamOptions *options ) { JackHandle *handle = (JackHandle *) stream_.apiHandle; // Look for jack server and try to become a client (only do once per stream). jack_client_t *client = 0; if ( mode == OUTPUT || ( mode == INPUT && stream_.mode != OUTPUT ) ) { jack_options_t jackoptions = (jack_options_t) ( JackNoStartServer ); //JackNullOption; jack_status_t *status = NULL; if ( options && !options->streamName.empty() ) client = jack_client_open( options->streamName.c_str(), jackoptions, status ); else client = jack_client_open( "RtApiJack", jackoptions, status ); if ( client == 0 ) { errorText_ = "RtApiJack::probeDeviceOpen: Jack server not found or connection error!"; error( RTAUDIO_WARNING ); return FAILURE; } } else { // The handle must have been created on an earlier pass. client = handle->client; } std::string deviceName; for ( unsigned int m=0; mflags & RTAUDIO_JACK_DONT_CONNECT)) ) { // Count the available ports containing the client name as device // channels. Jack "input ports" equal RtAudio output channels. unsigned int nChannels = 0; ports = jack_get_ports( client, escapeJackPortRegex(deviceName).c_str(), JACK_DEFAULT_AUDIO_TYPE, flag ); if ( ports ) { while ( ports[ nChannels ] ) nChannels++; free( ports ); } // Compare the jack ports for specified client to the requested number of channels. if ( nChannels < (channels + firstChannel) ) { errorStream_ << "RtApiJack::probeDeviceOpen: requested number of channels (" << channels << ") + offset (" << firstChannel << ") not found for specified device (" << deviceName << ")."; errorText_ = errorStream_.str(); return FAILURE; } } // Check the jack server sample rate. unsigned int jackRate = jack_get_sample_rate( client ); if ( sampleRate != jackRate ) { jack_client_close( client ); errorStream_ << "RtApiJack::probeDeviceOpen: the requested sample rate (" << sampleRate << ") is different than the JACK server rate (" << jackRate << ")."; errorText_ = errorStream_.str(); return FAILURE; } stream_.sampleRate = jackRate; // Get the latency of the JACK port. ports = jack_get_ports( client, escapeJackPortRegex(deviceName).c_str(), JACK_DEFAULT_AUDIO_TYPE, flag ); if ( ports[ firstChannel ] ) { // Added by Ge Wang jack_latency_callback_mode_t cbmode = (mode == INPUT ? JackCaptureLatency : JackPlaybackLatency); // the range (usually the min and max are equal) jack_latency_range_t latrange; latrange.min = latrange.max = 0; // get the latency range jack_port_get_latency_range( jack_port_by_name( client, ports[firstChannel] ), cbmode, &latrange ); // be optimistic, use the min! stream_.latency[mode] = latrange.min; //stream_.latency[mode] = jack_port_get_latency( jack_port_by_name( client, ports[ firstChannel ] ) ); } free( ports ); // The jack server always uses 32-bit floating-point data. stream_.deviceFormat[mode] = RTAUDIO_FLOAT32; stream_.userFormat = format; if ( options && options->flags & RTAUDIO_NONINTERLEAVED ) stream_.userInterleaved = false; else stream_.userInterleaved = true; // Jack always uses non-interleaved buffers. stream_.deviceInterleaved[mode] = false; // Jack always provides host byte-ordered data. stream_.doByteSwap[mode] = false; // Get the buffer size. The buffer size and number of buffers // (periods) is set when the jack server is started. stream_.bufferSize = (int) jack_get_buffer_size( client ); *bufferSize = stream_.bufferSize; stream_.nDeviceChannels[mode] = channels; stream_.nUserChannels[mode] = channels; // Set flags for buffer conversion. stream_.doConvertBuffer[mode] = false; if ( stream_.userFormat != stream_.deviceFormat[mode] ) stream_.doConvertBuffer[mode] = true; if ( stream_.userInterleaved != stream_.deviceInterleaved[mode] && stream_.nUserChannels[mode] > 1 ) stream_.doConvertBuffer[mode] = true; // Allocate our JackHandle structure for the stream. if ( handle == 0 ) { try { handle = new JackHandle; } catch ( std::bad_alloc& ) { errorText_ = "RtApiJack::probeDeviceOpen: error allocating JackHandle memory."; goto error; } if ( pthread_cond_init(&handle->condition, NULL) ) { errorText_ = "RtApiJack::probeDeviceOpen: error initializing pthread condition variable."; goto error; } stream_.apiHandle = (void *) handle; handle->client = client; } handle->deviceName[mode] = deviceName; // Allocate necessary internal buffers. unsigned long bufferBytes; bufferBytes = stream_.nUserChannels[mode] * *bufferSize * formatBytes( stream_.userFormat ); stream_.userBuffer[mode] = (char *) calloc( bufferBytes, 1 ); if ( stream_.userBuffer[mode] == NULL ) { errorText_ = "RtApiJack::probeDeviceOpen: error allocating user buffer memory."; goto error; } if ( stream_.doConvertBuffer[mode] ) { bool makeBuffer = true; if ( mode == OUTPUT ) bufferBytes = stream_.nDeviceChannels[0] * formatBytes( stream_.deviceFormat[0] ); else { // mode == INPUT bufferBytes = stream_.nDeviceChannels[1] * formatBytes( stream_.deviceFormat[1] ); if ( stream_.mode == OUTPUT && stream_.deviceBuffer ) { unsigned long bytesOut = stream_.nDeviceChannels[0] * formatBytes(stream_.deviceFormat[0]); if ( bufferBytes < bytesOut ) makeBuffer = false; } } if ( makeBuffer ) { bufferBytes *= *bufferSize; if ( stream_.deviceBuffer ) free( stream_.deviceBuffer ); stream_.deviceBuffer = (char *) calloc( bufferBytes, 1 ); if ( stream_.deviceBuffer == NULL ) { errorText_ = "RtApiJack::probeDeviceOpen: error allocating device buffer memory."; goto error; } } } // Allocate memory for the Jack ports (channels) identifiers. handle->ports[mode] = (jack_port_t **) malloc ( sizeof (jack_port_t *) * channels ); if ( handle->ports[mode] == NULL ) { errorText_ = "RtApiJack::probeDeviceOpen: error allocating port memory."; goto error; } stream_.channelOffset[mode] = firstChannel; stream_.state = STREAM_STOPPED; stream_.callbackInfo.object = (void *) this; if ( stream_.mode == OUTPUT && mode == INPUT ) // We had already set up the stream for output. stream_.mode = DUPLEX; else { stream_.mode = mode; jack_set_process_callback( handle->client, jackCallbackHandler, (void *) &stream_.callbackInfo ); jack_set_xrun_callback( handle->client, jackXrun, (void *) &stream_.apiHandle ); jack_on_shutdown( handle->client, jackShutdown, (void *) &stream_.callbackInfo ); //jack_set_client_registration_callback( handle->client, jackClientChange, (void *) &stream_.callbackInfo ); } // Register our ports. char label[64]; if ( mode == OUTPUT ) { for ( unsigned int i=0; iports[0][i] = jack_port_register( handle->client, (const char *)label, JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0 ); } } else { for ( unsigned int i=0; iports[1][i] = jack_port_register( handle->client, (const char *)label, JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0 ); } } // Setup the buffer conversion information structure. We don't use // buffers to do channel offsets, so we override that parameter // here. if ( stream_.doConvertBuffer[mode] ) setConvertInfo( mode, 0 ); if ( options && options->flags & RTAUDIO_JACK_DONT_CONNECT ) shouldAutoconnect_ = false; return SUCCESS; error: if ( handle ) { pthread_cond_destroy( &handle->condition ); jack_client_close( handle->client ); if ( handle->ports[0] ) free( handle->ports[0] ); if ( handle->ports[1] ) free( handle->ports[1] ); delete handle; stream_.apiHandle = 0; } for ( int i=0; i<2; i++ ) { if ( stream_.userBuffer[i] ) { free( stream_.userBuffer[i] ); stream_.userBuffer[i] = 0; } } if ( stream_.deviceBuffer ) { free( stream_.deviceBuffer ); stream_.deviceBuffer = 0; } return FAILURE; } void RtApiJack :: closeStream( void ) { if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiJack::closeStream(): no open stream to close!"; error( RTAUDIO_WARNING ); return; } JackHandle *handle = (JackHandle *) stream_.apiHandle; if ( handle ) { if ( stream_.state == STREAM_RUNNING ) jack_deactivate( handle->client ); if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { for ( unsigned int i=0; iclient, handle->ports[0][i] ); } if ( stream_.mode == INPUT || stream_.mode == DUPLEX ) { for ( unsigned int i=0; iclient, handle->ports[1][i] ); } jack_client_close( handle->client ); if ( handle->ports[0] ) free( handle->ports[0] ); if ( handle->ports[1] ) free( handle->ports[1] ); pthread_cond_destroy( &handle->condition ); delete handle; stream_.apiHandle = 0; } CallbackInfo *info = (CallbackInfo *) &stream_.callbackInfo; if ( info->deviceDisconnected ) { errorText_ = "RtApiJack: the Jack server is shutting down this client ... stream stopped and closed!"; error( RTAUDIO_DEVICE_DISCONNECT ); } for ( int i=0; i<2; i++ ) { if ( stream_.userBuffer[i] ) { free( stream_.userBuffer[i] ); stream_.userBuffer[i] = 0; } } if ( stream_.deviceBuffer ) { free( stream_.deviceBuffer ); stream_.deviceBuffer = 0; } clearStreamInfo(); } RtAudioErrorType RtApiJack :: startStream( void ) { if ( stream_.state != STREAM_STOPPED ) { if ( stream_.state == STREAM_RUNNING ) errorText_ = "RtApiJack::startStream(): the stream is already running!"; else if ( stream_.state == STREAM_STOPPING || stream_.state == STREAM_CLOSED ) errorText_ = "RtApiJack::startStream(): the stream is stopping or closed!"; return error( RTAUDIO_WARNING ); } /* #if defined( HAVE_GETTIMEOFDAY ) gettimeofday( &stream_.lastTickTimestamp, NULL ); #endif */ JackHandle *handle = (JackHandle *) stream_.apiHandle; int result = jack_activate( handle->client ); if ( result ) { errorText_ = "RtApiJack::startStream(): unable to activate JACK client!"; goto unlock; } const char **ports; // Get the list of available ports. if ( shouldAutoconnect_ && (stream_.mode == OUTPUT || stream_.mode == DUPLEX) ) { ports = jack_get_ports( handle->client, escapeJackPortRegex(handle->deviceName[0]).c_str(), JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput); if ( ports == NULL) { errorText_ = "RtApiJack::startStream(): error determining available JACK input ports!"; goto unlock; } // Now make the port connections. Since RtAudio wasn't designed to // allow the user to select particular channels of a device, we'll // just open the first "nChannels" ports with offset. for ( unsigned int i=0; iclient, jack_port_name( handle->ports[0][i] ), ports[ stream_.channelOffset[0] + i ] ); if ( result ) { free( ports ); errorText_ = "RtApiJack::startStream(): error connecting output ports!"; goto unlock; } } free(ports); } if ( shouldAutoconnect_ && (stream_.mode == INPUT || stream_.mode == DUPLEX) ) { ports = jack_get_ports( handle->client, escapeJackPortRegex(handle->deviceName[1]).c_str(), JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput ); if ( ports == NULL) { errorText_ = "RtApiJack::startStream(): error determining available JACK output ports!"; goto unlock; } // Now make the port connections. See note above. for ( unsigned int i=0; iclient, ports[ stream_.channelOffset[1] + i ], jack_port_name( handle->ports[1][i] ) ); if ( result ) { free( ports ); errorText_ = "RtApiJack::startStream(): error connecting input ports!"; goto unlock; } } free(ports); } handle->drainCounter = 0; handle->internalDrain = false; stream_.state = STREAM_RUNNING; unlock: if ( result == 0 ) return RTAUDIO_NO_ERROR; return error( RTAUDIO_SYSTEM_ERROR ); } RtAudioErrorType RtApiJack :: stopStream( void ) { if ( stream_.state != STREAM_RUNNING && stream_.state != STREAM_STOPPING ) { if ( stream_.state == STREAM_STOPPED ) errorText_ = "RtApiJack::stopStream(): the stream is already stopped!"; else if ( stream_.state == STREAM_CLOSED ) errorText_ = "RtApiJack::stopStream(): the stream is closed!"; return error( RTAUDIO_WARNING ); } JackHandle *handle = (JackHandle *) stream_.apiHandle; if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { if ( handle->drainCounter == 0 ) { handle->drainCounter = 2; pthread_cond_wait( &handle->condition, &stream_.mutex ); // block until signaled } } jack_deactivate( handle->client ); stream_.state = STREAM_STOPPED; return RTAUDIO_NO_ERROR; } RtAudioErrorType RtApiJack :: abortStream( void ) { if ( stream_.state != STREAM_RUNNING ) { if ( stream_.state == STREAM_STOPPED ) errorText_ = "RtApiJack::abortStream(): the stream is already stopped!"; else if ( stream_.state == STREAM_STOPPING || stream_.state == STREAM_CLOSED ) errorText_ = "RtApiJack::abortStream(): the stream is stopping or closed!"; return error( RTAUDIO_WARNING ); } JackHandle *handle = (JackHandle *) stream_.apiHandle; handle->drainCounter = 2; return stopStream(); } // This function will be called by a spawned thread when the user // callback function signals that the stream should be stopped or // aborted. It is necessary to handle it this way because the // callbackEvent() function must return before the jack_deactivate() // function will return. static void *jackStopStream( void *ptr ) { CallbackInfo *info = (CallbackInfo *) ptr; RtApiJack *object = (RtApiJack *) info->object; object->stopStream(); pthread_exit( NULL ); } bool RtApiJack :: callbackEvent( unsigned long nframes ) { if ( stream_.state == STREAM_STOPPED || stream_.state == STREAM_STOPPING ) return SUCCESS; if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiJack::callbackEvent(): the stream is closed ... this shouldn't happen!"; error( RTAUDIO_WARNING ); return FAILURE; } if ( stream_.bufferSize != nframes ) { errorText_ = "RtApiJack::callbackEvent(): the JACK buffer size has changed ... cannot process!"; error( RTAUDIO_WARNING ); return FAILURE; } CallbackInfo *info = (CallbackInfo *) &stream_.callbackInfo; JackHandle *handle = (JackHandle *) stream_.apiHandle; // Check if we were draining the stream and signal is finished. if ( handle->drainCounter > 3 ) { ThreadHandle threadId; stream_.state = STREAM_STOPPING; if ( handle->internalDrain == true ) pthread_create( &threadId, NULL, jackStopStream, info ); else // external call to stopStream() pthread_cond_signal( &handle->condition ); return SUCCESS; } // Invoke user callback first, to get fresh output data. if ( handle->drainCounter == 0 ) { RtAudioCallback callback = (RtAudioCallback) info->callback; double streamTime = getStreamTime(); RtAudioStreamStatus status = 0; if ( stream_.mode != INPUT && handle->xrun[0] == true ) { status |= RTAUDIO_OUTPUT_UNDERFLOW; handle->xrun[0] = false; } if ( stream_.mode != OUTPUT && handle->xrun[1] == true ) { status |= RTAUDIO_INPUT_OVERFLOW; handle->xrun[1] = false; } int cbReturnValue = callback( stream_.userBuffer[0], stream_.userBuffer[1], stream_.bufferSize, streamTime, status, info->userData ); if ( cbReturnValue == 2 ) { stream_.state = STREAM_STOPPING; handle->drainCounter = 2; ThreadHandle id; pthread_create( &id, NULL, jackStopStream, info ); return SUCCESS; } else if ( cbReturnValue == 1 ) { handle->drainCounter = 1; handle->internalDrain = true; } } jack_default_audio_sample_t *jackbuffer; unsigned long bufferBytes = nframes * sizeof( jack_default_audio_sample_t ); if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { if ( handle->drainCounter > 1 ) { // write zeros to the output stream for ( unsigned int i=0; iports[0][i], (jack_nframes_t) nframes ); memset( jackbuffer, 0, bufferBytes ); } } else if ( stream_.doConvertBuffer[0] ) { convertBuffer( stream_.deviceBuffer, stream_.userBuffer[0], stream_.convertInfo[0] ); for ( unsigned int i=0; iports[0][i], (jack_nframes_t) nframes ); memcpy( jackbuffer, &stream_.deviceBuffer[i*bufferBytes], bufferBytes ); } } else { // no buffer conversion for ( unsigned int i=0; iports[0][i], (jack_nframes_t) nframes ); memcpy( jackbuffer, &stream_.userBuffer[0][i*bufferBytes], bufferBytes ); } } } // Don't bother draining input if ( handle->drainCounter ) { handle->drainCounter++; goto unlock; } if ( stream_.mode == INPUT || stream_.mode == DUPLEX ) { if ( stream_.doConvertBuffer[1] ) { for ( unsigned int i=0; iports[1][i], (jack_nframes_t) nframes ); memcpy( &stream_.deviceBuffer[i*bufferBytes], jackbuffer, bufferBytes ); } convertBuffer( stream_.userBuffer[1], stream_.deviceBuffer, stream_.convertInfo[1] ); } else { // no buffer conversion for ( unsigned int i=0; iports[1][i], (jack_nframes_t) nframes ); memcpy( &stream_.userBuffer[1][i*bufferBytes], jackbuffer, bufferBytes ); } } } unlock: RtApi::tickStreamTime(); return SUCCESS; } //******************** End of __UNIX_JACK__ *********************// #endif #if defined(__WINDOWS_ASIO__) // ASIO API on Windows // The ASIO API is designed around a callback scheme, so this // implementation is similar to that used for OS-X CoreAudio and unix // Jack. The primary constraint with ASIO is that it only allows // access to a single driver at a time. Thus, it is not possible to // have more than one simultaneous RtAudio stream. // // This implementation also requires a number of external ASIO files // and a few global variables. The ASIO callback scheme does not // allow for the passing of user data, so we must create a global // pointer to our callbackInfo structure. // // On unix systems, we make use of a pthread condition variable. // Since there is no equivalent in Windows, I hacked something based // on information found in // http://www.cs.wustl.edu/~schmidt/win32-cv-1.html. #include "asiosys.h" #include "asio.h" #include "iasiothiscallresolver.h" #include "asiodrivers.h" #include static AsioDrivers drivers; static ASIOCallbacks asioCallbacks; static ASIODriverInfo driverInfo; static CallbackInfo *asioCallbackInfo; static bool asioXRun; static bool streamOpen = false; // Tracks whether any instance of RtAudio has a stream open struct AsioHandle { int drainCounter; // Tracks callback counts when draining bool internalDrain; // Indicates if stop is initiated from callback or not. ASIOBufferInfo *bufferInfos; HANDLE condition; AsioHandle() :drainCounter(0), internalDrain(false), bufferInfos(0) {} }; // Function declarations (definitions at end of section) static const char* getAsioErrorString( ASIOError result ); static void sampleRateChanged( ASIOSampleRate sRate ); static long asioMessages( long selector, long value, void* message, double* opt ); RtApiAsio :: RtApiAsio() { // ASIO cannot run on a multi-threaded apartment. You can call // CoInitialize beforehand, but it must be for apartment threading // (in which case, CoInitilialize will return S_FALSE here). coInitialized_ = false; HRESULT hr = CoInitialize( NULL ); if ( FAILED(hr) ) { errorText_ = "RtApiAsio::ASIO requires a single-threaded apartment. Call CoInitializeEx(0,COINIT_APARTMENTTHREADED)"; error( RTAUDIO_WARNING ); } coInitialized_ = true; // Check whether another RtAudio instance has an ASIO stream open. if ( streamOpen ) { errorText_ = "RtApiAsio(): Another RtAudio ASIO stream is open, functionality may be limited."; error( RTAUDIO_WARNING ); } else drivers.removeCurrentDriver(); driverInfo.asioVersion = 2; // See note in DirectSound implementation about GetDesktopWindow(). driverInfo.sysRef = GetForegroundWindow(); } RtApiAsio :: ~RtApiAsio() { if ( stream_.state != STREAM_CLOSED ) closeStream(); if ( coInitialized_ ) CoUninitialize(); } void RtApiAsio :: probeDevices( void ) { // See list of required functionality in RtApi::probeDevices(). if ( streamOpen ) { errorText_ = "RtApiAsio::probeDevices: Another RtAudio ASIO stream is open, cannot probe devices."; error( RTAUDIO_WARNING ); return; } unsigned int nDevices = drivers.asioGetNumDev(); if ( nDevices == 0 ) { deviceList_.clear(); return; } char tmp[32]; std::vector< std::string > driverNames; unsigned int n, m; for ( n=0; n::iterator it=deviceList_.begin(); it!=deviceList_.end(); ) { for ( m=0; m 0) { getDefaultInputDevice(); getDefaultOutputDevice(); } } bool RtApiAsio :: probeDeviceInfo( RtAudio::DeviceInfo &info ) { if ( !drivers.loadDriver( const_cast(info.name.c_str()) ) ) { errorStream_ << "RtApiAsio::probeDeviceInfo: unable to load driver (" << info.name << ")."; errorText_ = errorStream_.str(); error( RTAUDIO_WARNING ); return false; } ASIOError result = ASIOInit( &driverInfo ); if ( result != ASE_OK ) { drivers.removeCurrentDriver(); errorStream_ << "RtApiAsio::probeDeviceInfo: error (" << getAsioErrorString( result ) << ") initializing driver (" << info.name << ")."; errorText_ = errorStream_.str(); error( RTAUDIO_WARNING ); return false; } // Determine the device channel information. long inputChannels, outputChannels; result = ASIOGetChannels( &inputChannels, &outputChannels ); if ( result != ASE_OK ) { ASIOExit(); drivers.removeCurrentDriver(); errorStream_ << "RtApiAsio::probeDeviceInfo: error (" << getAsioErrorString( result ) << ") getting channel count (" << info.name << ")."; errorText_ = errorStream_.str(); error( RTAUDIO_WARNING ); return false; } info.outputChannels = outputChannels; info.inputChannels = inputChannels; if ( info.outputChannels > 0 && info.inputChannels > 0 ) info.duplexChannels = (info.outputChannels > info.inputChannels) ? info.inputChannels : info.outputChannels; // Determine the supported sample rates. info.sampleRates.clear(); for ( unsigned int i=0; i info.preferredSampleRate ) ) info.preferredSampleRate = SAMPLE_RATES[i]; } } // Determine supported data types ... just check first channel and assume rest are the same. ASIOChannelInfo channelInfo; channelInfo.channel = 0; channelInfo.isInput = true; if ( info.inputChannels <= 0 ) channelInfo.isInput = false; result = ASIOGetChannelInfo( &channelInfo ); if ( result != ASE_OK ) { ASIOExit(); drivers.removeCurrentDriver(); errorStream_ << "RtApiAsio::probeDeviceInfo: error (" << getAsioErrorString( result ) << ") getting driver channel info (" << info.name << ")."; errorText_ = errorStream_.str(); error( RTAUDIO_WARNING ); return false; } info.nativeFormats = 0; if ( channelInfo.type == ASIOSTInt16MSB || channelInfo.type == ASIOSTInt16LSB ) info.nativeFormats |= RTAUDIO_SINT16; else if ( channelInfo.type == ASIOSTInt32MSB || channelInfo.type == ASIOSTInt32LSB ) info.nativeFormats |= RTAUDIO_SINT32; else if ( channelInfo.type == ASIOSTFloat32MSB || channelInfo.type == ASIOSTFloat32LSB ) info.nativeFormats |= RTAUDIO_FLOAT32; else if ( channelInfo.type == ASIOSTFloat64MSB || channelInfo.type == ASIOSTFloat64LSB ) info.nativeFormats |= RTAUDIO_FLOAT64; else if ( channelInfo.type == ASIOSTInt24MSB || channelInfo.type == ASIOSTInt24LSB ) info.nativeFormats |= RTAUDIO_SINT24; ASIOExit(); drivers.removeCurrentDriver(); return true; } static void bufferSwitch( long index, ASIOBool /*processNow*/ ) { RtApiAsio *object = (RtApiAsio *) asioCallbackInfo->object; object->callbackEvent( index ); } bool RtApiAsio :: probeDeviceOpen( unsigned int deviceId, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, RtAudio::StreamOptions *options ) { bool isDuplexInput = mode == INPUT && stream_.mode == OUTPUT; // For ASIO, a duplex stream MUST use the same driver. if ( isDuplexInput && stream_.deviceId[0] != deviceId ) { errorText_ = "RtApiAsio::probeDeviceOpen: an ASIO duplex stream must use the same device for input and output!"; return FAILURE; } std::string driverName; for ( unsigned int m=0; m(driverName.c_str()) ) ) { errorStream_ << "RtApiAsio::probeDeviceOpen: unable to load driver (" << driverName << ")."; errorText_ = errorStream_.str(); return FAILURE; } result = ASIOInit( &driverInfo ); if ( result != ASE_OK ) { drivers.removeCurrentDriver(); errorStream_ << "RtApiAsio::probeDeviceOpen: error (" << getAsioErrorString( result ) << ") initializing driver (" << driverName << ")."; errorText_ = errorStream_.str(); return FAILURE; } } bool buffersAllocated = false; AsioHandle *handle = (AsioHandle *) stream_.apiHandle; unsigned int nChannels; // Check the device channel count. long inputChannels, outputChannels; result = ASIOGetChannels( &inputChannels, &outputChannels ); if ( result != ASE_OK ) { errorStream_ << "RtApiAsio::probeDeviceOpen: error (" << getAsioErrorString( result ) << ") getting channel count (" << driverName << ")."; errorText_ = errorStream_.str(); goto error; } if ( ( mode == OUTPUT && (channels+firstChannel) > (unsigned int) outputChannels) || ( mode == INPUT && (channels+firstChannel) > (unsigned int) inputChannels) ) { errorStream_ << "RtApiAsio::probeDeviceOpen: driver (" << driverName << ") does not support requested channel count (" << channels << ") + offset (" << firstChannel << ")."; errorText_ = errorStream_.str(); goto error; } stream_.nDeviceChannels[mode] = channels; stream_.nUserChannels[mode] = channels; stream_.channelOffset[mode] = firstChannel; // Verify the sample rate is supported. result = ASIOCanSampleRate( (ASIOSampleRate) sampleRate ); if ( result != ASE_OK ) { errorStream_ << "RtApiAsio::probeDeviceOpen: driver (" << driverName << ") does not support requested sample rate (" << sampleRate << ")."; errorText_ = errorStream_.str(); goto error; } // Get the current sample rate ASIOSampleRate currentRate; result = ASIOGetSampleRate( ¤tRate ); if ( result != ASE_OK ) { errorStream_ << "RtApiAsio::probeDeviceOpen: driver (" << driverName << ") error getting sample rate."; errorText_ = errorStream_.str(); goto error; } // Set the sample rate only if necessary if ( currentRate != sampleRate ) { result = ASIOSetSampleRate( (ASIOSampleRate) sampleRate ); if ( result != ASE_OK ) { errorStream_ << "RtApiAsio::probeDeviceOpen: driver (" << driverName << ") error setting sample rate (" << sampleRate << ")."; errorText_ = errorStream_.str(); goto error; } } // Determine the driver data type. ASIOChannelInfo channelInfo; channelInfo.channel = 0; if ( mode == OUTPUT ) channelInfo.isInput = false; else channelInfo.isInput = true; result = ASIOGetChannelInfo( &channelInfo ); if ( result != ASE_OK ) { errorStream_ << "RtApiAsio::probeDeviceOpen: driver (" << driverName << ") error (" << getAsioErrorString( result ) << ") getting data format."; errorText_ = errorStream_.str(); goto error; } // Assuming WINDOWS host is always little-endian. stream_.doByteSwap[mode] = false; stream_.userFormat = format; stream_.deviceFormat[mode] = 0; if ( channelInfo.type == ASIOSTInt16MSB || channelInfo.type == ASIOSTInt16LSB ) { stream_.deviceFormat[mode] = RTAUDIO_SINT16; if ( channelInfo.type == ASIOSTInt16MSB ) stream_.doByteSwap[mode] = true; } else if ( channelInfo.type == ASIOSTInt32MSB || channelInfo.type == ASIOSTInt32LSB ) { stream_.deviceFormat[mode] = RTAUDIO_SINT32; if ( channelInfo.type == ASIOSTInt32MSB ) stream_.doByteSwap[mode] = true; } else if ( channelInfo.type == ASIOSTFloat32MSB || channelInfo.type == ASIOSTFloat32LSB ) { stream_.deviceFormat[mode] = RTAUDIO_FLOAT32; if ( channelInfo.type == ASIOSTFloat32MSB ) stream_.doByteSwap[mode] = true; } else if ( channelInfo.type == ASIOSTFloat64MSB || channelInfo.type == ASIOSTFloat64LSB ) { stream_.deviceFormat[mode] = RTAUDIO_FLOAT64; if ( channelInfo.type == ASIOSTFloat64MSB ) stream_.doByteSwap[mode] = true; } else if ( channelInfo.type == ASIOSTInt24MSB || channelInfo.type == ASIOSTInt24LSB ) { stream_.deviceFormat[mode] = RTAUDIO_SINT24; if ( channelInfo.type == ASIOSTInt24MSB ) stream_.doByteSwap[mode] = true; } if ( stream_.deviceFormat[mode] == 0 ) { errorStream_ << "RtApiAsio::probeDeviceOpen: driver (" << driverName << ") data format not supported by RtAudio."; errorText_ = errorStream_.str(); goto error; } // Set the buffer size. For a duplex stream, this will end up // setting the buffer size based on the input constraints, which // should be ok. long minSize, maxSize, preferSize, granularity; result = ASIOGetBufferSize( &minSize, &maxSize, &preferSize, &granularity ); if ( result != ASE_OK ) { errorStream_ << "RtApiAsio::probeDeviceOpen: driver (" << driverName << ") error (" << getAsioErrorString( result ) << ") getting buffer size."; errorText_ = errorStream_.str(); goto error; } if ( isDuplexInput ) { // When this is the duplex input (output was opened before), then we have to use the same // buffersize as the output, because it might use the preferred buffer size, which most // likely wasn't passed as input to this. The buffer sizes have to be identically anyway, // So instead of throwing an error, make them equal. The caller uses the reference // to the "bufferSize" param as usual to set up processing buffers. *bufferSize = stream_.bufferSize; } else { if ( *bufferSize == 0 ) *bufferSize = preferSize; else if ( *bufferSize < (unsigned int) minSize ) *bufferSize = (unsigned int) minSize; else if ( *bufferSize > (unsigned int) maxSize ) *bufferSize = (unsigned int) maxSize; else if ( granularity == -1 ) { // Make sure bufferSize is a power of two. int log2_of_min_size = 0; int log2_of_max_size = 0; for ( unsigned int i = 0; i < sizeof(long) * 8; i++ ) { if ( minSize & ((long)1 << i) ) log2_of_min_size = i; if ( maxSize & ((long)1 << i) ) log2_of_max_size = i; } long min_delta = std::abs( (long)*bufferSize - ((long)1 << log2_of_min_size) ); int min_delta_num = log2_of_min_size; for (int i = log2_of_min_size + 1; i <= log2_of_max_size; i++) { long current_delta = std::abs( (long)*bufferSize - ((long)1 << i) ); if (current_delta < min_delta) { min_delta = current_delta; min_delta_num = i; } } *bufferSize = ( (unsigned int)1 << min_delta_num ); if ( *bufferSize < (unsigned int) minSize ) *bufferSize = (unsigned int) minSize; else if ( *bufferSize > (unsigned int) maxSize ) *bufferSize = (unsigned int) maxSize; } else if ( granularity != 0 ) { // Set to an even multiple of granularity, rounding up. *bufferSize = (*bufferSize + granularity-1) / granularity * granularity; } } /* // we don't use it anymore, see above! // Just left it here for the case... if ( isDuplexInput && stream_.bufferSize != *bufferSize ) { errorText_ = "RtApiAsio::probeDeviceOpen: input/output buffersize discrepancy!"; goto error; } */ stream_.bufferSize = *bufferSize; stream_.nBuffers = 2; if ( options && options->flags & RTAUDIO_NONINTERLEAVED ) stream_.userInterleaved = false; else stream_.userInterleaved = true; // ASIO always uses non-interleaved buffers. stream_.deviceInterleaved[mode] = false; // Allocate, if necessary, our AsioHandle structure for the stream. if ( handle == 0 ) { try { handle = new AsioHandle; } catch ( std::bad_alloc& ) { errorText_ = "RtApiAsio::probeDeviceOpen: error allocating AsioHandle memory."; goto error; } handle->bufferInfos = 0; // Create a manual-reset event. handle->condition = CreateEvent( NULL, // no security TRUE, // manual-reset FALSE, // non-signaled initially NULL ); // unnamed stream_.apiHandle = (void *) handle; } // Create the ASIO internal buffers. Since RtAudio sets up input // and output separately, we'll have to dispose of previously // created output buffers for a duplex stream. if ( mode == INPUT && stream_.mode == OUTPUT ) { ASIODisposeBuffers(); if ( handle->bufferInfos ) free( handle->bufferInfos ); } // Allocate, initialize, and save the bufferInfos in our stream callbackInfo structure. unsigned int i; nChannels = stream_.nDeviceChannels[0] + stream_.nDeviceChannels[1]; handle->bufferInfos = (ASIOBufferInfo *) malloc( nChannels * sizeof(ASIOBufferInfo) ); if ( handle->bufferInfos == NULL ) { errorStream_ << "RtApiAsio::probeDeviceOpen: error allocating bufferInfo memory for driver (" << driverName << ")."; errorText_ = errorStream_.str(); goto error; } ASIOBufferInfo *infos; infos = handle->bufferInfos; for ( i=0; iisInput = ASIOFalse; infos->channelNum = i + stream_.channelOffset[0]; infos->buffers[0] = infos->buffers[1] = 0; } for ( i=0; iisInput = ASIOTrue; infos->channelNum = i + stream_.channelOffset[1]; infos->buffers[0] = infos->buffers[1] = 0; } // prepare for callbacks stream_.sampleRate = sampleRate; stream_.deviceId[mode] = deviceId; stream_.mode = isDuplexInput ? DUPLEX : mode; // store this class instance before registering callbacks, that are going to use it asioCallbackInfo = &stream_.callbackInfo; stream_.callbackInfo.object = (void *) this; // Set up the ASIO callback structure and create the ASIO data buffers. asioCallbacks.bufferSwitch = &bufferSwitch; asioCallbacks.sampleRateDidChange = &sampleRateChanged; asioCallbacks.asioMessage = &asioMessages; asioCallbacks.bufferSwitchTimeInfo = NULL; result = ASIOCreateBuffers( handle->bufferInfos, nChannels, stream_.bufferSize, &asioCallbacks ); if ( result != ASE_OK ) { // Standard method failed. This can happen with strict/misbehaving drivers that return valid buffer size ranges // but only accept the preferred buffer size as parameter for ASIOCreateBuffers (e.g. Creative's ASIO driver). // In that case, let's be naĂŻve and try that instead. *bufferSize = preferSize; stream_.bufferSize = *bufferSize; result = ASIOCreateBuffers( handle->bufferInfos, nChannels, stream_.bufferSize, &asioCallbacks ); } if ( result != ASE_OK ) { errorStream_ << "RtApiAsio::probeDeviceOpen: driver (" << driverName << ") error (" << getAsioErrorString( result ) << ") creating buffers."; errorText_ = errorStream_.str(); goto error; } buffersAllocated = true; stream_.state = STREAM_STOPPED; // Set flags for buffer conversion. stream_.doConvertBuffer[mode] = false; if ( stream_.userFormat != stream_.deviceFormat[mode] ) stream_.doConvertBuffer[mode] = true; if ( stream_.userInterleaved != stream_.deviceInterleaved[mode] && stream_.nUserChannels[mode] > 1 ) stream_.doConvertBuffer[mode] = true; // Allocate necessary internal buffers unsigned long bufferBytes; bufferBytes = stream_.nUserChannels[mode] * *bufferSize * formatBytes( stream_.userFormat ); stream_.userBuffer[mode] = (char *) calloc( bufferBytes, 1 ); if ( stream_.userBuffer[mode] == NULL ) { errorText_ = "RtApiAsio::probeDeviceOpen: error allocating user buffer memory."; goto error; } if ( stream_.doConvertBuffer[mode] ) { bool makeBuffer = true; bufferBytes = stream_.nDeviceChannels[mode] * formatBytes( stream_.deviceFormat[mode] ); if ( isDuplexInput && stream_.deviceBuffer ) { unsigned long bytesOut = stream_.nDeviceChannels[0] * formatBytes( stream_.deviceFormat[0] ); if ( bufferBytes <= bytesOut ) makeBuffer = false; } if ( makeBuffer ) { bufferBytes *= *bufferSize; if ( stream_.deviceBuffer ) free( stream_.deviceBuffer ); stream_.deviceBuffer = (char *) calloc( bufferBytes, 1 ); if ( stream_.deviceBuffer == NULL ) { errorText_ = "RtApiAsio::probeDeviceOpen: error allocating device buffer memory."; goto error; } } } // Determine device latencies long inputLatency, outputLatency; result = ASIOGetLatencies( &inputLatency, &outputLatency ); if ( result != ASE_OK ) { errorStream_ << "RtApiAsio::probeDeviceOpen: driver (" << driverName << ") error (" << getAsioErrorString( result ) << ") getting latency."; errorText_ = errorStream_.str(); error( RTAUDIO_WARNING); // warn but don't fail } else { stream_.latency[0] = outputLatency; stream_.latency[1] = inputLatency; } // Setup the buffer conversion information structure. We don't use // buffers to do channel offsets, so we override that parameter // here. if ( stream_.doConvertBuffer[mode] ) setConvertInfo( mode, 0 ); streamOpen = true; return SUCCESS; error: if ( !isDuplexInput ) { // the cleanup for error in the duplex input, is done by RtApi::openStream // So we clean up for single channel only if ( buffersAllocated ) ASIODisposeBuffers(); ASIOExit(); drivers.removeCurrentDriver(); if ( handle ) { CloseHandle( handle->condition ); if ( handle->bufferInfos ) free( handle->bufferInfos ); delete handle; stream_.apiHandle = 0; } if ( stream_.userBuffer[mode] ) { free( stream_.userBuffer[mode] ); stream_.userBuffer[mode] = 0; } if ( stream_.deviceBuffer ) { free( stream_.deviceBuffer ); stream_.deviceBuffer = 0; } } return FAILURE; } void RtApiAsio :: closeStream() { if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiAsio::closeStream(): no open stream to close!"; error( RTAUDIO_WARNING ); return; } if ( stream_.state == STREAM_RUNNING ) { stream_.state = STREAM_STOPPED; ASIOStop(); } stream_.state = STREAM_CLOSED; CallbackInfo *info = (CallbackInfo *) &stream_.callbackInfo; if ( info->deviceDisconnected ) { // This could be either a disconnect or a sample rate change. errorText_ = "RtApiAsio: the streaming device was disconnected or the sample rate changed, closing stream!"; error( RTAUDIO_DEVICE_DISCONNECT ); } ASIODisposeBuffers(); ASIOExit(); drivers.removeCurrentDriver(); AsioHandle *handle = (AsioHandle *) stream_.apiHandle; if ( handle ) { CloseHandle( handle->condition ); if ( handle->bufferInfos ) free( handle->bufferInfos ); delete handle; stream_.apiHandle = 0; } for ( int i=0; i<2; i++ ) { if ( stream_.userBuffer[i] ) { free( stream_.userBuffer[i] ); stream_.userBuffer[i] = 0; } } if ( stream_.deviceBuffer ) { free( stream_.deviceBuffer ); stream_.deviceBuffer = 0; } clearStreamInfo(); streamOpen = false; //stream_.mode = UNINITIALIZED; //stream_.state = STREAM_CLOSED; } RtAudioErrorType RtApiAsio :: startStream() { if ( stream_.state != STREAM_STOPPED ) { if ( stream_.state == STREAM_RUNNING ) errorText_ = "RtApiAsio::startStream(): the stream is already running!"; else if ( stream_.state == STREAM_STOPPING || stream_.state == STREAM_CLOSED ) errorText_ = "RtApiAsio::startStream(): the stream is stopping or closed!"; return error( RTAUDIO_WARNING ); } /* #if defined( HAVE_GETTIMEOFDAY ) gettimeofday( &stream_.lastTickTimestamp, NULL ); #endif */ AsioHandle *handle = (AsioHandle *) stream_.apiHandle; ASIOError result = ASIOStart(); if ( result != ASE_OK ) { errorStream_ << "RtApiAsio::startStream: error (" << getAsioErrorString( result ) << ") starting device."; errorText_ = errorStream_.str(); goto unlock; } handle->drainCounter = 0; handle->internalDrain = false; ResetEvent( handle->condition ); stream_.state = STREAM_RUNNING; asioXRun = false; unlock: if ( result == ASE_OK ) return RTAUDIO_NO_ERROR; return error( RTAUDIO_SYSTEM_ERROR ); } RtAudioErrorType RtApiAsio :: stopStream() { if ( stream_.state != STREAM_RUNNING && stream_.state != STREAM_STOPPING ) { if ( stream_.state == STREAM_STOPPED ) errorText_ = "RtApiAsio::stopStream(): the stream is already stopped!"; else if ( stream_.state == STREAM_CLOSED ) errorText_ = "RtApiAsio::stopStream(): the stream is closed!"; return error( RTAUDIO_WARNING ); } AsioHandle *handle = (AsioHandle *) stream_.apiHandle; if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { if ( handle->drainCounter == 0 ) { handle->drainCounter = 2; WaitForSingleObject( handle->condition, INFINITE ); // block until signaled } } stream_.state = STREAM_STOPPED; ASIOError result = ASIOStop(); if ( result != ASE_OK ) { errorStream_ << "RtApiAsio::stopStream: error (" << getAsioErrorString( result ) << ") stopping device."; errorText_ = errorStream_.str(); } if ( result == ASE_OK ) return RTAUDIO_NO_ERROR; return error( RTAUDIO_SYSTEM_ERROR ); } RtAudioErrorType RtApiAsio :: abortStream() { if ( stream_.state != STREAM_RUNNING ) { if ( stream_.state == STREAM_STOPPED ) errorText_ = "RtApiAsio::abortStream(): the stream is already stopped!"; else if ( stream_.state == STREAM_STOPPING || stream_.state == STREAM_CLOSED ) errorText_ = "RtApiAsio::abortStream(): the stream is stopping or closed!"; return error( RTAUDIO_WARNING ); } // The following lines were commented-out because some behavior was // noted where the device buffers need to be zeroed to avoid // continuing sound, even when the device buffers are completely // disposed. So now, calling abort is the same as calling stop. // AsioHandle *handle = (AsioHandle *) stream_.apiHandle; // handle->drainCounter = 2; stopStream(); return RTAUDIO_NO_ERROR; } // This function will be called by a spawned thread when: 1. The user // callback function signals that the stream should be stopped or // aborted; or 2. When a signal is received indicating that the device // sample rate has changed or it has been disconnected. It is // necessary to handle it this way because the callbackEvent() or // signaling function must return before the ASIOStop() function will // return (or the driver can be removed). static unsigned __stdcall asioStopStream( void *ptr ) { CallbackInfo *info = (CallbackInfo *) ptr; RtApiAsio *object = (RtApiAsio *) info->object; if ( info->deviceDisconnected == false ) object->stopStream(); // drain the stream else object->closeStream(); // disconnect or sample rate change ... close the stream _endthreadex( 0 ); return 0; } bool RtApiAsio :: callbackEvent( long bufferIndex ) { if ( stream_.state == STREAM_STOPPED || stream_.state == STREAM_STOPPING ) return SUCCESS; if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiAsio::callbackEvent(): the stream is closed ... this shouldn't happen!"; error( RTAUDIO_WARNING ); return FAILURE; } CallbackInfo *info = (CallbackInfo *) &stream_.callbackInfo; AsioHandle *handle = (AsioHandle *) stream_.apiHandle; // Check if we were draining the stream and signal if finished. if ( handle->drainCounter > 3 ) { stream_.state = STREAM_STOPPING; if ( handle->internalDrain == false ) SetEvent( handle->condition ); else { // spawn a thread to stop the stream unsigned threadId; stream_.callbackInfo.thread = _beginthreadex( NULL, 0, &asioStopStream, &stream_.callbackInfo, 0, &threadId ); } return SUCCESS; } // Invoke user callback to get fresh output data UNLESS we are // draining stream. if ( handle->drainCounter == 0 ) { RtAudioCallback callback = (RtAudioCallback) info->callback; double streamTime = getStreamTime(); RtAudioStreamStatus status = 0; if ( stream_.mode != INPUT && asioXRun == true ) { status |= RTAUDIO_OUTPUT_UNDERFLOW; asioXRun = false; } if ( stream_.mode != OUTPUT && asioXRun == true ) { status |= RTAUDIO_INPUT_OVERFLOW; asioXRun = false; } int cbReturnValue = callback( stream_.userBuffer[0], stream_.userBuffer[1], stream_.bufferSize, streamTime, status, info->userData ); if ( cbReturnValue == 2 ) { stream_.state = STREAM_STOPPING; handle->drainCounter = 2; unsigned threadId; stream_.callbackInfo.thread = _beginthreadex( NULL, 0, &asioStopStream, &stream_.callbackInfo, 0, &threadId ); return SUCCESS; } else if ( cbReturnValue == 1 ) { handle->drainCounter = 1; handle->internalDrain = true; } } unsigned int nChannels, bufferBytes, i, j; nChannels = stream_.nDeviceChannels[0] + stream_.nDeviceChannels[1]; if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { bufferBytes = stream_.bufferSize * formatBytes( stream_.deviceFormat[0] ); if ( handle->drainCounter > 1 ) { // write zeros to the output stream for ( i=0, j=0; ibufferInfos[i].isInput != ASIOTrue ) memset( handle->bufferInfos[i].buffers[bufferIndex], 0, bufferBytes ); } } else if ( stream_.doConvertBuffer[0] ) { convertBuffer( stream_.deviceBuffer, stream_.userBuffer[0], stream_.convertInfo[0] ); if ( stream_.doByteSwap[0] ) byteSwapBuffer( stream_.deviceBuffer, stream_.bufferSize * stream_.nDeviceChannels[0], stream_.deviceFormat[0] ); for ( i=0, j=0; ibufferInfos[i].isInput != ASIOTrue ) memcpy( handle->bufferInfos[i].buffers[bufferIndex], &stream_.deviceBuffer[j++*bufferBytes], bufferBytes ); } } else { if ( stream_.doByteSwap[0] ) byteSwapBuffer( stream_.userBuffer[0], stream_.bufferSize * stream_.nUserChannels[0], stream_.userFormat ); for ( i=0, j=0; ibufferInfos[i].isInput != ASIOTrue ) memcpy( handle->bufferInfos[i].buffers[bufferIndex], &stream_.userBuffer[0][bufferBytes*j++], bufferBytes ); } } } // Don't bother draining input if ( handle->drainCounter ) { handle->drainCounter++; goto unlock; } if ( stream_.mode == INPUT || stream_.mode == DUPLEX ) { bufferBytes = stream_.bufferSize * formatBytes(stream_.deviceFormat[1]); if (stream_.doConvertBuffer[1]) { // Always interleave ASIO input data. for ( i=0, j=0; ibufferInfos[i].isInput == ASIOTrue ) memcpy( &stream_.deviceBuffer[j++*bufferBytes], handle->bufferInfos[i].buffers[bufferIndex], bufferBytes ); } if ( stream_.doByteSwap[1] ) byteSwapBuffer( stream_.deviceBuffer, stream_.bufferSize * stream_.nDeviceChannels[1], stream_.deviceFormat[1] ); convertBuffer( stream_.userBuffer[1], stream_.deviceBuffer, stream_.convertInfo[1] ); } else { for ( i=0, j=0; ibufferInfos[i].isInput == ASIOTrue ) { memcpy( &stream_.userBuffer[1][bufferBytes*j++], handle->bufferInfos[i].buffers[bufferIndex], bufferBytes ); } } if ( stream_.doByteSwap[1] ) byteSwapBuffer( stream_.userBuffer[1], stream_.bufferSize * stream_.nUserChannels[1], stream_.userFormat ); } } unlock: // The following call was suggested by Malte Clasen. While the API // documentation indicates it should not be required, some device // drivers apparently do not function correctly without it. ASIOOutputReady(); RtApi::tickStreamTime(); return SUCCESS; } static void sampleRateChanged( ASIOSampleRate sRate ) { // The ASIO documentation says that this usually only happens during // external sync. Audio processing is not stopped by the driver, // actual sample rate might not have even changed, maybe only the // sample rate status of an AES/EBU or S/PDIF digital input at the // audio device. RtApi *object = (RtApi *) asioCallbackInfo->object; if ( object->getStreamSampleRate() != sRate ) { asioCallbackInfo->deviceDisconnected = true; // flag for either rate change or disconnect unsigned threadId; asioCallbackInfo->thread = _beginthreadex( NULL, 0, &asioStopStream, asioCallbackInfo, 0, &threadId ); } } static long asioMessages( long selector, long value, void* /*message*/, double* /*opt*/ ) { long ret = 0; switch( selector ) { case kAsioSelectorSupported: if ( value == kAsioResetRequest || value == kAsioEngineVersion || value == kAsioResyncRequest || value == kAsioLatenciesChanged // The following three were added for ASIO 2.0, you don't // necessarily have to support them. || value == kAsioSupportsTimeInfo || value == kAsioSupportsTimeCode || value == kAsioSupportsInputMonitor) ret = 1L; break; case kAsioResetRequest: // This message is received when a device is disconnected (and // perhaps when the sample rate changes). It indicates that the // driver should be reset, which is accomplished by calling // ASIOStop(), ASIODisposeBuffers() and removing the driver. But // since this message comes from the driver, we need to let this // function return before attempting to close the stream and // remove the driver. Thus, we invoke a thread to initiate the // stream closing. asioCallbackInfo->deviceDisconnected = true; // flag for either rate change or disconnect unsigned threadId; asioCallbackInfo->thread = _beginthreadex( NULL, 0, &asioStopStream, asioCallbackInfo, 0, &threadId ); //std::cerr << "\nRtApiAsio: driver reset requested!!!" << std::endl; ret = 1L; break; case kAsioResyncRequest: // This informs the application that the driver encountered some // non-fatal data loss. It is used for synchronization purposes // of different media. Added mainly to work around the Win16Mutex // problems in Windows 95/98 with the Windows Multimedia system, // which could lose data because the Mutex was held too long by // another thread. However a driver can issue it in other // situations, too. // std::cerr << "\nRtApiAsio: driver resync requested!!!" << std::endl; asioXRun = true; ret = 1L; break; case kAsioLatenciesChanged: // This will inform the host application that the drivers were // latencies changed. Beware, it this does not mean that the // buffer sizes have changed! You might need to update internal // delay data. std::cerr << "\nRtApiAsio: driver latency may have changed!!!" << std::endl; ret = 1L; break; case kAsioEngineVersion: // Return the supported ASIO version of the host application. If // a host application does not implement this selector, ASIO 1.0 // is assumed by the driver. ret = 2L; break; case kAsioSupportsTimeInfo: // Informs the driver whether the // asioCallbacks.bufferSwitchTimeInfo() callback is supported. // For compatibility with ASIO 1.0 drivers the host application // should always support the "old" bufferSwitch method, too. ret = 0; break; case kAsioSupportsTimeCode: // Informs the driver whether application is interested in time // code info. If an application does not need to know about time // code, the driver has less work to do. ret = 0; break; } return ret; } static const char* getAsioErrorString( ASIOError result ) { struct Messages { ASIOError value; const char*message; }; static const Messages m[] = { { ASE_NotPresent, "Hardware input or output is not present or available." }, { ASE_HWMalfunction, "Hardware is malfunctioning." }, { ASE_InvalidParameter, "Invalid input parameter." }, { ASE_InvalidMode, "Invalid mode." }, { ASE_SPNotAdvancing, "Sample position not advancing." }, { ASE_NoClock, "Sample clock or rate cannot be determined or is not present." }, { ASE_NoMemory, "Not enough memory to complete the request." } }; for ( unsigned int i = 0; i < sizeof(m)/sizeof(m[0]); ++i ) if ( m[i].value == result ) return m[i].message; return "Unknown error."; } //******************** End of __WINDOWS_ASIO__ *********************// #endif #if defined(__WINDOWS_WASAPI__) // Windows WASAPI API // Authored by Marcus Tomlinson , April 2014 // Updates for new device selection scheme by Gary Scavone, January 2022 // - Introduces support for the Windows WASAPI API // - Aims to deliver bit streams to and from hardware at the lowest possible latency, via the absolute minimum buffer sizes required // - Provides flexible stream configuration to an otherwise strict and inflexible WASAPI interface // - Includes automatic internal conversion of sample rate and buffer size between hardware and the user #ifndef INITGUID #define INITGUID #endif #include #include #include #include #include #include #include #include #include #ifndef MF_E_TRANSFORM_NEED_MORE_INPUT #define MF_E_TRANSFORM_NEED_MORE_INPUT _HRESULT_TYPEDEF_(0xc00d6d72) #endif #ifndef MFSTARTUP_NOSOCKET #define MFSTARTUP_NOSOCKET 0x1 #endif #ifdef _MSC_VER #pragma comment( lib, "ksuser" ) #pragma comment( lib, "mfplat.lib" ) #pragma comment( lib, "mfuuid.lib" ) #pragma comment( lib, "wmcodecdspuuid" ) #endif //============================================================================= #define SAFE_RELEASE( objectPtr )\ if ( objectPtr )\ {\ objectPtr->Release();\ objectPtr = NULL;\ } typedef HANDLE ( __stdcall *TAvSetMmThreadCharacteristicsPtr )( LPCWSTR TaskName, LPDWORD TaskIndex ); #ifndef __IAudioClient3_INTERFACE_DEFINED__ MIDL_INTERFACE( "00000000-0000-0000-0000-000000000000" ) IAudioClient3 { virtual HRESULT GetSharedModeEnginePeriod( WAVEFORMATEX*, UINT32*, UINT32*, UINT32*, UINT32* ) = 0; virtual HRESULT InitializeSharedAudioStream( DWORD, UINT32, WAVEFORMATEX*, LPCGUID ) = 0; virtual HRESULT Release() = 0; }; #ifdef __CRT_UUID_DECL __CRT_UUID_DECL( IAudioClient3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ) #endif #endif //----------------------------------------------------------------------------- // WASAPI dictates stream sample rate, format, channel count, and in some cases, buffer size. // Therefore we must perform all necessary conversions to user buffers in order to satisfy these // requirements. WasapiBuffer ring buffers are used between HwIn->UserIn and UserOut->HwOut to // provide intermediate storage for read / write synchronization. class WasapiBuffer { public: WasapiBuffer() : buffer_( NULL ), bufferSize_( 0 ), inIndex_( 0 ), outIndex_( 0 ) {} ~WasapiBuffer() { free( buffer_ ); } // sets the length of the internal ring buffer void setBufferSize( unsigned int bufferSize, unsigned int formatBytes ) { free( buffer_ ); buffer_ = ( char* ) calloc( bufferSize, formatBytes ); bufferSize_ = bufferSize; inIndex_ = 0; outIndex_ = 0; } // attempt to push a buffer into the ring buffer at the current "in" index bool pushBuffer( char* buffer, unsigned int bufferSize, RtAudioFormat format ) { if ( !buffer || // incoming buffer is NULL bufferSize == 0 || // incoming buffer has no data bufferSize > bufferSize_ ) // incoming buffer too large { return false; } unsigned int relOutIndex = outIndex_; unsigned int inIndexEnd = inIndex_ + bufferSize; if ( relOutIndex < inIndex_ && inIndexEnd >= bufferSize_ ) { relOutIndex += bufferSize_; } // the "IN" index CAN BEGIN at the "OUT" index // the "IN" index CANNOT END at the "OUT" index if ( inIndex_ < relOutIndex && inIndexEnd >= relOutIndex ) { return false; // not enough space between "in" index and "out" index } // copy buffer from external to internal int fromZeroSize = inIndex_ + bufferSize - bufferSize_; fromZeroSize = fromZeroSize < 0 ? 0 : fromZeroSize; int fromInSize = bufferSize - fromZeroSize; switch( format ) { case RTAUDIO_SINT8: memcpy( &( ( char* ) buffer_ )[inIndex_], buffer, fromInSize * sizeof( char ) ); memcpy( buffer_, &( ( char* ) buffer )[fromInSize], fromZeroSize * sizeof( char ) ); break; case RTAUDIO_SINT16: memcpy( &( ( short* ) buffer_ )[inIndex_], buffer, fromInSize * sizeof( short ) ); memcpy( buffer_, &( ( short* ) buffer )[fromInSize], fromZeroSize * sizeof( short ) ); break; case RTAUDIO_SINT24: memcpy( &( ( S24* ) buffer_ )[inIndex_], buffer, fromInSize * sizeof( S24 ) ); memcpy( buffer_, &( ( S24* ) buffer )[fromInSize], fromZeroSize * sizeof( S24 ) ); break; case RTAUDIO_SINT32: memcpy( &( ( int* ) buffer_ )[inIndex_], buffer, fromInSize * sizeof( int ) ); memcpy( buffer_, &( ( int* ) buffer )[fromInSize], fromZeroSize * sizeof( int ) ); break; case RTAUDIO_FLOAT32: memcpy( &( ( float* ) buffer_ )[inIndex_], buffer, fromInSize * sizeof( float ) ); memcpy( buffer_, &( ( float* ) buffer )[fromInSize], fromZeroSize * sizeof( float ) ); break; case RTAUDIO_FLOAT64: memcpy( &( ( double* ) buffer_ )[inIndex_], buffer, fromInSize * sizeof( double ) ); memcpy( buffer_, &( ( double* ) buffer )[fromInSize], fromZeroSize * sizeof( double ) ); break; } // update "in" index inIndex_ += bufferSize; inIndex_ %= bufferSize_; return true; } // attempt to pull a buffer from the ring buffer from the current "out" index bool pullBuffer( char* buffer, unsigned int bufferSize, RtAudioFormat format ) { if ( !buffer || // incoming buffer is NULL bufferSize == 0 || // incoming buffer has no data bufferSize > bufferSize_ ) // incoming buffer too large { return false; } unsigned int relInIndex = inIndex_; unsigned int outIndexEnd = outIndex_ + bufferSize; if ( relInIndex < outIndex_ && outIndexEnd >= bufferSize_ ) { relInIndex += bufferSize_; } // the "OUT" index CANNOT BEGIN at the "IN" index // the "OUT" index CAN END at the "IN" index if ( outIndex_ <= relInIndex && outIndexEnd > relInIndex ) { return false; // not enough space between "out" index and "in" index } // copy buffer from internal to external int fromZeroSize = outIndex_ + bufferSize - bufferSize_; fromZeroSize = fromZeroSize < 0 ? 0 : fromZeroSize; int fromOutSize = bufferSize - fromZeroSize; switch( format ) { case RTAUDIO_SINT8: memcpy( buffer, &( ( char* ) buffer_ )[outIndex_], fromOutSize * sizeof( char ) ); memcpy( &( ( char* ) buffer )[fromOutSize], buffer_, fromZeroSize * sizeof( char ) ); break; case RTAUDIO_SINT16: memcpy( buffer, &( ( short* ) buffer_ )[outIndex_], fromOutSize * sizeof( short ) ); memcpy( &( ( short* ) buffer )[fromOutSize], buffer_, fromZeroSize * sizeof( short ) ); break; case RTAUDIO_SINT24: memcpy( buffer, &( ( S24* ) buffer_ )[outIndex_], fromOutSize * sizeof( S24 ) ); memcpy( &( ( S24* ) buffer )[fromOutSize], buffer_, fromZeroSize * sizeof( S24 ) ); break; case RTAUDIO_SINT32: memcpy( buffer, &( ( int* ) buffer_ )[outIndex_], fromOutSize * sizeof( int ) ); memcpy( &( ( int* ) buffer )[fromOutSize], buffer_, fromZeroSize * sizeof( int ) ); break; case RTAUDIO_FLOAT32: memcpy( buffer, &( ( float* ) buffer_ )[outIndex_], fromOutSize * sizeof( float ) ); memcpy( &( ( float* ) buffer )[fromOutSize], buffer_, fromZeroSize * sizeof( float ) ); break; case RTAUDIO_FLOAT64: memcpy( buffer, &( ( double* ) buffer_ )[outIndex_], fromOutSize * sizeof( double ) ); memcpy( &( ( double* ) buffer )[fromOutSize], buffer_, fromZeroSize * sizeof( double ) ); break; } // update "out" index outIndex_ += bufferSize; outIndex_ %= bufferSize_; return true; } private: char* buffer_; unsigned int bufferSize_; unsigned int inIndex_; unsigned int outIndex_; }; //----------------------------------------------------------------------------- // In order to satisfy WASAPI's buffer requirements, we need a means of converting sample rate // between HW and the user. The WasapiResampler class is used to perform this conversion between // HwIn->UserIn and UserOut->HwOut during the stream callback loop. class WasapiResampler { public: WasapiResampler( bool isFloat, unsigned int bitsPerSample, unsigned int channelCount, unsigned int inSampleRate, unsigned int outSampleRate ) : _bytesPerSample( bitsPerSample / 8 ) , _channelCount( channelCount ) , _sampleRatio( ( float ) outSampleRate / inSampleRate ) , _transformUnk( NULL ) , _transform( NULL ) , _mediaType( NULL ) , _inputMediaType( NULL ) , _outputMediaType( NULL ) #ifdef __IWMResamplerProps_FWD_DEFINED__ , _resamplerProps( NULL ) #endif { // 1. Initialization MFStartup( MF_VERSION, MFSTARTUP_NOSOCKET ); // 2. Create Resampler Transform Object CoCreateInstance( CLSID_CResamplerMediaObject, NULL, CLSCTX_INPROC_SERVER, IID_IUnknown, ( void** ) &_transformUnk ); _transformUnk->QueryInterface( IID_PPV_ARGS( &_transform ) ); #ifdef __IWMResamplerProps_FWD_DEFINED__ _transformUnk->QueryInterface( IID_PPV_ARGS( &_resamplerProps ) ); _resamplerProps->SetHalfFilterLength( 60 ); // best conversion quality #endif // 3. Specify input / output format MFCreateMediaType( &_mediaType ); _mediaType->SetGUID( MF_MT_MAJOR_TYPE, MFMediaType_Audio ); _mediaType->SetGUID( MF_MT_SUBTYPE, isFloat ? MFAudioFormat_Float : MFAudioFormat_PCM ); _mediaType->SetUINT32( MF_MT_AUDIO_NUM_CHANNELS, channelCount ); _mediaType->SetUINT32( MF_MT_AUDIO_SAMPLES_PER_SECOND, inSampleRate ); _mediaType->SetUINT32( MF_MT_AUDIO_BLOCK_ALIGNMENT, _bytesPerSample * channelCount ); _mediaType->SetUINT32( MF_MT_AUDIO_AVG_BYTES_PER_SECOND, _bytesPerSample * channelCount * inSampleRate ); _mediaType->SetUINT32( MF_MT_AUDIO_BITS_PER_SAMPLE, bitsPerSample ); _mediaType->SetUINT32( MF_MT_ALL_SAMPLES_INDEPENDENT, TRUE ); MFCreateMediaType( &_inputMediaType ); _mediaType->CopyAllItems( _inputMediaType ); _transform->SetInputType( 0, _inputMediaType, 0 ); MFCreateMediaType( &_outputMediaType ); _mediaType->CopyAllItems( _outputMediaType ); _outputMediaType->SetUINT32( MF_MT_AUDIO_SAMPLES_PER_SECOND, outSampleRate ); _outputMediaType->SetUINT32( MF_MT_AUDIO_AVG_BYTES_PER_SECOND, _bytesPerSample * channelCount * outSampleRate ); _transform->SetOutputType( 0, _outputMediaType, 0 ); // 4. Send stream start messages to Resampler _transform->ProcessMessage( MFT_MESSAGE_COMMAND_FLUSH, 0 ); _transform->ProcessMessage( MFT_MESSAGE_NOTIFY_BEGIN_STREAMING, 0 ); _transform->ProcessMessage( MFT_MESSAGE_NOTIFY_START_OF_STREAM, 0 ); } ~WasapiResampler() { // 8. Send stream stop messages to Resampler _transform->ProcessMessage( MFT_MESSAGE_NOTIFY_END_OF_STREAM, 0 ); _transform->ProcessMessage( MFT_MESSAGE_NOTIFY_END_STREAMING, 0 ); // 9. Cleanup MFShutdown(); SAFE_RELEASE( _transformUnk ); SAFE_RELEASE( _transform ); SAFE_RELEASE( _mediaType ); SAFE_RELEASE( _inputMediaType ); SAFE_RELEASE( _outputMediaType ); #ifdef __IWMResamplerProps_FWD_DEFINED__ SAFE_RELEASE( _resamplerProps ); #endif } void Convert( char* outBuffer, const char* inBuffer, unsigned int inSampleCount, unsigned int& outSampleCount, int maxOutSampleCount = -1 ) { unsigned int inputBufferSize = _bytesPerSample * _channelCount * inSampleCount; if ( _sampleRatio == 1 ) { // no sample rate conversion required memcpy( outBuffer, inBuffer, inputBufferSize ); outSampleCount = inSampleCount; return; } unsigned int outputBufferSize = 0; if ( maxOutSampleCount != -1 ) { outputBufferSize = _bytesPerSample * _channelCount * maxOutSampleCount; } else { outputBufferSize = ( unsigned int ) ceilf( inputBufferSize * _sampleRatio ) + ( _bytesPerSample * _channelCount ); } IMFMediaBuffer* rInBuffer; IMFSample* rInSample; BYTE* rInByteBuffer = NULL; // 5. Create Sample object from input data MFCreateMemoryBuffer( inputBufferSize, &rInBuffer ); rInBuffer->Lock( &rInByteBuffer, NULL, NULL ); memcpy( rInByteBuffer, inBuffer, inputBufferSize ); rInBuffer->Unlock(); rInByteBuffer = NULL; rInBuffer->SetCurrentLength( inputBufferSize ); MFCreateSample( &rInSample ); rInSample->AddBuffer( rInBuffer ); // 6. Pass input data to Resampler _transform->ProcessInput( 0, rInSample, 0 ); SAFE_RELEASE( rInBuffer ); SAFE_RELEASE( rInSample ); // 7. Perform sample rate conversion IMFMediaBuffer* rOutBuffer = NULL; BYTE* rOutByteBuffer = NULL; MFT_OUTPUT_DATA_BUFFER rOutDataBuffer; DWORD rStatus; DWORD rBytes = outputBufferSize; // maximum bytes accepted per ProcessOutput // 7.1 Create Sample object for output data memset( &rOutDataBuffer, 0, sizeof rOutDataBuffer ); MFCreateSample( &( rOutDataBuffer.pSample ) ); MFCreateMemoryBuffer( rBytes, &rOutBuffer ); rOutDataBuffer.pSample->AddBuffer( rOutBuffer ); rOutDataBuffer.dwStreamID = 0; rOutDataBuffer.dwStatus = 0; rOutDataBuffer.pEvents = NULL; // 7.2 Get output data from Resampler if ( _transform->ProcessOutput( 0, 1, &rOutDataBuffer, &rStatus ) == MF_E_TRANSFORM_NEED_MORE_INPUT ) { outSampleCount = 0; SAFE_RELEASE( rOutBuffer ); SAFE_RELEASE( rOutDataBuffer.pSample ); return; } // 7.3 Write output data to outBuffer SAFE_RELEASE( rOutBuffer ); rOutDataBuffer.pSample->ConvertToContiguousBuffer( &rOutBuffer ); rOutBuffer->GetCurrentLength( &rBytes ); rOutBuffer->Lock( &rOutByteBuffer, NULL, NULL ); memcpy( outBuffer, rOutByteBuffer, rBytes ); rOutBuffer->Unlock(); rOutByteBuffer = NULL; outSampleCount = rBytes / _bytesPerSample / _channelCount; SAFE_RELEASE( rOutBuffer ); SAFE_RELEASE( rOutDataBuffer.pSample ); } private: unsigned int _bytesPerSample; unsigned int _channelCount; float _sampleRatio; IUnknown* _transformUnk; IMFTransform* _transform; IMFMediaType* _mediaType; IMFMediaType* _inputMediaType; IMFMediaType* _outputMediaType; #ifdef __IWMResamplerProps_FWD_DEFINED__ IWMResamplerProps* _resamplerProps; #endif }; //----------------------------------------------------------------------------- // A structure to hold various information related to the WASAPI implementation. struct WasapiHandle { IAudioClient* captureAudioClient; IAudioClient* renderAudioClient; IAudioCaptureClient* captureClient; IAudioRenderClient* renderClient; HANDLE captureEvent; HANDLE renderEvent; WasapiHandle() : captureAudioClient( NULL ), renderAudioClient( NULL ), captureClient( NULL ), renderClient( NULL ), captureEvent( NULL ), renderEvent( NULL ) {} }; //----------------------------------------------------------------------------- RtApiWasapi::RtApiWasapi() : coInitialized_( false ), deviceEnumerator_( NULL ) { // WASAPI can run either apartment or multi-threaded HRESULT hr = CoInitialize( NULL ); if ( !FAILED( hr ) ) coInitialized_ = true; // Instantiate device enumerator hr = CoCreateInstance( __uuidof( MMDeviceEnumerator ), NULL, CLSCTX_ALL, __uuidof( IMMDeviceEnumerator ), ( void** ) &deviceEnumerator_ ); // If this runs on an old Windows, it will fail. Ignore and proceed. if ( FAILED( hr ) ) deviceEnumerator_ = NULL; } //----------------------------------------------------------------------------- RtApiWasapi::~RtApiWasapi() { MUTEX_LOCK( &stream_.mutex ); if ( stream_.state != STREAM_CLOSED ) { MUTEX_UNLOCK( &stream_.mutex ); closeStream(); MUTEX_LOCK( &stream_.mutex ); } SAFE_RELEASE( deviceEnumerator_ ); // If this object previously called CoInitialize() if ( coInitialized_ ) CoUninitialize(); MUTEX_UNLOCK( &stream_.mutex ); } //----------------------------------------------------------------------------- unsigned int RtApiWasapi::getDefaultInputDevice( void ) { IMMDevice* devicePtr = NULL; LPWSTR defaultId = NULL; std::string id; if ( !deviceEnumerator_ ) return 0; // invalid ID errorText_.clear(); // Get the default capture device Id. HRESULT hr = deviceEnumerator_->GetDefaultAudioEndpoint( eCapture, eConsole, &devicePtr ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::getDefaultInputDevice: Unable to retrieve default capture device handle."; goto Release; } hr = devicePtr->GetId( &defaultId ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::getDefaultInputDevice: Unable to get default capture device Id."; goto Release; } id = convertCharPointerToStdString( defaultId ); Release: SAFE_RELEASE( devicePtr ); CoTaskMemFree( defaultId ); if ( !errorText_.empty() ) { error( RTAUDIO_DRIVER_ERROR ); return 0; } for ( unsigned int m=0; mGetDefaultAudioEndpoint( eRender, eConsole, &devicePtr ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::getDefaultOutputDevice: Unable to retrieve default render device handle."; goto Release; } hr = devicePtr->GetId( &defaultId ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::getDefaultOutputDevice: Unable to get default render device Id."; goto Release; } id = convertCharPointerToStdString( defaultId ); Release: SAFE_RELEASE( devicePtr ); CoTaskMemFree( defaultId ); if ( !errorText_.empty() ) { error( RTAUDIO_DRIVER_ERROR ); return 0; } for ( unsigned int m=0; m > ids; LPWSTR deviceId = NULL; if ( !deviceEnumerator_ ) return; errorText_.clear(); // Count capture devices HRESULT hr = deviceEnumerator_->EnumAudioEndpoints( eCapture, DEVICE_STATE_ACTIVE, &captureDevices ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::probeDevices: Unable to retrieve capture device collection."; goto Exit; } hr = captureDevices->GetCount( &captureDeviceCount ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::probeDevices: Unable to retrieve capture device count."; goto Exit; } // Count render devices hr = deviceEnumerator_->EnumAudioEndpoints( eRender, DEVICE_STATE_ACTIVE, &renderDevices ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::probeDevices: Unable to retrieve render device collection."; goto Exit; } hr = renderDevices->GetCount( &renderDeviceCount ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::probeDevices: Unable to retrieve render device count."; goto Exit; } nDevices = captureDeviceCount + renderDeviceCount; if ( nDevices == 0 ) { errorText_ = "RtApiWasapi::probeDevices: No devices found."; goto Exit; } // Get the default capture device Id. hr = deviceEnumerator_->GetDefaultAudioEndpoint( eCapture, eConsole, &devicePtr ); if ( SUCCEEDED( hr) ) { hr = devicePtr->GetId( &defaultCaptureId ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::probeDevices: Unable to get default capture device Id."; goto Exit; } defaultCaptureString = convertCharPointerToStdString( defaultCaptureId ); } // Get the default render device Id. SAFE_RELEASE( devicePtr ); hr = deviceEnumerator_->GetDefaultAudioEndpoint( eRender, eConsole, &devicePtr ); if ( SUCCEEDED( hr) ) { hr = devicePtr->GetId( &defaultRenderId ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::probeDevices: Unable to get default render device Id."; goto Exit; } defaultRenderString = convertCharPointerToStdString( defaultRenderId ); } // Collect device IDs with mode. for ( unsigned int n=0; nItem( n, &devicePtr ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::probeDevices: Unable to retrieve render device handle."; error( RTAUDIO_WARNING ); continue; } } else { hr = captureDevices->Item( n - renderDeviceCount, &devicePtr ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::probeDevices: Unable to retrieve capture device handle."; error( RTAUDIO_WARNING ); continue; } isCaptureDevice = true; } hr = devicePtr->GetId( &deviceId ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::probeDevices: Unable to get device Id."; error( RTAUDIO_WARNING ); continue; } ids.push_back( std::pair< std::string, bool>(convertCharPointerToStdString(deviceId), isCaptureDevice) ); CoTaskMemFree( deviceId ); } // Fill or update the deviceList_ and also save a corresponding list of Ids. for ( unsigned int n=0; n >::iterator it=deviceIds_.begin(); it!=deviceIds_.end(); ) { for ( m=0; mGetDevice( deviceId, &devicePtr ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::probeDeviceInfo: Unable to retrieve device handle."; goto Exit; } // Get device name hr = devicePtr->OpenPropertyStore( STGM_READ, &devicePropStore ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::probeDeviceInfo: Unable to open device property store."; goto Exit; } PropVariantInit( &deviceNameProp ); hr = devicePropStore->GetValue( PKEY_Device_FriendlyName, &deviceNameProp ); if ( FAILED( hr ) || deviceNameProp.pwszVal == nullptr ) { errorText_ = "RtApiWasapi::probeDeviceInfo: Unable to retrieve device property: PKEY_Device_FriendlyName."; goto Exit; } info.name = convertCharPointerToStdString( deviceNameProp.pwszVal ); // Get audio client hr = devicePtr->Activate( __uuidof( IAudioClient ), CLSCTX_ALL, NULL, ( void** ) &audioClient ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::probeDeviceInfo: Unable to retrieve device audio client."; goto Exit; } hr = audioClient->GetMixFormat( &deviceFormat ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::probeDeviceInfo: Unable to retrieve device mix format."; goto Exit; } // Set channel count if ( isCaptureDevice ) { info.inputChannels = deviceFormat->nChannels; info.outputChannels = 0; info.duplexChannels = 0; } else { info.inputChannels = 0; info.outputChannels = deviceFormat->nChannels; info.duplexChannels = 0; } // Set sample rates info.sampleRates.clear(); // Allow support for all sample rates as we have a built-in sample rate converter. for ( unsigned int i = 0; i < MAX_SAMPLE_RATES; i++ ) { info.sampleRates.push_back( SAMPLE_RATES[i] ); } info.preferredSampleRate = deviceFormat->nSamplesPerSec; // Set native formats info.nativeFormats = 0; if ( deviceFormat->wFormatTag == WAVE_FORMAT_IEEE_FLOAT || ( deviceFormat->wFormatTag == WAVE_FORMAT_EXTENSIBLE && ( ( WAVEFORMATEXTENSIBLE* ) deviceFormat )->SubFormat == KSDATAFORMAT_SUBTYPE_IEEE_FLOAT ) ) { if ( deviceFormat->wBitsPerSample == 32 ) { info.nativeFormats |= RTAUDIO_FLOAT32; } else if ( deviceFormat->wBitsPerSample == 64 ) { info.nativeFormats |= RTAUDIO_FLOAT64; } } else if ( deviceFormat->wFormatTag == WAVE_FORMAT_PCM || ( deviceFormat->wFormatTag == WAVE_FORMAT_EXTENSIBLE && ( ( WAVEFORMATEXTENSIBLE* ) deviceFormat )->SubFormat == KSDATAFORMAT_SUBTYPE_PCM ) ) { if ( deviceFormat->wBitsPerSample == 8 ) { info.nativeFormats |= RTAUDIO_SINT8; } else if ( deviceFormat->wBitsPerSample == 16 ) { info.nativeFormats |= RTAUDIO_SINT16; } else if ( deviceFormat->wBitsPerSample == 24 ) { info.nativeFormats |= RTAUDIO_SINT24; } else if ( deviceFormat->wBitsPerSample == 32 ) { info.nativeFormats |= RTAUDIO_SINT32; } } Exit: // Release all references PropVariantClear( &deviceNameProp ); SAFE_RELEASE( devicePtr ); SAFE_RELEASE( audioClient ); SAFE_RELEASE( devicePropStore ); CoTaskMemFree( deviceFormat ); CoTaskMemFree( closestMatchFormat ); if ( !errorText_.empty() ) { error( errorType ); return false; } return true; } void RtApiWasapi::closeStream( void ) { MUTEX_LOCK( &stream_.mutex ); if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiWasapi::closeStream: No open stream to close."; error( RTAUDIO_WARNING ); MUTEX_UNLOCK( &stream_.mutex ); return; } if ( stream_.state != STREAM_STOPPED ) { MUTEX_UNLOCK( &stream_.mutex ); stopStream(); MUTEX_LOCK( &stream_.mutex ); } // clean up stream memory SAFE_RELEASE(((WasapiHandle*)stream_.apiHandle)->captureClient) SAFE_RELEASE(((WasapiHandle*)stream_.apiHandle)->renderClient) SAFE_RELEASE( ( ( WasapiHandle* ) stream_.apiHandle )->captureAudioClient ) SAFE_RELEASE( ( ( WasapiHandle* ) stream_.apiHandle )->renderAudioClient ) if ( ( ( WasapiHandle* ) stream_.apiHandle )->captureEvent ) CloseHandle( ( ( WasapiHandle* ) stream_.apiHandle )->captureEvent ); if ( ( ( WasapiHandle* ) stream_.apiHandle )->renderEvent ) CloseHandle( ( ( WasapiHandle* ) stream_.apiHandle )->renderEvent ); delete ( WasapiHandle* ) stream_.apiHandle; stream_.apiHandle = NULL; for ( int i = 0; i < 2; i++ ) { if ( stream_.userBuffer[i] ) { free( stream_.userBuffer[i] ); stream_.userBuffer[i] = 0; } } if ( stream_.deviceBuffer ) { free( stream_.deviceBuffer ); stream_.deviceBuffer = 0; } clearStreamInfo(); MUTEX_UNLOCK( &stream_.mutex ); } //----------------------------------------------------------------------------- RtAudioErrorType RtApiWasapi::startStream( void ) { MUTEX_LOCK( &stream_.mutex ); if ( stream_.state != STREAM_STOPPED ) { if ( stream_.state == STREAM_RUNNING ) errorText_ = "RtApiWasapi::startStream(): the stream is already running!"; else if ( stream_.state == STREAM_STOPPING || stream_.state == STREAM_CLOSED ) errorText_ = "RtApiWasapi::startStream(): the stream is stopping or closed!"; MUTEX_UNLOCK( &stream_.mutex ); return error( RTAUDIO_WARNING ); } /* #if defined( HAVE_GETTIMEOFDAY ) gettimeofday( &stream_.lastTickTimestamp, NULL ); #endif */ // update stream state stream_.state = STREAM_RUNNING; // create WASAPI stream thread stream_.callbackInfo.thread = ( ThreadHandle ) CreateThread( NULL, 0, runWasapiThread, this, CREATE_SUSPENDED, NULL ); if ( !stream_.callbackInfo.thread ) { errorText_ = "RtApiWasapi::startStream: Unable to instantiate callback thread."; MUTEX_UNLOCK( &stream_.mutex ); return error( RTAUDIO_THREAD_ERROR ); } else { SetThreadPriority( ( void* ) stream_.callbackInfo.thread, stream_.callbackInfo.priority ); ResumeThread( ( void* ) stream_.callbackInfo.thread ); } MUTEX_UNLOCK( &stream_.mutex ); return RTAUDIO_NO_ERROR; } //----------------------------------------------------------------------------- RtAudioErrorType RtApiWasapi::stopStream( void ) { MUTEX_LOCK( &stream_.mutex ); if ( stream_.state != STREAM_RUNNING && stream_.state != STREAM_STOPPING ) { if ( stream_.state == STREAM_STOPPED ) errorText_ = "RtApiWasapi::stopStream(): the stream is already stopped!"; else if ( stream_.state == STREAM_CLOSED ) errorText_ = "RtApiWasapi::stopStream(): the stream is closed!"; MUTEX_UNLOCK( &stream_.mutex ); return error( RTAUDIO_WARNING ); } // inform stream thread by setting stream state to STREAM_STOPPING stream_.state = STREAM_STOPPING; WaitForSingleObject( ( void* ) stream_.callbackInfo.thread, INFINITE ); // close thread handle if ( stream_.callbackInfo.thread && !CloseHandle( ( void* ) stream_.callbackInfo.thread ) ) { errorText_ = "RtApiWasapi::stopStream: Unable to close callback thread."; MUTEX_UNLOCK( &stream_.mutex ); return error( RTAUDIO_THREAD_ERROR ); } stream_.callbackInfo.thread = (ThreadHandle) NULL; MUTEX_UNLOCK( &stream_.mutex ); return RTAUDIO_NO_ERROR; } //----------------------------------------------------------------------------- RtAudioErrorType RtApiWasapi::abortStream( void ) { MUTEX_LOCK( &stream_.mutex ); if ( stream_.state != STREAM_RUNNING ) { if ( stream_.state == STREAM_STOPPED ) errorText_ = "RtApiWasapi::abortStream(): the stream is already stopped!"; else if ( stream_.state == STREAM_STOPPING || stream_.state == STREAM_CLOSED ) errorText_ = "RtApiWasapi::abortStream(): the stream is stopping or closed!"; MUTEX_UNLOCK( &stream_.mutex ); return error( RTAUDIO_WARNING ); } // inform stream thread by setting stream state to STREAM_STOPPING stream_.state = STREAM_STOPPING; WaitForSingleObject( ( void* ) stream_.callbackInfo.thread, INFINITE ); // close thread handle if ( stream_.callbackInfo.thread && !CloseHandle( ( void* ) stream_.callbackInfo.thread ) ) { errorText_ = "RtApiWasapi::abortStream: Unable to close callback thread."; MUTEX_UNLOCK( &stream_.mutex ); return error( RTAUDIO_THREAD_ERROR ); } stream_.callbackInfo.thread = (ThreadHandle) NULL; MUTEX_UNLOCK( &stream_.mutex ); return RTAUDIO_NO_ERROR; } //----------------------------------------------------------------------------- bool RtApiWasapi::probeDeviceOpen( unsigned int deviceId, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int* bufferSize, RtAudio::StreamOptions* options ) { MUTEX_LOCK( &stream_.mutex ); bool methodResult = FAILURE; IMMDevice* devicePtr = NULL; WAVEFORMATEX* deviceFormat = NULL; unsigned int bufferBytes; stream_.state = STREAM_STOPPED; bool isInput = false; std::string id; unsigned int deviceIdx; for ( deviceIdx=0; deviceIdxGetDevice( (LPWSTR)temp.c_str(), &devicePtr ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve device handle."; MUTEX_UNLOCK( &stream_.mutex ); return FAILURE; } // Create API handle if not already created. if ( !stream_.apiHandle ) stream_.apiHandle = ( void* ) new WasapiHandle(); if ( isInput ) { IAudioClient*& captureAudioClient = ( ( WasapiHandle* ) stream_.apiHandle )->captureAudioClient; hr = devicePtr->Activate( __uuidof( IAudioClient ), CLSCTX_ALL, NULL, ( void** ) &captureAudioClient ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve capture device audio client."; goto Exit; } hr = captureAudioClient->GetMixFormat( &deviceFormat ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve capture device mix format."; goto Exit; } stream_.nDeviceChannels[mode] = deviceFormat->nChannels; captureAudioClient->GetStreamLatency( ( long long* ) &stream_.latency[mode] ); } // If an output device and is configured for loopback (input mode) if ( isInput == false && mode == INPUT ) { // If renderAudioClient is not initialised, initialise it now IAudioClient*& renderAudioClient = ( ( WasapiHandle* ) stream_.apiHandle )->renderAudioClient; if ( !renderAudioClient ) { MUTEX_UNLOCK( &stream_.mutex ); probeDeviceOpen( deviceId, OUTPUT, channels, firstChannel, sampleRate, format, bufferSize, options ); MUTEX_LOCK( &stream_.mutex ); } // Retrieve captureAudioClient from our stream handle. IAudioClient*& captureAudioClient = ( ( WasapiHandle* ) stream_.apiHandle )->captureAudioClient; hr = devicePtr->Activate( __uuidof( IAudioClient ), CLSCTX_ALL, NULL, ( void** ) &captureAudioClient ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve render device audio client."; goto Exit; } hr = captureAudioClient->GetMixFormat( &deviceFormat ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve render device mix format."; goto Exit; } stream_.nDeviceChannels[mode] = deviceFormat->nChannels; captureAudioClient->GetStreamLatency( ( long long* ) &stream_.latency[mode] ); } // If output device and is configured for output. if ( isInput == false && mode == OUTPUT ) { // If renderAudioClient is already initialised, don't initialise it again IAudioClient*& renderAudioClient = ( ( WasapiHandle* ) stream_.apiHandle )->renderAudioClient; if ( renderAudioClient ) { methodResult = SUCCESS; goto Exit; } hr = devicePtr->Activate( __uuidof( IAudioClient ), CLSCTX_ALL, NULL, ( void** ) &renderAudioClient ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve render device audio client."; goto Exit; } hr = renderAudioClient->GetMixFormat( &deviceFormat ); if ( FAILED( hr ) ) { errorText_ = "RtApiWasapi::probeDeviceOpen: Unable to retrieve render device mix format."; goto Exit; } stream_.nDeviceChannels[mode] = deviceFormat->nChannels; renderAudioClient->GetStreamLatency( ( long long* ) &stream_.latency[mode] ); } // Fill stream data if ( ( stream_.mode == OUTPUT && mode == INPUT ) || ( stream_.mode == INPUT && mode == OUTPUT ) ) { stream_.mode = DUPLEX; } else { stream_.mode = mode; } stream_.deviceId[mode] = deviceId; stream_.doByteSwap[mode] = false; stream_.sampleRate = sampleRate; stream_.bufferSize = *bufferSize; stream_.nBuffers = 1; stream_.nUserChannels[mode] = channels; stream_.channelOffset[mode] = firstChannel; stream_.userFormat = format; stream_.deviceFormat[mode] = deviceList_[deviceIdx].nativeFormats; if ( options && options->flags & RTAUDIO_NONINTERLEAVED ) stream_.userInterleaved = false; else stream_.userInterleaved = true; stream_.deviceInterleaved[mode] = true; // Set flags for buffer conversion. stream_.doConvertBuffer[mode] = false; if ( stream_.userFormat != stream_.deviceFormat[mode] || stream_.nUserChannels[0] != stream_.nDeviceChannels[0] || stream_.nUserChannels[1] != stream_.nDeviceChannels[1] ) stream_.doConvertBuffer[mode] = true; else if ( stream_.userInterleaved != stream_.deviceInterleaved[mode] && stream_.nUserChannels[mode] > 1 ) stream_.doConvertBuffer[mode] = true; if ( stream_.doConvertBuffer[mode] ) setConvertInfo( mode, firstChannel ); // Allocate necessary internal buffers bufferBytes = stream_.nUserChannels[mode] * stream_.bufferSize * formatBytes( stream_.userFormat ); stream_.userBuffer[mode] = ( char* ) calloc( bufferBytes, 1 ); if ( !stream_.userBuffer[mode] ) { errorType = RTAUDIO_MEMORY_ERROR; errorText_ = "RtApiWasapi::probeDeviceOpen: Error allocating user buffer memory."; goto Exit; } if ( options && options->flags & RTAUDIO_SCHEDULE_REALTIME ) stream_.callbackInfo.priority = 15; else stream_.callbackInfo.priority = 0; ///! TODO: RTAUDIO_MINIMIZE_LATENCY // Provide stream buffers directly to callback ///! TODO: RTAUDIO_HOG_DEVICE // Exclusive mode methodResult = SUCCESS; Exit: //clean up SAFE_RELEASE( devicePtr ); CoTaskMemFree( deviceFormat ); // if method failed, close the stream if ( methodResult == FAILURE ) { MUTEX_UNLOCK( &stream_.mutex ); closeStream(); MUTEX_LOCK( &stream_.mutex ); } if ( !errorText_.empty() ) error( errorType ); MUTEX_UNLOCK( &stream_.mutex ); return methodResult; } //============================================================================= DWORD WINAPI RtApiWasapi::runWasapiThread( void* wasapiPtr ) { if ( wasapiPtr ) ( ( RtApiWasapi* ) wasapiPtr )->wasapiThread(); return 0; } DWORD WINAPI RtApiWasapi::stopWasapiThread( void* wasapiPtr ) { if ( wasapiPtr ) ( ( RtApiWasapi* ) wasapiPtr )->stopStream(); return 0; } DWORD WINAPI RtApiWasapi::abortWasapiThread( void* wasapiPtr ) { if ( wasapiPtr ) ( ( RtApiWasapi* ) wasapiPtr )->abortStream(); return 0; } //----------------------------------------------------------------------------- void RtApiWasapi::wasapiThread() { // as this is a new thread, we must CoInitialize it CoInitialize( NULL ); HRESULT hr; IAudioClient* captureAudioClient = ( ( WasapiHandle* ) stream_.apiHandle )->captureAudioClient; IAudioClient* renderAudioClient = ( ( WasapiHandle* ) stream_.apiHandle )->renderAudioClient; IAudioCaptureClient* captureClient = ( ( WasapiHandle* ) stream_.apiHandle )->captureClient; IAudioRenderClient* renderClient = ( ( WasapiHandle* ) stream_.apiHandle )->renderClient; HANDLE captureEvent = ( ( WasapiHandle* ) stream_.apiHandle )->captureEvent; HANDLE renderEvent = ( ( WasapiHandle* ) stream_.apiHandle )->renderEvent; WAVEFORMATEX* captureFormat = NULL; WAVEFORMATEX* renderFormat = NULL; float captureSrRatio = 0.0f; float renderSrRatio = 0.0f; WasapiBuffer captureBuffer; WasapiBuffer renderBuffer; WasapiResampler* captureResampler = NULL; WasapiResampler* renderResampler = NULL; // declare local stream variables RtAudioCallback callback = ( RtAudioCallback ) stream_.callbackInfo.callback; BYTE* streamBuffer = NULL; DWORD captureFlags = 0; unsigned int bufferFrameCount = 0; unsigned int numFramesPadding = 0; unsigned int convBufferSize = 0; bool loopbackEnabled = stream_.deviceId[INPUT] == stream_.deviceId[OUTPUT]; bool callbackPushed = true; bool callbackPulled = false; bool callbackStopped = false; int callbackResult = 0; // convBuffer is used to store converted buffers between WASAPI and the user char* convBuffer = NULL; unsigned int convBuffSize = 0; unsigned int deviceBuffSize = 0; std::string errorText; RtAudioErrorType errorType = RTAUDIO_DRIVER_ERROR; // Attempt to assign "Pro Audio" characteristic to thread HMODULE AvrtDll = LoadLibraryW( L"AVRT.dll" ); if ( AvrtDll ) { DWORD taskIndex = 0; TAvSetMmThreadCharacteristicsPtr AvSetMmThreadCharacteristicsPtr = ( TAvSetMmThreadCharacteristicsPtr ) (void(*)()) GetProcAddress( AvrtDll, "AvSetMmThreadCharacteristicsW" ); AvSetMmThreadCharacteristicsPtr( L"Pro Audio", &taskIndex ); FreeLibrary( AvrtDll ); } // start capture stream if applicable if ( captureAudioClient ) { hr = captureAudioClient->GetMixFormat( &captureFormat ); if ( FAILED( hr ) ) { errorText = "RtApiWasapi::wasapiThread: Unable to retrieve device mix format."; goto Exit; } // init captureResampler captureResampler = new WasapiResampler( stream_.deviceFormat[INPUT] == RTAUDIO_FLOAT32 || stream_.deviceFormat[INPUT] == RTAUDIO_FLOAT64, formatBytes( stream_.deviceFormat[INPUT] ) * 8, stream_.nDeviceChannels[INPUT], captureFormat->nSamplesPerSec, stream_.sampleRate ); captureSrRatio = ( ( float ) captureFormat->nSamplesPerSec / stream_.sampleRate ); if ( !captureClient ) { IAudioClient3* captureAudioClient3 = nullptr; captureAudioClient->QueryInterface( __uuidof( IAudioClient3 ), ( void** ) &captureAudioClient3 ); if ( captureAudioClient3 && !loopbackEnabled ) { UINT32 Ignore; UINT32 MinPeriodInFrames; hr = captureAudioClient3->GetSharedModeEnginePeriod( captureFormat, &Ignore, &Ignore, &MinPeriodInFrames, &Ignore ); if ( FAILED( hr ) ) { errorText = "RtApiWasapi::wasapiThread: Unable to initialize capture audio client."; goto Exit; } hr = captureAudioClient3->InitializeSharedAudioStream( AUDCLNT_STREAMFLAGS_EVENTCALLBACK, MinPeriodInFrames, captureFormat, NULL ); SAFE_RELEASE(captureAudioClient3); } else { hr = captureAudioClient->Initialize( AUDCLNT_SHAREMODE_SHARED, loopbackEnabled ? AUDCLNT_STREAMFLAGS_LOOPBACK : AUDCLNT_STREAMFLAGS_EVENTCALLBACK, 0, 0, captureFormat, NULL ); } if ( FAILED( hr ) ) { errorText = "RtApiWasapi::wasapiThread: Unable to initialize capture audio client."; goto Exit; } hr = captureAudioClient->GetService( __uuidof( IAudioCaptureClient ), ( void** ) &captureClient ); if ( FAILED( hr ) ) { errorText = "RtApiWasapi::wasapiThread: Unable to retrieve capture client handle."; goto Exit; } // don't configure captureEvent if in loopback mode if ( !loopbackEnabled ) { // configure captureEvent to trigger on every available capture buffer captureEvent = CreateEvent( NULL, FALSE, FALSE, NULL ); if ( !captureEvent ) { errorType = RTAUDIO_SYSTEM_ERROR; errorText = "RtApiWasapi::wasapiThread: Unable to create capture event."; goto Exit; } hr = captureAudioClient->SetEventHandle( captureEvent ); if ( FAILED( hr ) ) { errorText = "RtApiWasapi::wasapiThread: Unable to set capture event handle."; goto Exit; } ( ( WasapiHandle* ) stream_.apiHandle )->captureEvent = captureEvent; } ( ( WasapiHandle* ) stream_.apiHandle )->captureClient = captureClient; // reset the capture stream hr = captureAudioClient->Reset(); if ( FAILED( hr ) ) { errorText = "RtApiWasapi::wasapiThread: Unable to reset capture stream."; goto Exit; } // start the capture stream hr = captureAudioClient->Start(); if ( FAILED( hr ) ) { errorText = "RtApiWasapi::wasapiThread: Unable to start capture stream."; goto Exit; } } unsigned int inBufferSize = 0; hr = captureAudioClient->GetBufferSize( &inBufferSize ); if ( FAILED( hr ) ) { errorText = "RtApiWasapi::wasapiThread: Unable to get capture buffer size."; goto Exit; } // scale outBufferSize according to stream->user sample rate ratio unsigned int outBufferSize = ( unsigned int ) ceilf( stream_.bufferSize * captureSrRatio ) * stream_.nDeviceChannels[INPUT]; inBufferSize *= stream_.nDeviceChannels[INPUT]; // set captureBuffer size captureBuffer.setBufferSize( inBufferSize + outBufferSize, formatBytes( stream_.deviceFormat[INPUT] ) ); } // start render stream if applicable if ( renderAudioClient ) { hr = renderAudioClient->GetMixFormat( &renderFormat ); if ( FAILED( hr ) ) { errorText = "RtApiWasapi::wasapiThread: Unable to retrieve device mix format."; goto Exit; } // init renderResampler renderResampler = new WasapiResampler( stream_.deviceFormat[OUTPUT] == RTAUDIO_FLOAT32 || stream_.deviceFormat[OUTPUT] == RTAUDIO_FLOAT64, formatBytes( stream_.deviceFormat[OUTPUT] ) * 8, stream_.nDeviceChannels[OUTPUT], stream_.sampleRate, renderFormat->nSamplesPerSec ); renderSrRatio = ( ( float ) renderFormat->nSamplesPerSec / stream_.sampleRate ); if ( !renderClient ) { IAudioClient3* renderAudioClient3 = nullptr; renderAudioClient->QueryInterface( __uuidof( IAudioClient3 ), ( void** ) &renderAudioClient3 ); if ( renderAudioClient3 ) { UINT32 Ignore; UINT32 MinPeriodInFrames; hr = renderAudioClient3->GetSharedModeEnginePeriod( renderFormat, &Ignore, &Ignore, &MinPeriodInFrames, &Ignore ); if ( FAILED( hr ) ) { errorText = "RtApiWasapi::wasapiThread: Unable to initialize render audio client."; goto Exit; } hr = renderAudioClient3->InitializeSharedAudioStream( AUDCLNT_STREAMFLAGS_EVENTCALLBACK, MinPeriodInFrames, renderFormat, NULL ); SAFE_RELEASE(renderAudioClient3); } else { hr = renderAudioClient->Initialize( AUDCLNT_SHAREMODE_SHARED, AUDCLNT_STREAMFLAGS_EVENTCALLBACK, 0, 0, renderFormat, NULL ); } if ( FAILED( hr ) ) { errorText = "RtApiWasapi::wasapiThread: Unable to initialize render audio client."; goto Exit; } hr = renderAudioClient->GetService( __uuidof( IAudioRenderClient ), ( void** ) &renderClient ); if ( FAILED( hr ) ) { errorText = "RtApiWasapi::wasapiThread: Unable to retrieve render client handle."; goto Exit; } // configure renderEvent to trigger on every available render buffer renderEvent = CreateEvent( NULL, FALSE, FALSE, NULL ); if ( !renderEvent ) { errorType = RTAUDIO_SYSTEM_ERROR; errorText = "RtApiWasapi::wasapiThread: Unable to create render event."; goto Exit; } hr = renderAudioClient->SetEventHandle( renderEvent ); if ( FAILED( hr ) ) { errorText = "RtApiWasapi::wasapiThread: Unable to set render event handle."; goto Exit; } ( ( WasapiHandle* ) stream_.apiHandle )->renderClient = renderClient; ( ( WasapiHandle* ) stream_.apiHandle )->renderEvent = renderEvent; // reset the render stream hr = renderAudioClient->Reset(); if ( FAILED( hr ) ) { errorText = "RtApiWasapi::wasapiThread: Unable to reset render stream."; goto Exit; } // start the render stream hr = renderAudioClient->Start(); if ( FAILED( hr ) ) { errorText = "RtApiWasapi::wasapiThread: Unable to start render stream."; goto Exit; } } unsigned int outBufferSize = 0; hr = renderAudioClient->GetBufferSize( &outBufferSize ); if ( FAILED( hr ) ) { errorText = "RtApiWasapi::wasapiThread: Unable to get render buffer size."; goto Exit; } // scale inBufferSize according to user->stream sample rate ratio unsigned int inBufferSize = ( unsigned int ) ceilf( stream_.bufferSize * renderSrRatio ) * stream_.nDeviceChannels[OUTPUT]; outBufferSize *= stream_.nDeviceChannels[OUTPUT]; // set renderBuffer size renderBuffer.setBufferSize( inBufferSize + outBufferSize, formatBytes( stream_.deviceFormat[OUTPUT] ) ); } // malloc buffer memory if ( stream_.mode == INPUT ) { using namespace std; // for ceilf convBuffSize = ( unsigned int ) ( ceilf( stream_.bufferSize * captureSrRatio ) ) * stream_.nDeviceChannels[INPUT] * formatBytes( stream_.deviceFormat[INPUT] ); deviceBuffSize = stream_.bufferSize * stream_.nDeviceChannels[INPUT] * formatBytes( stream_.deviceFormat[INPUT] ); } else if ( stream_.mode == OUTPUT ) { convBuffSize = ( unsigned int ) ( ceilf( stream_.bufferSize * renderSrRatio ) ) * stream_.nDeviceChannels[OUTPUT] * formatBytes( stream_.deviceFormat[OUTPUT] ); deviceBuffSize = stream_.bufferSize * stream_.nDeviceChannels[OUTPUT] * formatBytes( stream_.deviceFormat[OUTPUT] ); } else if ( stream_.mode == DUPLEX ) { convBuffSize = std::max( ( unsigned int ) ( ceilf( stream_.bufferSize * captureSrRatio ) ) * stream_.nDeviceChannels[INPUT] * formatBytes( stream_.deviceFormat[INPUT] ), ( unsigned int ) ( ceilf( stream_.bufferSize * renderSrRatio ) ) * stream_.nDeviceChannels[OUTPUT] * formatBytes( stream_.deviceFormat[OUTPUT] ) ); deviceBuffSize = std::max( stream_.bufferSize * stream_.nDeviceChannels[INPUT] * formatBytes( stream_.deviceFormat[INPUT] ), stream_.bufferSize * stream_.nDeviceChannels[OUTPUT] * formatBytes( stream_.deviceFormat[OUTPUT] ) ); } convBuffSize *= 2; // allow overflow for *SrRatio remainders convBuffer = ( char* ) calloc( convBuffSize, 1 ); stream_.deviceBuffer = ( char* ) calloc( deviceBuffSize, 1 ); if ( !convBuffer || !stream_.deviceBuffer ) { errorType = RTAUDIO_MEMORY_ERROR; errorText = "RtApiWasapi::wasapiThread: Error allocating device buffer memory."; goto Exit; } // stream process loop while ( stream_.state != STREAM_STOPPING ) { if ( !callbackPulled ) { // Callback Input // ============== // 1. Pull callback buffer from inputBuffer // 2. If 1. was successful: Convert callback buffer to user sample rate and channel count // Convert callback buffer to user format if ( captureAudioClient ) { int samplesToPull = ( unsigned int ) floorf( stream_.bufferSize * captureSrRatio ); convBufferSize = 0; while ( convBufferSize < stream_.bufferSize ) { // Pull callback buffer from inputBuffer callbackPulled = captureBuffer.pullBuffer( convBuffer, samplesToPull * stream_.nDeviceChannels[INPUT], stream_.deviceFormat[INPUT] ); if ( !callbackPulled ) { break; } // Convert callback buffer to user sample rate unsigned int deviceBufferOffset = convBufferSize * stream_.nDeviceChannels[INPUT] * formatBytes( stream_.deviceFormat[INPUT] ); unsigned int convSamples = 0; captureResampler->Convert( stream_.deviceBuffer + deviceBufferOffset, convBuffer, samplesToPull, convSamples, convBufferSize == 0 ? -1 : stream_.bufferSize - convBufferSize ); convBufferSize += convSamples; samplesToPull = 1; // now pull one sample at a time until we have stream_.bufferSize samples } if ( callbackPulled ) { if ( stream_.doConvertBuffer[INPUT] ) { // Convert callback buffer to user format convertBuffer( stream_.userBuffer[INPUT], stream_.deviceBuffer, stream_.convertInfo[INPUT] ); } else { // no further conversion, simple copy deviceBuffer to userBuffer memcpy( stream_.userBuffer[INPUT], stream_.deviceBuffer, stream_.bufferSize * stream_.nUserChannels[INPUT] * formatBytes( stream_.userFormat ) ); } } } else { // if there is no capture stream, set callbackPulled flag callbackPulled = true; } // Execute Callback // ================ // 1. Execute user callback method // 2. Handle return value from callback // if callback has not requested the stream to stop if ( callbackPulled && !callbackStopped ) { // Execute user callback method callbackResult = callback( stream_.userBuffer[OUTPUT], stream_.userBuffer[INPUT], stream_.bufferSize, getStreamTime(), captureFlags & AUDCLNT_BUFFERFLAGS_DATA_DISCONTINUITY ? RTAUDIO_INPUT_OVERFLOW : 0, stream_.callbackInfo.userData ); // tick stream time RtApi::tickStreamTime(); // Handle return value from callback if ( callbackResult == 1 ) { // instantiate a thread to stop this thread HANDLE threadHandle = CreateThread( NULL, 0, stopWasapiThread, this, 0, NULL ); if ( !threadHandle ) { errorType = RTAUDIO_THREAD_ERROR; errorText = "RtApiWasapi::wasapiThread: Unable to instantiate stream stop thread."; goto Exit; } else if ( !CloseHandle( threadHandle ) ) { errorType = RTAUDIO_THREAD_ERROR; errorText = "RtApiWasapi::wasapiThread: Unable to close stream stop thread handle."; goto Exit; } callbackStopped = true; } else if ( callbackResult == 2 ) { // instantiate a thread to stop this thread HANDLE threadHandle = CreateThread( NULL, 0, abortWasapiThread, this, 0, NULL ); if ( !threadHandle ) { errorType = RTAUDIO_THREAD_ERROR; errorText = "RtApiWasapi::wasapiThread: Unable to instantiate stream abort thread."; goto Exit; } else if ( !CloseHandle( threadHandle ) ) { errorType = RTAUDIO_THREAD_ERROR; errorText = "RtApiWasapi::wasapiThread: Unable to close stream abort thread handle."; goto Exit; } callbackStopped = true; } } } // Callback Output // =============== // 1. Convert callback buffer to stream format // 2. Convert callback buffer to stream sample rate and channel count // 3. Push callback buffer into outputBuffer if ( renderAudioClient && callbackPulled ) { // if the last call to renderBuffer.PushBuffer() was successful if ( callbackPushed || convBufferSize == 0 ) { if ( stream_.doConvertBuffer[OUTPUT] ) { // Convert callback buffer to stream format convertBuffer( stream_.deviceBuffer, stream_.userBuffer[OUTPUT], stream_.convertInfo[OUTPUT] ); } else { // no further conversion, simple copy userBuffer to deviceBuffer memcpy( stream_.deviceBuffer, stream_.userBuffer[OUTPUT], stream_.bufferSize * stream_.nUserChannels[OUTPUT] * formatBytes( stream_.userFormat ) ); } // Convert callback buffer to stream sample rate renderResampler->Convert( convBuffer, stream_.deviceBuffer, stream_.bufferSize, convBufferSize ); } // Push callback buffer into outputBuffer callbackPushed = renderBuffer.pushBuffer( convBuffer, convBufferSize * stream_.nDeviceChannels[OUTPUT], stream_.deviceFormat[OUTPUT] ); } else { // if there is no render stream, set callbackPushed flag callbackPushed = true; } // Stream Capture // ============== // 1. Get capture buffer from stream // 2. Push capture buffer into inputBuffer // 3. If 2. was successful: Release capture buffer if ( captureAudioClient ) { // if the callback input buffer was not pulled from captureBuffer, wait for next capture event if ( !callbackPulled ) { WaitForSingleObject( loopbackEnabled ? renderEvent : captureEvent, INFINITE ); } // Get capture buffer from stream hr = captureClient->GetBuffer( &streamBuffer, &bufferFrameCount, &captureFlags, NULL, NULL ); if ( FAILED( hr ) ) { errorText = "RtApiWasapi::wasapiThread: Unable to retrieve capture buffer."; goto Exit; } if ( bufferFrameCount != 0 ) { // Push capture buffer into inputBuffer if ( captureBuffer.pushBuffer( ( char* ) streamBuffer, bufferFrameCount * stream_.nDeviceChannels[INPUT], stream_.deviceFormat[INPUT] ) ) { // Release capture buffer hr = captureClient->ReleaseBuffer( bufferFrameCount ); if ( FAILED( hr ) ) { errorText = "RtApiWasapi::wasapiThread: Unable to release capture buffer."; goto Exit; } } else { // Inform WASAPI that capture was unsuccessful hr = captureClient->ReleaseBuffer( 0 ); if ( FAILED( hr ) ) { errorText = "RtApiWasapi::wasapiThread: Unable to release capture buffer."; goto Exit; } } } else { // Inform WASAPI that capture was unsuccessful hr = captureClient->ReleaseBuffer( 0 ); if ( FAILED( hr ) ) { errorText = "RtApiWasapi::wasapiThread: Unable to release capture buffer."; goto Exit; } } } // Stream Render // ============= // 1. Get render buffer from stream // 2. Pull next buffer from outputBuffer // 3. If 2. was successful: Fill render buffer with next buffer // Release render buffer if ( renderAudioClient ) { // if the callback output buffer was not pushed to renderBuffer, wait for next render event if ( callbackPulled && !callbackPushed ) { WaitForSingleObject( renderEvent, INFINITE ); } // Get render buffer from stream hr = renderAudioClient->GetBufferSize( &bufferFrameCount ); if ( FAILED( hr ) ) { errorText = "RtApiWasapi::wasapiThread: Unable to retrieve render buffer size."; goto Exit; } hr = renderAudioClient->GetCurrentPadding( &numFramesPadding ); if ( FAILED( hr ) ) { errorText = "RtApiWasapi::wasapiThread: Unable to retrieve render buffer padding."; goto Exit; } bufferFrameCount -= numFramesPadding; if ( bufferFrameCount != 0 ) { hr = renderClient->GetBuffer( bufferFrameCount, &streamBuffer ); if ( FAILED( hr ) ) { errorText = "RtApiWasapi::wasapiThread: Unable to retrieve render buffer."; goto Exit; } // Pull next buffer from outputBuffer // Fill render buffer with next buffer if ( renderBuffer.pullBuffer( ( char* ) streamBuffer, bufferFrameCount * stream_.nDeviceChannels[OUTPUT], stream_.deviceFormat[OUTPUT] ) ) { // Release render buffer hr = renderClient->ReleaseBuffer( bufferFrameCount, 0 ); if ( FAILED( hr ) ) { errorText = "RtApiWasapi::wasapiThread: Unable to release render buffer."; goto Exit; } } else { // Inform WASAPI that render was unsuccessful hr = renderClient->ReleaseBuffer( 0, 0 ); if ( FAILED( hr ) ) { errorText = "RtApiWasapi::wasapiThread: Unable to release render buffer."; goto Exit; } } } else { // Inform WASAPI that render was unsuccessful hr = renderClient->ReleaseBuffer( 0, 0 ); if ( FAILED( hr ) ) { errorText = "RtApiWasapi::wasapiThread: Unable to release render buffer."; goto Exit; } } } // if the callback buffer was pushed renderBuffer reset callbackPulled flag if ( callbackPushed ) { // unsetting the callbackPulled flag lets the stream know that // the audio device is ready for another callback output buffer. callbackPulled = false; } } Exit: // clean up CoTaskMemFree( captureFormat ); CoTaskMemFree( renderFormat ); free ( convBuffer ); delete renderResampler; delete captureResampler; CoUninitialize(); if ( !errorText.empty() ) { errorText_ = errorText; error( errorType ); } // update stream state stream_.state = STREAM_STOPPED; } //******************** End of __WINDOWS_WASAPI__ *********************// #endif #if defined(__WINDOWS_DS__) // Windows DirectSound API // Modified by Robin Davies, October 2005 // - Improvements to DirectX pointer chasing. // - Bug fix for non-power-of-two Asio granularity used by Edirol PCR-A30. // - Auto-call CoInitialize for DSOUND and ASIO platforms. // Various revisions for RtAudio 4.0 by Gary Scavone, April 2007 // Changed device query structure for RtAudio 4.0.7, January 2010 #include #include #include #include #include #include #include #if defined(__MINGW32__) // missing from latest mingw winapi #define WAVE_FORMAT_96M08 0x00010000 /* 96 kHz, Mono, 8-bit */ #define WAVE_FORMAT_96S08 0x00020000 /* 96 kHz, Stereo, 8-bit */ #define WAVE_FORMAT_96M16 0x00040000 /* 96 kHz, Mono, 16-bit */ #define WAVE_FORMAT_96S16 0x00080000 /* 96 kHz, Stereo, 16-bit */ #endif #define MINIMUM_DEVICE_BUFFER_SIZE 32768 #ifdef _MSC_VER // if Microsoft Visual C++ #pragma comment( lib, "winmm.lib" ) // then, auto-link winmm.lib. Otherwise, it has to be added manually. #endif static inline DWORD dsPointerBetween( DWORD pointer, DWORD laterPointer, DWORD earlierPointer, DWORD bufferSize ) { if ( pointer > bufferSize ) pointer -= bufferSize; if ( laterPointer < earlierPointer ) laterPointer += bufferSize; if ( pointer < earlierPointer ) pointer += bufferSize; return pointer >= earlierPointer && pointer < laterPointer; } // A structure to hold various information related to the DirectSound // API implementation. struct DsHandle { unsigned int drainCounter; // Tracks callback counts when draining bool internalDrain; // Indicates if stop is initiated from callback or not. void *id[2]; void *buffer[2]; bool xrun[2]; UINT bufferPointer[2]; DWORD dsBufferSize[2]; DWORD dsPointerLeadTime[2]; // the number of bytes ahead of the safe pointer to lead by. HANDLE condition; DsHandle() :drainCounter(0), internalDrain(false) { id[0] = 0; id[1] = 0; buffer[0] = 0; buffer[1] = 0; xrun[0] = false; xrun[1] = false; bufferPointer[0] = 0; bufferPointer[1] = 0; } }; // Declarations for utility functions, callbacks, and structures // specific to the DirectSound implementation. static BOOL CALLBACK deviceQueryCallback( LPGUID lpguid, LPCTSTR description, LPCTSTR module, LPVOID lpContext ); static const char* getErrorString( int code ); static unsigned __stdcall callbackHandler( void *ptr ); struct DsDevice { LPGUID id; bool isInput; std::string name; std::string epID; // endpoint ID DsDevice() : isInput(false) {} }; struct DsProbeData { bool isInput; std::vector* dsDevices; }; RtApiDs :: RtApiDs() { // Dsound will run both-threaded. If CoInitialize fails, then just // accept whatever the mainline chose for a threading model. coInitialized_ = false; HRESULT hr = CoInitialize( NULL ); if ( !FAILED( hr ) ) coInitialized_ = true; } RtApiDs :: ~RtApiDs() { if ( stream_.state != STREAM_CLOSED ) closeStream(); if ( coInitialized_ ) CoUninitialize(); // balanced call. } void RtApiDs :: probeDevices( void ) { // See list of required functionality in RtApi::probeDevices(). // Query DirectSound devices. struct DsProbeData probeInfo; probeInfo.isInput = false; std::vector< struct DsDevice > devices; probeInfo.dsDevices = &devices; HRESULT result = DirectSoundEnumerate( (LPDSENUMCALLBACK) deviceQueryCallback, &probeInfo ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::probeDevices: error (" << getErrorString( result ) << ") enumerating output devices!"; errorText_ = errorStream_.str(); error( RTAUDIO_WARNING ); } // Query DirectSoundCapture devices. probeInfo.isInput = true; result = DirectSoundCaptureEnumerate( (LPDSENUMCALLBACK) deviceQueryCallback, &probeInfo ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::probeDevices: error (" << getErrorString( result ) << ") enumerating input devices!"; errorText_ = errorStream_.str(); error( RTAUDIO_WARNING ); } // Now fill or update our deviceList_ vector. unsigned int m, n; for ( n=0; n::iterator it=dsDevices_.begin(); it!=dsDevices_.end(); ) { for ( n=0; nGetCaps( &outCaps ); if ( FAILED( result ) ) { output->Release(); errorStream_ << "RtApiDs::probeDeviceInfo: error (" << getErrorString( result ) << ") getting capabilities!"; errorText_ = errorStream_.str(); error( RTAUDIO_WARNING ); goto probeInput; } // Get output channel information. info.outputChannels = ( outCaps.dwFlags & DSCAPS_PRIMARYSTEREO ) ? 2 : 1; // Get sample rate information. info.sampleRates.clear(); for ( unsigned int k=0; k= (unsigned int) outCaps.dwMinSecondarySampleRate && SAMPLE_RATES[k] <= (unsigned int) outCaps.dwMaxSecondarySampleRate ) { info.sampleRates.push_back( SAMPLE_RATES[k] ); if ( !info.preferredSampleRate || ( SAMPLE_RATES[k] <= 48000 && SAMPLE_RATES[k] > info.preferredSampleRate ) ) info.preferredSampleRate = SAMPLE_RATES[k]; } } // Get format information. if ( outCaps.dwFlags & DSCAPS_PRIMARY16BIT ) info.nativeFormats |= RTAUDIO_SINT16; if ( outCaps.dwFlags & DSCAPS_PRIMARY8BIT ) info.nativeFormats |= RTAUDIO_SINT8; output->Release(); info.name = dsDevice.name; return true; probeInput: LPDIRECTSOUNDCAPTURE input; result = DirectSoundCaptureCreate( dsDevice.id, &input, NULL ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::probeDeviceInfo: error (" << getErrorString( result ) << ") opening input device (" << dsDevice.name << ")!"; errorText_ = errorStream_.str(); error( RTAUDIO_WARNING ); return false; } DSCCAPS inCaps; inCaps.dwSize = sizeof( inCaps ); result = input->GetCaps( &inCaps ); if ( FAILED( result ) ) { input->Release(); errorStream_ << "RtApiDs::probeDeviceInfo: error (" << getErrorString( result ) << ") getting object capabilities (" << dsDevice.name << ")!"; errorText_ = errorStream_.str(); error( RTAUDIO_WARNING ); return false; } // Get input channel information. info.inputChannels = inCaps.dwChannels; // Get sample rate and format information. std::vector rates; if ( inCaps.dwChannels >= 2 ) { if ( inCaps.dwFormats & WAVE_FORMAT_1S16 ) info.nativeFormats |= RTAUDIO_SINT16; if ( inCaps.dwFormats & WAVE_FORMAT_2S16 ) info.nativeFormats |= RTAUDIO_SINT16; if ( inCaps.dwFormats & WAVE_FORMAT_4S16 ) info.nativeFormats |= RTAUDIO_SINT16; if ( inCaps.dwFormats & WAVE_FORMAT_96S16 ) info.nativeFormats |= RTAUDIO_SINT16; if ( inCaps.dwFormats & WAVE_FORMAT_1S08 ) info.nativeFormats |= RTAUDIO_SINT8; if ( inCaps.dwFormats & WAVE_FORMAT_2S08 ) info.nativeFormats |= RTAUDIO_SINT8; if ( inCaps.dwFormats & WAVE_FORMAT_4S08 ) info.nativeFormats |= RTAUDIO_SINT8; if ( inCaps.dwFormats & WAVE_FORMAT_96S08 ) info.nativeFormats |= RTAUDIO_SINT8; if ( info.nativeFormats & RTAUDIO_SINT16 ) { if ( inCaps.dwFormats & WAVE_FORMAT_1S16 ) rates.push_back( 11025 ); if ( inCaps.dwFormats & WAVE_FORMAT_2S16 ) rates.push_back( 22050 ); if ( inCaps.dwFormats & WAVE_FORMAT_4S16 ) rates.push_back( 44100 ); if ( inCaps.dwFormats & WAVE_FORMAT_96S16 ) rates.push_back( 96000 ); } else if ( info.nativeFormats & RTAUDIO_SINT8 ) { if ( inCaps.dwFormats & WAVE_FORMAT_1S08 ) rates.push_back( 11025 ); if ( inCaps.dwFormats & WAVE_FORMAT_2S08 ) rates.push_back( 22050 ); if ( inCaps.dwFormats & WAVE_FORMAT_4S08 ) rates.push_back( 44100 ); if ( inCaps.dwFormats & WAVE_FORMAT_96S08 ) rates.push_back( 96000 ); } } else if ( inCaps.dwChannels == 1 ) { if ( inCaps.dwFormats & WAVE_FORMAT_1M16 ) info.nativeFormats |= RTAUDIO_SINT16; if ( inCaps.dwFormats & WAVE_FORMAT_2M16 ) info.nativeFormats |= RTAUDIO_SINT16; if ( inCaps.dwFormats & WAVE_FORMAT_4M16 ) info.nativeFormats |= RTAUDIO_SINT16; if ( inCaps.dwFormats & WAVE_FORMAT_96M16 ) info.nativeFormats |= RTAUDIO_SINT16; if ( inCaps.dwFormats & WAVE_FORMAT_1M08 ) info.nativeFormats |= RTAUDIO_SINT8; if ( inCaps.dwFormats & WAVE_FORMAT_2M08 ) info.nativeFormats |= RTAUDIO_SINT8; if ( inCaps.dwFormats & WAVE_FORMAT_4M08 ) info.nativeFormats |= RTAUDIO_SINT8; if ( inCaps.dwFormats & WAVE_FORMAT_96M08 ) info.nativeFormats |= RTAUDIO_SINT8; if ( info.nativeFormats & RTAUDIO_SINT16 ) { if ( inCaps.dwFormats & WAVE_FORMAT_1M16 ) rates.push_back( 11025 ); if ( inCaps.dwFormats & WAVE_FORMAT_2M16 ) rates.push_back( 22050 ); if ( inCaps.dwFormats & WAVE_FORMAT_4M16 ) rates.push_back( 44100 ); if ( inCaps.dwFormats & WAVE_FORMAT_96M16 ) rates.push_back( 96000 ); } else if ( info.nativeFormats & RTAUDIO_SINT8 ) { if ( inCaps.dwFormats & WAVE_FORMAT_1M08 ) rates.push_back( 11025 ); if ( inCaps.dwFormats & WAVE_FORMAT_2M08 ) rates.push_back( 22050 ); if ( inCaps.dwFormats & WAVE_FORMAT_4M08 ) rates.push_back( 44100 ); if ( inCaps.dwFormats & WAVE_FORMAT_96M08 ) rates.push_back( 96000 ); } } else info.inputChannels = 0; // technically, this would be an error input->Release(); if ( info.inputChannels == 0 ) return false; // Copy the supported rates to the info structure but avoid duplication. bool found; for ( unsigned int i=0; i info.preferredSampleRate ) ) info.preferredSampleRate = info.sampleRates[i]; } // Copy name and return. info.name = dsDevice.name; return true; } bool RtApiDs :: probeDeviceOpen( unsigned int deviceId, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, RtAudio::StreamOptions *options ) { if ( channels + firstChannel > 2 ) { errorText_ = "RtApiDs::probeDeviceOpen: DirectSound does not support more than 2 channels per device."; return FAILURE; } size_t nDevices = dsDevices_.size(); if ( nDevices == 0 ) { // This should not happen because a check is made before this function is called. errorText_ = "RtApiDs::probeDeviceOpen: no devices found!"; return FAILURE; } int deviceIdx = -1; for ( unsigned int m=0; mnumberOfBuffers; if ( options && options->flags & RTAUDIO_MINIMIZE_LATENCY ) nBuffers = 2; if ( nBuffers < 2 ) nBuffers = 3; // Check the lower range of the user-specified buffer size and set // (arbitrarily) to a lower bound of 32. if ( *bufferSize < 32 ) *bufferSize = 32; // Create the wave format structure. The data format setting will // be determined later. WAVEFORMATEX waveFormat; ZeroMemory( &waveFormat, sizeof(WAVEFORMATEX) ); waveFormat.wFormatTag = WAVE_FORMAT_PCM; waveFormat.nChannels = channels + firstChannel; waveFormat.nSamplesPerSec = (unsigned long) sampleRate; // Determine the device buffer size. By default, we'll use the value // defined above (32K), but we will grow it to make allowances for // very large software buffer sizes. DWORD dsBufferSize = MINIMUM_DEVICE_BUFFER_SIZE; DWORD dsPointerLeadTime = 0; void *ohandle = 0, *bhandle = 0; HRESULT result; if ( mode == OUTPUT ) { LPDIRECTSOUND output; result = DirectSoundCreate( dsDevices_[ deviceIdx ].id, &output, NULL ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") opening output device (" << dsDevices_[ deviceIdx ].name << ")!"; errorText_ = errorStream_.str(); return FAILURE; } DSCAPS outCaps; outCaps.dwSize = sizeof( outCaps ); result = output->GetCaps( &outCaps ); if ( FAILED( result ) ) { output->Release(); errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") getting capabilities (" << dsDevices_[ deviceIdx ].name << ")!"; errorText_ = errorStream_.str(); return FAILURE; } // Check channel information. if ( channels + firstChannel == 2 && !( outCaps.dwFlags & DSCAPS_PRIMARYSTEREO ) ) { errorStream_ << "RtApiDs::probeDeviceInfo: the output device (" << dsDevices_[ deviceIdx ].name << ") does not support stereo playback."; errorText_ = errorStream_.str(); return FAILURE; } // Check format information. Use 16-bit format unless not // supported or user requests 8-bit. if ( outCaps.dwFlags & DSCAPS_PRIMARY16BIT && !( format == RTAUDIO_SINT8 && outCaps.dwFlags & DSCAPS_PRIMARY8BIT ) ) { waveFormat.wBitsPerSample = 16; stream_.deviceFormat[mode] = RTAUDIO_SINT16; } else { waveFormat.wBitsPerSample = 8; stream_.deviceFormat[mode] = RTAUDIO_SINT8; } stream_.userFormat = format; // Update wave format structure and buffer information. waveFormat.nBlockAlign = waveFormat.nChannels * waveFormat.wBitsPerSample / 8; waveFormat.nAvgBytesPerSec = waveFormat.nSamplesPerSec * waveFormat.nBlockAlign; dsPointerLeadTime = nBuffers * (*bufferSize) * (waveFormat.wBitsPerSample / 8) * channels; // If the user wants an even bigger buffer, increase the device buffer size accordingly. while ( dsPointerLeadTime * 2U > dsBufferSize ) dsBufferSize *= 2; // Set cooperative level to DSSCL_EXCLUSIVE ... sound stops when window focus changes. // result = output->SetCooperativeLevel( hWnd, DSSCL_EXCLUSIVE ); // Set cooperative level to DSSCL_PRIORITY ... sound remains when window focus changes. result = output->SetCooperativeLevel( hWnd, DSSCL_PRIORITY ); if ( FAILED( result ) ) { output->Release(); errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") setting cooperative level (" << dsDevices_[ deviceIdx ].name << ")!"; errorText_ = errorStream_.str(); return FAILURE; } // Even though we will write to the secondary buffer, we need to // access the primary buffer to set the correct output format // (since the default is 8-bit, 22 kHz!). Setup the DS primary // buffer description. DSBUFFERDESC bufferDescription; ZeroMemory( &bufferDescription, sizeof( DSBUFFERDESC ) ); bufferDescription.dwSize = sizeof( DSBUFFERDESC ); bufferDescription.dwFlags = DSBCAPS_PRIMARYBUFFER; // Obtain the primary buffer LPDIRECTSOUNDBUFFER buffer; result = output->CreateSoundBuffer( &bufferDescription, &buffer, NULL ); if ( FAILED( result ) ) { output->Release(); errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") accessing primary buffer (" << dsDevices_[ deviceIdx ].name << ")!"; errorText_ = errorStream_.str(); return FAILURE; } // Set the primary DS buffer sound format. result = buffer->SetFormat( &waveFormat ); if ( FAILED( result ) ) { output->Release(); errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") setting primary buffer format (" << dsDevices_[ deviceIdx ].name << ")!"; errorText_ = errorStream_.str(); return FAILURE; } // Setup the secondary DS buffer description. ZeroMemory( &bufferDescription, sizeof( DSBUFFERDESC ) ); bufferDescription.dwSize = sizeof( DSBUFFERDESC ); bufferDescription.dwFlags = ( DSBCAPS_STICKYFOCUS | DSBCAPS_GLOBALFOCUS | DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_LOCHARDWARE ); // Force hardware mixing bufferDescription.dwBufferBytes = dsBufferSize; bufferDescription.lpwfxFormat = &waveFormat; // Try to create the secondary DS buffer. If that doesn't work, // try to use software mixing. Otherwise, there's a problem. result = output->CreateSoundBuffer( &bufferDescription, &buffer, NULL ); if ( FAILED( result ) ) { bufferDescription.dwFlags = ( DSBCAPS_STICKYFOCUS | DSBCAPS_GLOBALFOCUS | DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_LOCSOFTWARE ); // Force software mixing result = output->CreateSoundBuffer( &bufferDescription, &buffer, NULL ); if ( FAILED( result ) ) { output->Release(); errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") creating secondary buffer (" << dsDevices_[ deviceIdx ].name << ")!"; errorText_ = errorStream_.str(); return FAILURE; } } // Get the buffer size ... might be different from what we specified. DSBCAPS dsbcaps; dsbcaps.dwSize = sizeof( DSBCAPS ); result = buffer->GetCaps( &dsbcaps ); if ( FAILED( result ) ) { output->Release(); buffer->Release(); errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") getting buffer settings (" << dsDevices_[ deviceIdx ].name << ")!"; errorText_ = errorStream_.str(); return FAILURE; } dsBufferSize = dsbcaps.dwBufferBytes; // Lock the DS buffer LPVOID audioPtr; DWORD dataLen; result = buffer->Lock( 0, dsBufferSize, &audioPtr, &dataLen, NULL, NULL, 0 ); if ( FAILED( result ) ) { output->Release(); buffer->Release(); errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") locking buffer (" << dsDevices_[ deviceIdx ].name << ")!"; errorText_ = errorStream_.str(); return FAILURE; } // Zero the DS buffer ZeroMemory( audioPtr, dataLen ); // Unlock the DS buffer result = buffer->Unlock( audioPtr, dataLen, NULL, 0 ); if ( FAILED( result ) ) { output->Release(); buffer->Release(); errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") unlocking buffer (" << dsDevices_[ deviceIdx ].name << ")!"; errorText_ = errorStream_.str(); return FAILURE; } ohandle = (void *) output; bhandle = (void *) buffer; } if ( mode == INPUT ) { LPDIRECTSOUNDCAPTURE input; result = DirectSoundCaptureCreate( dsDevices_[ deviceIdx ].id, &input, NULL ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") opening input device (" << dsDevices_[ deviceIdx ].name << ")!"; errorText_ = errorStream_.str(); return FAILURE; } DSCCAPS inCaps; inCaps.dwSize = sizeof( inCaps ); result = input->GetCaps( &inCaps ); if ( FAILED( result ) ) { input->Release(); errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") getting input capabilities (" << dsDevices_[ deviceIdx ].name << ")!"; errorText_ = errorStream_.str(); return FAILURE; } // Check channel information. if ( inCaps.dwChannels < channels + firstChannel ) { errorText_ = "RtApiDs::probeDeviceInfo: the input device does not support requested input channels."; return FAILURE; } // Check format information. Use 16-bit format unless user // requests 8-bit. DWORD deviceFormats; if ( channels + firstChannel == 2 ) { deviceFormats = WAVE_FORMAT_1S08 | WAVE_FORMAT_2S08 | WAVE_FORMAT_4S08 | WAVE_FORMAT_96S08; if ( format == RTAUDIO_SINT8 && inCaps.dwFormats & deviceFormats ) { waveFormat.wBitsPerSample = 8; stream_.deviceFormat[mode] = RTAUDIO_SINT8; } else { // assume 16-bit is supported waveFormat.wBitsPerSample = 16; stream_.deviceFormat[mode] = RTAUDIO_SINT16; } } else { // channel == 1 deviceFormats = WAVE_FORMAT_1M08 | WAVE_FORMAT_2M08 | WAVE_FORMAT_4M08 | WAVE_FORMAT_96M08; if ( format == RTAUDIO_SINT8 && inCaps.dwFormats & deviceFormats ) { waveFormat.wBitsPerSample = 8; stream_.deviceFormat[mode] = RTAUDIO_SINT8; } else { // assume 16-bit is supported waveFormat.wBitsPerSample = 16; stream_.deviceFormat[mode] = RTAUDIO_SINT16; } } stream_.userFormat = format; // Update wave format structure and buffer information. waveFormat.nBlockAlign = waveFormat.nChannels * waveFormat.wBitsPerSample / 8; waveFormat.nAvgBytesPerSec = waveFormat.nSamplesPerSec * waveFormat.nBlockAlign; dsPointerLeadTime = nBuffers * (*bufferSize) * (waveFormat.wBitsPerSample / 8) * channels; // If the user wants an even bigger buffer, increase the device buffer size accordingly. while ( dsPointerLeadTime * 2U > dsBufferSize ) dsBufferSize *= 2; // Setup the secondary DS buffer description. DSCBUFFERDESC bufferDescription; ZeroMemory( &bufferDescription, sizeof( DSCBUFFERDESC ) ); bufferDescription.dwSize = sizeof( DSCBUFFERDESC ); bufferDescription.dwFlags = 0; bufferDescription.dwReserved = 0; bufferDescription.dwBufferBytes = dsBufferSize; bufferDescription.lpwfxFormat = &waveFormat; // Create the capture buffer. LPDIRECTSOUNDCAPTUREBUFFER buffer; result = input->CreateCaptureBuffer( &bufferDescription, &buffer, NULL ); if ( FAILED( result ) ) { input->Release(); errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") creating input buffer (" << dsDevices_[ deviceIdx ].name << ")!"; errorText_ = errorStream_.str(); return FAILURE; } // Get the buffer size ... might be different from what we specified. DSCBCAPS dscbcaps; dscbcaps.dwSize = sizeof( DSCBCAPS ); result = buffer->GetCaps( &dscbcaps ); if ( FAILED( result ) ) { input->Release(); buffer->Release(); errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") getting buffer settings (" << dsDevices_[ deviceIdx ].name << ")!"; errorText_ = errorStream_.str(); return FAILURE; } dsBufferSize = dscbcaps.dwBufferBytes; // NOTE: We could have a problem here if this is a duplex stream // and the play and capture hardware buffer sizes are different // (I'm actually not sure if that is a problem or not). // Currently, we are not verifying that. // Lock the capture buffer LPVOID audioPtr; DWORD dataLen; result = buffer->Lock( 0, dsBufferSize, &audioPtr, &dataLen, NULL, NULL, 0 ); if ( FAILED( result ) ) { input->Release(); buffer->Release(); errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") locking input buffer (" << dsDevices_[ deviceIdx ].name << ")!"; errorText_ = errorStream_.str(); return FAILURE; } // Zero the buffer ZeroMemory( audioPtr, dataLen ); // Unlock the buffer result = buffer->Unlock( audioPtr, dataLen, NULL, 0 ); if ( FAILED( result ) ) { input->Release(); buffer->Release(); errorStream_ << "RtApiDs::probeDeviceOpen: error (" << getErrorString( result ) << ") unlocking input buffer (" << dsDevices_[ deviceIdx ].name << ")!"; errorText_ = errorStream_.str(); return FAILURE; } ohandle = (void *) input; bhandle = (void *) buffer; } // Set various stream parameters DsHandle *handle = 0; stream_.nDeviceChannels[mode] = channels + firstChannel; stream_.nUserChannels[mode] = channels; stream_.bufferSize = *bufferSize; stream_.channelOffset[mode] = firstChannel; stream_.deviceInterleaved[mode] = true; if ( options && options->flags & RTAUDIO_NONINTERLEAVED ) stream_.userInterleaved = false; else stream_.userInterleaved = true; // Set flag for buffer conversion stream_.doConvertBuffer[mode] = false; if (stream_.nUserChannels[mode] != stream_.nDeviceChannels[mode]) stream_.doConvertBuffer[mode] = true; if (stream_.userFormat != stream_.deviceFormat[mode]) stream_.doConvertBuffer[mode] = true; if ( stream_.userInterleaved != stream_.deviceInterleaved[mode] && stream_.nUserChannels[mode] > 1 ) stream_.doConvertBuffer[mode] = true; // Allocate necessary internal buffers long bufferBytes = stream_.nUserChannels[mode] * *bufferSize * formatBytes( stream_.userFormat ); stream_.userBuffer[mode] = (char *) calloc( bufferBytes, 1 ); if ( stream_.userBuffer[mode] == NULL ) { errorText_ = "RtApiDs::probeDeviceOpen: error allocating user buffer memory."; goto error; } if ( stream_.doConvertBuffer[mode] ) { bool makeBuffer = true; bufferBytes = stream_.nDeviceChannels[mode] * formatBytes( stream_.deviceFormat[mode] ); if ( mode == INPUT ) { if ( stream_.mode == OUTPUT && stream_.deviceBuffer ) { unsigned long bytesOut = stream_.nDeviceChannels[0] * formatBytes( stream_.deviceFormat[0] ); if ( bufferBytes <= (long) bytesOut ) makeBuffer = false; } } if ( makeBuffer ) { bufferBytes *= *bufferSize; if ( stream_.deviceBuffer ) free( stream_.deviceBuffer ); stream_.deviceBuffer = (char *) calloc( bufferBytes, 1 ); if ( stream_.deviceBuffer == NULL ) { errorText_ = "RtApiDs::probeDeviceOpen: error allocating device buffer memory."; goto error; } } } // Allocate our DsHandle structures for the stream. if ( stream_.apiHandle == 0 ) { try { handle = new DsHandle; } catch ( std::bad_alloc& ) { errorText_ = "RtApiDs::probeDeviceOpen: error allocating AsioHandle memory."; goto error; } // Create a manual-reset event. handle->condition = CreateEvent( NULL, // no security TRUE, // manual-reset FALSE, // non-signaled initially NULL ); // unnamed stream_.apiHandle = (void *) handle; } else handle = (DsHandle *) stream_.apiHandle; handle->id[mode] = ohandle; handle->buffer[mode] = bhandle; handle->dsBufferSize[mode] = dsBufferSize; handle->dsPointerLeadTime[mode] = dsPointerLeadTime; stream_.deviceId[mode] = deviceIdx; stream_.state = STREAM_STOPPED; if ( stream_.mode == OUTPUT && mode == INPUT ) // We had already set up an output stream. stream_.mode = DUPLEX; else stream_.mode = mode; stream_.nBuffers = nBuffers; stream_.sampleRate = sampleRate; // Setup the buffer conversion information structure. if ( stream_.doConvertBuffer[mode] ) setConvertInfo( mode, firstChannel ); // Setup the callback thread. if ( stream_.callbackInfo.isRunning == false ) { unsigned threadId; stream_.callbackInfo.isRunning = true; stream_.callbackInfo.object = (void *) this; stream_.callbackInfo.thread = _beginthreadex( NULL, 0, &callbackHandler, &stream_.callbackInfo, 0, &threadId ); if ( stream_.callbackInfo.thread == 0 ) { errorText_ = "RtApiDs::probeDeviceOpen: error creating callback thread!"; goto error; } // Boost DS thread priority SetThreadPriority( (HANDLE) stream_.callbackInfo.thread, THREAD_PRIORITY_HIGHEST ); } return SUCCESS; error: if ( handle ) { if ( handle->buffer[0] ) { // the object pointer can be NULL and valid LPDIRECTSOUND object = (LPDIRECTSOUND) handle->id[0]; LPDIRECTSOUNDBUFFER buffer = (LPDIRECTSOUNDBUFFER) handle->buffer[0]; if ( buffer ) buffer->Release(); object->Release(); } if ( handle->buffer[1] ) { LPDIRECTSOUNDCAPTURE object = (LPDIRECTSOUNDCAPTURE) handle->id[1]; LPDIRECTSOUNDCAPTUREBUFFER buffer = (LPDIRECTSOUNDCAPTUREBUFFER) handle->buffer[1]; if ( buffer ) buffer->Release(); object->Release(); } CloseHandle( handle->condition ); delete handle; stream_.apiHandle = 0; } for ( int i=0; i<2; i++ ) { if ( stream_.userBuffer[i] ) { free( stream_.userBuffer[i] ); stream_.userBuffer[i] = 0; } } if ( stream_.deviceBuffer ) { free( stream_.deviceBuffer ); stream_.deviceBuffer = 0; } stream_.state = STREAM_CLOSED; return FAILURE; } void RtApiDs :: closeStream() { if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiDs::closeStream(): no open stream to close!"; error( RTAUDIO_WARNING ); return; } // Stop the callback thread. stream_.callbackInfo.isRunning = false; WaitForSingleObject( (HANDLE) stream_.callbackInfo.thread, INFINITE ); CloseHandle( (HANDLE) stream_.callbackInfo.thread ); DsHandle *handle = (DsHandle *) stream_.apiHandle; if ( handle ) { if ( handle->buffer[0] ) { // the object pointer can be NULL and valid LPDIRECTSOUND object = (LPDIRECTSOUND) handle->id[0]; LPDIRECTSOUNDBUFFER buffer = (LPDIRECTSOUNDBUFFER) handle->buffer[0]; if ( buffer ) { buffer->Stop(); buffer->Release(); } object->Release(); } if ( handle->buffer[1] ) { LPDIRECTSOUNDCAPTURE object = (LPDIRECTSOUNDCAPTURE) handle->id[1]; LPDIRECTSOUNDCAPTUREBUFFER buffer = (LPDIRECTSOUNDCAPTUREBUFFER) handle->buffer[1]; if ( buffer ) { buffer->Stop(); buffer->Release(); } object->Release(); } CloseHandle( handle->condition ); delete handle; stream_.apiHandle = 0; } for ( int i=0; i<2; i++ ) { if ( stream_.userBuffer[i] ) { free( stream_.userBuffer[i] ); stream_.userBuffer[i] = 0; } } if ( stream_.deviceBuffer ) { free( stream_.deviceBuffer ); stream_.deviceBuffer = 0; } clearStreamInfo(); //stream_.mode = UNINITIALIZED; //stream_.state = STREAM_CLOSED; } RtAudioErrorType RtApiDs :: startStream() { if ( stream_.state != STREAM_STOPPED ) { if ( stream_.state == STREAM_RUNNING ) errorText_ = "RtApiDs::startStream(): the stream is already running!"; else if ( stream_.state == STREAM_STOPPING || stream_.state == STREAM_CLOSED ) errorText_ = "RtApiDs::startStream(): the stream is stopping or closed!"; return error( RTAUDIO_WARNING ); } /* #if defined( HAVE_GETTIMEOFDAY ) gettimeofday( &stream_.lastTickTimestamp, NULL ); #endif */ DsHandle *handle = (DsHandle *) stream_.apiHandle; // Increase scheduler frequency on lesser windows (a side-effect of // increasing timer accuracy). On greater windows (Win2K or later), // this is already in effect. timeBeginPeriod( 1 ); buffersRolling = false; duplexPrerollBytes = 0; if ( stream_.mode == DUPLEX ) { // 0.5 seconds of silence in DUPLEX mode while the devices spin up and synchronize. duplexPrerollBytes = (int) ( 0.5 * stream_.sampleRate * formatBytes( stream_.deviceFormat[1] ) * stream_.nDeviceChannels[1] ); } HRESULT result = 0; if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { LPDIRECTSOUNDBUFFER buffer = (LPDIRECTSOUNDBUFFER) handle->buffer[0]; result = buffer->Play( 0, 0, DSBPLAY_LOOPING ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::startStream: error (" << getErrorString( result ) << ") starting output buffer!"; errorText_ = errorStream_.str(); goto unlock; } } if ( stream_.mode == INPUT || stream_.mode == DUPLEX ) { LPDIRECTSOUNDCAPTUREBUFFER buffer = (LPDIRECTSOUNDCAPTUREBUFFER) handle->buffer[1]; result = buffer->Start( DSCBSTART_LOOPING ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::startStream: error (" << getErrorString( result ) << ") starting input buffer!"; errorText_ = errorStream_.str(); goto unlock; } } handle->drainCounter = 0; handle->internalDrain = false; ResetEvent( handle->condition ); stream_.state = STREAM_RUNNING; unlock: if ( FAILED( result ) ) error( RTAUDIO_SYSTEM_ERROR ); return RTAUDIO_NO_ERROR; } RtAudioErrorType RtApiDs :: stopStream() { if ( stream_.state != STREAM_RUNNING && stream_.state != STREAM_STOPPING ) { if ( stream_.state == STREAM_STOPPED ) errorText_ = "RtApiDs::stopStream(): the stream is already stopped!"; else if ( stream_.state == STREAM_CLOSED ) errorText_ = "RtApiDs::stopStream(): the stream is closed!"; return error( RTAUDIO_WARNING ); } HRESULT result = 0; LPVOID audioPtr; DWORD dataLen; DsHandle *handle = (DsHandle *) stream_.apiHandle; if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { if ( handle->drainCounter == 0 ) { handle->drainCounter = 2; WaitForSingleObject( handle->condition, INFINITE ); // block until signaled } stream_.state = STREAM_STOPPED; MUTEX_LOCK( &stream_.mutex ); // Stop the buffer and clear memory LPDIRECTSOUNDBUFFER buffer = (LPDIRECTSOUNDBUFFER) handle->buffer[0]; result = buffer->Stop(); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::stopStream: error (" << getErrorString( result ) << ") stopping output buffer!"; errorText_ = errorStream_.str(); goto unlock; } // Lock the buffer and clear it so that if we start to play again, // we won't have old data playing. result = buffer->Lock( 0, handle->dsBufferSize[0], &audioPtr, &dataLen, NULL, NULL, 0 ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::stopStream: error (" << getErrorString( result ) << ") locking output buffer!"; errorText_ = errorStream_.str(); goto unlock; } // Zero the DS buffer ZeroMemory( audioPtr, dataLen ); // Unlock the DS buffer result = buffer->Unlock( audioPtr, dataLen, NULL, 0 ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::stopStream: error (" << getErrorString( result ) << ") unlocking output buffer!"; errorText_ = errorStream_.str(); goto unlock; } // If we start playing again, we must begin at beginning of buffer. handle->bufferPointer[0] = 0; } if ( stream_.mode == INPUT || stream_.mode == DUPLEX ) { LPDIRECTSOUNDCAPTUREBUFFER buffer = (LPDIRECTSOUNDCAPTUREBUFFER) handle->buffer[1]; audioPtr = NULL; dataLen = 0; stream_.state = STREAM_STOPPED; if ( stream_.mode != DUPLEX ) MUTEX_LOCK( &stream_.mutex ); result = buffer->Stop(); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::stopStream: error (" << getErrorString( result ) << ") stopping input buffer!"; errorText_ = errorStream_.str(); goto unlock; } // Lock the buffer and clear it so that if we start to play again, // we won't have old data playing. result = buffer->Lock( 0, handle->dsBufferSize[1], &audioPtr, &dataLen, NULL, NULL, 0 ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::stopStream: error (" << getErrorString( result ) << ") locking input buffer!"; errorText_ = errorStream_.str(); goto unlock; } // Zero the DS buffer ZeroMemory( audioPtr, dataLen ); // Unlock the DS buffer result = buffer->Unlock( audioPtr, dataLen, NULL, 0 ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::stopStream: error (" << getErrorString( result ) << ") unlocking input buffer!"; errorText_ = errorStream_.str(); goto unlock; } // If we start recording again, we must begin at beginning of buffer. handle->bufferPointer[1] = 0; } unlock: timeEndPeriod( 1 ); // revert to normal scheduler frequency on lesser windows. MUTEX_UNLOCK( &stream_.mutex ); if ( FAILED( result ) ) error( RTAUDIO_SYSTEM_ERROR ); return RTAUDIO_NO_ERROR; } RtAudioErrorType RtApiDs :: abortStream() { if ( stream_.state != STREAM_RUNNING ) { if ( stream_.state == STREAM_STOPPED ) errorText_ = "RtApiDs::abortStream(): the stream is already stopped!"; else if ( stream_.state == STREAM_STOPPING || stream_.state == STREAM_CLOSED ) errorText_ = "RtApiDs::abortStream(): the stream is stopping or closed!"; return error( RTAUDIO_WARNING ); } DsHandle *handle = (DsHandle *) stream_.apiHandle; handle->drainCounter = 2; return stopStream(); } void RtApiDs :: callbackEvent() { if ( stream_.state == STREAM_STOPPED || stream_.state == STREAM_STOPPING ) { Sleep( 50 ); // sleep 50 milliseconds return; } if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiDs::callbackEvent(): the stream is closed ... this shouldn't happen!"; error( RTAUDIO_WARNING ); return; } CallbackInfo *info = (CallbackInfo *) &stream_.callbackInfo; DsHandle *handle = (DsHandle *) stream_.apiHandle; // Check if we were draining the stream and signal is finished. if ( handle->drainCounter > stream_.nBuffers + 2 ) { stream_.state = STREAM_STOPPING; if ( handle->internalDrain == false ) SetEvent( handle->condition ); else stopStream(); return; } // Invoke user callback to get fresh output data UNLESS we are // draining stream. if ( handle->drainCounter == 0 ) { RtAudioCallback callback = (RtAudioCallback) info->callback; double streamTime = getStreamTime(); RtAudioStreamStatus status = 0; if ( stream_.mode != INPUT && handle->xrun[0] == true ) { status |= RTAUDIO_OUTPUT_UNDERFLOW; handle->xrun[0] = false; } if ( stream_.mode != OUTPUT && handle->xrun[1] == true ) { status |= RTAUDIO_INPUT_OVERFLOW; handle->xrun[1] = false; } int cbReturnValue = callback( stream_.userBuffer[0], stream_.userBuffer[1], stream_.bufferSize, streamTime, status, info->userData ); if ( cbReturnValue == 2 ) { stream_.state = STREAM_STOPPING; handle->drainCounter = 2; abortStream(); return; } else if ( cbReturnValue == 1 ) { handle->drainCounter = 1; handle->internalDrain = true; } } HRESULT result; DWORD currentWritePointer, safeWritePointer; DWORD currentReadPointer, safeReadPointer; UINT nextWritePointer; LPVOID buffer1 = NULL; LPVOID buffer2 = NULL; DWORD bufferSize1 = 0; DWORD bufferSize2 = 0; char *buffer; long bufferBytes; MUTEX_LOCK( &stream_.mutex ); if ( stream_.state == STREAM_STOPPED ) { MUTEX_UNLOCK( &stream_.mutex ); return; } if ( buffersRolling == false ) { if ( stream_.mode == DUPLEX ) { //assert( handle->dsBufferSize[0] == handle->dsBufferSize[1] ); // It takes a while for the devices to get rolling. As a result, // there's no guarantee that the capture and write device pointers // will move in lockstep. Wait here for both devices to start // rolling, and then set our buffer pointers accordingly. // e.g. Crystal Drivers: the capture buffer starts up 5700 to 9600 // bytes later than the write buffer. // Stub: a serious risk of having a pre-emptive scheduling round // take place between the two GetCurrentPosition calls... but I'm // really not sure how to solve the problem. Temporarily boost to // Realtime priority, maybe; but I'm not sure what priority the // DirectSound service threads run at. We *should* be roughly // within a ms or so of correct. LPDIRECTSOUNDBUFFER dsWriteBuffer = (LPDIRECTSOUNDBUFFER) handle->buffer[0]; LPDIRECTSOUNDCAPTUREBUFFER dsCaptureBuffer = (LPDIRECTSOUNDCAPTUREBUFFER) handle->buffer[1]; DWORD startSafeWritePointer, startSafeReadPointer; result = dsWriteBuffer->GetCurrentPosition( NULL, &startSafeWritePointer ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current write position!"; errorText_ = errorStream_.str(); MUTEX_UNLOCK( &stream_.mutex ); error( RTAUDIO_SYSTEM_ERROR ); return; } result = dsCaptureBuffer->GetCurrentPosition( NULL, &startSafeReadPointer ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current read position!"; errorText_ = errorStream_.str(); MUTEX_UNLOCK( &stream_.mutex ); error( RTAUDIO_SYSTEM_ERROR ); return; } while ( true ) { result = dsWriteBuffer->GetCurrentPosition( NULL, &safeWritePointer ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current write position!"; errorText_ = errorStream_.str(); MUTEX_UNLOCK( &stream_.mutex ); error( RTAUDIO_SYSTEM_ERROR ); return; } result = dsCaptureBuffer->GetCurrentPosition( NULL, &safeReadPointer ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current read position!"; errorText_ = errorStream_.str(); MUTEX_UNLOCK( &stream_.mutex ); error( RTAUDIO_SYSTEM_ERROR ); return; } if ( safeWritePointer != startSafeWritePointer && safeReadPointer != startSafeReadPointer ) break; Sleep( 1 ); } //assert( handle->dsBufferSize[0] == handle->dsBufferSize[1] ); handle->bufferPointer[0] = safeWritePointer + handle->dsPointerLeadTime[0]; if ( handle->bufferPointer[0] >= handle->dsBufferSize[0] ) handle->bufferPointer[0] -= handle->dsBufferSize[0]; handle->bufferPointer[1] = safeReadPointer; } else if ( stream_.mode == OUTPUT ) { // Set the proper nextWritePosition after initial startup. LPDIRECTSOUNDBUFFER dsWriteBuffer = (LPDIRECTSOUNDBUFFER) handle->buffer[0]; result = dsWriteBuffer->GetCurrentPosition( ¤tWritePointer, &safeWritePointer ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current write position!"; errorText_ = errorStream_.str(); MUTEX_UNLOCK( &stream_.mutex ); error( RTAUDIO_SYSTEM_ERROR ); return; } handle->bufferPointer[0] = safeWritePointer + handle->dsPointerLeadTime[0]; if ( handle->bufferPointer[0] >= handle->dsBufferSize[0] ) handle->bufferPointer[0] -= handle->dsBufferSize[0]; } buffersRolling = true; } if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { LPDIRECTSOUNDBUFFER dsBuffer = (LPDIRECTSOUNDBUFFER) handle->buffer[0]; if ( handle->drainCounter > 1 ) { // write zeros to the output stream bufferBytes = stream_.bufferSize * stream_.nUserChannels[0]; bufferBytes *= formatBytes( stream_.userFormat ); memset( stream_.userBuffer[0], 0, bufferBytes ); } // Setup parameters and do buffer conversion if necessary. if ( stream_.doConvertBuffer[0] ) { buffer = stream_.deviceBuffer; convertBuffer( buffer, stream_.userBuffer[0], stream_.convertInfo[0] ); bufferBytes = stream_.bufferSize * stream_.nDeviceChannels[0]; bufferBytes *= formatBytes( stream_.deviceFormat[0] ); } else { buffer = stream_.userBuffer[0]; bufferBytes = stream_.bufferSize * stream_.nUserChannels[0]; bufferBytes *= formatBytes( stream_.userFormat ); } // No byte swapping necessary in DirectSound implementation. // Ahhh ... windoze. 16-bit data is signed but 8-bit data is // unsigned. So, we need to convert our signed 8-bit data here to // unsigned. if ( stream_.deviceFormat[0] == RTAUDIO_SINT8 ) for ( int i=0; idsBufferSize[0]; nextWritePointer = handle->bufferPointer[0]; DWORD endWrite, leadPointer; while ( true ) { // Find out where the read and "safe write" pointers are. result = dsBuffer->GetCurrentPosition( ¤tWritePointer, &safeWritePointer ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current write position!"; errorText_ = errorStream_.str(); MUTEX_UNLOCK( &stream_.mutex ); error( RTAUDIO_SYSTEM_ERROR ); return; } // We will copy our output buffer into the region between // safeWritePointer and leadPointer. If leadPointer is not // beyond the next endWrite position, wait until it is. leadPointer = safeWritePointer + handle->dsPointerLeadTime[0]; //std::cout << "safeWritePointer = " << safeWritePointer << ", leadPointer = " << leadPointer << ", nextWritePointer = " << nextWritePointer << std::endl; if ( leadPointer > dsBufferSize ) leadPointer -= dsBufferSize; if ( leadPointer < nextWritePointer ) leadPointer += dsBufferSize; // unwrap offset endWrite = nextWritePointer + bufferBytes; // Check whether the entire write region is behind the play pointer. if ( leadPointer >= endWrite ) break; // If we are here, then we must wait until the leadPointer advances // beyond the end of our next write region. We use the // Sleep() function to suspend operation until that happens. double millis = ( endWrite - leadPointer ) * 1000.0; millis /= ( formatBytes( stream_.deviceFormat[0]) * stream_.nDeviceChannels[0] * stream_.sampleRate); if ( millis < 1.0 ) millis = 1.0; Sleep( (DWORD) millis ); } if ( dsPointerBetween( nextWritePointer, safeWritePointer, currentWritePointer, dsBufferSize ) || dsPointerBetween( endWrite, safeWritePointer, currentWritePointer, dsBufferSize ) ) { // We've strayed into the forbidden zone ... resync the read pointer. handle->xrun[0] = true; nextWritePointer = safeWritePointer + handle->dsPointerLeadTime[0] - bufferBytes; if ( nextWritePointer >= dsBufferSize ) nextWritePointer -= dsBufferSize; handle->bufferPointer[0] = nextWritePointer; endWrite = nextWritePointer + bufferBytes; } // Lock free space in the buffer result = dsBuffer->Lock( nextWritePointer, bufferBytes, &buffer1, &bufferSize1, &buffer2, &bufferSize2, 0 ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") locking buffer during playback!"; errorText_ = errorStream_.str(); MUTEX_UNLOCK( &stream_.mutex ); error( RTAUDIO_SYSTEM_ERROR ); return; } // Copy our buffer into the DS buffer CopyMemory( buffer1, buffer, bufferSize1 ); if ( buffer2 != NULL ) CopyMemory( buffer2, buffer+bufferSize1, bufferSize2 ); // Update our buffer offset and unlock sound buffer dsBuffer->Unlock( buffer1, bufferSize1, buffer2, bufferSize2 ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") unlocking buffer during playback!"; errorText_ = errorStream_.str(); MUTEX_UNLOCK( &stream_.mutex ); error( RTAUDIO_SYSTEM_ERROR ); return; } nextWritePointer = ( nextWritePointer + bufferSize1 + bufferSize2 ) % dsBufferSize; handle->bufferPointer[0] = nextWritePointer; } // Don't bother draining input if ( handle->drainCounter ) { handle->drainCounter++; goto unlock; } if ( stream_.mode == INPUT || stream_.mode == DUPLEX ) { // Setup parameters. if ( stream_.doConvertBuffer[1] ) { buffer = stream_.deviceBuffer; bufferBytes = stream_.bufferSize * stream_.nDeviceChannels[1]; bufferBytes *= formatBytes( stream_.deviceFormat[1] ); } else { buffer = stream_.userBuffer[1]; bufferBytes = stream_.bufferSize * stream_.nUserChannels[1]; bufferBytes *= formatBytes( stream_.userFormat ); } LPDIRECTSOUNDCAPTUREBUFFER dsBuffer = (LPDIRECTSOUNDCAPTUREBUFFER) handle->buffer[1]; long nextReadPointer = handle->bufferPointer[1]; DWORD dsBufferSize = handle->dsBufferSize[1]; // Find out where the write and "safe read" pointers are. result = dsBuffer->GetCurrentPosition( ¤tReadPointer, &safeReadPointer ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current read position!"; errorText_ = errorStream_.str(); MUTEX_UNLOCK( &stream_.mutex ); error( RTAUDIO_SYSTEM_ERROR ); return; } if ( safeReadPointer < (DWORD)nextReadPointer ) safeReadPointer += dsBufferSize; // unwrap offset DWORD endRead = nextReadPointer + bufferBytes; // Handling depends on whether we are INPUT or DUPLEX. // If we're in INPUT mode then waiting is a good thing. If we're in DUPLEX mode, // then a wait here will drag the write pointers into the forbidden zone. // // In DUPLEX mode, rather than wait, we will back off the read pointer until // it's in a safe position. This causes dropouts, but it seems to be the only // practical way to sync up the read and write pointers reliably, given the // the very complex relationship between phase and increment of the read and write // pointers. // // In order to minimize audible dropouts in DUPLEX mode, we will // provide a pre-roll period of 0.5 seconds in which we return // zeros from the read buffer while the pointers sync up. if ( stream_.mode == DUPLEX ) { if ( safeReadPointer < endRead ) { if ( duplexPrerollBytes <= 0 ) { // Pre-roll time over. Be more aggressive. int adjustment = endRead-safeReadPointer; handle->xrun[1] = true; // Two cases: // - large adjustments: we've probably run out of CPU cycles, so just resync exactly, // and perform fine adjustments later. // - small adjustments: back off by twice as much. if ( adjustment >= 2*bufferBytes ) nextReadPointer = safeReadPointer-2*bufferBytes; else nextReadPointer = safeReadPointer-bufferBytes-adjustment; if ( nextReadPointer < 0 ) nextReadPointer += dsBufferSize; } else { // In pre=roll time. Just do it. nextReadPointer = safeReadPointer - bufferBytes; while ( nextReadPointer < 0 ) nextReadPointer += dsBufferSize; } endRead = nextReadPointer + bufferBytes; } } else { // mode == INPUT while ( safeReadPointer < endRead && stream_.callbackInfo.isRunning ) { // See comments for playback. double millis = (endRead - safeReadPointer) * 1000.0; millis /= ( formatBytes(stream_.deviceFormat[1]) * stream_.nDeviceChannels[1] * stream_.sampleRate); if ( millis < 1.0 ) millis = 1.0; Sleep( (DWORD) millis ); // Wake up and find out where we are now. result = dsBuffer->GetCurrentPosition( ¤tReadPointer, &safeReadPointer ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") getting current read position!"; errorText_ = errorStream_.str(); MUTEX_UNLOCK( &stream_.mutex ); error( RTAUDIO_SYSTEM_ERROR ); return; } if ( safeReadPointer < (DWORD)nextReadPointer ) safeReadPointer += dsBufferSize; // unwrap offset } } // Lock free space in the buffer result = dsBuffer->Lock( nextReadPointer, bufferBytes, &buffer1, &bufferSize1, &buffer2, &bufferSize2, 0 ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") locking capture buffer!"; errorText_ = errorStream_.str(); MUTEX_UNLOCK( &stream_.mutex ); error( RTAUDIO_SYSTEM_ERROR ); return; } if ( duplexPrerollBytes <= 0 ) { // Copy our buffer into the DS buffer CopyMemory( buffer, buffer1, bufferSize1 ); if ( buffer2 != NULL ) CopyMemory( buffer+bufferSize1, buffer2, bufferSize2 ); } else { memset( buffer, 0, bufferSize1 ); if ( buffer2 != NULL ) memset( buffer + bufferSize1, 0, bufferSize2 ); duplexPrerollBytes -= bufferSize1 + bufferSize2; } // Update our buffer offset and unlock sound buffer nextReadPointer = ( nextReadPointer + bufferSize1 + bufferSize2 ) % dsBufferSize; dsBuffer->Unlock( buffer1, bufferSize1, buffer2, bufferSize2 ); if ( FAILED( result ) ) { errorStream_ << "RtApiDs::callbackEvent: error (" << getErrorString( result ) << ") unlocking capture buffer!"; errorText_ = errorStream_.str(); MUTEX_UNLOCK( &stream_.mutex ); error( RTAUDIO_SYSTEM_ERROR ); return; } handle->bufferPointer[1] = nextReadPointer; // No byte swapping necessary in DirectSound implementation. // If necessary, convert 8-bit data from unsigned to signed. if ( stream_.deviceFormat[1] == RTAUDIO_SINT8 ) for ( int j=0; jobject; bool* isRunning = &info->isRunning; while ( *isRunning == true ) { object->callbackEvent(); } _endthreadex( 0 ); return 0; } static BOOL CALLBACK deviceQueryCallback( LPGUID lpguid, LPCTSTR description, LPCTSTR lpctstr, LPVOID lpContext ) { struct DsProbeData& probeInfo = *(struct DsProbeData*) lpContext; std::vector& dsDevices = *probeInfo.dsDevices; HRESULT hr; bool validDevice = false; if ( probeInfo.isInput == true ) { DSCCAPS caps; LPDIRECTSOUNDCAPTURE object; hr = DirectSoundCaptureCreate( lpguid, &object, NULL ); if ( hr != DS_OK ) return TRUE; caps.dwSize = sizeof(caps); hr = object->GetCaps( &caps ); if ( hr == DS_OK ) { if ( caps.dwChannels > 0 && caps.dwFormats > 0 ) validDevice = true; } object->Release(); } else { DSCAPS caps; LPDIRECTSOUND object; hr = DirectSoundCreate( lpguid, &object, NULL ); if ( hr != DS_OK ) return TRUE; caps.dwSize = sizeof(caps); hr = object->GetCaps( &caps ); if ( hr == DS_OK ) { if ( caps.dwFlags & DSCAPS_PRIMARYMONO || caps.dwFlags & DSCAPS_PRIMARYSTEREO ) validDevice = true; } object->Release(); } if ( validDevice ) { // If good device, then save its name and guid. DsDevice device; device.name = convertCharPointerToStdString( description ); device.epID = convertCharPointerToStdString(lpctstr); device.id = lpguid; device.isInput = probeInfo.isInput; dsDevices.push_back( device ); } return TRUE; } static const char* getErrorString( int code ) { switch ( code ) { case DSERR_ALLOCATED: return "Already allocated"; case DSERR_CONTROLUNAVAIL: return "Control unavailable"; case DSERR_INVALIDPARAM: return "Invalid parameter"; case DSERR_INVALIDCALL: return "Invalid call"; case DSERR_GENERIC: return "Generic error"; case DSERR_PRIOLEVELNEEDED: return "Priority level needed"; case DSERR_OUTOFMEMORY: return "Out of memory"; case DSERR_BADFORMAT: return "The sample rate or the channel format is not supported"; case DSERR_UNSUPPORTED: return "Not supported"; case DSERR_NODRIVER: return "No driver"; case DSERR_ALREADYINITIALIZED: return "Already initialized"; case DSERR_NOAGGREGATION: return "No aggregation"; case DSERR_BUFFERLOST: return "Buffer lost"; case DSERR_OTHERAPPHASPRIO: return "Another application already has priority"; case DSERR_UNINITIALIZED: return "Uninitialized"; default: return "DirectSound unknown error"; } } //******************** End of __WINDOWS_DS__ *********************// #endif #if defined(__LINUX_ALSA__) #include #include // A structure to hold various information related to the ALSA API // implementation. struct AlsaHandle { snd_pcm_t *handles[2]; bool synchronized; bool xrun[2]; pthread_cond_t runnable_cv; bool runnable; AlsaHandle() #if _cplusplus >= 201103L :handles{nullptr, nullptr}, synchronized(false), runnable(false) { xrun[0] = false; xrun[1] = false; } #else : synchronized(false), runnable(false) { handles[0] = NULL; handles[1] = NULL; xrun[0] = false; xrun[1] = false; } #endif }; static void *alsaCallbackHandler( void * ptr ); RtApiAlsa :: RtApiAlsa() { // Nothing to do here. } RtApiAlsa :: ~RtApiAlsa() { if ( stream_.state != STREAM_CLOSED ) closeStream(); } void RtApiAlsa :: probeDevices( void ) { // See list of required functionality in RtApi::probeDevices(). int result, device, card; char name[128]; snd_ctl_t *handle = 0; snd_ctl_card_info_t *ctlinfo; snd_pcm_info_t *pcminfo; snd_ctl_card_info_alloca(&ctlinfo); snd_pcm_info_alloca(&pcminfo); // First element isthe device hw ID, second is the device "pretty name" std::vector> deviceID_prettyName; snd_pcm_stream_t stream; std::string defaultDeviceName; // Add the default interface if available. result = snd_ctl_open( &handle, "default", 0 ); if (result == 0) { deviceID_prettyName.push_back({"default", "Default ALSA Device"}); defaultDeviceName = deviceID_prettyName[0].second; snd_ctl_close( handle ); } // Add the Pulse interface if available. result = snd_ctl_open( &handle, "pulse", 0 ); if (result == 0) { deviceID_prettyName.push_back({"pulse", "PulseAudio Sound Server"}); snd_ctl_close( handle ); } // Count cards and devices and get ascii identifiers. card = -1; snd_card_next( &card ); while ( card >= 0 ) { sprintf( name, "hw:%d", card ); result = snd_ctl_open( &handle, name, 0 ); if ( result < 0 ) { handle = 0; errorStream_ << "RtApiAlsa::probeDevices: control open, card = " << card << ", " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); error( RTAUDIO_WARNING ); goto nextcard; } result = snd_ctl_card_info( handle, ctlinfo ); if ( result < 0 ) { errorStream_ << "RtApiAlsa::probeDevices: control info, card = " << card << ", " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); error( RTAUDIO_WARNING ); goto nextcard; } device = -1; while( 1 ) { result = snd_ctl_pcm_next_device( handle, &device ); if ( result < 0 ) { errorStream_ << "RtApiAlsa::probeDevices: control next device, card = " << card << ", " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); error( RTAUDIO_WARNING ); break; } if ( device < 0 ) break; snd_pcm_info_set_device( pcminfo, device ); snd_pcm_info_set_subdevice( pcminfo, 0 ); stream = SND_PCM_STREAM_PLAYBACK; snd_pcm_info_set_stream( pcminfo, stream ); result = snd_ctl_pcm_info( handle, pcminfo ); if ( result < 0 ) { if ( result == -ENOENT ) { // try as input stream stream = SND_PCM_STREAM_CAPTURE; snd_pcm_info_set_stream( pcminfo, stream ); result = snd_ctl_pcm_info( handle, pcminfo ); if ( result < 0 ) { errorStream_ << "RtApiAlsa::probeDevices: control pcm info, card = " << card << ", device = " << device << ", " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); error( RTAUDIO_WARNING ); continue; } } else continue; } sprintf( name, "hw:%s,%d", snd_ctl_card_info_get_id(ctlinfo), device ); std::string id(name); sprintf( name, "%s (%s)", snd_ctl_card_info_get_name(ctlinfo), snd_pcm_info_get_id(pcminfo) ); std::string prettyName(name); deviceID_prettyName.push_back( {id, prettyName} ); if ( card == 0 && device == 0 && defaultDeviceName.empty() ) defaultDeviceName = name; } nextcard: if ( handle ) snd_ctl_close( handle ); snd_card_next( &card ); } if ( deviceID_prettyName.size() == 0 ) { deviceList_.clear(); deviceIdPairs_.clear(); return; } // Clean removed devices for ( auto it = deviceIdPairs_.begin(); it != deviceIdPairs_.end(); ) { bool found = false; for ( auto& d: deviceID_prettyName ) { if ( d.first == (*it).first ) { found = true; break; } } if ( found ) ++it; else it = deviceIdPairs_.erase(it); } // Fill or update the deviceList_ and also save a corresponding list of Ids. for ( auto& d : deviceID_prettyName ) { bool found = false; for ( auto& dID : deviceIdPairs_ ) { if ( d.first == dID.first ) { found = true; break; // We already have this device. } } if ( found ) continue; // new device RtAudio::DeviceInfo info; info.name = d.second; if ( probeDeviceInfo( info, d.first ) == false ) continue; // ignore if probe fails info.ID = currentDeviceId_++; // arbitrary internal device ID if ( info.name == defaultDeviceName ) { if ( info.outputChannels > 0 ) info.isDefaultOutput = true; if ( info.inputChannels > 0 ) info.isDefaultInput = true; } deviceList_.push_back( info ); deviceIdPairs_.push_back({d.first, info.ID}); // I don't see that ALSA provides property listeners to know if // devices are removed or added. } // Remove any devices left in the list that are no longer available. for ( std::vector::iterator it=deviceList_.begin(); it!=deviceList_.end(); ) { auto itID = deviceIdPairs_.begin(); while ( itID != deviceIdPairs_.end() ) { if ( (*it).ID == (*itID).second ) { break; } ++itID; } if ( itID == deviceIdPairs_.end() ) { // not found so remove it from our list it = deviceList_.erase( it ); } else ++it; } } bool RtApiAlsa :: probeDeviceInfo( RtAudio::DeviceInfo& info, std::string name ) { int result, openMode = SND_PCM_ASYNC; snd_pcm_stream_t stream; snd_pcm_t *phandle; snd_pcm_hw_params_t *params; snd_pcm_hw_params_alloca( ¶ms ); // First try for playback stream = SND_PCM_STREAM_PLAYBACK; result = snd_pcm_open( &phandle, name.c_str(), stream, openMode | SND_PCM_NONBLOCK ); if ( result < 0 ) { if ( result == -16 ) return false; // device busy ... can't probe or use if ( result != -2 ) { // device doesn't support playback errorStream_ << "RtApiAlsa::probeDeviceInfo: snd_pcm_open (playback) error for device (" << name << "), " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); error( RTAUDIO_WARNING ); } goto captureProbe; } // The device is open ... fill the parameter structure. result = snd_pcm_hw_params_any( phandle, params ); if ( result < 0 ) { snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::probeDeviceInfo: snd_pcm_hw_params error for device (" << name << "), " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); error( RTAUDIO_WARNING ); goto captureProbe; } // Get output channel information. unsigned int value; result = snd_pcm_hw_params_get_channels_max( params, &value ); if ( result < 0 ) { snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::probeDeviceInfo: error getting device (" << name << ") output channels, " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); error( RTAUDIO_WARNING ); goto captureProbe; } info.outputChannels = value; snd_pcm_close( phandle ); captureProbe: stream = SND_PCM_STREAM_CAPTURE; result = snd_pcm_open( &phandle, name.c_str(), stream, openMode | SND_PCM_NONBLOCK); if ( result < 0 && result ) { if ( result != -2 && result != -16 ) { // device busy or doesn't support capture errorStream_ << "RtApiAlsa::probeDeviceInfo: snd_pcm_open (capture) error for device (" << name << "), " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); error( RTAUDIO_WARNING ); } if ( info.outputChannels == 0 ) return false; goto probeParameters; } // The device is open ... fill the parameter structure. result = snd_pcm_hw_params_any( phandle, params ); if ( result < 0 ) { snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::probeDeviceInfo: snd_pcm_hw_params error for device (" << name << "), " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); error( RTAUDIO_WARNING ); if ( info.outputChannels == 0 ) return false; goto probeParameters; } result = snd_pcm_hw_params_get_channels_max( params, &value ); if ( result < 0 ) { snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::probeDeviceInfo: error getting device (" << name << ") input channels, " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); error( RTAUDIO_WARNING ); if ( info.outputChannels == 0 ) return false; goto probeParameters; } info.inputChannels = value; snd_pcm_close( phandle ); // If device opens for both playback and capture, we determine the channels. if ( info.outputChannels > 0 && info.inputChannels > 0 ) info.duplexChannels = (info.outputChannels > info.inputChannels) ? info.inputChannels : info.outputChannels; probeParameters: // At this point, we just need to figure out the supported data // formats and sample rates. We'll proceed by opening the device in // the direction with the maximum number of channels, or playback if // they are equal. This might limit our sample rate options, but so // be it. if ( info.outputChannels >= info.inputChannels ) stream = SND_PCM_STREAM_PLAYBACK; else stream = SND_PCM_STREAM_CAPTURE; result = snd_pcm_open( &phandle, name.c_str(), stream, openMode | SND_PCM_NONBLOCK); if ( result < 0 ) { errorStream_ << "RtApiAlsa::probeDeviceInfo: snd_pcm_open error for device (" << name << "), " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); error( RTAUDIO_WARNING ); return false; } // The device is open ... fill the parameter structure. result = snd_pcm_hw_params_any( phandle, params ); if ( result < 0 ) { snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::probeDeviceInfo: snd_pcm_hw_params error for device (" << name << "), " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); error( RTAUDIO_WARNING ); return false; } // Test our discrete set of sample rate values. info.sampleRates.clear(); for ( unsigned int i=0; i info.preferredSampleRate ) ) info.preferredSampleRate = SAMPLE_RATES[i]; } } if ( info.sampleRates.size() == 0 ) { snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::probeDeviceInfo: no supported sample rates found for device (" << name << ")."; errorText_ = errorStream_.str(); error( RTAUDIO_WARNING ); return false; } // Probe the supported data formats ... we don't care about endian-ness just yet snd_pcm_format_t format; info.nativeFormats = 0; format = SND_PCM_FORMAT_S8; if ( snd_pcm_hw_params_test_format( phandle, params, format ) == 0 ) info.nativeFormats |= RTAUDIO_SINT8; format = SND_PCM_FORMAT_S16; if ( snd_pcm_hw_params_test_format( phandle, params, format ) == 0 ) info.nativeFormats |= RTAUDIO_SINT16; format = SND_PCM_FORMAT_S24; if ( snd_pcm_hw_params_test_format( phandle, params, format ) == 0 ) info.nativeFormats |= RTAUDIO_SINT24; format = SND_PCM_FORMAT_S32; if ( snd_pcm_hw_params_test_format( phandle, params, format ) == 0 ) info.nativeFormats |= RTAUDIO_SINT32; format = SND_PCM_FORMAT_FLOAT; if ( snd_pcm_hw_params_test_format( phandle, params, format ) == 0 ) info.nativeFormats |= RTAUDIO_FLOAT32; format = SND_PCM_FORMAT_FLOAT64; if ( snd_pcm_hw_params_test_format( phandle, params, format ) == 0 ) info.nativeFormats |= RTAUDIO_FLOAT64; // Check that we have at least one supported format if ( info.nativeFormats == 0 ) { snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::probeDeviceInfo: pcm device (" << name << ") data format not supported by RtAudio."; errorText_ = errorStream_.str(); error( RTAUDIO_WARNING ); return false; } // Close the device and return snd_pcm_close( phandle ); return true; } bool RtApiAlsa :: probeDeviceOpen( unsigned int deviceId, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, RtAudio::StreamOptions *options ) { #if defined(__RTAUDIO_DEBUG__) struct SndOutputTdealloc { SndOutputTdealloc() : _out(NULL) { snd_output_stdio_attach(&_out, stderr, 0); } ~SndOutputTdealloc() { snd_output_close(_out); } operator snd_output_t*() { return _out; } snd_output_t *_out; } out; #endif std::string name; for ( auto& id : deviceIdPairs_) { if ( id.second == deviceId ) { name = id.first; break; } } snd_pcm_stream_t stream; if ( mode == OUTPUT ) stream = SND_PCM_STREAM_PLAYBACK; else stream = SND_PCM_STREAM_CAPTURE; snd_pcm_t *phandle; int openMode = SND_PCM_ASYNC; int result = snd_pcm_open( &phandle, name.c_str(), stream, openMode ); if ( result < 0 ) { if ( mode == OUTPUT ) errorStream_ << "RtApiAlsa::probeDeviceOpen: pcm device (" << name << ") won't open for output."; else errorStream_ << "RtApiAlsa::probeDeviceOpen: pcm device (" << name << ") won't open for input."; errorText_ = errorStream_.str(); return FAILURE; } // Fill the parameter structure. snd_pcm_hw_params_t *hw_params; snd_pcm_hw_params_alloca( &hw_params ); result = snd_pcm_hw_params_any( phandle, hw_params ); if ( result < 0 ) { snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::probeDeviceOpen: error getting pcm device (" << name << ") parameters, " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); return FAILURE; } #if defined(__RTAUDIO_DEBUG__) fprintf( stderr, "\nRtApiAlsa: dump hardware params just after device open:\n\n" ); snd_pcm_hw_params_dump( hw_params, out ); #endif // Set access ... check user preference. if ( options && options->flags & RTAUDIO_NONINTERLEAVED ) { stream_.userInterleaved = false; result = snd_pcm_hw_params_set_access( phandle, hw_params, SND_PCM_ACCESS_RW_NONINTERLEAVED ); if ( result < 0 ) { result = snd_pcm_hw_params_set_access( phandle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED ); stream_.deviceInterleaved[mode] = true; } else stream_.deviceInterleaved[mode] = false; } else { stream_.userInterleaved = true; result = snd_pcm_hw_params_set_access( phandle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED ); if ( result < 0 ) { result = snd_pcm_hw_params_set_access( phandle, hw_params, SND_PCM_ACCESS_RW_NONINTERLEAVED ); stream_.deviceInterleaved[mode] = false; } else stream_.deviceInterleaved[mode] = true; } if ( result < 0 ) { snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::probeDeviceOpen: error setting pcm device (" << name << ") access, " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); return FAILURE; } // Determine how to set the device format. stream_.userFormat = format; snd_pcm_format_t deviceFormat = SND_PCM_FORMAT_UNKNOWN; if ( format == RTAUDIO_SINT8 ) deviceFormat = SND_PCM_FORMAT_S8; else if ( format == RTAUDIO_SINT16 ) deviceFormat = SND_PCM_FORMAT_S16; else if ( format == RTAUDIO_SINT24 ) deviceFormat = SND_PCM_FORMAT_S24; else if ( format == RTAUDIO_SINT32 ) deviceFormat = SND_PCM_FORMAT_S32; else if ( format == RTAUDIO_FLOAT32 ) deviceFormat = SND_PCM_FORMAT_FLOAT; else if ( format == RTAUDIO_FLOAT64 ) deviceFormat = SND_PCM_FORMAT_FLOAT64; if ( snd_pcm_hw_params_test_format(phandle, hw_params, deviceFormat) == 0) { stream_.deviceFormat[mode] = format; goto setFormat; } // The user requested format is not natively supported by the device. deviceFormat = SND_PCM_FORMAT_FLOAT64; if ( snd_pcm_hw_params_test_format( phandle, hw_params, deviceFormat ) == 0 ) { stream_.deviceFormat[mode] = RTAUDIO_FLOAT64; goto setFormat; } deviceFormat = SND_PCM_FORMAT_FLOAT; if ( snd_pcm_hw_params_test_format(phandle, hw_params, deviceFormat ) == 0 ) { stream_.deviceFormat[mode] = RTAUDIO_FLOAT32; goto setFormat; } deviceFormat = SND_PCM_FORMAT_S32; if ( snd_pcm_hw_params_test_format(phandle, hw_params, deviceFormat ) == 0 ) { stream_.deviceFormat[mode] = RTAUDIO_SINT32; goto setFormat; } deviceFormat = SND_PCM_FORMAT_S24; if ( snd_pcm_hw_params_test_format(phandle, hw_params, deviceFormat ) == 0 ) { stream_.deviceFormat[mode] = RTAUDIO_SINT24; goto setFormat; } deviceFormat = SND_PCM_FORMAT_S16; if ( snd_pcm_hw_params_test_format(phandle, hw_params, deviceFormat ) == 0 ) { stream_.deviceFormat[mode] = RTAUDIO_SINT16; goto setFormat; } deviceFormat = SND_PCM_FORMAT_S8; if ( snd_pcm_hw_params_test_format(phandle, hw_params, deviceFormat ) == 0 ) { stream_.deviceFormat[mode] = RTAUDIO_SINT8; goto setFormat; } // If we get here, no supported format was found. snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::probeDeviceOpen: pcm device (" << name << ") data format not supported by RtAudio."; errorText_ = errorStream_.str(); return FAILURE; setFormat: result = snd_pcm_hw_params_set_format( phandle, hw_params, deviceFormat ); if ( result < 0 ) { snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::probeDeviceOpen: error setting pcm device (" << name << ") data format, " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); return FAILURE; } // Determine whether byte-swaping is necessary. stream_.doByteSwap[mode] = false; if ( deviceFormat != SND_PCM_FORMAT_S8 ) { result = snd_pcm_format_cpu_endian( deviceFormat ); if ( result == 0 ) stream_.doByteSwap[mode] = true; else if (result < 0) { snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::probeDeviceOpen: error getting pcm device (" << name << ") endian-ness, " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); return FAILURE; } } // Set the sample rate. result = snd_pcm_hw_params_set_rate_near( phandle, hw_params, (unsigned int*) &sampleRate, 0 ); if ( result < 0 ) { snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::probeDeviceOpen: error setting sample rate on device (" << name << "), " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); return FAILURE; } // Determine the number of channels for this device. We support a possible // minimum device channel number > than the value requested by the user. stream_.nUserChannels[mode] = channels; unsigned int value; result = snd_pcm_hw_params_get_channels_max( hw_params, &value ); unsigned int deviceChannels = value; if ( result < 0 || deviceChannels < channels + firstChannel ) { snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::probeDeviceOpen: requested channel parameters not supported by device (" << name << "), " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); return FAILURE; } result = snd_pcm_hw_params_get_channels_min( hw_params, &value ); if ( result < 0 ) { snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::probeDeviceOpen: error getting minimum channels for device (" << name << "), " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); return FAILURE; } deviceChannels = value; if ( deviceChannels < channels + firstChannel ) deviceChannels = channels + firstChannel; stream_.nDeviceChannels[mode] = deviceChannels; // Set the device channels. result = snd_pcm_hw_params_set_channels( phandle, hw_params, deviceChannels ); if ( result < 0 ) { snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::probeDeviceOpen: error setting channels for device (" << name << "), " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); return FAILURE; } // Set the buffer (or period) size. int dir = 0; snd_pcm_uframes_t periodSize = *bufferSize; result = snd_pcm_hw_params_set_period_size_near( phandle, hw_params, &periodSize, &dir ); if ( result < 0 ) { snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::probeDeviceOpen: error setting period size for device (" << name << "), " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); return FAILURE; } *bufferSize = periodSize; // Set the buffer number, which in ALSA is referred to as the "period". unsigned int periods = 0; if ( options && options->flags & RTAUDIO_MINIMIZE_LATENCY ) periods = 2; if ( options && options->numberOfBuffers > 0 ) periods = options->numberOfBuffers; if ( periods < 2 ) periods = 4; // a fairly safe default value result = snd_pcm_hw_params_set_periods_near( phandle, hw_params, &periods, &dir ); if ( result < 0 ) { snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::probeDeviceOpen: error setting periods for device (" << name << "), " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); return FAILURE; } // If attempting to setup a duplex stream, the bufferSize parameter // MUST be the same in both directions! if ( stream_.mode == OUTPUT && mode == INPUT && *bufferSize != stream_.bufferSize ) { snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::probeDeviceOpen: system error setting buffer size for duplex stream on device (" << name << ")."; errorText_ = errorStream_.str(); return FAILURE; } stream_.bufferSize = *bufferSize; // Install the hardware configuration result = snd_pcm_hw_params( phandle, hw_params ); if ( result < 0 ) { snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::probeDeviceOpen: error installing hardware configuration on device (" << name << "), " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); return FAILURE; } #if defined(__RTAUDIO_DEBUG__) fprintf(stderr, "\nRtApiAlsa: dump hardware params after installation:\n\n"); snd_pcm_hw_params_dump( hw_params, out ); #endif // Set the software configuration to fill buffers with zeros and prevent device stopping on xruns. snd_pcm_sw_params_t *sw_params = NULL; snd_pcm_sw_params_alloca( &sw_params ); snd_pcm_sw_params_current( phandle, sw_params ); snd_pcm_sw_params_set_start_threshold( phandle, sw_params, *bufferSize ); snd_pcm_sw_params_set_stop_threshold( phandle, sw_params, ULONG_MAX ); snd_pcm_sw_params_set_silence_threshold( phandle, sw_params, 0 ); // The following two settings were suggested by Theo Veenker //snd_pcm_sw_params_set_avail_min( phandle, sw_params, *bufferSize ); //snd_pcm_sw_params_set_xfer_align( phandle, sw_params, 1 ); // here are two options for a fix //snd_pcm_sw_params_set_silence_size( phandle, sw_params, ULONG_MAX ); snd_pcm_uframes_t val; snd_pcm_sw_params_get_boundary( sw_params, &val ); snd_pcm_sw_params_set_silence_size( phandle, sw_params, val ); result = snd_pcm_sw_params( phandle, sw_params ); if ( result < 0 ) { snd_pcm_close( phandle ); errorStream_ << "RtApiAlsa::probeDeviceOpen: error installing software configuration on device (" << name << "), " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); return FAILURE; } #if defined(__RTAUDIO_DEBUG__) fprintf(stderr, "\nRtApiAlsa: dump software params after installation:\n\n"); snd_pcm_sw_params_dump( sw_params, out ); #endif // Set flags for buffer conversion stream_.doConvertBuffer[mode] = false; if ( stream_.userFormat != stream_.deviceFormat[mode] ) stream_.doConvertBuffer[mode] = true; if ( stream_.nUserChannels[mode] < stream_.nDeviceChannels[mode] ) stream_.doConvertBuffer[mode] = true; if ( stream_.userInterleaved != stream_.deviceInterleaved[mode] && stream_.nUserChannels[mode] > 1 ) stream_.doConvertBuffer[mode] = true; // Allocate the ApiHandle if necessary and then save. AlsaHandle *apiInfo = 0; if ( stream_.apiHandle == 0 ) { try { apiInfo = (AlsaHandle *) new AlsaHandle; } catch ( std::bad_alloc& ) { errorText_ = "RtApiAlsa::probeDeviceOpen: error allocating AlsaHandle memory."; goto error; } if ( pthread_cond_init( &apiInfo->runnable_cv, NULL ) ) { errorText_ = "RtApiAlsa::probeDeviceOpen: error initializing pthread condition variable."; goto error; } stream_.apiHandle = (void *) apiInfo; apiInfo->handles[0] = 0; apiInfo->handles[1] = 0; } else { apiInfo = (AlsaHandle *) stream_.apiHandle; } apiInfo->handles[mode] = phandle; phandle = 0; // Allocate necessary internal buffers. unsigned long bufferBytes; bufferBytes = stream_.nUserChannels[mode] * *bufferSize * formatBytes( stream_.userFormat ); stream_.userBuffer[mode] = (char *) calloc( bufferBytes, 1 ); if ( stream_.userBuffer[mode] == NULL ) { errorText_ = "RtApiAlsa::probeDeviceOpen: error allocating user buffer memory."; goto error; } if ( stream_.doConvertBuffer[mode] ) { bool makeBuffer = true; bufferBytes = stream_.nDeviceChannels[mode] * formatBytes( stream_.deviceFormat[mode] ); if ( mode == INPUT ) { if ( stream_.mode == OUTPUT && stream_.deviceBuffer ) { unsigned long bytesOut = stream_.nDeviceChannels[0] * formatBytes( stream_.deviceFormat[0] ); if ( bufferBytes <= bytesOut ) makeBuffer = false; } } if ( makeBuffer ) { bufferBytes *= *bufferSize; if ( stream_.deviceBuffer ) free( stream_.deviceBuffer ); stream_.deviceBuffer = (char *) calloc( bufferBytes, 1 ); if ( stream_.deviceBuffer == NULL ) { errorText_ = "RtApiAlsa::probeDeviceOpen: error allocating device buffer memory."; goto error; } } } stream_.sampleRate = sampleRate; stream_.nBuffers = periods; stream_.deviceId[mode] = deviceId; stream_.state = STREAM_STOPPED; // Setup the buffer conversion information structure. if ( stream_.doConvertBuffer[mode] ) setConvertInfo( mode, firstChannel ); // Setup thread if necessary. if ( stream_.mode == OUTPUT && mode == INPUT ) { // We had already set up an output stream. stream_.mode = DUPLEX; // Link the streams if possible. apiInfo->synchronized = false; if ( snd_pcm_link( apiInfo->handles[0], apiInfo->handles[1] ) == 0 ) apiInfo->synchronized = true; else { errorText_ = "RtApiAlsa::probeDeviceOpen: unable to synchronize input and output devices."; error( RTAUDIO_WARNING ); } } else { stream_.mode = mode; // Setup callback thread. stream_.callbackInfo.object = (void *) this; // Set the thread attributes for joinable and realtime scheduling // priority (optional). The higher priority will only take affect // if the program is run as root or suid. Note, under Linux // processes with CAP_SYS_NICE privilege, a user can change // scheduling policy and priority (thus need not be root). See // POSIX "capabilities". pthread_attr_t attr; pthread_attr_init( &attr ); pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_JOINABLE ); #ifdef SCHED_RR // Undefined with some OSes (e.g. NetBSD 1.6.x with GNU Pthread) if ( options && options->flags & RTAUDIO_SCHEDULE_REALTIME ) { stream_.callbackInfo.doRealtime = true; struct sched_param param; int priority = options->priority; int min = sched_get_priority_min( SCHED_RR ); int max = sched_get_priority_max( SCHED_RR ); if ( priority < min ) priority = min; else if ( priority > max ) priority = max; param.sched_priority = priority; // Set the policy BEFORE the priority. Otherwise it fails. pthread_attr_setschedpolicy(&attr, SCHED_RR); pthread_attr_setscope (&attr, PTHREAD_SCOPE_SYSTEM); // This is definitely required. Otherwise it fails. pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED); pthread_attr_setschedparam(&attr, ¶m); } else pthread_attr_setschedpolicy( &attr, SCHED_OTHER ); #else pthread_attr_setschedpolicy( &attr, SCHED_OTHER ); #endif stream_.callbackInfo.isRunning = true; result = pthread_create( &stream_.callbackInfo.thread, &attr, alsaCallbackHandler, &stream_.callbackInfo ); pthread_attr_destroy( &attr ); if ( result ) { // Failed. Try instead with default attributes. result = pthread_create( &stream_.callbackInfo.thread, NULL, alsaCallbackHandler, &stream_.callbackInfo ); if ( result ) { stream_.callbackInfo.isRunning = false; errorText_ = "RtApiAlsa::error creating callback thread!"; goto error; } } } return SUCCESS; error: if ( apiInfo ) { pthread_cond_destroy( &apiInfo->runnable_cv ); if ( apiInfo->handles[0] ) snd_pcm_close( apiInfo->handles[0] ); if ( apiInfo->handles[1] ) snd_pcm_close( apiInfo->handles[1] ); delete apiInfo; stream_.apiHandle = 0; } if ( phandle) snd_pcm_close( phandle ); for ( int i=0; i<2; i++ ) { if ( stream_.userBuffer[i] ) { free( stream_.userBuffer[i] ); stream_.userBuffer[i] = 0; } } if ( stream_.deviceBuffer ) { free( stream_.deviceBuffer ); stream_.deviceBuffer = 0; } stream_.state = STREAM_CLOSED; return FAILURE; } void RtApiAlsa :: closeStream() { if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiAlsa::closeStream(): no open stream to close!"; error( RTAUDIO_WARNING ); return; } AlsaHandle *apiInfo = (AlsaHandle *) stream_.apiHandle; stream_.callbackInfo.isRunning = false; MUTEX_LOCK( &stream_.mutex ); if ( stream_.state == STREAM_STOPPED ) { apiInfo->runnable = true; pthread_cond_signal( &apiInfo->runnable_cv ); } MUTEX_UNLOCK( &stream_.mutex ); pthread_join( stream_.callbackInfo.thread, NULL ); if ( stream_.state == STREAM_RUNNING ) { stream_.state = STREAM_STOPPED; if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) snd_pcm_drop( apiInfo->handles[0] ); if ( stream_.mode == INPUT || stream_.mode == DUPLEX ) snd_pcm_drop( apiInfo->handles[1] ); } if ( apiInfo ) { pthread_cond_destroy( &apiInfo->runnable_cv ); if ( apiInfo->handles[0] ) snd_pcm_close( apiInfo->handles[0] ); if ( apiInfo->handles[1] ) snd_pcm_close( apiInfo->handles[1] ); delete apiInfo; stream_.apiHandle = 0; } for ( int i=0; i<2; i++ ) { if ( stream_.userBuffer[i] ) { free( stream_.userBuffer[i] ); stream_.userBuffer[i] = 0; } } if ( stream_.deviceBuffer ) { free( stream_.deviceBuffer ); stream_.deviceBuffer = 0; } clearStreamInfo(); } RtAudioErrorType RtApiAlsa :: startStream() { // This method calls snd_pcm_prepare if the device isn't already in that state. if ( stream_.state != STREAM_STOPPED ) { if ( stream_.state == STREAM_RUNNING ) errorText_ = "RtApiAlsa::startStream(): the stream is already running!"; else if ( stream_.state == STREAM_STOPPING || stream_.state == STREAM_CLOSED ) errorText_ = "RtApiAlsa::startStream(): the stream is stopping or closed!"; return error( RTAUDIO_WARNING ); } MUTEX_LOCK( &stream_.mutex ); /* #if defined( HAVE_GETTIMEOFDAY ) gettimeofday( &stream_.lastTickTimestamp, NULL ); #endif */ int result = 0; snd_pcm_state_t state; AlsaHandle *apiInfo = (AlsaHandle *) stream_.apiHandle; snd_pcm_t **handle = (snd_pcm_t **) apiInfo->handles; if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { state = snd_pcm_state( handle[0] ); if ( state != SND_PCM_STATE_PREPARED ) { result = snd_pcm_prepare( handle[0] ); if ( result < 0 ) { errorStream_ << "RtApiAlsa::startStream: error preparing output pcm device, " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); goto unlock; } } } if ( ( stream_.mode == INPUT || stream_.mode == DUPLEX ) && !apiInfo->synchronized ) { result = snd_pcm_drop(handle[1]); // fix to remove stale data received since device has been open state = snd_pcm_state( handle[1] ); if ( state != SND_PCM_STATE_PREPARED ) { result = snd_pcm_prepare( handle[1] ); if ( result < 0 ) { errorStream_ << "RtApiAlsa::startStream: error preparing input pcm device, " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); goto unlock; } } } stream_.state = STREAM_RUNNING; unlock: apiInfo->runnable = true; pthread_cond_signal( &apiInfo->runnable_cv ); MUTEX_UNLOCK( &stream_.mutex ); if ( result < 0 ) return error( RTAUDIO_SYSTEM_ERROR ); return RTAUDIO_NO_ERROR; } RtAudioErrorType RtApiAlsa :: stopStream() { if ( stream_.state != STREAM_RUNNING && stream_.state != STREAM_STOPPING ) { if ( stream_.state == STREAM_STOPPED ) errorText_ = "RtApiAlsa::stopStream(): the stream is already stopped!"; else if ( stream_.state == STREAM_CLOSED ) errorText_ = "RtApiAlsa::stopStream(): the stream is closed!"; return error( RTAUDIO_WARNING ); } stream_.state = STREAM_STOPPED; MUTEX_LOCK( &stream_.mutex ); int result = 0; AlsaHandle *apiInfo = (AlsaHandle *) stream_.apiHandle; snd_pcm_t **handle = (snd_pcm_t **) apiInfo->handles; if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { if ( apiInfo->synchronized ) result = snd_pcm_drop( handle[0] ); else result = snd_pcm_drain( handle[0] ); if ( result < 0 ) { errorStream_ << "RtApiAlsa::stopStream: error draining output pcm device, " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); goto unlock; } } if ( ( stream_.mode == INPUT || stream_.mode == DUPLEX ) && !apiInfo->synchronized ) { result = snd_pcm_drop( handle[1] ); if ( result < 0 ) { errorStream_ << "RtApiAlsa::stopStream: error stopping input pcm device, " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); goto unlock; } } unlock: apiInfo->runnable = false; // fixes high CPU usage when stopped MUTEX_UNLOCK( &stream_.mutex ); if ( result < 0 ) return error( RTAUDIO_SYSTEM_ERROR ); return RTAUDIO_NO_ERROR; } RtAudioErrorType RtApiAlsa :: abortStream() { if ( stream_.state != STREAM_RUNNING ) { if ( stream_.state == STREAM_STOPPED ) errorText_ = "RtApiAlsa::abortStream(): the stream is already stopped!"; else if ( stream_.state == STREAM_STOPPING || stream_.state == STREAM_CLOSED ) errorText_ = "RtApiAlsa::abortStream(): the stream is stopping or closed!"; return error( RTAUDIO_WARNING ); } stream_.state = STREAM_STOPPED; MUTEX_LOCK( &stream_.mutex ); int result = 0; AlsaHandle *apiInfo = (AlsaHandle *) stream_.apiHandle; snd_pcm_t **handle = (snd_pcm_t **) apiInfo->handles; if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { result = snd_pcm_drop( handle[0] ); if ( result < 0 ) { errorStream_ << "RtApiAlsa::abortStream: error aborting output pcm device, " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); goto unlock; } } if ( ( stream_.mode == INPUT || stream_.mode == DUPLEX ) && !apiInfo->synchronized ) { result = snd_pcm_drop( handle[1] ); if ( result < 0 ) { errorStream_ << "RtApiAlsa::abortStream: error aborting input pcm device, " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); goto unlock; } } unlock: apiInfo->runnable = false; // fixes high CPU usage when stopped MUTEX_UNLOCK( &stream_.mutex ); if ( result < 0 ) return error( RTAUDIO_SYSTEM_ERROR ); return RTAUDIO_NO_ERROR; } void RtApiAlsa :: callbackEvent() { AlsaHandle *apiInfo = (AlsaHandle *) stream_.apiHandle; if ( stream_.state == STREAM_STOPPED ) { MUTEX_LOCK( &stream_.mutex ); while ( !apiInfo->runnable ) pthread_cond_wait( &apiInfo->runnable_cv, &stream_.mutex ); if ( stream_.state != STREAM_RUNNING ) { MUTEX_UNLOCK( &stream_.mutex ); return; } MUTEX_UNLOCK( &stream_.mutex ); } if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiAlsa::callbackEvent(): the stream is closed ... this shouldn't happen!"; error( RTAUDIO_WARNING ); return; } int doStopStream = 0; RtAudioCallback callback = (RtAudioCallback) stream_.callbackInfo.callback; double streamTime = getStreamTime(); RtAudioStreamStatus status = 0; if ( stream_.mode != INPUT && apiInfo->xrun[0] == true ) { status |= RTAUDIO_OUTPUT_UNDERFLOW; apiInfo->xrun[0] = false; } if ( stream_.mode != OUTPUT && apiInfo->xrun[1] == true ) { status |= RTAUDIO_INPUT_OVERFLOW; apiInfo->xrun[1] = false; } doStopStream = callback( stream_.userBuffer[0], stream_.userBuffer[1], stream_.bufferSize, streamTime, status, stream_.callbackInfo.userData ); if ( doStopStream == 2 ) { abortStream(); return; } MUTEX_LOCK( &stream_.mutex ); // The state might change while waiting on a mutex. if ( stream_.state == STREAM_STOPPED ) goto unlock; int result; char *buffer; int channels; snd_pcm_t **handle; snd_pcm_sframes_t frames; RtAudioFormat format; handle = (snd_pcm_t **) apiInfo->handles; if ( stream_.mode == INPUT || stream_.mode == DUPLEX ) { // Setup parameters. if ( stream_.doConvertBuffer[1] ) { buffer = stream_.deviceBuffer; channels = stream_.nDeviceChannels[1]; format = stream_.deviceFormat[1]; } else { buffer = stream_.userBuffer[1]; channels = stream_.nUserChannels[1]; format = stream_.userFormat; } // Read samples from device in interleaved/non-interleaved format. if ( stream_.deviceInterleaved[1] ) result = snd_pcm_readi( handle[1], buffer, stream_.bufferSize ); else { void *bufs[channels]; size_t offset = stream_.bufferSize * formatBytes( format ); for ( int i=0; ixrun[1] = true; result = snd_pcm_prepare( handle[1] ); if ( result < 0 ) { errorStream_ << "RtApiAlsa::callbackEvent: error preparing device after overrun, " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); } } else { errorStream_ << "RtApiAlsa::callbackEvent: error, current state is " << snd_pcm_state_name( state ) << ", " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); } } else { errorStream_ << "RtApiAlsa::callbackEvent: audio read error, " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); } error( RTAUDIO_WARNING ); goto tryOutput; } // Do byte swapping if necessary. if ( stream_.doByteSwap[1] ) byteSwapBuffer( buffer, stream_.bufferSize * channels, format ); // Do buffer conversion if necessary. if ( stream_.doConvertBuffer[1] ) convertBuffer( stream_.userBuffer[1], stream_.deviceBuffer, stream_.convertInfo[1] ); // Check stream latency result = snd_pcm_delay( handle[1], &frames ); if ( result == 0 && frames > 0 ) stream_.latency[1] = frames; } tryOutput: if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { // Setup parameters and do buffer conversion if necessary. if ( stream_.doConvertBuffer[0] ) { buffer = stream_.deviceBuffer; convertBuffer( buffer, stream_.userBuffer[0], stream_.convertInfo[0] ); channels = stream_.nDeviceChannels[0]; format = stream_.deviceFormat[0]; } else { buffer = stream_.userBuffer[0]; channels = stream_.nUserChannels[0]; format = stream_.userFormat; } // Do byte swapping if necessary. if ( stream_.doByteSwap[0] ) byteSwapBuffer(buffer, stream_.bufferSize * channels, format); // Write samples to device in interleaved/non-interleaved format. if ( stream_.deviceInterleaved[0] ) result = snd_pcm_writei( handle[0], buffer, stream_.bufferSize ); else { void *bufs[channels]; size_t offset = stream_.bufferSize * formatBytes( format ); for ( int i=0; ixrun[0] = true; result = snd_pcm_prepare( handle[0] ); if ( result < 0 ) { errorStream_ << "RtApiAlsa::callbackEvent: error preparing device after underrun, " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); } else errorText_ = "RtApiAlsa::callbackEvent: audio write error, underrun."; } else { errorStream_ << "RtApiAlsa::callbackEvent: error, current state is " << snd_pcm_state_name( state ) << ", " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); } } else { errorStream_ << "RtApiAlsa::callbackEvent: audio write error, " << snd_strerror( result ) << "."; errorText_ = errorStream_.str(); } error( RTAUDIO_WARNING ); goto unlock; } // Check stream latency result = snd_pcm_delay( handle[0], &frames ); if ( result == 0 && frames > 0 ) stream_.latency[0] = frames; } unlock: MUTEX_UNLOCK( &stream_.mutex ); RtApi::tickStreamTime(); if ( doStopStream == 1 ) this->stopStream(); } static void *alsaCallbackHandler( void *ptr ) { CallbackInfo *info = (CallbackInfo *) ptr; RtApiAlsa *object = (RtApiAlsa *) info->object; bool *isRunning = &info->isRunning; #ifdef SCHED_RR // Undefined with some OSes (e.g. NetBSD 1.6.x with GNU Pthread) if ( info->doRealtime ) { std::cerr << "RtAudio alsa: " << (sched_getscheduler(0) == SCHED_RR ? "" : "_NOT_ ") << "running realtime scheduling" << std::endl; } #endif while ( *isRunning == true ) { pthread_testcancel(); object->callbackEvent(); } pthread_exit( NULL ); } //******************** End of __LINUX_ALSA__ *********************// #endif #if defined(__LINUX_PULSE__) // Code written by Peter Meerwald, pmeerw@pmeerw.net and Tristan Matthews. // Updated by Gary Scavone, 2021. #include #include #include // A structure needed to pass variables for device probing. struct PaDeviceProbeInfo { pa_mainloop_api *paMainLoopApi; std::string defaultSinkName; std::string defaultSourceName; int defaultRate; unsigned int *currentDeviceId; std::vector< std::string > deviceNames; std::vector< RtApiPulse::PaDeviceInfo > *paDeviceList; std::vector< RtAudio::DeviceInfo > *rtDeviceList; }; static const unsigned int SUPPORTED_SAMPLERATES[] = { 8000, 16000, 22050, 32000, 44100, 48000, 96000, 192000, 0}; struct rtaudio_pa_format_mapping_t { RtAudioFormat rtaudio_format; pa_sample_format_t pa_format; }; static const rtaudio_pa_format_mapping_t supported_sampleformats[] = { {RTAUDIO_SINT16, PA_SAMPLE_S16LE}, {RTAUDIO_SINT24, PA_SAMPLE_S24LE}, {RTAUDIO_SINT32, PA_SAMPLE_S32LE}, {RTAUDIO_FLOAT32, PA_SAMPLE_FLOAT32LE}, {0, PA_SAMPLE_INVALID}}; struct PulseAudioHandle { pa_simple *s_play; pa_simple *s_rec; pthread_t thread; pthread_cond_t runnable_cv; bool runnable; PulseAudioHandle() : s_play(0), s_rec(0), runnable(false) { } }; // The following 3 functions are called by the device probing // system. This first one gets overall system information. static void rt_pa_set_server_info( pa_context *context, const pa_server_info *info, void *userdata ) { (void)context; pa_sample_spec ss; PaDeviceProbeInfo *paProbeInfo = static_cast( userdata ); if (!info) { paProbeInfo->paMainLoopApi->quit( paProbeInfo->paMainLoopApi, 1 ); return; } ss = info->sample_spec; paProbeInfo->defaultRate = ss.rate; paProbeInfo->defaultSinkName = info->default_sink_name; paProbeInfo->defaultSourceName = info->default_source_name; } // Used to get output device information. static void rt_pa_set_sink_info( pa_context * /*c*/, const pa_sink_info *i, int eol, void *userdata ) { if ( eol ) return; PaDeviceProbeInfo *paProbeInfo = static_cast( userdata ); std::string name = pa_proplist_gets( i->proplist, "device.description" ); paProbeInfo->deviceNames.push_back( name ); for ( size_t n=0; nrtDeviceList->size(); n++ ) if ( paProbeInfo->rtDeviceList->at(n).name == name ) return; // we've already probed this one RtAudio::DeviceInfo info; info.name = name; info.outputChannels = i->sample_spec.channels; info.preferredSampleRate = i->sample_spec.rate; info.isDefaultOutput = ( paProbeInfo->defaultSinkName == i->name ); for ( const unsigned int *sr = SUPPORTED_SAMPLERATES; *sr; ++sr ) info.sampleRates.push_back( *sr ); for ( const rtaudio_pa_format_mapping_t *fm = supported_sampleformats; fm->rtaudio_format; ++fm ) info.nativeFormats |= fm->rtaudio_format; info.ID = *(paProbeInfo->currentDeviceId); *(paProbeInfo->currentDeviceId) = info.ID + 1; paProbeInfo->rtDeviceList->push_back( info ); RtApiPulse::PaDeviceInfo painfo; painfo.sinkName = i->name; paProbeInfo->paDeviceList->push_back( painfo ); } // Used to get input device information. static void rt_pa_set_source_info_and_quit( pa_context * /*c*/, const pa_source_info *i, int eol, void *userdata ) { PaDeviceProbeInfo *paProbeInfo = static_cast( userdata ); if ( eol ) { paProbeInfo->paMainLoopApi->quit( paProbeInfo->paMainLoopApi, 0 ); return; } std::string name = pa_proplist_gets( i->proplist, "device.description" ); paProbeInfo->deviceNames.push_back( name ); for ( size_t n=0; nrtDeviceList->size(); n++ ) { if ( paProbeInfo->rtDeviceList->at(n).name == name ) { // Check if we've already probed this as an output. if ( !paProbeInfo->paDeviceList->at(n).sinkName.empty() ) { // This must be a duplex device. Update the device info. paProbeInfo->paDeviceList->at(n).sourceName = i->name; paProbeInfo->rtDeviceList->at(n).inputChannels = i->sample_spec.channels; paProbeInfo->rtDeviceList->at(n).isDefaultInput = ( paProbeInfo->defaultSourceName == i->name ); paProbeInfo->rtDeviceList->at(n).duplexChannels = (paProbeInfo->rtDeviceList->at(n).inputChannels < paProbeInfo->rtDeviceList->at(n).outputChannels) ? paProbeInfo->rtDeviceList->at(n).inputChannels : paProbeInfo->rtDeviceList->at(n).outputChannels; } return; // we already have this } } RtAudio::DeviceInfo info; info.name = name; info.inputChannels = i->sample_spec.channels; info.preferredSampleRate = i->sample_spec.rate; info.isDefaultInput = ( paProbeInfo->defaultSourceName == i->name ); for ( const unsigned int *sr = SUPPORTED_SAMPLERATES; *sr; ++sr ) info.sampleRates.push_back( *sr ); for ( const rtaudio_pa_format_mapping_t *fm = supported_sampleformats; fm->rtaudio_format; ++fm ) info.nativeFormats |= fm->rtaudio_format; info.ID = *(paProbeInfo->currentDeviceId); *(paProbeInfo->currentDeviceId) = info.ID + 1; paProbeInfo->rtDeviceList->push_back( info ); RtApiPulse::PaDeviceInfo painfo; painfo.sourceName = i->name; paProbeInfo->paDeviceList->push_back( painfo ); } // This is the initial function that is called when the callback is // set. This one then calls the functions above. static void rt_pa_context_state_callback( pa_context *context, void *userdata ) { PaDeviceProbeInfo *paProbeInfo = static_cast( userdata ); auto state = pa_context_get_state(context); switch (state) { case PA_CONTEXT_CONNECTING: case PA_CONTEXT_AUTHORIZING: case PA_CONTEXT_SETTING_NAME: break; case PA_CONTEXT_READY: pa_context_get_server_info( context, rt_pa_set_server_info, userdata ); // server info pa_context_get_sink_info_list( context, rt_pa_set_sink_info, userdata ); // output info ... needs to be before input pa_context_get_source_info_list( context, rt_pa_set_source_info_and_quit, userdata ); // input info break; case PA_CONTEXT_TERMINATED: paProbeInfo->paMainLoopApi->quit( paProbeInfo->paMainLoopApi, 0 ); break; case PA_CONTEXT_FAILED: default: paProbeInfo->paMainLoopApi->quit( paProbeInfo->paMainLoopApi, 1 ); } } RtApiPulse::~RtApiPulse() { if ( stream_.state != STREAM_CLOSED ) closeStream(); } void RtApiPulse :: probeDevices( void ) { // See list of required functionality in RtApi::probeDevices(). pa_mainloop *ml = NULL; pa_context *context = NULL; char *server = NULL; int ret = 1; PaDeviceProbeInfo paProbeInfo; if (!(ml = pa_mainloop_new())) { errorStream_ << "RtApiPulse::probeDevices: pa_mainloop_new() failed."; errorText_ = errorStream_.str(); error( RTAUDIO_WARNING ); goto quit; } paProbeInfo.paMainLoopApi = pa_mainloop_get_api( ml ); paProbeInfo.currentDeviceId = ¤tDeviceId_; paProbeInfo.paDeviceList = &paDeviceList_; paProbeInfo.rtDeviceList = &deviceList_; if (!(context = pa_context_new_with_proplist( paProbeInfo.paMainLoopApi, NULL, NULL ))) { errorStream_ << "RtApiPulse::probeDevices: pa_context_new() failed."; errorText_ = errorStream_.str(); error( RTAUDIO_WARNING ); goto quit; } pa_context_set_state_callback( context, rt_pa_context_state_callback, &paProbeInfo ); if (pa_context_connect( context, server, PA_CONTEXT_NOFLAGS, NULL ) < 0) { errorStream_ << "RtApiPulse::probeDevices: pa_context_connect() failed: " << pa_strerror(pa_context_errno(context)); errorText_ = errorStream_.str(); error( RTAUDIO_WARNING ); goto quit; } if (pa_mainloop_run( ml, &ret ) < 0) { errorStream_ << "RtApiPulse::probeDevices: pa_mainloop_run() failed."; errorText_ = errorStream_.str(); error( RTAUDIO_WARNING ); goto quit; } if (ret != 0) { errorStream_ << "RtApiPulse::probeDevices: could not get server info."; errorText_ = errorStream_.str(); error( RTAUDIO_WARNING ); goto quit; } // Check for devices that have been unplugged. unsigned int m; for ( std::vector::iterator it=deviceList_.begin(); it!=deviceList_.end(); ) { for ( m=0; m( user ); RtApiPulse *context = static_cast( cbi->object ); volatile bool *isRunning = &cbi->isRunning; #ifdef SCHED_RR // Undefined with some OSes (e.g. NetBSD 1.6.x with GNU Pthread) if (cbi->doRealtime) { std::cerr << "RtAudio pulse: " << (sched_getscheduler(0) == SCHED_RR ? "" : "_NOT_ ") << "running realtime scheduling" << std::endl; } #endif while ( *isRunning ) { pthread_testcancel(); context->callbackEvent(); } pthread_exit( NULL ); } bool RtApiPulse::probeDeviceOpen( unsigned int deviceId, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, RtAudio::StreamOptions *options ) { PulseAudioHandle *pah = 0; unsigned long bufferBytes = 0; pa_sample_spec ss; int deviceIdx = -1; for ( unsigned int m=0; mrtaudio_format && sf->pa_format != PA_SAMPLE_INVALID; ++sf ) { if ( format == sf->rtaudio_format ) { sf_found = true; stream_.userFormat = sf->rtaudio_format; stream_.deviceFormat[mode] = stream_.userFormat; ss.format = sf->pa_format; break; } } if ( !sf_found ) { // Use internal data format conversion. stream_.userFormat = format; stream_.deviceFormat[mode] = RTAUDIO_FLOAT32; ss.format = PA_SAMPLE_FLOAT32LE; } // Set other stream parameters. if ( options && options->flags & RTAUDIO_NONINTERLEAVED ) stream_.userInterleaved = false; else stream_.userInterleaved = true; stream_.deviceInterleaved[mode] = true; stream_.nBuffers = options ? options->numberOfBuffers : 1; stream_.doByteSwap[mode] = false; stream_.nUserChannels[mode] = channels; stream_.nDeviceChannels[mode] = channels + firstChannel; stream_.channelOffset[mode] = 0; std::string streamName = "RtAudio"; // Set flags for buffer conversion. stream_.doConvertBuffer[mode] = false; if ( stream_.userFormat != stream_.deviceFormat[mode] ) stream_.doConvertBuffer[mode] = true; if ( stream_.nUserChannels[mode] < stream_.nDeviceChannels[mode] ) stream_.doConvertBuffer[mode] = true; if ( stream_.userInterleaved != stream_.deviceInterleaved[mode] ) stream_.doConvertBuffer[mode] = true; // Allocate necessary internal buffers. bufferBytes = stream_.nUserChannels[mode] * *bufferSize * formatBytes( stream_.userFormat ); stream_.userBuffer[mode] = (char *) calloc( bufferBytes, 1 ); if ( stream_.userBuffer[mode] == NULL ) { errorText_ = "RtApiPulse::probeDeviceOpen: error allocating user buffer memory."; goto error; } stream_.bufferSize = *bufferSize; if ( stream_.doConvertBuffer[mode] ) { bool makeBuffer = true; bufferBytes = stream_.nDeviceChannels[mode] * formatBytes( stream_.deviceFormat[mode] ); if ( mode == INPUT ) { if ( stream_.mode == OUTPUT && stream_.deviceBuffer ) { unsigned long bytesOut = stream_.nDeviceChannels[0] * formatBytes( stream_.deviceFormat[0] ); if ( bufferBytes <= bytesOut ) makeBuffer = false; } } if ( makeBuffer ) { bufferBytes *= *bufferSize; if ( stream_.deviceBuffer ) free( stream_.deviceBuffer ); stream_.deviceBuffer = (char *) calloc( bufferBytes, 1 ); if ( stream_.deviceBuffer == NULL ) { errorText_ = "RtApiPulse::probeDeviceOpen: error allocating device buffer memory."; goto error; } } } stream_.deviceId[mode] = deviceIdx; // Setup the buffer conversion information structure. if ( stream_.doConvertBuffer[mode] ) setConvertInfo( mode, firstChannel ); if ( !stream_.apiHandle ) { PulseAudioHandle *pah = new PulseAudioHandle; if ( !pah ) { errorText_ = "RtApiPulse::probeDeviceOpen: error allocating memory for handle."; goto error; } stream_.apiHandle = pah; if ( pthread_cond_init( &pah->runnable_cv, NULL ) != 0 ) { errorText_ = "RtApiPulse::probeDeviceOpen: error creating condition variable."; goto error; } } pah = static_cast( stream_.apiHandle ); int error; if ( options && !options->streamName.empty() ) streamName = options->streamName; switch ( mode ) { pa_buffer_attr buffer_attr; case INPUT: buffer_attr.fragsize = bufferBytes; buffer_attr.maxlength = -1; pah->s_rec = pa_simple_new( NULL, streamName.c_str(), PA_STREAM_RECORD, dev_input, "Record", &ss, NULL, &buffer_attr, &error ); if ( !pah->s_rec ) { errorText_ = "RtApiPulse::probeDeviceOpen: error connecting input to PulseAudio server."; goto error; } break; case OUTPUT: { pa_buffer_attr * attr_ptr; if ( options && options->numberOfBuffers > 0 ) { // pa_buffer_attr::fragsize is recording-only. // Hopefully PortAudio won't access uninitialized fields. buffer_attr.maxlength = bufferBytes * options->numberOfBuffers; buffer_attr.minreq = -1; buffer_attr.prebuf = -1; buffer_attr.tlength = -1; attr_ptr = &buffer_attr; } else { attr_ptr = nullptr; } pah->s_play = pa_simple_new( NULL, streamName.c_str(), PA_STREAM_PLAYBACK, dev_output, "Playback", &ss, NULL, attr_ptr, &error ); if ( !pah->s_play ) { errorText_ = "RtApiPulse::probeDeviceOpen: error connecting output to PulseAudio server."; goto error; } break; } case DUPLEX: /* Note: We could add DUPLEX by synchronizing multiple streams, but it would mean moving from Simple API to Asynchronous API: https://freedesktop.org/software/pulseaudio/doxygen/streams.html#sync_streams */ errorText_ = "RtApiPulse::probeDeviceOpen: duplex not supported for PulseAudio."; goto error; default: goto error; } if ( stream_.mode == UNINITIALIZED ) stream_.mode = mode; else if ( stream_.mode == mode ) goto error; else stream_.mode = DUPLEX; if ( !stream_.callbackInfo.isRunning ) { stream_.callbackInfo.object = this; stream_.state = STREAM_STOPPED; // Set the thread attributes for joinable and realtime scheduling // priority (optional). The higher priority will only take affect // if the program is run as root or suid. Note, under Linux // processes with CAP_SYS_NICE privilege, a user can change // scheduling policy and priority (thus need not be root). See // POSIX "capabilities". pthread_attr_t attr; pthread_attr_init( &attr ); pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_JOINABLE ); #ifdef SCHED_RR // Undefined with some OSes (e.g. NetBSD 1.6.x with GNU Pthread) if ( options && options->flags & RTAUDIO_SCHEDULE_REALTIME ) { stream_.callbackInfo.doRealtime = true; struct sched_param param; int priority = options->priority; int min = sched_get_priority_min( SCHED_RR ); int max = sched_get_priority_max( SCHED_RR ); if ( priority < min ) priority = min; else if ( priority > max ) priority = max; param.sched_priority = priority; // Set the policy BEFORE the priority. Otherwise it fails. pthread_attr_setschedpolicy(&attr, SCHED_RR); pthread_attr_setscope (&attr, PTHREAD_SCOPE_SYSTEM); // This is definitely required. Otherwise it fails. pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED); pthread_attr_setschedparam(&attr, ¶m); } else pthread_attr_setschedpolicy( &attr, SCHED_OTHER ); #else pthread_attr_setschedpolicy( &attr, SCHED_OTHER ); #endif stream_.callbackInfo.isRunning = true; int result = pthread_create( &pah->thread, &attr, pulseaudio_callback, (void *)&stream_.callbackInfo); pthread_attr_destroy(&attr); if(result != 0) { // Failed. Try instead with default attributes. result = pthread_create( &pah->thread, NULL, pulseaudio_callback, (void *)&stream_.callbackInfo); if(result != 0) { stream_.callbackInfo.isRunning = false; errorText_ = "RtApiPulse::probeDeviceOpen: error creating thread."; goto error; } } } return SUCCESS; error: if ( pah && stream_.callbackInfo.isRunning ) { pthread_cond_destroy( &pah->runnable_cv ); delete pah; stream_.apiHandle = 0; } for ( int i=0; i<2; i++ ) { if ( stream_.userBuffer[i] ) { free( stream_.userBuffer[i] ); stream_.userBuffer[i] = 0; } } if ( stream_.deviceBuffer ) { free( stream_.deviceBuffer ); stream_.deviceBuffer = 0; } stream_.state = STREAM_CLOSED; return FAILURE; } void RtApiPulse::closeStream( void ) { PulseAudioHandle *pah = static_cast( stream_.apiHandle ); stream_.callbackInfo.isRunning = false; if ( pah ) { MUTEX_LOCK( &stream_.mutex ); if ( stream_.state == STREAM_STOPPED ) { pah->runnable = true; pthread_cond_signal( &pah->runnable_cv ); } MUTEX_UNLOCK( &stream_.mutex ); pthread_join( pah->thread, 0 ); if ( pah->s_play ) { pa_simple_flush( pah->s_play, NULL ); pa_simple_free( pah->s_play ); } if ( pah->s_rec ) pa_simple_free( pah->s_rec ); pthread_cond_destroy( &pah->runnable_cv ); delete pah; stream_.apiHandle = 0; } if ( stream_.userBuffer[0] ) { free( stream_.userBuffer[0] ); stream_.userBuffer[0] = 0; } if ( stream_.userBuffer[1] ) { free( stream_.userBuffer[1] ); stream_.userBuffer[1] = 0; } clearStreamInfo(); } void RtApiPulse::callbackEvent( void ) { PulseAudioHandle *pah = static_cast( stream_.apiHandle ); if ( stream_.state == STREAM_STOPPED ) { MUTEX_LOCK( &stream_.mutex ); while ( !pah->runnable ) pthread_cond_wait( &pah->runnable_cv, &stream_.mutex ); if ( stream_.state != STREAM_RUNNING ) { MUTEX_UNLOCK( &stream_.mutex ); return; } MUTEX_UNLOCK( &stream_.mutex ); } if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiPulse::callbackEvent(): the stream is closed ... " "this shouldn't happen!"; error( RTAUDIO_WARNING ); return; } RtAudioCallback callback = (RtAudioCallback) stream_.callbackInfo.callback; double streamTime = getStreamTime(); RtAudioStreamStatus status = 0; int doStopStream = callback( stream_.userBuffer[OUTPUT], stream_.userBuffer[INPUT], stream_.bufferSize, streamTime, status, stream_.callbackInfo.userData ); if ( doStopStream == 2 ) { abortStream(); return; } MUTEX_LOCK( &stream_.mutex ); void *pulse_in = stream_.doConvertBuffer[INPUT] ? stream_.deviceBuffer : stream_.userBuffer[INPUT]; void *pulse_out = stream_.doConvertBuffer[OUTPUT] ? stream_.deviceBuffer : stream_.userBuffer[OUTPUT]; if ( stream_.state != STREAM_RUNNING ) goto unlock; int pa_error; size_t bytes; if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { if ( stream_.doConvertBuffer[OUTPUT] ) { convertBuffer( stream_.deviceBuffer, stream_.userBuffer[OUTPUT], stream_.convertInfo[OUTPUT] ); bytes = stream_.nDeviceChannels[OUTPUT] * stream_.bufferSize * formatBytes( stream_.deviceFormat[OUTPUT] ); } else bytes = stream_.nUserChannels[OUTPUT] * stream_.bufferSize * formatBytes( stream_.userFormat ); if ( pa_simple_write( pah->s_play, pulse_out, bytes, &pa_error ) < 0 ) { errorStream_ << "RtApiPulse::callbackEvent: audio write error, " << pa_strerror( pa_error ) << "."; errorText_ = errorStream_.str(); error( RTAUDIO_WARNING ); } } if ( stream_.mode == INPUT || stream_.mode == DUPLEX) { if ( stream_.doConvertBuffer[INPUT] ) bytes = stream_.nDeviceChannels[INPUT] * stream_.bufferSize * formatBytes( stream_.deviceFormat[INPUT] ); else bytes = stream_.nUserChannels[INPUT] * stream_.bufferSize * formatBytes( stream_.userFormat ); if ( pa_simple_read( pah->s_rec, pulse_in, bytes, &pa_error ) < 0 ) { errorStream_ << "RtApiPulse::callbackEvent: audio read error, " << pa_strerror( pa_error ) << "."; errorText_ = errorStream_.str(); error( RTAUDIO_WARNING ); } if ( stream_.doConvertBuffer[INPUT] ) { convertBuffer( stream_.userBuffer[INPUT], stream_.deviceBuffer, stream_.convertInfo[INPUT] ); } } unlock: MUTEX_UNLOCK( &stream_.mutex ); RtApi::tickStreamTime(); if ( doStopStream == 1 ) stopStream(); } RtAudioErrorType RtApiPulse::startStream( void ) { if ( stream_.state != STREAM_STOPPED ) { if ( stream_.state == STREAM_RUNNING ) errorText_ = "RtApiPulse::startStream(): the stream is already running!"; else if ( stream_.state == STREAM_STOPPING || stream_.state == STREAM_CLOSED ) errorText_ = "RtApiPulse::startStream(): the stream is stopping or closed!"; return error( RTAUDIO_WARNING ); } PulseAudioHandle *pah = static_cast( stream_.apiHandle ); MUTEX_LOCK( &stream_.mutex ); /* #if defined( HAVE_GETTIMEOFDAY ) gettimeofday( &stream_.lastTickTimestamp, NULL ); #endif */ stream_.state = STREAM_RUNNING; pah->runnable = true; pthread_cond_signal( &pah->runnable_cv ); MUTEX_UNLOCK( &stream_.mutex ); return RTAUDIO_NO_ERROR; } RtAudioErrorType RtApiPulse::stopStream( void ) { if ( stream_.state != STREAM_RUNNING && stream_.state != STREAM_STOPPING ) { if ( stream_.state == STREAM_STOPPED ) errorText_ = "RtApiPulse::stopStream(): the stream is already stopped!"; else if ( stream_.state == STREAM_CLOSED ) errorText_ = "RtApiPulse::stopStream(): the stream is closed!"; return error( RTAUDIO_WARNING ); } PulseAudioHandle *pah = static_cast( stream_.apiHandle ); stream_.state = STREAM_STOPPED; MUTEX_LOCK( &stream_.mutex ); if ( pah ) { pah->runnable = false; if ( pah->s_play ) { int pa_error; if ( pa_simple_drain( pah->s_play, &pa_error ) < 0 ) { errorStream_ << "RtApiPulse::stopStream: error draining output device, " << pa_strerror( pa_error ) << "."; errorText_ = errorStream_.str(); MUTEX_UNLOCK( &stream_.mutex ); return error( RTAUDIO_SYSTEM_ERROR ); } } } stream_.state = STREAM_STOPPED; MUTEX_UNLOCK( &stream_.mutex ); return RTAUDIO_NO_ERROR; } RtAudioErrorType RtApiPulse::abortStream( void ) { if ( stream_.state != STREAM_RUNNING ) { if ( stream_.state == STREAM_STOPPED ) errorText_ = "RtApiPulse::abortStream(): the stream is already stopped!"; else if ( stream_.state == STREAM_STOPPING || stream_.state == STREAM_CLOSED ) errorText_ = "RtApiPulse::abortStream(): the stream is stopping or closed!"; return error( RTAUDIO_WARNING ); } PulseAudioHandle *pah = static_cast( stream_.apiHandle ); stream_.state = STREAM_STOPPED; MUTEX_LOCK( &stream_.mutex ); if ( pah ) { pah->runnable = false; if ( pah->s_play ) { int pa_error; if ( pa_simple_flush( pah->s_play, &pa_error ) < 0 ) { errorStream_ << "RtApiPulse::abortStream: error flushing output device, " << pa_strerror( pa_error ) << "."; errorText_ = errorStream_.str(); MUTEX_UNLOCK( &stream_.mutex ); return error( RTAUDIO_SYSTEM_ERROR ); } } } stream_.state = STREAM_STOPPED; MUTEX_UNLOCK( &stream_.mutex ); return RTAUDIO_NO_ERROR; } //******************** End of __LINUX_PULSE__ *********************// #endif #if defined(__LINUX_OSS__) #include #include #include #include #include #include static void *ossCallbackHandler(void * ptr); // A structure to hold various information related to the OSS API // implementation. struct OssHandle { int id[2]; // device ids bool xrun[2]; bool triggered; pthread_cond_t runnable; OssHandle() :triggered(false) { id[0] = 0; id[1] = 0; xrun[0] = false; xrun[1] = false; } }; RtApiOss :: RtApiOss() { // Nothing to do here. } RtApiOss :: ~RtApiOss() { if ( stream_.state != STREAM_CLOSED ) closeStream(); } void RtApiOss :: probeDevices( void ) { // See list of required functionality in RtApi::probeDevices(). int mixerfd = open( "/dev/mixer", O_RDWR, 0 ); if ( mixerfd == -1 ) { errorText_ = "RtApiOss::probeDevices: error opening '/dev/mixer'."; error( RTAUDIO_SYSTEM_ERROR ); return; } oss_sysinfo sysinfo; if ( ioctl( mixerfd, SNDCTL_SYSINFO, &sysinfo ) == -1 ) { close( mixerfd ); errorText_ = "RtApiOss::probeDevices: error getting sysinfo, OSS version >= 4.0 is required."; error( RTAUDIO_SYSTEM_ERROR ); return; } unsigned int nDevices = sysinfo.numaudios; if ( nDevices == 0 ) { close( mixerfd ); deviceList_.clear(); return; } oss_audioinfo ainfo; unsigned int m, n; std::vector deviceNames; for ( n=0; n::iterator it=deviceList_.begin(); it!=deviceList_.end(); ) { for ( m=0; m 0 && info.inputChannels > 0 && ainfo.caps & PCM_CAP_DUPLEX ) info.duplexChannels = (info.outputChannels > info.inputChannels) ? info.inputChannels : info.outputChannels; } // Probe data formats ... do for input unsigned long mask = ainfo.iformats; if ( mask & AFMT_S16_LE || mask & AFMT_S16_BE ) info.nativeFormats |= RTAUDIO_SINT16; if ( mask & AFMT_S8 ) info.nativeFormats |= RTAUDIO_SINT8; if ( mask & AFMT_S32_LE || mask & AFMT_S32_BE ) info.nativeFormats |= RTAUDIO_SINT32; #ifdef AFMT_FLOAT if ( mask & AFMT_FLOAT ) info.nativeFormats |= RTAUDIO_FLOAT32; #endif if ( mask & AFMT_S24_LE || mask & AFMT_S24_BE ) info.nativeFormats |= RTAUDIO_SINT24; // Check that we have at least one supported format if ( info.nativeFormats == 0 ) { errorStream_ << "RtApiOss::probeDeviceInfo: device (" << ainfo.name << ") data format not supported by RtAudio."; errorText_ = errorStream_.str(); error( RTAUDIO_WARNING ); return false; } // Probe the supported sample rates. info.sampleRates.clear(); if ( ainfo.nrates ) { for ( unsigned int i=0; i info.preferredSampleRate ) ) info.preferredSampleRate = SAMPLE_RATES[k]; break; } } } } else { // Check min and max rate values; for ( unsigned int k=0; k= (int) SAMPLE_RATES[k] ) { info.sampleRates.push_back( SAMPLE_RATES[k] ); if ( !info.preferredSampleRate || ( SAMPLE_RATES[k] <= 48000 && SAMPLE_RATES[k] > info.preferredSampleRate ) ) info.preferredSampleRate = SAMPLE_RATES[k]; } } } if ( info.sampleRates.size() == 0 ) { errorStream_ << "RtApiOss::probeDeviceInfo: no supported sample rates found for device (" << ainfo.name << ")."; errorText_ = errorStream_.str(); error( RTAUDIO_WARNING ); return false; } return true; } bool RtApiOss :: probeDeviceOpen( unsigned int deviceId, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, RtAudio::StreamOptions *options ) { int mixerfd = open( "/dev/mixer", O_RDWR, 0 ); if ( mixerfd == -1 ) { errorText_ = "RtApiOss::probeDeviceOpen: error opening '/dev/mixer'."; return FAILURE; } oss_sysinfo sysinfo; int result = ioctl( mixerfd, SNDCTL_SYSINFO, &sysinfo ); if ( result == -1 ) { close( mixerfd ); errorText_ = "RtApiOss::probeDeviceOpen: error getting sysinfo, OSS version >= 4.0 is required."; return FAILURE; } unsigned int nDevices = sysinfo.numaudios; if ( nDevices == 0 ) { // This should not happen because a check is made before this function is called. close( mixerfd ); errorText_ = "RtApiOss::probeDeviceOpen: no devices found!"; return FAILURE; } std::string deviceName; unsigned int m, device; for ( m=0; mid[0] ); handle->id[0] = 0; if ( !( ainfo.caps & PCM_CAP_DUPLEX ) ) { errorStream_ << "RtApiOss::probeDeviceOpen: device (" << ainfo.name << ") does not support duplex mode."; errorText_ = errorStream_.str(); return FAILURE; } // Check that the number previously set channels is the same. if ( stream_.nUserChannels[0] != channels ) { errorStream_ << "RtApiOss::probeDeviceOpen: input/output channels must be equal for OSS duplex device (" << ainfo.name << ")."; errorText_ = errorStream_.str(); return FAILURE; } flags |= O_RDWR; } else flags |= O_RDONLY; } // Set exclusive access if specified. if ( options && options->flags & RTAUDIO_HOG_DEVICE ) flags |= O_EXCL; // Try to open the device. int fd; fd = open( ainfo.devnode, flags, 0 ); if ( fd == -1 ) { if ( errno == EBUSY ) errorStream_ << "RtApiOss::probeDeviceOpen: device (" << ainfo.name << ") is busy."; else errorStream_ << "RtApiOss::probeDeviceOpen: error opening device (" << ainfo.name << ")."; errorText_ = errorStream_.str(); return FAILURE; } // For duplex operation, specifically set this mode (this doesn't seem to work). /* if ( flags | O_RDWR ) { result = ioctl( fd, SNDCTL_DSP_SETDUPLEX, NULL ); if ( result == -1) { errorStream_ << "RtApiOss::probeDeviceOpen: error setting duplex mode for device (" << ainfo.name << ")."; errorText_ = errorStream_.str(); return FAILURE; } } */ // Check the device channel support. stream_.nUserChannels[mode] = channels; if ( ainfo.max_channels < (int)(channels + firstChannel) ) { close( fd ); errorStream_ << "RtApiOss::probeDeviceOpen: the device (" << ainfo.name << ") does not support requested channel parameters."; errorText_ = errorStream_.str(); return FAILURE; } // Set the number of channels. int deviceChannels = channels + firstChannel; result = ioctl( fd, SNDCTL_DSP_CHANNELS, &deviceChannels ); if ( result == -1 || deviceChannels < (int)(channels + firstChannel) ) { close( fd ); errorStream_ << "RtApiOss::probeDeviceOpen: error setting channel parameters on device (" << ainfo.name << ")."; errorText_ = errorStream_.str(); return FAILURE; } stream_.nDeviceChannels[mode] = deviceChannels; // Get the data format mask int mask; result = ioctl( fd, SNDCTL_DSP_GETFMTS, &mask ); if ( result == -1 ) { close( fd ); errorStream_ << "RtApiOss::probeDeviceOpen: error getting device (" << ainfo.name << ") data formats."; errorText_ = errorStream_.str(); return FAILURE; } // Determine how to set the device format. stream_.userFormat = format; int deviceFormat = -1; stream_.doByteSwap[mode] = false; if ( format == RTAUDIO_SINT8 ) { if ( mask & AFMT_S8 ) { deviceFormat = AFMT_S8; stream_.deviceFormat[mode] = RTAUDIO_SINT8; } } else if ( format == RTAUDIO_SINT16 ) { if ( mask & AFMT_S16_NE ) { deviceFormat = AFMT_S16_NE; stream_.deviceFormat[mode] = RTAUDIO_SINT16; } else if ( mask & AFMT_S16_OE ) { deviceFormat = AFMT_S16_OE; stream_.deviceFormat[mode] = RTAUDIO_SINT16; stream_.doByteSwap[mode] = true; } } else if ( format == RTAUDIO_SINT24 ) { if ( mask & AFMT_S24_NE ) { deviceFormat = AFMT_S24_NE; stream_.deviceFormat[mode] = RTAUDIO_SINT24; } else if ( mask & AFMT_S24_OE ) { deviceFormat = AFMT_S24_OE; stream_.deviceFormat[mode] = RTAUDIO_SINT24; stream_.doByteSwap[mode] = true; } } else if ( format == RTAUDIO_SINT32 ) { if ( mask & AFMT_S32_NE ) { deviceFormat = AFMT_S32_NE; stream_.deviceFormat[mode] = RTAUDIO_SINT32; } else if ( mask & AFMT_S32_OE ) { deviceFormat = AFMT_S32_OE; stream_.deviceFormat[mode] = RTAUDIO_SINT32; stream_.doByteSwap[mode] = true; } } if ( deviceFormat == -1 ) { // The user requested format is not natively supported by the device. if ( mask & AFMT_S16_NE ) { deviceFormat = AFMT_S16_NE; stream_.deviceFormat[mode] = RTAUDIO_SINT16; } else if ( mask & AFMT_S32_NE ) { deviceFormat = AFMT_S32_NE; stream_.deviceFormat[mode] = RTAUDIO_SINT32; } else if ( mask & AFMT_S24_NE ) { deviceFormat = AFMT_S24_NE; stream_.deviceFormat[mode] = RTAUDIO_SINT24; } else if ( mask & AFMT_S16_OE ) { deviceFormat = AFMT_S16_OE; stream_.deviceFormat[mode] = RTAUDIO_SINT16; stream_.doByteSwap[mode] = true; } else if ( mask & AFMT_S32_OE ) { deviceFormat = AFMT_S32_OE; stream_.deviceFormat[mode] = RTAUDIO_SINT32; stream_.doByteSwap[mode] = true; } else if ( mask & AFMT_S24_OE ) { deviceFormat = AFMT_S24_OE; stream_.deviceFormat[mode] = RTAUDIO_SINT24; stream_.doByteSwap[mode] = true; } else if ( mask & AFMT_S8) { deviceFormat = AFMT_S8; stream_.deviceFormat[mode] = RTAUDIO_SINT8; } } if ( stream_.deviceFormat[mode] == 0 ) { // This really shouldn't happen ... close( fd ); errorStream_ << "RtApiOss::probeDeviceOpen: device (" << ainfo.name << ") data format not supported by RtAudio."; errorText_ = errorStream_.str(); return FAILURE; } // Set the data format. int temp = deviceFormat; result = ioctl( fd, SNDCTL_DSP_SETFMT, &deviceFormat ); if ( result == -1 || deviceFormat != temp ) { close( fd ); errorStream_ << "RtApiOss::probeDeviceOpen: error setting data format on device (" << ainfo.name << ")."; errorText_ = errorStream_.str(); return FAILURE; } // Attempt to set the buffer size. According to OSS, the minimum // number of buffers is two. The supposed minimum buffer size is 16 // bytes, so that will be our lower bound. The argument to this // call is in the form 0xMMMMSSSS (hex), where the buffer size (in // bytes) is given as 2^SSSS and the number of buffers as 2^MMMM. // We'll check the actual value used near the end of the setup // procedure. int ossBufferBytes = *bufferSize * formatBytes( stream_.deviceFormat[mode] ) * deviceChannels; if ( ossBufferBytes < 16 ) ossBufferBytes = 16; int buffers = 0; if ( options ) buffers = options->numberOfBuffers; if ( options && options->flags & RTAUDIO_MINIMIZE_LATENCY ) buffers = 2; if ( buffers < 2 ) buffers = 3; temp = ((int) buffers << 16) + (int)( log10( (double)ossBufferBytes ) / log10( 2.0 ) ); result = ioctl( fd, SNDCTL_DSP_SETFRAGMENT, &temp ); if ( result == -1 ) { close( fd ); errorStream_ << "RtApiOss::probeDeviceOpen: error setting buffer size on device (" << ainfo.name << ")."; errorText_ = errorStream_.str(); return FAILURE; } stream_.nBuffers = buffers; // Save buffer size (in sample frames). *bufferSize = ossBufferBytes / ( formatBytes(stream_.deviceFormat[mode]) * deviceChannels ); stream_.bufferSize = *bufferSize; // Set the sample rate. int srate = sampleRate; result = ioctl( fd, SNDCTL_DSP_SPEED, &srate ); if ( result == -1 ) { close( fd ); errorStream_ << "RtApiOss::probeDeviceOpen: error setting sample rate (" << sampleRate << ") on device (" << ainfo.name << ")."; errorText_ = errorStream_.str(); return FAILURE; } // Verify the sample rate setup worked. if ( abs( srate - (int)sampleRate ) > 100 ) { close( fd ); errorStream_ << "RtApiOss::probeDeviceOpen: device (" << ainfo.name << ") does not support sample rate (" << sampleRate << ")."; errorText_ = errorStream_.str(); return FAILURE; } stream_.sampleRate = sampleRate; if ( mode == INPUT && stream_.mode == OUTPUT && stream_.deviceId[0] == device) { // We're doing duplex setup here. stream_.deviceFormat[0] = stream_.deviceFormat[1]; stream_.nDeviceChannels[0] = deviceChannels; } // Set interleaving parameters. stream_.userInterleaved = true; stream_.deviceInterleaved[mode] = true; if ( options && options->flags & RTAUDIO_NONINTERLEAVED ) stream_.userInterleaved = false; // Set flags for buffer conversion stream_.doConvertBuffer[mode] = false; if ( stream_.userFormat != stream_.deviceFormat[mode] ) stream_.doConvertBuffer[mode] = true; if ( stream_.nUserChannels[mode] < stream_.nDeviceChannels[mode] ) stream_.doConvertBuffer[mode] = true; if ( stream_.userInterleaved != stream_.deviceInterleaved[mode] && stream_.nUserChannels[mode] > 1 ) stream_.doConvertBuffer[mode] = true; // Allocate the stream handles if necessary and then save. if ( stream_.apiHandle == 0 ) { try { handle = new OssHandle; } catch ( std::bad_alloc& ) { errorText_ = "RtApiOss::probeDeviceOpen: error allocating OssHandle memory."; goto error; } if ( pthread_cond_init( &handle->runnable, NULL ) ) { errorText_ = "RtApiOss::probeDeviceOpen: error initializing pthread condition variable."; goto error; } stream_.apiHandle = (void *) handle; } else { handle = (OssHandle *) stream_.apiHandle; } handle->id[mode] = fd; // Allocate necessary internal buffers. unsigned long bufferBytes; bufferBytes = stream_.nUserChannels[mode] * *bufferSize * formatBytes( stream_.userFormat ); stream_.userBuffer[mode] = (char *) calloc( bufferBytes, 1 ); if ( stream_.userBuffer[mode] == NULL ) { errorText_ = "RtApiOss::probeDeviceOpen: error allocating user buffer memory."; goto error; } if ( stream_.doConvertBuffer[mode] ) { bool makeBuffer = true; bufferBytes = stream_.nDeviceChannels[mode] * formatBytes( stream_.deviceFormat[mode] ); if ( mode == INPUT ) { if ( stream_.mode == OUTPUT && stream_.deviceBuffer ) { unsigned long bytesOut = stream_.nDeviceChannels[0] * formatBytes( stream_.deviceFormat[0] ); if ( bufferBytes <= bytesOut ) makeBuffer = false; } } if ( makeBuffer ) { bufferBytes *= *bufferSize; if ( stream_.deviceBuffer ) free( stream_.deviceBuffer ); stream_.deviceBuffer = (char *) calloc( bufferBytes, 1 ); if ( stream_.deviceBuffer == NULL ) { errorText_ = "RtApiOss::probeDeviceOpen: error allocating device buffer memory."; goto error; } } } stream_.deviceId[mode] = device; stream_.state = STREAM_STOPPED; // Setup the buffer conversion information structure. if ( stream_.doConvertBuffer[mode] ) setConvertInfo( mode, firstChannel ); // Setup thread if necessary. if ( stream_.mode == OUTPUT && mode == INPUT ) { // We had already set up an output stream. stream_.mode = DUPLEX; if ( stream_.deviceId[0] == device ) handle->id[0] = fd; } else { stream_.mode = mode; // Setup callback thread. stream_.callbackInfo.object = (void *) this; // Set the thread attributes for joinable and realtime scheduling // priority. The higher priority will only take affect if the // program is run as root or suid. pthread_attr_t attr; pthread_attr_init( &attr ); pthread_attr_setdetachstate( &attr, PTHREAD_CREATE_JOINABLE ); #ifdef SCHED_RR // Undefined with some OSes (e.g. NetBSD 1.6.x with GNU Pthread) if ( options && options->flags & RTAUDIO_SCHEDULE_REALTIME ) { stream_.callbackInfo.doRealtime = true; struct sched_param param; int priority = options->priority; int min = sched_get_priority_min( SCHED_RR ); int max = sched_get_priority_max( SCHED_RR ); if ( priority < min ) priority = min; else if ( priority > max ) priority = max; param.sched_priority = priority; // Set the policy BEFORE the priority. Otherwise it fails. pthread_attr_setschedpolicy(&attr, SCHED_RR); pthread_attr_setscope (&attr, PTHREAD_SCOPE_SYSTEM); // This is definitely required. Otherwise it fails. pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED); pthread_attr_setschedparam(&attr, ¶m); } else pthread_attr_setschedpolicy( &attr, SCHED_OTHER ); #else pthread_attr_setschedpolicy( &attr, SCHED_OTHER ); #endif stream_.callbackInfo.isRunning = true; result = pthread_create( &stream_.callbackInfo.thread, &attr, ossCallbackHandler, &stream_.callbackInfo ); pthread_attr_destroy( &attr ); if ( result ) { // Failed. Try instead with default attributes. result = pthread_create( &stream_.callbackInfo.thread, NULL, ossCallbackHandler, &stream_.callbackInfo ); if ( result ) { stream_.callbackInfo.isRunning = false; errorText_ = "RtApiOss::error creating callback thread!"; goto error; } } } return SUCCESS; error: if ( handle ) { pthread_cond_destroy( &handle->runnable ); if ( handle->id[0] ) close( handle->id[0] ); if ( handle->id[1] ) close( handle->id[1] ); delete handle; stream_.apiHandle = 0; } for ( int i=0; i<2; i++ ) { if ( stream_.userBuffer[i] ) { free( stream_.userBuffer[i] ); stream_.userBuffer[i] = 0; } } if ( stream_.deviceBuffer ) { free( stream_.deviceBuffer ); stream_.deviceBuffer = 0; } stream_.state = STREAM_CLOSED; return FAILURE; } void RtApiOss :: closeStream() { if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiOss::closeStream(): no open stream to close!"; error( RTAUDIO_WARNING ); return; } OssHandle *handle = (OssHandle *) stream_.apiHandle; stream_.callbackInfo.isRunning = false; MUTEX_LOCK( &stream_.mutex ); if ( stream_.state == STREAM_STOPPED ) pthread_cond_signal( &handle->runnable ); MUTEX_UNLOCK( &stream_.mutex ); pthread_join( stream_.callbackInfo.thread, NULL ); if ( stream_.state == STREAM_RUNNING ) { if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) ioctl( handle->id[0], SNDCTL_DSP_HALT, 0 ); else ioctl( handle->id[1], SNDCTL_DSP_HALT, 0 ); stream_.state = STREAM_STOPPED; } if ( handle ) { pthread_cond_destroy( &handle->runnable ); if ( handle->id[0] ) close( handle->id[0] ); if ( handle->id[1] ) close( handle->id[1] ); delete handle; stream_.apiHandle = 0; } for ( int i=0; i<2; i++ ) { if ( stream_.userBuffer[i] ) { free( stream_.userBuffer[i] ); stream_.userBuffer[i] = 0; } } if ( stream_.deviceBuffer ) { free( stream_.deviceBuffer ); stream_.deviceBuffer = 0; } clearStreamInfo(); //stream_.mode = UNINITIALIZED; //stream_.state = STREAM_CLOSED; } RtAudioErrorType RtApiOss :: startStream() { if ( stream_.state != STREAM_STOPPED ) { if ( stream_.state == STREAM_RUNNING ) errorText_ = "RtApiOss::startStream(): the stream is already running!"; else if ( stream_.state == STREAM_STOPPING || stream_.state == STREAM_CLOSED ) errorText_ = "RtApiOss::startStream(): the stream is stopping or closed!"; return error( RTAUDIO_WARNING ); } MUTEX_LOCK( &stream_.mutex ); /* #if defined( HAVE_GETTIMEOFDAY ) gettimeofday( &stream_.lastTickTimestamp, NULL ); #endif */ stream_.state = STREAM_RUNNING; // No need to do anything else here ... OSS automatically starts // when fed samples. MUTEX_UNLOCK( &stream_.mutex ); OssHandle *handle = (OssHandle *) stream_.apiHandle; pthread_cond_signal( &handle->runnable ); return RTAUDIO_NO_ERROR; } RtAudioErrorType RtApiOss :: stopStream() { if ( stream_.state != STREAM_RUNNING && stream_.state != STREAM_STOPPING ) { if ( stream_.state == STREAM_STOPPED ) errorText_ = "RtApiOss::stopStream(): the stream is already stopped!"; else if ( stream_.state == STREAM_CLOSED ) errorText_ = "RtApiOss::stopStream(): the stream is closed!"; return error( RTAUDIO_WARNING ); } MUTEX_LOCK( &stream_.mutex ); // The state might change while waiting on a mutex. if ( stream_.state == STREAM_STOPPED ) { MUTEX_UNLOCK( &stream_.mutex ); return RTAUDIO_NO_ERROR; } int result = 0; OssHandle *handle = (OssHandle *) stream_.apiHandle; if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { // Flush the output with zeros a few times. char *buffer; int samples; RtAudioFormat format; if ( stream_.doConvertBuffer[0] ) { buffer = stream_.deviceBuffer; samples = stream_.bufferSize * stream_.nDeviceChannels[0]; format = stream_.deviceFormat[0]; } else { buffer = stream_.userBuffer[0]; samples = stream_.bufferSize * stream_.nUserChannels[0]; format = stream_.userFormat; } memset( buffer, 0, samples * formatBytes(format) ); for ( unsigned int i=0; iid[0], buffer, samples * formatBytes(format) ); if ( result == -1 ) { errorText_ = "RtApiOss::stopStream: audio write error."; error( RTAUDIO_WARNING ); } } result = ioctl( handle->id[0], SNDCTL_DSP_HALT, 0 ); if ( result == -1 ) { errorStream_ << "RtApiOss::stopStream: system error stopping callback procedure on device (" << stream_.deviceId[0] << ")."; errorText_ = errorStream_.str(); goto unlock; } handle->triggered = false; } if ( stream_.mode == INPUT || ( stream_.mode == DUPLEX && handle->id[0] != handle->id[1] ) ) { result = ioctl( handle->id[1], SNDCTL_DSP_HALT, 0 ); if ( result == -1 ) { errorStream_ << "RtApiOss::stopStream: system error stopping input callback procedure on device (" << stream_.deviceId[0] << ")."; errorText_ = errorStream_.str(); goto unlock; } } unlock: stream_.state = STREAM_STOPPED; MUTEX_UNLOCK( &stream_.mutex ); if ( result != -1 ) return RTAUDIO_NO_ERROR; return error( RTAUDIO_SYSTEM_ERROR ); } RtAudioErrorType RtApiOss :: abortStream() { if ( stream_.state != STREAM_RUNNING ) { if ( stream_.state == STREAM_STOPPED ) errorText_ = "RtApiOss::abortStream(): the stream is already stopped!"; else if ( stream_.state == STREAM_STOPPING || stream_.state == STREAM_CLOSED ) errorText_ = "RtApiOss::abortStream(): the stream is stopping or closed!"; return error( RTAUDIO_WARNING ); } MUTEX_LOCK( &stream_.mutex ); // The state might change while waiting on a mutex. if ( stream_.state == STREAM_STOPPED ) { MUTEX_UNLOCK( &stream_.mutex ); return RTAUDIO_NO_ERROR; } int result = 0; OssHandle *handle = (OssHandle *) stream_.apiHandle; if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { result = ioctl( handle->id[0], SNDCTL_DSP_HALT, 0 ); if ( result == -1 ) { errorStream_ << "RtApiOss::abortStream: system error stopping callback procedure on device (" << stream_.deviceId[0] << ")."; errorText_ = errorStream_.str(); goto unlock; } handle->triggered = false; } if ( stream_.mode == INPUT || ( stream_.mode == DUPLEX && handle->id[0] != handle->id[1] ) ) { result = ioctl( handle->id[1], SNDCTL_DSP_HALT, 0 ); if ( result == -1 ) { errorStream_ << "RtApiOss::abortStream: system error stopping input callback procedure on device (" << stream_.deviceId[0] << ")."; errorText_ = errorStream_.str(); goto unlock; } } unlock: stream_.state = STREAM_STOPPED; MUTEX_UNLOCK( &stream_.mutex ); if ( result != -1 ) return RTAUDIO_SYSTEM_ERROR; return error( RTAUDIO_SYSTEM_ERROR ); } void RtApiOss :: callbackEvent() { OssHandle *handle = (OssHandle *) stream_.apiHandle; if ( stream_.state == STREAM_STOPPED ) { MUTEX_LOCK( &stream_.mutex ); pthread_cond_wait( &handle->runnable, &stream_.mutex ); if ( stream_.state != STREAM_RUNNING ) { MUTEX_UNLOCK( &stream_.mutex ); return; } MUTEX_UNLOCK( &stream_.mutex ); } if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApiOss::callbackEvent(): the stream is closed ... this shouldn't happen!"; error( RTAUDIO_WARNING ); return; } // Invoke user callback to get fresh output data. int doStopStream = 0; RtAudioCallback callback = (RtAudioCallback) stream_.callbackInfo.callback; double streamTime = getStreamTime(); RtAudioStreamStatus status = 0; if ( stream_.mode != INPUT && handle->xrun[0] == true ) { status |= RTAUDIO_OUTPUT_UNDERFLOW; handle->xrun[0] = false; } if ( stream_.mode != OUTPUT && handle->xrun[1] == true ) { status |= RTAUDIO_INPUT_OVERFLOW; handle->xrun[1] = false; } doStopStream = callback( stream_.userBuffer[0], stream_.userBuffer[1], stream_.bufferSize, streamTime, status, stream_.callbackInfo.userData ); if ( doStopStream == 2 ) { this->abortStream(); return; } MUTEX_LOCK( &stream_.mutex ); // The state might change while waiting on a mutex. if ( stream_.state == STREAM_STOPPED ) goto unlock; int result; char *buffer; int samples; RtAudioFormat format; if ( stream_.mode == OUTPUT || stream_.mode == DUPLEX ) { // Setup parameters and do buffer conversion if necessary. if ( stream_.doConvertBuffer[0] ) { buffer = stream_.deviceBuffer; convertBuffer( buffer, stream_.userBuffer[0], stream_.convertInfo[0] ); samples = stream_.bufferSize * stream_.nDeviceChannels[0]; format = stream_.deviceFormat[0]; } else { buffer = stream_.userBuffer[0]; samples = stream_.bufferSize * stream_.nUserChannels[0]; format = stream_.userFormat; } // Do byte swapping if necessary. if ( stream_.doByteSwap[0] ) byteSwapBuffer( buffer, samples, format ); if ( stream_.mode == DUPLEX && handle->triggered == false ) { int trig = 0; ioctl( handle->id[0], SNDCTL_DSP_SETTRIGGER, &trig ); result = write( handle->id[0], buffer, samples * formatBytes(format) ); trig = PCM_ENABLE_INPUT|PCM_ENABLE_OUTPUT; ioctl( handle->id[0], SNDCTL_DSP_SETTRIGGER, &trig ); handle->triggered = true; } else // Write samples to device. result = write( handle->id[0], buffer, samples * formatBytes(format) ); if ( result == -1 ) { // We'll assume this is an underrun, though there isn't a // specific means for determining that. handle->xrun[0] = true; errorText_ = "RtApiOss::callbackEvent: audio write error."; error( RTAUDIO_WARNING ); // Continue on to input section. } } if ( stream_.mode == INPUT || stream_.mode == DUPLEX ) { // Setup parameters. if ( stream_.doConvertBuffer[1] ) { buffer = stream_.deviceBuffer; samples = stream_.bufferSize * stream_.nDeviceChannels[1]; format = stream_.deviceFormat[1]; } else { buffer = stream_.userBuffer[1]; samples = stream_.bufferSize * stream_.nUserChannels[1]; format = stream_.userFormat; } // Read samples from device. result = read( handle->id[1], buffer, samples * formatBytes(format) ); if ( result == -1 ) { // We'll assume this is an overrun, though there isn't a // specific means for determining that. handle->xrun[1] = true; errorText_ = "RtApiOss::callbackEvent: audio read error."; error( RTAUDIO_WARNING ); goto unlock; } // Do byte swapping if necessary. if ( stream_.doByteSwap[1] ) byteSwapBuffer( buffer, samples, format ); // Do buffer conversion if necessary. if ( stream_.doConvertBuffer[1] ) convertBuffer( stream_.userBuffer[1], stream_.deviceBuffer, stream_.convertInfo[1] ); } unlock: MUTEX_UNLOCK( &stream_.mutex ); RtApi::tickStreamTime(); if ( doStopStream == 1 ) this->stopStream(); } static void *ossCallbackHandler( void *ptr ) { CallbackInfo *info = (CallbackInfo *) ptr; RtApiOss *object = (RtApiOss *) info->object; bool *isRunning = &info->isRunning; #ifdef SCHED_RR // Undefined with some OSes (e.g. NetBSD 1.6.x with GNU Pthread) if (info->doRealtime) { std::cerr << "RtAudio oss: " << (sched_getscheduler(0) == SCHED_RR ? "" : "_NOT_ ") << "running realtime scheduling" << std::endl; } #endif while ( *isRunning == true ) { pthread_testcancel(); object->callbackEvent(); } pthread_exit( NULL ); } //******************** End of __LINUX_OSS__ *********************// #endif // *************************************************** // // // Protected common (OS-independent) RtAudio methods. // // *************************************************** // // This method can be modified to control the behavior of error // message printing. RtAudioErrorType RtApi :: error( RtAudioErrorType type ) { errorStream_.str(""); // clear the ostringstream to avoid repeated messages // Don't output warnings if showWarnings_ is false if ( type == RTAUDIO_WARNING && showWarnings_ == false ) return type; if ( errorCallback_ ) { //const std::string errorMessage = errorText_; //errorCallback_( type, errorMessage ); errorCallback_( type, errorText_ ); } else std::cerr << '\n' << errorText_ << "\n\n"; return type; } /* void RtApi :: verifyStream() { if ( stream_.state == STREAM_CLOSED ) { errorText_ = "RtApi:: a stream is not open!"; error( RtAudioError::INVALID_USE ); } } */ void RtApi :: clearStreamInfo() { stream_.mode = UNINITIALIZED; stream_.state = STREAM_CLOSED; stream_.sampleRate = 0; stream_.bufferSize = 0; stream_.nBuffers = 0; stream_.userFormat = 0; stream_.userInterleaved = true; stream_.streamTime = 0.0; stream_.apiHandle = 0; stream_.deviceBuffer = 0; stream_.callbackInfo.callback = 0; stream_.callbackInfo.userData = 0; stream_.callbackInfo.isRunning = false; stream_.callbackInfo.deviceDisconnected = false; for ( int i=0; i<2; i++ ) { stream_.deviceId[i] = 11111; stream_.doConvertBuffer[i] = false; stream_.deviceInterleaved[i] = true; stream_.doByteSwap[i] = false; stream_.nUserChannels[i] = 0; stream_.nDeviceChannels[i] = 0; stream_.channelOffset[i] = 0; stream_.deviceFormat[i] = 0; stream_.latency[i] = 0; stream_.userBuffer[i] = 0; stream_.convertInfo[i].channels = 0; stream_.convertInfo[i].inJump = 0; stream_.convertInfo[i].outJump = 0; stream_.convertInfo[i].inFormat = 0; stream_.convertInfo[i].outFormat = 0; stream_.convertInfo[i].inOffset.clear(); stream_.convertInfo[i].outOffset.clear(); } } unsigned int RtApi :: formatBytes( RtAudioFormat format ) { if ( format == RTAUDIO_SINT16 ) return 2; else if ( format == RTAUDIO_SINT32 || format == RTAUDIO_FLOAT32 ) return 4; else if ( format == RTAUDIO_FLOAT64 ) return 8; else if ( format == RTAUDIO_SINT24 ) return 3; else if ( format == RTAUDIO_SINT8 ) return 1; errorText_ = "RtApi::formatBytes: undefined format."; error( RTAUDIO_WARNING ); return 0; } void RtApi :: setConvertInfo( StreamMode mode, unsigned int firstChannel ) { if ( mode == INPUT ) { // convert device to user buffer stream_.convertInfo[mode].inJump = stream_.nDeviceChannels[1]; stream_.convertInfo[mode].outJump = stream_.nUserChannels[1]; stream_.convertInfo[mode].inFormat = stream_.deviceFormat[1]; stream_.convertInfo[mode].outFormat = stream_.userFormat; } else { // convert user to device buffer stream_.convertInfo[mode].inJump = stream_.nUserChannels[0]; stream_.convertInfo[mode].outJump = stream_.nDeviceChannels[0]; stream_.convertInfo[mode].inFormat = stream_.userFormat; stream_.convertInfo[mode].outFormat = stream_.deviceFormat[0]; } if ( stream_.convertInfo[mode].inJump < stream_.convertInfo[mode].outJump ) stream_.convertInfo[mode].channels = stream_.convertInfo[mode].inJump; else stream_.convertInfo[mode].channels = stream_.convertInfo[mode].outJump; // Set up the interleave/deinterleave offsets. if ( stream_.deviceInterleaved[mode] != stream_.userInterleaved ) { if ( ( mode == OUTPUT && stream_.deviceInterleaved[mode] ) || ( mode == INPUT && stream_.userInterleaved ) ) { for ( int k=0; k 0 ) { if ( stream_.deviceInterleaved[mode] ) { if ( mode == OUTPUT ) { for ( int k=0; k info.inJump ) memset( outBuffer, 0, stream_.bufferSize * info.outJump * formatBytes( info.outFormat ) ); int j; if (info.outFormat == RTAUDIO_FLOAT64) { Float64 *out = (Float64 *)outBuffer; if (info.inFormat == RTAUDIO_SINT8) { signed char *in = (signed char *)inBuffer; for (unsigned int i=0; i> 8); //out[info.outOffset[j]] >>= 8; } in += info.inJump; out += info.outJump; } } else if (info.inFormat == RTAUDIO_FLOAT32) { Float32 *in = (Float32 *)inBuffer; for (unsigned int i=0; i> 8); } in += info.inJump; out += info.outJump; } } else if (info.inFormat == RTAUDIO_SINT32) { Int32 *in = (Int32 *)inBuffer; for (unsigned int i=0; i> 16) & 0x0000ffff); } in += info.inJump; out += info.outJump; } } else if (info.inFormat == RTAUDIO_FLOAT32) { Float32 *in = (Float32 *)inBuffer; for (unsigned int i=0; i> 8) & 0x00ff); } in += info.inJump; out += info.outJump; } } else if (info.inFormat == RTAUDIO_SINT24) { Int24 *in = (Int24 *)inBuffer; for (unsigned int i=0; i> 16); } in += info.inJump; out += info.outJump; } } else if (info.inFormat == RTAUDIO_SINT32) { Int32 *in = (Int32 *)inBuffer; for (unsigned int i=0; i> 24) & 0x000000ff); } in += info.inJump; out += info.outJump; } } else if (info.inFormat == RTAUDIO_FLOAT32) { Float32 *in = (Float32 *)inBuffer; for (unsigned int i=0; i>8) | (x<<8); } //static inline uint32_t bswap_32(uint32_t x) { return (bswap_16(x&0xffff)<<16) | (bswap_16(x>>16)); } //static inline uint64_t bswap_64(uint64_t x) { return (((unsigned long long)bswap_32(x&0xffffffffull))<<32) | (bswap_32(x>>32)); } void RtApi :: byteSwapBuffer( char *buffer, unsigned int samples, RtAudioFormat format ) { char val; char *ptr; ptr = buffer; if ( format == RTAUDIO_SINT16 ) { for ( unsigned int i=0; i 0 #define RTAUDIO_VERSION RTAUDIO_TOSTRING(RTAUDIO_VERSION_MAJOR) \ "." RTAUDIO_TOSTRING(RTAUDIO_VERSION_MINOR) \ "." RTAUDIO_TOSTRING(RTAUDIO_VERSION_PATCH) \ "beta" RTAUDIO_TOSTRING(RTAUDIO_VERSION_BETA) #else #define RTAUDIO_VERSION RTAUDIO_TOSTRING(RTAUDIO_VERSION_MAJOR) \ "." RTAUDIO_TOSTRING(RTAUDIO_VERSION_MINOR) \ "." RTAUDIO_TOSTRING(RTAUDIO_VERSION_PATCH) #endif #if defined _WIN32 || defined __CYGWIN__ #if defined(RTAUDIO_EXPORT) #define RTAUDIO_DLL_PUBLIC __declspec(dllexport) #else #define RTAUDIO_DLL_PUBLIC #endif #else #if __GNUC__ >= 4 #define RTAUDIO_DLL_PUBLIC __attribute__( (visibility( "default" )) ) #else #define RTAUDIO_DLL_PUBLIC #endif #endif #include #include #include #include /*! \typedef typedef unsigned long RtAudioFormat; \brief RtAudio data format type. Support for signed integers and floats. Audio data fed to/from an RtAudio stream is assumed to ALWAYS be in host byte order. The internal routines will automatically take care of any necessary byte-swapping between the host format and the soundcard. Thus, endian-ness is not a concern in the following format definitions. Note that there are no range checks for floating-point values that extend beyond plus/minus 1.0. - \e RTAUDIO_SINT8: 8-bit signed integer. - \e RTAUDIO_SINT16: 16-bit signed integer. - \e RTAUDIO_SINT24: 24-bit signed integer. - \e RTAUDIO_SINT32: 32-bit signed integer. - \e RTAUDIO_FLOAT32: Normalized between plus/minus 1.0. - \e RTAUDIO_FLOAT64: Normalized between plus/minus 1.0. */ typedef unsigned long RtAudioFormat; static const RtAudioFormat RTAUDIO_SINT8 = 0x1; // 8-bit signed integer. static const RtAudioFormat RTAUDIO_SINT16 = 0x2; // 16-bit signed integer. static const RtAudioFormat RTAUDIO_SINT24 = 0x4; // 24-bit signed integer. static const RtAudioFormat RTAUDIO_SINT32 = 0x8; // 32-bit signed integer. static const RtAudioFormat RTAUDIO_FLOAT32 = 0x10; // Normalized between plus/minus 1.0. static const RtAudioFormat RTAUDIO_FLOAT64 = 0x20; // Normalized between plus/minus 1.0. /*! \typedef typedef unsigned long RtAudioStreamFlags; \brief RtAudio stream option flags. The following flags can be OR'ed together to allow a client to make changes to the default stream behavior: - \e RTAUDIO_NONINTERLEAVED: Use non-interleaved buffers (default = interleaved). - \e RTAUDIO_MINIMIZE_LATENCY: Attempt to set stream parameters for lowest possible latency. - \e RTAUDIO_HOG_DEVICE: Attempt grab device for exclusive use. - \e RTAUDIO_ALSA_USE_DEFAULT: Use the "default" PCM device (ALSA only). - \e RTAUDIO_JACK_DONT_CONNECT: Do not automatically connect ports (JACK only). By default, RtAudio streams pass and receive audio data from the client in an interleaved format. By passing the RTAUDIO_NONINTERLEAVED flag to the openStream() function, audio data will instead be presented in non-interleaved buffers. In this case, each buffer argument in the RtAudioCallback function will point to a single array of data, with \c nFrames samples for each channel concatenated back-to-back. For example, the first sample of data for the second channel would be located at index \c nFrames (assuming the \c buffer pointer was recast to the correct data type for the stream). Certain audio APIs offer a number of parameters that influence the I/O latency of a stream. By default, RtAudio will attempt to set these parameters internally for robust (glitch-free) performance (though some APIs, like Windows DirectSound, make this difficult). By passing the RTAUDIO_MINIMIZE_LATENCY flag to the openStream() function, internal stream settings will be influenced in an attempt to minimize stream latency, though possibly at the expense of stream performance. If the RTAUDIO_HOG_DEVICE flag is set, RtAudio will attempt to open the input and/or output stream device(s) for exclusive use. Note that this is not possible with all supported audio APIs. If the RTAUDIO_SCHEDULE_REALTIME flag is set, RtAudio will attempt to select realtime scheduling (round-robin) for the callback thread. If the RTAUDIO_ALSA_USE_DEFAULT flag is set, RtAudio will attempt to open the "default" PCM device when using the ALSA API. Note that this will override any specified input or output device id. If the RTAUDIO_JACK_DONT_CONNECT flag is set, RtAudio will not attempt to automatically connect the ports of the client to the audio device. */ typedef unsigned int RtAudioStreamFlags; static const RtAudioStreamFlags RTAUDIO_NONINTERLEAVED = 0x1; // Use non-interleaved buffers (default = interleaved). static const RtAudioStreamFlags RTAUDIO_MINIMIZE_LATENCY = 0x2; // Attempt to set stream parameters for lowest possible latency. static const RtAudioStreamFlags RTAUDIO_HOG_DEVICE = 0x4; // Attempt grab device and prevent use by others. static const RtAudioStreamFlags RTAUDIO_SCHEDULE_REALTIME = 0x8; // Try to select realtime scheduling for callback thread. static const RtAudioStreamFlags RTAUDIO_ALSA_USE_DEFAULT = 0x10; // Use the "default" PCM device (ALSA only). static const RtAudioStreamFlags RTAUDIO_JACK_DONT_CONNECT = 0x20; // Do not automatically connect ports (JACK only). /*! \typedef typedef unsigned long RtAudioStreamStatus; \brief RtAudio stream status (over- or underflow) flags. Notification of a stream over- or underflow is indicated by a non-zero stream \c status argument in the RtAudioCallback function. The stream status can be one of the following two options, depending on whether the stream is open for output and/or input: - \e RTAUDIO_INPUT_OVERFLOW: Input data was discarded because of an overflow condition at the driver. - \e RTAUDIO_OUTPUT_UNDERFLOW: The output buffer ran low, likely producing a break in the output sound. */ typedef unsigned int RtAudioStreamStatus; static const RtAudioStreamStatus RTAUDIO_INPUT_OVERFLOW = 0x1; // Input data was discarded because of an overflow condition at the driver. static const RtAudioStreamStatus RTAUDIO_OUTPUT_UNDERFLOW = 0x2; // The output buffer ran low, likely causing a gap in the output sound. //! RtAudio callback function prototype. /*! All RtAudio clients must create a function of type RtAudioCallback to read and/or write data from/to the audio stream. When the underlying audio system is ready for new input or output data, this function will be invoked. \param outputBuffer For output (or duplex) streams, the client should write \c nFrames of audio sample frames into this buffer. This argument should be recast to the datatype specified when the stream was opened. For input-only streams, this argument will be NULL. \param inputBuffer For input (or duplex) streams, this buffer will hold \c nFrames of input audio sample frames. This argument should be recast to the datatype specified when the stream was opened. For output-only streams, this argument will be NULL. \param nFrames The number of sample frames of input or output data in the buffers. The actual buffer size in bytes is dependent on the data type and number of channels in use. \param streamTime The number of seconds that have elapsed since the stream was started. \param status If non-zero, this argument indicates a data overflow or underflow condition for the stream. The particular condition can be determined by comparison with the RtAudioStreamStatus flags. \param userData A pointer to optional data provided by the client when opening the stream (default = NULL). \return To continue normal stream operation, the RtAudioCallback function should return a value of zero. To stop the stream and drain the output buffer, the function should return a value of one. To abort the stream immediately, the client should return a value of two. */ typedef int (*RtAudioCallback)( void *outputBuffer, void *inputBuffer, unsigned int nFrames, double streamTime, RtAudioStreamStatus status, void *userData ); enum RtAudioErrorType { RTAUDIO_NO_ERROR = 0, /*!< No error. */ RTAUDIO_WARNING, /*!< A non-critical error. */ RTAUDIO_UNKNOWN_ERROR, /*!< An unspecified error type. */ RTAUDIO_NO_DEVICES_FOUND, /*!< No devices found on system. */ RTAUDIO_INVALID_DEVICE, /*!< An invalid device ID was specified. */ RTAUDIO_DEVICE_DISCONNECT, /*!< A device in use was disconnected. */ RTAUDIO_MEMORY_ERROR, /*!< An error occurred during memory allocation. */ RTAUDIO_INVALID_PARAMETER, /*!< An invalid parameter was specified to a function. */ RTAUDIO_INVALID_USE, /*!< The function was called incorrectly. */ RTAUDIO_DRIVER_ERROR, /*!< A system driver error occurred. */ RTAUDIO_SYSTEM_ERROR, /*!< A system error occurred. */ RTAUDIO_THREAD_ERROR /*!< A thread error occurred. */ }; //! RtAudio error callback function prototype. /*! \param type Type of error. \param errorText Error description. */ typedef std::function RtAudioErrorCallback; // **************************************************************** // // // RtAudio class declaration. // // RtAudio is a "controller" used to select an available audio i/o // interface. It presents a common API for the user to call but all // functionality is implemented by the class RtApi and its // subclasses. RtAudio creates an instance of an RtApi subclass // based on the user's API choice. If no choice is made, RtAudio // attempts to make a "logical" API selection. // // **************************************************************** // class RtApi; class RTAUDIO_DLL_PUBLIC RtAudio { public: //! Audio API specifier arguments. enum Api { UNSPECIFIED, /*!< Search for a working compiled API. */ MACOSX_CORE, /*!< Macintosh OS-X Core Audio API. */ LINUX_ALSA, /*!< The Advanced Linux Sound Architecture API. */ UNIX_JACK, /*!< The Jack Low-Latency Audio Server API. */ LINUX_PULSE, /*!< The Linux PulseAudio API. */ LINUX_OSS, /*!< The Linux Open Sound System API. */ WINDOWS_ASIO, /*!< The Steinberg Audio Stream I/O API. */ WINDOWS_WASAPI, /*!< The Microsoft WASAPI API. */ WINDOWS_DS, /*!< The Microsoft DirectSound API. */ RTAUDIO_DUMMY, /*!< A compilable but non-functional API. */ NUM_APIS /*!< Number of values in this enum. */ }; //! The public device information structure for returning queried values. struct DeviceInfo { unsigned int ID{}; /*!< Device ID used to specify a device to RtAudio. */ std::string name; /*!< Character string device name. */ unsigned int outputChannels{}; /*!< Maximum output channels supported by device. */ unsigned int inputChannels{}; /*!< Maximum input channels supported by device. */ unsigned int duplexChannels{}; /*!< Maximum simultaneous input/output channels supported by device. */ bool isDefaultOutput{false}; /*!< true if this is the default output device. */ bool isDefaultInput{false}; /*!< true if this is the default input device. */ std::vector sampleRates; /*!< Supported sample rates (queried from list of standard rates). */ unsigned int currentSampleRate{}; /*!< Current sample rate, system sample rate as currently configured. */ unsigned int preferredSampleRate{}; /*!< Preferred sample rate, e.g. for WASAPI the system sample rate. */ RtAudioFormat nativeFormats{}; /*!< Bit mask of supported data formats. */ }; //! The structure for specifying input or output stream parameters. struct StreamParameters { //std::string deviceName{}; /*!< Device name from device list. */ unsigned int deviceId{}; /*!< Device id as provided by getDeviceIds(). */ unsigned int nChannels{}; /*!< Number of channels. */ unsigned int firstChannel{}; /*!< First channel index on device (default = 0). */ }; //! The structure for specifying stream options. /*! The following flags can be OR'ed together to allow a client to make changes to the default stream behavior: - \e RTAUDIO_NONINTERLEAVED: Use non-interleaved buffers (default = interleaved). - \e RTAUDIO_MINIMIZE_LATENCY: Attempt to set stream parameters for lowest possible latency. - \e RTAUDIO_HOG_DEVICE: Attempt grab device for exclusive use. - \e RTAUDIO_SCHEDULE_REALTIME: Attempt to select realtime scheduling for callback thread. - \e RTAUDIO_ALSA_USE_DEFAULT: Use the "default" PCM device (ALSA only). By default, RtAudio streams pass and receive audio data from the client in an interleaved format. By passing the RTAUDIO_NONINTERLEAVED flag to the openStream() function, audio data will instead be presented in non-interleaved buffers. In this case, each buffer argument in the RtAudioCallback function will point to a single array of data, with \c nFrames samples for each channel concatenated back-to-back. For example, the first sample of data for the second channel would be located at index \c nFrames (assuming the \c buffer pointer was recast to the correct data type for the stream). Certain audio APIs offer a number of parameters that influence the I/O latency of a stream. By default, RtAudio will attempt to set these parameters internally for robust (glitch-free) performance (though some APIs, like Windows DirectSound, make this difficult). By passing the RTAUDIO_MINIMIZE_LATENCY flag to the openStream() function, internal stream settings will be influenced in an attempt to minimize stream latency, though possibly at the expense of stream performance. If the RTAUDIO_HOG_DEVICE flag is set, RtAudio will attempt to open the input and/or output stream device(s) for exclusive use. Note that this is not possible with all supported audio APIs. If the RTAUDIO_SCHEDULE_REALTIME flag is set, RtAudio will attempt to select realtime scheduling (round-robin) for the callback thread. The \c priority parameter will only be used if the RTAUDIO_SCHEDULE_REALTIME flag is set. It defines the thread's realtime priority. If the RTAUDIO_ALSA_USE_DEFAULT flag is set, RtAudio will attempt to open the "default" PCM device when using the ALSA API. Note that this will override any specified input or output device id. The \c numberOfBuffers parameter can be used to control stream latency in the Windows DirectSound, Linux OSS, and Linux Alsa APIs only. A value of two is usually the smallest allowed. Larger numbers can potentially result in more robust stream performance, though likely at the cost of stream latency. The value set by the user is replaced during execution of the RtAudio::openStream() function by the value actually used by the system. The \c streamName parameter can be used to set the client name when using the Jack API or the application name when using the Pulse API. By default, the Jack client name is set to RtApiJack. However, if you wish to create multiple instances of RtAudio with Jack, each instance must have a unique client name. The default Pulse application name is set to "RtAudio." */ struct StreamOptions { RtAudioStreamFlags flags{}; /*!< A bit-mask of stream flags (RTAUDIO_NONINTERLEAVED, RTAUDIO_MINIMIZE_LATENCY, RTAUDIO_HOG_DEVICE, RTAUDIO_ALSA_USE_DEFAULT). */ unsigned int numberOfBuffers{}; /*!< Number of stream buffers. */ std::string streamName; /*!< A stream name (currently used only in Jack). */ int priority{}; /*!< Scheduling priority of callback thread (only used with flag RTAUDIO_SCHEDULE_REALTIME). */ }; //! A static function to determine the current RtAudio version. static std::string getVersion( void ); //! A static function to determine the available compiled audio APIs. /*! The values returned in the std::vector can be compared against the enumerated list values. Note that there can be more than one API compiled for certain operating systems. */ static void getCompiledApi( std::vector &apis ); //! Return the name of a specified compiled audio API. /*! This obtains a short lower-case name used for identification purposes. This value is guaranteed to remain identical across library versions. If the API is unknown, this function will return the empty string. */ static std::string getApiName( RtAudio::Api api ); //! Return the display name of a specified compiled audio API. /*! This obtains a long name used for display purposes. If the API is unknown, this function will return the empty string. */ static std::string getApiDisplayName( RtAudio::Api api ); //! Return the compiled audio API having the given name. /*! A case insensitive comparison will check the specified name against the list of compiled APIs, and return the one that matches. On failure, the function returns UNSPECIFIED. */ static RtAudio::Api getCompiledApiByName( const std::string &name ); //! Return the compiled audio API having the given display name. /*! A case sensitive comparison will check the specified display name against the list of compiled APIs, and return the one that matches. On failure, the function returns UNSPECIFIED. */ static RtAudio::Api getCompiledApiByDisplayName( const std::string &name ); //! The class constructor. /*! The constructor attempts to create an RtApi instance. If an API argument is specified but that API has not been compiled, a warning is issued and an instance of an available API is created. If no compiled API is found, the routine will abort (though this should be impossible because RtDummy is the default if no API-specific preprocessor definition is provided to the compiler). If no API argument is specified and multiple API support has been compiled, the default order of use is JACK, ALSA, OSS (Linux systems) and ASIO, DS (Windows systems). An optional errorCallback function can be specified to subsequently receive warning and error messages. */ RtAudio( RtAudio::Api api=UNSPECIFIED, RtAudioErrorCallback&& errorCallback=0 ); //! The destructor. /*! If a stream is running or open, it will be stopped and closed automatically. */ ~RtAudio(); //! Returns the audio API specifier for the current instance of RtAudio. RtAudio::Api getCurrentApi( void ); //! A public function that queries for the number of audio devices available. /*! This function performs a system query of available devices each time it is called, thus supporting devices (dis)connected \e after instantiation. If a system error occurs during processing, a warning will be issued. */ unsigned int getDeviceCount( void ); //! A public function that returns a vector of audio device IDs. /*! The ID values returned by this function are used internally by RtAudio to identify a given device. The values themselves are arbitrary and do not correspond to device IDs used by the underlying API (nor are they index values). This function performs a system query of available devices each time it is called, thus supporting devices (dis)connected \e after instantiation. If no devices are available, the vector size will be zero. If a system error occurs during processing, a warning will be issued. */ std::vector getDeviceIds( void ); //! A public function that returns a vector of audio device names. /*! This function performs a system query of available devices each time it is called, thus supporting devices (dis)connected \e after instantiation. If no devices are available, the vector size will be zero. If a system error occurs during processing, a warning will be issued. */ std::vector getDeviceNames( void ); //! Return an RtAudio::DeviceInfo structure for a specified device ID. /*! Any device ID returned by getDeviceIds() is valid, unless it has been removed between the call to getDevceIds() and this function. If an invalid argument is provided, an RTAUDIO_INVALID_USE will be passed to the user-provided errorCallback function (or otherwise printed to stderr) and all members of the returned RtAudio::DeviceInfo structure will be initialized to default, invalid values (ID = 0, empty name, ...). If the specified device is the current default input or output device, the corresponding "isDefault" member will have a value of "true". */ RtAudio::DeviceInfo getDeviceInfo( unsigned int deviceId ); //! A function that returns the ID of the default output device. /*! If the underlying audio API does not provide a "default device", the first probed output device ID will be returned. If no devices are available, the return value will be 0 (which is an invalid device identifier). */ unsigned int getDefaultOutputDevice( void ); //! A function that returns the ID of the default input device. /*! If the underlying audio API does not provide a "default device", the first probed input device ID will be returned. If no devices are available, the return value will be 0 (which is an invalid device identifier). */ unsigned int getDefaultInputDevice( void ); //! A public function for opening a stream with the specified parameters. /*! An RTAUDIO_SYSTEM_ERROR is returned if a stream cannot be opened with the specified parameters or an error occurs during processing. An RTAUDIO_INVALID_USE is returned if a stream is already open or any invalid stream parameters are specified. \param outputParameters Specifies output stream parameters to use when opening a stream, including a device ID, number of channels, and starting channel number. For input-only streams, this argument should be NULL. The device ID is a value returned by getDeviceIds(). \param inputParameters Specifies input stream parameters to use when opening a stream, including a device ID, number of channels, and starting channel number. For output-only streams, this argument should be NULL. The device ID is a value returned by getDeviceIds(). \param format An RtAudioFormat specifying the desired sample data format. \param sampleRate The desired sample rate (sample frames per second). \param bufferFrames A pointer to a value indicating the desired internal buffer size in sample frames. The actual value used by the device is returned via the same pointer. A value of zero can be specified, in which case the lowest allowable value is determined. \param callback A client-defined function that will be invoked when input data is available and/or output data is needed. \param userData An optional pointer to data that can be accessed from within the callback function. \param options An optional pointer to a structure containing various global stream options, including a list of OR'ed RtAudioStreamFlags and a suggested number of stream buffers that can be used to control stream latency. More buffers typically result in more robust performance, though at a cost of greater latency. If a value of zero is specified, a system-specific median value is chosen. If the RTAUDIO_MINIMIZE_LATENCY flag bit is set, the lowest allowable value is used. The actual value used is returned via the structure argument. The parameter is API dependent. */ RtAudioErrorType openStream( RtAudio::StreamParameters *outputParameters, RtAudio::StreamParameters *inputParameters, RtAudioFormat format, unsigned int sampleRate, unsigned int *bufferFrames, RtAudioCallback callback, void *userData = NULL, RtAudio::StreamOptions *options = NULL ); //! A function that closes a stream and frees any associated stream memory. /*! If a stream is not open, an RTAUDIO_WARNING will be passed to the user-provided errorCallback function (or otherwise printed to stderr). */ void closeStream( void ); //! A function that starts a stream. /*! An RTAUDIO_SYSTEM_ERROR is returned if an error occurs during processing. An RTAUDIO_WARNING is returned if a stream is not open or is already running. */ RtAudioErrorType startStream( void ); //! Stop a stream, allowing any samples remaining in the output queue to be played. /*! An RTAUDIO_SYSTEM_ERROR is returned if an error occurs during processing. An RTAUDIO_WARNING is returned if a stream is not open or is already stopped. */ RtAudioErrorType stopStream( void ); //! Stop a stream, discarding any samples remaining in the input/output queue. /*! An RTAUDIO_SYSTEM_ERROR is returned if an error occurs during processing. An RTAUDIO_WARNING is returned if a stream is not open or is already stopped. */ RtAudioErrorType abortStream( void ); //! Retrieve the error message corresponding to the last error or warning condition. /*! This function can be used to get a detailed error message when a non-zero RtAudioErrorType is returned by a function. This is the same message sent to the user-provided errorCallback function. */ const std::string getErrorText( void ); //! Returns true if a stream is open and false if not. bool isStreamOpen( void ) const; //! Returns true if the stream is running and false if it is stopped or not open. bool isStreamRunning( void ) const; //! Returns the number of seconds of processed data since the stream was started. /*! The stream time is calculated from the number of sample frames processed by the underlying audio system, which will increment by units of the audio buffer size. It is not an absolute running time. If a stream is not open, the returned value may not be valid. */ double getStreamTime( void ); //! Set the stream time to a time in seconds greater than or equal to 0.0. void setStreamTime( double time ); //! Returns the internal stream latency in sample frames. /*! The stream latency refers to delay in audio input and/or output caused by internal buffering by the audio system and/or hardware. For duplex streams, the returned value will represent the sum of the input and output latencies. If a stream is not open, the returned value will be invalid. If the API does not report latency, the return value will be zero. */ long getStreamLatency( void ); //! Returns actual sample rate in use by the (open) stream. /*! On some systems, the sample rate used may be slightly different than that specified in the stream parameters. If a stream is not open, a value of zero is returned. */ unsigned int getStreamSampleRate( void ); //! Set a client-defined function that will be invoked when an error or warning occurs. void setErrorCallback( RtAudioErrorCallback errorCallback ); //! Specify whether warning messages should be output or not. /*! The default behaviour is for warning messages to be output, either to a client-defined error callback function (if specified) or to stderr. */ void showWarnings( bool value = true ); protected: void openRtApi( RtAudio::Api api ); RtApi *rtapi_; }; // Operating system dependent thread functionality. #if defined(_MSC_VER) #ifndef NOMINMAX #define NOMINMAX #endif #include #include #include typedef uintptr_t ThreadHandle; typedef CRITICAL_SECTION StreamMutex; #else // Using pthread library for various flavors of unix. #include typedef pthread_t ThreadHandle; typedef pthread_mutex_t StreamMutex; #endif // Setup for "dummy" behavior if no apis specified. #if !(defined(__WINDOWS_DS__) || defined(__WINDOWS_ASIO__) || defined(__WINDOWS_WASAPI__) \ || defined(__LINUX_ALSA__) || defined(__LINUX_PULSE__) || defined(__UNIX_JACK__) \ || defined(__LINUX_OSS__) || defined(__MACOSX_CORE__)) #define __RTAUDIO_DUMMY__ #endif // This global structure type is used to pass callback information // between the private RtAudio stream structure and global callback // handling functions. struct CallbackInfo { void *object{}; // Used as a "this" pointer. ThreadHandle thread{}; void *callback{}; void *userData{}; void *apiInfo{}; // void pointer for API specific callback information bool isRunning{false}; bool doRealtime{false}; int priority{}; bool deviceDisconnected{false}; }; // **************************************************************** // // // RtApi class declaration. // // Subclasses of RtApi contain all API- and OS-specific code necessary // to fully implement the RtAudio API. // // Note that RtApi is an abstract base class and cannot be // explicitly instantiated. The class RtAudio will create an // instance of an RtApi subclass (RtApiOss, RtApiAlsa, // RtApiJack, RtApiCore, RtApiDs, or RtApiAsio). // // **************************************************************** // #pragma pack(push, 1) class S24 { protected: unsigned char c3[3]; public: S24() {} S24& operator = ( const int& i ) { c3[0] = (unsigned char)(i & 0x000000ff); c3[1] = (unsigned char)((i & 0x0000ff00) >> 8); c3[2] = (unsigned char)((i & 0x00ff0000) >> 16); return *this; } S24( const double& d ) { *this = (int) d; } S24( const float& f ) { *this = (int) f; } S24( const signed short& s ) { *this = (int) s; } S24( const char& c ) { *this = (int) c; } int asInt() { int i = c3[0] | (c3[1] << 8) | (c3[2] << 16); if (i & 0x800000) i |= ~0xffffff; return i; } }; #pragma pack(pop) #if defined( HAVE_GETTIMEOFDAY ) #include #endif #include class RTAUDIO_DLL_PUBLIC RtApi { public: RtApi(); virtual ~RtApi(); virtual RtAudio::Api getCurrentApi( void ) = 0; unsigned int getDeviceCount( void ); std::vector getDeviceIds( void ); std::vector getDeviceNames( void ); RtAudio::DeviceInfo getDeviceInfo( unsigned int deviceId ); virtual unsigned int getDefaultInputDevice( void ); virtual unsigned int getDefaultOutputDevice( void ); RtAudioErrorType openStream( RtAudio::StreamParameters *outputParameters, RtAudio::StreamParameters *inputParameters, RtAudioFormat format, unsigned int sampleRate, unsigned int *bufferFrames, RtAudioCallback callback, void *userData, RtAudio::StreamOptions *options ); virtual void closeStream( void ); virtual RtAudioErrorType startStream( void ) = 0; virtual RtAudioErrorType stopStream( void ) = 0; virtual RtAudioErrorType abortStream( void ) = 0; const std::string getErrorText( void ) const { return errorText_; } long getStreamLatency( void ); unsigned int getStreamSampleRate( void ); virtual double getStreamTime( void ) const { return stream_.streamTime; } virtual void setStreamTime( double time ); bool isStreamOpen( void ) const { return stream_.state != STREAM_CLOSED; } bool isStreamRunning( void ) const { return stream_.state == STREAM_RUNNING; } void setErrorCallback( RtAudioErrorCallback errorCallback ) { errorCallback_ = errorCallback; } void showWarnings( bool value ) { showWarnings_ = value; } protected: static const unsigned int MAX_SAMPLE_RATES; static const unsigned int SAMPLE_RATES[]; enum { FAILURE, SUCCESS }; enum StreamState { STREAM_STOPPED, STREAM_STOPPING, STREAM_RUNNING, STREAM_CLOSED = -50 }; enum StreamMode { OUTPUT, INPUT, DUPLEX, UNINITIALIZED = -75 }; // A protected structure used for buffer conversion. struct ConvertInfo { int channels; int inJump, outJump; RtAudioFormat inFormat, outFormat; std::vector inOffset; std::vector outOffset; }; // A protected structure for audio streams. struct RtApiStream { unsigned int deviceId[2]; // Playback and record, respectively. void *apiHandle; // void pointer for API specific stream handle information StreamMode mode; // OUTPUT, INPUT, or DUPLEX. StreamState state; // STOPPED, RUNNING, or CLOSED char *userBuffer[2]; // Playback and record, respectively. char *deviceBuffer; bool doConvertBuffer[2]; // Playback and record, respectively. bool userInterleaved; bool deviceInterleaved[2]; // Playback and record, respectively. bool doByteSwap[2]; // Playback and record, respectively. unsigned int sampleRate; unsigned int bufferSize; unsigned int nBuffers; unsigned int nUserChannels[2]; // Playback and record, respectively. unsigned int nDeviceChannels[2]; // Playback and record channels, respectively. unsigned int channelOffset[2]; // Playback and record, respectively. unsigned long latency[2]; // Playback and record, respectively. RtAudioFormat userFormat; RtAudioFormat deviceFormat[2]; // Playback and record, respectively. StreamMutex mutex; CallbackInfo callbackInfo; ConvertInfo convertInfo[2]; double streamTime; // Number of elapsed seconds since the stream started. #if defined(HAVE_GETTIMEOFDAY) struct timeval lastTickTimestamp; #endif RtApiStream() :apiHandle(0), deviceBuffer(0) {} // { device[0] = std::string(); device[1] = std::string(); } }; typedef S24 Int24; typedef signed short Int16; typedef signed int Int32; typedef float Float32; typedef double Float64; std::ostringstream errorStream_; std::string errorText_; RtAudioErrorCallback errorCallback_; bool showWarnings_; std::vector deviceList_; unsigned int currentDeviceId_; RtApiStream stream_; /*! Protected, api-specific method that attempts to probe all device capabilities in a system. The function will not re-probe devices that were previously found and probed. This function MUST be implemented by all subclasses. If an error is encountered during the probe, a "warning" message may be reported and the internal list of devices may be incomplete. */ virtual void probeDevices( void ); /*! Protected, api-specific method that attempts to open a device with the given parameters. This function MUST be implemented by all subclasses. If an error is encountered during the probe, a "warning" message is reported and FAILURE is returned. A successful probe is indicated by a return value of SUCCESS. */ virtual bool probeDeviceOpen( unsigned int deviceId, StreamMode mode, unsigned int channels, unsigned int firstChannel, unsigned int sampleRate, RtAudioFormat format, unsigned int *bufferSize, RtAudio::StreamOptions *options ); //! A protected function used to increment the stream time. void tickStreamTime( void ); //! Protected common method to clear an RtApiStream structure. void clearStreamInfo(); //! Protected common error method to allow global control over error handling. RtAudioErrorType error( RtAudioErrorType type ); /*! Protected method used to perform format, channel number, and/or interleaving conversions between the user and device buffers. */ void convertBuffer( char *outBuffer, char *inBuffer, ConvertInfo &info ); //! Protected common method used to perform byte-swapping on buffers. void byteSwapBuffer( char *buffer, unsigned int samples, RtAudioFormat format ); //! Protected common method that returns the number of bytes for a given format. unsigned int formatBytes( RtAudioFormat format ); //! Protected common method that sets up the parameters for buffer conversion. void setConvertInfo( StreamMode mode, unsigned int firstChannel ); }; // **************************************************************** // // // Inline RtAudio definitions. // // **************************************************************** // inline RtAudio::Api RtAudio :: getCurrentApi( void ) { return rtapi_->getCurrentApi(); } inline unsigned int RtAudio :: getDeviceCount( void ) { return rtapi_->getDeviceCount(); } inline RtAudio::DeviceInfo RtAudio :: getDeviceInfo( unsigned int deviceId ) { return rtapi_->getDeviceInfo( deviceId ); } inline std::vector RtAudio :: getDeviceIds( void ) { return rtapi_->getDeviceIds(); } inline std::vector RtAudio :: getDeviceNames( void ) { return rtapi_->getDeviceNames(); } inline unsigned int RtAudio :: getDefaultInputDevice( void ) { return rtapi_->getDefaultInputDevice(); } inline unsigned int RtAudio :: getDefaultOutputDevice( void ) { return rtapi_->getDefaultOutputDevice(); } inline void RtAudio :: closeStream( void ) { return rtapi_->closeStream(); } inline RtAudioErrorType RtAudio :: startStream( void ) { return rtapi_->startStream(); } inline RtAudioErrorType RtAudio :: stopStream( void ) { return rtapi_->stopStream(); } inline RtAudioErrorType RtAudio :: abortStream( void ) { return rtapi_->abortStream(); } inline const std::string RtAudio :: getErrorText( void ) { return rtapi_->getErrorText(); } inline bool RtAudio :: isStreamOpen( void ) const { return rtapi_->isStreamOpen(); } inline bool RtAudio :: isStreamRunning( void ) const { return rtapi_->isStreamRunning(); } inline long RtAudio :: getStreamLatency( void ) { return rtapi_->getStreamLatency(); } inline unsigned int RtAudio :: getStreamSampleRate( void ) { return rtapi_->getStreamSampleRate(); } inline double RtAudio :: getStreamTime( void ) { return rtapi_->getStreamTime(); } inline void RtAudio :: setStreamTime( double time ) { return rtapi_->setStreamTime( time ); } inline void RtAudio :: setErrorCallback( RtAudioErrorCallback errorCallback ) { rtapi_->setErrorCallback( errorCallback ); } inline void RtAudio :: showWarnings( bool value ) { rtapi_->showWarnings( value ); } #endif // Indentation settings for Vim and Emacs // // Local Variables: // c-basic-offset: 2 // indent-tabs-mode: nil // End: // // vim: et sts=2 sw=2 mlt-7.22.0/src/modules/rtaudio/consumer_rtaudio.cpp000664 000000 000000 00000074555 14531534050 022372 0ustar00rootroot000000 000000 /* * consumer_rtaudio.c -- output through RtAudio audio wrapper * Copyright (C) 2011-2023 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with consumer library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #ifdef USE_INTERNAL_RTAUDIO #include "RtAudio.h" #else #include #endif #if defined(RTAUDIO_VERSION_MAJOR) && RTAUDIO_VERSION_MAJOR >= 6 #define RTAUDIO_VERSION_6 #endif static void consumer_refresh_cb(mlt_consumer sdl, mlt_consumer consumer, mlt_event_data); static int rtaudio_callback(void *outputBuffer, void *inputBuffer, unsigned int nFrames, double streamTime, RtAudioStreamStatus status, void *userData); static void *consumer_thread_proxy(void *arg); static void *video_thread_proxy(void *arg); static const char *rtaudio_api_str(RtAudio::Api api) { switch (api) { case RtAudio::UNSPECIFIED: return "UNSPECIFIED"; case RtAudio::LINUX_ALSA: return "LINUX_ALSA"; case RtAudio::LINUX_PULSE: return "LINUX_PULSE"; case RtAudio::LINUX_OSS: return "LINUX_OSS"; case RtAudio::UNIX_JACK: return "UNIX_JACK"; case RtAudio::MACOSX_CORE: return "MACOSX_CORE"; case RtAudio::WINDOWS_WASAPI: return "WINDOWS_WASAPI"; case RtAudio::WINDOWS_ASIO: return "WINDOWS_ASIO"; case RtAudio::WINDOWS_DS: return "WINDOWS_DS"; case RtAudio::RTAUDIO_DUMMY: return "RTAUDIO_DUMMY"; default: break; } return "UNKNOWN!?!"; } class RtAudioConsumer { public: struct mlt_consumer_s consumer; RtAudio *rt; int device_id; mlt_deque queue; pthread_t thread; int joined; int running; int out_channels; uint8_t audio_buffer[4096 * 10]; int audio_avail; pthread_mutex_t audio_mutex; pthread_cond_t audio_cond; pthread_mutex_t video_mutex; pthread_cond_t video_cond; int playing; pthread_cond_t refresh_cond; pthread_mutex_t refresh_mutex; int refresh_count; bool is_purge; mlt_consumer getConsumer() { return &consumer; } RtAudioConsumer() : rt(nullptr) , device_id(-1) , queue(nullptr) , joined(0) , running(0) , audio_avail(0) , playing(0) , refresh_count(0) , is_purge(false) { memset(&consumer, 0, sizeof(consumer)); } ~RtAudioConsumer() { // Close the queue mlt_deque_close(queue); // Destroy mutexes pthread_mutex_destroy(&audio_mutex); pthread_cond_destroy(&audio_cond); pthread_mutex_destroy(&video_mutex); pthread_cond_destroy(&video_cond); pthread_mutex_destroy(&refresh_mutex); pthread_cond_destroy(&refresh_cond); if (rt && rt->isStreamOpen()) rt->closeStream(); delete rt; rt = nullptr; } bool create_rtaudio(RtAudio::Api api, int channels, int frequency) { mlt_properties properties = MLT_CONSUMER_PROPERTIES(getConsumer()); const char *resource = mlt_properties_get(properties, "resource"); unsigned int bufferFrames = mlt_properties_get_int(properties, "audio_buffer"); mlt_log_info(getConsumer(), "Attempt to open RtAudio: %s\t%d\t%d\n", rtaudio_api_str(api), channels, frequency); rt = new RtAudio(api); if (!rt) { return false; } if (rt->getDeviceCount() < 1) { mlt_log_warning(getConsumer(), "no audio devices found\n"); delete rt; rt = nullptr; return false; } #if defined(RTAUDIO_VERSION_6) || !defined(__LINUX_ALSA__) device_id = rt->getDefaultOutputDevice(); #endif if (resource && strcmp(resource, "") && strcmp(resource, "default")) { // Get device ID by name RtAudio::DeviceInfo info; unsigned int i; #ifdef RTAUDIO_VERSION_6 auto ids = rt->getDeviceIds(); for (i = 0; i < ids.size(); i++) { info = rt->getDeviceInfo(ids[i]); mlt_log_verbose(nullptr, "RtAudio device %u = %s\n", ids[i], info.name.c_str()); if (info.name.find(resource) != std::string::npos) { device_id = info.ID; break; } } if (i == ids.size()) #else unsigned int n = rt->getDeviceCount(); for (i = 0; i < n; i++) { info = rt->getDeviceInfo(i); mlt_log_verbose(nullptr, "RtAudio device %d = %s\n", i, info.name.c_str()); if (info.probed && info.name == resource) { device_id = i; break; } } // Name selection failed, try arg as numeric if (i == n) #endif device_id = (int) strtol(resource, nullptr, 0); } RtAudio::StreamParameters parameters; parameters.deviceId = device_id; parameters.nChannels = channels; parameters.firstChannel = 0; RtAudio::StreamOptions options; if (device_id == -1) { options.flags = RTAUDIO_ALSA_USE_DEFAULT; parameters.deviceId = 0; } if (resource) { #ifdef RTAUDIO_VERSION_6 auto ids = rt->getDeviceIds(); for (unsigned i = 0; i < ids.size(); i++) { auto info = rt->getDeviceInfo(ids[i]); if (info.name == resource) { device_id = parameters.deviceId = info.ID; break; } } #else unsigned n = rt->getDeviceCount(); for (unsigned i = 0; i < n; i++) { RtAudio::DeviceInfo info = rt->getDeviceInfo(i); if (info.name == resource) { device_id = parameters.deviceId = i; break; } } #endif } #ifdef RTAUDIO_VERSION_6 if (rt->isStreamOpen()) { rt->closeStream(); } if (rt->openStream(¶meters, nullptr, RTAUDIO_SINT16, frequency, &bufferFrames, &rtaudio_callback, this, &options) || rt->startStream()) { mlt_log_info(getConsumer(), "%s\n", rt->getErrorText().c_str()); delete rt; rt = nullptr; return false; } #else try { if (rt->isStreamOpen()) { rt->closeStream(); } rt->openStream(¶meters, nullptr, RTAUDIO_SINT16, frequency, &bufferFrames, &rtaudio_callback, this, &options); rt->startStream(); } #ifdef RTERROR_H catch (RtError &e) { #else catch (RtAudioError &e) { #endif mlt_log_info(getConsumer(), "%s\n", e.getMessage().c_str()); delete rt; rt = nullptr; return false; } #endif mlt_log_info(getConsumer(), "Opened RtAudio: %s\t%d\t%d\n", rtaudio_api_str(rt->getCurrentApi()), channels, frequency); return true; } bool find_and_create_rtaudio(int requested_channels, int frequency, int *actual_channels) { bool result = false; #ifdef __WINDOWS_DS__ // Prefer DirectSound on Windows RtAudio::Api PREFERRED_API = RtAudio::WINDOWS_DS; #else RtAudio::Api PREFERRED_API = RtAudio::UNSPECIFIED; #endif *actual_channels = requested_channels; // First try with preferred API. result = create_rtaudio(PREFERRED_API, *actual_channels, frequency); if (!result) { // If the preferred API fails, try other APIs that are available. std::vector apis; RtAudio::getCompiledApi(apis); for (size_t i = 0; i < apis.size(); i++) { if (apis[i] == PREFERRED_API || apis[i] == RtAudio::RTAUDIO_DUMMY) { continue; } result = create_rtaudio(apis[i], *actual_channels, frequency); if (result) { break; } } } if (!result && *actual_channels != 2) { // If surround has failed for all APIs, try stereo. *actual_channels = 2; mlt_log_info(getConsumer(), "Unable to open %d channels. Try %d channels\n", requested_channels, *actual_channels); std::vector apis; RtAudio::getCompiledApi(apis); for (size_t i = 0; i < apis.size(); i++) { if (apis[i] == RtAudio::RTAUDIO_DUMMY) { continue; } result = create_rtaudio(apis[i], *actual_channels, frequency); if (result) { break; } } } return result; } bool open(const char *arg) { // Create the queue queue = mlt_deque_init(); // get a handle on properties mlt_properties properties = MLT_CONSUMER_PROPERTIES(&consumer); // Set the default volume mlt_properties_set_double(properties, "volume", 1.0); // This is the initialisation of the consumer pthread_mutex_init(&audio_mutex, nullptr); pthread_cond_init(&audio_cond, nullptr); pthread_mutex_init(&video_mutex, nullptr); pthread_cond_init(&video_cond, nullptr); // Default scaler (for now we'll use nearest) mlt_properties_set(properties, "rescale", "nearest"); mlt_properties_set(properties, "consumer.deinterlacer", "onefield"); // Default buffer for low latency mlt_properties_set_int(properties, "buffer", 1); // Default audio buffer mlt_properties_set_int(properties, "audio_buffer", 1024); // Set the resource to the device name arg mlt_properties_set(properties, "resource", arg); // Ensure we don't join on a non-running object joined = 1; // Initialize the refresh handler pthread_cond_init(&refresh_cond, nullptr); pthread_mutex_init(&refresh_mutex, nullptr); mlt_events_listen(properties, this, "property-changed", (mlt_listener) consumer_refresh_cb); return true; } int start() { if (!running) { stop(); running = 1; joined = 0; pthread_create(&thread, nullptr, consumer_thread_proxy, this); } return 0; } int stop() { if (running && !joined) { // Kill the thread and clean up joined = 1; running = 0; // Unlatch the consumer thread pthread_mutex_lock(&refresh_mutex); pthread_cond_broadcast(&refresh_cond); pthread_mutex_unlock(&refresh_mutex); // Cleanup the main thread pthread_join(thread, nullptr); // Unlatch the video thread pthread_mutex_lock(&video_mutex); pthread_cond_broadcast(&video_cond); pthread_mutex_unlock(&video_mutex); // Unlatch the audio callback pthread_mutex_lock(&audio_mutex); pthread_cond_broadcast(&audio_cond); pthread_mutex_unlock(&audio_mutex); if (rt && rt->isStreamOpen()) #ifdef RTAUDIO_VERSION_6 if (rt->stopStream()) { mlt_log_error(getConsumer(), "%s\n", rt->getErrorText().c_str()); } #else try { // Stop the stream rt->stopStream(); } #ifdef RTERROR_H catch (RtError &e) { #else catch (RtAudioError &e) { #endif mlt_log_error(getConsumer(), "%s\n", e.getMessage().c_str()); } #endif delete rt; rt = nullptr; } return 0; } void purge() { if (running) { pthread_mutex_lock(&video_mutex); mlt_frame frame = MLT_FRAME(mlt_deque_peek_back(queue)); // When playing rewind or fast forward then we need to keep one // frame in the queue to prevent playback stalling. double speed = frame ? mlt_properties_get_double(MLT_FRAME_PROPERTIES(frame), "_speed") : 0; int n = (speed == 0.0 || speed == 1.0) ? 0 : 1; while (mlt_deque_count(queue) > n) mlt_frame_close(MLT_FRAME(mlt_deque_pop_back(queue))); is_purge = true; pthread_cond_broadcast(&video_cond); pthread_mutex_unlock(&video_mutex); } } void consumer_thread() { // Get the properties mlt_properties consumer_props = MLT_CONSUMER_PROPERTIES(getConsumer()); // Video thread pthread_t thread; // internal initialization int init_audio = 1; int init_video = 1; mlt_frame frame = nullptr; mlt_properties properties = nullptr; int64_t duration = 0; int64_t playtime = mlt_properties_get_int(consumer_props, "video_delay") * 1000; struct timespec tm = {0, 100000}; // int last_position = -1; pthread_mutex_lock(&refresh_mutex); refresh_count = 0; pthread_mutex_unlock(&refresh_mutex); // Loop until told not to while (running) { // Get a frame from the attached producer frame = mlt_consumer_rt_frame(getConsumer()); // Ensure that we have a frame if (frame) { // Get the frame properties properties = MLT_FRAME_PROPERTIES(frame); // Get the speed of the frame double speed = mlt_properties_get_double(properties, "_speed"); // Get refresh request for the current frame int refresh = mlt_properties_get_int(consumer_props, "refresh"); // Clear refresh mlt_events_block(consumer_props, consumer_props); mlt_properties_set_int(consumer_props, "refresh", 0); mlt_events_unblock(consumer_props, consumer_props); // Play audio init_audio = play_audio(frame, init_audio, &duration); // Determine the start time now if (playing && init_video) { // Create the video thread pthread_create(&thread, nullptr, video_thread_proxy, this); // Video doesn't need to be initialised any more init_video = 0; } // Set playtime for this frame in microseconds mlt_properties_set_int64(properties, "playtime", playtime); while (running && speed != 0 && mlt_deque_count(queue) > 15) nanosleep(&tm, nullptr); // Push this frame to the back of the video queue if (running && speed) { pthread_mutex_lock(&video_mutex); if (is_purge && speed == 1.0) { mlt_frame_close(frame); is_purge = false; } else { mlt_deque_push_back(queue, frame); pthread_cond_broadcast(&video_cond); } pthread_mutex_unlock(&video_mutex); // Calculate the next playtime playtime += duration; } else if (running) { pthread_mutex_lock(&refresh_mutex); if (refresh == 0 && refresh_count <= 0) { play_video(frame); pthread_cond_wait(&refresh_cond, &refresh_mutex); } mlt_frame_close(frame); refresh_count--; pthread_mutex_unlock(&refresh_mutex); } else { mlt_frame_close(frame); frame = nullptr; } // Optimisation to reduce latency if (frame && speed == 1.0) { // TODO: disabled due to misbehavior on parallel-consumer // if ( last_position != -1 && last_position + 1 != mlt_frame_get_position( frame ) ) // mlt_consumer_purge( consumer ); // last_position = mlt_frame_get_position( frame ); } else if (speed == 0.0) { mlt_consumer_purge(getConsumer()); // last_position = -1; } } } // Kill the video thread if (init_video == 0) { pthread_mutex_lock(&video_mutex); pthread_cond_broadcast(&video_cond); pthread_mutex_unlock(&video_mutex); pthread_join(thread, nullptr); } while (mlt_deque_count(queue)) mlt_frame_close((mlt_frame) mlt_deque_pop_back(queue)); audio_avail = 0; } int callback(int16_t *outbuf, int16_t *inbuf, unsigned int samples, double streamTime, RtAudioStreamStatus status) { mlt_properties properties = MLT_CONSUMER_PROPERTIES(getConsumer()); double volume = mlt_properties_get_double(properties, "volume"); int len = mlt_audio_format_size(mlt_audio_s16, samples, out_channels); pthread_mutex_lock(&audio_mutex); // Block until audio received while (running && len > audio_avail) pthread_cond_wait(&audio_cond, &audio_mutex); if (audio_avail >= len) { // Place in the audio buffer memcpy(outbuf, audio_buffer, len); // Remove len from the audio available audio_avail -= len; // Remove the samples memmove(audio_buffer, audio_buffer + len, audio_avail); } else { // Just to be safe, wipe the stream first memset(outbuf, 0, len); // Copy what we have memcpy(outbuf, audio_buffer, audio_avail); // No audio left audio_avail = 0; } if (volume != 1.0) { int16_t *p = outbuf; int i = samples * out_channels + 1; while (--i) *p++ *= volume; } // We're definitely playing now playing = 1; pthread_cond_broadcast(&audio_cond); pthread_mutex_unlock(&audio_mutex); return 0; } int play_audio(mlt_frame frame, int init_audio, int64_t *duration) { // Get the properties of this consumer mlt_properties properties = MLT_CONSUMER_PROPERTIES(getConsumer()); mlt_audio_format afmt = mlt_audio_s16; // Set the preferred params of the test card signal int channels = mlt_properties_get_int(properties, "channels"); int frequency = mlt_properties_get_int(properties, "frequency"); int scrub = mlt_properties_get_int(properties, "scrub_audio"); static int counter = 0; int samples = mlt_audio_calculate_frame_samples(mlt_properties_get_double(properties, "fps"), frequency, counter++); int16_t *pcm; mlt_frame_get_audio(frame, (void **) &pcm, &afmt, &frequency, &channels, &samples); *duration = 1000000LL * samples / frequency; if (mlt_properties_get_int(properties, "audio_off")) { playing = 1; return init_audio; } if (init_audio == 1) { if (find_and_create_rtaudio(channels, frequency, &out_channels)) { init_audio = 0; playing = 1; } else { rt = nullptr; mlt_log_error(getConsumer(), "Unable to initialize RtAudio\n"); init_audio = 2; } } if (init_audio == 0) { mlt_properties properties = MLT_FRAME_PROPERTIES(frame); int samples_copied = 0; int dst_stride = out_channels * sizeof(*pcm); pthread_mutex_lock(&audio_mutex); while (running && samples_copied < samples) { int sample_space = (sizeof(audio_buffer) - audio_avail) / dst_stride; while (running && sample_space == 0) { pthread_cond_wait(&audio_cond, &audio_mutex); sample_space = (sizeof(audio_buffer) - audio_avail) / dst_stride; } if (running) { int samples_to_copy = samples - samples_copied; if (samples_to_copy > sample_space) { samples_to_copy = sample_space; } int dst_bytes = samples_to_copy * dst_stride; if (scrub || mlt_properties_get_double(properties, "_speed") == 1) { if (channels == out_channels) { memcpy(&audio_buffer[audio_avail], pcm, dst_bytes); pcm += samples_to_copy * channels; } else { int16_t *dest = (int16_t *) &audio_buffer[audio_avail]; int i = samples_to_copy + 1; while (--i) { memcpy(dest, pcm, dst_stride); pcm += channels; dest += out_channels; } } } else { memset(&audio_buffer[audio_avail], 0, dst_bytes); pcm += samples_to_copy * channels; } audio_avail += dst_bytes; samples_copied += samples_to_copy; } pthread_cond_broadcast(&audio_cond); } pthread_mutex_unlock(&audio_mutex); } return init_audio; } int play_video(mlt_frame frame) { // Get the properties of this consumer mlt_properties properties = MLT_CONSUMER_PROPERTIES(getConsumer()); if (running && !mlt_consumer_is_stopped(getConsumer())) { mlt_events_fire(properties, "consumer-frame-show", mlt_event_data_from_frame(frame)); } return 0; } void video_thread() { // Obtain time of thread start struct timeval now; int64_t start = 0; int64_t elapsed = 0; struct timespec tm; mlt_frame next = nullptr; mlt_properties consumerProperties = MLT_CONSUMER_PROPERTIES(getConsumer()); double speed = 0; // Get real time flag int real_time = mlt_properties_get_int(consumerProperties, "real_time"); // Get the current time gettimeofday(&now, nullptr); // Determine start time start = (int64_t) now.tv_sec * 1000000 + now.tv_usec; while (running) { // Pop the next frame pthread_mutex_lock(&video_mutex); next = (mlt_frame) mlt_deque_pop_front(queue); while (next == nullptr && running) { pthread_cond_wait(&video_cond, &video_mutex); next = (mlt_frame) mlt_deque_pop_front(queue); } pthread_mutex_unlock(&video_mutex); if (!running || next == nullptr) break; // Get the properties mlt_properties properties = MLT_FRAME_PROPERTIES(next); // Get the speed of the frame speed = mlt_properties_get_double(properties, "_speed"); // Get the current time gettimeofday(&now, nullptr); // Get the elapsed time elapsed = ((int64_t) now.tv_sec * 1000000 + now.tv_usec) - start; // See if we have to delay the display of the current frame if (mlt_properties_get_int(properties, "rendered") == 1 && running) { // Obtain the scheduled playout time in microseconds int64_t scheduled = mlt_properties_get_int64(properties, "playtime"); // Determine the difference between the elapsed time and the scheduled playout time int64_t difference = scheduled - elapsed; // Smooth playback a bit if (real_time && (difference > 20000 && speed == 1.0)) { tm.tv_sec = difference / 1000000; tm.tv_nsec = (difference % 1000000) * 1000; nanosleep(&tm, nullptr); } // Show current frame if not too old if (!real_time || (difference > -10000 || speed != 1.0 || mlt_deque_count(queue) < 2)) play_video(next); // If the queue is empty, recalculate start to allow build up again if (real_time && (mlt_deque_count(queue) == 0 && speed == 1.0)) { gettimeofday(&now, nullptr); start = ((int64_t) now.tv_sec * 1000000 + now.tv_usec) - scheduled + 20000; start += mlt_properties_get_int(consumerProperties, "video_delay") * 1000; } } // This frame can now be closed mlt_frame_close(next); next = nullptr; } if (next != nullptr) mlt_frame_close(next); mlt_consumer_stopped(getConsumer()); } }; static void consumer_refresh_cb(mlt_consumer sdl, mlt_consumer consumer, mlt_event_data event_data) { const char *name = mlt_event_data_to_string(event_data); if (name && !strcmp(name, "refresh")) { RtAudioConsumer *rtaudio = (RtAudioConsumer *) consumer->child; pthread_mutex_lock(&rtaudio->refresh_mutex); rtaudio->refresh_count = rtaudio->refresh_count <= 0 ? 1 : rtaudio->refresh_count + 1; pthread_cond_broadcast(&rtaudio->refresh_cond); pthread_mutex_unlock(&rtaudio->refresh_mutex); } } static int rtaudio_callback(void *outputBuffer, void *inputBuffer, unsigned int nFrames, double streamTime, RtAudioStreamStatus status, void *userData) { RtAudioConsumer *rtaudio = (RtAudioConsumer *) userData; return rtaudio->callback((int16_t *) outputBuffer, (int16_t *) inputBuffer, nFrames, streamTime, status); } static void *consumer_thread_proxy(void *arg) { RtAudioConsumer *rtaudio = (RtAudioConsumer *) arg; rtaudio->consumer_thread(); return nullptr; } static void *video_thread_proxy(void *arg) { RtAudioConsumer *rtaudio = (RtAudioConsumer *) arg; rtaudio->video_thread(); return nullptr; } /** Start the consumer. */ static int start(mlt_consumer consumer) { RtAudioConsumer *rtaudio = (RtAudioConsumer *) consumer->child; return rtaudio->start(); } /** Stop the consumer. */ static int stop(mlt_consumer consumer) { RtAudioConsumer *rtaudio = (RtAudioConsumer *) consumer->child; return rtaudio->stop(); } /** Determine if the consumer is stopped. */ static int is_stopped(mlt_consumer consumer) { RtAudioConsumer *rtaudio = (RtAudioConsumer *) consumer->child; return !rtaudio->running; } static void purge(mlt_consumer consumer) { RtAudioConsumer *rtaudio = (RtAudioConsumer *) consumer->child; rtaudio->purge(); } /** Close the consumer. */ static void close(mlt_consumer consumer) { // Stop the consumer mlt_consumer_stop(consumer); // Close the parent consumer->close = nullptr; mlt_consumer_close(consumer); // Free the memory delete (RtAudioConsumer *) consumer->child; } extern "C" { mlt_consumer consumer_rtaudio_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { // Allocate the consumer RtAudioConsumer *rtaudio = new RtAudioConsumer(); mlt_consumer consumer = nullptr; // If allocated if (rtaudio && !mlt_consumer_init(rtaudio->getConsumer(), rtaudio, profile)) { // If initialises without error if (rtaudio->open(arg ? arg : getenv("AUDIODEV"))) { // Setup callbacks consumer = rtaudio->getConsumer(); consumer->close = close; consumer->start = start; consumer->stop = stop; consumer->is_stopped = is_stopped; consumer->purge = purge; } else { mlt_consumer_close(rtaudio->getConsumer()); delete rtaudio; } } // Return consumer return consumer; } static mlt_properties metadata(mlt_service_type type, const char *id, void *data) { char file[PATH_MAX]; const char *service_type = "consumer"; snprintf(file, PATH_MAX, "%s/rtaudio/%s_%s.yml", mlt_environment("MLT_DATA"), service_type, id); return mlt_properties_parse_yaml(file); } MLT_REPOSITORY { MLT_REGISTER(mlt_service_consumer_type, "rtaudio", consumer_rtaudio_init); MLT_REGISTER_METADATA(mlt_service_consumer_type, "rtaudio", metadata, nullptr); } } // extern C mlt-7.22.0/src/modules/rtaudio/consumer_rtaudio.yml000664 000000 000000 00000002466 14531534050 022401 0ustar00rootroot000000 000000 schema_version: 0.3 type: consumer identifier: rtaudio title: RtAudio description: > RtAudio provides native, realtime audio output across Linux, Macintosh OS X, Windows, and some BSD operating systems. url: http://www.music.mcgill.ca/~gary/rtaudio/ version: 2 copyright: Meltytech, LLC creator: Dan Dennedy creator: Gary P. Scavone license: LGPLv2.1 language: en tags: - Audio parameters: - identifier: resource title: Device description: An optional device name, number, or ID to use. type: string required: no argument: yes - identifier: audio_buffer title: Audio buffer type: integer minimum: 256 maximum: 8192 default: 1024 unit: samples - identifier: volume title: Volume type: float minimum: 0.0 default: 1.0 mutable: yes - identifier: refresh description: > Applications should set this to update the video frame when paused. type: boolean minimum: 0 maximum: 1 - identifier: scrub_audio title: Audio scrubbing type: boolean description: If enabled, sound is played even when the speed is not normal. mutable: yes minimum: 0 maximum: 1 default: 0 widget: checkbox - identifier: video_delay title: Video delay mutable: no type: integer unit: milliseconds default: 0 mlt-7.22.0/src/modules/rubberband/000775 000000 000000 00000000000 14531534050 016723 5ustar00rootroot000000 000000 mlt-7.22.0/src/modules/rubberband/CMakeLists.txt000664 000000 000000 00000001054 14531534050 021463 0ustar00rootroot000000 000000 add_library(mltrubberband MODULE factory.c filter_rbpitch.cpp) file(GLOB YML "*.yml") add_custom_target(Other_rubberband_Files SOURCES ${YML} ) target_compile_options(mltrubberband PRIVATE ${MLT_COMPILE_OPTIONS}) target_link_libraries(mltrubberband PRIVATE mlt PkgConfig::rubberband) set_target_properties(mltrubberband PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${MLT_MODULE_OUTPUT_DIRECTORY}") install(TARGETS mltrubberband LIBRARY DESTINATION ${MLT_INSTALL_MODULE_DIR}) install(FILES filter_rbpitch.yml DESTINATION ${MLT_INSTALL_DATA_DIR}/rubberband) mlt-7.22.0/src/modules/rubberband/factory.c000664 000000 000000 00000003011 14531534050 020531 0ustar00rootroot000000 000000 /* * factory.c -- the factory method interfaces * Copyright (C) 2020 Meltytech, LLC * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include extern mlt_filter filter_rbpitch_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); static mlt_properties metadata(mlt_service_type type, const char *id, void *data) { char file[PATH_MAX]; snprintf(file, PATH_MAX, "%s/rubberband/%s", mlt_environment("MLT_DATA"), (char *) data); return mlt_properties_parse_yaml(file); } MLT_REPOSITORY { MLT_REGISTER(mlt_service_filter_type, "rbpitch", filter_rbpitch_init); MLT_REGISTER_METADATA(mlt_service_filter_type, "rbpitch", metadata, "filter_rbpitch.yml"); } mlt-7.22.0/src/modules/rubberband/filter_rbpitch.cpp000664 000000 000000 00000027154 14531534050 022440 0ustar00rootroot000000 000000 /* * filter_rbpitch.c -- adjust audio pitch * Copyright (C) 2020 Meltytech, LLC * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include #include #include #include using namespace RubberBand; // Private Types typedef struct { RubberBandStretcher *s; int rubberband_frequency; uint64_t in_samples; uint64_t out_samples; } private_data; static const size_t MAX_CHANNELS = 10; static int rbpitch_get_audio(mlt_frame frame, void **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples) { mlt_filter filter = static_cast(mlt_frame_pop_audio(frame)); mlt_properties filter_properties = MLT_FILTER_PROPERTIES(filter); private_data *pdata = (private_data *) filter->child; if (*channels > (int) MAX_CHANNELS) { mlt_log_error(MLT_FILTER_SERVICE(filter), "Too many channels requested: %d > %d\n", *channels, (int) MAX_CHANNELS); return mlt_frame_get_audio(frame, buffer, format, frequency, channels, samples); } mlt_properties unique_properties = mlt_frame_get_unique_properties(frame, MLT_FILTER_SERVICE(filter)); if (!unique_properties) { mlt_log_error(MLT_FILTER_SERVICE(filter), "Missing unique_properites\n"); return mlt_frame_get_audio(frame, buffer, format, frequency, channels, samples); } // Get the producer's audio int requested_frequency = *frequency; int requested_samples = *samples; *format = mlt_audio_float; int error = mlt_frame_get_audio(frame, buffer, format, frequency, channels, samples); if (error) return error; // Make sure the audio is in the correct format // This is useful if the filter is encapsulated in a producer and does not // have a normalizing filter before it. if (*format != mlt_audio_float && frame->convert_audio != NULL) { frame->convert_audio(frame, buffer, format, mlt_audio_float); } // Sanity check parameters // rubberband library crashes have been seen with a very large scale factor // or very small sampling frequency. Very small scale factor and very high // sampling frequency can result in too much audio lag. // Disallow these extreme scenarios for now. Maybe it will be improved in // the future. double pitchscale = mlt_properties_get_double(unique_properties, "pitchscale"); pitchscale = CLAMP(pitchscale, 0.05, 50.0); double timeratio = 1.0; int stretch = mlt_properties_get_int(unique_properties, "stretch"); int rubberband_frequency = *frequency; if (stretch) { rubberband_frequency = requested_frequency; timeratio = (double) requested_samples / (double) *samples; } rubberband_frequency = CLAMP(rubberband_frequency, 10000, 300000); // Protect the RubberBandStretcher instance. mlt_service_lock(MLT_FILTER_SERVICE(filter)); // Configure the stretcher. RubberBandStretcher *s = pdata->s; if (!s || s->available() == -1 || (int) s->getChannelCount() != *channels || pdata->rubberband_frequency != rubberband_frequency) { mlt_log_debug(MLT_FILTER_SERVICE(filter), "Create a new stretcher\t%d\t%d\t%f\n", *channels, rubberband_frequency, pitchscale); delete s; // Create a rubberband instance RubberBandStretcher::Options options = RubberBandStretcher::OptionProcessRealTime; #if RUBBERBAND_API_MAJOR_VERSION >= 2 && RUBBERBAND_API_MINOR_VERSION >= 7 // Use the higher quality engine if available. options |= RubberBandStretcher::OptionEngineFiner; #endif s = new RubberBandStretcher(rubberband_frequency, *channels, options, 1.0, pitchscale); pdata->s = s; pdata->rubberband_frequency = rubberband_frequency; pdata->in_samples = 0; pdata->out_samples = 0; } s->setPitchScale(pitchscale); if (pitchscale >= 0.5 && pitchscale <= 2.0) { // Pitch adjustment < 200% #if RUBBERBAND_API_MAJOR_VERSION <= 2 && RUBBERBAND_API_MINOR_VERSION < 7 s->setPitchOption(RubberBandStretcher::OptionPitchHighQuality); #endif s->setTransientsOption(RubberBandStretcher::OptionTransientsCrisp); } else { // Pitch adjustment > 200% // "HighConsistency" and "Smooth" options help to avoid large memory // consumption and crashes that can occur for large pitch adjustments. #if RUBBERBAND_API_MAJOR_VERSION <= 2 && RUBBERBAND_API_MINOR_VERSION < 7 s->setPitchOption(RubberBandStretcher::OptionPitchHighConsistency); #endif s->setTransientsOption(RubberBandStretcher::OptionTransientsSmooth); } s->setTimeRatio(timeratio); // Configure input and output buffers and counters. int consumed_samples = 0; int total_consumed_samples = 0; int received_samples = 0; struct mlt_audio_s in; struct mlt_audio_s out; mlt_audio_set_values(&in, *buffer, *frequency, *format, *samples, *channels); if (stretch) { *frequency = requested_frequency; *samples = requested_samples; } mlt_audio_set_values(&out, NULL, *frequency, *format, *samples, *channels); mlt_audio_alloc_data(&out); // Process all input samples while (true) { // Send more samples to the stretcher if (consumed_samples == in.samples) { // Continue to repeat input samples into the stretcher until it // provides the desired number of samples out. consumed_samples = 0; mlt_log_debug(MLT_FILTER_SERVICE(filter), "Repeat samples\n"); } int process_samples = std::min(in.samples - consumed_samples, (int) s->getSamplesRequired()); if (process_samples == 0 && received_samples == out.samples && total_consumed_samples < in.samples) { // No more out samples are needed, but input samples are still available. // Send the final input samples for processing. process_samples = in.samples - total_consumed_samples; } if (process_samples > 0) { float *in_planes[MAX_CHANNELS]; for (int i = 0; i < in.channels; i++) { in_planes[i] = ((float *) in.data) + (in.samples * i) + consumed_samples; } s->process(in_planes, process_samples, false); consumed_samples += process_samples; total_consumed_samples += process_samples; pdata->in_samples += process_samples; } // Receive samples from the stretcher int retrieve_samples = std::min(out.samples - received_samples, s->available()); if (retrieve_samples > 0) { float *out_planes[MAX_CHANNELS]; for (int i = 0; i < out.channels; i++) { out_planes[i] = ((float *) out.data) + (out.samples * i) + received_samples; } retrieve_samples = (int) s->retrieve(out_planes, retrieve_samples); received_samples += retrieve_samples; pdata->out_samples += retrieve_samples; } mlt_log_debug(MLT_FILTER_SERVICE(filter), "Process: %d\t Retrieve: %d\n", process_samples, retrieve_samples); if (received_samples == out.samples && total_consumed_samples >= in.samples) { // There is nothing more to do; break; } } // Save the processed samples. mlt_audio_shrink(&out, received_samples); mlt_frame_set_audio(frame, out.data, out.format, 0, out.release_data); mlt_audio_get_values(&out, buffer, frequency, format, samples, channels); // Report the latency. double latency = (double) (pdata->in_samples - pdata->out_samples) * 1000.0 / (double) *frequency; mlt_properties_set_double(filter_properties, "latency", latency); mlt_service_unlock(MLT_FILTER_SERVICE(filter)); mlt_log_debug(MLT_FILTER_SERVICE(filter), "Requested: %d\tReceived: %d\tSent: %d\tLatency: %d(%fms)\n", requested_samples, in.samples, out.samples, (int) (pdata->in_samples - pdata->out_samples), latency); return error; } static mlt_frame filter_process(mlt_filter filter, mlt_frame frame) { mlt_properties filter_properties = MLT_FILTER_PROPERTIES(filter); mlt_position position = mlt_filter_get_position(filter, frame); mlt_position length = mlt_filter_get_length2(filter, frame); // Determine the pitchscale double pitchscale = 1.0; if (mlt_properties_exists(filter_properties, "pitchscale")) { pitchscale = mlt_properties_anim_get_double(filter_properties, "pitchscale", position, length); } else { double octaveshift = mlt_properties_anim_get_double(filter_properties, "octaveshift", position, length); pitchscale = pow(2, octaveshift); } if (pitchscale <= 0.0 || /*check for nan:*/ pitchscale != pitchscale) { pitchscale = 1.0; } // Save the pitchscale on the frame to be used in rbpitch_get_audio mlt_properties unique_properties = mlt_frame_unique_properties(frame, MLT_FILTER_SERVICE(filter)); mlt_properties_set_double(unique_properties, "pitchscale", pitchscale); mlt_properties_set_int(unique_properties, "stretch", mlt_properties_get_int(filter_properties, "stretch")); mlt_frame_push_audio(frame, (void *) filter); mlt_frame_push_audio(frame, (void *) rbpitch_get_audio); return frame; } static void close_filter(mlt_filter filter) { private_data *pdata = (private_data *) filter->child; if (pdata) { RubberBandStretcher *s = static_cast(pdata->s); if (s) { delete s; } free(pdata); filter->child = NULL; } } extern "C" { mlt_filter filter_rbpitch_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = mlt_filter_new(); private_data *pdata = (private_data *) calloc(1, sizeof(private_data)); if (filter && pdata) { pdata->s = NULL; pdata->rubberband_frequency = 0; pdata->in_samples = 0; pdata->out_samples = 0; filter->process = filter_process; filter->close = close_filter; filter->child = pdata; } else { mlt_log_error(MLT_FILTER_SERVICE(filter), "Failed to initialize\n"); if (filter) { mlt_filter_close(filter); } if (pdata) { free(pdata); } filter = NULL; } return filter; } } mlt-7.22.0/src/modules/rubberband/filter_rbpitch.yml000664 000000 000000 00000003646 14531534050 022457 0ustar00rootroot000000 000000 schema_version: 7.0 type: filter identifier: rbpitch title: Rubberband Pitch version: 1 copyright: Meltytech, LLC license: GPLv2 language: en tags: - Audio description: Adjust the audio pitch using the Rubberband library. parameters: - identifier: octaveshift title: Octave Shift type: float description: > The octave shift. This is the octave shift of the source frequency. For example, a shift of +1 would double the frequency; -1 would halve the frequency and 0 would leave the pitch unaffected. To put this in frequency terms, a frequency shift f (where f greater than one for upwards shift and less than one for downwards) is: o = log(f) / log(2). Ignored if pitchscale is set. readonly: no mutable: yes animation: yes default: 0.0 minimum: -3.3 maximum: 3.3 unit: octaves - identifier: pitchscale title: Pitch Scale type: float description: > The pitch scaling ratio. This is the ratio of target frequency to source frequency. For example, a ratio of 2.0 would shift up by one octave; 0.5 down by one octave; or 1.0 leave the pitch unaffected. To put this in musical terms, a pitch scaling ratio corresponding to a shift of o octaves (where o is positive for an upwards shift and negative for downwards) is: f = pow(2.0, o). Overrides octaveshift. readonly: no mutable: yes animation: yes default: 1.0 minimum: 0.1 maximum: 10 - identifier: stretch title: Stretch type: boolean description: > Stretch the audio to fill the requested samples. This option will have no effect if the requested sample size is the same as the received sample size. readonly: yes - identifier: latency title: Latency type: float description: > The amount of delay for each sample from the input to the output. readonly: yes unit: ms mlt-7.22.0/src/modules/rubberband/gpl000664 000000 000000 00000000000 14531534050 017416 0ustar00rootroot000000 000000 mlt-7.22.0/src/modules/sdl/000775 000000 000000 00000000000 14531534050 015377 5ustar00rootroot000000 000000 mlt-7.22.0/src/modules/sdl/CMakeLists.txt000664 000000 000000 00000001460 14531534050 020140 0ustar00rootroot000000 000000 add_library(mltsdl MODULE consumer_sdl_audio.c consumer_sdl_preview.c consumer_sdl_still.c consumer_sdl.c factory.c ) file(GLOB YML "*.yml") add_custom_target(Other_sdl_Files SOURCES ${YML} ) target_compile_options(mltsdl PRIVATE ${MLT_COMPILE_OPTIONS}) target_link_libraries(mltsdl PRIVATE mlt m Threads::Threads PkgConfig::sdl) if(APPLE) target_link_libraries(mltsdl PRIVATE objc "-framework Foundation") elseif(UNIX) target_link_libraries(mltsdl PRIVATE X11) endif() set_target_properties(mltsdl PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${MLT_MODULE_OUTPUT_DIRECTORY}") install(TARGETS mltsdl LIBRARY DESTINATION ${MLT_INSTALL_MODULE_DIR}) install(FILES consumer_sdl_audio.yml consumer_sdl_preview.yml consumer_sdl_still.yml consumer_sdl.yml DESTINATION ${MLT_INSTALL_DATA_DIR}/sdl ) mlt-7.22.0/src/modules/sdl/consumer_sdl.c000664 000000 000000 00000100126 14531534050 020240 0ustar00rootroot000000 000000 /* * consumer_sdl.c -- A Simple DirectMedia Layer consumer * Copyright (C) 2003-2021 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "consumer_sdl_osx.h" extern pthread_mutex_t mlt_sdl_mutex; /** This classes definition. */ typedef struct consumer_sdl_s *consumer_sdl; struct consumer_sdl_s { struct mlt_consumer_s parent; mlt_properties properties; mlt_deque queue; pthread_t thread; int joined; atomic_int running; uint8_t audio_buffer[4096 * 10]; int audio_avail; pthread_mutex_t audio_mutex; pthread_cond_t audio_cond; pthread_mutex_t video_mutex; pthread_cond_t video_cond; int window_width; int window_height; int previous_width; int previous_height; int width; int height; atomic_int playing; int sdl_flags; SDL_Overlay *sdl_overlay; SDL_Rect rect; uint8_t *buffer; int bpp; int is_purge; }; /** Forward references to static functions. */ static int consumer_start(mlt_consumer parent); static int consumer_stop(mlt_consumer parent); static int consumer_is_stopped(mlt_consumer parent); static void consumer_purge(mlt_consumer parent); static void consumer_close(mlt_consumer parent); static void *consumer_thread(void *); static int consumer_get_dimensions(int *width, int *height); /** This is what will be called by the factory - anything can be passed in via the argument, but keep it simple. */ mlt_consumer consumer_sdl_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { // Create the consumer object consumer_sdl self = calloc(1, sizeof(struct consumer_sdl_s)); // If no malloc'd and consumer init ok if (self != NULL && mlt_consumer_init(&self->parent, self, profile) == 0) { // Create the queue self->queue = mlt_deque_init(); // Get the parent consumer object mlt_consumer parent = &self->parent; // We have stuff to clean up, so override the close method parent->close = consumer_close; // get a handle on properties mlt_service service = MLT_CONSUMER_SERVICE(parent); self->properties = MLT_SERVICE_PROPERTIES(service); // Set the default volume mlt_properties_set_double(self->properties, "volume", 1.0); // This is the initialisation of the consumer pthread_mutex_init(&self->audio_mutex, NULL); pthread_cond_init(&self->audio_cond, NULL); pthread_mutex_init(&self->video_mutex, NULL); pthread_cond_init(&self->video_cond, NULL); // Default scaler (for now we'll use nearest) mlt_properties_set(self->properties, "rescale", "nearest"); mlt_properties_set(self->properties, "consumer.deinterlacer", "onefield"); mlt_properties_set_int(self->properties, "top_field_first", -1); // Default buffer for low latency mlt_properties_set_int(self->properties, "buffer", 1); // Default audio buffer mlt_properties_set_int(self->properties, "audio_buffer", 2048); // Default scrub audio mlt_properties_set_int(self->properties, "scrub_audio", 1); // Ensure we don't join on a non-running object self->joined = 1; // process actual param if (arg && sscanf(arg, "%dx%d", &self->width, &self->height)) { mlt_properties_set_int(self->properties, "_arg_size", 1); } else { self->width = mlt_properties_get_int(self->properties, "width"); self->height = mlt_properties_get_int(self->properties, "height"); } // Set the sdl flags self->sdl_flags = SDL_HWSURFACE | SDL_ASYNCBLIT | SDL_HWACCEL | SDL_DOUBLEBUF; #if !defined(__APPLE__) self->sdl_flags |= SDL_RESIZABLE; #endif // Allow thread to be started/stopped parent->start = consumer_start; parent->stop = consumer_stop; parent->is_stopped = consumer_is_stopped; parent->purge = consumer_purge; // Register specific events mlt_events_register(self->properties, "consumer-sdl-event"); // Return the consumer produced return parent; } // malloc or consumer init failed free(self); // Indicate failure return NULL; } int consumer_start(mlt_consumer parent) { consumer_sdl self = parent->child; if (!self->running) { mlt_properties properties = MLT_CONSUMER_PROPERTIES(parent); int video_off = mlt_properties_get_int(properties, "video_off"); int preview_off = mlt_properties_get_int(properties, "preview_off"); int display_off = video_off | preview_off; int audio_off = mlt_properties_get_int(properties, "audio_off"); int sdl_started = mlt_properties_get_int(properties, "sdl_started"); char *output_display = mlt_properties_get(properties, "output_display"); char *window_id = mlt_properties_get(properties, "window_id"); char *audio_driver = mlt_properties_get(properties, "audio_driver"); char *video_driver = mlt_properties_get(properties, "video_driver"); char *audio_device = mlt_properties_get(properties, "audio_device"); consumer_stop(parent); self->running = 1; self->joined = 0; if (output_display != NULL) setenv("DISPLAY", output_display, 1); if (window_id != NULL) setenv("SDL_WINDOWID", window_id, 1); if (video_driver != NULL) setenv("SDL_VIDEODRIVER", video_driver, 1); if (audio_driver != NULL) setenv("SDL_AUDIODRIVER", audio_driver, 1); if (audio_device != NULL) setenv("AUDIODEV", audio_device, 1); if (!mlt_properties_get_int(self->properties, "_arg_size")) { if (mlt_properties_get_int(self->properties, "width") > 0) self->width = mlt_properties_get_int(self->properties, "width"); if (mlt_properties_get_int(self->properties, "height") > 0) self->height = mlt_properties_get_int(self->properties, "height"); } self->bpp = mlt_properties_get_int(self->properties, "bpp"); if (sdl_started == 0 && display_off == 0) { pthread_mutex_lock(&mlt_sdl_mutex); int ret = SDL_Init(SDL_INIT_VIDEO | SDL_INIT_NOPARACHUTE); pthread_mutex_unlock(&mlt_sdl_mutex); if (ret < 0) { mlt_log_error(MLT_CONSUMER_SERVICE(parent), "Failed to initialize SDL: %s\n", SDL_GetError()); return -1; } SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL); SDL_EnableUNICODE(1); } if (audio_off == 0) SDL_InitSubSystem(SDL_INIT_AUDIO); // Default window size if (mlt_properties_get_int(self->properties, "_arg_size")) { self->window_width = self->width; self->window_height = self->height; } else { double display_ratio = mlt_properties_get_double(self->properties, "display_ratio"); self->window_width = (double) self->height * display_ratio + 0.5; self->window_height = self->height; } pthread_mutex_lock(&mlt_sdl_mutex); if (!SDL_GetVideoSurface() && display_off == 0) { if (mlt_properties_get_int(self->properties, "fullscreen")) { const SDL_VideoInfo *vi; vi = SDL_GetVideoInfo(); self->window_width = vi->current_w; self->window_height = vi->current_h; self->sdl_flags |= SDL_FULLSCREEN; SDL_ShowCursor(SDL_DISABLE); } SDL_SetVideoMode(self->window_width, self->window_height, 0, self->sdl_flags); } pthread_mutex_unlock(&mlt_sdl_mutex); pthread_create(&self->thread, NULL, consumer_thread, self); } return 0; } int consumer_stop(mlt_consumer parent) { // Get the actual object consumer_sdl self = parent->child; if (self->joined == 0) { // Kill the thread and clean up self->joined = 1; self->running = 0; if (!mlt_properties_get_int(MLT_CONSUMER_PROPERTIES(parent), "audio_off")) { pthread_mutex_lock(&self->audio_mutex); pthread_cond_broadcast(&self->audio_cond); pthread_mutex_unlock(&self->audio_mutex); } #ifndef _WIN32 if (self->thread) #endif pthread_join(self->thread, NULL); // cleanup SDL pthread_mutex_lock(&mlt_sdl_mutex); if (self->sdl_overlay != NULL) SDL_FreeYUVOverlay(self->sdl_overlay); self->sdl_overlay = NULL; if (!mlt_properties_get_int(MLT_CONSUMER_PROPERTIES(parent), "audio_off")) SDL_QuitSubSystem(SDL_INIT_AUDIO); if (mlt_properties_get_int(MLT_CONSUMER_PROPERTIES(parent), "sdl_started") == 0) SDL_Quit(); pthread_mutex_unlock(&mlt_sdl_mutex); } return 0; } int consumer_is_stopped(mlt_consumer parent) { consumer_sdl self = parent->child; return !self->running; } void consumer_purge(mlt_consumer parent) { consumer_sdl self = parent->child; if (self->running) { pthread_mutex_lock(&self->video_mutex); while (mlt_deque_count(self->queue)) mlt_frame_close(mlt_deque_pop_back(self->queue)); self->is_purge = 1; pthread_cond_broadcast(&self->video_cond); pthread_mutex_unlock(&self->video_mutex); } } static int sdl_lock_display() { pthread_mutex_lock(&mlt_sdl_mutex); SDL_Surface *screen = SDL_GetVideoSurface(); int result = screen != NULL && (!SDL_MUSTLOCK(screen) || SDL_LockSurface(screen) >= 0); pthread_mutex_unlock(&mlt_sdl_mutex); return result; } static void sdl_unlock_display() { pthread_mutex_lock(&mlt_sdl_mutex); SDL_Surface *screen = SDL_GetVideoSurface(); if (screen != NULL && SDL_MUSTLOCK(screen)) SDL_UnlockSurface(screen); pthread_mutex_unlock(&mlt_sdl_mutex); } static void sdl_fill_audio(void *udata, uint8_t *stream, int len) { consumer_sdl self = udata; // Get the volume double volume = mlt_properties_get_double(self->properties, "volume"); pthread_mutex_lock(&self->audio_mutex); // Block until audio received while (self->running && len > self->audio_avail) pthread_cond_wait(&self->audio_cond, &self->audio_mutex); if (self->audio_avail >= len) { // Place in the audio buffer if (volume != 1.0) SDL_MixAudio(stream, self->audio_buffer, len, (int) ((float) SDL_MIX_MAXVOLUME * volume)); else memcpy(stream, self->audio_buffer, len); // Remove len from the audio available self->audio_avail -= len; // Remove the samples memmove(self->audio_buffer, self->audio_buffer + len, self->audio_avail); } else { // Just to be safe, wipe the stream first memset(stream, 0, len); // Mix the audio SDL_MixAudio(stream, self->audio_buffer, len, (int) ((float) SDL_MIX_MAXVOLUME * volume)); // No audio left self->audio_avail = 0; } // We're definitely playing now self->playing = 1; pthread_cond_broadcast(&self->audio_cond); pthread_mutex_unlock(&self->audio_mutex); } static int consumer_play_audio(consumer_sdl self, mlt_frame frame, int init_audio, int *duration) { // Get the properties of self consumer mlt_properties properties = self->properties; mlt_audio_format afmt = mlt_audio_s16; // Set the preferred params of the test card signal int channels = mlt_properties_get_int(properties, "channels"); int dest_channels = channels; int frequency = mlt_properties_get_int(properties, "frequency"); int scrub = mlt_properties_get_int(properties, "scrub_audio"); static int counter = 0; int samples = mlt_audio_calculate_frame_samples(mlt_properties_get_double(self->properties, "fps"), frequency, counter++); int16_t *pcm; mlt_frame_get_audio(frame, (void **) &pcm, &afmt, &frequency, &channels, &samples); *duration = ((samples * 1000) / frequency); pcm += mlt_properties_get_int(properties, "audio_offset"); if (mlt_properties_get_int(properties, "audio_off")) { self->playing = 1; init_audio = 1; return init_audio; } if (init_audio == 1) { SDL_AudioSpec request; SDL_AudioSpec got; int audio_buffer = mlt_properties_get_int(properties, "audio_buffer"); // specify audio format memset(&request, 0, sizeof(SDL_AudioSpec)); self->playing = 0; request.freq = frequency; request.format = AUDIO_S16SYS; request.channels = dest_channels; request.samples = audio_buffer; request.callback = sdl_fill_audio; request.userdata = (void *) self; if (SDL_OpenAudio(&request, &got) != 0) { mlt_log_error(MLT_CONSUMER_SERVICE(self), "SDL failed to open audio: %s\n", SDL_GetError()); init_audio = 2; } else if (got.size != 0) { SDL_PauseAudio(0); init_audio = 0; } } if (init_audio == 0) { mlt_properties properties = MLT_FRAME_PROPERTIES(frame); int samples_copied = 0; int dst_stride = dest_channels * sizeof(*pcm); pthread_mutex_lock(&self->audio_mutex); while (self->running && samples_copied < samples) { int sample_space = (sizeof(self->audio_buffer) - self->audio_avail) / dst_stride; while (self->running && sample_space == 0) { pthread_cond_wait(&self->audio_cond, &self->audio_mutex); sample_space = (sizeof(self->audio_buffer) - self->audio_avail) / dst_stride; } if (self->running) { int samples_to_copy = samples - samples_copied; if (samples_to_copy > sample_space) { samples_to_copy = sample_space; } int dst_bytes = samples_to_copy * dst_stride; if (scrub || mlt_properties_get_double(properties, "_speed") == 1) { if (channels == dest_channels) { memcpy(&self->audio_buffer[self->audio_avail], pcm, dst_bytes); pcm += samples_to_copy * channels; } else { int16_t *dest = (int16_t *) &self->audio_buffer[self->audio_avail]; int i = samples_to_copy + 1; while (--i) { memcpy(dest, pcm, dst_stride); pcm += channels; dest += dest_channels; } } } else { memset(&self->audio_buffer[self->audio_avail], 0, dst_bytes); pcm += samples_to_copy * channels; } self->audio_avail += dst_bytes; samples_copied += samples_to_copy; } pthread_cond_broadcast(&self->audio_cond); } pthread_mutex_unlock(&self->audio_mutex); } else { self->playing = 1; } return init_audio; } static int consumer_play_video(consumer_sdl self, mlt_frame frame) { // Get the properties of this consumer mlt_properties properties = self->properties; mlt_image_format vfmt = mlt_properties_get_int(properties, "mlt_image_format"); int width = self->width, height = self->height; uint8_t *image; int changed = 0; int video_off = mlt_properties_get_int(properties, "video_off"); int preview_off = mlt_properties_get_int(properties, "preview_off"); mlt_image_format preview_format = mlt_properties_get_int(properties, "preview_format"); int display_off = video_off | preview_off; if (self->running && display_off == 0) { // Get the image, width and height mlt_frame_get_image(frame, &image, &vfmt, &width, &height, 0); void *pool = mlt_cocoa_autorelease_init(); // Handle events if (SDL_GetVideoSurface()) { SDL_Event event; sdl_lock_display(); pthread_mutex_lock(&mlt_sdl_mutex); changed = consumer_get_dimensions(&self->window_width, &self->window_height); pthread_mutex_unlock(&mlt_sdl_mutex); sdl_unlock_display(); while (SDL_PollEvent(&event)) { mlt_events_fire(self->properties, "consumer-sdl-event", mlt_event_data_from_object(&event)); switch (event.type) { case SDL_VIDEORESIZE: self->window_width = event.resize.w; self->window_height = event.resize.h; changed = 1; break; case SDL_QUIT: self->running = 0; break; case SDL_KEYDOWN: { mlt_producer producer = mlt_properties_get_data(properties, "transport_producer", NULL); char keyboard[2] = " "; void (*callback)(mlt_producer, char *) = mlt_properties_get_data(properties, "transport_callback", NULL); if (callback != NULL && producer != NULL && event.key.keysym.unicode < 0x80 && event.key.keysym.unicode > 0) { keyboard[0] = (char) event.key.keysym.unicode; callback(producer, keyboard); } } break; } } } sdl_lock_display(); if (width != self->width || height != self->height) { if (self->sdl_overlay != NULL) SDL_FreeYUVOverlay(self->sdl_overlay); self->sdl_overlay = NULL; } if (self->running && (!SDL_GetVideoSurface() || changed)) { // Force an overlay recreation if (self->sdl_overlay != NULL) SDL_FreeYUVOverlay(self->sdl_overlay); self->sdl_overlay = NULL; // open SDL window with video overlay, if possible pthread_mutex_lock(&mlt_sdl_mutex); SDL_Surface *screen = SDL_SetVideoMode(self->window_width, self->window_height, self->bpp, self->sdl_flags); if (consumer_get_dimensions(&self->window_width, &self->window_height)) screen = SDL_SetVideoMode(self->window_width, self->window_height, self->bpp, self->sdl_flags); pthread_mutex_unlock(&mlt_sdl_mutex); if (screen) { uint32_t color = mlt_properties_get_int(self->properties, "window_background"); SDL_FillRect(screen, NULL, color >> 8); SDL_Flip(screen); } } if (self->running) { // Determine window's new display aspect ratio double this_aspect = (double) self->window_width / self->window_height; // Get the display aspect ratio double display_ratio = mlt_properties_get_double(properties, "display_ratio"); // Determine frame's display aspect ratio double frame_aspect = mlt_frame_get_aspect_ratio(frame) * width / height; // Store the width and height received self->width = width; self->height = height; // If using hardware scaler if (mlt_properties_get(properties, "rescale") != NULL && !strcmp(mlt_properties_get(properties, "rescale"), "none")) { // Use hardware scaler to normalize display aspect ratio self->rect.w = frame_aspect / this_aspect * self->window_width; self->rect.h = self->window_height; if (self->rect.w > self->window_width) { self->rect.w = self->window_width; self->rect.h = this_aspect / frame_aspect * self->window_height; } } // Special case optimisation to negate odd effect of sample aspect ratio // not corresponding exactly with image resolution. else if ((int) (this_aspect * 1000) == (int) (display_ratio * 1000)) { self->rect.w = self->window_width; self->rect.h = self->window_height; } // Use hardware scaler to normalize sample aspect ratio else if (self->window_height * display_ratio > self->window_width) { self->rect.w = self->window_width; self->rect.h = self->window_width / display_ratio; } else { self->rect.w = self->window_height * display_ratio; self->rect.h = self->window_height; } self->rect.x = (self->window_width - self->rect.w) / 2; self->rect.y = (self->window_height - self->rect.h) / 2; self->rect.x -= self->rect.x % 2; mlt_properties_set_int(self->properties, "rect_x", self->rect.x); mlt_properties_set_int(self->properties, "rect_y", self->rect.y); mlt_properties_set_int(self->properties, "rect_w", self->rect.w); mlt_properties_set_int(self->properties, "rect_h", self->rect.h); SDL_SetClipRect(SDL_GetVideoSurface(), &self->rect); } if (self->running && SDL_GetVideoSurface() && self->sdl_overlay == NULL) { SDL_SetClipRect(SDL_GetVideoSurface(), &self->rect); self->sdl_overlay = SDL_CreateYUVOverlay(width, height, (vfmt == mlt_image_yuv422 ? SDL_YUY2_OVERLAY : SDL_IYUV_OVERLAY), SDL_GetVideoSurface()); } if (self->running && SDL_GetVideoSurface() && self->sdl_overlay != NULL) { self->buffer = self->sdl_overlay->pixels[0]; if (SDL_LockYUVOverlay(self->sdl_overlay) >= 0) { int size = mlt_image_format_size(vfmt, width, height, NULL); if (image != NULL) memcpy(self->buffer, image, size); SDL_UnlockYUVOverlay(self->sdl_overlay); SDL_DisplayYUVOverlay(self->sdl_overlay, &SDL_GetVideoSurface()->clip_rect); } } sdl_unlock_display(); mlt_cocoa_autorelease_close(pool); mlt_events_fire(properties, "consumer-frame-show", mlt_event_data_from_frame(frame)); } else if (self->running) { vfmt = preview_format == mlt_image_none ? mlt_image_rgba : preview_format; if (!video_off) mlt_frame_get_image(frame, &image, &vfmt, &width, &height, 0); mlt_events_fire(properties, "consumer-frame-show", mlt_event_data_from_frame(frame)); } return 0; } static void *video_thread(void *arg) { // Identify the arg consumer_sdl self = arg; // Obtain time of thread start struct timeval now; int64_t start = 0; int64_t elapsed = 0; struct timespec tm; mlt_frame next = NULL; mlt_properties properties = NULL; double speed = 0; // Get real time flag int real_time = mlt_properties_get_int(self->properties, "real_time"); // Get the current time gettimeofday(&now, NULL); // Determine start time start = (int64_t) now.tv_sec * 1000000 + now.tv_usec; while (self->running) { // Pop the next frame pthread_mutex_lock(&self->video_mutex); next = mlt_deque_pop_front(self->queue); while (next == NULL && self->running) { pthread_cond_wait(&self->video_cond, &self->video_mutex); next = mlt_deque_pop_front(self->queue); } pthread_mutex_unlock(&self->video_mutex); if (!self->running || next == NULL) break; // Get the properties properties = MLT_FRAME_PROPERTIES(next); // Get the speed of the frame speed = mlt_properties_get_double(properties, "_speed"); // Get the current time gettimeofday(&now, NULL); // Get the elapsed time elapsed = ((int64_t) now.tv_sec * 1000000 + now.tv_usec) - start; // See if we have to delay the display of the current frame if (mlt_properties_get_int(properties, "rendered") == 1 && self->running) { // Obtain the scheduled playout time int64_t scheduled = mlt_properties_get_int(properties, "playtime"); // Determine the difference between the elapsed time and the scheduled playout time int64_t difference = scheduled - elapsed; // Smooth playback a bit if (real_time && (difference > 20000 && speed == 1.0)) { tm.tv_sec = difference / 1000000; tm.tv_nsec = (difference % 1000000) * 500; nanosleep(&tm, NULL); } // Show current frame if not too old if (!real_time || (difference > -10000 || speed != 1.0 || mlt_deque_count(self->queue) < 2)) consumer_play_video(self, next); // If the queue is empty, recalculate start to allow build up again if (real_time && (mlt_deque_count(self->queue) == 0 && speed == 1.0)) { gettimeofday(&now, NULL); start = ((int64_t) now.tv_sec * 1000000 + now.tv_usec) - scheduled + 20000; } } else { static int dropped = 0; mlt_log_info(MLT_CONSUMER_SERVICE(&self->parent), "dropped video frame %d\n", ++dropped); } // This frame can now be closed mlt_frame_close(next); next = NULL; } if (next != NULL) mlt_frame_close(next); mlt_consumer_stopped(&self->parent); return NULL; } /** Threaded wrapper for pipe. */ static void *consumer_thread(void *arg) { // Identify the arg consumer_sdl self = arg; // Get the consumer mlt_consumer consumer = &self->parent; // Convenience functionality int terminate_on_pause = mlt_properties_get_int(MLT_CONSUMER_PROPERTIES(consumer), "terminate_on_pause"); int terminated = 0; // Video thread pthread_t thread; // internal initialization int init_audio = 1; int init_video = 1; mlt_frame frame = NULL; int duration = 0; int64_t playtime = 0; struct timespec tm = {0, 100000}; // Loop until told not to while (self->running) { // Get a frame from the attached producer frame = !terminated ? mlt_consumer_rt_frame(consumer) : NULL; // Check for termination if (terminate_on_pause && frame) terminated = mlt_properties_get_double(MLT_FRAME_PROPERTIES(frame), "_speed") == 0.0; // Ensure that we have a frame if (frame) { // Play audio init_audio = consumer_play_audio(self, frame, init_audio, &duration); // Determine the start time now if (self->playing && init_video) { // Create the video thread pthread_create(&thread, NULL, video_thread, self); // Video doesn't need to be initialised any more init_video = 0; } // Set playtime for this frame mlt_properties_set_int(MLT_FRAME_PROPERTIES(frame), "playtime", playtime); while (self->running && mlt_deque_count(self->queue) > 15) nanosleep(&tm, NULL); // Push this frame to the back of the queue pthread_mutex_lock(&self->video_mutex); if (self->is_purge) { mlt_frame_close(frame); frame = NULL; self->is_purge = 0; } else { mlt_deque_push_back(self->queue, frame); pthread_cond_broadcast(&self->video_cond); } pthread_mutex_unlock(&self->video_mutex); // Calculate the next playtime playtime += (duration * 1000); } else if (terminated) { if (init_video || mlt_deque_count(self->queue) == 0) break; else nanosleep(&tm, NULL); } } self->running = 0; // Unblock sdl_preview if (mlt_properties_get_int(MLT_CONSUMER_PROPERTIES(consumer), "put_mode") && mlt_properties_get_int(MLT_CONSUMER_PROPERTIES(consumer), "put_pending")) { frame = mlt_consumer_get_frame(consumer); if (frame) mlt_frame_close(frame); frame = NULL; } // Kill the video thread if (init_video == 0) { pthread_mutex_lock(&self->video_mutex); pthread_cond_broadcast(&self->video_cond); pthread_mutex_unlock(&self->video_mutex); pthread_join(thread, NULL); } while (mlt_deque_count(self->queue)) mlt_frame_close(mlt_deque_pop_back(self->queue)); pthread_mutex_lock(&self->audio_mutex); self->audio_avail = 0; pthread_mutex_unlock(&self->audio_mutex); return NULL; } static int consumer_get_dimensions(int *width, int *height) { int changed = 0; // SDL windows manager structure SDL_SysWMinfo wm; // Specify the SDL Version SDL_VERSION(&wm.version); // Lock the display //sdl_lock_display(); #ifndef __APPLE__ // Get the wm structure if (SDL_GetWMInfo(&wm) == 1) { #ifndef _WIN32 // Check that we have the X11 wm if (wm.subsystem == SDL_SYSWM_X11) { // Get the SDL window Window window = wm.info.x11.window; // Get the display session Display *display = wm.info.x11.display; // Get the window attributes XWindowAttributes attr; XGetWindowAttributes(display, window, &attr); // Determine whether window has changed changed = *width != attr.width || *height != attr.height; // Return width and height *width = attr.width; *height = attr.height; } #endif } #endif // Unlock the display //sdl_unlock_display(); return changed; } /** Callback to allow override of the close method. */ static void consumer_close(mlt_consumer parent) { // Get the actual object consumer_sdl self = parent->child; // Stop the consumer ///mlt_consumer_stop( parent ); // Now clean up the rest mlt_consumer_close(parent); // Close the queue mlt_deque_close(self->queue); // Destroy mutexes pthread_mutex_destroy(&self->audio_mutex); pthread_cond_destroy(&self->audio_cond); // Finally clean up this free(self); } mlt-7.22.0/src/modules/sdl/consumer_sdl.yml000664 000000 000000 00000002645 14531534050 020626 0ustar00rootroot000000 000000 schema_version: 7.0 type: consumer identifier: sdl title: SDL Fast YUV version: 1 copyright: Meltytech, LLC creator: Dan Dennedy license: LGPLv2.1 language: en tags: - Audio - Video description: > Simple DirectMedia Layer audio and video output module. parameters: - identifier: resolution title: Resolution type: string description: The size of the window as WxH pixels. argument: yes required: no - identifier: volume title: Volume type: float description: Audio level factor. mutable: yes - identifier: video_off title: Video off type: boolean description: Disable video output mutable: yes default: 0 widget: checkbox - identifier: audio_off title: Audio off type: boolean description: Disable audio output mutable: yes default: 0 widget: checkbox - identifier: audio_buffer title: Audio buffer type: integer description: Size of the SDL audio buffer. mutable: yes default: 2048 minimum: 128 - identifier: scrub_audio title: Audio scrubbing type: boolean description: Play sound even when the speed is not normal. mutable: yes default: 1 widget: checkbox - identifier: terminate_on_pause title: Stop automatically type: boolean description: > Whether to stop playback at the end of the producer or when playback is paused. default: 0 widget: checkbox mlt-7.22.0/src/modules/sdl/consumer_sdl_audio.c000664 000000 000000 00000054263 14531534050 021433 0ustar00rootroot000000 000000 /* * consumer_sdl_audio.c -- A Simple DirectMedia Layer audio-only consumer * Copyright (C) 2009-2021 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include #include #include #include #include #include #include extern pthread_mutex_t mlt_sdl_mutex; /** This classes definition. */ typedef struct consumer_sdl_s *consumer_sdl; struct consumer_sdl_s { struct mlt_consumer_s parent; mlt_properties properties; mlt_deque queue; pthread_t thread; int joined; atomic_int running; uint8_t audio_buffer[4096 * 10]; int audio_avail; pthread_mutex_t audio_mutex; pthread_cond_t audio_cond; pthread_mutex_t video_mutex; pthread_cond_t video_cond; atomic_int playing; pthread_cond_t refresh_cond; pthread_mutex_t refresh_mutex; int refresh_count; int is_purge; }; /** Forward references to static functions. */ static int consumer_start(mlt_consumer parent); static int consumer_stop(mlt_consumer parent); static int consumer_is_stopped(mlt_consumer parent); static void consumer_purge(mlt_consumer parent); static void consumer_close(mlt_consumer parent); static void *consumer_thread(void *); static void consumer_refresh_cb(mlt_consumer sdl, mlt_consumer self, mlt_event_data); /** This is what will be called by the factory - anything can be passed in via the argument, but keep it simple. */ mlt_consumer consumer_sdl_audio_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { // Create the consumer object consumer_sdl self = calloc(1, sizeof(struct consumer_sdl_s)); // If no malloc'd and consumer init ok if (self != NULL && mlt_consumer_init(&self->parent, self, profile) == 0) { // Create the queue self->queue = mlt_deque_init(); // Get the parent consumer object mlt_consumer parent = &self->parent; // We have stuff to clean up, so override the close method parent->close = consumer_close; // get a handle on properties mlt_service service = MLT_CONSUMER_SERVICE(parent); self->properties = MLT_SERVICE_PROPERTIES(service); // Set the default volume mlt_properties_set_double(self->properties, "volume", 1.0); // This is the initialisation of the consumer pthread_mutex_init(&self->audio_mutex, NULL); pthread_cond_init(&self->audio_cond, NULL); pthread_mutex_init(&self->video_mutex, NULL); pthread_cond_init(&self->video_cond, NULL); // Default scaler (for now we'll use nearest) mlt_properties_set(self->properties, "rescale", "nearest"); mlt_properties_set(self->properties, "consumer.deinterlacer", "onefield"); mlt_properties_set_int(self->properties, "top_field_first", -1); // Default buffer for low latency mlt_properties_set_int(self->properties, "buffer", 1); // Default audio buffer mlt_properties_set_int(self->properties, "audio_buffer", 2048); #if defined(_WIN32) && SDL_MAJOR_VERSION == 2 mlt_properties_set(self->properties, "audio_driver", "DirectSound"); #endif // Ensure we don't join on a non-running object self->joined = 1; // Allow thread to be started/stopped parent->start = consumer_start; parent->stop = consumer_stop; parent->is_stopped = consumer_is_stopped; parent->purge = consumer_purge; // Initialize the refresh handler pthread_cond_init(&self->refresh_cond, NULL); pthread_mutex_init(&self->refresh_mutex, NULL); mlt_events_listen(MLT_CONSUMER_PROPERTIES(parent), self, "property-changed", (mlt_listener) consumer_refresh_cb); // Return the consumer produced return parent; } // malloc or consumer init failed free(self); // Indicate failure return NULL; } static void consumer_refresh_cb(mlt_consumer sdl, mlt_consumer parent, mlt_event_data event_data) { const char *name = mlt_event_data_to_string(event_data); if (name && !strcmp(name, "refresh")) { consumer_sdl self = parent->child; pthread_mutex_lock(&self->refresh_mutex); if (self->refresh_count < 2) self->refresh_count = self->refresh_count <= 0 ? 1 : self->refresh_count + 1; pthread_cond_broadcast(&self->refresh_cond); pthread_mutex_unlock(&self->refresh_mutex); } } int consumer_start(mlt_consumer parent) { consumer_sdl self = parent->child; if (!self->running) { consumer_stop(parent); mlt_properties properties = MLT_CONSUMER_PROPERTIES(parent); char *audio_driver = mlt_properties_get(properties, "audio_driver"); char *audio_device = mlt_properties_get(properties, "audio_device"); if (audio_driver && strcmp(audio_driver, "")) setenv("SDL_AUDIODRIVER", audio_driver, 1); if (audio_device && strcmp(audio_device, "")) setenv("AUDIODEV", audio_device, 1); pthread_mutex_lock(&mlt_sdl_mutex); int ret = SDL_Init(SDL_INIT_AUDIO | SDL_INIT_NOPARACHUTE); pthread_mutex_unlock(&mlt_sdl_mutex); if (ret < 0) { mlt_log_error(MLT_CONSUMER_SERVICE(parent), "Failed to initialize SDL: %s\n", SDL_GetError()); return -1; } self->running = 1; self->joined = 0; pthread_create(&self->thread, NULL, consumer_thread, self); } return 0; } int consumer_stop(mlt_consumer parent) { // Get the actual object consumer_sdl self = parent->child; if (self->running && !self->joined) { // Kill the thread and clean up self->joined = 1; self->running = 0; // Unlatch the consumer thread pthread_mutex_lock(&self->refresh_mutex); pthread_cond_broadcast(&self->refresh_cond); pthread_mutex_unlock(&self->refresh_mutex); // Cleanup the main thread #ifndef _WIN32 if (self->thread) #endif pthread_join(self->thread, NULL); // Unlatch the video thread pthread_mutex_lock(&self->video_mutex); pthread_cond_broadcast(&self->video_cond); pthread_mutex_unlock(&self->video_mutex); // Unlatch the audio callback pthread_mutex_lock(&self->audio_mutex); pthread_cond_broadcast(&self->audio_cond); pthread_mutex_unlock(&self->audio_mutex); SDL_QuitSubSystem(SDL_INIT_AUDIO); } return 0; } int consumer_is_stopped(mlt_consumer parent) { consumer_sdl self = parent->child; return !self->running; } void consumer_purge(mlt_consumer parent) { consumer_sdl self = parent->child; if (self->running) { pthread_mutex_lock(&self->video_mutex); mlt_frame frame = MLT_FRAME(mlt_deque_peek_back(self->queue)); // When playing rewind or fast forward then we need to keep one // frame in the queue to prevent playback stalling. double speed = frame ? mlt_properties_get_double(MLT_FRAME_PROPERTIES(frame), "_speed") : 0; int n = (speed == 0.0 || speed == 1.0) ? 0 : 1; while (mlt_deque_count(self->queue) > n) mlt_frame_close(mlt_deque_pop_back(self->queue)); self->is_purge = 1; pthread_cond_broadcast(&self->video_cond); pthread_mutex_unlock(&self->video_mutex); } } static void sdl_fill_audio(void *udata, uint8_t *stream, int len) { consumer_sdl self = udata; // Get the volume double volume = mlt_properties_get_double(self->properties, "volume"); // Wipe the stream first memset(stream, 0, len); pthread_mutex_lock(&self->audio_mutex); if (self->audio_avail >= len) { // Place in the audio buffer if (volume != 1.0) SDL_MixAudio(stream, self->audio_buffer, len, (int) ((float) SDL_MIX_MAXVOLUME * volume)); else memcpy(stream, self->audio_buffer, len); // Remove len from the audio available self->audio_avail -= len; // Remove the samples memmove(self->audio_buffer, self->audio_buffer + len, self->audio_avail); } else { // Mix the audio SDL_MixAudio(stream, self->audio_buffer, self->audio_avail, (int) ((float) SDL_MIX_MAXVOLUME * volume)); // No audio left self->audio_avail = 0; } // We're definitely playing now self->playing = 1; pthread_cond_broadcast(&self->audio_cond); pthread_mutex_unlock(&self->audio_mutex); } static int consumer_play_audio(consumer_sdl self, mlt_frame frame, int init_audio, int *duration) { // Get the properties of self consumer mlt_properties properties = self->properties; mlt_audio_format afmt = mlt_audio_s16; // Set the preferred params of the test card signal int channels = mlt_properties_get_int(properties, "channels"); int dest_channels = channels; int frequency = mlt_properties_get_int(properties, "frequency"); int scrub = mlt_properties_get_int(properties, "scrub_audio"); static int counter = 0; int samples = mlt_audio_calculate_frame_samples(mlt_properties_get_double(self->properties, "fps"), frequency, counter++); int16_t *pcm; mlt_frame_get_audio(frame, (void **) &pcm, &afmt, &frequency, &channels, &samples); *duration = ((samples * 1000) / frequency); pcm += mlt_properties_get_int(properties, "audio_offset"); if (mlt_properties_get_int(properties, "audio_off")) { self->playing = 1; init_audio = 1; return init_audio; } if (init_audio == 1) { SDL_AudioSpec request; SDL_AudioSpec got; int audio_buffer = mlt_properties_get_int(properties, "audio_buffer"); // specify audio format memset(&request, 0, sizeof(SDL_AudioSpec)); self->playing = 0; request.freq = frequency; request.format = AUDIO_S16SYS; request.channels = dest_channels; request.samples = audio_buffer; request.callback = sdl_fill_audio; request.userdata = (void *) self; if (SDL_OpenAudio(&request, &got) != 0) { mlt_log_error(MLT_CONSUMER_SERVICE(self), "SDL failed to open audio: %s\n", SDL_GetError()); init_audio = 2; } else if (got.size != 0) { SDL_PauseAudio(0); init_audio = 0; } } if (init_audio == 0) { mlt_properties properties = MLT_FRAME_PROPERTIES(frame); int samples_copied = 0; int dst_stride = dest_channels * sizeof(*pcm); pthread_mutex_lock(&self->audio_mutex); while (self->running && samples_copied < samples) { int sample_space = (sizeof(self->audio_buffer) - self->audio_avail) / dst_stride; while (self->running && sample_space == 0) { pthread_cond_wait(&self->audio_cond, &self->audio_mutex); sample_space = (sizeof(self->audio_buffer) - self->audio_avail) / dst_stride; } if (self->running) { int samples_to_copy = samples - samples_copied; if (samples_to_copy > sample_space) { samples_to_copy = sample_space; } int dst_bytes = samples_to_copy * dst_stride; if (scrub || mlt_properties_get_double(properties, "_speed") == 1) { if (channels == dest_channels) { memcpy(&self->audio_buffer[self->audio_avail], pcm, dst_bytes); pcm += samples_to_copy * channels; } else { int16_t *dest = (int16_t *) &self->audio_buffer[self->audio_avail]; int i = samples_to_copy + 1; while (--i) { memcpy(dest, pcm, dst_stride); pcm += channels; dest += dest_channels; } } } else { memset(&self->audio_buffer[self->audio_avail], 0, dst_bytes); pcm += samples_to_copy * channels; } self->audio_avail += dst_bytes; samples_copied += samples_to_copy; } pthread_cond_broadcast(&self->audio_cond); } pthread_mutex_unlock(&self->audio_mutex); } else { self->playing = 1; } return init_audio; } static int consumer_play_video(consumer_sdl self, mlt_frame frame) { // Get the properties of this consumer mlt_properties properties = self->properties; mlt_events_fire(properties, "consumer-frame-show", mlt_event_data_from_frame(frame)); return 0; } static void *video_thread(void *arg) { // Identify the arg consumer_sdl self = arg; // Obtain time of thread start struct timeval now; int64_t start = 0; int64_t elapsed = 0; struct timespec tm; mlt_frame next = NULL; mlt_properties properties = NULL; double speed = 0; // Get real time flag int real_time = mlt_properties_get_int(self->properties, "real_time"); // Get the current time gettimeofday(&now, NULL); // Determine start time start = (int64_t) now.tv_sec * 1000000 + now.tv_usec; while (self->running) { // Pop the next frame pthread_mutex_lock(&self->video_mutex); next = mlt_deque_pop_front(self->queue); while (next == NULL && self->running) { pthread_cond_wait(&self->video_cond, &self->video_mutex); next = mlt_deque_pop_front(self->queue); } pthread_mutex_unlock(&self->video_mutex); if (!self->running || next == NULL) break; // Get the properties properties = MLT_FRAME_PROPERTIES(next); // Get the speed of the frame speed = mlt_properties_get_double(properties, "_speed"); // Get the current time gettimeofday(&now, NULL); // Get the elapsed time elapsed = ((int64_t) now.tv_sec * 1000000 + now.tv_usec) - start; // See if we have to delay the display of the current frame if (mlt_properties_get_int(properties, "rendered") == 1) { // Obtain the scheduled playout time int64_t scheduled = mlt_properties_get_int(properties, "playtime"); // Determine the difference between the elapsed time and the scheduled playout time int64_t difference = scheduled - elapsed; // Smooth playback a bit if (real_time && (difference > 20000 && speed == 1.0)) { tm.tv_sec = difference / 1000000; tm.tv_nsec = (difference % 1000000) * 500; nanosleep(&tm, NULL); } // Show current frame if not too old if (!real_time || (difference > -10000 || speed != 1.0 || mlt_deque_count(self->queue) < 2)) consumer_play_video(self, next); // If the queue is empty, recalculate start to allow build up again if (real_time && (mlt_deque_count(self->queue) == 0 && speed == 1.0)) { gettimeofday(&now, NULL); start = ((int64_t) now.tv_sec * 1000000 + now.tv_usec) - scheduled + 20000; } } // This frame can now be closed mlt_frame_close(next); next = NULL; } // This consumer is stopping. But audio has already been played for all // the frames in the queue. Spit out all the frames so that the display has // the option to catch up with the audio. if (next != NULL) { consumer_play_video(self, next); mlt_frame_close(next); next = NULL; } while (mlt_deque_count(self->queue) > 0) { next = mlt_deque_pop_front(self->queue); consumer_play_video(self, next); mlt_frame_close(next); next = NULL; } mlt_consumer_stopped(&self->parent); return NULL; } /** Threaded wrapper for pipe. */ static void *consumer_thread(void *arg) { // Identify the arg consumer_sdl self = arg; // Get the consumer mlt_consumer consumer = &self->parent; // Get the properties mlt_properties consumer_props = MLT_CONSUMER_PROPERTIES(consumer); // Video thread pthread_t thread; // internal initialization int init_audio = 1; int init_video = 1; mlt_frame frame = NULL; mlt_properties properties = NULL; int duration = 0; int64_t playtime = 0; struct timespec tm = {0, 100000}; // int last_position = -1; pthread_mutex_lock(&self->refresh_mutex); self->refresh_count = 0; pthread_mutex_unlock(&self->refresh_mutex); // Loop until told not to while (self->running) { // Get a frame from the attached producer frame = mlt_consumer_rt_frame(consumer); // Ensure that we have a frame if (frame) { // Get the frame properties properties = MLT_FRAME_PROPERTIES(frame); // Get the speed of the frame double speed = mlt_properties_get_double(properties, "_speed"); // Clear refresh mlt_events_block(consumer_props, consumer_props); mlt_properties_set_int(consumer_props, "refresh", 0); mlt_events_unblock(consumer_props, consumer_props); // Play audio init_audio = consumer_play_audio(self, frame, init_audio, &duration); // Determine the start time now if (self->playing && init_video) { // Create the video thread pthread_create(&thread, NULL, video_thread, self); // Video doesn't need to be initialised any more init_video = 0; } // Set playtime for this frame mlt_properties_set_int(properties, "playtime", playtime); while (self->running && speed != 0 && mlt_deque_count(self->queue) > 15) nanosleep(&tm, NULL); // Push this frame to the back of the video queue if (self->running && speed) { pthread_mutex_lock(&self->video_mutex); if (self->is_purge && speed == 1.0) { mlt_frame_close(frame); frame = NULL; self->is_purge = 0; } else { mlt_deque_push_back(self->queue, frame); pthread_cond_broadcast(&self->video_cond); } pthread_mutex_unlock(&self->video_mutex); // Calculate the next playtime playtime += (duration * 1000); } else if (self->running) { pthread_mutex_lock(&self->refresh_mutex); consumer_play_video(self, frame); mlt_frame_close(frame); frame = NULL; self->refresh_count--; if (self->refresh_count <= 0) { pthread_cond_wait(&self->refresh_cond, &self->refresh_mutex); } pthread_mutex_unlock(&self->refresh_mutex); } // Optimisation to reduce latency if (speed == 1.0) { // TODO: disabled due to misbehavior on parallel-consumer // if ( last_position != -1 && last_position + 1 != mlt_frame_get_position( frame ) ) // mlt_consumer_purge( consumer ); // last_position = mlt_frame_get_position( frame ); } else if (speed == 0.0) { mlt_consumer_purge(consumer); // last_position = -1; } } } // Kill the video thread if (init_video == 0) { pthread_mutex_lock(&self->video_mutex); pthread_cond_broadcast(&self->video_cond); pthread_mutex_unlock(&self->video_mutex); pthread_join(thread, NULL); } if (frame) { // The video thread has cleared out the queue. But the audio was played // for this frame. So play the video before stopping so the display has // the option to catch up with the audio. consumer_play_video(self, frame); mlt_frame_close(frame); frame = NULL; } pthread_mutex_lock(&self->audio_mutex); self->audio_avail = 0; pthread_mutex_unlock(&self->audio_mutex); return NULL; } /** Callback to allow override of the close method. */ static void consumer_close(mlt_consumer parent) { // Get the actual object consumer_sdl self = parent->child; // Stop the consumer mlt_consumer_stop(parent); // Now clean up the rest mlt_consumer_close(parent); // Close the queue mlt_deque_close(self->queue); // Destroy mutexes pthread_mutex_destroy(&self->audio_mutex); pthread_cond_destroy(&self->audio_cond); pthread_mutex_destroy(&self->video_mutex); pthread_cond_destroy(&self->video_cond); pthread_mutex_destroy(&self->refresh_mutex); pthread_cond_destroy(&self->refresh_cond); // Finally clean up this free(self); } mlt-7.22.0/src/modules/sdl/consumer_sdl_audio.yml000664 000000 000000 00000001721 14531534050 022001 0ustar00rootroot000000 000000 schema_version: 0.1 type: consumer identifier: sdl_audio title: SDL Audio Only version: 1 copyright: Meltytech, LLC creator: Dan Dennedy license: LGPLv2.1 language: en tags: - Audio description: > Simple DirectMedia Layer audio only output module. parameters: - identifier: volume title: Volume type: float description: Audio level factor. mutable: yes - identifier: audio_off title: Audio off type: integer description: If 1, disable audio output mutable: yes minimum: 0 maximum: 1 default: 0 widget: checkbox - identifier: audio_buffer title: Audio buffer type: integer description: Size of the sdl audio buffer. mutable: yes default: 2048 minimum: 128 - identifier: scrub_audio title: Audio scrubbing type: integer description: If enabled, sound is played even when the speed is not normal. mutable: yes minimum: 0 maximum: 1 default: 0 widget: checkbox mlt-7.22.0/src/modules/sdl/consumer_sdl_osx.h000664 000000 000000 00000002237 14531534050 021142 0ustar00rootroot000000 000000 /* * consumer_sdl_osx.m -- An OS X compatibility shim for SDL * Copyright (C) 2010-2014 Meltytech, LLC * Author: Dan Dennedy * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef _CONSUMER_SDL_OSX_H_ #define _CONSUMER_SDL_OSX_H_ #ifdef __APPLE__ void *mlt_cocoa_autorelease_init(); void mlt_cocoa_autorelease_close(void *); #else static inline void *mlt_cocoa_autorelease_init() { return NULL; } static inline void mlt_cocoa_autorelease_close(void *p) {} #endif #endif mlt-7.22.0/src/modules/sdl/consumer_sdl_osx.m000664 000000 000000 00000005633 14531534050 021152 0ustar00rootroot000000 000000 /* * consumer_sdl_osx.m -- An OS X compatibility shim for SDL * Copyright (C) 2010-2014 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #import void* mlt_cocoa_autorelease_init() { return [[NSAutoreleasePool alloc] init]; } void mlt_cocoa_autorelease_close( void* p ) { NSAutoreleasePool* pool = ( NSAutoreleasePool* ) p; [pool release]; } #if 0 /* The code below is not used at this time - could not get it to work, but * it is based on code from gruntster on the avidemux project team. */ #import static NSWindow* nsWindow = nil; static NSQuickDrawView* nsView = nil; void sdl_cocoa_init(void* parent, int x, int y, int width, int height) { NSRect contentRect; contentRect = NSMakeRect(x, y, width, height); if (!nsWindow) { // initWithWindowRef always returns the same result for the same WindowRef nsWindow = [[NSWindow alloc] initWithWindowRef:(WindowRef)parent]; [nsWindow setContentView:[[[NSView alloc] initWithFrame:contentRect] autorelease]]; } if (!nsView) { nsView = [[NSQuickDrawView alloc] initWithFrame:contentRect]; [[nsWindow contentView] addSubview:nsView]; [nsView release]; [nsWindow orderOut:nil]; // very important, otherwise window won't be placed correctly on repeat showings [nsWindow orderFront:nil]; } else { [nsView setFrame:contentRect]; [[nsWindow contentView] setFrame:contentRect]; } // finally, set SDL environment variables with all this nonsense char SDL_windowhack[20]; sprintf(SDL_windowhack, "%d", (int)nsWindow); setenv("SDL_NSWindowPointer", SDL_windowhack, 1); sprintf(SDL_windowhack,"%d", (int)nsView); setenv("SDL_NSQuickDrawViewPointer", SDL_windowhack, 1); } void sdl_cocoa_close(void) { if (nsWindow) { // Reference count cannot fall below 2 because SDL releases the window when closing // and again when reinitialising (even though this is our own window...). if ([nsWindow retainCount] > 2) [nsWindow release]; // SDL takes care of all the destroying...a little too much, so make sure our Carbon // window is still displayed (via its Cocoa wrapper) [nsWindow makeKeyAndOrderFront:nil]; } } #endif mlt-7.22.0/src/modules/sdl/consumer_sdl_osx_hack.h000664 000000 000000 00000002106 14531534050 022123 0ustar00rootroot000000 000000 /* * Purpose: A dummy thread object to inform Cocoa that it needs to be thread safe. * Author: Zachary Drew * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #import @interface DummyThread : NSObject - init; - (void)startThread:(id)arg; @end @implementation DummyThread - init { [super init]; return self; } - (void)startThread:(id)arg { return; } @end mlt-7.22.0/src/modules/sdl/consumer_sdl_preview.c000664 000000 000000 00000052447 14531534050 022015 0ustar00rootroot000000 000000 /* * consumer_sdl_preview.c -- A Simple DirectMedia Layer consumer * Copyright (C) 2004-2021 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include #include #include #include #include extern pthread_mutex_t mlt_sdl_mutex; typedef struct consumer_sdl_s *consumer_sdl; struct consumer_sdl_s { struct mlt_consumer_s parent; mlt_consumer active; int ignore_change; mlt_consumer play; mlt_consumer still; pthread_t thread; int joined; int running; int sdl_flags; double last_speed; mlt_position last_position; pthread_cond_t refresh_cond; pthread_mutex_t refresh_mutex; int refresh_count; }; /** Forward references to static functions. */ static int consumer_start(mlt_consumer parent); static int consumer_stop(mlt_consumer parent); static int consumer_is_stopped(mlt_consumer parent); static void consumer_purge(mlt_consumer parent); static void consumer_close(mlt_consumer parent); static void *consumer_thread(void *); static void consumer_frame_show_cb(mlt_consumer sdl, mlt_consumer self, mlt_event_data); static void consumer_sdl_event_cb(mlt_consumer sdl, mlt_consumer self, mlt_event_data); static void consumer_refresh_cb(mlt_consumer sdl, mlt_consumer self, mlt_event_data); mlt_consumer consumer_sdl_preview_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { consumer_sdl self = calloc(1, sizeof(struct consumer_sdl_s)); if (self != NULL && mlt_consumer_init(&self->parent, self, profile) == 0) { // Get the parent consumer object mlt_consumer parent = &self->parent; // Get the properties mlt_properties properties = MLT_CONSUMER_PROPERTIES(parent); // Get the width and height int width = mlt_properties_get_int(properties, "width"); int height = mlt_properties_get_int(properties, "height"); // Process actual param if (arg == NULL || sscanf(arg, "%dx%d", &width, &height) == 2) { mlt_properties_set_int(properties, "width", width); mlt_properties_set_int(properties, "height", height); } // Create child consumers self->play = mlt_factory_consumer(profile, "sdl", arg); self->still = mlt_factory_consumer(profile, "sdl_still", arg); mlt_properties_set(properties, "rescale", "nearest"); mlt_properties_set(properties, "consumer.deinterlacer", "onefield"); mlt_properties_set_int(properties, "prefill", 1); mlt_properties_set_int(properties, "top_field_first", -1); parent->close = consumer_close; parent->start = consumer_start; parent->stop = consumer_stop; parent->is_stopped = consumer_is_stopped; parent->purge = consumer_purge; self->joined = 1; mlt_events_listen(MLT_CONSUMER_PROPERTIES(self->play), self, "consumer-frame-show", (mlt_listener) consumer_frame_show_cb); mlt_events_listen(MLT_CONSUMER_PROPERTIES(self->still), self, "consumer-frame-show", (mlt_listener) consumer_frame_show_cb); mlt_events_listen(MLT_CONSUMER_PROPERTIES(self->play), self, "consumer-sdl-event", (mlt_listener) consumer_sdl_event_cb); mlt_events_listen(MLT_CONSUMER_PROPERTIES(self->still), self, "consumer-sdl-event", (mlt_listener) consumer_sdl_event_cb); pthread_cond_init(&self->refresh_cond, NULL); pthread_mutex_init(&self->refresh_mutex, NULL); mlt_events_listen(MLT_CONSUMER_PROPERTIES(parent), self, "property-changed", (mlt_listener) consumer_refresh_cb); mlt_events_register(properties, "consumer-sdl-paused"); return parent; } free(self); return NULL; } void consumer_frame_show_cb(mlt_consumer sdl, mlt_consumer parent, mlt_event_data event_data) { mlt_frame frame = mlt_event_data_to_frame(event_data); consumer_sdl self = parent->child; if (frame && self) { self->last_speed = mlt_properties_get_double(MLT_FRAME_PROPERTIES(frame), "_speed"); self->last_position = mlt_frame_get_position(frame); mlt_events_fire(MLT_CONSUMER_PROPERTIES(parent), "consumer-frame-show", event_data); } } static void consumer_sdl_event_cb(mlt_consumer sdl, mlt_consumer parent, mlt_event_data event_data) { mlt_events_fire(MLT_CONSUMER_PROPERTIES(parent), "consumer-sdl-event", event_data); } static void consumer_refresh_cb(mlt_consumer sdl, mlt_consumer parent, mlt_event_data event_data) { const char *name = mlt_event_data_to_string(event_data); if (name && !strcmp(name, "refresh")) { consumer_sdl self = parent->child; pthread_mutex_lock(&self->refresh_mutex); self->refresh_count = self->refresh_count <= 0 ? 1 : self->refresh_count + 1; pthread_cond_broadcast(&self->refresh_cond); pthread_mutex_unlock(&self->refresh_mutex); } } static int consumer_start(mlt_consumer parent) { consumer_sdl self = parent->child; if (!self->running) { // properties mlt_properties properties = MLT_CONSUMER_PROPERTIES(parent); mlt_properties play = MLT_CONSUMER_PROPERTIES(self->play); mlt_properties still = MLT_CONSUMER_PROPERTIES(self->still); char *window_id = mlt_properties_get(properties, "window_id"); char *audio_driver = mlt_properties_get(properties, "audio_driver"); char *video_driver = mlt_properties_get(properties, "video_driver"); char *audio_device = mlt_properties_get(properties, "audio_device"); char *output_display = mlt_properties_get(properties, "output_display"); int progressive = mlt_properties_get_int(properties, "progressive") | mlt_properties_get_int(properties, "deinterlace"); consumer_stop(parent); self->running = 1; self->joined = 0; self->last_speed = 1; if (output_display != NULL) setenv("DISPLAY", output_display, 1); if (window_id != NULL) setenv("SDL_WINDOWID", window_id, 1); if (video_driver != NULL) setenv("SDL_VIDEODRIVER", video_driver, 1); if (audio_driver != NULL) setenv("SDL_AUDIODRIVER", audio_driver, 1); if (audio_device != NULL) setenv("AUDIODEV", audio_device, 1); pthread_mutex_lock(&mlt_sdl_mutex); int ret = SDL_Init(SDL_INIT_VIDEO | SDL_INIT_NOPARACHUTE); pthread_mutex_unlock(&mlt_sdl_mutex); if (ret < 0) { fprintf(stderr, "Failed to initialize SDL: %s\n", SDL_GetError()); return -1; } SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL); SDL_EnableUNICODE(1); // Pass properties down mlt_properties_set_data(play, "transport_producer", mlt_properties_get_data(properties, "transport_producer", NULL), 0, NULL, NULL); mlt_properties_set_data(still, "transport_producer", mlt_properties_get_data(properties, "transport_producer", NULL), 0, NULL, NULL); mlt_properties_set_data(play, "transport_callback", mlt_properties_get_data(properties, "transport_callback", NULL), 0, NULL, NULL); mlt_properties_set_data(still, "transport_callback", mlt_properties_get_data(properties, "transport_callback", NULL), 0, NULL, NULL); mlt_properties_set_int(play, "progressive", progressive); mlt_properties_set_int(still, "progressive", progressive); mlt_properties_pass_list( play, properties, "deinterlacer,deinterlace_method,resize,rescale,width,height,aspect_ratio,display_" "ratio,preview_off,preview_format,window_background" ",top_field_first,volume,real_time,buffer,prefill,audio_off,frequency,drop_max"); mlt_properties_pass_list( still, properties, "deinterlacer,deinterlace_method,resize,rescale,width,height,aspect_ratio,display_" "ratio,preview_off,preview_format,window_background" ",top_field_first"); mlt_properties_pass(play, properties, "play."); mlt_properties_pass(still, properties, "still."); mlt_properties_set_data(play, "app_lock", mlt_properties_get_data(properties, "app_lock", NULL), 0, NULL, NULL); mlt_properties_set_data(still, "app_lock", mlt_properties_get_data(properties, "app_lock", NULL), 0, NULL, NULL); mlt_properties_set_data(play, "app_unlock", mlt_properties_get_data(properties, "app_unlock", NULL), 0, NULL, NULL); mlt_properties_set_data(still, "app_unlock", mlt_properties_get_data(properties, "app_unlock", NULL), 0, NULL, NULL); mlt_properties_set_int(play, "put_mode", 1); mlt_properties_set_int(still, "put_mode", 1); mlt_properties_set_int(play, "terminate_on_pause", 1); // Start the still producer just to initialise the gui mlt_consumer_start(self->still); self->active = self->still; // Inform child consumers that we control the sdl mlt_properties_set_int(play, "sdl_started", 1); mlt_properties_set_int(still, "sdl_started", 1); pthread_create(&self->thread, NULL, consumer_thread, self); } return 0; } static int consumer_stop(mlt_consumer parent) { // Get the actual object consumer_sdl self = parent->child; if (self->joined == 0) { mlt_properties properties = MLT_CONSUMER_PROPERTIES(parent); int app_locked = mlt_properties_get_int(properties, "app_locked"); void (*lock)(void) = mlt_properties_get_data(properties, "app_lock", NULL); void (*unlock)(void) = mlt_properties_get_data(properties, "app_unlock", NULL); if (app_locked && unlock) unlock(); // Kill the thread and clean up self->running = 0; pthread_mutex_lock(&self->refresh_mutex); pthread_cond_broadcast(&self->refresh_cond); pthread_mutex_unlock(&self->refresh_mutex); #ifndef _WIN32 if (self->thread) #endif pthread_join(self->thread, NULL); self->joined = 1; if (app_locked && lock) lock(); pthread_mutex_lock(&mlt_sdl_mutex); SDL_Quit(); pthread_mutex_unlock(&mlt_sdl_mutex); } return 0; } static int consumer_is_stopped(mlt_consumer parent) { consumer_sdl self = parent->child; return !self->running; } void consumer_purge(mlt_consumer parent) { consumer_sdl self = parent->child; if (self->running) mlt_consumer_purge(self->play); } static void *consumer_thread(void *arg) { // Identify the arg consumer_sdl self = arg; // Get the consumer mlt_consumer consumer = &self->parent; // Get the properties mlt_properties properties = MLT_CONSUMER_PROPERTIES(consumer); // internal initialization mlt_frame frame = NULL; int last_position = -1; int eos = 0; int eos_threshold = 20; if (self->play) eos_threshold = eos_threshold + mlt_properties_get_int(MLT_CONSUMER_PROPERTIES(self->play), "buffer"); // Determine if the application is dealing with the preview int preview_off = mlt_properties_get_int(properties, "preview_off"); pthread_mutex_lock(&self->refresh_mutex); self->refresh_count = 0; pthread_mutex_unlock(&self->refresh_mutex); // Loop until told not to while (self->running) { // Get a frame from the attached producer frame = mlt_consumer_get_frame(consumer); // Ensure that we have a frame if (self->running && frame != NULL) { // Get the speed of the frame double speed = mlt_properties_get_double(MLT_FRAME_PROPERTIES(frame), "_speed"); // Lock during the operation mlt_service_lock(MLT_CONSUMER_SERVICE(consumer)); // Get refresh request for the current frame int refresh = mlt_properties_get_int(properties, "refresh"); // Decrement refresh and clear changed mlt_events_block(properties, properties); mlt_properties_set_int(properties, "refresh", 0); mlt_events_unblock(properties, properties); // Unlock after the operation mlt_service_unlock(MLT_CONSUMER_SERVICE(consumer)); // Set the changed property on this frame for the benefit of still mlt_properties_set_int(MLT_FRAME_PROPERTIES(frame), "refresh", refresh); // Make sure the recipient knows that this frame isn't really rendered mlt_properties_set_int(MLT_FRAME_PROPERTIES(frame), "rendered", 0); // Optimisation to reduce latency if (speed == 1.0) { if (last_position != -1 && last_position + 1 != mlt_frame_get_position(frame)) mlt_consumer_purge(self->play); last_position = mlt_frame_get_position(frame); } else { //mlt_consumer_purge( self->play ); last_position = -1; } // If we aren't playing normally, then use the still if (speed != 1) { mlt_producer producer = MLT_PRODUCER( mlt_service_get_producer(MLT_CONSUMER_SERVICE(consumer))); mlt_position duration = producer ? mlt_producer_get_playtime(producer) : -1; int pause = 0; #ifndef SKIP_WAIT_EOS if (self->active == self->play) { // Do not interrupt the play consumer near the end if (duration - self->last_position > eos_threshold) { // Get a new frame at the sought position mlt_frame_close(frame); if (producer) mlt_producer_seek(producer, self->last_position); frame = mlt_consumer_get_frame(consumer); pause = 1; } else { // Send frame with speed 0 to stop it if (frame && !mlt_consumer_is_stopped(self->play)) { mlt_consumer_put_frame(self->play, frame); frame = NULL; eos = 1; } // Check for end of stream if (mlt_consumer_is_stopped(self->play)) { // Stream has ended mlt_log_verbose(MLT_CONSUMER_SERVICE(consumer), "END OF STREAM\n"); pause = 1; eos = 0; // reset eos indicator } else { // Prevent a tight busy loop struct timespec tm = {0, 100000L}; // 100 usec nanosleep(&tm, NULL); } } } #else pause = self->active == self->play; #endif if (pause) { // Start the still consumer if (!mlt_consumer_is_stopped(self->play)) mlt_consumer_stop(self->play); self->last_speed = speed; self->active = self->still; self->ignore_change = 0; mlt_consumer_start(self->still); } // Send the frame to the active child if (frame && !eos) { mlt_properties_set_int(MLT_FRAME_PROPERTIES(frame), "refresh", 1); if (self->active) mlt_consumer_put_frame(self->active, frame); } if (pause && speed == 0.0) { mlt_events_fire(properties, "consumer-sdl-paused", mlt_event_data_none()); } } // Allow a little grace time before switching consumers on speed changes else if (self->ignore_change-- > 0 && self->active != NULL && !mlt_consumer_is_stopped(self->active)) { mlt_consumer_put_frame(self->active, frame); } // Otherwise use the normal player else { if (!mlt_consumer_is_stopped(self->still)) mlt_consumer_stop(self->still); if (mlt_consumer_is_stopped(self->play)) { self->last_speed = speed; self->active = self->play; self->ignore_change = 0; mlt_consumer_start(self->play); } if (self->play) mlt_consumer_put_frame(self->play, frame); } // Copy the rectangle info from the active consumer if (self->running && preview_off == 0 && self->active) { mlt_properties active = MLT_CONSUMER_PROPERTIES(self->active); mlt_service_lock(MLT_CONSUMER_SERVICE(consumer)); mlt_properties_set_int(properties, "rect_x", mlt_properties_get_int(active, "rect_x")); mlt_properties_set_int(properties, "rect_y", mlt_properties_get_int(active, "rect_y")); mlt_properties_set_int(properties, "rect_w", mlt_properties_get_int(active, "rect_w")); mlt_properties_set_int(properties, "rect_h", mlt_properties_get_int(active, "rect_h")); mlt_service_unlock(MLT_CONSUMER_SERVICE(consumer)); } if (self->active == self->still) { pthread_mutex_lock(&self->refresh_mutex); if (self->running && speed == 0 && self->refresh_count <= 0) { mlt_events_fire(properties, "consumer-sdl-paused", mlt_event_data_none()); pthread_cond_wait(&self->refresh_cond, &self->refresh_mutex); } self->refresh_count--; pthread_mutex_unlock(&self->refresh_mutex); } } else { if (frame) mlt_frame_close(frame); mlt_consumer_put_frame(self->active, NULL); self->running = 0; } } if (self->play) mlt_consumer_stop(self->play); if (self->still) mlt_consumer_stop(self->still); return NULL; } /** Callback to allow override of the close method. */ static void consumer_close(mlt_consumer parent) { // Get the actual object consumer_sdl self = parent->child; // Stop the consumer mlt_consumer_stop(parent); // Close the child consumers mlt_consumer_close(self->play); mlt_consumer_close(self->still); // Now clean up the rest mlt_consumer_close(parent); // Finally clean up self free(self); } mlt-7.22.0/src/modules/sdl/consumer_sdl_preview.yml000664 000000 000000 00000000263 14531534050 022361 0ustar00rootroot000000 000000 schema_version: 0.1 type: consumer identifier: sdl title: SDL version: 1 copyright: Meltytech, LLC creator: Charles Yates license: LGPLv2.1 language: en tags: - Audio - Video mlt-7.22.0/src/modules/sdl/consumer_sdl_still.c000664 000000 000000 00000051002 14531534050 021445 0ustar00rootroot000000 000000 /* * consumer_sdl_still.c -- A Simple DirectMedia Layer consumer * Copyright (C) 2003-2021 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "consumer_sdl_osx.h" extern pthread_mutex_t mlt_sdl_mutex; /** This classes definition. */ typedef struct consumer_sdl_s *consumer_sdl; struct consumer_sdl_s { struct mlt_consumer_s parent; mlt_properties properties; pthread_t thread; int joined; atomic_int running; int window_width; int window_height; int width; int height; atomic_int playing; int sdl_flags; SDL_Rect rect; uint8_t *buffer; int last_position; mlt_producer last_producer; }; /** Forward references to static functions. */ static int consumer_start(mlt_consumer parent); static int consumer_stop(mlt_consumer parent); static int consumer_is_stopped(mlt_consumer parent); static void consumer_close(mlt_consumer parent); static void *consumer_thread(void *); static int consumer_get_dimensions(int *width, int *height); /** This is what will be called by the factory - anything can be passed in via the argument, but keep it simple. */ mlt_consumer consumer_sdl_still_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { // Create the consumer object consumer_sdl this = calloc(1, sizeof(struct consumer_sdl_s)); // If no malloc'd and consumer init ok if (this != NULL && mlt_consumer_init(&this->parent, this, profile) == 0) { // Get the parent consumer object mlt_consumer parent = &this->parent; // get a handle on properties mlt_service service = MLT_CONSUMER_SERVICE(parent); this->properties = MLT_SERVICE_PROPERTIES(service); // We have stuff to clean up, so override the close method parent->close = consumer_close; // Default scaler (for now we'll use nearest) mlt_properties_set(this->properties, "rescale", "nearest"); // We're always going to run this in non-realtime mode mlt_properties_set(this->properties, "real_time", "0"); // Ensure we don't join on a non-running object this->joined = 1; // process actual param if (arg == NULL || sscanf(arg, "%dx%d", &this->width, &this->height) != 2) { this->width = mlt_properties_get_int(this->properties, "width"); this->height = mlt_properties_get_int(this->properties, "height"); } else { mlt_properties_set_int(this->properties, "width", this->width); mlt_properties_set_int(this->properties, "height", this->height); } // Set the sdl flags this->sdl_flags = SDL_HWSURFACE | SDL_ASYNCBLIT | SDL_HWACCEL | SDL_RESIZABLE | SDL_DOUBLEBUF; // Allow thread to be started/stopped parent->start = consumer_start; parent->stop = consumer_stop; parent->is_stopped = consumer_is_stopped; // Register specific events mlt_events_register(this->properties, "consumer-sdl-event"); // Return the consumer produced return parent; } // malloc or consumer init failed free(this); // Indicate failure return NULL; } static int consumer_start(mlt_consumer parent) { consumer_sdl this = parent->child; if (!this->running) { int preview_off = mlt_properties_get_int(MLT_CONSUMER_PROPERTIES(parent), "preview_off"); int sdl_started = mlt_properties_get_int(MLT_CONSUMER_PROPERTIES(parent), "sdl_started"); consumer_stop(parent); this->last_position = -1; this->running = 1; this->joined = 0; // Allow the user to force resizing to window size this->width = mlt_properties_get_int(this->properties, "width"); this->height = mlt_properties_get_int(this->properties, "height"); // Default window size double display_ratio = mlt_properties_get_double(this->properties, "display_ratio"); this->window_width = (double) this->height * display_ratio + 0.5; this->window_height = this->height; if (sdl_started == 0 && preview_off == 0) { pthread_mutex_lock(&mlt_sdl_mutex); int ret = SDL_Init(SDL_INIT_VIDEO | SDL_INIT_NOPARACHUTE); pthread_mutex_unlock(&mlt_sdl_mutex); if (ret < 0) { fprintf(stderr, "Failed to initialize SDL: %s\n", SDL_GetError()); return -1; } SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL); SDL_EnableUNICODE(1); } pthread_mutex_lock(&mlt_sdl_mutex); if (!SDL_GetVideoSurface() && preview_off == 0) SDL_SetVideoMode(this->window_width, this->window_height, 0, this->sdl_flags); pthread_mutex_unlock(&mlt_sdl_mutex); pthread_create(&this->thread, NULL, consumer_thread, this); } return 0; } static int consumer_stop(mlt_consumer parent) { // Get the actual object consumer_sdl this = parent->child; if (this->joined == 0) { int preview_off = mlt_properties_get_int(MLT_CONSUMER_PROPERTIES(parent), "preview_off"); int sdl_started = mlt_properties_get_int(MLT_CONSUMER_PROPERTIES(parent), "sdl_started"); // Kill the thread and clean up this->running = 0; pthread_join(this->thread, NULL); this->joined = 1; if (sdl_started == 0 && preview_off == 0) { pthread_mutex_lock(&mlt_sdl_mutex); SDL_Quit(); pthread_mutex_unlock(&mlt_sdl_mutex); } } return 0; } static int consumer_is_stopped(mlt_consumer parent) { consumer_sdl this = parent->child; return !this->running; } static int sdl_lock_display() { pthread_mutex_lock(&mlt_sdl_mutex); SDL_Surface *screen = SDL_GetVideoSurface(); int result = screen != NULL && (!SDL_MUSTLOCK(screen) || SDL_LockSurface(screen) >= 0); pthread_mutex_unlock(&mlt_sdl_mutex); return result; } static void sdl_unlock_display() { pthread_mutex_lock(&mlt_sdl_mutex); SDL_Surface *screen = SDL_GetVideoSurface(); if (screen != NULL && SDL_MUSTLOCK(screen)) SDL_UnlockSurface(screen); pthread_mutex_unlock(&mlt_sdl_mutex); } static inline void display_1( SDL_Surface *screen, SDL_Rect rect, uint8_t *image, int width, int height) { // Generate the affine transform scaling values if (rect.w == 0 || rect.h == 0) return; int scale_width = (width << 16) / rect.w; int scale_height = (height << 16) / rect.h; int stride = width * 4; int x, y, row_index; uint8_t *q, *row; // Constants defined for clarity and optimisation int scanlength = screen->pitch; uint8_t *start = (uint8_t *) screen->pixels + rect.y * scanlength + rect.x; uint8_t *p; // Iterate through the screen using a very basic scaling algorithm for (y = 0; y < rect.h; y++) { p = start; row_index = (32768 + scale_height * y) >> 16; row = image + stride * row_index; for (x = 0; x < rect.w; x++) { q = row + (((32768 + scale_width * x) >> 16) * 4); *p++ = SDL_MapRGB(screen->format, *q, *(q + 1), *(q + 2)); } start += scanlength; } } static inline void display_2( SDL_Surface *screen, SDL_Rect rect, uint8_t *image, int width, int height) { // Generate the affine transform scaling values if (rect.w == 0 || rect.h == 0) return; int scale_width = (width << 16) / rect.w; int scale_height = (height << 16) / rect.h; int stride = width * 4; int x, y, row_index; uint8_t *q, *row; // Constants defined for clarity and optimisation int scanlength = screen->pitch / 2; uint16_t *start = (uint16_t *) screen->pixels + rect.y * scanlength + rect.x; uint16_t *p; // Iterate through the screen using a very basic scaling algorithm for (y = 0; y < rect.h; y++) { p = start; row_index = (32768 + scale_height * y) >> 16; row = image + stride * row_index; for (x = 0; x < rect.w; x++) { q = row + (((32768 + scale_width * x) >> 16) * 4); *p++ = SDL_MapRGB(screen->format, *q, *(q + 1), *(q + 2)); } start += scanlength; } } static inline void display_3( SDL_Surface *screen, SDL_Rect rect, uint8_t *image, int width, int height) { // Generate the affine transform scaling values if (rect.w == 0 || rect.h == 0) return; int scale_width = (width << 16) / rect.w; int scale_height = (height << 16) / rect.h; int stride = width * 4; int x, y, row_index; uint8_t *q, *row; // Constants defined for clarity and optimisation int scanlength = screen->pitch; uint8_t *start = (uint8_t *) screen->pixels + rect.y * scanlength + rect.x; uint8_t *p; uint32_t pixel; // Iterate through the screen using a very basic scaling algorithm for (y = 0; y < rect.h; y++) { p = start; row_index = (32768 + scale_height * y) >> 16; row = image + stride * row_index; for (x = 0; x < rect.w; x++) { q = row + (((32768 + scale_width * x) >> 16) * 4); pixel = SDL_MapRGB(screen->format, *q, *(q + 1), *(q + 2)); *p++ = (pixel & 0xFF0000) >> 16; *p++ = (pixel & 0x00FF00) >> 8; *p++ = (pixel & 0x0000FF); } start += scanlength; } } static inline void display_4( SDL_Surface *screen, SDL_Rect rect, uint8_t *image, int width, int height) { // Generate the affine transform scaling values if (rect.w == 0 || rect.h == 0) return; int scale_width = (width << 16) / rect.w; int scale_height = (height << 16) / rect.h; int stride = width * 4; int x, y, row_index; uint8_t *q, *row; // Constants defined for clarity and optimisation int scanlength = screen->pitch / 4; uint32_t *start = (uint32_t *) screen->pixels + rect.y * scanlength + rect.x; uint32_t *p; // Iterate through the screen using a very basic scaling algorithm for (y = 0; y < rect.h; y++) { p = start; row_index = (32768 + scale_height * y) >> 16; row = image + stride * row_index; for (x = 0; x < rect.w; x++) { q = row + (((32768 + scale_width * x) >> 16) * 4); *p++ = SDL_MapRGB(screen->format, *q, *(q + 1), *(q + 2)); } start += scanlength; } } static int consumer_play_video(consumer_sdl this, mlt_frame frame) { // Get the properties of this consumer mlt_properties properties = this->properties; mlt_image_format vfmt = mlt_image_rgba; int height = this->height; int width = this->width; uint8_t *image = NULL; int changed = 0; double display_ratio = mlt_properties_get_double(this->properties, "display_ratio"); void (*lock)(void) = mlt_properties_get_data(properties, "app_lock", NULL); void (*unlock)(void) = mlt_properties_get_data(properties, "app_unlock", NULL); if (lock != NULL) lock(); void *pool = mlt_cocoa_autorelease_init(); sdl_lock_display(); // Handle events if (SDL_GetVideoSurface()) { SDL_Event event; pthread_mutex_lock(&mlt_sdl_mutex); changed = consumer_get_dimensions(&this->window_width, &this->window_height); pthread_mutex_unlock(&mlt_sdl_mutex); while (SDL_PollEvent(&event)) { mlt_events_fire(this->properties, "consumer-sdl-event", mlt_event_data_from_object(&event)); switch (event.type) { case SDL_VIDEORESIZE: this->window_width = event.resize.w; this->window_height = event.resize.h; changed = 1; break; case SDL_QUIT: this->running = 0; break; case SDL_KEYDOWN: { mlt_producer producer = mlt_properties_get_data(properties, "transport_producer", NULL); char keyboard[2] = " "; void (*callback)(mlt_producer, char *) = mlt_properties_get_data(properties, "transport_callback", NULL); if (callback != NULL && producer != NULL && event.key.keysym.unicode < 0x80 && event.key.keysym.unicode > 0) { keyboard[0] = (char) event.key.keysym.unicode; callback(producer, keyboard); } } break; } } } if (!SDL_GetVideoSurface() || changed) { // open SDL window pthread_mutex_lock(&mlt_sdl_mutex); SDL_Surface *screen = SDL_SetVideoMode(this->window_width, this->window_height, 0, this->sdl_flags); if (consumer_get_dimensions(&this->window_width, &this->window_height)) screen = SDL_SetVideoMode(this->window_width, this->window_height, 0, this->sdl_flags); if (screen) { uint32_t color = mlt_properties_get_int(this->properties, "window_background"); SDL_FillRect(screen, NULL, color >> 8); changed = 1; } pthread_mutex_unlock(&mlt_sdl_mutex); } mlt_properties_set_int(MLT_FRAME_PROPERTIES(frame), "test_audio", 1); if (changed == 0 && this->last_position == mlt_frame_get_position(frame) && this->last_producer == mlt_frame_get_original_producer(frame) && !mlt_properties_get_int(MLT_FRAME_PROPERTIES(frame), "refresh")) { sdl_unlock_display(); mlt_cocoa_autorelease_close(pool); if (unlock != NULL) unlock(); struct timespec tm = {0, 100000}; nanosleep(&tm, NULL); return 0; } // Update last frame shown info this->last_position = mlt_frame_get_position(frame); this->last_producer = mlt_frame_get_original_producer(frame); // Get the image, width and height mlt_frame_get_image(frame, &image, &vfmt, &width, &height, 0); if (image != NULL) { char *rescale = mlt_properties_get(properties, "rescale"); if (rescale != NULL && strcmp(rescale, "none")) { double this_aspect = display_ratio / ((double) this->window_width / (double) this->window_height); this->rect.w = this_aspect * this->window_width; this->rect.h = this->window_height; if (this->rect.w > this->window_width) { this->rect.w = this->window_width; this->rect.h = (1.0 / this_aspect) * this->window_height; } } else { double frame_aspect = mlt_frame_get_aspect_ratio(frame) * width / height; this->rect.w = frame_aspect * this->window_height; this->rect.h = this->window_height; if (this->rect.w > this->window_width) { this->rect.w = this->window_width; this->rect.h = (1.0 / frame_aspect) * this->window_width; } } this->rect.x = (this->window_width - this->rect.w) / 2; this->rect.y = (this->window_height - this->rect.h) / 2; mlt_properties_set_int(this->properties, "rect_x", this->rect.x); mlt_properties_set_int(this->properties, "rect_y", this->rect.y); mlt_properties_set_int(this->properties, "rect_w", this->rect.w); mlt_properties_set_int(this->properties, "rect_h", this->rect.h); } pthread_mutex_lock(&mlt_sdl_mutex); SDL_Surface *screen = SDL_GetVideoSurface(); if (!mlt_consumer_is_stopped(&this->parent) && screen && screen->pixels) { switch (screen->format->BytesPerPixel) { case 1: display_1(screen, this->rect, image, width, height); break; case 2: display_2(screen, this->rect, image, width, height); break; case 3: display_3(screen, this->rect, image, width, height); break; case 4: display_4(screen, this->rect, image, width, height); break; default: fprintf(stderr, "Unsupported video depth %d\n", screen->format->BytesPerPixel); break; } // Flip it into sight SDL_Flip(screen); } pthread_mutex_unlock(&mlt_sdl_mutex); sdl_unlock_display(); mlt_cocoa_autorelease_close(pool); if (unlock != NULL) unlock(); mlt_events_fire(properties, "consumer-frame-show", mlt_event_data_from_frame(frame)); return 1; } /** Threaded wrapper for pipe. */ static void *consumer_thread(void *arg) { // Identify the arg consumer_sdl this = arg; // Get the consumer mlt_consumer consumer = &this->parent; mlt_properties properties = MLT_CONSUMER_PROPERTIES(consumer); mlt_frame frame = NULL; // Allow the hosting app to provide the preview int preview_off = mlt_properties_get_int(properties, "preview_off"); // Loop until told not to while (this->running) { // Get a frame from the attached producer frame = mlt_consumer_rt_frame(consumer); // Ensure that we have a frame if (this->running && frame != NULL) { if (preview_off == 0) { consumer_play_video(this, frame); } else { mlt_image_format vfmt = mlt_image_rgba; int height = this->height; int width = this->width; uint8_t *image = NULL; mlt_image_format preview_format = mlt_properties_get_int(properties, "preview_format"); // Check if a specific colour space has been requested if (preview_off && preview_format != mlt_image_none) vfmt = preview_format; mlt_frame_get_image(frame, &image, &vfmt, &width, &height, 0); mlt_properties_set_int(MLT_FRAME_PROPERTIES(frame), "format", vfmt); mlt_events_fire(properties, "consumer-frame-show", mlt_event_data_from_frame(frame)); } mlt_frame_close(frame); } else { if (frame) mlt_frame_close(frame); this->running = 0; } } return NULL; } static int consumer_get_dimensions(int *width, int *height) { int changed = 0; // SDL windows manager structure SDL_SysWMinfo wm; // Specify the SDL Version SDL_VERSION(&wm.version); #ifndef __APPLE__ // Get the wm structure if (SDL_GetWMInfo(&wm) == 1) { #ifndef _WIN32 // Check that we have the X11 wm if (wm.subsystem == SDL_SYSWM_X11) { // Get the SDL window Window window = wm.info.x11.window; // Get the display session Display *display = wm.info.x11.display; // Get the window attributes XWindowAttributes attr; XGetWindowAttributes(display, window, &attr); // Determine whether window has changed changed = *width != attr.width || *height != attr.height; // Return width and height *width = attr.width; *height = attr.height; } #endif } #endif return changed; } /** Callback to allow override of the close method. */ static void consumer_close(mlt_consumer parent) { // Get the actual object consumer_sdl this = parent->child; // Stop the consumer mlt_consumer_stop(parent); // Now clean up the rest mlt_consumer_close(parent); // Finally clean up this free(this); } mlt-7.22.0/src/modules/sdl/consumer_sdl_still.yml000664 000000 000000 00000000263 14531534050 022027 0ustar00rootroot000000 000000 schema_version: 0.1 type: consumer identifier: sdl_still title: SDL RGB version: 1 copyright: Meltytech, LLC creator: Charles Yates license: LGPLv2.1 language: en tags: - Video mlt-7.22.0/src/modules/sdl/factory.c000664 000000 000000 00000006100 14531534050 017207 0ustar00rootroot000000 000000 /* * factory.c -- the factory method interfaces * Copyright (C) 2003-2018 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include extern mlt_consumer consumer_sdl_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_consumer consumer_sdl_audio_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_consumer consumer_sdl_still_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_consumer consumer_sdl_preview_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); static mlt_properties metadata(mlt_service_type type, const char *id, void *data) { char file[PATH_MAX]; snprintf(file, PATH_MAX, "%s/sdl/%s", mlt_environment("MLT_DATA"), (char *) data); return mlt_properties_parse_yaml(file); } MLT_REPOSITORY { MLT_REGISTER(mlt_service_consumer_type, "sdl", consumer_sdl_init); MLT_REGISTER_METADATA(mlt_service_consumer_type, "sdl", metadata, "consumer_sdl.yml"); MLT_REGISTER(mlt_service_consumer_type, "sdl_audio", consumer_sdl_audio_init); MLT_REGISTER_METADATA(mlt_service_consumer_type, "sdl_audio", metadata, "consumer_sdl_audio.yml"); MLT_REGISTER(mlt_service_consumer_type, "sdl_preview", consumer_sdl_preview_init); MLT_REGISTER_METADATA(mlt_service_consumer_type, "sdl_preview", metadata, "consumer_sdl_preview.yml"); MLT_REGISTER(mlt_service_consumer_type, "sdl_still", consumer_sdl_still_init); MLT_REGISTER_METADATA(mlt_service_consumer_type, "sdl_still", metadata, "consumer_sdl_still.yml"); } mlt-7.22.0/src/modules/sdl2/000775 000000 000000 00000000000 14531534050 015461 5ustar00rootroot000000 000000 mlt-7.22.0/src/modules/sdl2/CMakeLists.txt000664 000000 000000 00000001113 14531534050 020215 0ustar00rootroot000000 000000 add_library(mltsdl2 MODULE common.c consumer_sdl2_audio.c consumer_sdl2.c factory.c ) file(GLOB YML "*.yml") add_custom_target(Other_sdl2_Files SOURCES ${YML} ) target_compile_options(mltsdl2 PRIVATE ${MLT_COMPILE_OPTIONS}) target_link_libraries(mltsdl2 PRIVATE mlt m Threads::Threads PkgConfig::sdl2) set_target_properties(mltsdl2 PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${MLT_MODULE_OUTPUT_DIRECTORY}") install(TARGETS mltsdl2 LIBRARY DESTINATION ${MLT_INSTALL_MODULE_DIR}) install(FILES consumer_sdl2_audio.yml consumer_sdl2.yml DESTINATION ${MLT_INSTALL_DATA_DIR}/sdl2) mlt-7.22.0/src/modules/sdl2/common.c000664 000000 000000 00000004636 14531534050 017126 0ustar00rootroot000000 000000 /* * common.h * Copyright (C) 2018 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "common.h" #include SDL_AudioDeviceID sdl2_open_audio(const SDL_AudioSpec *desired, SDL_AudioSpec *obtained) { SDL_AudioDeviceID dev = 0; // First try to open using default/user requested driver. dev = SDL_OpenAudioDevice(NULL, 0, desired, obtained, SDL_AUDIO_ALLOW_CHANNELS_CHANGE); if (dev == 0) { mlt_log_info(NULL, "Failed to open audio device: %s\n", SDL_GetError()); // Try alternative drivers. int i = 0; int driver_count = SDL_GetNumAudioDrivers(); for (i = 0; i < driver_count; i++) { const char *driver = SDL_GetAudioDriver(i); if (strcmp(driver, "disk") == 0 || strcmp(driver, "dummy") == 0) { continue; } if (SDL_AudioInit(driver) != 0) { continue; } mlt_log_info(NULL, "[sdl2] Try alternative driver: %s\n", driver); dev = SDL_OpenAudioDevice(NULL, 0, desired, obtained, SDL_AUDIO_ALLOW_CHANNELS_CHANGE); if (dev != 0) { break; } else { mlt_log_info(NULL, "[sdl2] Open failed: %s\n", SDL_GetError()); } } } if (dev == 0 && desired->channels > 2) { // All drivers have failed to open with the provided spec. // Try stereo channels since all drivers support that. mlt_log_info(NULL, "Failed to open surround device. Try stereo instead\n"); SDL_AudioSpec desired_copy = *desired; desired_copy.channels = 2; SDL_AudioInit(NULL); dev = sdl2_open_audio(&desired_copy, obtained); } return dev; } mlt-7.22.0/src/modules/sdl2/common.h000664 000000 000000 00000001704 14531534050 017124 0ustar00rootroot000000 000000 /* * common.h * Copyright (C) 2018 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef COMMON_H #define COMMON_H #include SDL_AudioDeviceID sdl2_open_audio(const SDL_AudioSpec *desired, SDL_AudioSpec *obtained); #endif // COMMON_H mlt-7.22.0/src/modules/sdl2/consumer_sdl2.c000664 000000 000000 00000075520 14531534050 020415 0ustar00rootroot000000 000000 /* * consumer_sdl.c -- A Simple DirectMedia Layer consumer * Copyright (C) 2017-2023 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "common.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #undef MLT_IMAGE_FORMAT // only yuv422 working currently extern pthread_mutex_t mlt_sdl_mutex; /** This classes definition. */ typedef struct consumer_sdl_s *consumer_sdl; struct consumer_sdl_s { struct mlt_consumer_s parent; mlt_properties properties; mlt_deque queue; pthread_t thread; int joined; atomic_int running; uint8_t audio_buffer[4096 * 10]; int audio_avail; pthread_mutex_t audio_mutex; pthread_cond_t audio_cond; pthread_mutex_t video_mutex; pthread_cond_t video_cond; int window_width; int window_height; int previous_width; int previous_height; int width; int height; int out_channels; atomic_int playing; SDL_Window *sdl_window; SDL_Renderer *sdl_renderer; SDL_Texture *sdl_texture; SDL_Rect sdl_rect; uint8_t *buffer; int is_purge; #ifdef _WIN32 int no_quit_subsystem; #endif }; /** Forward references to static functions. */ static int consumer_start(mlt_consumer parent); static int consumer_stop(mlt_consumer parent); static int consumer_is_stopped(mlt_consumer parent); static void consumer_purge(mlt_consumer parent); static void consumer_close(mlt_consumer parent); static void *consumer_thread(void *); static int setup_sdl_video(consumer_sdl self); /** This is what will be called by the factory - anything can be passed in via the argument, but keep it simple. */ mlt_consumer consumer_sdl2_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { // Create the consumer object consumer_sdl self = calloc(1, sizeof(struct consumer_sdl_s)); // If no malloc'd and consumer init ok if (self != NULL && mlt_consumer_init(&self->parent, self, profile) == 0) { // Create the queue self->queue = mlt_deque_init(); // Get the parent consumer object mlt_consumer parent = &self->parent; // We have stuff to clean up, so override the close method parent->close = consumer_close; // get a handle on properties mlt_service service = MLT_CONSUMER_SERVICE(parent); self->properties = MLT_SERVICE_PROPERTIES(service); // Set the default volume mlt_properties_set_double(self->properties, "volume", 1.0); // This is the initialisation of the consumer pthread_mutex_init(&self->audio_mutex, NULL); pthread_cond_init(&self->audio_cond, NULL); pthread_mutex_init(&self->video_mutex, NULL); pthread_cond_init(&self->video_cond, NULL); // Default scaler (for now we'll use nearest) mlt_properties_set(self->properties, "rescale", "nearest"); mlt_properties_set(self->properties, "consumer.deinterlacer", "onefield"); mlt_properties_set_int(self->properties, "top_field_first", -1); // Default buffer for low latency mlt_properties_set_int(self->properties, "buffer", 1); // Default audio buffer mlt_properties_set_int(self->properties, "audio_buffer", 2048); // Default scrub audio mlt_properties_set_int(self->properties, "scrub_audio", 1); // Ensure we don't join on a non-running object self->joined = 1; // process actual param if (arg && sscanf(arg, "%dx%d", &self->width, &self->height)) { mlt_properties_set_int(self->properties, "resolution", 1); } else { self->width = mlt_properties_get_int(self->properties, "width"); self->height = mlt_properties_get_int(self->properties, "height"); } // Allow thread to be started/stopped parent->start = consumer_start; parent->stop = consumer_stop; parent->is_stopped = consumer_is_stopped; parent->purge = consumer_purge; // Register specific events mlt_events_register(self->properties, "consumer-sdl-event"); // Return the consumer produced return parent; } // malloc or consumer init failed free(self); // Indicate failure return NULL; } int consumer_start(mlt_consumer parent) { consumer_sdl self = parent->child; if (!self->running) { mlt_properties properties = MLT_CONSUMER_PROPERTIES(parent); int audio_off = mlt_properties_get_int(properties, "audio_off"); char *output_display = mlt_properties_get(properties, "output_display"); char *window_id = mlt_properties_get(properties, "window_id"); char *audio_driver = mlt_properties_get(properties, "audio_driver"); char *video_driver = mlt_properties_get(properties, "video_driver"); char *audio_device = mlt_properties_get(properties, "audio_device"); consumer_stop(parent); self->running = 1; self->joined = 0; if (output_display != NULL) setenv("DISPLAY", output_display, 1); if (window_id != NULL) setenv("SDL_WINDOWID", window_id, 1); if (video_driver != NULL) setenv("SDL_VIDEODRIVER", video_driver, 1); if (audio_driver != NULL) setenv("SDL_AUDIODRIVER", audio_driver, 1); if (audio_device != NULL) setenv("AUDIODEV", audio_device, 1); if (!mlt_properties_get_int(self->properties, "resolution")) { if (mlt_properties_get_int(self->properties, "width") > 0) self->width = mlt_properties_get_int(self->properties, "width"); if (mlt_properties_get_int(self->properties, "height") > 0) self->height = mlt_properties_get_int(self->properties, "height"); } if (audio_off == 0) SDL_InitSubSystem(SDL_INIT_AUDIO); // Default window size if (mlt_properties_get_int(self->properties, "resolution")) { self->window_width = self->width; self->window_height = self->height; } else { double display_ratio = mlt_properties_get_double(self->properties, "display_ratio"); self->window_width = (double) self->height * display_ratio + 0.5; self->window_height = self->height; } // Initialize SDL video if needed. if (!SDL_WasInit(SDL_INIT_VIDEO)) { pthread_mutex_lock(&mlt_sdl_mutex); int ret = SDL_Init(SDL_INIT_VIDEO); pthread_mutex_unlock(&mlt_sdl_mutex); if (ret < 0) { mlt_log_error(MLT_CONSUMER_SERVICE(&self->parent), "Failed to initialize SDL: %s\n", SDL_GetError()); return 1; } } pthread_create(&self->thread, NULL, consumer_thread, self); } return 0; } int consumer_stop(mlt_consumer parent) { // Get the actual object consumer_sdl self = parent->child; if (self->joined == 0) { // Kill the thread and clean up self->joined = 1; self->running = 0; if (!mlt_properties_get_int(MLT_CONSUMER_PROPERTIES(parent), "audio_off")) { pthread_mutex_lock(&self->audio_mutex); pthread_cond_broadcast(&self->audio_cond); pthread_mutex_unlock(&self->audio_mutex); } #ifndef _WIN32 if (self->thread) #endif pthread_join(self->thread, NULL); // cleanup SDL pthread_mutex_lock(&mlt_sdl_mutex); if (self->sdl_texture) SDL_DestroyTexture(self->sdl_texture); self->sdl_texture = NULL; if (self->sdl_renderer) SDL_DestroyRenderer(self->sdl_renderer); self->sdl_renderer = NULL; if (self->sdl_window) SDL_DestroyWindow(self->sdl_window); self->sdl_window = NULL; #ifdef _WIN32 if (!self->no_quit_subsystem) #endif if (!mlt_properties_get_int(MLT_CONSUMER_PROPERTIES(parent), "audio_off")) SDL_QuitSubSystem(SDL_INIT_AUDIO); if (mlt_properties_get_int(MLT_CONSUMER_PROPERTIES(parent), "sdl_started") == 0) SDL_Quit(); pthread_mutex_unlock(&mlt_sdl_mutex); } return 0; } int consumer_is_stopped(mlt_consumer parent) { consumer_sdl self = parent->child; return !self->running; } void consumer_purge(mlt_consumer parent) { consumer_sdl self = parent->child; if (self->running) { pthread_mutex_lock(&self->video_mutex); while (mlt_deque_count(self->queue)) mlt_frame_close(mlt_deque_pop_back(self->queue)); self->is_purge = 1; pthread_cond_broadcast(&self->video_cond); pthread_mutex_unlock(&self->video_mutex); } } static void sdl_fill_audio(void *udata, uint8_t *stream, int len) { consumer_sdl self = udata; // Get the volume double volume = mlt_properties_get_double(self->properties, "volume"); // Wipe the stream first memset(stream, 0, len); pthread_mutex_lock(&self->audio_mutex); // Block until audio received while (self->running && len > self->audio_avail) pthread_cond_wait(&self->audio_cond, &self->audio_mutex); if (self->audio_avail >= len) { // Place in the audio buffer if (volume != 1.0) SDL_MixAudio(stream, self->audio_buffer, len, (int) ((float) SDL_MIX_MAXVOLUME * volume)); else memcpy(stream, self->audio_buffer, len); // Remove len from the audio available self->audio_avail -= len; // Remove the samples memmove(self->audio_buffer, self->audio_buffer + len, self->audio_avail); } else { // Mix the audio SDL_MixAudio(stream, self->audio_buffer, len, (int) ((float) SDL_MIX_MAXVOLUME * volume)); // No audio left self->audio_avail = 0; } // We're definitely playing now self->playing = 1; pthread_cond_broadcast(&self->audio_cond); pthread_mutex_unlock(&self->audio_mutex); } static int consumer_play_audio(consumer_sdl self, mlt_frame frame, int init_audio, int *duration) { // Get the properties of self consumer mlt_properties properties = self->properties; mlt_audio_format afmt = mlt_audio_s16; // Set the preferred params of the test card signal int channels = mlt_properties_get_int(properties, "channels"); int frequency = mlt_properties_get_int(properties, "frequency"); int scrub = mlt_properties_get_int(properties, "scrub_audio"); static int counter = 0; int samples = mlt_audio_calculate_frame_samples(mlt_properties_get_double(self->properties, "fps"), frequency, counter++); int16_t *pcm; mlt_frame_get_audio(frame, (void **) &pcm, &afmt, &frequency, &channels, &samples); *duration = ((samples * 1000) / frequency); pcm += mlt_properties_get_int(properties, "audio_offset"); if (mlt_properties_get_int(properties, "audio_off")) { self->playing = 1; init_audio = 1; return init_audio; } if (init_audio == 1) { SDL_AudioSpec request; SDL_AudioSpec got; SDL_AudioDeviceID dev; int audio_buffer = mlt_properties_get_int(properties, "audio_buffer"); // specify audio format memset(&request, 0, sizeof(SDL_AudioSpec)); self->playing = 0; request.freq = frequency; request.format = AUDIO_S16SYS; request.channels = mlt_properties_get_int(properties, "channels"); request.samples = audio_buffer; request.callback = sdl_fill_audio; request.userdata = (void *) self; dev = sdl2_open_audio(&request, &got); if (dev == 0) { mlt_log_error(MLT_CONSUMER_SERVICE(self), "SDL failed to open audio\n"); init_audio = 2; } else { if (got.channels != request.channels) { mlt_log_info(MLT_CONSUMER_SERVICE(self), "Unable to output %d channels. Change to %d\n", request.channels, got.channels); } mlt_log_info(MLT_CONSUMER_SERVICE(self), "Audio Opened: driver=%s channels=%d frequency=%d\n", SDL_GetCurrentAudioDriver(), got.channels, got.freq); SDL_PauseAudioDevice(dev, 0); init_audio = 0; self->out_channels = got.channels; } } if (init_audio == 0) { mlt_properties properties = MLT_FRAME_PROPERTIES(frame); int samples_copied = 0; int dst_stride = self->out_channels * sizeof(*pcm); pthread_mutex_lock(&self->audio_mutex); while (self->running && samples_copied < samples) { int sample_space = (sizeof(self->audio_buffer) - self->audio_avail) / dst_stride; while (self->running && sample_space == 0) { struct timeval now; struct timespec tm; gettimeofday(&now, NULL); tm.tv_sec = now.tv_sec + 1; tm.tv_nsec = now.tv_usec * 1000; pthread_cond_timedwait(&self->audio_cond, &self->audio_mutex, &tm); sample_space = (sizeof(self->audio_buffer) - self->audio_avail) / dst_stride; if (sample_space == 0 && self->running) { mlt_log_warning(MLT_CONSUMER_SERVICE(&self->parent), "audio timed out\n"); pthread_mutex_unlock(&self->audio_mutex); #ifdef _WIN32 self->no_quit_subsystem = 1; #endif return 1; } } if (self->running) { int samples_to_copy = samples - samples_copied; if (samples_to_copy > sample_space) { samples_to_copy = sample_space; } int dst_bytes = samples_to_copy * dst_stride; if (scrub || mlt_properties_get_double(properties, "_speed") == 1) { if (channels == self->out_channels) { memcpy(&self->audio_buffer[self->audio_avail], pcm, dst_bytes); pcm += samples_to_copy * channels; } else { int16_t *dest = (int16_t *) &self->audio_buffer[self->audio_avail]; int i = samples_to_copy + 1; while (--i) { memcpy(dest, pcm, dst_stride); pcm += channels; dest += self->out_channels; } } } else { memset(&self->audio_buffer[self->audio_avail], 0, dst_bytes); pcm += samples_to_copy * channels; } self->audio_avail += dst_bytes; samples_copied += samples_to_copy; } pthread_cond_broadcast(&self->audio_cond); } pthread_mutex_unlock(&self->audio_mutex); } else { self->playing = 1; } return init_audio; } static int setup_sdl_video(consumer_sdl self) { int error = 0; int sdl_flags = SDL_WINDOW_RESIZABLE; int texture_format = SDL_PIXELFORMAT_YUY2; // Skip this if video is disabled. int video_off = mlt_properties_get_int(self->properties, "video_off"); int preview_off = mlt_properties_get_int(self->properties, "preview_off"); if (video_off || preview_off) return error; #ifdef MLT_IMAGE_FORMAT int image_format = mlt_properties_get_int(self->properties, "mlt_image_format"); if (image_format) switch (image_format) { case mlt_image_rgb: texture_format = SDL_PIXELFORMAT_RGB24; break; case mlt_image_rgba: texture_format = SDL_PIXELFORMAT_ABGR8888; break; case mlt_image_yuv420p: texture_format = SDL_PIXELFORMAT_IYUV; break; case mlt_image_yuv422: texture_format = SDL_PIXELFORMAT_YUY2; break; default: mlt_log_error(MLT_CONSUMER_SERVICE(&self->parent), "Invalid image format %s\n", mlt_image_format_name(image_format)); return -1; } #endif if (mlt_properties_get_int(self->properties, "fullscreen")) { self->window_width = self->width; self->window_height = self->height; sdl_flags |= SDL_WINDOW_FULLSCREEN_DESKTOP; SDL_ShowCursor(SDL_DISABLE); } pthread_mutex_lock(&mlt_sdl_mutex); self->sdl_window = SDL_CreateWindow("MLT", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, self->window_width, self->window_height, sdl_flags); self->sdl_renderer = SDL_CreateRenderer(self->sdl_window, -1, SDL_RENDERER_ACCELERATED); if (self->sdl_renderer) { // Get texture width and height from the profile. int width = mlt_properties_get_int(self->properties, "width"); int height = mlt_properties_get_int(self->properties, "height"); self->sdl_texture = SDL_CreateTexture(self->sdl_renderer, texture_format, SDL_TEXTUREACCESS_STREAMING, width, height); if (self->sdl_texture) { SDL_SetRenderDrawColor(self->sdl_renderer, 0, 0, 0, 255); } else { mlt_log_error(MLT_CONSUMER_SERVICE(&self->parent), "Failed to create SDL texture: %s\n", SDL_GetError()); error = -1; } } else { mlt_log_error(MLT_CONSUMER_SERVICE(&self->parent), "Failed to create SDL renderer: %s\n", SDL_GetError()); error = -1; } pthread_mutex_unlock(&mlt_sdl_mutex); return error; } static int consumer_play_video(consumer_sdl self, mlt_frame frame) { // Get the properties of this consumer mlt_properties properties = self->properties; #ifdef MLT_IMAGE_FORMAT mlt_image_format vfmt = mlt_properties_get_int(properties, "mlt_image_format"); #else mlt_image_format vfmt = mlt_image_yuv422; #endif int width = self->width, height = self->height; uint8_t *image; int video_off = mlt_properties_get_int(properties, "video_off"); int preview_off = mlt_properties_get_int(properties, "preview_off"); int display_off = video_off | preview_off; if (self->running && !display_off) { if (!self->sdl_window) { int error = setup_sdl_video(self); if (error) return error; } // Get the image, width and height mlt_frame_get_image(frame, &image, &vfmt, &width, &height, 0); if (self->running) { // Determine window's new display aspect ratio int x = mlt_properties_get_int(properties, "window_width"); if (x && x != self->window_width) self->window_width = x; x = mlt_properties_get_int(properties, "window_height"); if (x && x != self->window_height) self->window_height = x; double this_aspect = (double) self->window_width / self->window_height; // Get the display aspect ratio double display_ratio = mlt_properties_get_double(properties, "display_ratio"); // Determine frame's display aspect ratio double frame_aspect = mlt_frame_get_aspect_ratio(frame) * width / height; // Store the width and height received self->width = width; self->height = height; // If using hardware scaler if (mlt_properties_get(properties, "rescale") != NULL && !strcmp(mlt_properties_get(properties, "rescale"), "none")) { // Use hardware scaler to normalize display aspect ratio self->sdl_rect.w = frame_aspect / this_aspect * self->window_width; self->sdl_rect.h = self->window_height; if (self->sdl_rect.w > self->window_width) { self->sdl_rect.w = self->window_width; self->sdl_rect.h = this_aspect / frame_aspect * self->window_height; } } // Special case optimisation to negate odd effect of sample aspect ratio // not corresponding exactly with image resolution. else if ((int) (this_aspect * 1000) == (int) (display_ratio * 1000)) { self->sdl_rect.w = self->window_width; self->sdl_rect.h = self->window_height; } // Use hardware scaler to normalize sample aspect ratio else if (self->window_height * display_ratio > self->window_width) { self->sdl_rect.w = self->window_width; self->sdl_rect.h = self->window_width / display_ratio; } else { self->sdl_rect.w = self->window_height * display_ratio; self->sdl_rect.h = self->window_height; } self->sdl_rect.x = (self->window_width - self->sdl_rect.w) / 2; self->sdl_rect.y = (self->window_height - self->sdl_rect.h) / 2; self->sdl_rect.x -= self->sdl_rect.x % 2; mlt_properties_set_int(self->properties, "rect_x", self->sdl_rect.x); mlt_properties_set_int(self->properties, "rect_y", self->sdl_rect.y); mlt_properties_set_int(self->properties, "rect_w", self->sdl_rect.w); mlt_properties_set_int(self->properties, "rect_h", self->sdl_rect.h); } if (self->running && image) { unsigned char *planes[4]; int strides[4]; mlt_image_format_planes(vfmt, width, height, image, planes, strides); if (strides[1]) { SDL_UpdateYUVTexture(self->sdl_texture, NULL, planes[0], strides[0], planes[1], strides[1], planes[2], strides[2]); } else { SDL_UpdateTexture(self->sdl_texture, NULL, planes[0], strides[0]); } SDL_RenderClear(self->sdl_renderer); SDL_RenderCopy(self->sdl_renderer, self->sdl_texture, NULL, &self->sdl_rect); SDL_RenderPresent(self->sdl_renderer); } mlt_events_fire(properties, "consumer-frame-show", mlt_event_data_from_frame(frame)); } else if (self->running) { if (!video_off) { mlt_image_format preview_format = mlt_properties_get_int(properties, "preview_format"); vfmt = preview_format == mlt_image_none ? mlt_image_rgba : preview_format; mlt_frame_get_image(frame, &image, &vfmt, &width, &height, 0); } mlt_events_fire(properties, "consumer-frame-show", mlt_event_data_from_frame(frame)); } return 0; } static void *video_thread(void *arg) { // Identify the arg consumer_sdl self = arg; // Obtain time of thread start struct timeval now; int64_t start = 0; int64_t elapsed = 0; struct timespec tm; mlt_frame next = NULL; mlt_properties properties = NULL; double speed = 0; // Get real time flag int real_time = mlt_properties_get_int(self->properties, "real_time"); // Determine start time gettimeofday(&now, NULL); start = (int64_t) now.tv_sec * 1000000 + now.tv_usec; while (self->running) { // Pop the next frame pthread_mutex_lock(&self->video_mutex); next = mlt_deque_pop_front(self->queue); while (next == NULL && self->running) { pthread_cond_wait(&self->video_cond, &self->video_mutex); next = mlt_deque_pop_front(self->queue); } pthread_mutex_unlock(&self->video_mutex); if (!self->running || next == NULL) break; // Get the properties properties = MLT_FRAME_PROPERTIES(next); // Get the speed of the frame speed = mlt_properties_get_double(properties, "_speed"); // Get the current time gettimeofday(&now, NULL); // Get the elapsed time elapsed = ((int64_t) now.tv_sec * 1000000 + now.tv_usec) - start; // See if we have to delay the display of the current frame if (mlt_properties_get_int(properties, "rendered") == 1 && self->running) { // Obtain the scheduled playout time int64_t scheduled = mlt_properties_get_int(properties, "playtime"); // Determine the difference between the elapsed time and the scheduled playout time int64_t difference = scheduled - elapsed; // Smooth playback a bit if (real_time && (difference > 20000 && speed == 1.0)) { tm.tv_sec = difference / 1000000; tm.tv_nsec = (difference % 1000000) * 500; nanosleep(&tm, NULL); } // Show current frame if not too old if (!real_time || (difference > -10000 || speed != 1.0 || mlt_deque_count(self->queue) < 2)) consumer_play_video(self, next); // If the queue is empty, recalculate start to allow build up again if (real_time && (mlt_deque_count(self->queue) == 0 && speed == 1.0)) { gettimeofday(&now, NULL); start = ((int64_t) now.tv_sec * 1000000 + now.tv_usec) - scheduled + 20000; } } else { static int dropped = 0; mlt_log_info(MLT_CONSUMER_SERVICE(&self->parent), "dropped video frame %d\n", ++dropped); } // This frame can now be closed mlt_frame_close(next); next = NULL; } if (next != NULL) mlt_frame_close(next); mlt_consumer_stopped(&self->parent); return NULL; } static void *consumer_thread(void *arg) { // Identify the arg consumer_sdl self = arg; // Get the consumer mlt_consumer consumer = &self->parent; // Convenience functionality int terminate_on_pause = mlt_properties_get_int(MLT_CONSUMER_PROPERTIES(consumer), "terminate_on_pause"); int terminated = 0; // Video thread pthread_t thread; // internal initialization int init_audio = 1; int init_video = 1; mlt_frame frame = NULL; int duration = 0; int64_t playtime = 0; struct timespec tm = {0, 100000}; // Loop until told not to while (self->running) { // Get a frame from the attached producer frame = !terminated ? mlt_consumer_rt_frame(consumer) : NULL; // Check for termination if (terminate_on_pause && frame) terminated = mlt_properties_get_double(MLT_FRAME_PROPERTIES(frame), "_speed") == 0.0; // Ensure that we have a frame if (frame) { // Play audio init_audio = consumer_play_audio(self, frame, init_audio, &duration); // Determine the start time now if (self->playing && init_video) { // Create the video thread pthread_create(&thread, NULL, video_thread, self); // Video doesn't need to be initialised any more init_video = 0; } // Set playtime for this frame mlt_properties_set_int(MLT_FRAME_PROPERTIES(frame), "playtime", playtime); while (self->running && mlt_deque_count(self->queue) > 15) nanosleep(&tm, NULL); // Push this frame to the back of the queue pthread_mutex_lock(&self->video_mutex); if (self->is_purge) { mlt_frame_close(frame); frame = NULL; self->is_purge = 0; } else { mlt_deque_push_back(self->queue, frame); pthread_cond_broadcast(&self->video_cond); } pthread_mutex_unlock(&self->video_mutex); // Calculate the next playtime playtime += (duration * 1000); } else if (terminated) { if (init_video || mlt_deque_count(self->queue) == 0) break; else nanosleep(&tm, NULL); } } self->running = 0; // Unblock sdl_preview if (mlt_properties_get_int(MLT_CONSUMER_PROPERTIES(consumer), "put_mode") && mlt_properties_get_int(MLT_CONSUMER_PROPERTIES(consumer), "put_pending")) { frame = mlt_consumer_get_frame(consumer); if (frame) mlt_frame_close(frame); frame = NULL; } // Kill the video thread if (init_video == 0) { pthread_mutex_lock(&self->video_mutex); pthread_cond_broadcast(&self->video_cond); pthread_mutex_unlock(&self->video_mutex); pthread_join(thread, NULL); } while (mlt_deque_count(self->queue)) mlt_frame_close(mlt_deque_pop_back(self->queue)); pthread_mutex_lock(&self->audio_mutex); self->audio_avail = 0; pthread_mutex_unlock(&self->audio_mutex); return NULL; } static void consumer_close(mlt_consumer parent) { // Get the actual object consumer_sdl self = parent->child; // Stop the consumer ///mlt_consumer_stop( parent ); // Now clean up the rest mlt_consumer_close(parent); // Close the queue mlt_deque_close(self->queue); // Destroy mutexes pthread_mutex_destroy(&self->audio_mutex); pthread_cond_destroy(&self->audio_cond); // Finally clean up this free(self); } mlt-7.22.0/src/modules/sdl2/consumer_sdl2.yml000664 000000 000000 00000002632 14531534050 020766 0ustar00rootroot000000 000000 schema_version: 0.3 type: consumer identifier: sdl2 title: SDL2 version: 1 copyright: Meltytech, LLC creator: Dan Dennedy license: LGPLv2.1 language: en tags: - Audio - Video description: > Simple DirectMedia Layer audio and video output module parameters: - identifier: resolution title: Resolution type: string description: The size of the window as WxH pixels argument: yes required: no - identifier: volume title: Volume type: float description: Audio level factor mutable: yes - identifier: video_off title: Video off type: boolean description: Disable video output mutable: yes default: 0 widget: checkbox - identifier: audio_off title: Audio off type: boolean description: Disable audio output mutable: yes default: 0 widget: checkbox - identifier: audio_buffer title: Audio buffer type: integer description: Size of the SDL audio buffer mutable: yes default: 2048 minimum: 128 - identifier: scrub_audio title: Audio scrubbing type: boolean description: Play sound even when the speed is not normal. mutable: yes default: 1 widget: checkbox - identifier: terminate_on_pause title: Stop automatically type: boolean description: > Whether to stop playback at the end of the producer or when playback is paused. default: 0 widget: checkbox mlt-7.22.0/src/modules/sdl2/consumer_sdl2_audio.c000664 000000 000000 00000056422 14531534050 021576 0ustar00rootroot000000 000000 /* * consumer_sdl2_audio.c -- A Simple DirectMedia Layer audio-only consumer * Copyright (C) 2009-2021 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "common.h" #include #include #include #include #include #include #include #include #include #include #include #include #include extern pthread_mutex_t mlt_sdl_mutex; /** This classes definition. */ typedef struct consumer_sdl_s *consumer_sdl; struct consumer_sdl_s { struct mlt_consumer_s parent; mlt_properties properties; mlt_deque queue; pthread_t thread; int joined; atomic_int running; uint8_t audio_buffer[4096 * 10]; int audio_avail; pthread_mutex_t audio_mutex; pthread_cond_t audio_cond; pthread_mutex_t video_mutex; pthread_cond_t video_cond; int out_channels; atomic_int playing; pthread_cond_t refresh_cond; pthread_mutex_t refresh_mutex; int refresh_count; int is_purge; #ifdef _WIN32 int no_quit_subsystem; #endif }; /** Forward references to static functions. */ static int consumer_start(mlt_consumer parent); static int consumer_stop(mlt_consumer parent); static int consumer_is_stopped(mlt_consumer parent); static void consumer_purge(mlt_consumer parent); static void consumer_close(mlt_consumer parent); static void *consumer_thread(void *); static void consumer_refresh_cb(mlt_consumer sdl, mlt_consumer self, mlt_event_data); /** This is what will be called by the factory - anything can be passed in via the argument, but keep it simple. */ mlt_consumer consumer_sdl2_audio_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { // Create the consumer object consumer_sdl self = calloc(1, sizeof(struct consumer_sdl_s)); // If no malloc'd and consumer init ok if (self != NULL && mlt_consumer_init(&self->parent, self, profile) == 0) { // Create the queue self->queue = mlt_deque_init(); // Get the parent consumer object mlt_consumer parent = &self->parent; // We have stuff to clean up, so override the close method parent->close = consumer_close; // get a handle on properties mlt_service service = MLT_CONSUMER_SERVICE(parent); self->properties = MLT_SERVICE_PROPERTIES(service); // Set the default volume mlt_properties_set_double(self->properties, "volume", 1.0); // This is the initialisation of the consumer pthread_mutex_init(&self->audio_mutex, NULL); pthread_cond_init(&self->audio_cond, NULL); pthread_mutex_init(&self->video_mutex, NULL); pthread_cond_init(&self->video_cond, NULL); // Default scaler (for now we'll use nearest) mlt_properties_set(self->properties, "rescale", "nearest"); mlt_properties_set(self->properties, "consumer.deinterlacer", "onefield"); mlt_properties_set_int(self->properties, "top_field_first", -1); // Default buffer for low latency mlt_properties_set_int(self->properties, "buffer", 1); // Default audio buffer mlt_properties_set_int(self->properties, "audio_buffer", 2048); // Ensure we don't join on a non-running object self->joined = 1; // Allow thread to be started/stopped parent->start = consumer_start; parent->stop = consumer_stop; parent->is_stopped = consumer_is_stopped; parent->purge = consumer_purge; // Initialize the refresh handler pthread_cond_init(&self->refresh_cond, NULL); pthread_mutex_init(&self->refresh_mutex, NULL); mlt_events_listen(MLT_CONSUMER_PROPERTIES(parent), self, "property-changed", (mlt_listener) consumer_refresh_cb); // Return the consumer produced return parent; } // malloc or consumer init failed free(self); // Indicate failure return NULL; } static void consumer_refresh_cb(mlt_consumer sdl, mlt_consumer parent, mlt_event_data event_data) { const char *name = mlt_event_data_to_string(event_data); if (name && !strcmp(name, "refresh")) { consumer_sdl self = parent->child; pthread_mutex_lock(&self->refresh_mutex); if (self->refresh_count < 2) self->refresh_count = self->refresh_count <= 0 ? 1 : self->refresh_count + 1; pthread_cond_broadcast(&self->refresh_cond); pthread_mutex_unlock(&self->refresh_mutex); } } int consumer_start(mlt_consumer parent) { consumer_sdl self = parent->child; if (!self->running) { consumer_stop(parent); mlt_properties properties = MLT_CONSUMER_PROPERTIES(parent); char *audio_driver = mlt_properties_get(properties, "audio_driver"); char *audio_device = mlt_properties_get(properties, "audio_device"); if (audio_driver && strcmp(audio_driver, "")) setenv("SDL_AUDIODRIVER", audio_driver, 1); if (audio_device && strcmp(audio_device, "")) setenv("AUDIODEV", audio_device, 1); pthread_mutex_lock(&mlt_sdl_mutex); int ret = SDL_Init(SDL_INIT_AUDIO | SDL_INIT_NOPARACHUTE); pthread_mutex_unlock(&mlt_sdl_mutex); if (ret < 0) { mlt_log_error(MLT_CONSUMER_SERVICE(parent), "Failed to initialize SDL: %s\n", SDL_GetError()); return -1; } self->running = 1; self->joined = 0; pthread_create(&self->thread, NULL, consumer_thread, self); } return 0; } int consumer_stop(mlt_consumer parent) { // Get the actual object consumer_sdl self = parent->child; if (self->running && !self->joined) { // Kill the thread and clean up self->joined = 1; self->running = 0; // Unlatch the consumer thread pthread_mutex_lock(&self->refresh_mutex); pthread_cond_broadcast(&self->refresh_cond); pthread_mutex_unlock(&self->refresh_mutex); // Cleanup the main thread #ifndef _WIN32 if (self->thread) #endif pthread_join(self->thread, NULL); // Unlatch the video thread pthread_mutex_lock(&self->video_mutex); pthread_cond_broadcast(&self->video_cond); pthread_mutex_unlock(&self->video_mutex); // Unlatch the audio callback pthread_mutex_lock(&self->audio_mutex); pthread_cond_broadcast(&self->audio_cond); pthread_mutex_unlock(&self->audio_mutex); #ifdef _WIN32 if (!self->no_quit_subsystem) #endif SDL_QuitSubSystem(SDL_INIT_AUDIO); } return 0; } int consumer_is_stopped(mlt_consumer parent) { consumer_sdl self = parent->child; return !self->running; } void consumer_purge(mlt_consumer parent) { consumer_sdl self = parent->child; if (self->running) { pthread_mutex_lock(&self->video_mutex); mlt_frame frame = MLT_FRAME(mlt_deque_peek_back(self->queue)); // When playing rewind or fast forward then we need to keep one // frame in the queue to prevent playback stalling. double speed = frame ? mlt_properties_get_double(MLT_FRAME_PROPERTIES(frame), "_speed") : 0; int n = (speed == 0.0 || speed == 1.0) ? 0 : 1; while (mlt_deque_count(self->queue) > n) mlt_frame_close(mlt_deque_pop_back(self->queue)); self->is_purge = 1; pthread_cond_broadcast(&self->video_cond); pthread_mutex_unlock(&self->video_mutex); } } static void sdl_fill_audio(void *udata, uint8_t *stream, int len) { consumer_sdl self = udata; // Get the volume double volume = mlt_properties_get_double(self->properties, "volume"); // Wipe the stream first memset(stream, 0, len); pthread_mutex_lock(&self->audio_mutex); int bytes = MIN(len, self->audio_avail); // Place in the audio buffer if (volume != 1.0) { // Adjust the volume while copying. int16_t *src = (int16_t *) self->audio_buffer; int16_t *dst = (int16_t *) stream; int i = bytes / sizeof(*dst) + 1; while (--i) { *dst++ = CLAMP(volume * src[0], -32768, 32767); src++; } } else { memcpy(stream, self->audio_buffer, bytes); } // Remove len from the audio available self->audio_avail -= bytes; // Remove the samples memmove(self->audio_buffer, self->audio_buffer + bytes, self->audio_avail); // We're definitely playing now self->playing = 1; pthread_cond_broadcast(&self->audio_cond); pthread_mutex_unlock(&self->audio_mutex); } static int consumer_play_audio(consumer_sdl self, mlt_frame frame, int init_audio, int64_t *duration) { // Get the properties of self consumer mlt_properties properties = self->properties; mlt_audio_format afmt = mlt_audio_s16; // Set the preferred params of the test card signal int channels = mlt_properties_get_int(properties, "channels"); int frequency = mlt_properties_get_int(properties, "frequency"); int scrub = mlt_properties_get_int(properties, "scrub_audio"); static int counter = 0; int samples = mlt_audio_calculate_frame_samples(mlt_properties_get_double(self->properties, "fps"), frequency, counter++); int16_t *pcm; mlt_frame_get_audio(frame, (void **) &pcm, &afmt, &frequency, &channels, &samples); *duration = 1000000LL * samples / frequency; pcm += mlt_properties_get_int(properties, "audio_offset"); if (mlt_properties_get_int(properties, "audio_off")) { self->playing = 1; init_audio = 1; return init_audio; } if (init_audio == 1) { SDL_AudioSpec request; SDL_AudioSpec got; SDL_AudioDeviceID dev; int audio_buffer = mlt_properties_get_int(properties, "audio_buffer"); // specify audio format memset(&request, 0, sizeof(SDL_AudioSpec)); self->playing = 0; request.freq = frequency; request.format = AUDIO_S16SYS; request.channels = mlt_properties_get_int(properties, "channels"); request.samples = audio_buffer; request.callback = sdl_fill_audio; request.userdata = (void *) self; dev = sdl2_open_audio(&request, &got); if (dev == 0) { mlt_log_error(MLT_CONSUMER_SERVICE(self), "SDL failed to open audio\n"); init_audio = 2; } else { if (got.channels != request.channels) { mlt_log_info(MLT_CONSUMER_SERVICE(self), "Unable to output %d channels. Change to %d\n", request.channels, got.channels); } mlt_log_info(MLT_CONSUMER_SERVICE(self), "Audio Opened: driver=%s channels=%d frequency=%d\n", SDL_GetCurrentAudioDriver(), got.channels, got.freq); SDL_PauseAudioDevice(dev, 0); init_audio = 0; self->out_channels = got.channels; } } if (init_audio == 0) { mlt_properties properties = MLT_FRAME_PROPERTIES(frame); int samples_copied = 0; int dst_stride = self->out_channels * sizeof(*pcm); pthread_mutex_lock(&self->audio_mutex); while (self->running && samples_copied < samples) { int sample_space = (sizeof(self->audio_buffer) - self->audio_avail) / dst_stride; while (self->running && sample_space == 0) { struct timeval now; struct timespec tm; gettimeofday(&now, NULL); tm.tv_sec = now.tv_sec + 1; tm.tv_nsec = now.tv_usec * 1000; pthread_cond_timedwait(&self->audio_cond, &self->audio_mutex, &tm); sample_space = (sizeof(self->audio_buffer) - self->audio_avail) / dst_stride; if (sample_space == 0) { mlt_log_warning(MLT_CONSUMER_SERVICE(&self->parent), "audio timed out\n"); pthread_mutex_unlock(&self->audio_mutex); #ifdef _WIN32 self->no_quit_subsystem = 1; #endif return 1; } } if (self->running) { int samples_to_copy = samples - samples_copied; if (samples_to_copy > sample_space) { samples_to_copy = sample_space; } int dst_bytes = samples_to_copy * dst_stride; if (scrub || mlt_properties_get_double(properties, "_speed") == 1) { if (channels == self->out_channels) { memcpy(&self->audio_buffer[self->audio_avail], pcm, dst_bytes); pcm += samples_to_copy * channels; } else { int16_t *dest = (int16_t *) &self->audio_buffer[self->audio_avail]; int i = samples_to_copy + 1; while (--i) { memcpy(dest, pcm, dst_stride); pcm += channels; dest += self->out_channels; } } } else { memset(&self->audio_buffer[self->audio_avail], 0, dst_bytes); pcm += samples_to_copy * channels; } self->audio_avail += dst_bytes; samples_copied += samples_to_copy; } pthread_cond_broadcast(&self->audio_cond); } pthread_mutex_unlock(&self->audio_mutex); } else { self->playing = 1; } return init_audio; } static int consumer_play_video(consumer_sdl self, mlt_frame frame) { // Get the properties of this consumer mlt_properties properties = self->properties; mlt_events_fire(properties, "consumer-frame-show", mlt_event_data_from_frame(frame)); return 0; } static void *video_thread(void *arg) { // Identify the arg consumer_sdl self = arg; // Obtain time of thread start struct timeval now; int64_t start = 0; int64_t elapsed = 0; struct timespec tm; mlt_frame next = NULL; mlt_properties properties = NULL; double speed = 0; // Get real time flag int real_time = mlt_properties_get_int(self->properties, "real_time"); // Get the current time gettimeofday(&now, NULL); // Determine start time start = (int64_t) now.tv_sec * 1000000 + now.tv_usec; while (self->running) { // Pop the next frame pthread_mutex_lock(&self->video_mutex); next = mlt_deque_pop_front(self->queue); while (next == NULL && self->running) { pthread_cond_wait(&self->video_cond, &self->video_mutex); next = mlt_deque_pop_front(self->queue); } pthread_mutex_unlock(&self->video_mutex); if (!self->running || next == NULL) break; // Get the properties properties = MLT_FRAME_PROPERTIES(next); // Get the speed of the frame speed = mlt_properties_get_double(properties, "_speed"); // Get the current time gettimeofday(&now, NULL); // Get the elapsed time elapsed = ((int64_t) now.tv_sec * 1000000 + now.tv_usec) - start; // See if we have to delay the display of the current frame if (mlt_properties_get_int(properties, "rendered") == 1) { // Obtain the scheduled playout time in microseconds int64_t scheduled = mlt_properties_get_int64(properties, "playtime"); // Determine the difference between the elapsed time and the scheduled playout time int64_t difference = scheduled - elapsed; // Smooth playback a bit if (real_time && (difference > 20000 && speed == 1.0)) { tm.tv_sec = difference / 1000000; tm.tv_nsec = (difference % 1000000) * 1000; nanosleep(&tm, NULL); } // Show current frame if not too old if (!real_time || (difference > -10000 || speed != 1.0 || mlt_deque_count(self->queue) < 2)) consumer_play_video(self, next); // If the queue is empty, recalculate start to allow build up again if (real_time && (mlt_deque_count(self->queue) == 0 && speed == 1.0)) { gettimeofday(&now, NULL); start = ((int64_t) now.tv_sec * 1000000 + now.tv_usec) - scheduled + 20000; start += mlt_properties_get_int(self->properties, "video_delay") * 1000; } } // This frame can now be closed mlt_frame_close(next); next = NULL; } // This consumer is stopping. But audio has already been played for all // the frames in the queue. Spit out all the frames so that the display has // the option to catch up with the audio. if (next != NULL) { consumer_play_video(self, next); mlt_frame_close(next); next = NULL; } while (mlt_deque_count(self->queue) > 0) { next = mlt_deque_pop_front(self->queue); consumer_play_video(self, next); mlt_frame_close(next); next = NULL; } mlt_consumer_stopped(&self->parent); return NULL; } /** Threaded wrapper for pipe. */ static void *consumer_thread(void *arg) { // Identify the arg consumer_sdl self = arg; // Get the consumer mlt_consumer consumer = &self->parent; // Get the properties mlt_properties consumer_props = MLT_CONSUMER_PROPERTIES(consumer); // Video thread pthread_t thread; // internal initialization int init_audio = 1; int init_video = 1; mlt_frame frame = NULL; mlt_properties properties = NULL; int64_t duration = 0; int64_t playtime = mlt_properties_get_int(consumer_props, "video_delay") * 1000; struct timespec tm = {0, 100000}; // int last_position = -1; pthread_mutex_lock(&self->refresh_mutex); self->refresh_count = 0; pthread_mutex_unlock(&self->refresh_mutex); // Loop until told not to while (self->running) { // Get a frame from the attached producer frame = mlt_consumer_rt_frame(consumer); // Ensure that we have a frame if (frame) { // Get the frame properties properties = MLT_FRAME_PROPERTIES(frame); // Get the speed of the frame double speed = mlt_properties_get_double(properties, "_speed"); // Clear refresh mlt_events_block(consumer_props, consumer_props); mlt_properties_set_int(consumer_props, "refresh", 0); mlt_events_unblock(consumer_props, consumer_props); // Play audio init_audio = consumer_play_audio(self, frame, init_audio, &duration); // Determine the start time now if (self->playing && init_video) { // Create the video thread pthread_create(&thread, NULL, video_thread, self); // Video doesn't need to be initialised any more init_video = 0; } // Set playtime for this frame in microseconds mlt_properties_set_int64(properties, "playtime", playtime); while (self->running && speed != 0 && mlt_deque_count(self->queue) > 15) nanosleep(&tm, NULL); // Push this frame to the back of the queue if (self->running && speed) { pthread_mutex_lock(&self->video_mutex); if (self->is_purge && speed == 1.0) { mlt_frame_close(frame); frame = NULL; self->is_purge = 0; } else { mlt_deque_push_back(self->queue, frame); pthread_cond_broadcast(&self->video_cond); } pthread_mutex_unlock(&self->video_mutex); // Calculate the next playtime playtime += duration; } else if (self->running) { pthread_mutex_lock(&self->refresh_mutex); consumer_play_video(self, frame); mlt_frame_close(frame); frame = NULL; self->refresh_count--; if (self->refresh_count <= 0) { pthread_cond_wait(&self->refresh_cond, &self->refresh_mutex); } pthread_mutex_unlock(&self->refresh_mutex); } // Optimisation to reduce latency if (speed == 1.0) { // TODO: disabled due to misbehavior on parallel-consumer // if ( last_position != -1 && last_position + 1 != mlt_frame_get_position( frame ) ) // mlt_consumer_purge( consumer ); // last_position = mlt_frame_get_position( frame ); } else if (speed == 0.0) { mlt_consumer_purge(consumer); } } } // Kill the video thread if (init_video == 0) { pthread_mutex_lock(&self->video_mutex); pthread_cond_broadcast(&self->video_cond); pthread_mutex_unlock(&self->video_mutex); pthread_join(thread, NULL); } if (frame) { // The video thread has cleared out the queue. But the audio was played // for this frame. So play the video before stopping so the display has // the option to catch up with the audio. consumer_play_video(self, frame); mlt_frame_close(frame); frame = NULL; } pthread_mutex_lock(&self->audio_mutex); self->audio_avail = 0; pthread_mutex_unlock(&self->audio_mutex); return NULL; } /** Callback to allow override of the close method. */ static void consumer_close(mlt_consumer parent) { // Get the actual object consumer_sdl self = parent->child; // Stop the consumer mlt_consumer_stop(parent); // Now clean up the rest mlt_consumer_close(parent); // Close the queue mlt_deque_close(self->queue); // Destroy mutexes pthread_mutex_destroy(&self->audio_mutex); pthread_cond_destroy(&self->audio_cond); pthread_mutex_destroy(&self->video_mutex); pthread_cond_destroy(&self->video_cond); pthread_mutex_destroy(&self->refresh_mutex); pthread_cond_destroy(&self->refresh_cond); // Finally clean up this free(self); } mlt-7.22.0/src/modules/sdl2/consumer_sdl2_audio.yml000664 000000 000000 00000002117 14531534050 022145 0ustar00rootroot000000 000000 schema_version: 0.3 type: consumer identifier: sdl2_audio title: SDL2 Audio Only version: 2 copyright: Meltytech, LLC creator: Dan Dennedy license: LGPLv2.1 language: en tags: - Audio description: > Simple DirectMedia Layer audio only output module. parameters: - identifier: volume title: Volume type: float description: Audio level factor. mutable: yes - identifier: audio_off title: Audio off type: integer description: If 1, disable audio output mutable: yes minimum: 0 maximum: 1 default: 0 widget: checkbox - identifier: audio_buffer title: Audio buffer type: integer description: Size of the sdl audio buffer. mutable: yes default: 2048 minimum: 128 - identifier: scrub_audio title: Audio scrubbing type: integer description: If enabled, sound is played even when the speed is not normal. mutable: yes minimum: 0 maximum: 1 default: 0 widget: checkbox - identifier: video_delay title: Video delay mutable: no type: integer unit: milliseconds default: 0 mlt-7.22.0/src/modules/sdl2/factory.c000664 000000 000000 00000004077 14531534050 017304 0ustar00rootroot000000 000000 /* * factory.c -- the factory method interfaces * Copyright (C) 2018-2022 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include extern mlt_consumer consumer_sdl2_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_consumer consumer_sdl2_audio_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); static mlt_properties metadata(mlt_service_type type, const char *id, void *data) { char file[PATH_MAX]; snprintf(file, PATH_MAX, "%s/sdl2/%s", mlt_environment("MLT_DATA"), (char *) data); return mlt_properties_parse_yaml(file); } MLT_REPOSITORY { MLT_REGISTER(mlt_service_consumer_type, "sdl2", consumer_sdl2_init); MLT_REGISTER_METADATA(mlt_service_consumer_type, "sdl2", metadata, "consumer_sdl2.yml"); MLT_REGISTER(mlt_service_consumer_type, "sdl2_audio", consumer_sdl2_audio_init); MLT_REGISTER_METADATA(mlt_service_consumer_type, "sdl2_audio", metadata, "consumer_sdl2_audio.yml"); } mlt-7.22.0/src/modules/sox/000775 000000 000000 00000000000 14531534050 015426 5ustar00rootroot000000 000000 mlt-7.22.0/src/modules/sox/CMakeLists.txt000664 000000 000000 00000001136 14531534050 020167 0ustar00rootroot000000 000000 add_library(mltsox MODULE factory.c filter_sox.c) file(GLOB YML "*.yml") add_custom_target(Other_sox_Files SOURCES ${YML} ) target_compile_options(mltsox PRIVATE ${MLT_COMPILE_OPTIONS}) target_link_libraries(mltsox PRIVATE mlt m PkgConfig::sox) if(${sox_VERSION} GREATER 13) target_compile_definitions(mltsox PRIVATE SOX14) endif() set_target_properties(mltsox PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${MLT_MODULE_OUTPUT_DIRECTORY}") install(TARGETS mltsox LIBRARY DESTINATION ${MLT_INSTALL_MODULE_DIR}) install(FILES filter_sox_effect.yml filter_sox.yml DESTINATION ${MLT_INSTALL_DATA_DIR}/sox) mlt-7.22.0/src/modules/sox/factory.c000664 000000 000000 00000006207 14531534050 017246 0ustar00rootroot000000 000000 /* * factory.c -- the factory method interfaces * Copyright (C) 2003-2014 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #ifdef SOX14 #include #endif extern mlt_filter filter_sox_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); static mlt_properties metadata(mlt_service_type type, const char *id, void *data) { char file[PATH_MAX]; mlt_properties result = NULL; // Load the yaml file snprintf(file, PATH_MAX, "%s/sox/filter_%s.yml", mlt_environment("MLT_DATA"), strcmp(id, "sox") ? "sox_effect" : "sox"); result = mlt_properties_parse_yaml(file); #ifdef SOX14 if (result && (type == mlt_service_filter_type) && strcmp(id, "sox")) { // Annotate the yaml properties with sox effect usage. mlt_properties params = mlt_properties_get_data(result, "parameters", NULL); const sox_effect_handler_t *e; int i; for (i = 0; sox_effect_fns[i]; i++) { e = sox_effect_fns[i](); if (e && e->name && !strcmp(e->name, id + 4)) { mlt_properties p = mlt_properties_get_data(params, "0", NULL); mlt_properties_set(result, "identifier", e->name); mlt_properties_set(result, "title", e->name); mlt_properties_set(p, "type", "string"); mlt_properties_set(p, "title", "Options"); if (e->usage) mlt_properties_set(p, "format", e->usage); break; } } } #endif return result; } MLT_REPOSITORY { MLT_REGISTER(mlt_service_filter_type, "sox", filter_sox_init); MLT_REGISTER_METADATA(mlt_service_filter_type, "sox", metadata, NULL); #ifdef SOX14 int i; const sox_effect_handler_t *e; char name[64] = "sox."; for (i = 0; sox_effect_fns[i]; i++) { e = sox_effect_fns[i](); if (e && e->name && !(e->flags & SOX_EFF_DEPRECATED) #if (SOX_LIB_VERSION_CODE >= SOX_LIB_VERSION(14, 3, 0)) && !(e->flags & SOX_EFF_INTERNAL) #endif ) { strcpy(name + 4, e->name); MLT_REGISTER(mlt_service_filter_type, name, filter_sox_init); MLT_REGISTER_METADATA(mlt_service_filter_type, name, metadata, NULL); } } #endif } mlt-7.22.0/src/modules/sox/filter_sox.c000664 000000 000000 00000044663 14531534050 017765 0ustar00rootroot000000 000000 /* * filter_sox.c -- apply any number of SOX effects using libst * Copyright (C) 2003-2014 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include #include // TODO: does not support multiple effects with SoX v14.1.0+ #ifdef SOX14 #include #define ST_EOF SOX_EOF #define ST_SUCCESS SOX_SUCCESS #define st_sample_t sox_sample_t #define eff_t sox_effect_t * #define ST_LIB_VERSION_CODE SOX_LIB_VERSION_CODE #define ST_LIB_VERSION SOX_LIB_VERSION #if (ST_LIB_VERSION_CODE >= ST_LIB_VERSION(14, 2, 0)) #define st_size_t size_t #else #define st_size_t sox_size_t #endif #define ST_SIGNED_WORD_TO_SAMPLE(d, clips) SOX_SIGNED_16BIT_TO_SAMPLE(d, clips) #if (ST_LIB_VERSION_CODE >= ST_LIB_VERSION(14, 1, 0)) #define ST_SSIZE_MIN SOX_SAMPLE_MIN #else #define ST_SSIZE_MIN SOX_SSIZE_MIN #endif #define ST_SAMPLE_TO_SIGNED_WORD(d, clips) SOX_SAMPLE_TO_SIGNED_16BIT(d, clips) #else #include #endif #define BUFFER_LEN 8192 #define AMPLITUDE_NORM 0.2511886431509580 /* -12dBFS */ #define AMPLITUDE_MIN 0.00001 #define DBFSTOAMP(x) pow(10, (x) / 20.0) /** Compute the mean of a set of doubles skipping unset values flagged as -1 */ static inline double mean(double *buf, int count) { double mean = 0; int i; int j = 0; for (i = 0; i < count; i++) { if (buf[i] != -1.0) { mean += buf[i]; j++; } } if (j > 0) mean /= j; return mean; } #if (ST_LIB_VERSION_CODE >= ST_LIB_VERSION(14, 1, 0)) static void delete_effect(eff_t effp) { free(effp->priv); free((void *) effp->in_encoding); free(effp); } #endif /** Create an effect state instance for a channels */ static int create_effect(mlt_filter this, char *value, int count, int channel, int frequency) { mlt_tokeniser tokeniser = mlt_tokeniser_init(); char id[256]; int error = 1; // Tokenise the effect specification mlt_tokeniser_parse_new(tokeniser, value, " "); if (tokeniser->count < 1) { mlt_tokeniser_close(tokeniser); return error; } // Locate the effect mlt_destructor effect_destructor = mlt_pool_release; #ifdef SOX14 //fprintf(stderr, "%s: effect %s count %d\n", __FUNCTION__, tokeniser->tokens[0], tokeniser->count ); #if (ST_LIB_VERSION_CODE >= ST_LIB_VERSION(14, 1, 0)) sox_effect_handler_t const *eff_handle = sox_find_effect(tokeniser->tokens[0]); if (eff_handle == NULL) return error; eff_t eff = sox_create_effect(eff_handle); effect_destructor = (mlt_destructor) delete_effect; sox_encodinginfo_t *enc = calloc(1, sizeof(sox_encodinginfo_t)); enc->encoding = SOX_ENCODING_SIGN2; enc->bits_per_sample = 16; eff->in_encoding = eff->out_encoding = enc; #else eff_t eff = mlt_pool_alloc(sizeof(sox_effect_t)); sox_create_effect(eff, sox_find_effect(tokeniser->tokens[0])); #endif int opt_count = tokeniser->count - 1; #else eff_t eff = mlt_pool_alloc(sizeof(struct st_effect)); int opt_count = st_geteffect_opt(eff, tokeniser->count, tokeniser->tokens); #endif // If valid effect if (opt_count != ST_EOF) { // Supply the effect parameters #ifdef SOX14 #if (ST_LIB_VERSION_CODE >= ST_LIB_VERSION(14, 2, 0)) if (sox_effect_options(eff, opt_count, &tokeniser->tokens[tokeniser->count > 1 ? 1 : 0]) == ST_SUCCESS) #else if ((*eff->handler.getopts)(eff, opt_count, &tokeniser->tokens[tokeniser->count > 1 ? 1 : 0]) == ST_SUCCESS) #endif #else if ((*eff->h->getopts)(eff, opt_count, &tokeniser->tokens[tokeniser->count - opt_count]) == ST_SUCCESS) #endif { // Set the sox signal parameters #if (ST_LIB_VERSION_CODE >= ST_LIB_VERSION(14, 1, 0)) eff->in_signal.rate = frequency; eff->out_signal.rate = frequency; eff->in_signal.channels = 1; eff->out_signal.channels = 1; eff->in_signal.precision = 16; eff->out_signal.precision = 16; eff->in_signal.length = 0; eff->out_signal.length = 0; #else eff->ininfo.rate = frequency; eff->outinfo.rate = frequency; eff->ininfo.channels = 1; eff->outinfo.channels = 1; #endif // Start the effect #ifdef SOX14 if ((*eff->handler.start)(eff) == ST_SUCCESS) #else if ((*eff->h->start)(eff) == ST_SUCCESS) #endif { // Construct id sprintf(id, "_effect_%d_%d", count, channel); // Save the effect state mlt_properties_set_data(MLT_FILTER_PROPERTIES(this), id, eff, 0, effect_destructor, NULL); error = 0; } } } // Some error occurred so delete the temp effect state if (error == 1) effect_destructor(eff); mlt_tokeniser_close(tokeniser); return error; } /** Get the audio. */ static int filter_get_audio(mlt_frame frame, void **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples) { #if (ST_LIB_VERSION_CODE >= ST_LIB_VERSION(14, 3, 0)) SOX_SAMPLE_LOCALS; #endif // Get the filter service mlt_filter filter = mlt_frame_pop_audio(frame); // Get the filter properties mlt_properties filter_properties = MLT_FILTER_PROPERTIES(filter); mlt_service_lock(MLT_FILTER_SERVICE(filter)); // Get the properties st_sample_t *input_buffer; // = mlt_properties_get_data( filter_properties, "input_buffer", NULL ); st_sample_t *output_buffer = mlt_properties_get_data(filter_properties, "output_buffer", NULL); int i; // channel int count = mlt_properties_get_int(filter_properties, "_effect_count"); int analysis = mlt_properties_get(filter_properties, "effect") && !strcmp(mlt_properties_get(filter_properties, "effect"), "analysis"); // Get the producer's audio *format = mlt_audio_s32; mlt_frame_get_audio(frame, buffer, format, frequency, channels, samples); // Even though some effects are multi-channel aware, it is not reliable // We must maintain a separate effect state for each channel for (i = 0; i < *channels; i++) { char id[256]; sprintf(id, "_effect_0_%d", i); // Get an existing effect state eff_t e = mlt_properties_get_data(filter_properties, id, NULL); // Validate the existing effect state #if (ST_LIB_VERSION_CODE >= ST_LIB_VERSION(14, 1, 0)) if (e != NULL && (e->in_signal.rate != *frequency || e->out_signal.rate != *frequency)) #else if (e != NULL && (e->ininfo.rate != *frequency || e->outinfo.rate != *frequency)) #endif e = NULL; // (Re)Create the effect state if (e == NULL) { int j = 0; // Reset the count count = 0; // Loop over all properties for (j = 0; j < mlt_properties_count(filter_properties); j++) { // Get the name of this property char *name = mlt_properties_get_name(filter_properties, j); // If the name does not contain a . and matches effect if (!strncmp(name, "effect", 6)) { // Get the effect specification char *value = mlt_properties_get_value(filter_properties, j); // Create an instance if (create_effect(filter, value, count, i, *frequency) == 0) count++; } } // Save the number of filters mlt_properties_set_int(filter_properties, "_effect_count", count); } if (*samples > 0 && (count > 0 || analysis)) { input_buffer = (st_sample_t *) *buffer + i * *samples; st_sample_t *p = input_buffer; st_size_t isamp = *samples; st_size_t osamp = *samples; int j = *samples + 1; int normalize = mlt_properties_get_int(filter_properties, "normalize") || mlt_properties_get_int(filter_properties, "normalise"); double normalized_gain = 1.0; if (analysis) { // Run analysis to compute a gain level to normalize the audio across entire filter duration double max_power = mlt_properties_get_double(filter_properties, "_max_power"); double peak = mlt_properties_get_double(filter_properties, "_max_peak"); double use_peak = mlt_properties_get_int(filter_properties, "use_peak"); double power = 0; int n = *samples + 1; // Compute power level of samples in this channel of this frame while (--n) { double s = abs(*p++); // Track peak if (s > peak) { peak = s; mlt_properties_set_double(filter_properties, "_max_peak", peak); } power += s * s; } power /= *samples; // Track maximum power if (power > max_power) { max_power = power; mlt_properties_set_double(filter_properties, "_max_power", max_power); } // Complete analysis the last channel of the last frame. if (i + 1 == *channels && mlt_filter_get_position(filter, frame) + 1 == mlt_filter_get_length2(filter, frame)) { double rms = sqrt(max_power / ST_SSIZE_MIN / ST_SSIZE_MIN); char effect[32]; // Convert RMS or peak to gain if (use_peak) normalized_gain = ST_SSIZE_MIN / -peak; else { double gain = DBFSTOAMP(-12); // default -12 dBFS char *p = mlt_properties_get(filter_properties, "analysis_level"); if (p) { gain = mlt_properties_get_double(filter_properties, "analysis_level"); if (strstr(p, "dB")) gain = DBFSTOAMP(gain); } normalized_gain = gain / rms; } // Set properties for serialization snprintf(effect, sizeof(effect), "vol %f", normalized_gain); effect[31] = 0; mlt_properties_set(filter_properties, "effect", effect); mlt_properties_set(filter_properties, "analyze", NULL); // Show output comparable to normalize --no-adjust --fractions mlt_properties_set_double(filter_properties, "level", rms); mlt_properties_set_double(filter_properties, "gain", normalized_gain); mlt_properties_set_double(filter_properties, "peak", -peak / ST_SSIZE_MIN); } // restore some variables p = input_buffer; } if (normalize) { int window = mlt_properties_get_int(filter_properties, "window"); double *smooth_buffer = mlt_properties_get_data(filter_properties, "smooth_buffer", NULL); double max_gain = mlt_properties_get_double(filter_properties, "max_gain"); double rms = 0; // Default the maximum gain factor to 20dBFS if (max_gain == 0) max_gain = 10.0; // Compute rms amplitude while (--j) { rms += (double) *p * (double) *p; p++; } rms = sqrt(rms / *samples / ST_SSIZE_MIN / ST_SSIZE_MIN); // The smoothing buffer prevents radical shifts in the gain level if (window > 0 && smooth_buffer != NULL) { int smooth_index = mlt_properties_get_int(filter_properties, "_smooth_index"); smooth_buffer[smooth_index] = rms; // Ignore very small values that adversely affect the mean if (rms > AMPLITUDE_MIN) mlt_properties_set_int(filter_properties, "_smooth_index", (smooth_index + 1) % window); // Smoothing is really just a mean over the past N values normalized_gain = AMPLITUDE_NORM / mean(smooth_buffer, window); } else if (rms > 0) { // Determine gain to apply as current amplitude normalized_gain = AMPLITUDE_NORM / rms; } //printf("filter_sox: rms %.3f gain %.3f\n", rms, normalized_gain ); // Govern the maximum gain if (normalized_gain > max_gain) normalized_gain = max_gain; } // For each effect for (j = 0; j < count; j++) { sprintf(id, "_effect_%d_%d", j, i); e = mlt_properties_get_data(filter_properties, id, NULL); // We better have this guy if (e != NULL) { float saved_gain = 1.0; // XXX: hack to apply the normalized gain level to the vol effect #ifdef SOX14 if (normalize && strcmp(e->handler.name, "vol") == 0) #else if (normalize && strcmp(e->name, "vol") == 0) #endif { float *f = (float *) (e->priv); saved_gain = *f; *f = saved_gain * normalized_gain; } // Apply the effect #ifdef SOX14 if ((*e->handler.flow)(e, input_buffer, output_buffer, &isamp, &osamp) != ST_SUCCESS) #else if ((*e->h->flow)(e, input_buffer, output_buffer, &isamp, &osamp) != ST_SUCCESS) #endif { mlt_log_warning(MLT_FILTER_SERVICE(filter), "effect processing failed\n"); } // XXX: hack to restore the original vol gain to prevent accumulation #ifdef SOX14 if (normalize && strcmp(e->handler.name, "vol") == 0) #else if (normalize && strcmp(e->name, "vol") == 0) #endif { float *f = (float *) (e->priv); *f = saved_gain; } } } // Write back memcpy(input_buffer, output_buffer, *samples * sizeof(st_sample_t)); } } mlt_service_unlock(MLT_FILTER_SERVICE(filter)); return 0; } /** Filter processing. */ static mlt_frame filter_process(mlt_filter this, mlt_frame frame) { if (mlt_frame_is_test_audio(frame) == 0) { // Add the filter to the frame mlt_frame_push_audio(frame, this); mlt_frame_push_audio(frame, filter_get_audio); // Parse the window property and allocate smoothing buffer if needed mlt_properties properties = MLT_FILTER_PROPERTIES(this); int window = mlt_properties_get_int(properties, "window"); if (mlt_properties_get(properties, "smooth_buffer") == NULL && window > 1) { // Create a smoothing buffer for the calculated "max power" of frame of audio used in normalization double *smooth_buffer = (double *) calloc(window, sizeof(double)); int i; for (i = 0; i < window; i++) smooth_buffer[i] = -1.0; mlt_properties_set_data(properties, "smooth_buffer", smooth_buffer, 0, free, NULL); } } return frame; } /** Constructor for the filter. */ mlt_filter filter_sox_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter this = mlt_filter_new(); if (this != NULL) { void *input_buffer = mlt_pool_alloc(BUFFER_LEN); void *output_buffer = mlt_pool_alloc(BUFFER_LEN); mlt_properties properties = MLT_FILTER_PROPERTIES(this); this->process = filter_process; if (!strncmp(id, "sox.", 4)) { char *s = malloc(strlen(id) + (arg ? strlen(arg) + 2 : 1)); strcpy(s, id + 4); if (arg) { strcat(s, " "); strcat(s, arg); } mlt_properties_set(properties, "effect", s); free(s); } else if (arg) mlt_properties_set(properties, "effect", arg); mlt_properties_set_data(properties, "input_buffer", input_buffer, BUFFER_LEN, mlt_pool_release, NULL); mlt_properties_set_data(properties, "output_buffer", output_buffer, BUFFER_LEN, mlt_pool_release, NULL); mlt_properties_set_int(properties, "window", 75); mlt_properties_set(properties, "version", sox_version()); } return this; } // What to do when a libst internal failure occurs void cleanup(void) {} mlt-7.22.0/src/modules/sox/filter_sox.yml000664 000000 000000 00000005136 14531534050 020334 0ustar00rootroot000000 000000 schema_version: 0.1 type: filter identifier: sox title: SoX version: 2 copyright: Meltytech, LLC license: LGPL language: en url: http://sox.sourceforge.net/ creator: Dan Dennedy tags: - Audio description: Process audio using a SoX effect. bugs: - Some effects are stereo only, but MLT processes each channel separately. - Some effects have a temporal side-effect that do not work well. parameters: - identifier: effect argument: yes title: Effect name and options type: string format: effect [options] description: > If the effect name is "analysis" then it does not run any effect. Instead, it analyzes the audio to determine a normalized gain level. The results are put into the level, peak, and gain properties as well as this effect property as the parameter to the vol effect. - identifier: analysis_level title: Normalization level type: string default: -12dBFS description: > Normalize the volume to the specified amplitude. The normalization may be indicated as a floating point value of the relative volume with 1.0 being maximum. The normalization may also be indicated as a numeric value with the suffix "dB" to set the amplitude in decibels. - identifier: level title: Signal power level (RMS) type: float readonly: yes - identifier: peak title: Peak signal level type: float readonly: yes - identifier: gain title: Gain to normalize type: float readonly: yes - identifier: use_peak title: Use peak description: > Use peak signal level instead of RMS (root mean square) power level to compute gain for normalization. type: integer minimum: 0 maximum: 1 default: 0 widget: checkbox - identifier: normalize title: Dynamic normalization description: > This computes the gain for normalization dynamically per frame, but it uses a sliding smoothing window to prevent the gain from fluctuating wildly. Currently, this must be used in combination with some SoX effect. type: integer minimum: 0 maximum: 1 default: 0 widget: checkbox - identifier: normalise title: Dynamic normalisation (*DEPRECATED*) description: Deprecated. See normalize - identifier: window title: Smoothing window size type: integer minimum: 0 default: 75 unit: frames widget: spinner - identifier: max_gain title: Maximum gain description: > With dynamic normalization, this puts a maximum limit on the amount of gain. type: float minimum: 0 maximum: 20 default: 10 mlt-7.22.0/src/modules/sox/filter_sox_effect.yml000664 000000 000000 00000001017 14531534050 021642 0ustar00rootroot000000 000000 schema_version: 7.0 type: filter identifier: sox title: sox version: 1 copyright: Meltytech, LLC license: LGPL language: en url: http://sox.sourceforge.net/ creator: Dan Dennedy tags: - Audio description: Process audio using a SoX effect. bugs: - Some effects are stereo only, but MLT processes each channel separately. - Some effects have a temporal side-effect that do not work well. parameters: - identifier: effect argument: yes title: Effect name and options type: string format: effect [options] mlt-7.22.0/src/modules/vid.stab/000775 000000 000000 00000000000 14531534050 016327 5ustar00rootroot000000 000000 mlt-7.22.0/src/modules/vid.stab/CMakeLists.txt000664 000000 000000 00000001136 14531534050 021070 0ustar00rootroot000000 000000 add_library(mltvidstab MODULE common.c common.h factory.c filter_deshake.cpp filter_vidstab.cpp ) file(GLOB YML "*.yml") add_custom_target(Other_vidstab_Files SOURCES ${YML} ) target_compile_options(mltvidstab PRIVATE ${MLT_COMPILE_OPTIONS}) target_link_libraries(mltvidstab PRIVATE mlt m mlt++ PkgConfig::vidstab) set_target_properties(mltvidstab PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${MLT_MODULE_OUTPUT_DIRECTORY}") install(TARGETS mltvidstab LIBRARY DESTINATION ${MLT_INSTALL_MODULE_DIR}) install(FILES filter_deshake.yml filter_vidstab.yml DESTINATION ${MLT_INSTALL_DATA_DIR}/vid.stab) mlt-7.22.0/src/modules/vid.stab/common.c000664 000000 000000 00000013715 14531534050 017772 0ustar00rootroot000000 000000 /* * common.c * Copyright (C) 2014 Meltytech, LLC * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "common.h" #include #include mlt_image_format validate_format(mlt_image_format format) { switch (format) { case mlt_image_yuv420p: return mlt_image_yuv420p; default: return mlt_image_yuv422; } } /** Convert an MLT image to one that can be used by VS. * Use free_vsimage() when done with the resulting image. */ VSPixelFormat mltimage_to_vsimage( mlt_image_format mlt_format, int width, int height, uint8_t *mlt_img, uint8_t **vs_img) { switch (mlt_format) { case mlt_image_yuv420p: // This format maps with no conversion { *vs_img = mlt_img; return PF_YUV420P; } case mlt_image_yuv422: // Convert packed YUV422 to planar YUV444 // Note: vid.stab 0.98 seems to suffer chroma bleeding // when using PF_YUV422P - which is why PF_YUV444P is used. { *vs_img = mlt_pool_alloc(width * height * 3); uint8_t *yp = *vs_img; uint8_t *up = yp + (width * height); uint8_t *vp = up + (width * height); int i, j, n = width / 2 + 1; for (i = 0; i < height; i++) { j = n; while (--j) { *yp++ = mlt_img[0]; *up++ = mlt_img[1]; *vp++ = mlt_img[3]; *yp++ = mlt_img[2]; *up++ = mlt_img[1]; *vp++ = mlt_img[3]; mlt_img += 4; } if (width % 2) { *yp++ = mlt_img[0]; *up++ = mlt_img[1]; *vp++ = (mlt_img - 4)[3]; mlt_img += 2; } } return PF_YUV444P; } default: return PF_NONE; } } /** Convert a VS image back to the MLT image it originally came from in mltimage_to_vsimage(). */ void vsimage_to_mltimage( uint8_t *vs_img, uint8_t *mlt_img, mlt_image_format mlt_format, int width, int height) { switch (mlt_format) { case mlt_image_yuv420p: // This format was never converted break; case mlt_image_yuv422: // Convert planar YUV444 to packed YUV422 { uint8_t *yp = vs_img; uint8_t *up = yp + (width * height); uint8_t *vp = up + (width * height); int i, j, n = width / 2 + 1; for (i = 0; i < height; i++) { j = n; while (--j) { *mlt_img++ = yp[0]; *mlt_img++ = (up[0] + up[1]) >> 1; *mlt_img++ = yp[1]; *mlt_img++ = (vp[0] + vp[1]) >> 1; yp += 2; up += 2; vp += 2; } if (width % 2) { *mlt_img++ = yp[0]; *mlt_img++ = up[0]; yp += 1; up += 1; vp += 1; } } } break; default: break; } } /** Free an image allocated by mltimage_to_vsimage(). */ void free_vsimage(uint8_t *vs_img, VSPixelFormat format) { if (format != PF_YUV420P) { mlt_pool_release(vs_img); } } /** Compare two VSMotionDetectConfig structures. * Return 1 if they are different. 0 if they are the same. */ int compare_motion_config(VSMotionDetectConfig *a, VSMotionDetectConfig *b) { if (a->shakiness != b->shakiness || a->accuracy != b->accuracy || a->stepSize != b->stepSize || // Skip: Deprecated // a->algo != b->algo || a->virtualTripod != b->virtualTripod || a->show != b->show || // Skip: inconsequential? // a->modName != b->modName || a->contrastThreshold != b->contrastThreshold) { return 1; } return 0; } /** Compare two VSTransformConfig structures. * Return 1 if they are different. 0 if they are the same. */ int compare_transform_config(VSTransformConfig *a, VSTransformConfig *b) { if (a->relative != b->relative || a->smoothing != b->smoothing || a->crop != b->crop || a->invert != b->invert || a->zoom != b->zoom || a->optZoom != b->optZoom || a->zoomSpeed != b->zoomSpeed || a->interpolType != b->interpolType || a->maxShift != b->maxShift || a->maxAngle != b->maxAngle || // Skip: inconsequential? // a->modName != b->modName || // Skip: unused? // a->verbose != b->verbose || a->simpleMotionCalculation != b->simpleMotionCalculation || // Skip: unused? // a->storeTransforms != b->storeTransforms || a->smoothZoom != b->smoothZoom || a->camPathAlgo != b->camPathAlgo) { return 1; } return 0; } static int vs_log_wrapper(int type, const char *tag, const char *format, ...) { va_list vl; if (type > mlt_log_get_level()) return VS_OK; va_start(vl, format); fprintf(stderr, "[%s] ", tag); vfprintf(stderr, format, vl); va_end(vl); return VS_OK; } void init_vslog() { VS_ERROR_TYPE = MLT_LOG_ERROR; VS_WARN_TYPE = MLT_LOG_WARNING; VS_INFO_TYPE = MLT_LOG_INFO; VS_MSG_TYPE = MLT_LOG_VERBOSE; vs_log = vs_log_wrapper; } mlt-7.22.0/src/modules/vid.stab/common.h000664 000000 000000 00000002762 14531534050 017777 0ustar00rootroot000000 000000 /* * common.h * Copyright (C) 2013 Jakub Ksiezniak * Copyright (C) 2014 Meltytech, LLC * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef VIDSTAB_COMMON_H_ #define VIDSTAB_COMMON_H_ #include #include mlt_image_format validate_format(mlt_image_format format); VSPixelFormat mltimage_to_vsimage( mlt_image_format mlt_format, int width, int height, uint8_t *mlt_img, uint8_t **vs_img); void vsimage_to_mltimage( uint8_t *vs_img, uint8_t *mlt_img, mlt_image_format mlt_format, int width, int height); void free_vsimage(uint8_t *vs_img, VSPixelFormat format); int compare_motion_config(VSMotionDetectConfig *a, VSMotionDetectConfig *b); int compare_transform_config(VSTransformConfig *a, VSTransformConfig *b); void init_vslog(); #endif /* VIDSTAB_COMMON_H_ */ mlt-7.22.0/src/modules/vid.stab/factory.c000664 000000 000000 00000003641 14531534050 020146 0ustar00rootroot000000 000000 /* * factory.c -- the factory method interfaces * Copyright (C) 2013 Dan Dennedy * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include extern mlt_filter filter_deshake_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter filter_vidstab_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); static mlt_properties metadata(mlt_service_type type, const char *id, void *data) { char file[PATH_MAX]; snprintf(file, PATH_MAX, "%s/vid.stab/filter_%s.yml", mlt_environment("MLT_DATA"), id); return mlt_properties_parse_yaml(file); } MLT_REPOSITORY { MLT_REGISTER(mlt_service_filter_type, "deshake", filter_deshake_init); MLT_REGISTER(mlt_service_filter_type, "vidstab", filter_vidstab_init); MLT_REGISTER_METADATA(mlt_service_filter_type, "deshake", metadata, "filter_deshake.yml"); MLT_REGISTER_METADATA(mlt_service_filter_type, "vidstab", metadata, "filter_vidstab.yml"); } mlt-7.22.0/src/modules/vid.stab/filter_deshake.cpp000664 000000 000000 00000020600 14531534050 022002 0ustar00rootroot000000 000000 /* * filter_deshake.cpp * Copyright (C) 2013 Marco Gittler * Copyright (C) 2013 Jakub Ksiezniak * Copyright (C) 2014 Meltytech, LLC * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ extern "C" { #include "common.h" #include } #include #include #include typedef struct _deshake_data { bool initialized; VSMotionDetect md; VSTransformData td; VSSlidingAvgTrans avg; VSMotionDetectConfig mconf; VSTransformConfig tconf; mlt_position lastFrame; } DeshakeData; static void get_config(VSTransformConfig *tconf, VSMotionDetectConfig *mconf, mlt_filter filter, mlt_frame frame) { mlt_properties properties = MLT_FILTER_PROPERTIES(filter); const char *filterName = mlt_properties_get(properties, "mlt_service"); memset(mconf, 0, sizeof(VSMotionDetectConfig)); *mconf = vsMotionDetectGetDefaultConfig(filterName); mconf->shakiness = mlt_properties_get_int(properties, "shakiness"); mconf->accuracy = mlt_properties_get_int(properties, "accuracy"); mconf->stepSize = mlt_properties_get_int(properties, "stepsize"); mconf->contrastThreshold = mlt_properties_get_double(properties, "mincontrast"); memset(tconf, 0, sizeof(VSTransformConfig)); *tconf = vsTransformGetDefaultConfig(filterName); tconf->smoothing = mlt_properties_get_int(properties, "smoothing"); tconf->maxShift = mlt_properties_get_int(properties, "maxshift"); tconf->maxAngle = mlt_properties_get_double(properties, "maxangle"); tconf->crop = (VSBorderType) mlt_properties_get_int(properties, "crop"); tconf->zoom = mlt_properties_get_int(properties, "zoom"); tconf->optZoom = mlt_properties_get_int(properties, "optzoom"); tconf->zoomSpeed = mlt_properties_get_double(properties, "zoomspeed"); tconf->relative = 1; // by default a bicubic interpolation is selected const char *interps = mlt_properties_get(MLT_FRAME_PROPERTIES(frame), "consumer.rescale"); tconf->interpolType = VS_BiCubic; if (strcmp(interps, "nearest") == 0 || strcmp(interps, "neighbor") == 0) tconf->interpolType = VS_Zero; else if (strcmp(interps, "tiles") == 0 || strcmp(interps, "fast_bilinear") == 0) tconf->interpolType = VS_Linear; else if (strcmp(interps, "bilinear") == 0) tconf->interpolType = VS_BiLinear; } static int check_config(mlt_filter filter, mlt_frame frame) { DeshakeData *data = static_cast(filter->child); VSTransformConfig new_tconf; VSMotionDetectConfig new_mconf; get_config(&new_tconf, &new_mconf, filter, frame); if (compare_transform_config(&data->tconf, &new_tconf) || compare_motion_config(&data->mconf, &new_mconf)) { return 1; } return 0; } static void init_deshake(DeshakeData *data, mlt_filter filter, mlt_frame frame, VSPixelFormat vs_format, int *width, int *height) { VSFrameInfo fiIn, fiOut; vsFrameInfoInit(&fiIn, *width, *height, vs_format); vsFrameInfoInit(&fiOut, *width, *height, vs_format); get_config(&data->tconf, &data->mconf, filter, frame); vsMotionDetectInit(&data->md, &data->mconf, &fiIn); vsTransformDataInit(&data->td, &data->tconf, &fiIn, &fiOut); data->avg.initialized = 0; } static void clear_deshake(DeshakeData *data) { if (data->initialized) { vsMotionDetectionCleanup(&data->md); vsTransformDataCleanup(&data->td); } } static int get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { mlt_filter filter = (mlt_filter) mlt_frame_pop_service(frame); uint8_t *vs_image = NULL; VSPixelFormat vs_format = PF_NONE; // VS only works on progressive frames mlt_properties_set_int(MLT_FRAME_PROPERTIES(frame), "consumer.progressive", 1); *format = validate_format(*format); DeshakeData *data = static_cast(filter->child); int error = mlt_frame_get_image(frame, image, format, width, height, 1); // Convert the received image to a format vid.stab can handle if (!error) { vs_format = mltimage_to_vsimage(*format, *width, *height, *image, &vs_image); } if (vs_image) { // Service locks are for concurrency control mlt_service_lock(MLT_FILTER_SERVICE(filter)); // clear deshake data, when seeking or dropping frames mlt_position pos = mlt_filter_get_position(filter, frame); if (pos != data->lastFrame + 1 || check_config(filter, frame) == 1) { clear_deshake(data); data->initialized = false; } data->lastFrame = pos; if (!data->initialized) { init_deshake(data, filter, frame, vs_format, width, height); data->initialized = true; } VSMotionDetect *md = &data->md; VSTransformData *td = &data->td; LocalMotions localmotions; VSTransform motion; VSFrame vsFrame; vsFrameFillFromBuffer(&vsFrame, vs_image, &md->fi); vsMotionDetection(md, &localmotions, &vsFrame); const char *filterName = mlt_properties_get(MLT_FILTER_PROPERTIES(filter), "mlt_service"); motion = vsSimpleMotionsToTransform(md->fi, filterName, &localmotions); vs_vector_del(&localmotions); vsTransformPrepare(td, &vsFrame, &vsFrame); VSTransform t = vsLowPassTransforms(td, &data->avg, &motion); // mlt_log_warning(filter, "Trans: det: %f %f %f \n\t\t act: %f %f %f %f", // motion.x, motion.y, motion.alpha, // t.x, t.y, t.alpha, t.zoom); vsDoTransform(td, t); vsTransformFinish(td); vsimage_to_mltimage(vs_image, *image, *format, *width, *height); mlt_service_unlock(MLT_FILTER_SERVICE(filter)); free_vsimage(vs_image, vs_format); } return error; } static mlt_frame process_filter(mlt_filter filter, mlt_frame frame) { mlt_frame_push_service(frame, filter); mlt_frame_push_get_image(frame, get_image); return frame; } static void close_filter(mlt_filter filter) { DeshakeData *data = static_cast(filter->child); if (data) { clear_deshake(data); delete data; filter->child = NULL; } } extern "C" { mlt_filter filter_deshake_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = NULL; DeshakeData *data = new DeshakeData; memset(data, 0, sizeof(DeshakeData)); if ((filter = mlt_filter_new())) { filter->process = process_filter; filter->close = close_filter; filter->child = data; mlt_properties properties = MLT_FILTER_PROPERTIES(filter); //properties for stabilize mlt_properties_set(properties, "shakiness", "4"); mlt_properties_set(properties, "accuracy", "4"); mlt_properties_set(properties, "stepsize", "6"); mlt_properties_set_double(properties, "mincontrast", 0.3); //properties for transform mlt_properties_set(properties, "smoothing", "15"); mlt_properties_set(properties, "maxshift", "-1"); mlt_properties_set(properties, "maxangle", "-1"); mlt_properties_set(properties, "crop", "0"); mlt_properties_set(properties, "zoom", "0"); mlt_properties_set(properties, "optzoom", "1"); mlt_properties_set_double(properties, "zoomspeed", 0.25); init_vslog(); return filter; } delete data; return NULL; } } mlt-7.22.0/src/modules/vid.stab/filter_deshake.yml000664 000000 000000 00000006237 14531534050 022033 0ustar00rootroot000000 000000 schema_version: 0.1 type: filter identifier: deshake title: Vid.Stab Deshake copyright: Jakub Ksiezniak creator: Georg Martius version: 1 license: GPLv2 language: en url: http://public.hronopik.de/vid.stab/ tags: - Video description: Stabilize Video (for wiggly/rolling video) notes: > Deshakes a video clip by extracting relative transformations of subsequent frames and transforms the high-frequency away. This is a single pass version of the vidstab filter. parameters: - identifier: shakiness title: Shakiness type: integer description: How shaky the video is. readonly: no required: no minimum: 1 maximum: 10 default: 4 mutable: yes widget: spinner - identifier: accuracy title: Accuracy type: integer description: The accuracy of shakiness detection. readonly: no required: no minimum: 1 maximum: 15 default: 4 mutable: yes widget: spinner - identifier: stepsize title: Stepsize type: integer description: The step size of the search process. readonly: no required: no minimum: 0 maximum: 100 default: 6 mutable: yes widget: spinner - identifier: mincontrast title: Minimum Contrast type: float description: Below this contrast, a field is discarded. readonly: no required: no minimum: 0 maximum: 1 default: 0.3 mutable: yes widget: spinner - identifier: smoothing title: Smoothing type: integer description: Number of frames for lowpass filtering (2N + 1 frames) readonly: no required: no minimum: 0 maximum: 100 default: 15 mutable: yes widget: spinner - identifier: maxshift title: Maxshift type: integer description: Maximum number of pixels to transform the image. -1 = no limit unit: pixels readonly: no required: no minimum: -1 maximum: 1000 default: -1 mutable: yes widget: spinner - identifier: maxangle title: Maxangle type: float description: Maximum angle to rotate, -1 = no limit unit: radians readonly: no required: no minimum: -1 maximum: 3.142 default: -1 mutable: yes widget: spinner - identifier: crop title: Crop type: integer description: 0 = keep border, 1 = black background readonly: no required: no minimum: 0 maximum: 1 default: 0 mutable: yes widget: spinner - identifier: zoom title: Zoom type: integer description: Additional zoom amount unit: percent readonly: no required: no minimum: -500 maximum: 500 default: 0 mutable: yes widget: spinner - identifier: optzoom title: Optimal Zoom type: integer description: Automatically determine optimal zoom. 1 - static zoom, 2 - adaptive zoom readonly: no required: no minimum: 0 maximum: 2 default: 1 mutable: yes widget: spinner - identifier: zoomspeed title: Optimal Zoom Speed type: float description: Zoom per frame (used when optzoom = 2) unit: percent readonly: no required: no minimum: 0 maximum: 1 default: 0.25 mutable: yes widget: spinner mlt-7.22.0/src/modules/vid.stab/filter_vidstab.cpp000664 000000 000000 00000040126 14531534050 022037 0ustar00rootroot000000 000000 /* * filter_vidstab.cpp * Copyright (C) 2013 Marco Gittler * Copyright (C) 2013 Jakub Ksiezniak * Copyright (C) 2014-2023 Meltytech, LLC * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ extern "C" { #include "common.h" #include #include #include } #include #include #include typedef struct { VSMotionDetect md; FILE *results; mlt_position last_position; } vs_analyze; typedef struct { VSTransformData td; VSTransformConfig conf; VSTransformations trans; VSPixelFormat format; } vs_apply; typedef struct { vs_analyze *analyze_data; vs_apply *apply_data; } vs_data; static void get_transform_config(VSTransformConfig *conf, mlt_filter filter, mlt_frame frame) { mlt_properties properties = MLT_FILTER_PROPERTIES(filter); const char *filterName = mlt_properties_get(properties, "mlt_service"); *conf = vsTransformGetDefaultConfig(filterName); conf->smoothing = mlt_properties_get_int(properties, "smoothing"); conf->maxShift = mlt_properties_get_int(properties, "maxshift"); conf->maxAngle = mlt_properties_get_double(properties, "maxangle"); conf->crop = (VSBorderType) mlt_properties_get_int(properties, "crop"); conf->zoom = mlt_properties_get_int(properties, "zoom"); conf->optZoom = mlt_properties_get_int(properties, "optzoom"); conf->zoomSpeed = mlt_properties_get_double(properties, "zoomspeed"); conf->relative = mlt_properties_get_int(properties, "relative"); conf->invert = mlt_properties_get_int(properties, "invert"); if (mlt_properties_get_int(properties, "tripod") != 0) { // Virtual tripod mode: relative=False, smoothing=0 conf->relative = 0; conf->smoothing = 0; } // by default a bicubic interpolation is selected const char *interps = mlt_properties_get(MLT_FRAME_PROPERTIES(frame), "consumer.rescale"); conf->interpolType = VS_BiCubic; if (strcmp(interps, "nearest") == 0 || strcmp(interps, "neighbor") == 0) conf->interpolType = VS_Zero; else if (strcmp(interps, "tiles") == 0 || strcmp(interps, "fast_bilinear") == 0) conf->interpolType = VS_Linear; else if (strcmp(interps, "bilinear") == 0) conf->interpolType = VS_BiLinear; } static int check_apply_config(mlt_filter filter, mlt_frame frame, VSPixelFormat format) { vs_apply *apply_data = ((vs_data *) filter->child)->apply_data; if (apply_data) { VSTransformConfig new_conf; memset(&new_conf, 0, sizeof(VSTransformConfig)); get_transform_config(&new_conf, filter, frame); return format != apply_data->format || compare_transform_config(&apply_data->conf, &new_conf); } return 0; } static void destroy_apply_data(vs_apply *apply_data) { if (apply_data) { vsTransformDataCleanup(&apply_data->td); vsTransformationsCleanup(&apply_data->trans); free(apply_data); } } static void init_apply_data( mlt_filter filter, mlt_frame frame, VSPixelFormat vs_format, int width, int height) { mlt_properties properties = MLT_FILTER_PROPERTIES(filter); vs_data *data = (vs_data *) filter->child; vs_apply *apply_data = (vs_apply *) calloc(1, sizeof(vs_apply)); char *results = mlt_properties_get(properties, "results"); char *filename = mlt_properties_get(properties, "filename"); // The XML producer can convert "filename" from relative to absolute, but // it does not do the same for "results". Therefore, if both exist and // "filename" ends with "results", then use "filename" instead. if (results && filename && strlen(filename) >= strlen(results) && !strcmp(&filename[strlen(filename) - strlen(results)], results)) { filename = mlt_properties_get(properties, "filename"); } else { filename = mlt_properties_get(properties, "results"); } mlt_log_info(MLT_FILTER_SERVICE(filter), "Load results from %s\n", filename); // Initialize the VSTransformConfig memset(apply_data, 0, sizeof(vs_apply)); get_transform_config(&apply_data->conf, filter, frame); // Initialize VSTransformData VSFrameInfo fi_src, fi_dst; vsFrameInfoInit(&fi_src, width, height, vs_format); vsFrameInfoInit(&fi_dst, width, height, vs_format); vsTransformDataInit(&apply_data->td, &apply_data->conf, &fi_src, &fi_dst); apply_data->format = vs_format; // Initialize VSTransformations vsTransformationsInit(&apply_data->trans); // Load the motions from the analyze step and convert them to VSTransformations FILE *f = mlt_fopen(filename, "r"); VSManyLocalMotions mlms; if (f && vsReadLocalMotionsFile(f, &mlms) == VS_OK) { int i = 0; mlt_log_info(MLT_FILTER_SERVICE(filter), "Successfully loaded %d motions\n", vs_vector_size(&mlms)); vsLocalmotions2Transforms(&apply_data->td, &mlms, &apply_data->trans); vsPreprocessTransforms(&apply_data->td, &apply_data->trans); // Free the MultipleLocalMotions for (i = 0; i < vs_vector_size(&mlms); i++) { LocalMotions *lms = (LocalMotions *) vs_vector_get(&mlms, i); if (lms) { vs_vector_del(lms); } } vs_vector_del(&mlms); data->apply_data = apply_data; } else { mlt_log_error(MLT_FILTER_SERVICE(filter), "Can not read results file: %s\n", filename); destroy_apply_data(apply_data); data->apply_data = NULL; } if (f) { fclose(f); } } void destroy_analyze_data(vs_analyze *analyze_data) { if (analyze_data) { vsMotionDetectionCleanup(&analyze_data->md); if (analyze_data->results) { fclose(analyze_data->results); analyze_data->results = NULL; } free(analyze_data); } } static void init_analyze_data( mlt_filter filter, mlt_frame frame, VSPixelFormat vs_format, int width, int height) { mlt_properties properties = MLT_FILTER_PROPERTIES(filter); vs_data *data = (vs_data *) filter->child; vs_analyze *analyze_data = (vs_analyze *) calloc(1, sizeof(vs_analyze)); memset(analyze_data, 0, sizeof(vs_analyze)); // Initialize a VSMotionDetectConfig const char *filterName = mlt_properties_get(properties, "mlt_service"); VSMotionDetectConfig conf = vsMotionDetectGetDefaultConfig(filterName); conf.shakiness = mlt_properties_get_int(properties, "shakiness"); conf.accuracy = mlt_properties_get_int(properties, "accuracy"); conf.stepSize = mlt_properties_get_int(properties, "stepsize"); conf.contrastThreshold = mlt_properties_get_double(properties, "mincontrast"); conf.show = mlt_properties_get_int(properties, "show"); conf.virtualTripod = mlt_properties_get_int(properties, "tripod"); // Initialize a VSFrameInfo VSFrameInfo fi; vsFrameInfoInit(&fi, width, height, vs_format); // Initialize the saved VSMotionDetect vsMotionDetectInit(&analyze_data->md, &conf, &fi); #ifdef ASCII_SERIALIZATION_MODE analyze_data->md.serializationMode = ASCII_SERIALIZATION_MODE; #endif // Initialize the file to save results to char *filename = mlt_properties_get(properties, "filename"); analyze_data->results = mlt_fopen(filename, "w"); if (vsPrepareFile(&analyze_data->md, analyze_data->results) != VS_OK) { mlt_log_error(MLT_FILTER_SERVICE(filter), "Can not write to results file: %s\n", filename); destroy_analyze_data(analyze_data); data->analyze_data = NULL; } else { data->analyze_data = analyze_data; } } static int apply_results(mlt_filter filter, mlt_frame frame, uint8_t *vs_image, VSPixelFormat vs_format, int width, int height) { int error = 0; mlt_properties properties = MLT_FILTER_PROPERTIES(filter); vs_data *data = (vs_data *) filter->child; if (check_apply_config(filter, frame, vs_format) || mlt_properties_get_int(properties, "reload")) { mlt_properties_set_int(properties, "reload", 0); destroy_apply_data(data->apply_data); data->apply_data = NULL; } // Init transform data if necessary (first time) if (!data->apply_data) { init_apply_data(filter, frame, vs_format, width, height); } if (data->apply_data) { // Apply transformations to this image VSTransformData *td = &data->apply_data->td; VSTransformations *trans = &data->apply_data->trans; VSFrame vsFrame; vsFrameFillFromBuffer(&vsFrame, vs_image, vsTransformGetSrcFrameInfo(td)); trans->current = mlt_filter_get_position(filter, frame); vsTransformPrepare(td, &vsFrame, &vsFrame); VSTransform t = vsGetNextTransform(td, trans); vsDoTransform(td, t); vsTransformFinish(td); } return error; } static void analyze_image(mlt_filter filter, mlt_frame frame, uint8_t *vs_image, VSPixelFormat vs_format, int width, int height) { mlt_properties properties = MLT_FILTER_PROPERTIES(filter); vs_data *data = (vs_data *) filter->child; mlt_position pos = mlt_filter_get_position(filter, frame); // If any frames are skipped, analysis data will be incomplete. if (data->analyze_data && pos != data->analyze_data->last_position + 1) { mlt_log_error(MLT_FILTER_SERVICE(filter), "Bad frame sequence pos %d last_position %d\n", pos, data->analyze_data->last_position); destroy_analyze_data(data->analyze_data); data->analyze_data = NULL; } if (!data->analyze_data && pos == 0) { // Analysis must start on the first frame init_analyze_data(filter, frame, vs_format, width, height); } if (data->analyze_data) { // Initialize the VSFrame to be analyzed. VSMotionDetect *md = &data->analyze_data->md; LocalMotions localmotions; VSFrame vsFrame; vsFrameFillFromBuffer(&vsFrame, vs_image, &md->fi); // Detect and save motions. if (vsMotionDetection(md, &localmotions, &vsFrame) == VS_OK) { vsWriteToFile(md, data->analyze_data->results, &localmotions); vs_vector_del(&localmotions); } else { mlt_log_error(MLT_FILTER_SERVICE(filter), "Motion detection failed\n"); destroy_analyze_data(data->analyze_data); data->analyze_data = NULL; } // Publish the motions if this is the last frame. if (pos + 1 == mlt_filter_get_length2(filter, frame)) { mlt_log_info(MLT_FILTER_SERVICE(filter), "Analysis complete\n"); destroy_analyze_data(data->analyze_data); data->analyze_data = NULL; mlt_properties_set(properties, "results", mlt_properties_get(properties, "filename")); } else if (data->analyze_data) { data->analyze_data->last_position = pos; } } } static int get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { mlt_filter filter = (mlt_filter) mlt_frame_pop_service(frame); mlt_properties properties = MLT_FILTER_PROPERTIES(filter); uint8_t *vs_image = NULL; VSPixelFormat vs_format = PF_NONE; mlt_profile profile = mlt_service_profile(MLT_FILTER_SERVICE(filter)); // Disable consumer scaling if (profile && profile->width && profile->height) { *width = profile->width; *height = profile->height; } // VS only works on progressive frames mlt_properties_set_int(MLT_FRAME_PROPERTIES(frame), "consumer.progressive", 1); *format = validate_format(*format); int error = mlt_frame_get_image(frame, image, format, width, height, 1); // Convert the received image to a format vid.stab can handle if (!error) { vs_format = mltimage_to_vsimage(*format, *width, *height, *image, &vs_image); } if (vs_image) { mlt_service_lock(MLT_FILTER_SERVICE(filter)); char *results = mlt_properties_get(properties, "results"); if (results && strcmp(results, "")) { apply_results(filter, frame, vs_image, vs_format, *width, *height); vsimage_to_mltimage(vs_image, *image, *format, *width, *height); } else if (!mlt_properties_get(properties, "analyze") || mlt_properties_get_int(properties, "analyze")) { analyze_image(filter, frame, vs_image, vs_format, *width, *height); if (mlt_properties_get_int(properties, "show") == 1) { vsimage_to_mltimage(vs_image, *image, *format, *width, *height); } } mlt_service_unlock(MLT_FILTER_SERVICE(filter)); free_vsimage(vs_image, vs_format); } return error; } static mlt_frame process_filter(mlt_filter filter, mlt_frame frame) { mlt_frame_push_service(frame, filter); mlt_frame_push_get_image(frame, get_image); return frame; } static void filter_close(mlt_filter filter) { vs_data *data = (vs_data *) filter->child; if (data) { if (data->analyze_data) destroy_analyze_data(data->analyze_data); if (data->apply_data) destroy_apply_data(data->apply_data); free(data); } filter->close = NULL; filter->child = NULL; filter->parent.close = NULL; mlt_service_close(&filter->parent); } extern "C" { mlt_filter filter_vidstab_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = mlt_filter_new(); vs_data *data = (vs_data *) calloc(1, sizeof(vs_data)); if (filter && data) { data->analyze_data = NULL; data->apply_data = NULL; filter->close = filter_close; filter->child = data; filter->process = process_filter; mlt_properties properties = MLT_FILTER_PROPERTIES(filter); //properties for analyze mlt_properties_set(properties, "filename", "vidstab.trf"); mlt_properties_set(properties, "shakiness", "4"); mlt_properties_set(properties, "accuracy", "4"); mlt_properties_set(properties, "stepsize", "6"); mlt_properties_set(properties, "algo", "1"); mlt_properties_set_double(properties, "mincontrast", 0.3); mlt_properties_set(properties, "show", "0"); mlt_properties_set(properties, "tripod", "0"); // properties for apply mlt_properties_set(properties, "smoothing", "15"); mlt_properties_set(properties, "maxshift", "-1"); mlt_properties_set(properties, "maxangle", "-1"); mlt_properties_set(properties, "crop", "0"); mlt_properties_set(properties, "invert", "0"); mlt_properties_set(properties, "relative", "1"); mlt_properties_set(properties, "zoom", "0"); mlt_properties_set(properties, "optzoom", "1"); mlt_properties_set_double(properties, "zoomspeed", 0.25); mlt_properties_set(properties, "reload", "0"); mlt_properties_set(properties, "vid.stab.version", LIBVIDSTAB_VERSION); init_vslog(); } else { if (filter) { mlt_filter_close(filter); } if (data) { free(data); } filter = NULL; } return filter; } } mlt-7.22.0/src/modules/vid.stab/filter_vidstab.yml000664 000000 000000 00000014367 14531534050 022066 0ustar00rootroot000000 000000 schema_version: 0.3 type: filter identifier: vidstab title: Vid.Stab Detect and Transform copyright: Jakub Ksiezniak creator: Marco Gittler version: 2 license: GPL language: en url: http://public.hronopik.de/vid.stab/ tags: - Video description: Stabilize Video (for wiggly/rolling video) notes: > This filter requires two passes. The first pass performs analysis and stores the result in a file. Upon successful completion of the analysis, the "results" property is updated with the name of the file storing the results. The second pass applies the results to the image. To use with melt, use 'melt ... -consumer xml:output.mlt all=1 real_time=-1' for the first pass. Parallel processing (real_time < -1 or > 1) is not supported for the first pass. For the second pass, use output.mlt as the input. parameters: - identifier: results title: Analysis Results type: string description: > Set after analysis. Used during application. A set of image motion information that describes the analyzed clip. When results are not supplied, the filter computes the results and stores them in a file. This property is updated with the name of that file when the last frame has been processed. mutable: no - identifier: filename title: Target File Name type: string description: > Used during analysis. The name of the file to store the analysis results in. required: no readonly: no default: vidstab.trf widget: fileopen - identifier: shakiness title: Shakiness type: integer description: > Used during analysis. How shaky the video is. readonly: no required: no minimum: 1 maximum: 10 default: 4 mutable: no widget: spinner - identifier: accuracy title: Accuracy type: integer description: > Used during analysis. The accuracy of shakiness detection. readonly: no required: no minimum: 1 maximum: 15 default: 4 mutable: no widget: spinner - identifier: stepsize title: Stepsize type: integer description: > Used during analysis. The step size of the search process. readonly: no required: no minimum: 0 maximum: 100 default: 6 mutable: no widget: spinner - identifier: mincontrast title: Minimum Contrast type: float description: > Used during analysis. Below this contrast, a field is discarded. readonly: no required: no minimum: 0 maximum: 1 default: 0.3 mutable: no widget: spinner - identifier: show title: Show type: integer description: > Used during analysis. 0 = draw nothing 1 or 2 = show fields and transforms readonly: no required: no minimum: 0 maximum: 2 default: 0 mutable: no widget: spinner - identifier: tripod title: Tripod type: integer description: > Used during analysis and application. if 0, tripod mode is disabled. if > 0, specifies the frame to be used as a reference frame for tripod mode During application, relative and smoothing properties are both ignored if tripod mode is in use. readonly: no required: no minimum: 0 maximum: 100000 default: 0 mutable: no widget: spinner - identifier: smoothing title: Smoothing type: integer description: > Used during application. Number of frames for lowpass filtering (2N + 1 frames) readonly: no required: no minimum: 0 maximum: 100 default: 15 mutable: yes widget: spinner - identifier: maxshift title: Maxshift type: integer description: > Used during application. Maximum number of pixels to transform the image. -1 = no limit unit: pixels readonly: no required: no minimum: -1 maximum: 1000 default: -1 mutable: yes widget: spinner - identifier: maxangle title: Maxangle type: float description: > Used during application. Maximum angle to rotate, -1 = no limit unit: radians readonly: no required: no minimum: -1 maximum: 3.142 default: -1 mutable: yes widget: spinner - identifier: crop title: Crop type: boolean description: > Used during application. 0 = keep border, 1 = black background readonly: no required: no default: 0 mutable: yes - identifier: invert title: Invert type: boolean description: > Used during application. Invert transforms readonly: no required: no default: 0 mutable: yes - identifier: relative title: Relative type: boolean description: > Used during application. Consider transforms as absolute (0) or relative (1) readonly: no required: no default: 1 mutable: yes - identifier: zoom title: Zoom type: integer description: > Used during application. Additional zoom amount unit: percent readonly: no required: no minimum: -500 maximum: 500 default: 0 mutable: yes widget: spinner - identifier: optzoom title: Optimal Zoom type: integer description: > Used during application. Automatically determine optimal zoom. 1 - static zoom, 2 - adaptive zoom readonly: no required: no minimum: 0 maximum: 2 default: 1 mutable: yes widget: spinner - identifier: zoomspeed title: Optimal Zoom Speed type: float description: > Used during application. Zoom per frame (used when optzoom = 2) unit: percent readonly: no required: no minimum: 0 maximum: 1 default: 0.25 mutable: yes widget: spinner - identifier: reload title: Reload Results description: > The application should set this to 1 when it updates the results property to indicate that the results should be reloaded. type: boolean mutable: yes - identifier: analyze title: Commence analysis description: > This is optional, but it can help applications avoid race conditions writing to the file. When not set, it does not affect the start of analysis. When set, analysis only starts and the file written if true. type: boolean mlt-7.22.0/src/modules/vid.stab/gpl000664 000000 000000 00000000000 14531534050 017022 0ustar00rootroot000000 000000 mlt-7.22.0/src/modules/vorbis/000775 000000 000000 00000000000 14531534050 016121 5ustar00rootroot000000 000000 mlt-7.22.0/src/modules/vorbis/CMakeLists.txt000664 000000 000000 00000001042 14531534050 020656 0ustar00rootroot000000 000000 add_library(mltvorbis MODULE factory.c producer_vorbis.c) file(GLOB YML "*.yml") add_custom_target(Other_vorbis_Files SOURCES ${YML} ) target_compile_options(mltvorbis PRIVATE ${MLT_COMPILE_OPTIONS}) target_link_libraries(mltvorbis PRIVATE mlt PkgConfig::vorbis PkgConfig::vorbisfile) set_target_properties(mltvorbis PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${MLT_MODULE_OUTPUT_DIRECTORY}") install(TARGETS mltvorbis LIBRARY DESTINATION ${MLT_INSTALL_MODULE_DIR}) install(FILES producer_vorbis.yml DESTINATION ${MLT_INSTALL_DATA_DIR}/vorbis) mlt-7.22.0/src/modules/vorbis/factory.c000664 000000 000000 00000003060 14531534050 017733 0ustar00rootroot000000 000000 /* * factory.c -- the factory method interfaces * Copyright (C) 2003-2014 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include extern mlt_producer producer_vorbis_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); static mlt_properties metadata(mlt_service_type type, const char *id, void *data) { char file[PATH_MAX]; snprintf(file, PATH_MAX, "%s/vorbis/%s", mlt_environment("MLT_DATA"), (char *) data); return mlt_properties_parse_yaml(file); } MLT_REPOSITORY { MLT_REGISTER(mlt_service_producer_type, "vorbis", producer_vorbis_init); MLT_REGISTER_METADATA(mlt_service_producer_type, "vorbis", metadata, "producer_vorbis.yml"); } mlt-7.22.0/src/modules/vorbis/producer_vorbis.c000664 000000 000000 00000027207 14531534050 021504 0ustar00rootroot000000 000000 /* * producer_vorbis.c -- vorbis producer * Copyright (C) 2003-2017 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ // MLT Header files #include #include #include // vorbis Header files #include #include // System header files #include #include #include // Forward references. static int producer_open(mlt_producer this, mlt_profile profile, char *file); static int producer_get_frame(mlt_producer this, mlt_frame_ptr frame, int index); /** Structure for metadata reading */ typedef struct _sw_metadata sw_metadata; struct _sw_metadata { char *name; char *content; }; static sw_metadata *vorbis_metadata_from_str(char *str) { sw_metadata *meta = NULL; int i; for (i = 0; str[i]; i++) { str[i] = tolower(str[i]); if (str[i] == '=') { str[i] = '\0'; meta = malloc(sizeof(sw_metadata)); meta->name = malloc(strlen(str) + 18); sprintf(meta->name, "meta.attr.%s.markup", str); meta->content = strdup(&str[i + 1]); break; } } return meta; } /** Constructor for libvorbis. */ mlt_producer producer_vorbis_init(mlt_profile profile, mlt_service_type type, const char *id, char *file) { mlt_producer this = NULL; // Check that we have a non-NULL argument if (file != NULL) { // Construct the producer this = calloc(1, sizeof(struct mlt_producer_s)); // Initialise it if (mlt_producer_init(this, NULL) == 0) { // Get the properties mlt_properties properties = MLT_PRODUCER_PROPERTIES(this); // Set the resource property (required for all producers) mlt_properties_set(properties, "resource", file); // Register our get_frame implementation this->get_frame = producer_get_frame; // Open the file if (producer_open(this, profile, file) != 0) { // Clean up mlt_producer_close(this); this = NULL; } } } return this; } /** Destructor for ogg files. */ static void producer_file_close(void *file) { if (file != NULL) { // Close the ogg vorbis structure ov_clear(file); // Free the memory free(file); } } /** Open the file. */ static int producer_open(mlt_producer this, mlt_profile profile, char *file) { // FILE pointer for file FILE *input = mlt_fopen(file, "rb"); // Error code to return int error = input == NULL; // Continue if file is open if (error == 0) { // OggVorbis file structure OggVorbis_File *ov = calloc(1, sizeof(OggVorbis_File)); // Attempt to open the stream error = ov == NULL || ov_open(input, ov, NULL, 0) != 0; // Assign to producer properties if successful if (error == 0) { // Get the properties mlt_properties properties = MLT_PRODUCER_PROPERTIES(this); // Assign the ov structure mlt_properties_set_data(properties, "ogg_vorbis_file", ov, 0, producer_file_close, NULL); // Read metadata sw_metadata *metadata = NULL; char **ptr = ov_comment(ov, -1)->user_comments; while (*ptr) { metadata = vorbis_metadata_from_str(*ptr); if (metadata != NULL) { mlt_properties_set(properties, metadata->name, metadata->content); free(metadata->name); free(metadata->content); free(metadata); } ++ptr; } if (ov_seekable(ov)) { // Get the length of the file double length = ov_time_total(ov, -1); // We will treat everything with the producer fps double fps = mlt_profile_fps(profile); // Set out and length of file mlt_properties_set_position(properties, "out", (length * fps) - 1); mlt_properties_set_position(properties, "length", (length * fps)); // Get the vorbis info vorbis_info *vi = ov_info(ov, -1); mlt_properties_set_int(properties, "audio_frequency", (int) vi->rate); mlt_properties_set_int(properties, "audio_channels", vi->channels); // Set some media metadata mlt_properties_set_int(properties, "meta.media.nb_streams", 1); mlt_properties_set_int(properties, "audio_index", 0); mlt_properties_set(properties, "meta.media.0.stream.type", "audio"); mlt_properties_set(properties, "meta.media.0.codec.name", "vorbis"); mlt_properties_set(properties, "meta.media.0.codec.long_name", "Vorbis"); } } else { // Clean up free(ov); // Must close input file when open fails fclose(input); } } return error; } /** Convert a frame position to a time code. */ static double producer_time_of_frame(mlt_producer this, mlt_position position) { return (double) position / mlt_producer_get_fps(this); } /** Get the audio from a frame. */ static int producer_get_audio(mlt_frame frame, void **buffer, mlt_audio_format *format, int *frequency, int *channels, int *samples) { // Obtain the frame number of this frame mlt_position position = mlt_frame_original_position(frame); // Get the producer mlt_producer this = mlt_frame_pop_audio(frame); // Get the producer properties mlt_properties properties = MLT_PRODUCER_PROPERTIES(this); mlt_service_lock(MLT_PRODUCER_SERVICE(this)); // Get the ogg vorbis file OggVorbis_File *ov = mlt_properties_get_data(properties, "ogg_vorbis_file", NULL); // Obtain the expected frame number mlt_position expected = mlt_properties_get_position(properties, "audio_expected"); // Get the fps for this producer double fps = mlt_producer_get_fps(this); // Get the vorbis info vorbis_info *vi = ov_info(ov, -1); // Obtain the audio buffer int16_t *audio_buffer = mlt_properties_get_data(properties, "audio_buffer", NULL); // Get amount of audio used int audio_used = mlt_properties_get_int(properties, "audio_used"); // Number of frames to ignore (for ffwd) int ignore = 0; // Flag for paused (silence) int paused = 0; // Check for audio buffer and create if necessary if (audio_buffer == NULL) { // Allocate the audio buffer audio_buffer = mlt_pool_alloc(131072 * sizeof(int16_t)); // And store it on properties for reuse mlt_properties_set_data(properties, "audio_buffer", audio_buffer, 0, mlt_pool_release, NULL); } // Seek if necessary if (position != expected) { if (position + 1 == expected) { // We're paused - silence required paused = 1; } else if (position > expected && (position - expected) < 250) { // Fast forward - seeking is inefficient for small distances - just ignore following frames ignore = position - expected; } else { // Seek to the required position ov_time_seek(ov, producer_time_of_frame(this, position)); expected = position; audio_used = 0; } } // Return info in frame *frequency = vi->rate; *channels = vi->channels; // Get the audio if required if (!paused) { // Bitstream section int current_section; // Get the number of samples for the current frame *samples = mlt_audio_calculate_frame_samples(fps, *frequency, expected++); while (*samples > audio_used) { // Read the samples int bytes = ov_read(ov, (char *) (&audio_buffer[audio_used * 2]), 4096, 0, 2, 1, ¤t_section); // Break if error or eof if (bytes <= 0) break; // Increment number of samples used audio_used += bytes / (sizeof(int16_t) * *channels); // Handle ignore while (ignore && audio_used >= *samples) { ignore--; audio_used -= *samples; memmove(audio_buffer, &audio_buffer[*samples * *channels], audio_used * sizeof(int16_t) * *channels); *samples = mlt_audio_calculate_frame_samples(fps, *frequency, expected++); } } // Now handle the audio if we have enough if (audio_used >= *samples) { int size = *samples * *channels * sizeof(int16_t); *format = mlt_audio_s16; *buffer = mlt_pool_alloc(size); memcpy(*buffer, audio_buffer, size); audio_used -= *samples; memmove(audio_buffer, &audio_buffer[*samples * *channels], audio_used * *channels * sizeof(int16_t)); mlt_frame_set_audio(frame, *buffer, *format, size, mlt_pool_release); } else { mlt_frame_get_audio(frame, buffer, format, frequency, channels, samples); audio_used = 0; } // Store the number of audio samples still available mlt_properties_set_int(properties, "audio_used", audio_used); } else { // Get silence and don't touch the context *samples = mlt_audio_calculate_frame_samples(fps, *frequency, position); mlt_frame_get_audio(frame, buffer, format, frequency, channels, samples); } // Regardless of speed, we expect to get the next frame (cos we ain't too bright) mlt_properties_set_position(properties, "audio_expected", position + 1); mlt_service_unlock(MLT_PRODUCER_SERVICE(this)); return 0; } /** Our get frame implementation. */ static int producer_get_frame(mlt_producer this, mlt_frame_ptr frame, int index) { // Create an empty frame *frame = mlt_frame_init(MLT_PRODUCER_SERVICE(this)); // Update timecode on the frame we're creating mlt_frame_set_position(*frame, mlt_producer_position(this)); // Set up the audio mlt_frame_push_audio(*frame, this); mlt_frame_push_audio(*frame, producer_get_audio); // Pass audio properties to the frame mlt_properties_pass_list(MLT_FRAME_PROPERTIES(*frame), MLT_PRODUCER_PROPERTIES(this), "frequency, channels"); // Calculate the next timecode mlt_producer_prepare_next(this); return 0; } mlt-7.22.0/src/modules/vorbis/producer_vorbis.yml000664 000000 000000 00000000675 14531534050 022063 0ustar00rootroot000000 000000 schema_version: 7.0 type: producer identifier: vorbis title: Ogg Vorbis version: 1 copyright: Meltytech, LLC creator: Charles Yates license: LGPLv2.1 language: en tags: - Audio description: | OGG Vorbis file reader. parameters: - identifier: resource argument: yes title: File type: string description: File to use (only .ogg supported at the moment) readonly: no required: yes mutable: no widget: fileopen mlt-7.22.0/src/modules/xine/000775 000000 000000 00000000000 14531534050 015560 5ustar00rootroot000000 000000 mlt-7.22.0/src/modules/xine/CMakeLists.txt000664 000000 000000 00000002531 14531534050 020321 0ustar00rootroot000000 000000 add_library(mltxine MODULE common.c common.h factory.c deinterlace.c deinterlace.h yadif.c yadif.h filter_deinterlace.c link_deinterlace.c ) file(GLOB YML "*.yml") add_custom_target(Other_xine_Files SOURCES ${YML} ) target_compile_options(mltxine PRIVATE ${MLT_COMPILE_OPTIONS}) target_link_libraries(mltxine PRIVATE mlt ${CMAKE_DL_LIBS}) set_target_properties(mltxine PROPERTIES POSITION_INDEPENDENT_CODE ON) target_compile_definitions(mltxine PRIVATE PIC) if(CPU_MMX) target_compile_definitions(mltxine PRIVATE USE_MMX) target_sources(mltxine PRIVATE cpu_accel.c) if(CMAKE_C_COMPILER_ID MATCHES "GNU") # avoid crash in yadif filter_line_sse2 target_compile_options(mltxine PRIVATE -fno-tree-dominator-opts -fno-tree-pre) endif() endif() if(CPU_SSE) target_compile_definitions(mltxine PRIVATE USE_SSE) endif() if(CPU_SSE2) target_compile_definitions(mltxine PRIVATE USE_SSE2) endif() if(CPU_X86_32) target_compile_definitions(mltxine PRIVATE ARCH_X86) endif() if(CPU_X86_64) target_compile_definitions(mltxine PRIVATE ARCH_X86_64) endif() set_target_properties(mltxine PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${MLT_MODULE_OUTPUT_DIRECTORY}") install(TARGETS mltxine LIBRARY DESTINATION ${MLT_INSTALL_MODULE_DIR}) install(FILES filter_deinterlace.yml link_deinterlace.yml DESTINATION ${MLT_INSTALL_DATA_DIR}/xine ) mlt-7.22.0/src/modules/xine/attributes.h000664 000000 000000 00000002721 14531534050 020121 0ustar00rootroot000000 000000 /* * attributes.h * Copyright (C) 1999-2000 Aaron Holtzman * * This file is part of mpeg2dec, a free MPEG-2 video stream decoder. * * mpeg2dec is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * mpeg2dec is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ /* use gcc attribs to align critical data structures */ #ifndef ATTRIBUTE_H_ #define ATTRIBUTE_H_ #ifdef ATTRIBUTE_ALIGNED_MAX #define ATTR_ALIGN(align) __attribute__ ((__aligned__ ((ATTRIBUTE_ALIGNED_MAX < align) ? ATTRIBUTE_ALIGNED_MAX : align))) #else #define ATTR_ALIGN(align) #endif /* disable GNU __attribute__ extension, when not compiling with GNU C */ #if defined(__GNUC__) || defined (__ICC) #ifndef ATTRIBUTE_PACKED #define ATTRIBUTE_PACKED 1 #endif #else #undef ATTRIBUTE_PACKED #ifndef __attribute__ #define __attribute__(x) /**/ #endif /* __attribute __*/ #endif #endif /* ATTRIBUTE_H_ */ mlt-7.22.0/src/modules/xine/common.c000664 000000 000000 00000023112 14531534050 017213 0ustar00rootroot000000 000000 /* * common.c * Copyright (C) 2023 Meltytech, LLC * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "common.h" #include "deinterlace.h" #include "yadif.h" static yadif_filter *init_yadif(int width, int height) { yadif_filter *yadif = mlt_pool_alloc(sizeof(*yadif)); yadif->cpu = 0; // Pure C #ifdef USE_SSE yadif->cpu |= AVS_CPU_INTEGER_SSE; #endif #ifdef USE_SSE2 yadif->cpu |= AVS_CPU_SSE2; #endif // Create intermediate planar planes yadif->yheight = height; yadif->ywidth = width; yadif->uvwidth = yadif->ywidth / 2; yadif->ypitch = (yadif->ywidth + 15) / 16 * 16; yadif->uvpitch = (yadif->uvwidth + 15) / 16 * 16; yadif->ysrc = (unsigned char *) mlt_pool_alloc(yadif->yheight * yadif->ypitch); yadif->usrc = (unsigned char *) mlt_pool_alloc(yadif->yheight * yadif->uvpitch); yadif->vsrc = (unsigned char *) mlt_pool_alloc(yadif->yheight * yadif->uvpitch); yadif->yprev = (unsigned char *) mlt_pool_alloc(yadif->yheight * yadif->ypitch); yadif->uprev = (unsigned char *) mlt_pool_alloc(yadif->yheight * yadif->uvpitch); yadif->vprev = (unsigned char *) mlt_pool_alloc(yadif->yheight * yadif->uvpitch); yadif->ynext = (unsigned char *) mlt_pool_alloc(yadif->yheight * yadif->ypitch); yadif->unext = (unsigned char *) mlt_pool_alloc(yadif->yheight * yadif->uvpitch); yadif->vnext = (unsigned char *) mlt_pool_alloc(yadif->yheight * yadif->uvpitch); yadif->ydest = (unsigned char *) mlt_pool_alloc(yadif->yheight * yadif->ypitch); yadif->udest = (unsigned char *) mlt_pool_alloc(yadif->yheight * yadif->uvpitch); yadif->vdest = (unsigned char *) mlt_pool_alloc(yadif->yheight * yadif->uvpitch); return yadif; } static void close_yadif(yadif_filter *yadif) { mlt_pool_release(yadif->ysrc); mlt_pool_release(yadif->usrc); mlt_pool_release(yadif->vsrc); mlt_pool_release(yadif->yprev); mlt_pool_release(yadif->uprev); mlt_pool_release(yadif->vprev); mlt_pool_release(yadif->ynext); mlt_pool_release(yadif->unext); mlt_pool_release(yadif->vnext); mlt_pool_release(yadif->ydest); mlt_pool_release(yadif->udest); mlt_pool_release(yadif->vdest); mlt_pool_release(yadif); #if defined(__GNUC__) && !defined(PIC) // Set SSSE3 bit to cpu asm("mov $1, %%eax \n\t" "push %%ebx \n\t" "cpuid \n\t" "pop %%ebx \n\t" "mov %%ecx, %%edx \n\t" "shr $9, %%edx \n\t" "and $1, %%edx \n\t" "shl $9, %%edx \n\t" "and $511, %%ebx \n\t" "or %%edx, %%ebx \n\t" : "=b"(yadif->cpu) : "p"(yadif->cpu) : "%eax", "%ecx", "%edx"); #endif } mlt_deinterlacer supported_method(mlt_deinterlacer method) { // Round the method down to the next one that is supported if (method <= mlt_deinterlacer_onefield) method = mlt_deinterlacer_onefield; else if (method <= mlt_deinterlacer_linearblend) method = mlt_deinterlacer_linearblend; else if (method <= mlt_deinterlacer_weave) method = mlt_deinterlacer_weave; else if (method <= mlt_deinterlacer_bob) method = mlt_deinterlacer_bob; else if (method <= mlt_deinterlacer_greedy) method = mlt_deinterlacer_greedy; else if (method <= mlt_deinterlacer_yadif_nospatial) method = mlt_deinterlacer_yadif_nospatial; else if (method <= mlt_deinterlacer_invalid) method = mlt_deinterlacer_yadif; return method; } int deinterlace_image( mlt_image dst, mlt_image src, mlt_image prev, mlt_image next, int tff, mlt_deinterlacer method) { if (!dst || !src || !dst->data || !src->data) { return 1; } if (method >= mlt_deinterlacer_yadif_nospatial && (!prev || !next || !prev->data || !next->data)) { method = mlt_deinterlacer_linearblend; } else if ((method == mlt_deinterlacer_weave || method == mlt_deinterlacer_greedy) && (!next || !next->data)) { method = mlt_deinterlacer_linearblend; } if (method == mlt_deinterlacer_bob) { deinterlace_yuv(dst->data, (uint8_t **) &src->data, src->width * 2, src->height, DEINTERLACE_BOB); } else if (method == mlt_deinterlacer_weave) { uint8_t *src_array[2]; src_array[0] = src->data; src_array[1] = next->data; deinterlace_yuv(dst->data, src_array, src->width * 2, src->height, DEINTERLACE_WEAVE); } else if (method == mlt_deinterlacer_onefield) { deinterlace_yuv(dst->data, (uint8_t **) &src->data, src->width * 2, src->height, DEINTERLACE_ONEFIELD); } else if (method == mlt_deinterlacer_linearblend) { deinterlace_yuv(dst->data, (uint8_t **) &src->data, src->width * 2, src->height, DEINTERLACE_LINEARBLEND); } else if (method == mlt_deinterlacer_greedy) { uint8_t *src_array[2]; src_array[0] = src->data; src_array[1] = next->data; deinterlace_yuv(dst->data, src_array, src->width * 2, src->height, DEINTERLACE_GREEDY); } else if (method >= mlt_deinterlacer_yadif_nospatial) { yadif_filter *yadif = init_yadif(src->width, src->height); if (yadif) { const int pitch = src->width << 1; const int parity = 0; const int YADIF_MODE_TEMPORAL_SPATIAL = 0; const int YADIF_MODE_TEMPORAL = 2; int mode = YADIF_MODE_TEMPORAL_SPATIAL; if (method == mlt_deinterlacer_yadif_nospatial) { mode = YADIF_MODE_TEMPORAL; } // Convert packed to planar YUY2ToPlanes(src->data, pitch, src->width, src->height, yadif->ysrc, yadif->ypitch, yadif->usrc, yadif->vsrc, yadif->uvpitch, yadif->cpu); YUY2ToPlanes(prev->data, pitch, src->width, src->height, yadif->yprev, yadif->ypitch, yadif->uprev, yadif->vprev, yadif->uvpitch, yadif->cpu); YUY2ToPlanes(next->data, pitch, src->width, src->height, yadif->ynext, yadif->ypitch, yadif->unext, yadif->vnext, yadif->uvpitch, yadif->cpu); // Deinterlace each plane filter_plane(mode, yadif->ydest, yadif->ypitch, yadif->yprev, yadif->ysrc, yadif->ynext, yadif->ypitch, src->width, src->height, parity, tff, yadif->cpu); filter_plane(mode, yadif->udest, yadif->uvpitch, yadif->uprev, yadif->usrc, yadif->unext, yadif->uvpitch, src->width >> 1, src->height, parity, tff, yadif->cpu); filter_plane(mode, yadif->vdest, yadif->uvpitch, yadif->vprev, yadif->vsrc, yadif->vnext, yadif->uvpitch, src->width >> 1, src->height, parity, tff, yadif->cpu); // Convert planar to packed YUY2FromPlanes(dst->data, pitch, src->width, src->height, yadif->ydest, yadif->ypitch, yadif->udest, yadif->vdest, yadif->uvpitch, yadif->cpu); close_yadif(yadif); } } else { // If all else fails, default to linear blend deinterlace_yuv(dst->data, (uint8_t **) &src->data, src->width * 2, src->height, DEINTERLACE_LINEARBLEND); } return 0; } mlt-7.22.0/src/modules/xine/common.h000664 000000 000000 00000002030 14531534050 017214 0ustar00rootroot000000 000000 /* * common.h * Copyright (C) 2023 Meltytech, LLC * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef __COMMON_H__ #define __COMMON_H__ #include mlt_deinterlacer supported_method(mlt_deinterlacer method); int deinterlace_image( mlt_image dst, mlt_image src, mlt_image prev, mlt_image next, int tff, mlt_deinterlacer method); #endif mlt-7.22.0/src/modules/xine/cpu_accel.c000664 000000 000000 00000012700 14531534050 017642 0ustar00rootroot000000 000000 /* * cpu_accel.c * Copyright (C) 1999-2001 Aaron Holtzman * * This file is part of mpeg2dec, a free MPEG-2 video stream decoder. * * mpeg2dec is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * mpeg2dec is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ //#include "config.h" #include #include #include #include #include #include #define LOG_MODULE "cpu_accel" #define LOG_VERBOSE /* #define LOG */ #include "xineutils.h" #if defined(ARCH_X86) || defined(ARCH_X86_64) #if defined __x86_64__ static uint32_t arch_accel (void) { uint32_t caps; /* No need to test for this on AMD64, we know what the platform has. */ caps = MM_ACCEL_X86_MMX | MM_ACCEL_X86_SSE | MM_ACCEL_X86_MMXEXT | MM_ACCEL_X86_SSE2; return caps; } #else static uint32_t arch_accel (void) { #ifndef _MSC_VER uint32_t eax, ebx, ecx, edx; int AMD; uint32_t caps; #ifndef PIC #define cpuid(op,eax,ebx,ecx,edx) \ __asm__ ("cpuid" \ : "=a" (eax), \ "=b" (ebx), \ "=c" (ecx), \ "=d" (edx) \ : "a" (op) \ : "cc") #else /* PIC version : save ebx */ #define cpuid(op,eax,ebx,ecx,edx) \ __asm__ ("pushl %%ebx\n\t" \ "cpuid\n\t" \ "movl %%ebx,%1\n\t" \ "popl %%ebx" \ : "=a" (eax), \ "=r" (ebx), \ "=c" (ecx), \ "=d" (edx) \ : "a" (op) \ : "cc") #endif __asm__ ("pushfl\n\t" "pushfl\n\t" "popl %0\n\t" "movl %0,%1\n\t" "xorl $0x200000,%0\n\t" "pushl %0\n\t" "popfl\n\t" "pushfl\n\t" "popl %0\n\t" "popfl" : "=r" (eax), "=r" (ebx) : : "cc"); if (eax == ebx) /* no cpuid */ return 0; cpuid (0x00000000, eax, ebx, ecx, edx); if (!eax) /* vendor string only */ return 0; AMD = (ebx == 0x68747541) && (ecx == 0x444d4163) && (edx == 0x69746e65); cpuid (0x00000001, eax, ebx, ecx, edx); if (! (edx & 0x00800000)) /* no MMX */ return 0; caps = MM_ACCEL_X86_MMX; if (edx & 0x02000000) /* SSE - identical to AMD MMX extensions */ caps |= MM_ACCEL_X86_SSE | MM_ACCEL_X86_MMXEXT; if (edx & 0x04000000) /* SSE2 */ caps |= MM_ACCEL_X86_SSE2; cpuid (0x80000000, eax, ebx, ecx, edx); if (eax < 0x80000001) /* no extended capabilities */ return caps; cpuid (0x80000001, eax, ebx, ecx, edx); if (edx & 0x80000000) caps |= MM_ACCEL_X86_3DNOW; if (AMD && (edx & 0x00400000)) /* AMD MMX extensions */ caps |= MM_ACCEL_X86_MMXEXT; return caps; #else /* _MSC_VER */ return 0; #endif } #endif /* x86_64 */ static jmp_buf sigill_return; static void sigill_handler (int n) { longjmp(sigill_return, 1); } #endif /* ARCH_X86 */ #if defined (ARCH_PPC) && defined (ENABLE_ALTIVEC) static sigjmp_buf jmpbuf; static volatile sig_atomic_t canjump = 0; static void sigill_handler (int sig) { if (!canjump) { signal (sig, SIG_DFL); raise (sig); } canjump = 0; siglongjmp (jmpbuf, 1); } static uint32_t arch_accel (void) { signal (SIGILL, sigill_handler); if (sigsetjmp (jmpbuf, 1)) { signal (SIGILL, SIG_DFL); return 0; } canjump = 1; __asm__ volatile ("mtspr 256, %0\n\t" "vand %%v0, %%v0, %%v0" : : "r" (-1)); signal (SIGILL, SIG_DFL); return MM_ACCEL_PPC_ALTIVEC; } #endif /* ARCH_PPC */ uint32_t xine_mm_accel (void) { static int initialized = 0; static uint32_t accel; if (!initialized) { #if defined (ARCH_X86) || defined(ARCH_X86_64) || (defined (ARCH_PPC) && defined (ENABLE_ALTIVEC)) accel = arch_accel (); #elif defined (HAVE_MLIB) #ifdef MLIB_LAZYLOAD void *hndl; if ((hndl = dlopen("libmlib.so.2", RTLD_LAZY | RTLD_GLOBAL | RTLD_NODELETE)) == NULL) { accel = 0; } else { dlclose(hndl); accel = MM_ACCEL_MLIB; } #else accel = MM_ACCEL_MLIB; #endif #else accel = 0; #endif #if defined(ARCH_X86) || defined(ARCH_X86_64) #ifndef _MSC_VER /* test OS support for SSE */ if( accel & MM_ACCEL_X86_SSE ) { void (*old_sigill_handler)(int); old_sigill_handler = signal (SIGILL, sigill_handler); if (setjmp(sigill_return)) { lprintf ("OS doesn't support SSE instructions.\n"); accel &= ~(MM_ACCEL_X86_SSE|MM_ACCEL_X86_SSE2); } else { __asm__ volatile ("xorps %xmm0, %xmm0"); } signal (SIGILL, old_sigill_handler); } #endif /* _MSC_VER */ #endif /* ARCH_X86 || ARCH_X86_64 */ if(getenv("XINE_NO_ACCEL")) { accel = 0; } initialized = 1; } return accel; } mlt-7.22.0/src/modules/xine/deinterlace.c000664 000000 000000 00000066141 14531534050 020213 0ustar00rootroot000000 000000 /* * Copyright (C) 2001 the xine project * * This file is part of xine, a free video player. * * xine is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * xine is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * * Deinterlace routines by Miguel Freitas * based of DScaler project sources (deinterlace.sourceforge.net) * * Currently only available for Xv driver and MMX extensions * * small todo list: * - implement non-MMX versions for all methods * - support MMX2 instructions * - move some generic code from xv driver to this file * - make it also work for yuy2 frames * */ #include #include #include "deinterlace.h" #include "xineutils.h" #define xine_fast_memcpy memcpy #define xine_fast_memmove memmove /* DeinterlaceFieldBob algorithm Based on Virtual Dub plugin by Gunnar Thalin MMX asm version from dscaler project (deinterlace.sourceforge.net) Linux version for Xine player by Miguel Freitas */ static void deinterlace_bob_yuv_mmx( uint8_t *pdst, uint8_t *psrc[], int width, int height ) { #ifdef USE_MMX int Line; uint64_t *YVal1; uint64_t *YVal2; uint64_t *YVal3; uint64_t *Dest; uint8_t* pEvenLines = psrc[0]; uint8_t* pOddLines = psrc[0]+width; int LineLength = width; int SourcePitch = width * 2; int IsOdd = 1; long EdgeDetect = 625; long JaggieThreshold = 73; int n; uint64_t qwEdgeDetect; uint64_t qwThreshold; static mmx_t YMask = {.ub={0xff,0,0xff,0,0xff,0,0xff,0}}; static mmx_t Mask = {.ub={0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe}}; qwEdgeDetect = EdgeDetect; qwEdgeDetect += (qwEdgeDetect << 48) + (qwEdgeDetect << 32) + (qwEdgeDetect << 16); qwThreshold = JaggieThreshold; qwThreshold += (qwThreshold << 48) + (qwThreshold << 32) + (qwThreshold << 16); // copy first even line no matter what, and the first odd line if we're // processing an odd field. xine_fast_memcpy(pdst, pEvenLines, LineLength); if (IsOdd) xine_fast_memcpy(pdst + LineLength, pOddLines, LineLength); height = height / 2; for (Line = 0; Line < height - 1; ++Line) { if (IsOdd) { YVal1 = (uint64_t *)(pOddLines + Line * SourcePitch); YVal2 = (uint64_t *)(pEvenLines + (Line + 1) * SourcePitch); YVal3 = (uint64_t *)(pOddLines + (Line + 1) * SourcePitch); Dest = (uint64_t *)(pdst + (Line * 2 + 2) * LineLength); } else { YVal1 = (uint64_t *)(pEvenLines + Line * SourcePitch); YVal2 = (uint64_t *)(pOddLines + Line * SourcePitch); YVal3 = (uint64_t *)(pEvenLines + (Line + 1) * SourcePitch); Dest = (uint64_t *)(pdst + (Line * 2 + 1) * LineLength); } // For ease of reading, the comments below assume that we're operating on an odd // field (i.e., that bIsOdd is true). The exact same processing is done when we // operate on an even field, but the roles of the odd and even fields are reversed. // It's just too cumbersome to explain the algorithm in terms of "the next odd // line if we're doing an odd field, or the next even line if we're doing an // even field" etc. So wherever you see "odd" or "even" below, keep in mind that // half the time this function is called, those words' meanings will invert. // Copy the odd line to the overlay verbatim. xine_fast_memcpy((char *)Dest + LineLength, YVal3, LineLength); n = LineLength >> 3; while( n-- ) { movq_m2r (*YVal1++, mm0); movq_m2r (*YVal2++, mm1); movq_m2r (*YVal3++, mm2); // get intensities in mm3 - 4 movq_r2r ( mm0, mm3 ); pand_m2r ( YMask, mm3 ); movq_r2r ( mm1, mm4 ); pand_m2r ( YMask, mm4 ); movq_r2r ( mm2, mm5 ); pand_m2r ( YMask, mm5 ); // get average in mm0 pand_m2r ( Mask, mm0 ); pand_m2r ( Mask, mm2 ); psrlw_i2r ( 01, mm0 ); psrlw_i2r ( 01, mm2 ); paddw_r2r ( mm2, mm0 ); // work out (O1 - E) * (O2 - E) / 2 - EdgeDetect * (O1 - O2) ^ 2 >> 12 // result will be in mm6 psrlw_i2r ( 01, mm3 ); psrlw_i2r ( 01, mm4 ); psrlw_i2r ( 01, mm5 ); movq_r2r ( mm3, mm6 ); psubw_r2r ( mm4, mm6 ); //mm6 = O1 - E movq_r2r ( mm5, mm7 ); psubw_r2r ( mm4, mm7 ); //mm7 = O2 - E pmullw_r2r ( mm7, mm6 ); // mm6 = (O1 - E) * (O2 - E) movq_r2r ( mm3, mm7 ); psubw_r2r ( mm5, mm7 ); // mm7 = (O1 - O2) pmullw_r2r ( mm7, mm7 ); // mm7 = (O1 - O2) ^ 2 psrlw_i2r ( 12, mm7 ); // mm7 = (O1 - O2) ^ 2 >> 12 pmullw_m2r ( *&qwEdgeDetect, mm7 );// mm7 = EdgeDetect * (O1 - O2) ^ 2 >> 12 psubw_r2r ( mm7, mm6 ); // mm6 is what we want pcmpgtw_m2r ( *&qwThreshold, mm6 ); movq_r2r ( mm6, mm7 ); pand_r2r ( mm6, mm0 ); pandn_r2r ( mm1, mm7 ); por_r2r ( mm0, mm7 ); movq_r2m ( mm7, *Dest++ ); } } // Copy last odd line if we're processing an even field. if (! IsOdd) { xine_fast_memcpy(pdst + (height * 2 - 1) * LineLength, pOddLines + (height - 1) * SourcePitch, LineLength); } // clear out the MMX registers ready for doing floating point // again emms(); #endif } /* Deinterlace the latest field, with a tendency to weave rather than bob. Good for high detail on low-movement scenes. Seems to produce bad output in general case, need to check if this is normal or if the code is broken. */ static int deinterlace_weave_yuv_mmx( uint8_t *pdst, uint8_t *psrc[], int width, int height ) { #ifdef USE_MMX int Line; uint64_t *YVal1; uint64_t *YVal2; uint64_t *YVal3; uint64_t *YVal4; uint64_t *Dest; uint8_t* pEvenLines = psrc[0]; uint8_t* pOddLines = psrc[0]+width; uint8_t* pPrevLines; int LineLength = width; int SourcePitch = width * 2; int IsOdd = 1; long TemporalTolerance = 300; long SpatialTolerance = 600; long SimilarityThreshold = 25; int n; uint64_t qwSpatialTolerance; uint64_t qwTemporalTolerance; uint64_t qwThreshold; static mmx_t YMask = {.ub={0xff,0,0xff,0,0xff,0,0xff,0}}; static mmx_t Mask = {.ub={0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe}}; // Make sure we have all the data we need. if ( psrc[0] == NULL || psrc[1] == NULL ) return 0; if (IsOdd) pPrevLines = psrc[1] + width; else pPrevLines = psrc[1]; // Since the code uses MMX to process 4 pixels at a time, we need our constants // to be represented 4 times per quadword. qwSpatialTolerance = SpatialTolerance; qwSpatialTolerance += (qwSpatialTolerance << 48) + (qwSpatialTolerance << 32) + (qwSpatialTolerance << 16); qwTemporalTolerance = TemporalTolerance; qwTemporalTolerance += (qwTemporalTolerance << 48) + (qwTemporalTolerance << 32) + (qwTemporalTolerance << 16); qwThreshold = SimilarityThreshold; qwThreshold += (qwThreshold << 48) + (qwThreshold << 32) + (qwThreshold << 16); // copy first even line no matter what, and the first odd line if we're // processing an even field. xine_fast_memcpy(pdst, pEvenLines, LineLength); if (!IsOdd) xine_fast_memcpy(pdst + LineLength, pOddLines, LineLength); height = height / 2; for (Line = 0; Line < height - 1; ++Line) { if (IsOdd) { YVal1 = (uint64_t *)(pEvenLines + Line * SourcePitch); YVal2 = (uint64_t *)(pOddLines + Line * SourcePitch); YVal3 = (uint64_t *)(pEvenLines + (Line + 1) * SourcePitch); YVal4 = (uint64_t *)(pPrevLines + Line * SourcePitch); Dest = (uint64_t *)(pdst + (Line * 2 + 1) * LineLength); } else { YVal1 = (uint64_t *)(pOddLines + Line * SourcePitch); YVal2 = (uint64_t *)(pEvenLines + (Line + 1) * SourcePitch); YVal3 = (uint64_t *)(pOddLines + (Line + 1) * SourcePitch); YVal4 = (uint64_t *)(pPrevLines + (Line + 1) * SourcePitch); Dest = (uint64_t *)(pdst + (Line * 2 + 2) * LineLength); } // For ease of reading, the comments below assume that we're operating on an odd // field (i.e., that bIsOdd is true). The exact same processing is done when we // operate on an even field, but the roles of the odd and even fields are reversed. // It's just too cumbersome to explain the algorithm in terms of "the next odd // line if we're doing an odd field, or the next even line if we're doing an // even field" etc. So wherever you see "odd" or "even" below, keep in mind that // half the time this function is called, those words' meanings will invert. // Copy the even scanline below this one to the overlay buffer, since we'll be // adapting the current scanline to the even lines surrounding it. The scanline // above has already been copied by the previous pass through the loop. xine_fast_memcpy((char *)Dest + LineLength, YVal3, LineLength); n = LineLength >> 3; while( n-- ) { movq_m2r ( *YVal1++, mm0 ); // mm0 = E1 movq_m2r ( *YVal2++, mm1 ); // mm1 = O movq_m2r ( *YVal3++, mm2 ); // mm2 = E2 movq_r2r ( mm0, mm3 ); // mm3 = intensity(E1) movq_r2r ( mm1, mm4 ); // mm4 = intensity(O) movq_r2r ( mm2, mm6 ); // mm6 = intensity(E2) pand_m2r ( YMask, mm3 ); pand_m2r ( YMask, mm4 ); pand_m2r ( YMask, mm6 ); // Average E1 and E2 for interpolated bobbing. // leave result in mm0 pand_m2r ( Mask, mm0 ); // mm0 = E1 with lower chroma bit stripped off pand_m2r ( Mask, mm2 ); // mm2 = E2 with lower chroma bit stripped off psrlw_i2r ( 01, mm0 ); // mm0 = E1 / 2 psrlw_i2r ( 01, mm2 ); // mm2 = E2 / 2 paddb_r2r ( mm2, mm0 ); // The meat of the work is done here. We want to see whether this pixel is // close in luminosity to ANY of: its top neighbor, its bottom neighbor, // or its predecessor. To do this without branching, we use MMX's // saturation feature, which gives us Z(x) = x if x>=0, or 0 if x<0. // // The formula we're computing here is // Z(ST - (E1 - O) ^ 2) + Z(ST - (E2 - O) ^ 2) + Z(TT - (Oold - O) ^ 2) // where ST is spatial tolerance and TT is temporal tolerance. The idea // is that if a pixel is similar to none of its neighbors, the resulting // value will be pretty low, probably zero. A high value therefore indicates // that the pixel had a similar neighbor. The pixel in the same position // in the field before last (Oold) is considered a neighbor since we want // to be able to display 1-pixel-high horizontal lines. movq_m2r ( *&qwSpatialTolerance, mm7 ); movq_r2r ( mm3, mm5 ); // mm5 = E1 psubsw_r2r ( mm4, mm5 ); // mm5 = E1 - O psraw_i2r ( 1, mm5 ); pmullw_r2r ( mm5, mm5 ); // mm5 = (E1 - O) ^ 2 psubusw_r2r ( mm5, mm7 ); // mm7 = ST - (E1 - O) ^ 2, or 0 if that's negative movq_m2r ( *&qwSpatialTolerance, mm3 ); movq_r2r ( mm6, mm5 ); // mm5 = E2 psubsw_r2r ( mm4, mm5 ); // mm5 = E2 - O psraw_i2r ( 1, mm5 ); pmullw_r2r ( mm5, mm5 ); // mm5 = (E2 - O) ^ 2 psubusw_r2r ( mm5, mm3 ); // mm0 = ST - (E2 - O) ^ 2, or 0 if that's negative paddusw_r2r ( mm3, mm7 ); // mm7 = (ST - (E1 - O) ^ 2) + (ST - (E2 - O) ^ 2) movq_m2r ( *&qwTemporalTolerance, mm3 ); movq_m2r ( *YVal4++, mm5 ); // mm5 = Oold pand_m2r ( YMask, mm5 ); psubsw_r2r ( mm4, mm5 ); // mm5 = Oold - O psraw_i2r ( 1, mm5 ); // XXX pmullw_r2r ( mm5, mm5 ); // mm5 = (Oold - O) ^ 2 psubusw_r2r ( mm5, mm3 ); /* mm0 = TT - (Oold - O) ^ 2, or 0 if that's negative */ paddusw_r2r ( mm3, mm7 ); // mm7 = our magic number /* * Now compare the similarity totals against our threshold. The pcmpgtw * instruction will populate the target register with a bunch of mask bits, * filling words where the comparison is true with 1s and ones where it's * false with 0s. A few ANDs and NOTs and an OR later, we have bobbed * values for pixels under the similarity threshold and weaved ones for * pixels over the threshold. */ pcmpgtw_m2r( *&qwThreshold, mm7 ); // mm7 = 0xffff where we're greater than the threshold, 0 elsewhere movq_r2r ( mm7, mm6 ); // mm6 = 0xffff where we're greater than the threshold, 0 elsewhere pand_r2r ( mm1, mm7 ); // mm7 = weaved data where we're greater than the threshold, 0 elsewhere pandn_r2r ( mm0, mm6 ); // mm6 = bobbed data where we're not greater than the threshold, 0 elsewhere por_r2r ( mm6, mm7 ); // mm7 = bobbed and weaved data movq_r2m ( mm7, *Dest++ ); } } // Copy last odd line if we're processing an odd field. if (IsOdd) { xine_fast_memcpy(pdst + (height * 2 - 1) * LineLength, pOddLines + (height - 1) * SourcePitch, LineLength); } // clear out the MMX registers ready for doing floating point // again emms(); #endif return 1; } // This is a simple lightweight DeInterlace method that uses little CPU time // but gives very good results for low or intermedite motion. (MORE CPU THAN BOB) // It defers frames by one field, but that does not seem to produce noticeable // lip sync problems. // // The method used is to take either the older or newer weave pixel depending // upon which give the smaller comb factor, and then clip to avoid large damage // when wrong. // // I'd intended this to be part of a larger more elaborate method added to // Blended Clip but this give too good results for the CPU to ignore here. static int deinterlace_greedy_yuv_mmx( uint8_t *pdst, uint8_t *psrc[], int width, int height ) { #ifdef USE_MMX int Line; int LoopCtr; uint64_t *L1; // ptr to Line1, of 3 uint64_t *L2; // ptr to Line2, the weave line uint64_t *L3; // ptr to Line3 uint64_t *LP2; // ptr to prev Line2 uint64_t *Dest; uint8_t* pEvenLines = psrc[0]; uint8_t* pOddLines = psrc[0]+width; uint8_t* pPrevLines; static mmx_t ShiftMask = {.ub={0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe}}; int LineLength = width; int SourcePitch = width * 2; int IsOdd = 1; long GreedyMaxComb = 15; static mmx_t MaxComb; int i; if ( psrc[0] == NULL || psrc[1] == NULL ) return 0; if (IsOdd) pPrevLines = psrc[1] + width; else pPrevLines = psrc[1]; for( i = 0; i < 8; i++ ) MaxComb.ub[i] = GreedyMaxComb; // How badly do we let it weave? 0-255 // copy first even line no matter what, and the first odd line if we're // processing an EVEN field. (note diff from other deint rtns.) xine_fast_memcpy(pdst, pEvenLines, LineLength); //DL0 if (!IsOdd) xine_fast_memcpy(pdst + LineLength, pOddLines, LineLength); //DL1 height = height / 2; for (Line = 0; Line < height - 1; ++Line) { LoopCtr = LineLength / 8; // there are LineLength / 8 qwords per line if (IsOdd) { L1 = (uint64_t *)(pEvenLines + Line * SourcePitch); L2 = (uint64_t *)(pOddLines + Line * SourcePitch); L3 = (uint64_t *)(pEvenLines + (Line + 1) * SourcePitch); LP2 = (uint64_t *)(pPrevLines + Line * SourcePitch); // prev Odd lines Dest = (uint64_t *)(pdst + (Line * 2 + 1) * LineLength); } else { L1 = (uint64_t *)(pOddLines + Line * SourcePitch); L2 = (uint64_t *)(pEvenLines + (Line + 1) * SourcePitch); L3 = (uint64_t *)(pOddLines + (Line + 1) * SourcePitch); LP2 = (uint64_t *)(pPrevLines + (Line + 1) * SourcePitch); //prev even lines Dest = (uint64_t *)(pdst + (Line * 2 + 2) * LineLength); } xine_fast_memcpy((char *)Dest + LineLength, L3, LineLength); // For ease of reading, the comments below assume that we're operating on an odd // field (i.e., that info->IsOdd is true). Assume the obvious for even lines.. while( LoopCtr-- ) { movq_m2r ( *L1++, mm1 ); movq_m2r ( *L2++, mm2 ); movq_m2r ( *L3++, mm3 ); movq_m2r ( *LP2++, mm0 ); // average L1 and L3 leave result in mm4 movq_r2r ( mm1, mm4 ); // L1 pand_m2r ( ShiftMask, mm4 ); psrlw_i2r ( 01, mm4 ); movq_r2r ( mm3, mm5 ); // L3 pand_m2r ( ShiftMask, mm5 ); psrlw_i2r ( 01, mm5 ); paddb_r2r ( mm5, mm4 ); // the average, for computing comb // get abs value of possible L2 comb movq_r2r ( mm2, mm7 ); // L2 psubusb_r2r ( mm4, mm7 ); // L2 - avg movq_r2r ( mm4, mm5 ); // avg psubusb_r2r ( mm2, mm5 ); // avg - L2 por_r2r ( mm7, mm5 ); // abs(avg-L2) movq_r2r ( mm4, mm6 ); // copy of avg for later // get abs value of possible LP2 comb movq_r2r ( mm0, mm7 ); // LP2 psubusb_r2r ( mm4, mm7 ); // LP2 - avg psubusb_r2r ( mm0, mm4 ); // avg - LP2 por_r2r ( mm7, mm4 ); // abs(avg-LP2) // use L2 or LP2 depending upon which makes smaller comb psubusb_r2r ( mm5, mm4 ); // see if it goes to zero psubusb_r2r ( mm5, mm5 ); // 0 pcmpeqb_r2r ( mm5, mm4 ); // if (mm4=0) then FF else 0 pcmpeqb_r2r ( mm4, mm5 ); // opposite of mm4 // if Comb(LP2) <= Comb(L2) then mm4=ff, mm5=0 else mm4=0, mm5 = 55 pand_r2r ( mm2, mm5 ); // use L2 if mm5 == ff, else 0 pand_r2r ( mm0, mm4 ); // use LP2 if mm4 = ff, else 0 por_r2r ( mm5, mm4 ); // may the best win // Now lets clip our chosen value to be not outside of the range // of the high/low range L1-L3 by more than abs(L1-L3) // This allows some comb but limits the damages and also allows more // detail than a boring oversmoothed clip. movq_r2r ( mm1, mm2 ); // copy L1 psubusb_r2r ( mm3, mm2 ); // - L3, with saturation paddusb_r2r ( mm3, mm2 ); // now = Max(L1,L3) pcmpeqb_r2r ( mm7, mm7 ); // all ffffffff psubusb_r2r ( mm1, mm7 ); // - L1 paddusb_r2r ( mm7, mm3 ); // add, may sat at fff.. psubusb_r2r ( mm7, mm3 ); // now = Min(L1,L3) // allow the value to be above the high or below the low by amt of MaxComb paddusb_m2r ( MaxComb, mm2 ); // increase max by diff psubusb_m2r ( MaxComb, mm3 ); // lower min by diff psubusb_r2r ( mm3, mm4 ); // best - Min paddusb_r2r ( mm3, mm4 ); // now = Max(best,Min(L1,L3) pcmpeqb_r2r ( mm7, mm7 ); // all ffffffff psubusb_r2r ( mm4, mm7 ); // - Max(best,Min(best,L3) paddusb_r2r ( mm7, mm2 ); // add may sat at FFF.. psubusb_r2r ( mm7, mm2 ); // now = Min( Max(best, Min(L1,L3), L2 )=L2 clipped movq_r2m ( mm2, *Dest++ ); // move in our clipped best } } /* Copy last odd line if we're processing an Odd field. */ if (IsOdd) { xine_fast_memcpy(pdst + (height * 2 - 1) * LineLength, pOddLines + (height - 1) * SourcePitch, LineLength); } /* clear out the MMX registers ready for doing floating point again */ emms(); #endif return 1; } /* Use one field to interpolate the other (low cpu utilization) Will lose resolution but does not produce weaving effect (good for fast moving scenes) also know as "linear interpolation" */ static void deinterlace_onefield_yuv_mmx( uint8_t *pdst, uint8_t *psrc[], int width, int height ) { #ifdef USE_MMX int Line; uint64_t *YVal1; uint64_t *YVal3; uint64_t *Dest; uint8_t* pEvenLines = psrc[0]; uint8_t* pOddLines = psrc[0]+width; int LineLength = width; int SourcePitch = width * 2; int IsOdd = 1; int n; static mmx_t Mask = {.ub={0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe}}; /* * copy first even line no matter what, and the first odd line if we're * processing an odd field. */ xine_fast_memcpy(pdst, pEvenLines, LineLength); if (IsOdd) xine_fast_memcpy(pdst + LineLength, pOddLines, LineLength); height = height / 2; for (Line = 0; Line < height - 1; ++Line) { if (IsOdd) { YVal1 = (uint64_t *)(pOddLines + Line * SourcePitch); YVal3 = (uint64_t *)(pOddLines + (Line + 1) * SourcePitch); Dest = (uint64_t *)(pdst + (Line * 2 + 2) * LineLength); } else { YVal1 = (uint64_t *)(pEvenLines + Line * SourcePitch); YVal3 = (uint64_t *)(pEvenLines + (Line + 1) * SourcePitch); Dest = (uint64_t *)(pdst + (Line * 2 + 1) * LineLength); } // Copy the odd line to the overlay verbatim. xine_fast_memcpy((char *)Dest + LineLength, YVal3, LineLength); n = LineLength >> 3; while( n-- ) { movq_m2r (*YVal1++, mm0); movq_m2r (*YVal3++, mm2); // get average in mm0 pand_m2r ( Mask, mm0 ); pand_m2r ( Mask, mm2 ); psrlw_i2r ( 01, mm0 ); psrlw_i2r ( 01, mm2 ); paddw_r2r ( mm2, mm0 ); movq_r2m ( mm0, *Dest++ ); } } /* Copy last odd line if we're processing an even field. */ if (! IsOdd) { xine_fast_memcpy(pdst + (height * 2 - 1) * LineLength, pOddLines + (height - 1) * SourcePitch, LineLength); } /* clear out the MMX registers ready for doing floating point * again */ emms(); #endif } /* Linear Blend filter - does a kind of vertical blurring on the image. (idea borrowed from mplayer's sources) */ static void deinterlace_linearblend_yuv_mmx( uint8_t *pdst, uint8_t *psrc[], int width, int height ) { #ifdef USE_MMX int Line; uint64_t *YVal1; uint64_t *YVal2; uint64_t *YVal3; uint64_t *Dest; int LineLength = width; int n; /* Copy first line */ xine_fast_memmove(pdst, psrc[0], LineLength); for (Line = 1; Line < height - 1; ++Line) { YVal1 = (uint64_t *)(psrc[0] + (Line - 1) * LineLength); YVal2 = (uint64_t *)(psrc[0] + (Line) * LineLength); YVal3 = (uint64_t *)(psrc[0] + (Line + 1) * LineLength); Dest = (uint64_t *)(pdst + Line * LineLength); n = LineLength >> 3; while( n-- ) { /* load data from 3 lines */ movq_m2r (*YVal1++, mm0); movq_m2r (*YVal2++, mm1); movq_m2r (*YVal3++, mm2); /* expand bytes to words */ punpckhbw_r2r (mm0, mm3); punpckhbw_r2r (mm1, mm4); punpckhbw_r2r (mm2, mm5); punpcklbw_r2r (mm0, mm0); punpcklbw_r2r (mm1, mm1); punpcklbw_r2r (mm2, mm2); /* * deinterlacing: * deint_line = (line0 + 2*line1 + line2) / 4 */ psrlw_i2r (07, mm0); psrlw_i2r (06, mm1); psrlw_i2r (07, mm2); psrlw_i2r (07, mm3); psrlw_i2r (06, mm4); psrlw_i2r (07, mm5); paddw_r2r (mm1, mm0); paddw_r2r (mm2, mm0); paddw_r2r (mm4, mm3); paddw_r2r (mm5, mm3); psrlw_i2r (03, mm0); psrlw_i2r (03, mm3); /* pack 8 words to 8 bytes in mm0 */ packuswb_r2r (mm3, mm0); movq_r2m ( mm0, *Dest++ ); } } /* Copy last line */ xine_fast_memmove(pdst + Line * LineLength, psrc[0] + Line * LineLength, LineLength); /* clear out the MMX registers ready for doing floating point * again */ emms(); #endif } /* Linear Blend filter - C version contributed by Rogerio Brito. This algorithm has the same interface as the other functions. The destination "screen" (pdst) is constructed from the source screen (psrc[0]) line by line. The i-th line of the destination screen is the average of 3 lines from the source screen: the (i-1)-th, i-th and (i+1)-th lines, with the i-th line having weight 2 in the computation. Remarks: * each line on pdst doesn't depend on previous lines; * due to the way the algorithm is defined, the first & last lines of the screen aren't deinterlaced. */ static void deinterlace_linearblend_yuv( uint8_t *pdst, uint8_t *psrc[], int width, int height ) { register int x, y; register uint8_t *l0, *l1, *l2, *l3; l0 = pdst; /* target line */ l1 = psrc[0]; /* 1st source line */ l2 = l1 + width; /* 2nd source line = line that follows l1 */ l3 = l2 + width; /* 3rd source line = line that follows l2 */ /* Copy the first line */ xine_fast_memcpy(l0, l1, width); l0 += width; for (y = 1; y < height-1; ++y) { /* computes avg of: l1 + 2*l2 + l3 */ for (x = 0; x < width; ++x) { l0[x] = (l1[x] + (l2[x]<<1) + l3[x]) >> 2; } /* updates the line pointers */ l1 = l2; l2 = l3; l3 += width; l0 += width; } /* Copy the last line */ xine_fast_memcpy(l0, l1, width); } static int check_for_mmx(void) { #ifdef USE_MMX static int config_flags = -1; if ( config_flags == -1 ) config_flags = xine_mm_accel(); if (config_flags & MM_ACCEL_X86_MMX) return 1; return 0; #else return 0; #endif } /* generic YUV deinterlacer pdst -> pointer to destination bitmap psrc -> array of pointers to source bitmaps ([0] = most recent) width,height -> dimension for bitmaps method -> DEINTERLACE_xxx */ void deinterlace_yuv( uint8_t *pdst, uint8_t *psrc[], int width, int height, int method ) { switch( method ) { case DEINTERLACE_NONE: xine_fast_memcpy(pdst,psrc[0],width*height); break; case DEINTERLACE_BOB: if( check_for_mmx() ) deinterlace_bob_yuv_mmx(pdst,psrc,width,height); else /* FIXME: provide an alternative? */ deinterlace_linearblend_yuv(pdst,psrc,width,height); break; case DEINTERLACE_WEAVE: if( check_for_mmx() ) { if( !deinterlace_weave_yuv_mmx(pdst,psrc,width,height) ) xine_fast_memcpy(pdst,psrc[0],width*height); } else /* FIXME: provide an alternative? */ deinterlace_linearblend_yuv(pdst,psrc,width,height); break; case DEINTERLACE_GREEDY: if( check_for_mmx() ) { if( !deinterlace_greedy_yuv_mmx(pdst,psrc,width,height) ) xine_fast_memcpy(pdst,psrc[0],width*height); } else /* FIXME: provide an alternative? */ deinterlace_linearblend_yuv(pdst,psrc,width,height); break; case DEINTERLACE_ONEFIELD: if( check_for_mmx() ) deinterlace_onefield_yuv_mmx(pdst,psrc,width,height); else /* FIXME: provide an alternative? */ deinterlace_linearblend_yuv(pdst,psrc,width,height); break; case DEINTERLACE_ONEFIELDXV: lprintf("ONEFIELDXV must be handled by the video driver.\n"); break; case DEINTERLACE_LINEARBLEND: if( check_for_mmx() ) deinterlace_linearblend_yuv_mmx(pdst,psrc,width,height); else deinterlace_linearblend_yuv(pdst,psrc,width,height); break; default: lprintf("unknown method %d.\n",method); break; } } int deinterlace_yuv_supported ( int method ) { switch( method ) { case DEINTERLACE_NONE: return 1; case DEINTERLACE_BOB: case DEINTERLACE_WEAVE: case DEINTERLACE_GREEDY: case DEINTERLACE_ONEFIELD: return check_for_mmx(); case DEINTERLACE_ONEFIELDXV: lprintf ("ONEFIELDXV must be handled by the video driver.\n"); return 0; case DEINTERLACE_LINEARBLEND: return 1; } return 0; } const char *deinterlace_methods[] = { "none", "bob", "weave", "greedy", "onefield", "onefield_xv", "linearblend", NULL }; mlt-7.22.0/src/modules/xine/deinterlace.h000664 000000 000000 00000003063 14531534050 020212 0ustar00rootroot000000 000000 /* * Copyright (C) 2001 the xine project * * This file is part of xine, a free video player. * * xine is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * xine is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * * Deinterlace routines by Miguel Freitas * based of DScaler project sources (deinterlace.sourceforge.net) * * Currently only available for Xv driver and MMX extensions * */ #ifndef __DEINTERLACE_H__ #define __DEINTERLACE_H__ //#include "video_out.h" #include int deinterlace_yuv_supported ( int method ); void deinterlace_yuv( uint8_t *pdst, uint8_t *psrc[], int width, int height, int method ); #define DEINTERLACE_NONE 0 #define DEINTERLACE_BOB 1 #define DEINTERLACE_WEAVE 2 #define DEINTERLACE_GREEDY 3 #define DEINTERLACE_ONEFIELD 4 #define DEINTERLACE_ONEFIELDXV 5 #define DEINTERLACE_LINEARBLEND 6 #define DEINTERLACE_YADIF 7 #define DEINTERLACE_YADIF_NOSPATIAL 8 extern const char *deinterlace_methods[]; #endif mlt-7.22.0/src/modules/xine/factory.c000664 000000 000000 00000003774 14531534050 017406 0ustar00rootroot000000 000000 /* * factory.c -- the factory method interfaces * Copyright (C) 2003-2022 Meltytech, LLC * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include extern mlt_filter filter_deinterlace_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_filter link_deinterlace_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); static mlt_properties metadata(mlt_service_type type, const char *id, void *data) { char file[PATH_MAX]; snprintf(file, PATH_MAX, "%s/xine/%s", mlt_environment("MLT_DATA"), (char *) data); return mlt_properties_parse_yaml(file); } MLT_REPOSITORY { MLT_REGISTER(mlt_service_filter_type, "deinterlace", filter_deinterlace_init); MLT_REGISTER_METADATA(mlt_service_filter_type, "deinterlace", metadata, "filter_deinterlace.yml"); MLT_REGISTER(mlt_service_link_type, "deinterlace", link_deinterlace_init); MLT_REGISTER_METADATA(mlt_service_link_type, "deinterlace", metadata, "link_deinterlace.yml"); } mlt-7.22.0/src/modules/xine/filter_deinterlace.c000664 000000 000000 00000043564 14531534050 021564 0ustar00rootroot000000 000000 /* * filter_deinterlace.c -- deinterlace filter * Copyright (C) 2003-2014 Meltytech, LLC * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "deinterlace.h" #include "yadif.h" #include #include #include #include #include #include #include #define YADIF_MODE_TEMPORAL_SPATIAL (0) #define YADIF_MODE_TEMPORAL (2) static yadif_filter *init_yadif(int width, int height) { yadif_filter *yadif = mlt_pool_alloc(sizeof(*yadif)); yadif->cpu = 0; // Pure C #ifdef USE_SSE yadif->cpu |= AVS_CPU_INTEGER_SSE; #endif #ifdef USE_SSE2 yadif->cpu |= AVS_CPU_SSE2; #endif // Create intermediate planar planes yadif->yheight = height; yadif->ywidth = width; yadif->uvwidth = yadif->ywidth / 2; yadif->ypitch = (yadif->ywidth + 15) / 16 * 16; yadif->uvpitch = (yadif->uvwidth + 15) / 16 * 16; yadif->ysrc = (unsigned char *) mlt_pool_alloc(yadif->yheight * yadif->ypitch); yadif->usrc = (unsigned char *) mlt_pool_alloc(yadif->yheight * yadif->uvpitch); yadif->vsrc = (unsigned char *) mlt_pool_alloc(yadif->yheight * yadif->uvpitch); yadif->yprev = (unsigned char *) mlt_pool_alloc(yadif->yheight * yadif->ypitch); yadif->uprev = (unsigned char *) mlt_pool_alloc(yadif->yheight * yadif->uvpitch); yadif->vprev = (unsigned char *) mlt_pool_alloc(yadif->yheight * yadif->uvpitch); yadif->ynext = (unsigned char *) mlt_pool_alloc(yadif->yheight * yadif->ypitch); yadif->unext = (unsigned char *) mlt_pool_alloc(yadif->yheight * yadif->uvpitch); yadif->vnext = (unsigned char *) mlt_pool_alloc(yadif->yheight * yadif->uvpitch); yadif->ydest = (unsigned char *) mlt_pool_alloc(yadif->yheight * yadif->ypitch); yadif->udest = (unsigned char *) mlt_pool_alloc(yadif->yheight * yadif->uvpitch); yadif->vdest = (unsigned char *) mlt_pool_alloc(yadif->yheight * yadif->uvpitch); return yadif; } static void close_yadif(yadif_filter *yadif) { mlt_pool_release(yadif->ysrc); mlt_pool_release(yadif->usrc); mlt_pool_release(yadif->vsrc); mlt_pool_release(yadif->yprev); mlt_pool_release(yadif->uprev); mlt_pool_release(yadif->vprev); mlt_pool_release(yadif->ynext); mlt_pool_release(yadif->unext); mlt_pool_release(yadif->vnext); mlt_pool_release(yadif->ydest); mlt_pool_release(yadif->udest); mlt_pool_release(yadif->vdest); mlt_pool_release(yadif); #if defined(__GNUC__) && !defined(PIC) // Set SSSE3 bit to cpu asm("mov $1, %%eax \n\t" "push %%ebx \n\t" "cpuid \n\t" "pop %%ebx \n\t" "mov %%ecx, %%edx \n\t" "shr $9, %%edx \n\t" "and $1, %%edx \n\t" "shl $9, %%edx \n\t" "and $511, %%ebx \n\t" "or %%edx, %%ebx \n\t" : "=b"(yadif->cpu) : "p"(yadif->cpu) : "%eax", "%ecx", "%edx"); #endif } static int deinterlace_yadif(mlt_frame frame, mlt_filter filter, uint8_t **image, mlt_image_format *format, int *width, int *height, int mode) { mlt_properties properties = MLT_FRAME_PROPERTIES(frame); mlt_frame previous_frame = mlt_properties_get_data(properties, "previous frame", NULL); uint8_t *previous_image = NULL; int previous_width = *width; int previous_height = *height; mlt_frame next_frame = mlt_properties_get_data(properties, "next frame", NULL); uint8_t *next_image = NULL; int next_width = *width; int next_height = *height; mlt_log_debug(MLT_FILTER_SERVICE(filter), "previous " MLT_POSITION_FMT " current " MLT_POSITION_FMT " next " MLT_POSITION_FMT "\n", previous_frame ? mlt_frame_original_position(previous_frame) : -1, mlt_frame_original_position(frame), next_frame ? mlt_frame_original_position(next_frame) : -1); if (!previous_frame || !next_frame) return 1; mlt_service_lock(MLT_FILTER_SERVICE(filter)); // Get the preceding frame's image int error = mlt_frame_get_image(previous_frame, &previous_image, format, &previous_width, &previous_height, 0); int progressive = mlt_properties_get_int(MLT_FRAME_PROPERTIES(previous_frame), "progressive"); // Check that we aren't already progressive if (!error && previous_image && !progressive) { // OK, now we know we have work to do and can request the image in our format frame->convert_image(previous_frame, &previous_image, format, mlt_image_yuv422); mlt_service_unlock(MLT_FILTER_SERVICE(filter)); // Get the current frame's image *format = mlt_image_yuv422; error = mlt_frame_get_image(frame, image, format, width, height, 1); if (!error && *image && *format == mlt_image_yuv422) { // Get the following frame's image error = mlt_frame_get_image(next_frame, &next_image, format, &next_width, &next_height, 0); if (!error && next_image && *format == mlt_image_yuv422) { yadif_filter *yadif = init_yadif(*width, *height); if (yadif) { const int order = mlt_properties_get_int(properties, "top_field_first"); const int pitch = *width << 1; const int parity = 0; // Convert packed to planar YUY2ToPlanes(*image, pitch, *width, *height, yadif->ysrc, yadif->ypitch, yadif->usrc, yadif->vsrc, yadif->uvpitch, yadif->cpu); YUY2ToPlanes(previous_image, pitch, *width, *height, yadif->yprev, yadif->ypitch, yadif->uprev, yadif->vprev, yadif->uvpitch, yadif->cpu); YUY2ToPlanes(next_image, pitch, *width, *height, yadif->ynext, yadif->ypitch, yadif->unext, yadif->vnext, yadif->uvpitch, yadif->cpu); // Deinterlace each plane filter_plane(mode, yadif->ydest, yadif->ypitch, yadif->yprev, yadif->ysrc, yadif->ynext, yadif->ypitch, *width, *height, parity, order, yadif->cpu); filter_plane(mode, yadif->udest, yadif->uvpitch, yadif->uprev, yadif->usrc, yadif->unext, yadif->uvpitch, *width >> 1, *height, parity, order, yadif->cpu); filter_plane(mode, yadif->vdest, yadif->uvpitch, yadif->vprev, yadif->vsrc, yadif->vnext, yadif->uvpitch, *width >> 1, *height, parity, order, yadif->cpu); // Convert planar to packed YUY2FromPlanes(*image, pitch, *width, *height, yadif->ydest, yadif->ypitch, yadif->udest, yadif->vdest, yadif->uvpitch, yadif->cpu); close_yadif(yadif); } } } } else { mlt_service_unlock(MLT_FILTER_SERVICE(filter)); // Get the current frame's image error = mlt_frame_get_image(frame, image, format, width, height, 0); } return error; } /** Do it :-). */ static int filter_get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { int error = 0; mlt_filter filter = mlt_frame_pop_service(frame); mlt_properties properties = MLT_FRAME_PROPERTIES(frame); int deinterlace = mlt_properties_get_int(properties, "consumer.progressive"); // The progressive var should only represent the frame's original state and not the state // as modified by this filter! int progressive = mlt_properties_get_int(properties, "progressive"); // At this point - before image was requested - (progressive == 0) cannot be trusted because // some producers (avformat) do not yet know. if (deinterlace && !mlt_properties_get_int(properties, "test_image")) { // Determine deinterlace method char *method_str = mlt_properties_get(MLT_FILTER_PROPERTIES(filter), "method"); int method = DEINTERLACE_NONE; char *frame_method_str = mlt_properties_get(properties, "consumer.deinterlacer"); if (frame_method_str) method_str = frame_method_str; if (!method_str || strcmp(method_str, "yadif") == 0) method = DEINTERLACE_YADIF; else if (strcmp(method_str, "yadif-nospatial") == 0) method = DEINTERLACE_YADIF_NOSPATIAL; else if (strcmp(method_str, "onefield") == 0) method = DEINTERLACE_ONEFIELD; else if (strcmp(method_str, "linearblend") == 0) method = DEINTERLACE_LINEARBLEND; else if (strcmp(method_str, "bob") == 0) method = DEINTERLACE_BOB; else if (strcmp(method_str, "weave") == 0) method = DEINTERLACE_BOB; else if (strcmp(method_str, "greedy") == 0) method = DEINTERLACE_GREEDY; // Some producers like pixbuf want rescale_width & _height, but will not get them if you request // the previous image first. So, on the first iteration, we use linearblend. if ((method == DEINTERLACE_YADIF || method == DEINTERLACE_YADIF_NOSPATIAL) && !mlt_properties_get_int(MLT_FILTER_PROPERTIES(filter), "_notfirst")) { mlt_properties_set_int(MLT_FILTER_PROPERTIES(filter), "_notfirst", 1); method = DEINTERLACE_LINEARBLEND; error = 1; } if (method == DEINTERLACE_YADIF) { error = deinterlace_yadif(frame, filter, image, format, width, height, YADIF_MODE_TEMPORAL_SPATIAL); } else if (method == DEINTERLACE_YADIF_NOSPATIAL) { error = deinterlace_yadif(frame, filter, image, format, width, height, YADIF_MODE_TEMPORAL); } if (error || (method > DEINTERLACE_NONE && method < DEINTERLACE_YADIF)) { mlt_service service = mlt_properties_get_data(MLT_FILTER_PROPERTIES(filter), "service", NULL); // Get the current frame's image int error2 = mlt_frame_get_image(frame, image, format, width, height, writable); progressive = mlt_properties_get_int(properties, "progressive"); if (error) { method = DEINTERLACE_LINEARBLEND; // If YADIF requested, prev/next cancelled because some previous frames were progressive, // but new frames are interlaced, then turn prev/next frames back on. if (!progressive) mlt_properties_set_int(MLT_SERVICE_PROPERTIES(service), "_need_previous_next", 1); } else { // Signal that we no longer need previous and next frames mlt_properties_set_int(MLT_SERVICE_PROPERTIES(service), "_need_previous_next", 0); } error = error2; if (!error && !progressive) { // OK, now we know we have work to do and can request the image in our format error = frame->convert_image(frame, image, format, mlt_image_yuv422); // Check that we aren't already progressive if (!error && *image && *format == mlt_image_yuv422) { // Deinterlace the image using one of the Xine deinterlacers int image_size = mlt_image_format_size(*format, *width, *height, NULL); uint8_t *new_image = mlt_pool_alloc(image_size); deinterlace_yuv(new_image, image, *width * 2, *height, method); mlt_frame_set_image(frame, new_image, image_size, mlt_pool_release); *image = new_image; } } } else if (method == DEINTERLACE_NONE) { error = mlt_frame_get_image(frame, image, format, width, height, writable); } // update progressive flag after having obtained image progressive = mlt_properties_get_int(properties, "progressive"); mlt_log_debug(MLT_FILTER_SERVICE(filter), "error %d deint %d prog %d fmt %s method %s\n", error, deinterlace, progressive, mlt_image_format_name(*format), method_str ? method_str : "yadif"); if (!error) { // Make sure that others know the frame is deinterlaced mlt_properties_set_int(properties, "progressive", 1); } } else { // Pass through error = mlt_frame_get_image(frame, image, format, width, height, writable); } if (!deinterlace || progressive) { // Signal that we no longer need previous and next frames mlt_service service = mlt_properties_get_data(MLT_FILTER_PROPERTIES(filter), "service", NULL); if (service) mlt_properties_set_int(MLT_SERVICE_PROPERTIES(service), "_need_previous_next", 0); } return error; } /** Deinterlace filter processing - this should be lazy evaluation here... */ static mlt_frame deinterlace_process(mlt_filter filter, mlt_frame frame) { // Push filter on to the service stack mlt_frame_push_service(frame, filter); // Push the get_image method on to the stack mlt_frame_push_get_image(frame, filter_get_image); return frame; } static void on_service_changed(mlt_service owner, mlt_service filter) { mlt_service service = mlt_properties_get_data(MLT_SERVICE_PROPERTIES(filter), "service", NULL); mlt_properties_set_int(MLT_SERVICE_PROPERTIES(service), "_need_previous_next", 1); } /** Constructor for the filter. */ mlt_filter filter_deinterlace_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_filter filter = mlt_filter_new(); if (filter != NULL) { filter->process = deinterlace_process; mlt_properties_set(MLT_FILTER_PROPERTIES(filter), "method", arg); mlt_events_listen(MLT_FILTER_PROPERTIES(filter), filter, "service-changed", (mlt_listener) on_service_changed); } return filter; } mlt-7.22.0/src/modules/xine/filter_deinterlace.yml000664 000000 000000 00000000674 14531534050 022136 0ustar00rootroot000000 000000 schema_version: 7.0 type: filter identifier: deinterlace title: Xine Deinterlacer version: 1 copyright: Meltytech, LLC license: GPLv2 language: en url: http://xinehq.de/ tags: - Video - Hidden description: Deinterlace interlaced video. notes: > This is not intended to be created directly. Rather, the loader producer loads it if it is available to set deinterlace interlaced input when the consumer or profile is set to progressive. mlt-7.22.0/src/modules/xine/gpl000664 000000 000000 00000000000 14531534050 016253 0ustar00rootroot000000 000000 mlt-7.22.0/src/modules/xine/link_deinterlace.c000664 000000 000000 00000021556 14531534050 021231 0ustar00rootroot000000 000000 /* * link_deinterlace.c * Copyright (C) 2023 Meltytech, LLC * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "common.h" #include #include #include #include #include // Private Types typedef struct { // Used by get_frame and get_image int prev_next_required; } private_data; static void link_configure(mlt_link self, mlt_profile chain_profile) { // Operate at the same frame rate as the next link mlt_service_set_profile(MLT_LINK_SERVICE(self), mlt_service_profile(MLT_PRODUCER_SERVICE(self->next))); } static int link_get_image(mlt_frame frame, uint8_t **image, mlt_image_format *format, int *width, int *height, int writable) { int error = 0; mlt_link self = (mlt_link) mlt_frame_pop_service(frame); private_data *pdata = (private_data *) self->child; mlt_properties frame_properties = MLT_FRAME_PROPERTIES(frame); struct mlt_image_s srcimg = {0}; struct mlt_image_s dstimg = {0}; struct mlt_image_s previmg = {0}; struct mlt_image_s nextimg = {0}; mlt_deinterlacer method = mlt_deinterlacer_id( mlt_properties_get(frame_properties, "consumer.deinterlacer")); if (!mlt_properties_get_int(frame_properties, "consumer.progressive") || method == mlt_deinterlacer_none || mlt_frame_is_test_card(frame)) { mlt_log_debug(MLT_LINK_SERVICE(self), "Do not deinterlace\n"); return mlt_frame_get_image(frame, image, format, width, height, writable); } // At this point, we know we need to deinterlace. method = supported_method(method); if (method == mlt_deinterlacer_weave || method == mlt_deinterlacer_greedy || method >= mlt_deinterlacer_yadif_nospatial) { pdata->prev_next_required = 1; } if (srcimg.data) // Maybe already received during progressive check { if (srcimg.format != mlt_image_yuv422) { error = frame->convert_image(frame, (uint8_t **) &srcimg.data, &srcimg.format, mlt_image_yuv422); if (error) { mlt_log_error(MLT_LINK_SERVICE(self), "Failed to convert image\n"); return error; } } } else { mlt_image_set_values(&srcimg, NULL, mlt_image_yuv422, *width, *height); error = mlt_frame_get_image(frame, (uint8_t **) &srcimg.data, &srcimg.format, &srcimg.width, &srcimg.height, 0); if (error) { mlt_log_error(MLT_LINK_SERVICE(self), "Failed to get image\n"); return error; } } mlt_image_set_values(&dstimg, NULL, srcimg.format, srcimg.width, srcimg.height); mlt_image_alloc_data(&dstimg); if (pdata->prev_next_required) { mlt_properties unique_properties = mlt_frame_unique_properties(frame, MLT_LINK_SERVICE(self)); mlt_frame prevframe = mlt_properties_get_data(unique_properties, "prev", NULL); if (prevframe) { mlt_image_set_values(&previmg, NULL, mlt_image_yuv422, srcimg.width, srcimg.height); error = mlt_frame_get_image(prevframe, (uint8_t **) &previmg.data, &previmg.format, &previmg.width, &previmg.height, 0); if (error) { mlt_log_error(MLT_LINK_SERVICE(self), "Failed to get prev image\n"); previmg.data = NULL; } } mlt_frame nextframe = mlt_properties_get_data(unique_properties, "next", NULL); if (nextframe) { mlt_image_set_values(&nextimg, NULL, mlt_image_yuv422, srcimg.width, srcimg.height); error = mlt_frame_get_image(nextframe, (uint8_t **) &nextimg.data, &nextimg.format, &nextimg.width, &nextimg.height, 0); if (error) { mlt_log_error(MLT_LINK_SERVICE(self), "Failed to get next image\n"); nextimg.data = NULL; } } } int tff = mlt_properties_get_int(MLT_FRAME_PROPERTIES(frame), "top_field_first"); error = deinterlace_image(&dstimg, &srcimg, &previmg, &nextimg, tff, method); if (error) { mlt_log_error(MLT_LINK_SERVICE(self), "Deinterlace failed\n"); return error; } mlt_image_get_values(&dstimg, (void **) image, format, width, height); mlt_frame_set_image(frame, dstimg.data, 0, dstimg.release_data); mlt_properties_set_int(frame_properties, "progressive", 1); return 0; } static int link_get_frame(mlt_link self, mlt_frame_ptr frame, int index) { int error = 0; private_data *pdata = (private_data *) self->child; mlt_position frame_pos = mlt_producer_position(MLT_LINK_PRODUCER(self)); mlt_producer_seek(self->next, frame_pos); error = mlt_service_get_frame(MLT_PRODUCER_SERVICE(self->next), frame, index); mlt_producer original_producer = mlt_frame_get_original_producer(*frame); mlt_producer_probe(original_producer); if (mlt_properties_get_int(MLT_PRODUCER_PROPERTIES(original_producer), "meta.media.progressive") || mlt_properties_get_int(MLT_PRODUCER_PROPERTIES(original_producer), "progressive")) { // Source is progressive. Deinterlace is not required; return error; } mlt_frame prev = NULL; mlt_frame next = NULL; if (pdata->prev_next_required) { mlt_properties unique_properties = mlt_frame_unique_properties(*frame, MLT_LINK_SERVICE(self)); mlt_producer_seek(self->next, frame_pos - 1); error = mlt_service_get_frame(MLT_PRODUCER_SERVICE(self->next), &prev, index); if (error) { mlt_log_error(MLT_LINK_SERVICE(self), "Unable to get prev: %d\n", frame_pos); } mlt_properties_set_data(unique_properties, "prev", prev, 0, (mlt_destructor) mlt_frame_close, NULL); mlt_producer_seek(self->next, frame_pos + 1); error = mlt_service_get_frame(MLT_PRODUCER_SERVICE(self->next), &next, index); if (error) { mlt_log_error(MLT_LINK_SERVICE(self), "Unable to get next: %d\n", frame_pos); } mlt_properties_set_data(unique_properties, "next", next, 0, (mlt_destructor) mlt_frame_close, NULL); } mlt_frame_push_service(*frame, self); mlt_frame_push_get_image(*frame, link_get_image); mlt_producer_prepare_next(MLT_LINK_PRODUCER(self)); return error; } static void link_close(mlt_link self) { if (self) { private_data *pdata = (private_data *) self->child; free(pdata); self->close = NULL; self->child = NULL; mlt_link_close(self); free(self); } } mlt_link link_deinterlace_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { mlt_link self = mlt_link_init(); private_data *pdata = (private_data *) calloc(1, sizeof(private_data)); if (self && pdata) { self->child = pdata; // Callback registration self->configure = link_configure; self->get_frame = link_get_frame; self->close = link_close; } else { free(pdata); mlt_link_close(self); self = NULL; } return self; } mlt-7.22.0/src/modules/xine/link_deinterlace.yml000664 000000 000000 00000000575 14531534050 021606 0ustar00rootroot000000 000000 schema_version: 7.0 type: link identifier: deinterlace title: Xine Deinterlacer version: 1 copyright: Meltytech, LLC license: GPLv2 language: en url: http://xinehq.de/ tags: - Video - Hidden description: > Deinterlace interlaced video. This link can be added to a chain to normalize video from the producer to provide deinterlaced images if requested by the consumer. mlt-7.22.0/src/modules/xine/vf_yadif_template.h000664 000000 000000 00000025266 14531534050 021426 0ustar00rootroot000000 000000 /* * Copyright (C) 2006 Michael Niedermayer * * SSE2/SSSE3 version (custom optimization) by h.yamagata * * Small fix by Alexander Balakhnin (fizick@avisynth.org.ru) * * MPlayer is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * MPlayer is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with MPlayer; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include #define LOAD8(mem,dst) \ "movq "mem", "#dst" \n\t"\ "punpcklbw %%xmm7, "#dst" \n\t" #define CHECK(pj,mj) \ "movdqu "#pj"(%[cur],%[mrefs]), %%xmm2 \n\t" /* cur[x-refs-1+j] */\ "movdqu "#mj"(%[cur],%[prefs]), %%xmm3 \n\t" /* cur[x+refs-1-j] */\ "movdqa %%xmm2, %%xmm4 \n\t"\ "movdqa %%xmm2, %%xmm5 \n\t"\ "pxor %%xmm3, %%xmm4 \n\t"\ "pavgb %%xmm3, %%xmm5 \n\t"\ "pand %[pb1], %%xmm4 \n\t"\ "psubusb %%xmm4, %%xmm5 \n\t"\ "psrldq $1, %%xmm5 \n\t"\ "punpcklbw %%xmm7, %%xmm5 \n\t" /* (cur[x-refs+j] + cur[x+refs-j])>>1 */\ "movdqa %%xmm2, %%xmm4 \n\t"\ "psubusb %%xmm3, %%xmm2 \n\t"\ "psubusb %%xmm4, %%xmm3 \n\t"\ "pmaxub %%xmm3, %%xmm2 \n\t"\ "movdqa %%xmm2, %%xmm3 \n\t"\ "movdqa %%xmm2, %%xmm4 \n\t" /* ABS(cur[x-refs-1+j] - cur[x+refs-1-j]) */\ "psrldq $1, %%xmm3 \n\t" /* ABS(cur[x-refs +j] - cur[x+refs -j]) */\ "psrldq $2, %%xmm4 \n\t" /* ABS(cur[x-refs+1+j] - cur[x+refs+1-j]) */\ "punpcklbw %%xmm7, %%xmm2 \n\t"\ "punpcklbw %%xmm7, %%xmm3 \n\t"\ "punpcklbw %%xmm7, %%xmm4 \n\t"\ "paddw %%xmm3, %%xmm2 \n\t"\ "paddw %%xmm4, %%xmm2 \n\t" /* score */ #define CHECK1 \ "movdqa %%xmm0, %%xmm3 \n\t"\ "pcmpgtw %%xmm2, %%xmm3 \n\t" /* if(score < spatial_score) */\ "pminsw %%xmm2, %%xmm0 \n\t" /* spatial_score= score; */\ "movdqa %%xmm3, %%xmm6 \n\t"\ "pand %%xmm3, %%xmm5 \n\t"\ "pandn %%xmm1, %%xmm3 \n\t"\ "por %%xmm5, %%xmm3 \n\t"\ "movdqa %%xmm3, %%xmm1 \n\t" /* spatial_pred= (cur[x-refs+j] + cur[x+refs-j])>>1; */ #define CHECK2 /* pretend not to have checked dir=2 if dir=1 was bad.\ hurts both quality and speed, but matches the C version. */\ "paddw %[pw1], %%xmm6 \n\t"\ "psllw $14, %%xmm6 \n\t"\ "paddsw %%xmm6, %%xmm2 \n\t"\ "movdqa %%xmm0, %%xmm3 \n\t"\ "pcmpgtw %%xmm2, %%xmm3 \n\t"\ "pminsw %%xmm2, %%xmm0 \n\t"\ "pand %%xmm3, %%xmm5 \n\t"\ "pandn %%xmm1, %%xmm3 \n\t"\ "por %%xmm5, %%xmm3 \n\t"\ "movdqa %%xmm3, %%xmm1 \n\t" /* mode argument mod - Fizick */ /* static attribute_align_arg void FILTER_LINE_FUNC_NAME(YadifContext *yadctx, uint8_t *dst, uint8_t *prev, uint8_t *cur, uint8_t *next, int w, int refs, int parity){ const int mode = yadctx->mode; */ static attribute_align_arg void FILTER_LINE_FUNC_NAME(int mode, uint8_t *dst, const uint8_t *prev, const uint8_t *cur, const uint8_t *next, int w, int refs, int parity){ DECLARE_ALIGNED(16, uint8_t, tmp0[16]); DECLARE_ALIGNED(16, uint8_t, tmp1[16]); DECLARE_ALIGNED(16, uint8_t, tmp2[16]); DECLARE_ALIGNED(16, uint8_t, tmp3[16]); int x; static DECLARE_ALIGNED(16, const unsigned short, pw_1[]) = { 0x0001,0x0001,0x0001,0x0001,0x0001,0x0001,0x0001,0x0001 }; static DECLARE_ALIGNED(16, const unsigned short, pb_1[]) = { 0x0101,0x0101,0x0101,0x0101,0x0101,0x0101,0x0101,0x0101 }; #define FILTER\ for(x=0; x>1 */\ "movdqa %%xmm0, %[tmp0] \n\t" /* c */\ "movdqa %%xmm3, %[tmp1] \n\t" /* d */\ "movdqa %%xmm1, %[tmp2] \n\t" /* e */\ "psubw %%xmm4, %%xmm2 \n\t"\ PABS( %%xmm4, %%xmm2) /* temporal_diff0 */\ LOAD8("(%[prev],%[mrefs])", %%xmm3) /* prev[x-refs] */\ LOAD8("(%[prev],%[prefs])", %%xmm4) /* prev[x+refs] */\ "psubw %%xmm0, %%xmm3 \n\t"\ "psubw %%xmm1, %%xmm4 \n\t"\ PABS( %%xmm5, %%xmm3)\ PABS( %%xmm5, %%xmm4)\ "paddw %%xmm4, %%xmm3 \n\t" /* temporal_diff1 */\ "psrlw $1, %%xmm2 \n\t"\ "psrlw $1, %%xmm3 \n\t"\ "pmaxsw %%xmm3, %%xmm2 \n\t"\ LOAD8("(%[next],%[mrefs])", %%xmm3) /* next[x-refs] */\ LOAD8("(%[next],%[prefs])", %%xmm4) /* next[x+refs] */\ "psubw %%xmm0, %%xmm3 \n\t"\ "psubw %%xmm1, %%xmm4 \n\t"\ PABS( %%xmm5, %%xmm3)\ PABS( %%xmm5, %%xmm4)\ "paddw %%xmm4, %%xmm3 \n\t" /* temporal_diff2 */\ "psrlw $1, %%xmm3 \n\t"\ "pmaxsw %%xmm3, %%xmm2 \n\t"\ "movdqa %%xmm2, %[tmp3] \n\t" /* diff */\ \ "paddw %%xmm0, %%xmm1 \n\t"\ "paddw %%xmm0, %%xmm0 \n\t"\ "psubw %%xmm1, %%xmm0 \n\t"\ "psrlw $1, %%xmm1 \n\t" /* spatial_pred */\ PABS( %%xmm2, %%xmm0) /* ABS(c-e) */\ \ "movdqu -1(%[cur],%[mrefs]), %%xmm2 \n\t" /* cur[x-refs-1] */\ "movdqu -1(%[cur],%[prefs]), %%xmm3 \n\t" /* cur[x+refs-1] */\ "movdqa %%xmm2, %%xmm4 \n\t"\ "psubusb %%xmm3, %%xmm2 \n\t"\ "psubusb %%xmm4, %%xmm3 \n\t"\ "pmaxub %%xmm3, %%xmm2 \n\t"\ /*"pshuflw $9,%%xmm2, %%xmm3 \n\t"*/\ /*"pshufhw $9,%%xmm2, %%xmm3 \n\t"*/\ "movdqa %%xmm2, %%xmm3 \n\t" /* correct replacement (here) */\ "psrldq $2, %%xmm3 \n\t"/* for "pshufw $9,%%mm2, %%mm3" - fix by Fizick */\ "punpcklbw %%xmm7, %%xmm2 \n\t" /* ABS(cur[x-refs-1] - cur[x+refs-1]) */\ "punpcklbw %%xmm7, %%xmm3 \n\t" /* ABS(cur[x-refs+1] - cur[x+refs+1]) */\ "paddw %%xmm2, %%xmm0 \n\t"\ "paddw %%xmm3, %%xmm0 \n\t"\ "psubw %[pw1], %%xmm0 \n\t" /* spatial_score */\ \ CHECK(-2,0)\ CHECK1\ CHECK(-3,1)\ CHECK2\ CHECK(0,-2)\ CHECK1\ CHECK(1,-3)\ CHECK2\ \ /* if(yadctx->mode<2) ... */\ "movdqa %[tmp3], %%xmm6 \n\t" /* diff */\ "cmpl $2, %[mode] \n\t"\ "jge 1f \n\t"\ LOAD8("(%["prev2"],%[mrefs],2)", %%xmm2) /* prev2[x-2*refs] */\ LOAD8("(%["next2"],%[mrefs],2)", %%xmm4) /* next2[x-2*refs] */\ LOAD8("(%["prev2"],%[prefs],2)", %%xmm3) /* prev2[x+2*refs] */\ LOAD8("(%["next2"],%[prefs],2)", %%xmm5) /* next2[x+2*refs] */\ "paddw %%xmm4, %%xmm2 \n\t"\ "paddw %%xmm5, %%xmm3 \n\t"\ "psrlw $1, %%xmm2 \n\t" /* b */\ "psrlw $1, %%xmm3 \n\t" /* f */\ "movdqa %[tmp0], %%xmm4 \n\t" /* c */\ "movdqa %[tmp1], %%xmm5 \n\t" /* d */\ "movdqa %[tmp2], %%xmm7 \n\t" /* e */\ "psubw %%xmm4, %%xmm2 \n\t" /* b-c */\ "psubw %%xmm7, %%xmm3 \n\t" /* f-e */\ "movdqa %%xmm5, %%xmm0 \n\t"\ "psubw %%xmm4, %%xmm5 \n\t" /* d-c */\ "psubw %%xmm7, %%xmm0 \n\t" /* d-e */\ "movdqa %%xmm2, %%xmm4 \n\t"\ "pminsw %%xmm3, %%xmm2 \n\t"\ "pmaxsw %%xmm4, %%xmm3 \n\t"\ "pmaxsw %%xmm5, %%xmm2 \n\t"\ "pminsw %%xmm5, %%xmm3 \n\t"\ "pmaxsw %%xmm0, %%xmm2 \n\t" /* max */\ "pminsw %%xmm0, %%xmm3 \n\t" /* min */\ "pxor %%xmm4, %%xmm4 \n\t"\ "pmaxsw %%xmm3, %%xmm6 \n\t"\ "psubw %%xmm2, %%xmm4 \n\t" /* -max */\ "pmaxsw %%xmm4, %%xmm6 \n\t" /* diff= MAX3(diff, min, -max); */\ "1: \n\t"\ \ "movdqa %[tmp1], %%xmm2 \n\t" /* d */\ "movdqa %%xmm2, %%xmm3 \n\t"\ "psubw %%xmm6, %%xmm2 \n\t" /* d-diff */\ "paddw %%xmm6, %%xmm3 \n\t" /* d+diff */\ "pmaxsw %%xmm2, %%xmm1 \n\t"\ "pminsw %%xmm3, %%xmm1 \n\t" /* d = clip(spatial_pred, d-diff, d+diff); */\ "packuswb %%xmm1, %%xmm1 \n\t"\ \ :[tmp0]"=m"(tmp0),\ [tmp1]"=m"(tmp1),\ [tmp2]"=m"(tmp2),\ [tmp3]"=m"(tmp3)\ :[prev] "r"(prev),\ [cur] "r"(cur),\ [next] "r"(next),\ [prefs]"r"((intptr_t)refs),\ [mrefs]"r"((intptr_t)-refs),\ [pw1] "m"(*pw_1),\ [pb1] "m"(*pb_1),\ [mode] "g"(mode)\ );\ __asm__ volatile("movq %%xmm1, %0" :"=m"(*dst));\ dst += 8;\ prev+= 8;\ cur += 8;\ next+= 8;\ } if(parity){ #define prev2 "prev" #define next2 "cur" FILTER #undef prev2 #undef next2 }else{ #define prev2 "cur" #define next2 "next" FILTER #undef prev2 #undef next2 } } #undef LOAD8 #undef PABS #undef CHECK #undef CHECK1 #undef CHECK2 #undef FILTER #undef FILTER_LINE_FUNC_NAME mlt-7.22.0/src/modules/xine/xineutils.h000664 000000 000000 00000111210 14531534050 017751 0ustar00rootroot000000 000000 /* * Copyright (C) 2000-2004 the xine project * * This file is part of xine, a free video player. * * xine is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * xine is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * */ #ifndef XINEUTILS_H #define XINEUTILS_H #ifdef __cplusplus extern "C" { #endif #include #include #include #include #include #include #if HAVE_LIBGEN_H # include #endif //#ifdef XINE_COMPILE # include "attributes.h" //# include "compat.h" //# include "xmlparser.h" //# include "xine_buffer.h" //# include "configfile.h" //#else //# include //# include //# include //# include //# include //#endif //#ifdef HAVE_CONFIG_H //#include "config.h" //#endif #include #include /* * debugable mutexes */ typedef struct { pthread_mutex_t mutex; char id[80]; char *locked_by; } xine_mutex_t; int xine_mutex_init (xine_mutex_t *mutex, const pthread_mutexattr_t *mutexattr, char *id); int xine_mutex_lock (xine_mutex_t *mutex, char *who); int xine_mutex_unlock (xine_mutex_t *mutex, char *who); int xine_mutex_destroy (xine_mutex_t *mutex); /* CPU Acceleration */ /* * The type of an value that fits in an MMX register (note that long * long constant values MUST be suffixed by LL and unsigned long long * values by ULL, lest they be truncated by the compiler) */ /* generic accelerations */ #define MM_ACCEL_MLIB 0x00000001 /* x86 accelerations */ #define MM_ACCEL_X86_MMX 0x80000000 #define MM_ACCEL_X86_3DNOW 0x40000000 #define MM_ACCEL_X86_MMXEXT 0x20000000 #define MM_ACCEL_X86_SSE 0x10000000 #define MM_ACCEL_X86_SSE2 0x08000000 /* powerpc accelerations */ #define MM_ACCEL_PPC_ALTIVEC 0x04000000 /* x86 compat defines */ #define MM_MMX MM_ACCEL_X86_MMX #define MM_3DNOW MM_ACCEL_X86_3DNOW #define MM_MMXEXT MM_ACCEL_X86_MMXEXT #define MM_SSE MM_ACCEL_X86_SSE #define MM_SSE2 MM_ACCEL_X86_SSE2 uint32_t xine_mm_accel (void); #ifdef USE_MMX typedef union { int64_t q; /* Quadword (64-bit) value */ uint64_t uq; /* Unsigned Quadword */ int d[2]; /* 2 Doubleword (32-bit) values */ unsigned int ud[2]; /* 2 Unsigned Doubleword */ short w[4]; /* 4 Word (16-bit) values */ unsigned short uw[4]; /* 4 Unsigned Word */ char b[8]; /* 8 Byte (8-bit) values */ unsigned char ub[8]; /* 8 Unsigned Byte */ float s[2]; /* Single-precision (32-bit) value */ } ATTR_ALIGN(8) mmx_t; /* On an 8-byte (64-bit) boundary */ #define mmx_i2r(op,imm,reg) \ __asm__ __volatile__ (#op " %0, %%" #reg \ : /* nothing */ \ : "i" (imm) ) #define mmx_m2r(op,mem,reg) \ __asm__ __volatile__ (#op " %0, %%" #reg \ : /* nothing */ \ : "m" (mem)) #define mmx_r2m(op,reg,mem) \ __asm__ __volatile__ (#op " %%" #reg ", %0" \ : "=m" (mem) \ : /* nothing */ ) #define mmx_r2r(op,regs,regd) \ __asm__ __volatile__ (#op " %" #regs ", %" #regd) #define emms() __asm__ __volatile__ ("emms") #define movd_m2r(var,reg) mmx_m2r (movd, var, reg) #define movd_r2m(reg,var) mmx_r2m (movd, reg, var) #define movd_r2r(regs,regd) mmx_r2r (movd, regs, regd) #define movq_m2r(var,reg) mmx_m2r (movq, var, reg) #define movq_r2m(reg,var) mmx_r2m (movq, reg, var) #define movq_r2r(regs,regd) mmx_r2r (movq, regs, regd) #define packssdw_m2r(var,reg) mmx_m2r (packssdw, var, reg) #define packssdw_r2r(regs,regd) mmx_r2r (packssdw, regs, regd) #define packsswb_m2r(var,reg) mmx_m2r (packsswb, var, reg) #define packsswb_r2r(regs,regd) mmx_r2r (packsswb, regs, regd) #define packuswb_m2r(var,reg) mmx_m2r (packuswb, var, reg) #define packuswb_r2r(regs,regd) mmx_r2r (packuswb, regs, regd) #define paddb_m2r(var,reg) mmx_m2r (paddb, var, reg) #define paddb_r2r(regs,regd) mmx_r2r (paddb, regs, regd) #define paddd_m2r(var,reg) mmx_m2r (paddd, var, reg) #define paddd_r2r(regs,regd) mmx_r2r (paddd, regs, regd) #define paddw_m2r(var,reg) mmx_m2r (paddw, var, reg) #define paddw_r2r(regs,regd) mmx_r2r (paddw, regs, regd) #define paddsb_m2r(var,reg) mmx_m2r (paddsb, var, reg) #define paddsb_r2r(regs,regd) mmx_r2r (paddsb, regs, regd) #define paddsw_m2r(var,reg) mmx_m2r (paddsw, var, reg) #define paddsw_r2r(regs,regd) mmx_r2r (paddsw, regs, regd) #define paddusb_m2r(var,reg) mmx_m2r (paddusb, var, reg) #define paddusb_r2r(regs,regd) mmx_r2r (paddusb, regs, regd) #define paddusw_m2r(var,reg) mmx_m2r (paddusw, var, reg) #define paddusw_r2r(regs,regd) mmx_r2r (paddusw, regs, regd) #define pand_m2r(var,reg) mmx_m2r (pand, var, reg) #define pand_r2r(regs,regd) mmx_r2r (pand, regs, regd) #define pandn_m2r(var,reg) mmx_m2r (pandn, var, reg) #define pandn_r2r(regs,regd) mmx_r2r (pandn, regs, regd) #define pcmpeqb_m2r(var,reg) mmx_m2r (pcmpeqb, var, reg) #define pcmpeqb_r2r(regs,regd) mmx_r2r (pcmpeqb, regs, regd) #define pcmpeqd_m2r(var,reg) mmx_m2r (pcmpeqd, var, reg) #define pcmpeqd_r2r(regs,regd) mmx_r2r (pcmpeqd, regs, regd) #define pcmpeqw_m2r(var,reg) mmx_m2r (pcmpeqw, var, reg) #define pcmpeqw_r2r(regs,regd) mmx_r2r (pcmpeqw, regs, regd) #define pcmpgtb_m2r(var,reg) mmx_m2r (pcmpgtb, var, reg) #define pcmpgtb_r2r(regs,regd) mmx_r2r (pcmpgtb, regs, regd) #define pcmpgtd_m2r(var,reg) mmx_m2r (pcmpgtd, var, reg) #define pcmpgtd_r2r(regs,regd) mmx_r2r (pcmpgtd, regs, regd) #define pcmpgtw_m2r(var,reg) mmx_m2r (pcmpgtw, var, reg) #define pcmpgtw_r2r(regs,regd) mmx_r2r (pcmpgtw, regs, regd) #define pmaddwd_m2r(var,reg) mmx_m2r (pmaddwd, var, reg) #define pmaddwd_r2r(regs,regd) mmx_r2r (pmaddwd, regs, regd) #define pmulhw_m2r(var,reg) mmx_m2r (pmulhw, var, reg) #define pmulhw_r2r(regs,regd) mmx_r2r (pmulhw, regs, regd) #define pmullw_m2r(var,reg) mmx_m2r (pmullw, var, reg) #define pmullw_r2r(regs,regd) mmx_r2r (pmullw, regs, regd) #define por_m2r(var,reg) mmx_m2r (por, var, reg) #define por_r2r(regs,regd) mmx_r2r (por, regs, regd) #define pslld_i2r(imm,reg) mmx_i2r (pslld, imm, reg) #define pslld_m2r(var,reg) mmx_m2r (pslld, var, reg) #define pslld_r2r(regs,regd) mmx_r2r (pslld, regs, regd) #define psllq_i2r(imm,reg) mmx_i2r (psllq, imm, reg) #define psllq_m2r(var,reg) mmx_m2r (psllq, var, reg) #define psllq_r2r(regs,regd) mmx_r2r (psllq, regs, regd) #define psllw_i2r(imm,reg) mmx_i2r (psllw, imm, reg) #define psllw_m2r(var,reg) mmx_m2r (psllw, var, reg) #define psllw_r2r(regs,regd) mmx_r2r (psllw, regs, regd) #define psrad_i2r(imm,reg) mmx_i2r (psrad, imm, reg) #define psrad_m2r(var,reg) mmx_m2r (psrad, var, reg) #define psrad_r2r(regs,regd) mmx_r2r (psrad, regs, regd) #define psraw_i2r(imm,reg) mmx_i2r (psraw, imm, reg) #define psraw_m2r(var,reg) mmx_m2r (psraw, var, reg) #define psraw_r2r(regs,regd) mmx_r2r (psraw, regs, regd) #define psrld_i2r(imm,reg) mmx_i2r (psrld, imm, reg) #define psrld_m2r(var,reg) mmx_m2r (psrld, var, reg) #define psrld_r2r(regs,regd) mmx_r2r (psrld, regs, regd) #define psrlq_i2r(imm,reg) mmx_i2r (psrlq, imm, reg) #define psrlq_m2r(var,reg) mmx_m2r (psrlq, var, reg) #define psrlq_r2r(regs,regd) mmx_r2r (psrlq, regs, regd) #define psrlw_i2r(imm,reg) mmx_i2r (psrlw, imm, reg) #define psrlw_m2r(var,reg) mmx_m2r (psrlw, var, reg) #define psrlw_r2r(regs,regd) mmx_r2r (psrlw, regs, regd) #define psubb_m2r(var,reg) mmx_m2r (psubb, var, reg) #define psubb_r2r(regs,regd) mmx_r2r (psubb, regs, regd) #define psubd_m2r(var,reg) mmx_m2r (psubd, var, reg) #define psubd_r2r(regs,regd) mmx_r2r (psubd, regs, regd) #define psubw_m2r(var,reg) mmx_m2r (psubw, var, reg) #define psubw_r2r(regs,regd) mmx_r2r (psubw, regs, regd) #define psubsb_m2r(var,reg) mmx_m2r (psubsb, var, reg) #define psubsb_r2r(regs,regd) mmx_r2r (psubsb, regs, regd) #define psubsw_m2r(var,reg) mmx_m2r (psubsw, var, reg) #define psubsw_r2r(regs,regd) mmx_r2r (psubsw, regs, regd) #define psubusb_m2r(var,reg) mmx_m2r (psubusb, var, reg) #define psubusb_r2r(regs,regd) mmx_r2r (psubusb, regs, regd) #define psubusw_m2r(var,reg) mmx_m2r (psubusw, var, reg) #define psubusw_r2r(regs,regd) mmx_r2r (psubusw, regs, regd) #define punpckhbw_m2r(var,reg) mmx_m2r (punpckhbw, var, reg) #define punpckhbw_r2r(regs,regd) mmx_r2r (punpckhbw, regs, regd) #define punpckhdq_m2r(var,reg) mmx_m2r (punpckhdq, var, reg) #define punpckhdq_r2r(regs,regd) mmx_r2r (punpckhdq, regs, regd) #define punpckhwd_m2r(var,reg) mmx_m2r (punpckhwd, var, reg) #define punpckhwd_r2r(regs,regd) mmx_r2r (punpckhwd, regs, regd) #define punpcklbw_m2r(var,reg) mmx_m2r (punpcklbw, var, reg) #define punpcklbw_r2r(regs,regd) mmx_r2r (punpcklbw, regs, regd) #define punpckldq_m2r(var,reg) mmx_m2r (punpckldq, var, reg) #define punpckldq_r2r(regs,regd) mmx_r2r (punpckldq, regs, regd) #define punpcklwd_m2r(var,reg) mmx_m2r (punpcklwd, var, reg) #define punpcklwd_r2r(regs,regd) mmx_r2r (punpcklwd, regs, regd) #define pxor_m2r(var,reg) mmx_m2r (pxor, var, reg) #define pxor_r2r(regs,regd) mmx_r2r (pxor, regs, regd) /* 3DNOW extensions */ #define pavgusb_m2r(var,reg) mmx_m2r (pavgusb, var, reg) #define pavgusb_r2r(regs,regd) mmx_r2r (pavgusb, regs, regd) /* AMD MMX extensions - also available in intel SSE */ #define mmx_m2ri(op,mem,reg,imm) \ __asm__ __volatile__ (#op " %1, %0, %%" #reg \ : /* nothing */ \ : "X" (mem), "X" (imm)) #define mmx_r2ri(op,regs,regd,imm) \ __asm__ __volatile__ (#op " %0, %%" #regs ", %%" #regd \ : /* nothing */ \ : "X" (imm) ) #define mmx_fetch(mem,hint) \ __asm__ __volatile__ ("prefetch" #hint " %0" \ : /* nothing */ \ : "X" (mem)) #define maskmovq(regs,maskreg) mmx_r2ri (maskmovq, regs, maskreg) #define movntq_r2m(mmreg,var) mmx_r2m (movntq, mmreg, var) #define pavgb_m2r(var,reg) mmx_m2r (pavgb, var, reg) #define pavgb_r2r(regs,regd) mmx_r2r (pavgb, regs, regd) #define pavgw_m2r(var,reg) mmx_m2r (pavgw, var, reg) #define pavgw_r2r(regs,regd) mmx_r2r (pavgw, regs, regd) #define pextrw_r2r(mmreg,reg,imm) mmx_r2ri (pextrw, mmreg, reg, imm) #define pinsrw_r2r(reg,mmreg,imm) mmx_r2ri (pinsrw, reg, mmreg, imm) #define pmaxsw_m2r(var,reg) mmx_m2r (pmaxsw, var, reg) #define pmaxsw_r2r(regs,regd) mmx_r2r (pmaxsw, regs, regd) #define pmaxub_m2r(var,reg) mmx_m2r (pmaxub, var, reg) #define pmaxub_r2r(regs,regd) mmx_r2r (pmaxub, regs, regd) #define pminsw_m2r(var,reg) mmx_m2r (pminsw, var, reg) #define pminsw_r2r(regs,regd) mmx_r2r (pminsw, regs, regd) #define pminub_m2r(var,reg) mmx_m2r (pminub, var, reg) #define pminub_r2r(regs,regd) mmx_r2r (pminub, regs, regd) #define pmovmskb(mmreg,reg) \ __asm__ __volatile__ ("movmskps %" #mmreg ", %" #reg) #define pmulhuw_m2r(var,reg) mmx_m2r (pmulhuw, var, reg) #define pmulhuw_r2r(regs,regd) mmx_r2r (pmulhuw, regs, regd) #define prefetcht0(mem) mmx_fetch (mem, t0) #define prefetcht1(mem) mmx_fetch (mem, t1) #define prefetcht2(mem) mmx_fetch (mem, t2) #define prefetchnta(mem) mmx_fetch (mem, nta) #define psadbw_m2r(var,reg) mmx_m2r (psadbw, var, reg) #define psadbw_r2r(regs,regd) mmx_r2r (psadbw, regs, regd) #define pshufw_m2r(var,reg,imm) mmx_m2ri(pshufw, var, reg, imm) #define pshufw_r2r(regs,regd,imm) mmx_r2ri(pshufw, regs, regd, imm) #define sfence() __asm__ __volatile__ ("sfence\n\t") typedef union { float sf[4]; /* Single-precision (32-bit) value */ } ATTR_ALIGN(16) sse_t; /* On a 16 byte (128-bit) boundary */ #define sse_i2r(op, imm, reg) \ __asm__ __volatile__ (#op " %0, %%" #reg \ : /* nothing */ \ : "X" (imm) ) #define sse_m2r(op, mem, reg) \ __asm__ __volatile__ (#op " %0, %%" #reg \ : /* nothing */ \ : "X" (mem)) #define sse_r2m(op, reg, mem) \ __asm__ __volatile__ (#op " %%" #reg ", %0" \ : "=X" (mem) \ : /* nothing */ ) #define sse_r2r(op, regs, regd) \ __asm__ __volatile__ (#op " %" #regs ", %" #regd) #define sse_r2ri(op, regs, regd, imm) \ __asm__ __volatile__ (#op " %0, %%" #regs ", %%" #regd \ : /* nothing */ \ : "X" (imm) ) #define sse_m2ri(op, mem, reg, subop) \ __asm__ __volatile__ (#op " %0, %%" #reg ", " #subop \ : /* nothing */ \ : "X" (mem)) #define movaps_m2r(var, reg) sse_m2r(movaps, var, reg) #define movaps_r2m(reg, var) sse_r2m(movaps, reg, var) #define movaps_r2r(regs, regd) sse_r2r(movaps, regs, regd) #define movntps_r2m(xmmreg, var) sse_r2m(movntps, xmmreg, var) #define movups_m2r(var, reg) sse_m2r(movups, var, reg) #define movups_r2m(reg, var) sse_r2m(movups, reg, var) #define movups_r2r(regs, regd) sse_r2r(movups, regs, regd) #define movhlps_r2r(regs, regd) sse_r2r(movhlps, regs, regd) #define movlhps_r2r(regs, regd) sse_r2r(movlhps, regs, regd) #define movhps_m2r(var, reg) sse_m2r(movhps, var, reg) #define movhps_r2m(reg, var) sse_r2m(movhps, reg, var) #define movlps_m2r(var, reg) sse_m2r(movlps, var, reg) #define movlps_r2m(reg, var) sse_r2m(movlps, reg, var) #define movss_m2r(var, reg) sse_m2r(movss, var, reg) #define movss_r2m(reg, var) sse_r2m(movss, reg, var) #define movss_r2r(regs, regd) sse_r2r(movss, regs, regd) #define shufps_m2r(var, reg, index) sse_m2ri(shufps, var, reg, index) #define shufps_r2r(regs, regd, index) sse_r2ri(shufps, regs, regd, index) #define cvtpi2ps_m2r(var, xmmreg) sse_m2r(cvtpi2ps, var, xmmreg) #define cvtpi2ps_r2r(mmreg, xmmreg) sse_r2r(cvtpi2ps, mmreg, xmmreg) #define cvtps2pi_m2r(var, mmreg) sse_m2r(cvtps2pi, var, mmreg) #define cvtps2pi_r2r(xmmreg, mmreg) sse_r2r(cvtps2pi, mmreg, xmmreg) #define cvttps2pi_m2r(var, mmreg) sse_m2r(cvttps2pi, var, mmreg) #define cvttps2pi_r2r(xmmreg, mmreg) sse_r2r(cvttps2pi, mmreg, xmmreg) #define cvtsi2ss_m2r(var, xmmreg) sse_m2r(cvtsi2ss, var, xmmreg) #define cvtsi2ss_r2r(reg, xmmreg) sse_r2r(cvtsi2ss, reg, xmmreg) #define cvtss2si_m2r(var, reg) sse_m2r(cvtss2si, var, reg) #define cvtss2si_r2r(xmmreg, reg) sse_r2r(cvtss2si, xmmreg, reg) #define cvttss2si_m2r(var, reg) sse_m2r(cvtss2si, var, reg) #define cvttss2si_r2r(xmmreg, reg) sse_r2r(cvtss2si, xmmreg, reg) #define movmskps(xmmreg, reg) \ __asm__ __volatile__ ("movmskps %" #xmmreg ", %" #reg) #define addps_m2r(var, reg) sse_m2r(addps, var, reg) #define addps_r2r(regs, regd) sse_r2r(addps, regs, regd) #define addss_m2r(var, reg) sse_m2r(addss, var, reg) #define addss_r2r(regs, regd) sse_r2r(addss, regs, regd) #define subps_m2r(var, reg) sse_m2r(subps, var, reg) #define subps_r2r(regs, regd) sse_r2r(subps, regs, regd) #define subss_m2r(var, reg) sse_m2r(subss, var, reg) #define subss_r2r(regs, regd) sse_r2r(subss, regs, regd) #define mulps_m2r(var, reg) sse_m2r(mulps, var, reg) #define mulps_r2r(regs, regd) sse_r2r(mulps, regs, regd) #define mulss_m2r(var, reg) sse_m2r(mulss, var, reg) #define mulss_r2r(regs, regd) sse_r2r(mulss, regs, regd) #define divps_m2r(var, reg) sse_m2r(divps, var, reg) #define divps_r2r(regs, regd) sse_r2r(divps, regs, regd) #define divss_m2r(var, reg) sse_m2r(divss, var, reg) #define divss_r2r(regs, regd) sse_r2r(divss, regs, regd) #define rcpps_m2r(var, reg) sse_m2r(rcpps, var, reg) #define rcpps_r2r(regs, regd) sse_r2r(rcpps, regs, regd) #define rcpss_m2r(var, reg) sse_m2r(rcpss, var, reg) #define rcpss_r2r(regs, regd) sse_r2r(rcpss, regs, regd) #define rsqrtps_m2r(var, reg) sse_m2r(rsqrtps, var, reg) #define rsqrtps_r2r(regs, regd) sse_r2r(rsqrtps, regs, regd) #define rsqrtss_m2r(var, reg) sse_m2r(rsqrtss, var, reg) #define rsqrtss_r2r(regs, regd) sse_r2r(rsqrtss, regs, regd) #define sqrtps_m2r(var, reg) sse_m2r(sqrtps, var, reg) #define sqrtps_r2r(regs, regd) sse_r2r(sqrtps, regs, regd) #define sqrtss_m2r(var, reg) sse_m2r(sqrtss, var, reg) #define sqrtss_r2r(regs, regd) sse_r2r(sqrtss, regs, regd) #define andps_m2r(var, reg) sse_m2r(andps, var, reg) #define andps_r2r(regs, regd) sse_r2r(andps, regs, regd) #define andnps_m2r(var, reg) sse_m2r(andnps, var, reg) #define andnps_r2r(regs, regd) sse_r2r(andnps, regs, regd) #define orps_m2r(var, reg) sse_m2r(orps, var, reg) #define orps_r2r(regs, regd) sse_r2r(orps, regs, regd) #define xorps_m2r(var, reg) sse_m2r(xorps, var, reg) #define xorps_r2r(regs, regd) sse_r2r(xorps, regs, regd) #define maxps_m2r(var, reg) sse_m2r(maxps, var, reg) #define maxps_r2r(regs, regd) sse_r2r(maxps, regs, regd) #define maxss_m2r(var, reg) sse_m2r(maxss, var, reg) #define maxss_r2r(regs, regd) sse_r2r(maxss, regs, regd) #define minps_m2r(var, reg) sse_m2r(minps, var, reg) #define minps_r2r(regs, regd) sse_r2r(minps, regs, regd) #define minss_m2r(var, reg) sse_m2r(minss, var, reg) #define minss_r2r(regs, regd) sse_r2r(minss, regs, regd) #define cmpps_m2r(var, reg, op) sse_m2ri(cmpps, var, reg, op) #define cmpps_r2r(regs, regd, op) sse_r2ri(cmpps, regs, regd, op) #define cmpeqps_m2r(var, reg) sse_m2ri(cmpps, var, reg, 0) #define cmpeqps_r2r(regs, regd) sse_r2ri(cmpps, regs, regd, 0) #define cmpltps_m2r(var, reg) sse_m2ri(cmpps, var, reg, 1) #define cmpltps_r2r(regs, regd) sse_r2ri(cmpps, regs, regd, 1) #define cmpleps_m2r(var, reg) sse_m2ri(cmpps, var, reg, 2) #define cmpleps_r2r(regs, regd) sse_r2ri(cmpps, regs, regd, 2) #define cmpunordps_m2r(var, reg) sse_m2ri(cmpps, var, reg, 3) #define cmpunordps_r2r(regs, regd) sse_r2ri(cmpps, regs, regd, 3) #define cmpneqps_m2r(var, reg) sse_m2ri(cmpps, var, reg, 4) #define cmpneqps_r2r(regs, regd) sse_r2ri(cmpps, regs, regd, 4) #define cmpnltps_m2r(var, reg) sse_m2ri(cmpps, var, reg, 5) #define cmpnltps_r2r(regs, regd) sse_r2ri(cmpps, regs, regd, 5) #define cmpnleps_m2r(var, reg) sse_m2ri(cmpps, var, reg, 6) #define cmpnleps_r2r(regs, regd) sse_r2ri(cmpps, regs, regd, 6) #define cmpordps_m2r(var, reg) sse_m2ri(cmpps, var, reg, 7) #define cmpordps_r2r(regs, regd) sse_r2ri(cmpps, regs, regd, 7) #define cmpss_m2r(var, reg, op) sse_m2ri(cmpss, var, reg, op) #define cmpss_r2r(regs, regd, op) sse_r2ri(cmpss, regs, regd, op) #define cmpeqss_m2r(var, reg) sse_m2ri(cmpss, var, reg, 0) #define cmpeqss_r2r(regs, regd) sse_r2ri(cmpss, regs, regd, 0) #define cmpltss_m2r(var, reg) sse_m2ri(cmpss, var, reg, 1) #define cmpltss_r2r(regs, regd) sse_r2ri(cmpss, regs, regd, 1) #define cmpless_m2r(var, reg) sse_m2ri(cmpss, var, reg, 2) #define cmpless_r2r(regs, regd) sse_r2ri(cmpss, regs, regd, 2) #define cmpunordss_m2r(var, reg) sse_m2ri(cmpss, var, reg, 3) #define cmpunordss_r2r(regs, regd) sse_r2ri(cmpss, regs, regd, 3) #define cmpneqss_m2r(var, reg) sse_m2ri(cmpss, var, reg, 4) #define cmpneqss_r2r(regs, regd) sse_r2ri(cmpss, regs, regd, 4) #define cmpnltss_m2r(var, reg) sse_m2ri(cmpss, var, reg, 5) #define cmpnltss_r2r(regs, regd) sse_r2ri(cmpss, regs, regd, 5) #define cmpnless_m2r(var, reg) sse_m2ri(cmpss, var, reg, 6) #define cmpnless_r2r(regs, regd) sse_r2ri(cmpss, regs, regd, 6) #define cmpordss_m2r(var, reg) sse_m2ri(cmpss, var, reg, 7) #define cmpordss_r2r(regs, regd) sse_r2ri(cmpss, regs, regd, 7) #define comiss_m2r(var, reg) sse_m2r(comiss, var, reg) #define comiss_r2r(regs, regd) sse_r2r(comiss, regs, regd) #define ucomiss_m2r(var, reg) sse_m2r(ucomiss, var, reg) #define ucomiss_r2r(regs, regd) sse_r2r(ucomiss, regs, regd) #define unpcklps_m2r(var, reg) sse_m2r(unpcklps, var, reg) #define unpcklps_r2r(regs, regd) sse_r2r(unpcklps, regs, regd) #define unpckhps_m2r(var, reg) sse_m2r(unpckhps, var, reg) #define unpckhps_r2r(regs, regd) sse_r2r(unpckhps, regs, regd) #define fxrstor(mem) \ __asm__ __volatile__ ("fxrstor %0" \ : /* nothing */ \ : "X" (mem)) #define fxsave(mem) \ __asm__ __volatile__ ("fxsave %0" \ : /* nothing */ \ : "X" (mem)) #define stmxcsr(mem) \ __asm__ __volatile__ ("stmxcsr %0" \ : /* nothing */ \ : "X" (mem)) #define ldmxcsr(mem) \ __asm__ __volatile__ ("ldmxcsr %0" \ : /* nothing */ \ : "X" (mem)) #endif /* USE_MMX */ /* Optimized/fast memcpy */ /* TODO : fix dll linkage problem for xine_fast_memcpy on win32 xine_fast_memcpy dll linkage is screwy here. declaring as dllimport seems to fix the problem but causes compiler warning with libxineutils */ #ifdef _MSC_VER __declspec( dllimport ) extern void *(* xine_fast_memcpy)(void *to, const void *from, size_t len); #else extern void *(* xine_fast_memcpy)(void *to, const void *from, size_t len); #endif #ifdef HAVE_XINE_INTERNAL_H /* Benchmark available memcpy methods */ void xine_probe_fast_memcpy(xine_t *xine); #endif /* * Debug stuff */ /* * profiling (unworkable in non DEBUG isn't defined) */ void xine_profiler_init (void); int xine_profiler_allocate_slot (char *label); void xine_profiler_start_count (int id); void xine_profiler_stop_count (int id); void xine_profiler_print_results (void); /* * Allocate and clean memory size_t 'size', then return the pointer * to the allocated memory. */ #if !defined(__GNUC__) || __GNUC__ < 3 void *xine_xmalloc(size_t size); #else void *xine_xmalloc(size_t size) __attribute__ ((__malloc__)); #endif /* * Same as above, but memory is aligned to 'alignement'. * **base is used to return pointer to un-aligned memory, use * this to free the mem chunk */ void *xine_xmalloc_aligned(size_t alignment, size_t size, void **base); /* * Get user home directory. */ const char *xine_get_homedir(void); /* * Clean a string (remove spaces and '=' at the begin, * and '\n', '\r' and spaces at the end. */ char *xine_chomp (char *str); /* * A thread-safe usecond sleep */ void xine_usec_sleep(unsigned usec); /* * Some string functions */ void xine_strdupa(char *dest, char *src); #define xine_strdupa(d, s) do { \ (d) = NULL; \ if((s) != NULL) { \ (d) = (char *) alloca(strlen((s)) + 1); \ strcpy((d), (s)); \ } \ } while(0) /* Shamefully copied from glibc 2.2.3 */ #ifdef HAVE_STRPBRK #define xine_strpbrk strpbrk #else static inline char *_private_strpbrk(char *s, const char *accept) { while(*s != '\0') { const char *a = accept; while(*a != '\0') if(*a++ == *s) return s; ++s; } return NULL; } #define xine_strpbrk _private_strpbrk #endif #if defined HAVE_STRSEP && !defined(_MSC_VER) #define xine_strsep strsep #else static inline char *_private_strsep(char **stringp, const char *delim) { char *begin, *end; begin = *stringp; if(begin == NULL) return NULL; if(delim[0] == '\0' || delim[1] == '\0') { char ch = delim[0]; if(ch == '\0') end = NULL; else { if(*begin == ch) end = begin; else if(*begin == '\0') end = NULL; else end = strchr(begin + 1, ch); } } else end = xine_strpbrk(begin, delim); if(end) { *end++ = '\0'; *stringp = end; } else *stringp = NULL; return begin; } #define xine_strsep _private_strsep #endif #ifdef HAVE_SETENV #define xine_setenv setenv #else static inline void _private_setenv(const char *name, const char *val, int _xx) { int len = strlen(name) + strlen(val) + 2; char env[len]; sprintf(env, "%s%c%s", name, '=', val); putenv(env); } #define xine_setenv _private_setenv #endif /* * Color Conversion Utility Functions * The following data structures and functions facilitate the conversion * of RGB images to packed YUV (YUY2) images. There are also functions to * convert from YUV9 -> YV12. All of the meaty details are written in * color.c. */ typedef struct yuv_planes_s { unsigned char *y; unsigned char *u; unsigned char *v; unsigned int row_width; /* frame width */ unsigned int row_count; /* frame height */ } yuv_planes_t; void init_yuv_conversion(void); void init_yuv_planes(yuv_planes_t *yuv_planes, int width, int height); void free_yuv_planes(yuv_planes_t *yuv_planes); extern void (*yuv444_to_yuy2) (yuv_planes_t *yuv_planes, unsigned char *yuy2_map, int pitch); extern void (*yuv9_to_yv12) (unsigned char *y_src, int y_src_pitch, unsigned char *y_dest, int y_dest_pitch, unsigned char *u_src, int u_src_pitch, unsigned char *u_dest, int u_dest_pitch, unsigned char *v_src, int v_src_pitch, unsigned char *v_dest, int v_dest_pitch, int width, int height); extern void (*yuv411_to_yv12) (unsigned char *y_src, int y_src_pitch, unsigned char *y_dest, int y_dest_pitch, unsigned char *u_src, int u_src_pitch, unsigned char *u_dest, int u_dest_pitch, unsigned char *v_src, int v_src_pitch, unsigned char *v_dest, int v_dest_pitch, int width, int height); extern void (*yv12_to_yuy2) (unsigned char *y_src, int y_src_pitch, unsigned char *u_src, int u_src_pitch, unsigned char *v_src, int v_src_pitch, unsigned char *yuy2_map, int yuy2_pitch, int width, int height, int progressive); extern void (*yuy2_to_yv12) (unsigned char *yuy2_map, int yuy2_pitch, unsigned char *y_dst, int y_dst_pitch, unsigned char *u_dst, int u_dst_pitch, unsigned char *v_dst, int v_dst_pitch, int width, int height); #define SCALEFACTOR 65536 #define CENTERSAMPLE 128 #define COMPUTE_Y(r, g, b) \ (unsigned char) \ ((y_r_table[r] + y_g_table[g] + y_b_table[b]) / SCALEFACTOR) #define COMPUTE_U(r, g, b) \ (unsigned char) \ ((u_r_table[r] + u_g_table[g] + u_b_table[b]) / SCALEFACTOR + CENTERSAMPLE) #define COMPUTE_V(r, g, b) \ (unsigned char) \ ((v_r_table[r] + v_g_table[g] + v_b_table[b]) / SCALEFACTOR + CENTERSAMPLE) #define UNPACK_BGR15(packed_pixel, r, g, b) \ b = (packed_pixel & 0x7C00) >> 7; \ g = (packed_pixel & 0x03E0) >> 2; \ r = (packed_pixel & 0x001F) << 3; #define UNPACK_BGR16(packed_pixel, r, g, b) \ b = (packed_pixel & 0xF800) >> 8; \ g = (packed_pixel & 0x07E0) >> 3; \ r = (packed_pixel & 0x001F) << 3; #define UNPACK_RGB15(packed_pixel, r, g, b) \ r = (packed_pixel & 0x7C00) >> 7; \ g = (packed_pixel & 0x03E0) >> 2; \ b = (packed_pixel & 0x001F) << 3; #define UNPACK_RGB16(packed_pixel, r, g, b) \ r = (packed_pixel & 0xF800) >> 8; \ g = (packed_pixel & 0x07E0) >> 3; \ b = (packed_pixel & 0x001F) << 3; extern int y_r_table[256]; extern int y_g_table[256]; extern int y_b_table[256]; extern int u_r_table[256]; extern int u_g_table[256]; extern int u_b_table[256]; extern int v_r_table[256]; extern int v_g_table[256]; extern int v_b_table[256]; /* frame copying functions */ extern void yv12_to_yv12 (unsigned char *y_src, int y_src_pitch, unsigned char *y_dst, int y_dst_pitch, unsigned char *u_src, int u_src_pitch, unsigned char *u_dst, int u_dst_pitch, unsigned char *v_src, int v_src_pitch, unsigned char *v_dst, int v_dst_pitch, int width, int height); extern void yuy2_to_yuy2 (unsigned char *src, int src_pitch, unsigned char *dst, int dst_pitch, int width, int height); /* print a hexdump of the given data */ void xine_hexdump (const char *buf, int length); /* * Optimization macros for conditions * Taken from the FIASCO L4 microkernel sources */ #if !defined(__GNUC__) || __GNUC__ < 3 # define EXPECT_TRUE(x) (x) # define EXPECT_FALSE(x) (x) #else # define EXPECT_TRUE(x) __builtin_expect((x),1) # define EXPECT_FALSE(x) __builtin_expect((x),0) #endif #ifdef NDEBUG #define _x_assert(exp) \ do { \ if (!(exp)) \ fprintf(stderr, "assert: %s:%d: %s: Assertion `%s' failed.\n", \ __FILE__, __LINE__, __XINE_FUNCTION__, #exp); \ } while(0) #else #define _x_assert(exp) \ do { \ if (!(exp)) { \ fprintf(stderr, "assert: %s:%d: %s: Assertion `%s' failed.\n", \ __FILE__, __LINE__, __XINE_FUNCTION__, #exp); \ abort(); \ } \ } while(0) #endif #define _x_abort() \ do { \ fprintf(stderr, "abort: %s:%d: %s: Aborting.\n", \ __FILE__, __LINE__, __XINE_FUNCTION__); \ abort(); \ } while(0) /****** logging with xine **********************************/ #ifndef LOG_MODULE #define LOG_MODULE __FILE__ #endif /* LOG_MODULE */ #define LOG_MODULE_STRING printf("%s: ", LOG_MODULE ); #ifdef LOG_VERBOSE #define LONG_LOG_MODULE_STRING \ printf("%s: (%s:%d) ", LOG_MODULE, __XINE_FUNCTION__, __LINE__ ); #else #define LONG_LOG_MODULE_STRING LOG_MODULE_STRING #endif /* LOG_VERBOSE */ #ifdef LOG #ifdef __GNUC__ #define lprintf(fmt, args...) \ do { \ LONG_LOG_MODULE_STRING \ printf(fmt, ##args); \ } while(0) #else /* __GNUC__ */ #ifdef _MSC_VER #define lprintf(fmtargs) \ do { \ LONG_LOG_MODULE_STRING \ printf("%s", fmtargs); \ } while(0) #else /* _MSC_VER */ #define lprintf(fmt, ...) \ do { \ LONG_LOG_MODULE_STRING \ printf(__VA_ARGS__); \ } while(0) #endif /* _MSC_VER */ #endif /* __GNUC__ */ #else /* LOG */ #ifdef __GNUC__ #define lprintf(fmt, args...) do {} while(0) #else #ifdef _MSC_VER #define lprintf #else #define lprintf(...) do {} while(0) #endif /* _MSC_VER */ #endif /* __GNUC__ */ #endif /* LOG */ #ifdef __GNUC__ #define llprintf(cat, fmt, args...) \ do{ \ if(cat){ \ LONG_LOG_MODULE_STRING \ printf( fmt, ##args ); \ } \ }while(0) #else #ifdef _MSC_VER #define llprintf(cat, fmtargs) \ do{ \ if(cat){ \ LONG_LOG_MODULE_STRING \ printf( "%s", fmtargs ); \ } \ }while(0) #else #define llprintf(cat, ...) \ do{ \ if(cat){ \ LONG_LOG_MODULE_STRING \ printf( __VA_ARGS__ ); \ } \ }while(0) #endif /* _MSC_VER */ #endif /* __GNUC__ */ #ifdef __GNUC__ #define xprintf(xine, verbose, fmt, args...) \ do { \ if((xine) && (xine)->verbosity >= verbose){ \ xine_log(xine, XINE_LOG_TRACE, fmt, ##args); \ } \ } while(0) #else #ifdef _MSC_VER #define xprintf(xine, verbose, fmtargs) \ do { \ if((xine) && (xine)->verbosity >= verbose){ \ xine_log(xine, XINE_LOG_TRACE, fmtargs); \ } \ } while(0) #else #define xprintf(xine, verbose, ...) \ do { \ if((xine) && (xine)->verbosity >= verbose){ \ xine_log(xine, XINE_LOG_TRACE, __VA_ARGS__); \ } \ } while(0) #endif /* _MSC_VER */ #endif /* __GNUC__ */ /* time measuring macros for profiling tasks */ #ifdef DEBUG # define XINE_PROFILE(function) \ do { \ struct timeval current_time; \ double dtime; \ gettimeofday(¤t_time, NULL); \ dtime = -(current_time.tv_sec + (current_time.tv_usec / 1000000.0)); \ function; \ gettimeofday(¤t_time, NULL); \ dtime += current_time.tv_sec + (current_time.tv_usec / 1000000.0); \ printf("%s: (%s:%d) took %lf seconds\n", \ LOG_MODULE, __XINE_FUNCTION__, __LINE__, dtime); \ } while(0) # define XINE_PROFILE_ACCUMULATE(function) \ do { \ struct timeval current_time; \ static double dtime = 0; \ gettimeofday(¤t_time, NULL); \ dtime -= current_time.tv_sec + (current_time.tv_usec / 1000000.0); \ function; \ gettimeofday(¤t_time, NULL); \ dtime += current_time.tv_sec + (current_time.tv_usec / 1000000.0); \ printf("%s: (%s:%d) took %lf seconds\n", \ LOG_MODULE, __XINE_FUNCTION__, __LINE__, dtime); \ } while(0) #else # define XINE_PROFILE(function) function # define XINE_PROFILE_ACCUMULATE(function) function #endif /* LOG */ /******** double chained lists with builtin iterator *******/ typedef struct xine_node_s { struct xine_node_s *next, *prev; void *content; int priority; } xine_node_t; typedef struct { xine_node_t *first, *last, *cur; } xine_list_t; xine_list_t *xine_list_new (void); /** * dispose the whole list. * note: disposes _only_ the list structure, content must be free()d elsewhere */ void xine_list_free(xine_list_t *l); /** * returns: Boolean */ int xine_list_is_empty (xine_list_t *l); /** * return content of first entry in list. */ void *xine_list_first_content (xine_list_t *l); /** * return next content in list. */ void *xine_list_next_content (xine_list_t *l); /** * Return last content of list. */ void *xine_list_last_content (xine_list_t *l); /** * Return previous content of list. */ void *xine_list_prev_content (xine_list_t *l); /** * Append content to list, sorted by decreasing priority. */ void xine_list_append_priority_content (xine_list_t *l, void *content, int priority); /** * Append content to list. */ void xine_list_append_content (xine_list_t *l, void *content); /** * Insert content in list. */ void xine_list_insert_content (xine_list_t *l, void *content); /** * Remove current content in list. * note: removes only the list entry; content must be free()d elsewhere. */ void xine_list_delete_current (xine_list_t *l); #ifndef HAVE_BASENAME /* * get base name */ char *basename (char const *name); #endif #ifdef __cplusplus } #endif #endif mlt-7.22.0/src/modules/xine/yadif.c000664 000000 000000 00000050442 14531534050 017025 0ustar00rootroot000000 000000 /* Yadif C-plugin for Avisynth 2.5 - Yet Another DeInterlacing Filter Copyright (C)2007 Alexander G. Balakhnin aka Fizick http://avisynth.org.ru Port of YADIF filter from MPlayer Copyright (C) 2006 Michael Niedermayer This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. Avisynth_C plugin Assembler optimized for GNU C compiler */ #include "yadif.h" #include #include #include #define MIN(a,b) ((a) > (b) ? (b) : (a)) #define MAX(a,b) ((a) < (b) ? (b) : (a)) #define ABS(a) ((a) > 0 ? (a) : (-(a))) #define MIN3(a,b,c) MIN(MIN(a,b),c) #define MAX3(a,b,c) MAX(MAX(a,b),c) static void (*filter_line)(int mode, uint8_t *dst, const uint8_t *prev, const uint8_t *cur, const uint8_t *next, int w, int refs, int parity); #if defined(__GNUC__) && defined(USE_SSE) #define LOAD4(mem,dst) \ "movd "mem", "#dst" \n\t"\ "punpcklbw %%mm7, "#dst" \n\t" #define PABS(tmp,dst) \ "pxor "#tmp", "#tmp" \n\t"\ "psubw "#dst", "#tmp" \n\t"\ "pmaxsw "#tmp", "#dst" \n\t" #define CHECK(pj,mj) \ "movq "#pj"(%[cur],%[mrefs]), %%mm2 \n\t" /* cur[x-refs-1+j] */\ "movq "#mj"(%[cur],%[prefs]), %%mm3 \n\t" /* cur[x+refs-1-j] */\ "movq %%mm2, %%mm4 \n\t"\ "movq %%mm2, %%mm5 \n\t"\ "pxor %%mm3, %%mm4 \n\t"\ "pavgb %%mm3, %%mm5 \n\t"\ "pand %[pb1], %%mm4 \n\t"\ "psubusb %%mm4, %%mm5 \n\t"\ "psrlq $8, %%mm5 \n\t"\ "punpcklbw %%mm7, %%mm5 \n\t" /* (cur[x-refs+j] + cur[x+refs-j])>>1 */\ "movq %%mm2, %%mm4 \n\t"\ "psubusb %%mm3, %%mm2 \n\t"\ "psubusb %%mm4, %%mm3 \n\t"\ "pmaxub %%mm3, %%mm2 \n\t"\ "movq %%mm2, %%mm3 \n\t"\ "movq %%mm2, %%mm4 \n\t" /* ABS(cur[x-refs-1+j] - cur[x+refs-1-j]) */\ "psrlq $8, %%mm3 \n\t" /* ABS(cur[x-refs +j] - cur[x+refs -j]) */\ "psrlq $16, %%mm4 \n\t" /* ABS(cur[x-refs+1+j] - cur[x+refs+1-j]) */\ "punpcklbw %%mm7, %%mm2 \n\t"\ "punpcklbw %%mm7, %%mm3 \n\t"\ "punpcklbw %%mm7, %%mm4 \n\t"\ "paddw %%mm3, %%mm2 \n\t"\ "paddw %%mm4, %%mm2 \n\t" /* score */ #define CHECK1 \ "movq %%mm0, %%mm3 \n\t"\ "pcmpgtw %%mm2, %%mm3 \n\t" /* if(score < spatial_score) */\ "pminsw %%mm2, %%mm0 \n\t" /* spatial_score= score; */\ "movq %%mm3, %%mm6 \n\t"\ "pand %%mm3, %%mm5 \n\t"\ "pandn %%mm1, %%mm3 \n\t"\ "por %%mm5, %%mm3 \n\t"\ "movq %%mm3, %%mm1 \n\t" /* spatial_pred= (cur[x-refs+j] + cur[x+refs-j])>>1; */ #define CHECK2 /* pretend not to have checked dir=2 if dir=1 was bad.\ hurts both quality and speed, but matches the C version. */\ "paddw %[pw1], %%mm6 \n\t"\ "psllw $14, %%mm6 \n\t"\ "paddsw %%mm6, %%mm2 \n\t"\ "movq %%mm0, %%mm3 \n\t"\ "pcmpgtw %%mm2, %%mm3 \n\t"\ "pminsw %%mm2, %%mm0 \n\t"\ "pand %%mm3, %%mm5 \n\t"\ "pandn %%mm1, %%mm3 \n\t"\ "por %%mm5, %%mm3 \n\t"\ "movq %%mm3, %%mm1 \n\t" static void filter_line_mmx2(int mode, uint8_t *dst, const uint8_t *prev, const uint8_t *cur, const uint8_t *next, int w, int refs, int parity){ static const uint64_t pw_1 = 0x0001000100010001ULL; static const uint64_t pb_1 = 0x0101010101010101ULL; // const int mode = p->mode; uint64_t tmp0, tmp1, tmp2, tmp3; int x; #define FILTER\ for(x=0; x>1 */\ "movq %%mm0, %[tmp0] \n\t" /* c */\ "movq %%mm3, %[tmp1] \n\t" /* d */\ "movq %%mm1, %[tmp2] \n\t" /* e */\ "psubw %%mm4, %%mm2 \n\t"\ PABS( %%mm4, %%mm2) /* temporal_diff0 */\ LOAD4("(%[prev],%[mrefs])", %%mm3) /* prev[x-refs] */\ LOAD4("(%[prev],%[prefs])", %%mm4) /* prev[x+refs] */\ "psubw %%mm0, %%mm3 \n\t"\ "psubw %%mm1, %%mm4 \n\t"\ PABS( %%mm5, %%mm3)\ PABS( %%mm5, %%mm4)\ "paddw %%mm4, %%mm3 \n\t" /* temporal_diff1 */\ "psrlw $1, %%mm2 \n\t"\ "psrlw $1, %%mm3 \n\t"\ "pmaxsw %%mm3, %%mm2 \n\t"\ LOAD4("(%[next],%[mrefs])", %%mm3) /* next[x-refs] */\ LOAD4("(%[next],%[prefs])", %%mm4) /* next[x+refs] */\ "psubw %%mm0, %%mm3 \n\t"\ "psubw %%mm1, %%mm4 \n\t"\ PABS( %%mm5, %%mm3)\ PABS( %%mm5, %%mm4)\ "paddw %%mm4, %%mm3 \n\t" /* temporal_diff2 */\ "psrlw $1, %%mm3 \n\t"\ "pmaxsw %%mm3, %%mm2 \n\t"\ "movq %%mm2, %[tmp3] \n\t" /* diff */\ \ "paddw %%mm0, %%mm1 \n\t"\ "paddw %%mm0, %%mm0 \n\t"\ "psubw %%mm1, %%mm0 \n\t"\ "psrlw $1, %%mm1 \n\t" /* spatial_pred */\ PABS( %%mm2, %%mm0) /* ABS(c-e) */\ \ "movq -1(%[cur],%[mrefs]), %%mm2 \n\t" /* cur[x-refs-1] */\ "movq -1(%[cur],%[prefs]), %%mm3 \n\t" /* cur[x+refs-1] */\ "movq %%mm2, %%mm4 \n\t"\ "psubusb %%mm3, %%mm2 \n\t"\ "psubusb %%mm4, %%mm3 \n\t"\ "pmaxub %%mm3, %%mm2 \n\t"\ /*"pshufw $9,%%mm2, %%mm3 \n\t"*/\ "movq %%mm2, %%mm3 \n\t" /* replace for "pshufw $9,%%mm2, %%mm3" - Fizick */\ "psrlq $16, %%mm3 \n\t"/* replace for "pshufw $9,%%mm2, %%mm3" - Fizick*/\ "punpcklbw %%mm7, %%mm2 \n\t" /* ABS(cur[x-refs-1] - cur[x+refs-1]) */\ "punpcklbw %%mm7, %%mm3 \n\t" /* ABS(cur[x-refs+1] - cur[x+refs+1]) */\ "paddw %%mm2, %%mm0 \n\t"\ "paddw %%mm3, %%mm0 \n\t"\ "psubw %[pw1], %%mm0 \n\t" /* spatial_score */\ \ CHECK(-2,0)\ CHECK1\ CHECK(-3,1)\ CHECK2\ CHECK(0,-2)\ CHECK1\ CHECK(1,-3)\ CHECK2\ \ /* if(p->mode<2) ... */\ "movq %[tmp3], %%mm6 \n\t" /* diff */\ "cmpl $2, %[mode] \n\t"\ "jge 1f \n\t"\ LOAD4("(%["prev2"],%[mrefs],2)", %%mm2) /* prev2[x-2*refs] */\ LOAD4("(%["next2"],%[mrefs],2)", %%mm4) /* next2[x-2*refs] */\ LOAD4("(%["prev2"],%[prefs],2)", %%mm3) /* prev2[x+2*refs] */\ LOAD4("(%["next2"],%[prefs],2)", %%mm5) /* next2[x+2*refs] */\ "paddw %%mm4, %%mm2 \n\t"\ "paddw %%mm5, %%mm3 \n\t"\ "psrlw $1, %%mm2 \n\t" /* b */\ "psrlw $1, %%mm3 \n\t" /* f */\ "movq %[tmp0], %%mm4 \n\t" /* c */\ "movq %[tmp1], %%mm5 \n\t" /* d */\ "movq %[tmp2], %%mm7 \n\t" /* e */\ "psubw %%mm4, %%mm2 \n\t" /* b-c */\ "psubw %%mm7, %%mm3 \n\t" /* f-e */\ "movq %%mm5, %%mm0 \n\t"\ "psubw %%mm4, %%mm5 \n\t" /* d-c */\ "psubw %%mm7, %%mm0 \n\t" /* d-e */\ "movq %%mm2, %%mm4 \n\t"\ "pminsw %%mm3, %%mm2 \n\t"\ "pmaxsw %%mm4, %%mm3 \n\t"\ "pmaxsw %%mm5, %%mm2 \n\t"\ "pminsw %%mm5, %%mm3 \n\t"\ "pmaxsw %%mm0, %%mm2 \n\t" /* max */\ "pminsw %%mm0, %%mm3 \n\t" /* min */\ "pxor %%mm4, %%mm4 \n\t"\ "pmaxsw %%mm3, %%mm6 \n\t"\ "psubw %%mm2, %%mm4 \n\t" /* -max */\ "pmaxsw %%mm4, %%mm6 \n\t" /* diff= MAX3(diff, min, -max); */\ "1: \n\t"\ \ "movq %[tmp1], %%mm2 \n\t" /* d */\ "movq %%mm2, %%mm3 \n\t"\ "psubw %%mm6, %%mm2 \n\t" /* d-diff */\ "paddw %%mm6, %%mm3 \n\t" /* d+diff */\ "pmaxsw %%mm2, %%mm1 \n\t"\ "pminsw %%mm3, %%mm1 \n\t" /* d = clip(spatial_pred, d-diff, d+diff); */\ "packuswb %%mm1, %%mm1 \n\t"\ \ :[tmp0]"=m"(tmp0),\ [tmp1]"=m"(tmp1),\ [tmp2]"=m"(tmp2),\ [tmp3]"=m"(tmp3)\ :[prev] "r"(prev),\ [cur] "r"(cur),\ [next] "r"(next),\ [prefs]"r"((intptr_t)refs),\ [mrefs]"r"((intptr_t)-refs),\ [pw1] "m"(pw_1),\ [pb1] "m"(pb_1),\ [mode] "g"(mode)\ );\ asm volatile("movd %%mm1, %0" :"=m"(*dst));\ dst += 4;\ prev+= 4;\ cur += 4;\ next+= 4;\ } if(parity){ #define prev2 "prev" #define next2 "cur" FILTER #undef prev2 #undef next2 }else{ #define prev2 "cur" #define next2 "next" FILTER #undef prev2 #undef next2 } } #undef LOAD4 #undef PABS #undef CHECK #undef CHECK1 #undef CHECK2 #undef FILTER #ifndef attribute_align_arg #if defined(__GNUC__) && (__GNUC__ > 4 || __GNUC__ == 4 && __GNUC_MINOR__>1) # define attribute_align_arg __attribute__((force_align_arg_pointer)) #else # define attribute_align_arg #endif #endif // for proper alignment SSE2 we need in GCC 4.2 and above #if (__GNUC__ > 4 || __GNUC__ == 4 && __GNUC_MINOR__>1) #ifndef DECLARE_ALIGNED #define DECLARE_ALIGNED(n,t,v) t v __attribute__ ((aligned (n))) #endif // ================= SSE2 ================= #if defined(USE_SSE2) && defined(ARCH_X86_64) #define PABS(tmp,dst) \ "pxor "#tmp", "#tmp" \n\t"\ "psubw "#dst", "#tmp" \n\t"\ "pmaxsw "#tmp", "#dst" \n\t" #define FILTER_LINE_FUNC_NAME filter_line_sse2 #include "vf_yadif_template.h" #endif // ================ SSSE3 ================= #ifdef USE_SSE3 #define PABS(tmp,dst) \ "pabsw "#dst", "#dst" \n\t" #define FILTER_LINE_FUNC_NAME filter_line_ssse3 #include "vf_yadif_template.h" #endif #endif // GCC 4.2+ #endif // GNUC, USE_SSE static void filter_line_c(int mode, uint8_t *dst, const uint8_t *prev, const uint8_t *cur, const uint8_t *next, int w, int refs, int parity){ int x; const uint8_t *prev2= parity ? prev : cur ; const uint8_t *next2= parity ? cur : next; for(x=0; x>1; int e= cur[+refs]; int temporal_diff0= ABS(prev2[0] - next2[0]); int temporal_diff1=( ABS(prev[-refs] - c) + ABS(prev[+refs] - e) )>>1; int temporal_diff2=( ABS(next[-refs] - c) + ABS(next[+refs] - e) )>>1; int diff= MAX3(temporal_diff0>>1, temporal_diff1, temporal_diff2); int spatial_pred= (c+e)>>1; int spatial_score= ABS(cur[-refs-1] - cur[+refs-1]) + ABS(c-e) + ABS(cur[-refs+1] - cur[+refs+1]) - 1; #define CHECK(j)\ { int score= ABS(cur[-refs-1+ j] - cur[+refs-1- j])\ + ABS(cur[-refs + j] - cur[+refs - j])\ + ABS(cur[-refs+1+ j] - cur[+refs+1- j]);\ if(score < spatial_score){\ spatial_score= score;\ spatial_pred= (cur[-refs + j] + cur[+refs - j])>>1;\ CHECK(-1) CHECK(-2) }} }} CHECK( 1) CHECK( 2) }} }} if(mode<2){ int b= (prev2[-2*refs] + next2[-2*refs])>>1; int f= (prev2[+2*refs] + next2[+2*refs])>>1; #if 0 int a= cur[-3*refs]; int g= cur[+3*refs]; int max= MAX3(d-e, d-c, MIN3(MAX(b-c,f-e),MAX(b-c,b-a),MAX(f-g,f-e)) ); int min= MIN3(d-e, d-c, MAX3(MIN(b-c,f-e),MIN(b-c,b-a),MIN(f-g,f-e)) ); #else int max= MAX3(d-e, d-c, MIN(b-c, f-e)); int min= MIN3(d-e, d-c, MAX(b-c, f-e)); #endif diff= MAX3(diff, min, -max); } if(spatial_pred > d + diff) spatial_pred = d + diff; else if(spatial_pred < d - diff) spatial_pred = d - diff; dst[0] = spatial_pred; dst++; cur++; prev++; next++; prev2++; next2++; } } static void interpolate(uint8_t *dst, const uint8_t *cur0, const uint8_t *cur2, int w) { int x; for (x=0; x>1; // simple average } } void filter_plane(int mode, uint8_t *dst, int dst_stride, const uint8_t *prev0, const uint8_t *cur0, const uint8_t *next0, int refs, int w, int h, int parity, int tff, int cpu){ int y; filter_line = filter_line_c; #ifdef __GNUC__ #if (__GNUC__ > 4 || __GNUC__ == 4 && __GNUC_MINOR__>1) #ifdef USE_SSE3 if (cpu & AVS_CPU_SSSE3) filter_line = filter_line_ssse3; else #endif #if defined(USE_SSE2) && defined(ARCH_X86_64) if (cpu & AVS_CPU_SSE2) filter_line = filter_line_sse2; else #endif #endif // GCC 4.2+ #ifdef USE_SSE if (cpu & AVS_CPU_INTEGER_SSE) filter_line = filter_line_mmx2; #endif #endif // GNUC y=0; if(((y ^ parity) & 1)){ memcpy(dst, cur0 + refs, w);// duplicate 1 }else{ memcpy(dst, cur0, w); } y=1; if(((y ^ parity) & 1)){ interpolate(dst + dst_stride, cur0, cur0 + refs*2, w); // interpolate 0 and 2 }else{ memcpy(dst + dst_stride, cur0 + refs, w); // copy original } for(y=2; y= AVS_CPU_INTEGER_SSE) asm volatile("emms"); #endif } #if defined(__GNUC__) && defined(USE_SSE) && !defined(PIC) static attribute_align_arg void YUY2ToPlanes_mmx(const unsigned char *srcYUY2, int pitch_yuy2, int width, int height, unsigned char *py, int pitch_y, unsigned char *pu, unsigned char *pv, int pitch_uv) { /* process by 16 bytes (8 pixels), so width is assumed mod 8 */ int widthdiv2 = width>>1; // static unsigned __int64 Ymask = 0x00FF00FF00FF00FFULL; int h; for (h=0; h> 1; int h; for (h=0; h>1)] = pSrcYUY2[w2+1]; pSrcV[(w>>1)] = pSrcYUY2[w2+3]; } pSrcY += srcPitchY; pSrcU += srcPitchUV; pSrcV += srcPitchUV; pSrcYUY2 += nSrcPitchYUY2; } } //---------------------------------------------------------------------------------------------- void YUY2FromPlanes(unsigned char *pSrcYUY2, int nSrcPitchYUY2, int nWidth, int nHeight, const unsigned char * pSrcY, int srcPitchY, const unsigned char * pSrcU, const unsigned char * pSrcV, int srcPitchUV, int cpu) { int h,w; int w0 = 0; #if defined(__GNUC__) && defined(USE_SSE) && !defined(PIC) if (cpu & AVS_CPU_INTEGER_SSE) { w0 = (nWidth/8)*8; YUY2FromPlanes_mmx(pSrcYUY2, nSrcPitchYUY2, w0, nHeight, pSrcY, srcPitchY, pSrcU, pSrcV, srcPitchUV); } #endif for (h=0; h>1)]; pSrcYUY2[w2+2] = pSrcY[w+1]; pSrcYUY2[w2+3] = pSrcV[(w>>1)]; } pSrcY += srcPitchY; pSrcU += srcPitchUV; pSrcV += srcPitchUV; pSrcYUY2 += nSrcPitchYUY2; } } mlt-7.22.0/src/modules/xine/yadif.h000664 000000 000000 00000004103 14531534050 017023 0ustar00rootroot000000 000000 /* Yadif C-plugin for Avisynth 2.5 - Yet Another DeInterlacing Filter Copyright (C)2007 Alexander G. Balakhnin aka Fizick http://avisynth.org.ru Port of YADIF filter from MPlayer Copyright (C) 2006 Michael Niedermayer This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. Avisynth_C plugin Assembler optimized for GNU C compiler */ #ifndef YADIF_H_ #define YADIF_H_ #include #define AVS_CPU_INTEGER_SSE 0x1 #define AVS_CPU_SSE2 0x2 #define AVS_CPU_SSSE3 0x4 typedef struct yadif_filter { int cpu; // optimization int yheight; int ypitch; int uvpitch; int ywidth; int uvwidth; unsigned char *ysrc; unsigned char *usrc; unsigned char *vsrc; unsigned char *yprev; unsigned char *uprev; unsigned char *vprev; unsigned char *ynext; unsigned char *unext; unsigned char *vnext; unsigned char *ydest; unsigned char *udest; unsigned char *vdest; } yadif_filter; void filter_plane(int mode, uint8_t *dst, int dst_stride, const uint8_t *prev0, const uint8_t *cur0, const uint8_t *next0, int refs, int w, int h, int parity, int tff, int cpu); void YUY2ToPlanes(const unsigned char *pSrcYUY2, int nSrcPitchYUY2, int nWidth, int nHeight, unsigned char * pSrcY, int srcPitchY, unsigned char * pSrcU, unsigned char * pSrcV, int srcPitchUV, int cpu); void YUY2FromPlanes(unsigned char *pSrcYUY2, int nSrcPitchYUY2, int nWidth, int nHeight, const unsigned char * pSrcY, int srcPitchY, const unsigned char * pSrcU, const unsigned char * pSrcV, int srcPitchUV, int cpu); #endif mlt-7.22.0/src/modules/xml/000775 000000 000000 00000000000 14531534050 015415 5ustar00rootroot000000 000000 mlt-7.22.0/src/modules/xml/CMakeLists.txt000664 000000 000000 00000001221 14531534050 020151 0ustar00rootroot000000 000000 add_library(mltxml MODULE common.c common.h consumer_xml.c factory.c producer_xml.c ) file(GLOB YML "*.yml") add_custom_target(Other_xml_Files SOURCES ${YML} mlt-xml.dtd ) target_compile_options(mltxml PRIVATE ${MLT_COMPILE_OPTIONS}) target_link_libraries(mltxml PRIVATE mlt m Threads::Threads PkgConfig::xml) set_target_properties(mltxml PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${MLT_MODULE_OUTPUT_DIRECTORY}") install(TARGETS mltxml LIBRARY DESTINATION ${MLT_INSTALL_MODULE_DIR}) install(FILES consumer_xml.yml producer_xml-nogl.yml producer_xml-string.yml producer_xml.yml mlt-xml.dtd DESTINATION ${MLT_INSTALL_DATA_DIR}/xml ) mlt-7.22.0/src/modules/xml/common.c000664 000000 000000 00000003550 14531534050 017054 0ustar00rootroot000000 000000 /* * Copyright (C) 2016 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "common.h" #include #include // Returns string length of "plain:" prefix if webvx or speed parameter if timewarp. // Otherwise, returns 0. size_t mlt_xml_prefix_size(mlt_properties properties, const char *name, const char *value) { size_t result = 0; if (!strcmp("resource", name)) { const char *mlt_service = mlt_properties_get(properties, "mlt_service"); const char *plain = "plain:"; size_t plain_len = strlen(plain); if (mlt_service && !strcmp("timewarp", mlt_service)) { const char *delimiter = strchr(value, ':'); if (delimiter) result = delimiter - value; if (result && (value[result - 1] == '.' || value[result - 1] == ',' || isdigit(value[result - 1]))) ++result; // include the delimiter else result = 0; // invalid } else if (!strncmp(value, plain, plain_len)) { result = plain_len; } } return result; } mlt-7.22.0/src/modules/xml/common.h000664 000000 000000 00000002001 14531534050 017047 0ustar00rootroot000000 000000 /* * Copyright (C) 2016 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef MLT_XML_COMMON_H #define MLT_XML_COMMON_H #include size_t mlt_xml_prefix_size(mlt_properties properties, const char *name, const char *value); #endif // MLT_XML_COMMON_H mlt-7.22.0/src/modules/xml/consumer_xml.c000664 000000 000000 00000132004 14531534050 020274 0ustar00rootroot000000 000000 /* * consumer_xml.c -- a libxml2 serialiser of mlt service networks * Copyright (C) 2003-2021 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "common.h" #include #include #include #include #include #include #include #include #define ID_SIZE 128 #define TIME_PROPERTY "_consumer_xml" #define _x (const xmlChar *) #define _s (const char *) // This maintains counters for adding ids to elements struct serialise_context_s { mlt_properties id_map; int producer_count; int multitrack_count; int playlist_count; int tractor_count; int filter_count; int transition_count; int chain_count; int link_count; int pass; mlt_properties hide_map; char *root; char *store; int no_meta; mlt_profile profile; mlt_time_format time_format; }; typedef struct serialise_context_s *serialise_context; /** Forward references to static functions. */ static int consumer_start(mlt_consumer parent); static int consumer_stop(mlt_consumer parent); static int consumer_is_stopped(mlt_consumer consumer); static void consumer_close(mlt_consumer parent); static void *consumer_thread(void *arg); static void serialise_service(serialise_context context, mlt_service service, xmlNode *node); typedef enum { xml_existing, xml_producer, xml_multitrack, xml_playlist, xml_tractor, xml_filter, xml_transition, xml_chain, xml_link, } xml_type; /** Create or retrieve an id associated to this service. */ static char *xml_get_id(serialise_context context, mlt_service service, xml_type type) { char *id = NULL; int i = 0; mlt_properties map = context->id_map; // Search the map for the service for (i = 0; i < mlt_properties_count(map); i++) if (mlt_properties_get_data_at(map, i, NULL) == service) break; // If the service is not in the map, and the type indicates a new id is needed... if (i >= mlt_properties_count(map) && type != xml_existing) { // Attempt to reuse existing id id = mlt_properties_get(MLT_SERVICE_PROPERTIES(service), "id"); // If no id, or the id is used in the map (for another service), then // create a new one. if (id == NULL || mlt_properties_get_data(map, id, NULL) != NULL) { char temp[ID_SIZE]; do { switch (type) { case xml_producer: sprintf(temp, "producer%d", context->producer_count++); break; case xml_multitrack: sprintf(temp, "multitrack%d", context->multitrack_count++); break; case xml_playlist: sprintf(temp, "playlist%d", context->playlist_count++); break; case xml_tractor: sprintf(temp, "tractor%d", context->tractor_count++); break; case xml_filter: sprintf(temp, "filter%d", context->filter_count++); break; case xml_transition: sprintf(temp, "transition%d", context->transition_count++); break; case xml_chain: sprintf(temp, "chain%d", context->chain_count++); break; case xml_link: sprintf(temp, "link%d", context->link_count++); break; case xml_existing: // Never gets here break; } } while (mlt_properties_get_data(map, temp, NULL) != NULL); // Set the data at the generated name mlt_properties_set_data(map, temp, service, 0, NULL, NULL); // Get the pointer to the name (i is the end of the list) id = mlt_properties_get_name(map, i); } else { // Store the existing id in the map mlt_properties_set_data(map, id, service, 0, NULL, NULL); } } else if (type == xml_existing) { id = mlt_properties_get_name(map, i); } return id; } /** This is what will be called by the factory - anything can be passed in via the argument, but keep it simple. */ mlt_consumer consumer_xml_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg) { // Create the consumer object mlt_consumer consumer = calloc(1, sizeof(struct mlt_consumer_s)); // If no malloc'd and consumer init ok if (consumer != NULL && mlt_consumer_init(consumer, NULL, profile) == 0) { // Allow thread to be started/stopped consumer->start = consumer_start; consumer->stop = consumer_stop; consumer->is_stopped = consumer_is_stopped; // Assign close callback consumer->close = consumer_close; mlt_properties_set(MLT_CONSUMER_PROPERTIES(consumer), "resource", arg); mlt_properties_set_int(MLT_CONSUMER_PROPERTIES(consumer), "real_time", 0); mlt_properties_set_int(MLT_CONSUMER_PROPERTIES(consumer), "prefill", 1); mlt_properties_set_int(MLT_CONSUMER_PROPERTIES(consumer), "terminate_on_pause", 1); // Return the consumer produced return consumer; } // malloc or consumer init failed free(consumer); // Indicate failure return NULL; } static void serialise_properties(serialise_context context, mlt_properties properties, xmlNode *node) { int i; xmlNode *p; // Enumerate the properties for (i = 0; i < mlt_properties_count(properties); i++) { char *name = mlt_properties_get_name(properties, i); if (name == NULL || name[0] == '_') { continue; } else if (mlt_properties_get_value(properties, i) != NULL && (!context->no_meta || strncmp(name, "meta.", 5)) && strcmp(name, "mlt") && strcmp(name, "mlt_type") && strcmp(name, "in") && strcmp(name, "out") && strcmp(name, "id") && strcmp(name, "title") && strcmp(name, "root") && strcmp(name, "width") && strcmp(name, "height")) { char *value = mlt_properties_get_value_tf(properties, i, context->time_format); if (value) { int rootlen = strlen(context->root); const char *value_orig = value; size_t prefix_size = mlt_xml_prefix_size(properties, name, value); // Strip off prefix. if (prefix_size) value += prefix_size; // Ignore trailing slash on root. if (rootlen && (context->root[rootlen - 1] == '/' || context->root[rootlen - 1] == '\\')) --rootlen; // convert absolute path to relative if (rootlen && !strncmp(value, context->root, rootlen) && (value[rootlen] == '/' || value[rootlen] == '\\')) { if (prefix_size) { char *s = calloc(1, strlen(value_orig) - rootlen + 1); strncat(s, value_orig, prefix_size); strcat(s, value + rootlen + 1); p = xmlNewTextChild(node, NULL, _x("property"), _x(s)); free(s); } else { p = xmlNewTextChild(node, NULL, _x("property"), _x(value_orig + rootlen + 1)); } } else p = xmlNewTextChild(node, NULL, _x("property"), _x(value_orig)); xmlNewProp(p, _x("name"), _x(name)); } } else if (mlt_properties_get_properties_at(properties, i) != NULL) { mlt_properties child_properties = mlt_properties_get_properties_at(properties, i); p = xmlNewChild(node, NULL, _x("properties"), NULL); xmlNewProp(p, _x("name"), _x(name)); serialise_properties(context, child_properties, p); } } } static void serialise_store_properties(serialise_context context, mlt_properties properties, xmlNode *node, const char *store) { int i; xmlNode *p; // Enumerate the properties for (i = 0; store != NULL && i < mlt_properties_count(properties); i++) { char *name = mlt_properties_get_name(properties, i); if (!strncmp(name, store, strlen(store))) { char *value = mlt_properties_get_value_tf(properties, i, context->time_format); if (value) { int rootlen = strlen(context->root); // convert absolute path to relative if (rootlen && !strncmp(value, context->root, rootlen) && value[rootlen] == '/') p = xmlNewTextChild(node, NULL, _x("property"), _x(value + rootlen + 1)); else p = xmlNewTextChild(node, NULL, _x("property"), _x(value)); xmlNewProp(p, _x("name"), _x(name)); } else if (mlt_properties_get_properties_at(properties, i) != NULL) { mlt_properties child_properties = mlt_properties_get_properties_at(properties, i); p = xmlNewChild(node, NULL, _x("properties"), NULL); xmlNewProp(p, _x("name"), _x(name)); serialise_properties(context, child_properties, p); } } } } static inline void serialise_service_filters(serialise_context context, mlt_service service, xmlNode *node) { int i; xmlNode *p; mlt_filter filter = NULL; // Enumerate the filters for (i = 0; (filter = mlt_producer_filter(MLT_PRODUCER(service), i)) != NULL; i++) { mlt_properties properties = MLT_FILTER_PROPERTIES(filter); if (mlt_properties_get_int(properties, "_loader") == 0) { // Get a new id - if already allocated, do nothing char *id = xml_get_id(context, MLT_FILTER_SERVICE(filter), xml_filter); if (id != NULL) { p = xmlNewChild(node, NULL, _x("filter"), NULL); xmlNewProp(p, _x("id"), _x(id)); if (mlt_properties_get(properties, "title")) xmlNewProp(p, _x("title"), _x(mlt_properties_get(properties, "title"))); if (mlt_properties_get_position(properties, "in")) xmlNewProp(p, _x("in"), _x(mlt_properties_get_time(properties, "in", context->time_format))); if (mlt_properties_get_position(properties, "out")) xmlNewProp(p, _x("out"), _x(mlt_properties_get_time(properties, "out", context->time_format))); serialise_properties(context, properties, p); serialise_service_filters(context, MLT_FILTER_SERVICE(filter), p); } } } } static void serialise_producer(serialise_context context, mlt_service service, xmlNode *node) { xmlNode *child = node; mlt_service parent = MLT_SERVICE(mlt_producer_cut_parent(MLT_PRODUCER(service))); if (context->pass == 0) { mlt_properties properties = MLT_SERVICE_PROPERTIES(parent); // Get a new id - if already allocated, do nothing char *id = xml_get_id(context, parent, xml_producer); if (id == NULL) return; child = xmlNewChild(node, NULL, _x("producer"), NULL); // Set the id xmlNewProp(child, _x("id"), _x(id)); if (mlt_properties_get(properties, "title")) xmlNewProp(child, _x("title"), _x(mlt_properties_get(properties, "title"))); xmlNewProp(child, _x("in"), _x(mlt_properties_get_time(properties, "in", context->time_format))); xmlNewProp(child, _x("out"), _x(mlt_properties_get_time(properties, "out", context->time_format))); // If the xml producer fails to load a producer, it creates a text producer that says INVALID // and sets the xml_mlt_service property to the original service. const char *xml_mlt_service = mlt_properties_get(properties, "_xml_mlt_service"); if (xml_mlt_service) { // We should not serialize this as a text producer but using the original mlt_service. mlt_properties_set(properties, "mlt_service", xml_mlt_service); } serialise_properties(context, properties, child); serialise_service_filters(context, service, child); // Add producer to the map mlt_properties_set_int(context->hide_map, id, mlt_properties_get_int(properties, "hide")); } else { char *id = xml_get_id(context, parent, xml_existing); mlt_properties properties = MLT_SERVICE_PROPERTIES(service); xmlNewProp(node, _x("parent"), _x(id)); xmlNewProp(node, _x("in"), _x(mlt_properties_get_time(properties, "in", context->time_format))); xmlNewProp(node, _x("out"), _x(mlt_properties_get_time(properties, "out", context->time_format))); } } static void serialise_tractor(serialise_context context, mlt_service service, xmlNode *node); static void serialise_multitrack(serialise_context context, mlt_service service, xmlNode *node) { int i; if (context->pass == 0) { // Iterate over the tracks to collect the producers for (i = 0; i < mlt_multitrack_count(MLT_MULTITRACK(service)); i++) { mlt_producer producer = mlt_producer_cut_parent( mlt_multitrack_track(MLT_MULTITRACK(service), i)); serialise_service(context, MLT_SERVICE(producer), node); } } else { // Get a new id - if already allocated, do nothing char *id = xml_get_id(context, service, xml_multitrack); if (id == NULL) return; // Serialise the tracks for (i = 0; i < mlt_multitrack_count(MLT_MULTITRACK(service)); i++) { xmlNode *track = xmlNewChild(node, NULL, _x("track"), NULL); int hide = 0; mlt_producer producer = mlt_multitrack_track(MLT_MULTITRACK(service), i); mlt_properties properties = MLT_PRODUCER_PROPERTIES(producer); mlt_service parent = MLT_SERVICE(mlt_producer_cut_parent(producer)); char *id = xml_get_id(context, MLT_SERVICE(parent), xml_existing); xmlNewProp(track, _x("producer"), _x(id)); if (mlt_producer_is_cut(producer)) { xmlNewProp(track, _x("in"), _x(mlt_properties_get_time(properties, "in", context->time_format))); xmlNewProp(track, _x("out"), _x(mlt_properties_get_time(properties, "out", context->time_format))); serialise_store_properties(context, MLT_PRODUCER_PROPERTIES(producer), track, context->store); serialise_store_properties(context, MLT_PRODUCER_PROPERTIES(producer), track, "xml_"); if (!context->no_meta) serialise_store_properties(context, MLT_PRODUCER_PROPERTIES(producer), track, "meta."); serialise_service_filters(context, MLT_PRODUCER_SERVICE(producer), track); } hide = mlt_properties_get_int(context->hide_map, id); if (hide) xmlNewProp(track, _x("hide"), _x(hide == 1 ? "video" : (hide == 2 ? "audio" : "both"))); } serialise_service_filters(context, service, node); } } static void serialise_playlist(serialise_context context, mlt_service service, xmlNode *node) { int i; xmlNode *child = node; mlt_playlist_clip_info info; mlt_properties properties = MLT_SERVICE_PROPERTIES(service); if (context->pass == 0) { // Get a new id - if already allocated, do nothing char *id = xml_get_id(context, service, xml_playlist); if (id == NULL) return; // Iterate over the playlist entries to collect the producers for (i = 0; i < mlt_playlist_count(MLT_PLAYLIST(service)); i++) { if (!mlt_playlist_get_clip_info(MLT_PLAYLIST(service), &info, i)) { if (info.producer != NULL) { mlt_producer producer = mlt_producer_cut_parent(info.producer); char *service_s = mlt_properties_get(MLT_PRODUCER_PROPERTIES(producer), "mlt_service"); char *resource_s = mlt_properties_get(MLT_PRODUCER_PROPERTIES(producer), "resource"); if (resource_s != NULL && !strcmp(resource_s, "")) serialise_playlist(context, MLT_SERVICE(producer), node); else if (service_s != NULL && strcmp(service_s, "blank") != 0) serialise_service(context, MLT_SERVICE(producer), node); } } } child = xmlNewChild(node, NULL, _x("playlist"), NULL); // Set the id xmlNewProp(child, _x("id"), _x(id)); if (mlt_properties_get(properties, "title")) xmlNewProp(child, _x("title"), _x(mlt_properties_get(properties, "title"))); // Store application specific properties serialise_store_properties(context, properties, child, context->store); serialise_store_properties(context, properties, child, "xml_"); if (!context->no_meta) serialise_store_properties(context, properties, child, "meta."); // Add producer to the map mlt_properties_set_int(context->hide_map, id, mlt_properties_get_int(properties, "hide")); // Iterate over the playlist entries for (i = 0; i < mlt_playlist_count(MLT_PLAYLIST(service)); i++) { if (!mlt_playlist_get_clip_info(MLT_PLAYLIST(service), &info, i)) { mlt_producer producer = mlt_producer_cut_parent(info.producer); mlt_properties producer_props = MLT_PRODUCER_PROPERTIES(producer); char *service_s = mlt_properties_get(producer_props, "mlt_service"); if (service_s != NULL && strcmp(service_s, "blank") == 0) { xmlNode *entry = xmlNewChild(child, NULL, _x("blank"), NULL); mlt_properties_set_data(producer_props, "_profile", context->profile, 0, NULL, NULL); mlt_properties_set_position(producer_props, TIME_PROPERTY, info.frame_count); xmlNewProp(entry, _x("length"), _x(mlt_properties_get_time(producer_props, TIME_PROPERTY, context->time_format))); } else { char temp[20]; xmlNode *entry = xmlNewChild(child, NULL, _x("entry"), NULL); id = xml_get_id(context, MLT_SERVICE(producer), xml_existing); xmlNewProp(entry, _x("producer"), _x(id)); mlt_properties_set_position(producer_props, TIME_PROPERTY, info.frame_in); xmlNewProp(entry, _x("in"), _x(mlt_properties_get_time(producer_props, TIME_PROPERTY, context->time_format))); mlt_properties_set_position(producer_props, TIME_PROPERTY, info.frame_out); xmlNewProp(entry, _x("out"), _x(mlt_properties_get_time(producer_props, TIME_PROPERTY, context->time_format))); if (info.repeat > 1) { sprintf(temp, "%d", info.repeat); xmlNewProp(entry, _x("repeat"), _x(temp)); } if (mlt_producer_is_cut(info.cut)) { serialise_store_properties(context, MLT_PRODUCER_PROPERTIES(info.cut), entry, context->store); serialise_store_properties(context, MLT_PRODUCER_PROPERTIES(info.cut), entry, "xml_"); if (!context->no_meta) serialise_store_properties(context, MLT_PRODUCER_PROPERTIES(info.cut), entry, "meta."); serialise_service_filters(context, MLT_PRODUCER_SERVICE(info.cut), entry); } } } } serialise_service_filters(context, service, child); } else if (xmlStrcmp(node->name, _x("tractor")) != 0) { char *id = xml_get_id(context, service, xml_existing); xmlNewProp(node, _x("producer"), _x(id)); } } static void serialise_tractor(serialise_context context, mlt_service service, xmlNode *node) { xmlNode *child = node; mlt_properties properties = MLT_SERVICE_PROPERTIES(service); if (context->pass == 0) { // Recurse on connected producer serialise_service(context, mlt_service_producer(service), node); } else { // Get a new id - if already allocated, do nothing char *id = xml_get_id(context, service, xml_tractor); if (id == NULL) return; child = xmlNewChild(node, NULL, _x("tractor"), NULL); // Set the id xmlNewProp(child, _x("id"), _x(id)); if (mlt_properties_get(properties, "title")) xmlNewProp(child, _x("title"), _x(mlt_properties_get(properties, "title"))); if (mlt_properties_get_position(properties, "in") >= 0) xmlNewProp(child, _x("in"), _x(mlt_properties_get_time(properties, "in", context->time_format))); if (mlt_properties_get_position(properties, "out") >= 0) xmlNewProp(child, _x("out"), _x(mlt_properties_get_time(properties, "out", context->time_format))); // Store application specific properties serialise_store_properties(context, MLT_SERVICE_PROPERTIES(service), child, context->store); serialise_store_properties(context, MLT_SERVICE_PROPERTIES(service), child, "xml_"); if (!context->no_meta) serialise_store_properties(context, MLT_SERVICE_PROPERTIES(service), child, "meta."); // Recurse on connected producer serialise_service(context, mlt_service_producer(service), child); serialise_service_filters(context, service, child); } } static void serialise_filter(serialise_context context, mlt_service service, xmlNode *node) { xmlNode *child = node; mlt_properties properties = MLT_SERVICE_PROPERTIES(service); // Recurse on connected producer serialise_service(context, mlt_service_producer(service), node); if (context->pass == 1) { // Get a new id - if already allocated, do nothing char *id = xml_get_id(context, service, xml_filter); if (id == NULL) return; child = xmlNewChild(node, NULL, _x("filter"), NULL); // Set the id xmlNewProp(child, _x("id"), _x(id)); if (mlt_properties_get(properties, "title")) xmlNewProp(child, _x("title"), _x(mlt_properties_get(properties, "title"))); if (mlt_properties_get_position(properties, "in")) xmlNewProp(child, _x("in"), _x(mlt_properties_get_time(properties, "in", context->time_format))); if (mlt_properties_get_position(properties, "out")) xmlNewProp(child, _x("out"), _x(mlt_properties_get_time(properties, "out", context->time_format))); serialise_properties(context, properties, child); serialise_service_filters(context, service, child); } } static void serialise_transition(serialise_context context, mlt_service service, xmlNode *node) { xmlNode *child = node; mlt_properties properties = MLT_SERVICE_PROPERTIES(service); // Recurse on connected producer serialise_service(context, MLT_SERVICE(MLT_TRANSITION(service)->producer), node); if (context->pass == 1) { // Get a new id - if already allocated, do nothing char *id = xml_get_id(context, service, xml_transition); if (id == NULL) return; child = xmlNewChild(node, NULL, _x("transition"), NULL); // Set the id xmlNewProp(child, _x("id"), _x(id)); if (mlt_properties_get(properties, "title")) xmlNewProp(child, _x("title"), _x(mlt_properties_get(properties, "title"))); if (mlt_properties_get_position(properties, "in")) xmlNewProp(child, _x("in"), _x(mlt_properties_get_time(properties, "in", context->time_format))); if (mlt_properties_get_position(properties, "out")) xmlNewProp(child, _x("out"), _x(mlt_properties_get_time(properties, "out", context->time_format))); serialise_properties(context, properties, child); serialise_service_filters(context, service, child); } } static void serialise_link(serialise_context context, mlt_service service, xmlNode *node) { xmlNode *child = node; mlt_properties properties = MLT_SERVICE_PROPERTIES(service); if (context->pass == 0) { // Get a new id - if already allocated, do nothing char *id = xml_get_id(context, service, xml_link); if (id == NULL) return; child = xmlNewChild(node, NULL, _x("link"), NULL); // Set the id xmlNewProp(child, _x("id"), _x(id)); if (mlt_properties_get(properties, "title")) xmlNewProp(child, _x("title"), _x(mlt_properties_get(properties, "title"))); if (mlt_properties_get_position(properties, "in")) xmlNewProp(child, _x("in"), _x(mlt_properties_get_time(properties, "in", context->time_format))); if (mlt_properties_get_position(properties, "out")) xmlNewProp(child, _x("out"), _x(mlt_properties_get_time(properties, "out", context->time_format))); serialise_properties(context, properties, child); serialise_service_filters(context, service, child); } } static void serialise_chain(serialise_context context, mlt_service service, xmlNode *node) { int i = 0; xmlNode *child = node; mlt_properties properties = MLT_SERVICE_PROPERTIES(service); if (context->pass == 0) { // Get a new id - if already allocated, do nothing char *id = xml_get_id(context, service, xml_chain); if (id == NULL) return; child = xmlNewChild(node, NULL, _x("chain"), NULL); // Set the id xmlNewProp(child, _x("id"), _x(id)); if (mlt_properties_get(properties, "title")) xmlNewProp(child, _x("title"), _x(mlt_properties_get(properties, "title"))); if (mlt_properties_get_position(properties, "in")) xmlNewProp(child, _x("in"), _x(mlt_properties_get_time(properties, "in", context->time_format))); if (mlt_properties_get_position(properties, "out")) xmlNewProp(child, _x("out"), _x(mlt_properties_get_time(properties, "out", context->time_format))); serialise_properties(context, properties, child); // Serialize links for (i = 0; i < mlt_chain_link_count(MLT_CHAIN(service)); i++) { mlt_link link = mlt_chain_link(MLT_CHAIN(service), i); if (link && mlt_properties_get_int(MLT_LINK_PROPERTIES(link), "_loader") == 0) { serialise_link(context, MLT_LINK_SERVICE(link), child); } } serialise_service_filters(context, service, child); } } static void serialise_service(serialise_context context, mlt_service service, xmlNode *node) { // Iterate over consumer/producer connections while (service != NULL) { mlt_properties properties = MLT_SERVICE_PROPERTIES(service); char *mlt_type = mlt_properties_get(properties, "mlt_type"); // Tell about the producer if (strcmp(mlt_type, "producer") == 0) { char *mlt_service = mlt_properties_get(properties, "mlt_service"); if (mlt_properties_get(properties, "xml") == NULL && (mlt_service != NULL && !strcmp(mlt_service, "tractor"))) { context->pass = 0; serialise_tractor(context, service, node); context->pass = 1; serialise_tractor(context, service, node); context->pass = 0; break; } else { serialise_producer(context, service, node); } if (mlt_properties_get(properties, "xml") != NULL) break; } // Tell about the framework container producers else if (strcmp(mlt_type, "mlt_producer") == 0) { char *resource = mlt_properties_get(properties, "resource"); // Recurse on multitrack's tracks if (resource && strcmp(resource, "") == 0) { serialise_multitrack(context, service, node); break; } // Recurse on playlist's clips else if (resource && strcmp(resource, "") == 0) { serialise_playlist(context, service, node); } // Recurse on tractor's producer else if (resource && strcmp(resource, "") == 0) { context->pass = 0; serialise_tractor(context, service, node); context->pass = 1; serialise_tractor(context, service, node); context->pass = 0; break; } // Treat it as a normal chain else if (mlt_properties_get_int(properties, "_original_type") == mlt_service_chain_type) { serialise_chain(context, service, node); mlt_properties_set(properties, "mlt_type", "chain"); if (mlt_properties_get(properties, "xml") != NULL) break; } // Treat it as a normal producer else { serialise_producer(context, service, node); if (mlt_properties_get(properties, "xml") != NULL) break; } } // Tell about a chain else if (strcmp(mlt_type, "chain") == 0) { serialise_chain(context, service, node); break; } // Tell about a filter else if (strcmp(mlt_type, "filter") == 0) { serialise_filter(context, service, node); break; } // Tell about a transition else if (strcmp(mlt_type, "transition") == 0) { serialise_transition(context, service, node); break; } // Get the next connected service service = mlt_service_producer(service); } } static void serialise_other(mlt_properties properties, struct serialise_context_s *context, xmlNodePtr root) { int i; for (i = 0; i < mlt_properties_count(properties); i++) { const char *name = mlt_properties_get_name(properties, i); if (strlen(name) > 10 && !strncmp(name, "xml_retain", 10)) { mlt_service service = mlt_properties_get_data_at(properties, i, NULL); if (service) { mlt_properties_set_int(MLT_SERVICE_PROPERTIES(service), "xml_retain", 1); serialise_service(context, service, root); } } } } xmlDocPtr xml_make_doc(mlt_consumer consumer, mlt_service service) { mlt_properties properties = MLT_SERVICE_PROPERTIES(service); xmlDocPtr doc = xmlNewDoc(_x("1.0")); xmlNodePtr root = xmlNewNode(NULL, _x("mlt")); struct serialise_context_s *context = calloc(1, sizeof(struct serialise_context_s)); mlt_profile profile = mlt_service_profile(MLT_CONSUMER_SERVICE(consumer)); char tmpstr[32]; xmlDocSetRootElement(doc, root); // Indicate the numeric locale if (mlt_properties_get_lcnumeric(properties)) xmlNewProp(root, _x("LC_NUMERIC"), _x(mlt_properties_get_lcnumeric(properties))); else #ifdef _WIN32 { char *lcnumeric = getlocale(); mlt_properties_set(properties, "_xml_lcnumeric_in", lcnumeric); free(lcnumeric); mlt_properties_to_utf8(properties, "_xml_lcnumeric_in", "_xml_lcnumeric_out"); lcnumeric = mlt_properties_get(properties, "_xml_lcnumeric_out"); xmlNewProp(root, _x("LC_NUMERIC"), _x(lcnumeric)); } #else xmlNewProp(root, _x("LC_NUMERIC"), _x(setlocale(LC_NUMERIC, NULL))); #endif // Indicate the version xmlNewProp(root, _x("version"), _x(mlt_version_get_string())); // If we have root, then deal with it now if (mlt_properties_get(properties, "root") != NULL) { if (!mlt_properties_get_int(MLT_CONSUMER_PROPERTIES(consumer), "no_root")) xmlNewProp(root, _x("root"), _x(mlt_properties_get(properties, "root"))); context->root = strdup(mlt_properties_get(properties, "root")); } else { context->root = strdup(""); } // Assign the additional 'storage' pattern for properties context->store = mlt_properties_get(MLT_CONSUMER_PROPERTIES(consumer), "store"); context->no_meta = mlt_properties_get_int(MLT_CONSUMER_PROPERTIES(consumer), "no_meta"); const char *time_format = mlt_properties_get(MLT_CONSUMER_PROPERTIES(consumer), "time_format"); if (time_format && (!strcmp(time_format, "smpte") || !strcmp(time_format, "SMPTE") || !strcmp(time_format, "timecode") || !strcmp(time_format, "smpte_df"))) context->time_format = mlt_time_smpte_df; else if (time_format && (!strcmp(time_format, "smpte_ndf"))) context->time_format = mlt_time_smpte_ndf; else if (time_format && (!strcmp(time_format, "clock") || !strcmp(time_format, "CLOCK"))) context->time_format = mlt_time_clock; // Assign a title property if (mlt_properties_get(properties, "title") != NULL) xmlNewProp(root, _x("title"), _x(mlt_properties_get(properties, "title"))); // Add a profile child element if (profile) { if (!mlt_properties_get_int(MLT_CONSUMER_PROPERTIES(consumer), "no_profile")) { xmlNodePtr profile_node = xmlNewChild(root, NULL, _x("profile"), NULL); if (profile->description) xmlNewProp(profile_node, _x("description"), _x(profile->description)); sprintf(tmpstr, "%d", profile->width); xmlNewProp(profile_node, _x("width"), _x(tmpstr)); sprintf(tmpstr, "%d", profile->height); xmlNewProp(profile_node, _x("height"), _x(tmpstr)); sprintf(tmpstr, "%d", profile->progressive); xmlNewProp(profile_node, _x("progressive"), _x(tmpstr)); sprintf(tmpstr, "%d", profile->sample_aspect_num); xmlNewProp(profile_node, _x("sample_aspect_num"), _x(tmpstr)); sprintf(tmpstr, "%d", profile->sample_aspect_den); xmlNewProp(profile_node, _x("sample_aspect_den"), _x(tmpstr)); sprintf(tmpstr, "%d", profile->display_aspect_num); xmlNewProp(profile_node, _x("display_aspect_num"), _x(tmpstr)); sprintf(tmpstr, "%d", profile->display_aspect_den); xmlNewProp(profile_node, _x("display_aspect_den"), _x(tmpstr)); sprintf(tmpstr, "%d", profile->frame_rate_num); xmlNewProp(profile_node, _x("frame_rate_num"), _x(tmpstr)); sprintf(tmpstr, "%d", profile->frame_rate_den); xmlNewProp(profile_node, _x("frame_rate_den"), _x(tmpstr)); sprintf(tmpstr, "%d", profile->colorspace); xmlNewProp(profile_node, _x("colorspace"), _x(tmpstr)); } context->profile = profile; } // Construct the context maps context->id_map = mlt_properties_new(); context->hide_map = mlt_properties_new(); // Ensure producer is a framework producer mlt_properties_set_int(properties, "_original_type", mlt_service_identify(service)); mlt_properties_set(MLT_SERVICE_PROPERTIES(service), "mlt_type", "mlt_producer"); // In pass one, we serialise the end producers and playlists, // adding them to a map keyed by address. serialise_other(MLT_SERVICE_PROPERTIES(service), context, root); serialise_service(context, service, root); // In pass two, we serialise the tractor and reference the // producers and playlists context->pass++; serialise_other(MLT_SERVICE_PROPERTIES(service), context, root); serialise_service(context, service, root); // Cleanup resource mlt_properties_close(context->id_map); mlt_properties_close(context->hide_map); free(context->root); free(context); return doc; } static void output_xml(mlt_consumer consumer) { // Get the producer service mlt_service service = mlt_service_producer(MLT_CONSUMER_SERVICE(consumer)); mlt_properties properties = MLT_CONSUMER_PROPERTIES(consumer); char *resource = mlt_properties_get(properties, "resource"); xmlDocPtr doc = NULL; if (!service) return; // Set the title if provided if (mlt_properties_get(properties, "title")) mlt_properties_set(MLT_SERVICE_PROPERTIES(service), "title", mlt_properties_get(properties, "title")); // Check for a root on the consumer properties and pass to service if (mlt_properties_get(properties, "root")) mlt_properties_set(MLT_SERVICE_PROPERTIES(service), "root", mlt_properties_get(properties, "root")); // Specify roots in other cases... if (resource != NULL && mlt_properties_get(properties, "root") == NULL) { // Get the current working directory char *cwd = getcwd(NULL, 0); mlt_properties_set(MLT_SERVICE_PROPERTIES(service), "root", cwd); free(cwd); } // Make the document doc = xml_make_doc(consumer, service); // Handle the output if (resource == NULL || !strcmp(resource, "")) { xmlDocFormatDump(stdout, doc, 1); } else if (strchr(resource, '.') == NULL) { xmlChar *buffer = NULL; int length = 0; xmlDocDumpMemoryEnc(doc, &buffer, &length, "utf-8"); mlt_properties_set(properties, resource, _s(buffer)); #ifdef _WIN32 xmlFreeFunc xmlFree = NULL; xmlMemGet(&xmlFree, NULL, NULL, NULL); #endif xmlFree(buffer); } else { xmlSaveFormatFileEnc(resource, doc, "utf-8", 1); } // Close the document xmlFreeDoc(doc); } static int consumer_start(mlt_consumer consumer) { mlt_properties properties = MLT_CONSUMER_PROPERTIES(consumer); if (mlt_properties_get_int(properties, "all")) { // Check that we're not already running if (!mlt_properties_get_int(properties, "running")) { // Allocate a thread pthread_t *thread = calloc(1, sizeof(pthread_t)); // Assign the thread to properties mlt_properties_set_data(properties, "thread", thread, sizeof(pthread_t), free, NULL); // Set the running state mlt_properties_set_int(properties, "running", 1); mlt_properties_set_int(properties, "joined", 0); // Create the thread pthread_create(thread, NULL, consumer_thread, consumer); } } else { output_xml(consumer); mlt_consumer_stop(consumer); mlt_consumer_stopped(consumer); } return 0; } static int consumer_is_stopped(mlt_consumer consumer) { mlt_properties properties = MLT_CONSUMER_PROPERTIES(consumer); return !mlt_properties_get_int(properties, "running"); } static int consumer_stop(mlt_consumer consumer) { // Get the properties mlt_properties properties = MLT_CONSUMER_PROPERTIES(consumer); // Check that we're running if (!mlt_properties_get_int(properties, "joined")) { // Get the thread pthread_t *thread = mlt_properties_get_data(properties, "thread", NULL); // Stop the thread mlt_properties_set_int(properties, "running", 0); mlt_properties_set_int(properties, "joined", 1); // Wait for termination if (thread) pthread_join(*thread, NULL); } return 0; } static void *consumer_thread(void *arg) { // Map the argument to the object mlt_consumer consumer = arg; // Get the properties mlt_properties properties = MLT_CONSUMER_PROPERTIES(consumer); // Convenience functionality int terminate_on_pause = mlt_properties_get_int(properties, "terminate_on_pause"); int terminated = 0; // Frame and size mlt_frame frame = NULL; int video_off = mlt_properties_get_int(properties, "video_off"); int audio_off = mlt_properties_get_int(properties, "audio_off"); // Loop while running while (!terminated && mlt_properties_get_int(properties, "running")) { // Get the frame frame = mlt_consumer_rt_frame(consumer); // Check for termination if (terminate_on_pause && frame != NULL) terminated = mlt_properties_get_double(MLT_FRAME_PROPERTIES(frame), "_speed") == 0.0; // Check that we have a frame to work with if (frame) { int width = 0, height = 0; int frequency = mlt_properties_get_int(properties, "frequency"); int channels = mlt_properties_get_int(properties, "channels"); float fps = mlt_profile_fps(mlt_service_profile(MLT_CONSUMER_SERVICE(consumer))); int samples = mlt_audio_calculate_frame_samples(fps, frequency, mlt_frame_get_position(frame)); mlt_image_format iformat = mlt_image_yuv422; mlt_audio_format aformat = mlt_audio_s16; uint8_t *buffer; if (!video_off) mlt_frame_get_image(frame, &buffer, &iformat, &width, &height, 0); if (!audio_off) mlt_frame_get_audio(frame, (void **) &buffer, &aformat, &frequency, &channels, &samples); // Close the frame mlt_events_fire(properties, "consumer-frame-show", mlt_event_data_from_frame(frame)); mlt_frame_close(frame); } } output_xml(consumer); // Indicate that the consumer is stopped mlt_properties_set_int(properties, "running", 0); mlt_consumer_stopped(consumer); return NULL; } static void consumer_close(mlt_consumer consumer) { // Stop the consumer mlt_consumer_stop(consumer); // Close the parent mlt_consumer_close(consumer); // Free the memory free(consumer); } mlt-7.22.0/src/modules/xml/consumer_xml.yml000664 000000 000000 00000007155 14531534050 020663 0ustar00rootroot000000 000000 schema_version: 0.3 type: consumer identifier: xml title: XML version: 1 copyright: Meltytech, LLC creator: Dan Dennedy license: LGPLv2.1 language: en tags: - Audio - Video description: > Serialise the service network to XML. See docs/mlt-xml.txt for more information. notes: > If you set a data property beginning with (and longer than) "xml_retain" on the service connected to this consumer where the data is a mlt_service pointer, then the pointed at service will also be serialized before the connected service. This can be useful, for example, to save a playlist as a media bin along with a multitrack. You can serialize more than one of these additional services by setting more than property, each with a unique key beginning with "xml_retain". bugs: - Untested arbitrary nesting of multitracks and playlists. - > Property "id" is generated as service type followed by number if no property named "id" exists, but it fails to guarantee uniqueness. parameters: - identifier: resource argument: yes title: File type: string description: > The name of a file in which to store the XML. If the value does not contain a period (to start an extension), then the value is interpreted as the name of a property in which to store the XML. This makes it easy for an application to use the consumer to serialize a service network and retrieve the XML in-memory. readonly: no required: no mutable: no default: stdout widget: fileopen - identifier: all title: Process all frames type: boolean description: > Without this option, the XML consumer does not process any frames and simply serializes the service network. However, some filters (.e.g, vid.stab) require two passes where the first pass performs some analysis and stores the result in a property. Therefore, set this property to 1 (true) to cause the consumer to process all frames before serializing to XML. default: 0 - identifier: title title: Title type: string description: > You can give the composition a friendly name that some applications may use. - identifier: root title: Base path type: string description: > If a file name in the XML is relative, but not relative to the current XML file's directory, then you can set the directory to which it is relative here. - identifier: no_meta title: Exclude meta properties type: boolean description: > Set this to disable the output of properties with the prefix "meta." default: 0 widget: checkbox - identifier: no_root title: No root attribute type: boolean description: > Set this to disable the output of the root attribute on the root element. default: 0 widget: checkbox - identifier: time_format title: Time format type: string description: Output time-based values as timecode or clock formats. values: - frames - smpte_df # SMPTE drop-frame timecode - smpte_ndf # SMPTE non-drop-frame timecode - smpte # or SMPTE - same as smpte_df - timecode # same as smpte_df - clock # or CLOCK default: frames widget: dropdown - identifier: store title: Include property prefix type: string description: > To save additional properties that MLT does not know about, supply an application-specific property name prefix that you are using. - identifier: no_profile title: No profile element type: boolean description: Set this to disable the output of the profile element. default: 0 widget: checkbox mlt-7.22.0/src/modules/xml/factory.c000664 000000 000000 00000004516 14531534050 017236 0ustar00rootroot000000 000000 /* * factory.c -- the factory method interfaces * Copyright (C) 2003-2014 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include extern mlt_consumer consumer_xml_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); extern mlt_producer producer_xml_init(mlt_profile profile, mlt_service_type type, const char *id, char *arg); static mlt_properties metadata(mlt_service_type type, const char *id, void *data) { char file[PATH_MAX]; snprintf(file, PATH_MAX, "%s/xml/%s", mlt_environment("MLT_DATA"), (char *) data); return mlt_properties_parse_yaml(file); } MLT_REPOSITORY { MLT_REGISTER(mlt_service_consumer_type, "xml", consumer_xml_init); MLT_REGISTER(mlt_service_producer_type, "xml", producer_xml_init); MLT_REGISTER(mlt_service_producer_type, "xml-string", producer_xml_init); MLT_REGISTER(mlt_service_producer_type, "xml-nogl", producer_xml_init); MLT_REGISTER_METADATA(mlt_service_consumer_type, "xml", metadata, "consumer_xml.yml"); MLT_REGISTER_METADATA(mlt_service_producer_type, "xml", metadata, "producer_xml.yml"); MLT_REGISTER_METADATA(mlt_service_producer_type, "xml-string", metadata, "producer_xml-string.yml"); MLT_REGISTER_METADATA(mlt_service_producer_type, "xml-nogl", metadata, "producer_xml-nogl.yml"); } mlt-7.22.0/src/modules/xml/mlt-xml.dtd000664 000000 000000 00000006646 14531534050 017520 0ustar00rootroot000000 000000 mlt-7.22.0/src/modules/xml/producer_xml-nogl.yml000664 000000 000000 00000000661 14531534050 021603 0ustar00rootroot000000 000000 schema_version: 0.1 type: producer identifier: xml-nogl title: XML without OpenGL version: 1 copyright: Meltytech, LLC creator: Dan Dennedy license: LGPLv2.1 language: en tags: - Audio - Video description: > This is the same as the regular "xml" producer except it prevents automatically creating the qglsl consumer when it detects the usage of OpenGL-based services within the XML. See ProducerXml for more information. mlt-7.22.0/src/modules/xml/producer_xml-string.yml000664 000000 000000 00000000726 14531534050 022154 0ustar00rootroot000000 000000 schema_version: 0.1 type: producer identifier: xml-string title: XML String version: 1 copyright: Meltytech, LLC creator: Dan Dennedy license: LGPLv2.1 language: en tags: - Audio - Video description: > This is the same as the regular "xml" producer except it takes a pointer to a string as the constructor argument. That means it can only be used by applications and not directly exposed to users of those applications. See ProducerXml for more information. mlt-7.22.0/src/modules/xml/producer_xml.c000664 000000 000000 00000260622 14531534050 020274 0ustar00rootroot000000 000000 /* * producer_xml.c -- a libxml2 parser of mlt service networks * Copyright (C) 2003-2023 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ // TODO: destroy unreferenced producers (they are currently destroyed // when the returned producer is closed). #include "common.h" #include #include #include #include #include #include #include #include // for xmlCreateFileParserCtxt #include #define BRANCH_SIG_LEN 4000 #define _x (const xmlChar *) #define _s (const char *) #undef DEBUG #ifdef DEBUG extern xmlDocPtr xml_make_doc(mlt_service service); #endif enum service_type { mlt_invalid_type, mlt_unknown_type, mlt_producer_type, mlt_playlist_type, mlt_entry_type, mlt_tractor_type, mlt_multitrack_type, mlt_filter_type, mlt_transition_type, mlt_consumer_type, mlt_field_type, mlt_services_type, mlt_dummy_filter_type, mlt_dummy_transition_type, mlt_dummy_producer_type, mlt_dummy_consumer_type, mlt_chain_type, mlt_link_type, }; struct deserialise_context_s { mlt_deque stack_types; mlt_deque stack_service; mlt_deque stack_properties; mlt_properties producer_map; mlt_properties destructors; char *property; int is_value; xmlDocPtr value_doc; mlt_deque stack_node; xmlDocPtr entity_doc; int entity_is_replace; mlt_deque stack_branch; const xmlChar *publicId; const xmlChar *systemId; mlt_properties params; mlt_profile profile; mlt_profile consumer_profile; int pass; char *lc_numeric; mlt_consumer consumer; int multi_consumer; int consumer_count; int seekable; mlt_consumer qglsl; }; typedef struct deserialise_context_s *deserialise_context; /** Trim the leading and trailing whitespace from a string in-place. */ static char *trim(char *s) { int n; if (s && (n = strlen(s))) { int i = 0; while (i < n && isspace(s[i])) i++; while (--n && isspace(s[n])) ; n = n - i + 1; if (n > 0) memmove(s, s + i, n); s[n] = 0; } return s; } /** Convert the numerical current branch address to a dot-delimited string. */ static char *serialise_branch(deserialise_context context, char *s) { int i, n = mlt_deque_count(context->stack_branch); s[0] = 0; for (i = 0; i < n - 1; i++) { int len = strlen(s); snprintf(s + len, BRANCH_SIG_LEN - len, "%" PRIu64 ".", (uint64_t) mlt_deque_peek(context->stack_branch, i)); } return s; } /** Push a service. */ static void context_push_service(deserialise_context context, mlt_service that, enum service_type type) { mlt_deque_push_back(context->stack_service, that); mlt_deque_push_back_int(context->stack_types, type); // Record the tree branch on which this service lives if (that != NULL && mlt_properties_get(MLT_SERVICE_PROPERTIES(that), "_xml_branch") == NULL) { char s[BRANCH_SIG_LEN]; mlt_properties_set_string(MLT_SERVICE_PROPERTIES(that), "_xml_branch", serialise_branch(context, s)); } } /** Pop a service. */ static mlt_service context_pop_service(deserialise_context context, enum service_type *type) { mlt_service result = NULL; if (type) *type = mlt_invalid_type; if (mlt_deque_count(context->stack_service) > 0) { result = mlt_deque_pop_back(context->stack_service); if (type != NULL) *type = mlt_deque_pop_back_int(context->stack_types); // Set the service's profile and locale so mlt_property time-to-position conversions can get fps if (result) { mlt_properties_set_data(MLT_SERVICE_PROPERTIES(result), "_profile", context->profile, 0, NULL, NULL); mlt_properties_set_lcnumeric(MLT_SERVICE_PROPERTIES(result), context->lc_numeric); } } return result; } /** Push a node. */ static void context_push_node(deserialise_context context, xmlNodePtr node) { mlt_deque_push_back(context->stack_node, node); } /** Pop a node. */ static xmlNodePtr context_pop_node(deserialise_context context) { return mlt_deque_pop_back(context->stack_node); } // Get the current properties object to add each property to. // The object could be a service, or it could be a properties instance // nested under a service. static mlt_properties current_properties(deserialise_context context) { mlt_properties properties = NULL; if (mlt_deque_count(context->stack_properties)) { // Nested properties are being parsed properties = mlt_deque_peek_back(context->stack_properties); } else if (mlt_deque_count(context->stack_service)) { // Properties belong to the service being parsed mlt_service service = mlt_deque_peek_back(context->stack_service); properties = MLT_SERVICE_PROPERTIES(service); } if (properties) { mlt_properties_set_data(properties, "_profile", context->profile, 0, NULL, NULL); mlt_properties_set_lcnumeric(properties, context->lc_numeric); } return properties; } // Set the destructor on a new service static void track_service(mlt_properties properties, void *service, mlt_destructor destructor) { int registered = mlt_properties_get_int(properties, "registered"); char *key = mlt_properties_get(properties, "registered"); mlt_properties_set_data(properties, key, service, 0, destructor, NULL); mlt_properties_set_int(properties, "registered", ++registered); } static inline int is_known_prefix(const char *resource) { char *prefix = strchr(resource, ':'); if (prefix) { const char *whitelist[] = {"alsa", "avfoundation", "dshow", "fbdev", "gdigrab", "jack", "lavfi", "oss", "pulse", "sndio", "video4linux2", "v4l2", "x11grab", "async", "cache", "concat", "crypto", "data", "ffrtmphttp", "file", "ftp", "fs", "gopher", "hls", "http", "httpproxy", "mmsh", "mmst", "pipe", "rtmp", "rtmpt", "rtp", "srtp", "subfile", "tcp", "udp", "udplite", "unix", "color", "colour", "consumer"}; size_t i, n = prefix - resource; for (i = 0; i < sizeof(whitelist) / sizeof(whitelist[0]); ++i) { if (!strncmp(whitelist[i], resource, n)) return 1; } } return 0; } // Prepend the property value with the document root static inline void qualify_property(deserialise_context context, mlt_properties properties, const char *name) { const char *resource_orig = mlt_properties_get(properties, name); char *resource = mlt_properties_get(properties, name); if (resource != NULL && resource[0]) { char *root = mlt_properties_get(context->producer_map, "root"); int n = strlen(root) + strlen(resource) + 2; size_t prefix_size = mlt_xml_prefix_size(properties, name, resource); // Strip off prefix. if (prefix_size) resource += prefix_size; // Qualify file name properties if (root != NULL && strcmp(root, "")) { char *full_resource = calloc(1, n); int drive_letter = strlen(resource) > 3 && resource[1] == ':' && (resource[2] == '/' || resource[2] == '\\'); if (resource[0] != '/' && resource[0] != '\\' && !drive_letter && !is_known_prefix(resource)) { if (prefix_size) strncat(full_resource, resource_orig, prefix_size); strcat(full_resource, root); strcat(full_resource, "/"); strcat(full_resource, resource); } else { strcpy(full_resource, resource_orig); } mlt_properties_set_string(properties, name, full_resource); free(full_resource); } } } /** This function adds a producer to a playlist or multitrack when there is no entry or track element. */ static int add_producer(deserialise_context context, mlt_service service, mlt_position in, mlt_position out) { // Return value (0 = service remains top of stack, 1 means it can be removed) int result = 0; // Get the parent producer enum service_type type = mlt_invalid_type; mlt_service container = context_pop_service(context, &type); int contained = 0; if (service != NULL && container != NULL) { char *container_branch = mlt_properties_get(MLT_SERVICE_PROPERTIES(container), "_xml_branch"); char *service_branch = mlt_properties_get(MLT_SERVICE_PROPERTIES(service), "_xml_branch"); contained = !strncmp(container_branch, service_branch, strlen(container_branch)); } if (contained) { mlt_properties properties = MLT_SERVICE_PROPERTIES(service); char *hide_s = mlt_properties_get(properties, "hide"); // Indicate that this service is no longer top of stack result = 1; switch (type) { case mlt_tractor_type: { mlt_multitrack multitrack = mlt_tractor_multitrack(MLT_TRACTOR(container)); mlt_multitrack_connect(multitrack, MLT_PRODUCER(service), mlt_multitrack_count(multitrack)); } break; case mlt_multitrack_type: { mlt_multitrack_connect(MLT_MULTITRACK(container), MLT_PRODUCER(service), mlt_multitrack_count(MLT_MULTITRACK(container))); } break; case mlt_playlist_type: { mlt_playlist_append_io(MLT_PLAYLIST(container), MLT_PRODUCER(service), in, out); } break; default: result = 0; mlt_log_warning( NULL, "[producer_xml] Producer defined inside something that isn't a container\n"); break; }; // Set the hide state of the track producer if (hide_s != NULL) { if (strcmp(hide_s, "video") == 0) mlt_properties_set_int(properties, "hide", 1); else if (strcmp(hide_s, "audio") == 0) mlt_properties_set_int(properties, "hide", 2); else if (strcmp(hide_s, "both") == 0) mlt_properties_set_int(properties, "hide", 3); } } // Put the parent producer back if (container != NULL) context_push_service(context, container, type); return result; } /** Attach filters defined on that to this. */ static void attach_filters(mlt_service service, mlt_service that) { if (that != NULL) { int i = 0; mlt_filter filter = NULL; for (i = 0; (filter = mlt_service_filter(that, i)) != NULL; i++) { mlt_service_attach(service, filter); attach_filters(MLT_FILTER_SERVICE(filter), MLT_FILTER_SERVICE(filter)); } } } static void on_start_profile(deserialise_context context, const xmlChar *name, const xmlChar **atts) { mlt_profile p = context->profile; for (; atts != NULL && *atts != NULL; atts += 2) { if (xmlStrcmp(atts[0], _x("name")) == 0 || xmlStrcmp(atts[0], _x("profile")) == 0) { mlt_profile my_profile = mlt_profile_init(_s(atts[1])); if (my_profile) { p->description = strdup(my_profile->description); p->display_aspect_den = my_profile->display_aspect_den; p->display_aspect_num = my_profile->display_aspect_num; p->frame_rate_den = my_profile->frame_rate_den; p->frame_rate_num = my_profile->frame_rate_num; p->width = my_profile->width; p->height = my_profile->height; p->progressive = my_profile->progressive; p->sample_aspect_den = my_profile->sample_aspect_den; p->sample_aspect_num = my_profile->sample_aspect_num; p->colorspace = my_profile->colorspace; p->is_explicit = 1; mlt_profile_close(my_profile); } } else if (xmlStrcmp(atts[0], _x("description")) == 0) { free(p->description); p->description = strdup(_s(atts[1])); p->is_explicit = 1; } else if (xmlStrcmp(atts[0], _x("display_aspect_den")) == 0) p->display_aspect_den = strtol(_s(atts[1]), NULL, 0); else if (xmlStrcmp(atts[0], _x("display_aspect_num")) == 0) p->display_aspect_num = strtol(_s(atts[1]), NULL, 0); else if (xmlStrcmp(atts[0], _x("sample_aspect_num")) == 0) p->sample_aspect_num = strtol(_s(atts[1]), NULL, 0); else if (xmlStrcmp(atts[0], _x("sample_aspect_den")) == 0) p->sample_aspect_den = strtol(_s(atts[1]), NULL, 0); else if (xmlStrcmp(atts[0], _x("width")) == 0) p->width = strtol(_s(atts[1]), NULL, 0); else if (xmlStrcmp(atts[0], _x("height")) == 0) p->height = strtol(_s(atts[1]), NULL, 0); else if (xmlStrcmp(atts[0], _x("progressive")) == 0) p->progressive = strtol(_s(atts[1]), NULL, 0); else if (xmlStrcmp(atts[0], _x("frame_rate_num")) == 0) p->frame_rate_num = strtol(_s(atts[1]), NULL, 0); else if (xmlStrcmp(atts[0], _x("frame_rate_den")) == 0) p->frame_rate_den = strtol(_s(atts[1]), NULL, 0); else if (xmlStrcmp(atts[0], _x("colorspace")) == 0) p->colorspace = strtol(_s(atts[1]), NULL, 0); } } static void on_start_tractor(deserialise_context context, const xmlChar *name, const xmlChar **atts) { mlt_tractor tractor = mlt_tractor_new(); mlt_service service = MLT_TRACTOR_SERVICE(tractor); mlt_properties properties = MLT_SERVICE_PROPERTIES(service); track_service(context->destructors, service, (mlt_destructor) mlt_tractor_close); mlt_properties_set_lcnumeric(MLT_SERVICE_PROPERTIES(service), context->lc_numeric); for (; atts != NULL && *atts != NULL; atts += 2) mlt_properties_set_string(MLT_SERVICE_PROPERTIES(service), (const char *) atts[0], atts[1] == NULL ? "" : (const char *) atts[1]); if (mlt_properties_get(properties, "id") != NULL) mlt_properties_set_data(context->producer_map, mlt_properties_get(properties, "id"), service, 0, NULL, NULL); context_push_service(context, service, mlt_tractor_type); } static void on_end_tractor(deserialise_context context, const xmlChar *name) { // Get the tractor enum service_type type; mlt_service tractor = context_pop_service(context, &type); if (tractor != NULL && type == mlt_tractor_type) { // See if the tractor should be added to a playlist or multitrack if (add_producer(context, tractor, 0, mlt_producer_get_out(MLT_PRODUCER(tractor))) == 0) context_push_service(context, tractor, type); } else { mlt_log_error(NULL, "[producer_xml] Invalid state for tractor\n"); } } static void on_start_multitrack(deserialise_context context, const xmlChar *name, const xmlChar **atts) { enum service_type type; mlt_service parent = context_pop_service(context, &type); // If we don't have a parent, then create one now, providing we're in a state where we can if (parent == NULL || (type == mlt_playlist_type || type == mlt_multitrack_type)) { mlt_tractor tractor = NULL; // Push the parent back if (parent != NULL) context_push_service(context, parent, type); // Create a tractor to contain the multitrack tractor = mlt_tractor_new(); parent = MLT_TRACTOR_SERVICE(tractor); track_service(context->destructors, parent, (mlt_destructor) mlt_tractor_close); mlt_properties_set_lcnumeric(MLT_SERVICE_PROPERTIES(parent), context->lc_numeric); type = mlt_tractor_type; // Flag it as a synthesised tractor for clean up later mlt_properties_set_int(MLT_SERVICE_PROPERTIES(parent), "loader_synth", 1); } if (type == mlt_tractor_type) { mlt_service service = MLT_SERVICE(mlt_tractor_multitrack(MLT_TRACTOR(parent))); mlt_properties properties = MLT_SERVICE_PROPERTIES(service); for (; atts != NULL && *atts != NULL; atts += 2) mlt_properties_set_string(properties, (const char *) atts[0], atts[1] == NULL ? "" : (const char *) atts[1]); if (mlt_properties_get(properties, "id") != NULL) mlt_properties_set_data(context->producer_map, mlt_properties_get(properties, "id"), service, 0, NULL, NULL); context_push_service(context, parent, type); context_push_service(context, service, mlt_multitrack_type); } else { mlt_log_error(NULL, "[producer_xml] Invalid multitrack position\n"); } } static void on_end_multitrack(deserialise_context context, const xmlChar *name) { // Get the multitrack from the stack enum service_type type; mlt_service service = context_pop_service(context, &type); if (service == NULL || type != mlt_multitrack_type) mlt_log_error(NULL, "[producer_xml] End multitrack in the wrong state...\n"); } static void on_start_playlist(deserialise_context context, const xmlChar *name, const xmlChar **atts) { mlt_playlist playlist = mlt_playlist_new(context->profile); mlt_service service = MLT_PLAYLIST_SERVICE(playlist); mlt_properties properties = MLT_SERVICE_PROPERTIES(service); track_service(context->destructors, service, (mlt_destructor) mlt_playlist_close); for (; atts != NULL && *atts != NULL; atts += 2) { mlt_properties_set_string(properties, (const char *) atts[0], atts[1] == NULL ? "" : (const char *) atts[1]); // Out will be overwritten later as we append, so we need to save it if (xmlStrcmp(atts[0], _x("out")) == 0) mlt_properties_set_string(properties, "_xml.out", (const char *) atts[1]); } if (mlt_properties_get(properties, "id") != NULL) mlt_properties_set_data(context->producer_map, mlt_properties_get(properties, "id"), service, 0, NULL, NULL); context_push_service(context, service, mlt_playlist_type); } static void on_end_playlist(deserialise_context context, const xmlChar *name) { // Get the playlist from the stack enum service_type type; mlt_service service = context_pop_service(context, &type); if (service != NULL && type == mlt_playlist_type) { mlt_properties properties = MLT_SERVICE_PROPERTIES(service); mlt_position in = -1; mlt_position out = -1; if (mlt_properties_get(properties, "in")) in = mlt_properties_get_position(properties, "in"); if (mlt_properties_get(properties, "out")) out = mlt_properties_get_position(properties, "out"); // See if the playlist should be added to a playlist or multitrack if (add_producer(context, service, in, out) == 0) context_push_service(context, service, type); } else { mlt_log_error(NULL, "[producer_xml] Invalid state of playlist end %d\n", type); } } static void on_start_chain(deserialise_context context, const xmlChar *name, const xmlChar **atts) { mlt_chain chain = mlt_chain_init(context->profile); mlt_service service = MLT_CHAIN_SERVICE(chain); mlt_properties properties = MLT_SERVICE_PROPERTIES(service); track_service(context->destructors, service, (mlt_destructor) mlt_chain_close); for (; atts != NULL && *atts != NULL; atts += 2) { mlt_properties_set_string(properties, (const char *) atts[0], atts[1] == NULL ? "" : (const char *) atts[1]); // Out will be overwritten later as we append, so we need to save it if (xmlStrcmp(atts[0], _x("out")) == 0) mlt_properties_set_string(properties, "_xml.out", (const char *) atts[1]); } if (mlt_properties_get(properties, "id") != NULL) mlt_properties_set_data(context->producer_map, mlt_properties_get(properties, "id"), service, 0, NULL, NULL); context_push_service(context, service, mlt_chain_type); } static void on_end_chain(deserialise_context context, const xmlChar *name) { // Get the chain from the stack enum service_type type; mlt_service service = context_pop_service(context, &type); if (service != NULL && type == mlt_chain_type) { mlt_chain chain = MLT_CHAIN(service); mlt_properties properties = MLT_SERVICE_PROPERTIES(service); mlt_position in = -1; mlt_position out = -1; mlt_producer source = NULL; qualify_property(context, properties, "resource"); char *resource = mlt_properties_get(properties, "resource"); // Let Kino-SMIL src be a synonym for resource if (resource == NULL) { qualify_property(context, properties, "src"); resource = mlt_properties_get(properties, "src"); } // Instantiate the producer if (mlt_properties_get(properties, "mlt_service") != NULL) { char *service_name = trim(mlt_properties_get(properties, "mlt_service")); if (resource) { // If a document was saved as +INVALID.txt (see below), then ignore the mlt_service and // try to load it just from the resource. This is an attempt to recover the failed // producer in case, for example, a file returns. if (!strcmp("qtext", service_name)) { const char *text = mlt_properties_get(properties, "text"); if (text && !strcmp("INVALID", text)) { service_name = NULL; } } else if (!strcmp("pango", service_name)) { const char *markup = mlt_properties_get(properties, "markup"); if (markup && !strcmp("INVALID", markup)) { service_name = NULL; } } if (service_name) { char *temp = calloc(1, strlen(service_name) + strlen(resource) + 2); strcat(temp, service_name); strcat(temp, ":"); strcat(temp, resource); source = mlt_factory_producer(context->profile, NULL, temp); free(temp); } } else { source = mlt_factory_producer(context->profile, NULL, service_name); } } // Just in case the plugin requested doesn't exist... if (!source && resource) source = mlt_factory_producer(context->profile, NULL, resource); if (!source) { mlt_log_error(NULL, "[producer_xml] failed to load chain \"%s\"\n", resource); source = mlt_factory_producer(context->profile, NULL, "+INVALID.txt"); if (source) { // Save the original mlt_service for the consumer to serialize it as original. mlt_properties_set_string(properties, "_xml_mlt_service", mlt_properties_get(properties, "mlt_service")); } } if (!source) source = mlt_factory_producer(context->profile, NULL, "colour:red"); // Propagate properties to the source mlt_properties_inherit(MLT_PRODUCER_PROPERTIES(source), properties); // Add the source producer to the chain mlt_chain_set_source(chain, source); mlt_producer_close(source); mlt_chain_attach_normalizers(chain); // See if the chain should be added to a playlist or multitrack if (mlt_properties_get(properties, "in")) in = mlt_properties_get_position(properties, "in"); if (mlt_properties_get(properties, "out")) out = mlt_properties_get_position(properties, "out"); if (add_producer(context, service, in, out) == 0) context_push_service(context, service, type); } else { mlt_log_error(NULL, "[producer_xml] Invalid state of chain end %d\n", type); } } static void on_start_link(deserialise_context context, const xmlChar *name, const xmlChar **atts) { // Store properties until the service type is known mlt_service service = calloc(1, sizeof(struct mlt_service_s)); mlt_service_init(service, NULL); mlt_properties properties = MLT_SERVICE_PROPERTIES(service); context_push_service(context, service, mlt_link_type); for (; atts != NULL && *atts != NULL; atts += 2) mlt_properties_set_string(properties, (const char *) atts[0], atts[1] == NULL ? "" : (const char *) atts[1]); } static void on_end_link(deserialise_context context, const xmlChar *name) { enum service_type type; mlt_service service = context_pop_service(context, &type); mlt_properties properties = MLT_SERVICE_PROPERTIES(service); enum service_type parent_type = mlt_invalid_type; mlt_service parent = context_pop_service(context, &parent_type); if (service != NULL && type == mlt_link_type) { char *id = trim(mlt_properties_get(properties, "mlt_service")); mlt_service link = MLT_SERVICE(mlt_factory_link(id, NULL)); mlt_properties link_props = MLT_SERVICE_PROPERTIES(link); if (!link) { mlt_log_error(NULL, "[producer_xml] failed to load link \"%s\"\n", id); if (parent) context_push_service(context, parent, parent_type); mlt_service_close(service); free(service); return; } track_service(context->destructors, link, (mlt_destructor) mlt_link_close); mlt_properties_set_lcnumeric(MLT_SERVICE_PROPERTIES(link), context->lc_numeric); // Do not let XML overwrite these important properties set by mlt_factory. mlt_properties_set_string(properties, "mlt_type", NULL); mlt_properties_set_string(properties, "mlt_service", NULL); // Propagate the properties mlt_properties_inherit(link_props, properties); // Attach the link to the chain if (parent != NULL) { if (parent_type == mlt_chain_type) { mlt_chain_attach(MLT_CHAIN(parent), MLT_LINK(link)); } else { mlt_log_error(NULL, "[producer_xml] link can only be added to a chain...\n"); } // Put the parent back on the stack context_push_service(context, parent, parent_type); } else { mlt_log_error(NULL, "[producer_xml] link closed with invalid parent...\n"); } } else { mlt_log_error(NULL, "[producer_xml] Invalid top of stack on link close\n"); } if (service) { mlt_service_close(service); free(service); } } static void on_start_producer(deserialise_context context, const xmlChar *name, const xmlChar **atts) { // use a dummy service to hold properties to allow arbitrary nesting mlt_service service = calloc(1, sizeof(struct mlt_service_s)); mlt_service_init(service, NULL); mlt_properties properties = MLT_SERVICE_PROPERTIES(service); context_push_service(context, service, mlt_dummy_producer_type); for (; atts != NULL && *atts != NULL; atts += 2) mlt_properties_set_string(properties, (const char *) atts[0], atts[1] == NULL ? "" : (const char *) atts[1]); } static void on_end_producer(deserialise_context context, const xmlChar *name) { enum service_type type; mlt_service service = context_pop_service(context, &type); mlt_properties properties = MLT_SERVICE_PROPERTIES(service); if (service != NULL && type == mlt_dummy_producer_type) { mlt_service producer = NULL; qualify_property(context, properties, "resource"); char *resource = mlt_properties_get(properties, "resource"); // Let Kino-SMIL src be a synonym for resource if (resource == NULL) { qualify_property(context, properties, "src"); resource = mlt_properties_get(properties, "src"); } // Instantiate the producer if (mlt_properties_get(properties, "mlt_service") != NULL) { char *service_name = trim(mlt_properties_get(properties, "mlt_service")); if (resource) { // If a document was saved as +INVALID.txt (see below), then ignore the mlt_service and // try to load it just from the resource. This is an attempt to recover the failed // producer in case, for example, a file returns. if (!strcmp("qtext", service_name)) { const char *text = mlt_properties_get(properties, "text"); if (text && !strcmp("INVALID", text)) { service_name = NULL; } } else if (!strcmp("pango", service_name)) { const char *markup = mlt_properties_get(properties, "markup"); if (markup && !strcmp("INVALID", markup)) { service_name = NULL; } } if (service_name) { char *temp = calloc(1, strlen(service_name) + strlen(resource) + 2); strcat(temp, service_name); strcat(temp, ":"); strcat(temp, resource); producer = MLT_SERVICE(mlt_factory_producer(context->profile, NULL, temp)); free(temp); } } else { producer = MLT_SERVICE(mlt_factory_producer(context->profile, NULL, service_name)); } } // Just in case the plugin requested doesn't exist... if (!producer && resource) producer = MLT_SERVICE(mlt_factory_producer(context->profile, NULL, resource)); if (!producer) { mlt_log_error(NULL, "[producer_xml] failed to load producer \"%s\"\n", resource); producer = MLT_SERVICE(mlt_factory_producer(context->profile, NULL, "+INVALID.txt")); if (producer) { // Save the original mlt_service for the consumer to serialize it as original. mlt_properties_set_string(MLT_SERVICE_PROPERTIES(producer), "_xml_mlt_service", mlt_properties_get(properties, "mlt_service")); } } if (!producer) producer = MLT_SERVICE(mlt_factory_producer(context->profile, NULL, "colour:red")); if (!producer) { mlt_service_close(service); free(service); return; } // Track this producer track_service(context->destructors, producer, (mlt_destructor) mlt_producer_close); mlt_properties producer_props = MLT_SERVICE_PROPERTIES(producer); mlt_properties_set_lcnumeric(producer_props, context->lc_numeric); if (mlt_properties_get(producer_props, "seekable")) context->seekable &= mlt_properties_get_int(producer_props, "seekable"); // Propagate the properties qualify_property(context, properties, "resource"); qualify_property(context, properties, "luma"); qualify_property(context, properties, "luma.resource"); qualify_property(context, properties, "composite.luma"); qualify_property(context, properties, "producer.resource"); qualify_property(context, properties, "argument"); // timewarp producer // Handle in/out properties separately mlt_position in = -1; mlt_position out = -1; // Get in if (mlt_properties_get(properties, "in")) in = mlt_properties_get_position(properties, "in"); // Let Kino-SMIL clipBegin be a synonym for in else if (mlt_properties_get(properties, "clipBegin")) in = mlt_properties_get_position(properties, "clipBegin"); // Get out if (mlt_properties_get(properties, "out")) out = mlt_properties_get_position(properties, "out"); // Let Kino-SMIL clipEnd be a synonym for out else if (mlt_properties_get(properties, "clipEnd")) out = mlt_properties_get_position(properties, "clipEnd"); // Remove in and out mlt_properties_clear(properties, "in"); mlt_properties_clear(properties, "out"); // Let a child XML length extend the length of a parent XML clip. if (mlt_properties_get(producer_props, "mlt_service") && (!strcmp("xml", mlt_properties_get(producer_props, "mlt_service")) || !strcmp("consumer", mlt_properties_get(producer_props, "mlt_service"))) && mlt_properties_get_position(producer_props, "length") > mlt_properties_get_position(properties, "length")) { mlt_properties_set_position(properties, "length", mlt_properties_get_position(producer_props, "length")); in = mlt_properties_get_position(producer_props, "in"); out = mlt_properties_get_position(producer_props, "out"); } // Do not let XML overwrite these important properties set by mlt_factory. mlt_properties_set_string(properties, "mlt_type", NULL); mlt_properties_set_string(properties, "mlt_service", NULL); // Inherit the properties mlt_properties_inherit(producer_props, properties); // Attach all filters from service onto producer attach_filters(producer, service); // Add the producer to the producer map if (mlt_properties_get(properties, "id") != NULL) mlt_properties_set_data(context->producer_map, mlt_properties_get(properties, "id"), producer, 0, NULL, NULL); // See if the producer should be added to a playlist or multitrack if (add_producer(context, producer, in, out) == 0) { // Otherwise, set in and out on... if (in != -1 || out != -1) { // Get the parent service enum service_type type; mlt_service parent = context_pop_service(context, &type); if (parent != NULL) { // Get the parent properties properties = MLT_SERVICE_PROPERTIES(parent); char *resource = mlt_properties_get(properties, "resource"); // Put the parent producer back context_push_service(context, parent, type); // If the parent is a track or entry if (resource && (strcmp(resource, "") == 0)) { if (in > -1) mlt_properties_set_position(properties, "in", in); if (out > -1) mlt_properties_set_position(properties, "out", out); } else { // Otherwise, set in and out on producer directly mlt_producer_set_in_and_out(MLT_PRODUCER(producer), in, out); } } else { // Otherwise, set in and out on producer directly mlt_producer_set_in_and_out(MLT_PRODUCER(producer), in, out); } } // Push the producer onto the stack context_push_service(context, producer, mlt_producer_type); } } if (service) { mlt_service_close(service); free(service); } } static void on_start_blank(deserialise_context context, const xmlChar *name, const xmlChar **atts) { // Get the playlist from the stack enum service_type type; mlt_service service = context_pop_service(context, &type); if (type == mlt_playlist_type && service != NULL) { // Look for the length attribute for (; atts != NULL && *atts != NULL; atts += 2) { if (xmlStrcmp(atts[0], _x("length")) == 0) { // Append a blank to the playlist mlt_playlist_blank_time(MLT_PLAYLIST(service), _s(atts[1])); break; } } // Push the playlist back onto the stack context_push_service(context, service, type); } else { mlt_log_error(NULL, "[producer_xml] blank without a playlist - a definite no no\n"); } } static void on_start_entry(deserialise_context context, const xmlChar *name, const xmlChar **atts) { mlt_producer entry = NULL; mlt_properties temp = mlt_properties_new(); mlt_properties_set_data(temp, "_profile", context->profile, 0, NULL, NULL); mlt_properties_set_lcnumeric(temp, context->lc_numeric); for (; atts != NULL && *atts != NULL; atts += 2) { mlt_properties_set_string(temp, (const char *) atts[0], atts[1] == NULL ? "" : (const char *) atts[1]); // Look for the producer attribute if (xmlStrcmp(atts[0], _x("producer")) == 0) { mlt_producer producer = mlt_properties_get_data(context->producer_map, (const char *) atts[1], NULL); if (producer != NULL) mlt_properties_set_data(temp, "producer", producer, 0, NULL, NULL); } } // If we have a valid entry if (mlt_properties_get_data(temp, "producer", NULL) != NULL) { mlt_playlist_clip_info info; enum service_type parent_type = mlt_invalid_type; mlt_service parent = context_pop_service(context, &parent_type); mlt_producer producer = mlt_properties_get_data(temp, "producer", NULL); if (parent_type == mlt_playlist_type) { // Append the producer to the playlist mlt_position in = -1; mlt_position out = -1; if (mlt_properties_get(temp, "in")) in = mlt_properties_get_position(temp, "in"); if (mlt_properties_get(temp, "out")) out = mlt_properties_get_position(temp, "out"); mlt_playlist_append_io(MLT_PLAYLIST(parent), producer, in, out); // Handle the repeat property if (mlt_properties_get_int(temp, "repeat") > 0) { mlt_playlist_repeat_clip(MLT_PLAYLIST(parent), mlt_playlist_count(MLT_PLAYLIST(parent)) - 1, mlt_properties_get_int(temp, "repeat")); } mlt_playlist_get_clip_info(MLT_PLAYLIST(parent), &info, mlt_playlist_count(MLT_PLAYLIST(parent)) - 1); entry = info.cut; } else { mlt_log_error(NULL, "[producer_xml] Entry not part of a playlist...\n"); } context_push_service(context, parent, parent_type); } // Push the cut onto the stack context_push_service(context, MLT_PRODUCER_SERVICE(entry), mlt_entry_type); mlt_properties_close(temp); } static void on_end_entry(deserialise_context context, const xmlChar *name) { // Get the entry from the stack enum service_type entry_type = mlt_invalid_type; mlt_service entry = context_pop_service(context, &entry_type); if (entry == NULL && entry_type != mlt_entry_type) { mlt_log_error(NULL, "[producer_xml] Invalid state at end of entry\n"); } } static void on_start_track(deserialise_context context, const xmlChar *name, const xmlChar **atts) { // use a dummy service to hold properties to allow arbitrary nesting mlt_service service = calloc(1, sizeof(struct mlt_service_s)); mlt_service_init(service, NULL); // Push the dummy service onto the stack context_push_service(context, service, mlt_entry_type); mlt_properties_set_string(MLT_SERVICE_PROPERTIES(service), "resource", ""); for (; atts != NULL && *atts != NULL; atts += 2) { mlt_properties_set_string(MLT_SERVICE_PROPERTIES(service), (const char *) atts[0], atts[1] == NULL ? "" : (const char *) atts[1]); // Look for the producer attribute if (xmlStrcmp(atts[0], _x("producer")) == 0) { mlt_producer producer = mlt_properties_get_data(context->producer_map, (const char *) atts[1], NULL); if (producer != NULL) mlt_properties_set_data(MLT_SERVICE_PROPERTIES(service), "producer", producer, 0, NULL, NULL); } } } static void on_end_track(deserialise_context context, const xmlChar *name) { // Get the track from the stack enum service_type track_type; mlt_service track = context_pop_service(context, &track_type); if (track != NULL && track_type == mlt_entry_type) { mlt_properties track_props = MLT_SERVICE_PROPERTIES(track); enum service_type parent_type = mlt_invalid_type; mlt_service parent = context_pop_service(context, &parent_type); mlt_multitrack multitrack = NULL; mlt_producer producer = mlt_properties_get_data(track_props, "producer", NULL); mlt_properties producer_props = MLT_PRODUCER_PROPERTIES(producer); if (parent_type == mlt_tractor_type) multitrack = mlt_tractor_multitrack(MLT_TRACTOR(parent)); else if (parent_type == mlt_multitrack_type) multitrack = MLT_MULTITRACK(parent); else mlt_log_error(NULL, "[producer_xml] track contained in an invalid container\n"); if (multitrack != NULL) { // Set producer i/o if specified if (mlt_properties_get(track_props, "in") != NULL || mlt_properties_get(track_props, "out") != NULL) { mlt_position in = -1; mlt_position out = -1; if (mlt_properties_get(track_props, "in")) in = mlt_properties_get_position(track_props, "in"); if (mlt_properties_get(track_props, "out")) out = mlt_properties_get_position(track_props, "out"); mlt_producer cut = mlt_producer_cut(MLT_PRODUCER(producer), in, out); mlt_multitrack_connect(multitrack, cut, mlt_multitrack_count(multitrack)); mlt_properties_inherit(MLT_PRODUCER_PROPERTIES(cut), track_props); track_props = MLT_PRODUCER_PROPERTIES(cut); mlt_producer_close(cut); } else { mlt_multitrack_connect(multitrack, producer, mlt_multitrack_count(multitrack)); } // Set the hide state of the track producer char *hide_s = mlt_properties_get(track_props, "hide"); if (hide_s != NULL) { if (strcmp(hide_s, "video") == 0) mlt_properties_set_int(producer_props, "hide", 1); else if (strcmp(hide_s, "audio") == 0) mlt_properties_set_int(producer_props, "hide", 2); else if (strcmp(hide_s, "both") == 0) mlt_properties_set_int(producer_props, "hide", 3); } } if (parent != NULL) context_push_service(context, parent, parent_type); } else { mlt_log_error(NULL, "[producer_xml] Invalid state at end of track\n"); } if (track) { mlt_service_close(track); free(track); } } static void on_start_filter(deserialise_context context, const xmlChar *name, const xmlChar **atts) { // use a dummy service to hold properties to allow arbitrary nesting mlt_service service = calloc(1, sizeof(struct mlt_service_s)); mlt_service_init(service, NULL); mlt_properties properties = MLT_SERVICE_PROPERTIES(service); context_push_service(context, service, mlt_dummy_filter_type); // Set the properties for (; atts != NULL && *atts != NULL; atts += 2) mlt_properties_set_string(properties, (const char *) atts[0], (const char *) atts[1]); } static void on_end_filter(deserialise_context context, const xmlChar *name) { enum service_type type; mlt_service service = context_pop_service(context, &type); mlt_properties properties = MLT_SERVICE_PROPERTIES(service); enum service_type parent_type = mlt_invalid_type; mlt_service parent = context_pop_service(context, &parent_type); if (service != NULL && type == mlt_dummy_filter_type) { char *id = trim(mlt_properties_get(properties, "mlt_service")); mlt_service filter = MLT_SERVICE(mlt_factory_filter(context->profile, id, NULL)); mlt_properties filter_props = MLT_SERVICE_PROPERTIES(filter); if (!filter) { mlt_log_error(NULL, "[producer_xml] failed to load filter \"%s\"\n", id); if (parent) context_push_service(context, parent, parent_type); mlt_service_close(service); free(service); return; } track_service(context->destructors, filter, (mlt_destructor) mlt_filter_close); mlt_properties_set_lcnumeric(MLT_SERVICE_PROPERTIES(filter), context->lc_numeric); // Do not let XML overwrite these important properties set by mlt_factory. mlt_properties_set_string(properties, "mlt_type", NULL); mlt_properties_set_string(properties, "mlt_service", NULL); // Propagate the properties qualify_property(context, properties, "resource"); qualify_property(context, properties, "luma"); qualify_property(context, properties, "luma.resource"); qualify_property(context, properties, "composite.luma"); qualify_property(context, properties, "producer.resource"); qualify_property(context, properties, "filename"); qualify_property(context, properties, "av.file"); qualify_property(context, properties, "av.filename"); qualify_property(context, properties, "filter.resource"); mlt_properties_inherit(filter_props, properties); // Attach all filters from service onto filter attach_filters(filter, service); // Associate the filter with the parent if (parent != NULL) { if (parent_type == mlt_tractor_type && mlt_properties_get(properties, "track")) { mlt_field field = mlt_tractor_field(MLT_TRACTOR(parent)); mlt_field_plant_filter(field, MLT_FILTER(filter), mlt_properties_get_int(properties, "track")); mlt_filter_set_in_and_out(MLT_FILTER(filter), mlt_properties_get_int(properties, "in"), mlt_properties_get_int(properties, "out")); } else { mlt_service_attach(parent, MLT_FILTER(filter)); } // Put the parent back on the stack context_push_service(context, parent, parent_type); } else { mlt_log_error(NULL, "[producer_xml] filter closed with invalid parent...\n"); } } else { mlt_log_error(NULL, "[producer_xml] Invalid top of stack on filter close\n"); } if (service) { mlt_service_close(service); free(service); } } static void on_start_transition(deserialise_context context, const xmlChar *name, const xmlChar **atts) { // use a dummy service to hold properties to allow arbitrary nesting mlt_service service = calloc(1, sizeof(struct mlt_service_s)); mlt_service_init(service, NULL); mlt_properties properties = MLT_SERVICE_PROPERTIES(service); context_push_service(context, service, mlt_dummy_transition_type); // Set the properties for (; atts != NULL && *atts != NULL; atts += 2) mlt_properties_set_string(properties, (const char *) atts[0], (const char *) atts[1]); } static void on_end_transition(deserialise_context context, const xmlChar *name) { enum service_type type; mlt_service service = context_pop_service(context, &type); mlt_properties properties = MLT_SERVICE_PROPERTIES(service); enum service_type parent_type = mlt_invalid_type; mlt_service parent = context_pop_service(context, &parent_type); if (service != NULL && type == mlt_dummy_transition_type) { char *id = trim(mlt_properties_get(properties, "mlt_service")); mlt_service effect = MLT_SERVICE(mlt_factory_transition(context->profile, id, NULL)); mlt_properties effect_props = MLT_SERVICE_PROPERTIES(effect); if (!effect) { mlt_log_error(NULL, "[producer_xml] failed to load transition \"%s\"\n", id); if (parent) context_push_service(context, parent, parent_type); mlt_service_close(service); free(service); return; } track_service(context->destructors, effect, (mlt_destructor) mlt_transition_close); mlt_properties_set_lcnumeric(MLT_SERVICE_PROPERTIES(effect), context->lc_numeric); // Do not let XML overwrite these important properties set by mlt_factory. mlt_properties_set_string(properties, "mlt_type", NULL); mlt_properties_set_string(properties, "mlt_service", NULL); // Propagate the properties qualify_property(context, properties, "resource"); qualify_property(context, properties, "luma"); qualify_property(context, properties, "luma.resource"); qualify_property(context, properties, "composite.luma"); qualify_property(context, properties, "producer.resource"); mlt_properties_inherit(effect_props, properties); // Attach all filters from service onto effect attach_filters(effect, service); // Associate the filter with the parent if (parent != NULL) { if (parent_type == mlt_tractor_type) { mlt_field field = mlt_tractor_field(MLT_TRACTOR(parent)); mlt_field_plant_transition(field, MLT_TRANSITION(effect), mlt_properties_get_int(properties, "a_track"), mlt_properties_get_int(properties, "b_track")); mlt_transition_set_in_and_out(MLT_TRANSITION(effect), mlt_properties_get_int(properties, "in"), mlt_properties_get_int(properties, "out")); } else { mlt_log_warning(NULL, "[producer_xml] Misplaced transition - ignoring\n"); } // Put the parent back on the stack context_push_service(context, parent, parent_type); } else { mlt_log_error(NULL, "[producer_xml] transition closed with invalid parent...\n"); } } else { mlt_log_error(NULL, "[producer_xml] Invalid top of stack on transition close\n"); } if (service) { mlt_service_close(service); free(service); } } static void on_start_consumer(deserialise_context context, const xmlChar *name, const xmlChar **atts) { if (context->pass == 1) { mlt_properties properties = mlt_properties_new(); mlt_properties_set_lcnumeric(properties, context->lc_numeric); context_push_service(context, (mlt_service) properties, mlt_dummy_consumer_type); // Set the properties from attributes for (; atts != NULL && *atts != NULL; atts += 2) mlt_properties_set_string(properties, (const char *) atts[0], (const char *) atts[1]); } } static void set_preview_scale(mlt_profile *consumer_profile, mlt_profile *profile, double scale) { *consumer_profile = mlt_profile_clone(*profile); if (*consumer_profile) { (*consumer_profile)->width *= scale; (*consumer_profile)->width -= (*consumer_profile)->width % 2; (*consumer_profile)->height *= scale; (*consumer_profile)->height -= (*consumer_profile)->height % 2; } } static void on_end_consumer(deserialise_context context, const xmlChar *name) { if (context->pass == 1) { // Get the consumer from the stack enum service_type type; mlt_properties properties = (mlt_properties) context_pop_service(context, &type); if (properties && type == mlt_dummy_consumer_type) { qualify_property(context, properties, "resource"); qualify_property(context, properties, "target"); char *resource = mlt_properties_get(properties, "resource"); if (context->multi_consumer > 1 || context->qglsl || mlt_properties_get_int(context->params, "multi")) { // Instantiate the multi consumer if (!context->consumer) { if (context->qglsl) context->consumer = context->qglsl; else context->consumer = mlt_factory_consumer(context->profile, "multi", NULL); if (context->consumer) { // Track this consumer track_service(context->destructors, MLT_CONSUMER_SERVICE(context->consumer), (mlt_destructor) mlt_consumer_close); mlt_properties_set_lcnumeric(MLT_CONSUMER_PROPERTIES(context->consumer), context->lc_numeric); } } if (context->consumer) { // Set this properties object on multi consumer mlt_properties consumer_properties = MLT_CONSUMER_PROPERTIES(context->consumer); char key[20]; snprintf(key, sizeof(key), "%d", context->consumer_count++); mlt_properties_inc_ref(properties); mlt_properties_set_data(consumer_properties, key, properties, 0, (mlt_destructor) mlt_properties_close, NULL); // Pass in / out if provided mlt_properties_pass_list(consumer_properties, properties, "in, out"); // Pass along quality and performance properties to the multi consumer and its render thread(s). if (!context->qglsl) { mlt_properties_pass_list( consumer_properties, properties, "real_time, deinterlacer, deinterlace_method, rescale, progressive, " "top_field_first, channels, channel_layout"); // We only really know how to optimize real_time for the avformat consumer. const char *service_name = mlt_properties_get(properties, "mlt_service"); if (service_name && !strcmp("avformat", service_name)) mlt_properties_set_int(properties, "real_time", -1); } } } else { double scale = mlt_properties_get_double(properties, "scale"); if (scale > 0.0) { set_preview_scale(&context->consumer_profile, &context->profile, scale); } // Instantiate the consumer char *id = trim(mlt_properties_get(properties, "mlt_service")); mlt_profile profile = context->consumer_profile ? context->consumer_profile : context->profile; context->consumer = mlt_factory_consumer(profile, id, resource); if (context->consumer) { // Track this consumer track_service(context->destructors, MLT_CONSUMER_SERVICE(context->consumer), (mlt_destructor) mlt_consumer_close); mlt_properties_set_lcnumeric(MLT_CONSUMER_PROPERTIES(context->consumer), context->lc_numeric); if (context->consumer_profile) { mlt_properties_set_data(MLT_CONSUMER_PROPERTIES(context->consumer), "_profile", context->consumer_profile, sizeof(*context->consumer_profile), (mlt_destructor) mlt_profile_close, NULL); } // Do not let XML overwrite these important properties set by mlt_factory. mlt_properties_set_string(properties, "mlt_type", NULL); mlt_properties_set_string(properties, "mlt_service", NULL); // Inherit the properties mlt_properties_inherit(MLT_CONSUMER_PROPERTIES(context->consumer), properties); } } } // Close the dummy if (properties) mlt_properties_close(properties); } } static void on_start_property(deserialise_context context, const xmlChar *name, const xmlChar **atts) { mlt_properties properties = current_properties(context); const char *value = NULL; if (properties != NULL) { // Set the properties for (; atts != NULL && *atts != NULL; atts += 2) { if (xmlStrcmp(atts[0], _x("name")) == 0) context->property = strdup(_s(atts[1])); else if (xmlStrcmp(atts[0], _x("value")) == 0) value = _s(atts[1]); } if (context->property != NULL) mlt_properties_set_string(properties, context->property, value == NULL ? "" : value); // Tell parser to collect any further nodes for serialisation context->is_value = 1; } else { mlt_log_error(NULL, "[producer_xml] Property without a parent '%s'?\n", (const char *) name); } } static void on_end_property(deserialise_context context, const xmlChar *name) { mlt_properties properties = current_properties(context); if (properties != NULL) { // Tell parser to stop building a tree context->is_value = 0; // See if there is a xml tree for the value if (context->property != NULL && context->value_doc != NULL) { xmlChar *value; int size; // Serialise the tree to get value xmlDocDumpMemory(context->value_doc, &value, &size); mlt_properties_set_string(properties, context->property, _s(value)); #ifdef _WIN32 xmlFreeFunc xmlFree = NULL; xmlMemGet(&xmlFree, NULL, NULL, NULL); #endif xmlFree(value); xmlFreeDoc(context->value_doc); context->value_doc = NULL; } // Close this property handling free(context->property); context->property = NULL; } else { mlt_log_error(NULL, "[producer_xml] Property without a parent '%s'??\n", (const char *) name); } } static void on_start_properties(deserialise_context context, const xmlChar *name, const xmlChar **atts) { mlt_properties parent_properties = current_properties(context); if (parent_properties != NULL) { mlt_properties properties = NULL; // Get the name for (; atts != NULL && *atts != NULL; atts += 2) { if (xmlStrcmp(atts[0], _x("name")) == 0) { properties = mlt_properties_new(); mlt_properties_set_properties(parent_properties, _s(atts[1]), properties); mlt_properties_dec_ref(properties); } else { mlt_log_warning(NULL, "[producer_xml] Invalid attribute for properties '%s=%s'\n", (const char *) atts[0], (const char *) atts[1]); } } if (properties) { mlt_deque_push_back(context->stack_properties, properties); } else { mlt_log_error(NULL, "[producer_xml] Properties without a name '%s'?\n", (const char *) name); } } else { mlt_log_error(NULL, "[producer_xml] Properties without a parent '%s'?\n", (const char *) name); } } static void on_end_properties(deserialise_context context, const xmlChar *name) { if (mlt_deque_count(context->stack_properties)) { mlt_deque_pop_back(context->stack_properties); } else { mlt_log_error(NULL, "[producer_xml] Properties end missing properties '%s'.\n", (const char *) name); } } static void on_start_element(void *ctx, const xmlChar *name, const xmlChar **atts) { struct _xmlParserCtxt *xmlcontext = (struct _xmlParserCtxt *) ctx; deserialise_context context = (deserialise_context) (xmlcontext->_private); if (context->pass == 0) { if (xmlStrcmp(name, _x("mlt")) == 0 || xmlStrcmp(name, _x("profile")) == 0 || xmlStrcmp(name, _x("profileinfo")) == 0) on_start_profile(context, name, atts); if (xmlStrcmp(name, _x("consumer")) == 0) context->multi_consumer++; // Check for a service beginning with glsl. or movit. for (; atts != NULL && *atts != NULL; atts += 2) { if (!xmlStrncmp(atts[1], _x("glsl."), 5) || !xmlStrncmp(atts[1], _x("movit."), 6)) { mlt_properties_set_int(context->params, "qglsl", 1); break; } } return; } mlt_deque_push_back_int(context->stack_branch, mlt_deque_pop_back_int(context->stack_branch) + 1); mlt_deque_push_back_int(context->stack_branch, 0); // Build a tree from nodes within a property value if (context->is_value == 1 && context->pass == 1) { xmlNodePtr node = xmlNewNode(NULL, name); if (context->value_doc == NULL) { // Start a new tree context->value_doc = xmlNewDoc(_x("1.0")); xmlDocSetRootElement(context->value_doc, node); } else { // Append child to tree xmlAddChild(mlt_deque_peek_back(context->stack_node), node); } context_push_node(context, node); // Set the attributes for (; atts != NULL && *atts != NULL; atts += 2) xmlSetProp(node, atts[0], atts[1]); } else if (xmlStrcmp(name, _x("tractor")) == 0) on_start_tractor(context, name, atts); else if (xmlStrcmp(name, _x("multitrack")) == 0) on_start_multitrack(context, name, atts); else if (xmlStrcmp(name, _x("playlist")) == 0 || xmlStrcmp(name, _x("seq")) == 0 || xmlStrcmp(name, _x("smil")) == 0) on_start_playlist(context, name, atts); else if (xmlStrcmp(name, _x("producer")) == 0 || xmlStrcmp(name, _x("video")) == 0) on_start_producer(context, name, atts); else if (xmlStrcmp(name, _x("blank")) == 0) on_start_blank(context, name, atts); else if (xmlStrcmp(name, _x("entry")) == 0) on_start_entry(context, name, atts); else if (xmlStrcmp(name, _x("track")) == 0) on_start_track(context, name, atts); else if (xmlStrcmp(name, _x("filter")) == 0) on_start_filter(context, name, atts); else if (xmlStrcmp(name, _x("transition")) == 0) on_start_transition(context, name, atts); else if (xmlStrcmp(name, _x("chain")) == 0) on_start_chain(context, name, atts); else if (xmlStrcmp(name, _x("link")) == 0) on_start_link(context, name, atts); else if (xmlStrcmp(name, _x("property")) == 0) on_start_property(context, name, atts); else if (xmlStrcmp(name, _x("properties")) == 0) on_start_properties(context, name, atts); else if (xmlStrcmp(name, _x("consumer")) == 0) on_start_consumer(context, name, atts); else if (xmlStrcmp(name, _x("westley")) == 0 || xmlStrcmp(name, _x("mlt")) == 0) { for (; atts != NULL && *atts != NULL; atts += 2) { if (xmlStrcmp(atts[0], _x("LC_NUMERIC"))) mlt_properties_set_string(context->producer_map, _s(atts[0]), _s(atts[1])); else if (!context->lc_numeric) context->lc_numeric = strdup(_s(atts[1])); } } } static void on_end_element(void *ctx, const xmlChar *name) { struct _xmlParserCtxt *xmlcontext = (struct _xmlParserCtxt *) ctx; deserialise_context context = (deserialise_context) (xmlcontext->_private); if (context->is_value == 1 && context->pass == 1 && xmlStrcmp(name, _x("property")) != 0) context_pop_node(context); else if (xmlStrcmp(name, _x("multitrack")) == 0) on_end_multitrack(context, name); else if (xmlStrcmp(name, _x("playlist")) == 0 || xmlStrcmp(name, _x("seq")) == 0 || xmlStrcmp(name, _x("smil")) == 0) on_end_playlist(context, name); else if (xmlStrcmp(name, _x("track")) == 0) on_end_track(context, name); else if (xmlStrcmp(name, _x("entry")) == 0) on_end_entry(context, name); else if (xmlStrcmp(name, _x("tractor")) == 0) on_end_tractor(context, name); else if (xmlStrcmp(name, _x("property")) == 0) on_end_property(context, name); else if (xmlStrcmp(name, _x("properties")) == 0) on_end_properties(context, name); else if (xmlStrcmp(name, _x("producer")) == 0 || xmlStrcmp(name, _x("video")) == 0) on_end_producer(context, name); else if (xmlStrcmp(name, _x("filter")) == 0) on_end_filter(context, name); else if (xmlStrcmp(name, _x("transition")) == 0) on_end_transition(context, name); else if (xmlStrcmp(name, _x("chain")) == 0) on_end_chain(context, name); else if (xmlStrcmp(name, _x("link")) == 0) on_end_link(context, name); else if (xmlStrcmp(name, _x("consumer")) == 0) on_end_consumer(context, name); mlt_deque_pop_back_int(context->stack_branch); } static void on_characters(void *ctx, const xmlChar *ch, int len) { struct _xmlParserCtxt *xmlcontext = (struct _xmlParserCtxt *) ctx; deserialise_context context = (deserialise_context) (xmlcontext->_private); char *value = calloc(1, len + 1); mlt_properties properties = current_properties(context); value[len] = 0; strncpy(value, (const char *) ch, len); if (mlt_deque_count(context->stack_node)) xmlNodeAddContent(mlt_deque_peek_back(context->stack_node), (xmlChar *) value); // libxml2 generates an on_characters immediately after a get_entity within // an element value, and we ignore it because it is called again during // actual substitution. else if (context->property != NULL && context->entity_is_replace == 0) { char *s = mlt_properties_get(properties, context->property); if (s != NULL) { // Append new text to existing content char *new = calloc(1, strlen(s) + len + 1); strcat(new, s); strcat(new, value); mlt_properties_set_string(properties, context->property, new); free(new); } else mlt_properties_set_string(properties, context->property, value); } context->entity_is_replace = 0; // Check for a service beginning with glsl. or movit. if (!strncmp(value, "glsl.", 5) || !strncmp(value, "movit.", 6)) mlt_properties_set_int(context->params, "qglsl", 1); free(value); } /** Convert parameters parsed from resource into entity declarations. */ static void params_to_entities(deserialise_context context) { if (context->params != NULL) { int i; // Add our params as entity declarations for (i = 0; i < mlt_properties_count(context->params); i++) { xmlChar *name = (xmlChar *) mlt_properties_get_name(context->params, i); xmlAddDocEntity(context->entity_doc, name, XML_INTERNAL_GENERAL_ENTITY, context->publicId, context->systemId, (xmlChar *) mlt_properties_get(context->params, _s(name))); } // Flag completion mlt_properties_close(context->params); context->params = NULL; } } // The following 3 facilitate entity substitution in the SAX parser static void on_internal_subset(void *ctx, const xmlChar *name, const xmlChar *publicId, const xmlChar *systemId) { struct _xmlParserCtxt *xmlcontext = (struct _xmlParserCtxt *) ctx; deserialise_context context = (deserialise_context) (xmlcontext->_private); context->publicId = publicId; context->systemId = systemId; xmlCreateIntSubset(context->entity_doc, name, publicId, systemId); // Override default entities with our parameters params_to_entities(context); } // TODO: Check this with Dan... I think this is for parameterisation // but it's breaking standard escaped entities (like < etc). static void on_entity_declaration(void *ctx, const xmlChar *name, int type, const xmlChar *publicId, const xmlChar *systemId, xmlChar *content) { struct _xmlParserCtxt *xmlcontext = (struct _xmlParserCtxt *) ctx; deserialise_context context = (deserialise_context) (xmlcontext->_private); xmlAddDocEntity(context->entity_doc, name, type, publicId, systemId, content); } // TODO: Check this functionality (see on_entity_declaration) static xmlEntityPtr on_get_entity(void *ctx, const xmlChar *name) { struct _xmlParserCtxt *xmlcontext = (struct _xmlParserCtxt *) ctx; deserialise_context context = (deserialise_context) (xmlcontext->_private); xmlEntityPtr e = NULL; // Setup for entity declarations if not ready if (xmlGetIntSubset(context->entity_doc) == NULL) { xmlCreateIntSubset(context->entity_doc, _x("mlt"), _x(""), _x("")); context->publicId = _x(""); context->systemId = _x(""); } // Add our parameters if not already params_to_entities(context); e = xmlGetPredefinedEntity(name); // Send signal to on_characters that an entity substitution is pending if (e == NULL) { e = xmlGetDocEntity(context->entity_doc, name); if (e != NULL) context->entity_is_replace = 1; } return e; } static void on_error(void *ctx, const char *msg, ...) { struct _xmlError *err_ptr = xmlCtxtGetLastError(ctx); switch (err_ptr->level) { case XML_ERR_WARNING: mlt_log_warning(NULL, "[producer_xml] parse warning: %s\trow: %d\tcol: %d\n", err_ptr->message, err_ptr->line, err_ptr->int2); break; case XML_ERR_ERROR: mlt_log_error(NULL, "[producer_xml] parse error: %s\trow: %d\tcol: %d\n", err_ptr->message, err_ptr->line, err_ptr->int2); break; default: case XML_ERR_FATAL: mlt_log_fatal(NULL, "[producer_xml] parse fatal: %s\trow: %d\tcol: %d\n", err_ptr->message, err_ptr->line, err_ptr->int2); break; } } /** Convert a hexadecimal character to its value. */ static int tohex(char p) { return isdigit(p) ? p - '0' : tolower(p) - 'a' + 10; } /** Decode a url-encoded string containing hexadecimal character sequences. */ static char *url_decode(char *dest, char *src) { char *p = dest; while (*src) { if (*src == '%') { *p++ = (tohex(*(src + 1)) << 4) | tohex(*(src + 2)); src += 3; } else { *p++ = *src++; } } *p = *src; return dest; } /** Extract the filename from a URL attaching parameters to a properties list. */ static void parse_url(mlt_properties properties, char *url) { int i; int n = strlen(url); char *name = NULL; char *value = NULL; int is_query = 0; for (i = 0; i < n; i++) { switch (url[i]) { case '?': url[i++] = '\0'; name = &url[i]; is_query = 1; break; case ':': #ifdef _WIN32 if (url[i + 1] != '/' && url[i + 1] != '\\') #endif case '=': if (is_query) { url[i++] = '\0'; value = &url[i]; } break; case '&': if (is_query) { url[i++] = '\0'; if (name != NULL && value != NULL) mlt_properties_set_string(properties, name, value); name = &url[i]; value = NULL; } break; } } if (name != NULL && value != NULL) mlt_properties_set_string(properties, name, value); } // Quick workaround to avoid unnecessary libxml2 warnings static int file_exists(char *name) { int exists = 0; if (name != NULL) { FILE *f = mlt_fopen(name, "r"); exists = f != NULL; if (exists) fclose(f); } return exists; } // This function will add remaining services in the context service stack marked // with a "xml_retain" property to a property named "xml_retain" on the returned // service. The property is a mlt_properties data property. static void retain_services(struct deserialise_context_s *context, mlt_service service) { mlt_properties retain_list = mlt_properties_new(); enum service_type type; mlt_service retain_service = context_pop_service(context, &type); while (retain_service) { mlt_properties retain_properties = MLT_SERVICE_PROPERTIES(retain_service); if (mlt_properties_get_int(retain_properties, "xml_retain")) { // Remove the retained service from the destructors list. int i; for (i = mlt_properties_count(context->destructors) - 1; i >= 1; i--) { const char *name = mlt_properties_get_name(context->destructors, i); if (mlt_properties_get_data_at(context->destructors, i, NULL) == retain_service) { mlt_properties_set_data(context->destructors, name, retain_service, 0, NULL, NULL); break; } } const char *name = mlt_properties_get(retain_properties, "id"); if (name) mlt_properties_set_data(retain_list, name, retain_service, 0, (mlt_destructor) mlt_service_close, NULL); } retain_service = context_pop_service(context, &type); } if (mlt_properties_count(retain_list) > 0) { mlt_properties_set_data(MLT_SERVICE_PROPERTIES(service), "xml_retain", retain_list, 0, (mlt_destructor) mlt_properties_close, NULL); } else { mlt_properties_close(retain_list); } } static deserialise_context context_new(mlt_profile profile) { deserialise_context context = calloc(1, sizeof(struct deserialise_context_s)); if (context) { context->producer_map = mlt_properties_new(); context->destructors = mlt_properties_new(); context->params = mlt_properties_new(); context->profile = profile; context->seekable = 1; context->stack_service = mlt_deque_init(); context->stack_types = mlt_deque_init(); context->stack_properties = mlt_deque_init(); context->stack_node = mlt_deque_init(); context->stack_branch = mlt_deque_init(); mlt_deque_push_back_int(context->stack_branch, 0); } return context; } static void context_close(deserialise_context context) { mlt_properties_close(context->producer_map); mlt_properties_close(context->destructors); mlt_properties_close(context->params); mlt_deque_close(context->stack_service); mlt_deque_close(context->stack_types); mlt_deque_close(context->stack_properties); mlt_deque_close(context->stack_node); mlt_deque_close(context->stack_branch); xmlFreeDoc(context->entity_doc); free(context->lc_numeric); free(context); } mlt_producer producer_xml_init(mlt_profile profile, mlt_service_type servtype, const char *id, char *data) { xmlSAXHandler *sax, *sax_orig; deserialise_context context; mlt_properties properties = NULL; int i = 0; struct _xmlParserCtxt *xmlcontext; int well_formed = 0; char *filename = NULL; int is_filename = strcmp(id, "xml-string"); // Strip file:// prefix if (data && strlen(data) >= 7 && strncmp(data, "file://", 7) == 0) data += 7; if (data == NULL || !strcmp(data, "")) return NULL; context = context_new(profile); if (context == NULL) return NULL; // Decode URL and parse parameters mlt_properties_set_string(context->producer_map, "root", ""); if (is_filename) { mlt_properties_set_string(context->params, "_mlt_xml_resource", data); filename = mlt_properties_get(context->params, "_mlt_xml_resource"); parse_url(context->params, url_decode(filename, data)); // We need the directory prefix which was used for the xml if (strchr(filename, '/') || strchr(filename, '\\')) { char *root = NULL; mlt_properties_set_string(context->producer_map, "root", filename); root = mlt_properties_get(context->producer_map, "root"); if (strchr(root, '/')) *(strrchr(root, '/')) = '\0'; else if (strchr(root, '\\')) *(strrchr(root, '\\')) = '\0'; // If we don't have an absolute path here, we're heading for disaster... if (root[0] != '/' && !strchr(root, ':')) { char *cwd = getcwd(NULL, 0); char *real = malloc(strlen(cwd) + strlen(root) + 2); sprintf(real, "%s/%s", cwd, root); mlt_properties_set_string(context->producer_map, "root", real); free(real); free(cwd); } } if (!file_exists(filename)) { // Try the un-converted text encoding as a fallback. // Fixes launching melt as child process from Shotcut on Windows // when there are extended characters in the path. filename = mlt_properties_get(context->params, "_mlt_xml_resource"); } if (!file_exists(filename)) { context_close(context); return NULL; } } // We need to track the number of registered filters mlt_properties_set_int(context->destructors, "registered", 0); // Setup SAX callbacks for first pass sax = calloc(1, sizeof(xmlSAXHandler)); sax->startElement = on_start_element; sax->characters = on_characters; sax->warning = on_error; sax->error = on_error; sax->fatalError = on_error; // Setup libxml2 SAX parsing xmlInitParser(); xmlSubstituteEntitiesDefault(1); // This is used to facilitate entity substitution in the SAX parser context->entity_doc = xmlNewDoc(_x("1.0")); if (is_filename) xmlcontext = xmlCreateFileParserCtxt(filename); else xmlcontext = xmlCreateMemoryParserCtxt(data, strlen(data)); // Invalid context - clean up and return NULL if (xmlcontext == NULL) { context_close(context); free(sax); return NULL; } // Parse sax_orig = xmlcontext->sax; xmlcontext->sax = sax; xmlcontext->_private = (void *) context; xmlParseDocument(xmlcontext); well_formed = xmlcontext->wellFormed; // Cleanup after parsing xmlcontext->sax = sax_orig; xmlcontext->_private = NULL; if (xmlcontext->myDoc) xmlFreeDoc(xmlcontext->myDoc); xmlFreeParserCtxt(xmlcontext); // Bad xml - clean up and return NULL if (!well_formed) { context_close(context); free(sax); return NULL; } // Setup the second pass context->pass++; if (is_filename) xmlcontext = xmlCreateFileParserCtxt(filename); else xmlcontext = xmlCreateMemoryParserCtxt(data, strlen(data)); // Invalid context - clean up and return NULL if (xmlcontext == NULL) { context_close(context); free(sax); return NULL; } // Reset the stack. mlt_deque_close(context->stack_service); mlt_deque_close(context->stack_types); mlt_deque_close(context->stack_properties); mlt_deque_close(context->stack_node); context->stack_service = mlt_deque_init(); context->stack_types = mlt_deque_init(); context->stack_properties = mlt_deque_init(); context->stack_node = mlt_deque_init(); // Create the qglsl consumer now, if requested, so that glsl.manager // may exist when trying to load glsl. or movit. services. // The "if requested" part can come from query string qglsl=1 or when // a service beginning with glsl. or movit. appears in the XML. if (mlt_properties_get_int(context->params, "qglsl") && strcmp(id, "xml-nogl") // Only if glslManager does not yet exist. && !mlt_properties_get_data(mlt_global_properties(), "glslManager", NULL)) context->qglsl = mlt_factory_consumer(profile, "qglsl", NULL); // Setup SAX callbacks for second pass sax->endElement = on_end_element; sax->cdataBlock = on_characters; sax->internalSubset = on_internal_subset; sax->entityDecl = on_entity_declaration; sax->getEntity = on_get_entity; // Parse sax_orig = xmlcontext->sax; xmlcontext->sax = sax; xmlcontext->_private = (void *) context; xmlParseDocument(xmlcontext); well_formed = xmlcontext->wellFormed; // Cleanup after parsing xmlFreeDoc(context->entity_doc); context->entity_doc = NULL; free(sax); xmlMemoryDump(); // for debugging xmlcontext->sax = sax_orig; xmlcontext->_private = NULL; if (xmlcontext->myDoc) xmlFreeDoc(xmlcontext->myDoc); xmlFreeParserCtxt(xmlcontext); // Get the last producer on the stack enum service_type type; mlt_service service = context_pop_service(context, &type); if (well_formed && service != NULL) { // Verify it is a producer service (mlt_type="mlt_producer") // (producer, chain, playlist, multitrack) char *type = mlt_properties_get(MLT_SERVICE_PROPERTIES(service), "mlt_type"); if (type == NULL || (strcmp(type, "mlt_producer") != 0 && strcmp(type, "producer") != 0 && strcmp(type, "chain") != 0)) service = NULL; } #ifdef DEBUG xmlDocPtr doc = xml_make_doc(service); xmlDocFormatDump(stdout, doc, 1); xmlFreeDoc(doc); service = NULL; #endif if (well_formed && service != NULL) { char *title = mlt_properties_get(context->producer_map, "title"); // Need the complete producer list for various reasons properties = context->destructors; // Now make sure we don't have a reference to the service in the properties for (i = mlt_properties_count(properties) - 1; i >= 1; i--) { char *name = mlt_properties_get_name(properties, i); if (mlt_properties_get_data_at(properties, i, NULL) == service) { mlt_properties_set_data(properties, name, service, 0, NULL, NULL); break; } } // We are done referencing destructor property list // Set this var to service properties for convenience properties = MLT_SERVICE_PROPERTIES(service); // Assign the title mlt_properties_set_string(properties, "title", title); // Optimise for overlapping producers mlt_producer_optimise(MLT_PRODUCER(service)); // Handle deep copies if (getenv("MLT_XML_DEEP") == NULL) { // Now assign additional properties if (is_filename && (mlt_service_identify(service) == mlt_service_tractor_type || mlt_service_identify(service) == mlt_service_playlist_type || mlt_service_identify(service) == mlt_service_multitrack_type)) { mlt_properties_set_int(properties, "_original_type", mlt_service_identify(service)); mlt_properties_set_string(properties, "_original_resource", mlt_properties_get(properties, "resource")); mlt_properties_set_string(properties, "resource", data); } // This tells consumer_xml not to deep copy mlt_properties_set_string(properties, "xml", "was here"); } else { // Allow the project to be edited mlt_properties_set_string(properties, "_xml", "was here"); mlt_properties_set_int(properties, "_mlt_service_hidden", 1); } // Make consumer available mlt_properties_inc_ref(MLT_CONSUMER_PROPERTIES(context->consumer)); mlt_properties_set_data(properties, "consumer", context->consumer, 0, (mlt_destructor) mlt_consumer_close, NULL); mlt_properties_set_int(properties, "seekable", context->seekable); retain_services(context, service); } else { // Return null if not well formed service = NULL; } // Clean up if (context->qglsl && context->consumer != context->qglsl) mlt_consumer_close(context->qglsl); context_close(context); return MLT_PRODUCER(service); } mlt-7.22.0/src/modules/xml/producer_xml.yml000664 000000 000000 00000002074 14531534050 020646 0ustar00rootroot000000 000000 schema_version: 7.0 type: producer identifier: xml title: XML File version: 1 copyright: Meltytech, LLC creator: Dan Dennedy license: LGPLv2.1 language: en tags: - Audio - Video description: | Construct a service network from an XML description. See docs/mlt-xml.txt. notes: > If there is a service with a property "xml_retain=1" that is not the producer, and if it also has an "id" property; then the extra service is put into a properties list keyed on the id property. Then, that properties list is placed as a property on the returned service with the name "xml_retain". This lets an application retrieve additional deserialized services that are not the lastmost producer or anywhere in its graph. bugs: - > This producer is not thread-safe during its construction because it may modify the mlt_profile, even if is_explicit is set. parameters: - identifier: resource argument: yes title: File type: string description: An XML text file containing MLT XML. readonly: no required: yes mutable: no widget: fileopen mlt-7.22.0/src/swig/000775 000000 000000 00000000000 14531534050 014116 5ustar00rootroot000000 000000 mlt-7.22.0/src/swig/CMakeLists.txt000664 000000 000000 00000001141 14531534050 016653 0ustar00rootroot000000 000000 include(UseSWIG) if(SWIG_CSHARP AND CSHARP_MONO_FOUND) add_subdirectory(csharp) endif() if(SWIG_JAVA AND JNI_FOUND) add_subdirectory(java) endif() if(SWIG_LUA AND LUA_FOUND) add_subdirectory(lua) endif() if(SWIG_NODEJS AND NODEJS_INCLUDE_DIRS) add_subdirectory(nodejs) endif() if(SWIG_PERL AND PERLLIBS_FOUND) add_subdirectory(perl) endif() if(SWIG_PHP AND PHP_FOUND) add_subdirectory(php) endif() if(SWIG_PYTHON AND Python3_FOUND) add_subdirectory(python) endif() if(SWIG_RUBY AND RUBY_FOUND) add_subdirectory(ruby) endif() if(SWIG_TCL AND TCL_FOUND) add_subdirectory(tcl) endif() mlt-7.22.0/src/swig/csharp/000775 000000 000000 00000000000 14531534050 015376 5ustar00rootroot000000 000000 mlt-7.22.0/src/swig/csharp/CMakeLists.txt000664 000000 000000 00000001066 14531534050 020141 0ustar00rootroot000000 000000 set_source_files_properties(../mlt.i PROPERTIES USE_TARGET_INCLUDE_DIRECTORIES ON CPLUSPLUS ON) swig_add_library(mltsharp LANGUAGE csharp OUTPUT_DIR src_swig SOURCES ../mlt.i) target_compile_options(mltsharp PRIVATE ${MLT_COMPILE_OPTIONS}) target_link_libraries(mltsharp PRIVATE mlt mlt++) set_target_properties(mltsharp PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/csharp") add_custom_command(TARGET mltsharp POST_BUILD COMMAND mcs -out:mlt-sharp.dll -target:library src_swig/*.cs WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} ) mlt-7.22.0/src/swig/csharp/build000775 000000 000000 00000001665 14531534050 016433 0ustar00rootroot000000 000000 #!/bin/sh CXX=${CXX:-g++} if [ "$1" = "clean" ] then ( cd `dirname $0`; rm -rf *.cxx *.snk *.so *.o *.exe *.dll mlt.i ../.cs src_swig ) exit 0 fi path=`which mcs 2> /dev/null` if [ $? = 0 ] then ln -sf ../mlt.i # Invoke swig mkdir src_swig swig -c++ -I../../mlt++ -I../.. -csharp -dllimport libmltsharp -outdir src_swig -namespace Mlt mlt.i || exit $? # Compile the wrapper ${CXX} -fPIC -D_GNU_SOURCE ${CXXFLAGS} -c -rdynamic -pthread -I../.. mlt_wrap.cxx || exit $? # Create the module ${CXX} ${CXXFLAGS} -shared mlt_wrap.o -L../../mlt++ -lmlt++ -o libmltsharp.so || exit $? # Compile the library assembly mcs -out:mlt-sharp.dll -target:library src_swig/*.cs # uncomment the below if you want to sign the assembly # sn -k mlt-sharp.snk # mcs -out:mlt-sharp.dll -target:library -keyfile:mlt-sharp.snk src_swig/*.cs # Compile the example mcs -r:mlt-sharp.dll play.cs else echo Mono C# compiler not installed. exit 1 fi mlt-7.22.0/src/swig/csharp/play.cs000664 000000 000000 00000000722 14531534050 016673 0ustar00rootroot000000 000000 using System; using System.Threading; using Mlt; public class Play { public static void Main(String[] args) { Console.WriteLine("Welcome to MLT."); Factory.init(); Profile profile = new Profile(""); Producer p = new Producer(profile, args[0], null); if (p.is_valid()) { Consumer c = new Consumer(profile, "sdl", null); c.set("rescale", "none"); c.connect(p); c.start(); while (!c.is_stopped()) Thread.Sleep(300); c.stop(); } } } mlt-7.22.0/src/swig/csharp/play.sh000775 000000 000000 00000000035 14531534050 016700 0ustar00rootroot000000 000000 #!/bin/sh mono play.exe "$@" mlt-7.22.0/src/swig/java/000775 000000 000000 00000000000 14531534050 015037 5ustar00rootroot000000 000000 mlt-7.22.0/src/swig/java/CMakeLists.txt000664 000000 000000 00000001213 14531534050 017574 0ustar00rootroot000000 000000 set_source_files_properties(../mlt.i PROPERTIES USE_TARGET_INCLUDE_DIRECTORIES ON CPLUSPLUS ON) swig_add_library(mltjava LANGUAGE java OUTPUT_DIR src_swig SOURCES ../mlt.i) target_compile_options(mltjava PRIVATE ${MLT_COMPILE_OPTIONS}) target_link_libraries(mltjava PRIVATE mlt mlt++) target_include_directories(mltjava PRIVATE ${JNI_INCLUDE_DIRS}) set_target_properties(mltjava PROPERTIES OUTPUT_NAME "mlt_java") set_target_properties(mltjava PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/java") add_custom_command(TARGET mltjava POST_BUILD COMMAND javac src_swig/*.java WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} ) mlt-7.22.0/src/swig/java/Play.java000664 000000 000000 00000001736 14531534050 016616 0ustar00rootroot000000 000000 import org.mltframework.*; public class Play { static { System.loadLibrary("mlt_java"); } public static void main (String[] args) { // Start the mlt system Factory.init( null ); // Set the output profile Profile profile = new Profile( "" ); // Create the producer Producer p = new Producer( profile, args[0], null ); if ( p.is_valid() ) { p.set ("eof", "loop"); // Create the consumer Consumer c = new Consumer( profile, "sdl", null); // Turn off the default rescaling c.set("rescale", "none"); // Connect the producer to the consumer c.connect(p); // Start the consumer c.start(); // Wait until the user stops the consumer Object o = new Object(); while ( !c.is_stopped() ) { synchronized (o) { try { o.wait(1000); } catch (InterruptedException e) { // ignored } } } // Stop it anyway c.stop(); } else { System.out.println ("Unable to open " + args[0]); } } } mlt-7.22.0/src/swig/java/Play.sh000775 000000 000000 00000000076 14531534050 016306 0ustar00rootroot000000 000000 #!/bin/sh java -Djava.library.path=. -cp .:src_swig Play "$@" mlt-7.22.0/src/swig/java/build000775 000000 000000 00000001701 14531534050 016063 0ustar00rootroot000000 000000 #!/bin/sh CXX=${CXX:-g++} if [ "$1" = "clean" ] then ( cd `dirname $0`; rm -rf *.cxx *.so *.o mlt.i ../.java *.class src_swig ) exit 0 fi path=`which java 2> /dev/null` if [ $? = 0 ] then # Locate the path for the include path=`dirname $path` path=`dirname $path` # Change this as needed # export JAVA_INCLUDE="-I$path/include -I$path/include/linux" ln -sf ../mlt.i # Invoke swig mkdir -p src_swig/org/mltframework swig -c++ -I../../mlt++ -I../.. -java -outdir src_swig/org/mltframework -package org.mltframework mlt.i || exit $? # Compile the wrapper ${CXX} -fPIC -D_GNU_SOURCE ${CXXFLAGS} -c -rdynamic -pthread -I../.. mlt_wrap.cxx $JAVA_INCLUDE || exit $? # Create the module ${CXX} ${CXXFLAGS} -shared mlt_wrap.o -L../../mlt++ -lmlt++ -o libmlt_java.so || exit $? # Compile the test javac `find src_swig -name '*.java'` || exit $? export CLASSPATH=`pwd`/src_swig javac Play.java else echo "Java command not found" exit 1 fi mlt-7.22.0/src/swig/lua/000775 000000 000000 00000000000 14531534050 014677 5ustar00rootroot000000 000000 mlt-7.22.0/src/swig/lua/CMakeLists.txt000664 000000 000000 00000000751 14531534050 017442 0ustar00rootroot000000 000000 set_source_files_properties(../mlt.i PROPERTIES USE_TARGET_INCLUDE_DIRECTORIES ON CPLUSPLUS ON) swig_add_library(mltlua LANGUAGE lua SOURCES ../mlt.i) target_compile_options(mltlua PRIVATE ${MLT_COMPILE_OPTIONS}) target_link_libraries(mltlua PRIVATE mlt mlt++) target_include_directories(mltlua PRIVATE ${LUA_INCLUDE_DIR}) set_target_properties(mltlua PROPERTIES OUTPUT_NAME "mlt") set_target_properties(mltlua PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/lua") mlt-7.22.0/src/swig/lua/build000775 000000 000000 00000001023 14531534050 015720 0ustar00rootroot000000 000000 #!/bin/sh CXX=${CXX:-g++} if [ "$1" = "clean" ] then ( cd `dirname $0`; rm -f *.cxx *.so *.o mlt.i ../.lua ) exit 0 fi path=`which lua 2> /dev/null` if [ $? = 0 ] then ln -sf ../mlt.i # Invoke swig swig -c++ -I../../mlt++ -I../.. -lua mlt.i || exit $? # Compile the wrapper ${CXX} -fPIC -DPIC -D_GNU_SOURCE ${CXXFLAGS} -c -rdynamic -pthread -I../.. mlt_wrap.cxx || exit $? # Create the module ${CXX} ${CXXFLAGS} -shared mlt_wrap.o -L../../mlt++ -lmlt++ -o mlt.so || exit $? else echo Lua not installed. exit 1 fi mlt-7.22.0/src/swig/lua/play.lua000775 000000 000000 00000000731 14531534050 016353 0ustar00rootroot000000 000000 #!/usr/bin/env lua require("mlt") mlt.Factory_init() profile = mlt.Profile() producer = mlt.Producer( profile, arg[1] ) if producer:is_valid() then consumer = mlt.Consumer( profile, "sdl" ) consumer:set( "rescale", "none" ) consumer:set( "terminate_on_pause", 1 ) consumer:connect( producer ) event = consumer:setup_wait_for( "consumer-stopped" ) consumer:start() consumer:wait_for( event ) else print( "Unable to open "..arg[1] ) end mlt.Factory_close() mlt-7.22.0/src/swig/mlt.i000664 000000 000000 00000014266 14531534050 015075 0ustar00rootroot000000 000000 /** * mlt.i - Swig Bindings for mlt++ * Copyright (C) 2004-2021 Meltytech, LLC * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ %module mlt %include "carrays.i" %array_class(unsigned char, UnsignedCharArray); %{ #include int mlt_log_get_level( void ); void mlt_log_set_level( int ); %} /** These methods return objects which should be gc'd. */ namespace Mlt { %newobject Factory::init( const char * ); %newobject Factory::producer( Profile &, char *, char * ); %newobject Factory::filter( Profile &, char *, char * ); %newobject Factory::transition( Profile &, char *, char * ); %newobject Factory::consumer( Profile &, char *, char * ); %newobject Properties::listen( const char *, void *, mlt_listener ); %newobject Properties::setup_wait_for( const char * ); %newobject Properties::parse_yaml( const char * ); %newobject Service::producer( ); %newobject Service::consumer( ); %newobject Service::get_frame( int ); %newobject Service::filter( int ); %newobject Producer::filter( int ); %newobject Producer::cut( int, int ); %newobject Playlist::current( ); %newobject Playlist::clip_info( int ); %newobject Playlist::get_clip( int ); %newobject Multitrack::track( int ); %newobject Tractor::multitrack( ); %newobject Tractor::field( ); %newobject Tractor::track( int ); %newobject Frame::get_original_producer( ); %newobject Repository::consumers( ); %newobject Repository::filters( ); %newobject Repository::producers( ); %newobject Repository::transitions( ); %newobject Repository::metadata( mlt_service_type, const char * ); %newobject Repository::languages( ); %newobject Profile::list(); %newobject Repository::presets(); %newobject Properties::get_anim(); %newobject Animation::Animation(); %rename(__assign__) Animation::operator=; %rename(__assign__) Frame::operator=; #if defined(SWIGPYTHON) %feature("shadow") Frame::get_waveform(int, int) %{ def get_waveform(*args): return _mlt7.frame_get_waveform(*args) %} %feature("shadow") Frame::get_image(mlt_image_format&, int&, int&) %{ def get_image(*args): return _mlt7.frame_get_image(*args) %} #endif } /** Classes to wrap. */ %include %include %include int mlt_log_get_level( void ); void mlt_log_set_level( int ); %include %include %include %include %include %include %include %include %include %include %include %include %include %include %include %include %include %include #if defined(SWIGRUBY) %{ static void ruby_listener( mlt_properties owner, void *object ); class RubyListener { protected: VALUE callback; Mlt::Event *event; public: RubyListener( VALUE callback ) : callback( callback ) {} RubyListener( Mlt::Properties &properties, char *id, VALUE callback ) : callback( callback ) { event = properties.listen( id, this, ( mlt_listener )ruby_listener ); } virtual ~RubyListener( ) { delete event; } void mark( ) { ((void (*)(VALUE))(rb_gc_mark))( callback ); } void doit( ) { ID method = rb_intern( "call" ); rb_funcall( callback, method, 0 ); } }; static void ruby_listener( mlt_properties owner, void *object ) { RubyListener *o = static_cast< RubyListener * >( object ); o->doit( ); } void markRubyListener( void* p ) { RubyListener *o = static_cast( p ); o->mark( ); } static void on_playlist_next( mlt_properties owner, void *object, mlt_event_data ); class PlaylistNextListener : RubyListener { private: Mlt::Event *event; public: PlaylistNextListener( Mlt::Properties *properties, VALUE callback ) : RubyListener( callback ) { event = properties->listen( "playlist-next", this, ( mlt_listener )on_playlist_next ); } ~PlaylistNextListener() { delete event; } void yield(const Mlt::EventData& eventData) { ID method = rb_intern( "call" ); rb_funcall( callback, method, 1, INT2FIX( eventData.to_int() ) ); } }; static void on_playlist_next( mlt_properties owner, void *object, mlt_event_data event_data ) { PlaylistNextListener *o = static_cast< PlaylistNextListener * >( object ); Mlt::EventData data(event_data); o->yield(data); } %} // Ruby wrapper %rename( Listener ) RubyListener; %markfunc RubyListener "markRubyListener"; %markfunc PlaylistNextListener "markRubyListener"; class RubyListener { public: RubyListener( Mlt::Properties &properties, char *id, VALUE callback ); }; class PlaylistNextListener { public: PlaylistNextListener( Mlt::Properties *properties, VALUE proc ); }; #endif // SWIGGRUBY #if defined(SWIGPYTHON) %{ typedef struct { int size; char* data; } binary_data; binary_data frame_get_waveform( Mlt::Frame &frame, int w, int h ) { binary_data result = { w * h, (char*) frame.get_waveform( w, h ) }; return result; } binary_data frame_get_image( Mlt::Frame &frame, mlt_image_format format, int w, int h ) { binary_data result = { mlt_image_format_size( format, w, h, NULL ), (char*) frame.get_image( format, w, h ) }; return result; } %} %typemap(out) binary_data { $result = %#if PY_MAJOR_VERSION < 3 PyString_FromStringAndSize( %#else PyByteArray_FromStringAndSize( %#endif $1.data, $1.size ); } binary_data frame_get_waveform(Mlt::Frame&, int, int); binary_data frame_get_image(Mlt::Frame&, mlt_image_format, int, int); #endif mlt-7.22.0/src/swig/nodejs/000775 000000 000000 00000000000 14531534050 015400 5ustar00rootroot000000 000000 mlt-7.22.0/src/swig/nodejs/CMakeLists.txt000664 000000 000000 00000001052 14531534050 020136 0ustar00rootroot000000 000000 set_source_files_properties(../mlt.i PROPERTIES USE_TARGET_INCLUDE_DIRECTORIES ON CPLUSPLUS ON) swig_add_library(mltnodejs LANGUAGE javascript SOURCES ../mlt.i) target_compile_options(mltnodejs PRIVATE ${MLT_COMPILE_OPTIONS}) target_link_libraries(mltnodejs PRIVATE mlt mlt++) target_include_directories(mltnodejs PRIVATE ${NODEJS_INCLUDE_DIRS}) set_target_properties(mltnodejs PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/nodejs") set_target_properties(mltnodejs PROPERTIES OUTPUT_NAME "mlt" SWIG_COMPILE_OPTIONS -node ) mlt-7.22.0/src/swig/nodejs/apply-filter.js000664 000000 000000 00000002003 14531534050 020341 0ustar00rootroot000000 000000 #!/usr/bin/env node const path = require('path') const mlt = require('./node-gyp-build/Release/mlt.node') const { Factory, Profile, Producer, Filter, Consumer } = mlt; Factory.init('') let profile = new Profile('hdv_720_25p') let producer = new Producer(profile, 'avformat', process.argv[2]) if (!producer.is_valid()) { console.log(`Unable to open producer`) process.exit(1) } producer.set_in_and_out(100, 200) let videoOutputFilePath = path.join(__dirname, 'output.mp4') console.log('output:', videoOutputFilePath) let consumer = new Consumer(profile, 'avformat', videoOutputFilePath) consumer.set('rescale', 'none') consumer.set('vcodec', 'libx264') consumer.set('acodec', 'aac') let filter = new Filter(profile, 'charcoal') producer.attach(filter) consumer.connect(producer) consumer.start() check() function check() { process.stdout.write('.') if (consumer.is_stopped()) { consumer.stop() Factory.close() return } setTimeout(check, 1000) } mlt-7.22.0/src/swig/nodejs/binding.gyp000664 000000 000000 00000000305 14531534050 017531 0ustar00rootroot000000 000000 { "targets": [{ "target_name": "mlt", "sources": [ "mlt_wrap.cxx" ], "include_dirs": [ "../.." ], "libraries": [ "-L../../../mlt++ -lmlt++" ] }] } mlt-7.22.0/src/swig/nodejs/build000775 000000 000000 00000000726 14531534050 016432 0ustar00rootroot000000 000000 #!/bin/sh CXX=${CXX:-g++} gypBuildDir="node-gyp-build" if [ "$1" = "clean" ] then ( cd `dirname $0`; rm -f *.cxx *.so *.o mlt.i ../.nodejs; rm -r "$gypBuildDir" ) exit 0 fi which node-gyp 2> /dev/null if [ $? = 0 ] then ln -sf ../mlt.i # Invoke swig swig -c++ -I../../mlt++ -I../.. -javascript -node mlt.i || exit $? # Compile the wrapper NODE_GYP_BUILD_DIR="$gypBuildDir" node-gyp configure build || exit $? else echo node-gyp not installed. exit 1 fi mlt-7.22.0/src/swig/nodejs/list-members.js000664 000000 000000 00000001155 14531534050 020343 0ustar00rootroot000000 000000 #!/usr/bin/env node const mlt = require('./node-gyp-build/Release/mlt.node') const members = Object.keys(mlt) const maxKeyLength = members.reduce((max, key) => key.length > max ? key.length : max, 0) const output = members.map(member => [member, mlt[member]]) .map(([key, val]) => `${padRight(key, maxKeyLength)}\t${isFunction(val) ? '[function]' : val}`) .join('\n') console.log(output) function padRight(str, len, char = ' ') { while (str.length < len) { str += char } return str; } function isFunction(val) { return typeof val === 'function' } function entries(obj) { return } mlt-7.22.0/src/swig/nodejs/play.js000664 000000 000000 00000001442 14531534050 016704 0ustar00rootroot000000 000000 #!/usr/bin/env node const path = require('path') const mlt = require('./node-gyp-build/Release/mlt.node') const { Factory, Profile, Producer, Consumer } = mlt; const videoFile = process.argv[2] console.log(videoFile) Factory.init('') let profile = new Profile('hdv_720_25p') let producer = new Producer(profile, 'avformat', videoFile) if (!producer.is_valid()) { console.log(`Unable to open producer`) process.exit(1) } console.log('get_fps', producer.get_fps()) let consumer = new Consumer(profile, 'sdl') consumer.connect(producer) consumer.start() check() function check() { console.log('is_stopped', consumer.is_stopped()) if (consumer.is_stopped()) { consumer.stop() Factory.close() return } setTimeout(check, 1000) } mlt-7.22.0/src/swig/perl/000775 000000 000000 00000000000 14531534050 015060 5ustar00rootroot000000 000000 mlt-7.22.0/src/swig/perl/CMakeLists.txt000664 000000 000000 00000001336 14531534050 017623 0ustar00rootroot000000 000000 set_source_files_properties(../mlt.i PROPERTIES USE_TARGET_INCLUDE_DIRECTORIES ON CPLUSPLUS ON) swig_add_library(mltperl LANGUAGE perl SOURCES ../mlt.i) target_compile_options(mltperl PRIVATE ${MLT_COMPILE_OPTIONS}) target_link_libraries(mltperl PRIVATE mlt mlt++) target_include_directories(mltperl PRIVATE ${PERL_INCLUDE_PATH}) set_target_properties(mltperl PROPERTIES OUTPUT_NAME "mlt") set_target_properties(mltperl PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/perl") string(REGEX MATCH "lib.*" PERL_MODULE_INSTALL_DIR ${PERL_VENDORARCH}) install(TARGETS mltperl DESTINATION ${PERL_MODULE_INSTALL_DIR}/auto/mlt) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/mlt.pm DESTINATION ${PERL_MODULE_INSTALL_DIR}) mlt-7.22.0/src/swig/perl/Makefile.PL000664 000000 000000 00000000645 14531534050 017037 0ustar00rootroot000000 000000 #!/bin/env perl use ExtUtils::MakeMaker; my $CXX = $ENV{'CXX'} || 'g++'; system( "ln -sf ../mlt.i" ); system( "swig -c++ -I../../mlt++ -I../.. -perl5 mlt.i" ); WriteMakefile( 'NAME' => 'mlt', 'CC' => '${CXX} -fPIC ${CXXFLAGS} -I../..', 'OPTIMIZE' => '-O2 -g -pipe -Wp,-D_FORTIFY_SOURCE=2 -fexceptions', 'LIBS' => ['-L../../mlt++ -lmlt++'], 'OBJECT' => 'mlt_wrap.o', 'DESTDIR' => $ENV{'DESTDIR'}, ); mlt-7.22.0/src/swig/perl/build000775 000000 000000 00000000250 14531534050 016102 0ustar00rootroot000000 000000 #!/bin/sh if [ "$1" = "clean" ] then ( cd `dirname $0`; rm -f *.cxx *.so *.o mlt.i ../.perl mlt.pm ) exit 0 fi CXXFLAGS="$CXXFLAGS" perl Makefile.PL || exit 1 make mlt-7.22.0/src/swig/perl/play.pl000775 000000 000000 00000001457 14531534050 016374 0ustar00rootroot000000 000000 #!/usr/bin/env perl # Import required modules use mlt; # Not sure why the mlt::Factory.init method fails... mlt::mlt_factory_init( undef ); # Establish the MLT profile $profile = new mlt::Profile( undef ); # Create the producer $p = new mlt::Producer( $profile, $ARGV[0] ); if ( $p->is_valid( ) ) { # Loop the video $p->set( "eof", "loop" ); # Create the consumer $c = new mlt::FilteredConsumer( $profile, "sdl" ); # Turn of the default rescaling $c->set( "rescale", "none" ); # Connect the producer to the consumer $c->connect( $p ); $e = $c->setup_wait_for( "consumer-stopped" ); # Start the consumer $c->start; # Wait until the user stops the consumer $c->wait_for( $e ); $e = undef; $c = undef; $p = undef; } else { print "Unable to open $ARGV[0]\n"; } mlt::mlt_factory_close( ); mlt-7.22.0/src/swig/php/000775 000000 000000 00000000000 14531534050 014705 5ustar00rootroot000000 000000 mlt-7.22.0/src/swig/php/CMakeLists.txt000664 000000 000000 00000001320 14531534050 017441 0ustar00rootroot000000 000000 set_source_files_properties(../mlt.i PROPERTIES USE_TARGET_INCLUDE_DIRECTORIES ON CPLUSPLUS ON) swig_add_library(mltphp LANGUAGE php SOURCES ../mlt.i) target_compile_options(mltphp PRIVATE ${MLT_COMPILE_OPTIONS}) target_link_libraries(mltphp PRIVATE mlt mlt++ PHP::Extension) set_target_properties(mltphp PROPERTIES OUTPUT_NAME "mlt") set_target_properties(mltphp PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/php") if(PHP_EXTENSION_DIR) string(REGEX MATCH "lib.*" PHP_EXTENSION_INSTALL_DIR ${PHP_EXTENSION_DIR}) install(TARGETS mltphp DESTINATION ${PHP_EXTENSION_INSTALL_DIR}) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/mlt.php DESTINATION ${PHP_EXTENSION_INSTALL_DIR} OPTIONAL) endif() mlt-7.22.0/src/swig/php/build000775 000000 000000 00000000617 14531534050 015736 0ustar00rootroot000000 000000 #!/bin/sh CXX=${CXX:-g++} if [ "$1" = "clean" ] then ( cd `dirname $0`; rm -f *.cpp *.so *.o mlt.i ../.php mlt.php *.h ) exit 0 fi ln -sf ../mlt.i swig -c++ -I../../mlt++ -I../.. -php5 -noproxy mlt.i ${CXX} -fPIC -DPIC -D_GNU_SOURCE ${CXXFLAGS} -c -rdynamic -pthread -I../.. `php-config --includes` mlt_wrap.cpp ${CXX} ${CXXFLAGS} -shared mlt_wrap.o -L../../mlt++ -lmlt++ -o mlt.so || exit $? mlt-7.22.0/src/swig/php/play.php000775 000000 000000 00000000666 14531534050 016376 0ustar00rootroot000000 000000 mlt-7.22.0/src/swig/python/000775 000000 000000 00000000000 14531534050 015437 5ustar00rootroot000000 000000 mlt-7.22.0/src/swig/python/CMakeLists.txt000664 000000 000000 00000001404 14531534050 020176 0ustar00rootroot000000 000000 set_source_files_properties(../mlt.i PROPERTIES USE_TARGET_INCLUDE_DIRECTORIES ON CPLUSPLUS ON) swig_add_library(mltpython LANGUAGE python SOURCES ../mlt.i) target_compile_options(mltpython PRIVATE ${MLT_COMPILE_OPTIONS}) target_link_libraries(mltpython PRIVATE mlt mlt++ Python3::Module) set_target_properties(mltpython PROPERTIES PREFIX "_" OUTPUT_NAME "mlt${MLT_VERSION_MAJOR}") set_target_properties(mltpython PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/python") string(REGEX MATCH "[lL]ib.*" PYTHON_MODULE_INSTALL_DIR ${Python3_SITEARCH}) install(TARGETS mltpython DESTINATION ${PYTHON_MODULE_INSTALL_DIR}) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/mlt.py DESTINATION ${PYTHON_MODULE_INSTALL_DIR} RENAME mlt${MLT_VERSION_MAJOR}.py ) mlt-7.22.0/src/swig/python/build000775 000000 000000 00000001421 14531534050 016462 0ustar00rootroot000000 000000 #!/bin/sh CXX=${CXX:-g++} PYTHON=${PYTHON:-python3} path=`which "$PYTHON" 2> /dev/null` if [ "$1" = "clean" ] then ( cd `dirname $0`; rm -f *.cxx *.so *.o mlt.i ../.python mlt.py* ) exit 0 fi if [ $? = 0 ] then # Change this as needed export PYTHON_INCLUDE="$("${PYTHON}-config" --includes)" [ -z "$PYTHON_INCLUDE" ] && echo "$PYTHON" development missing && exit 1 ln -sf ../mlt.i # Invoke swig swig -c++ -I../../mlt++ -I../.. -python mlt.i || exit $? # Compile the wrapper ${CXX} -fPIC -D_GNU_SOURCE ${CXXFLAGS} -c -I../.. $PYTHON_INCLUDE mlt_wrap.cxx || exit $? # Create the module ${CXX} ${CXXFLAGS} -shared mlt_wrap.o -L../../mlt++ -lmlt++ -L../../framework -lmlt $("${PYTHON}-config" --ldflags) -o _mlt.so || exit $? else echo Python not installed. exit 1 fi mlt-7.22.0/src/swig/python/codecs.py000775 000000 000000 00000001003 14531534050 017246 0ustar00rootroot000000 000000 #!/usr/bin/env python3 # -*- coding: utf-8 -*- # Import required modules from __future__ import print_function import mlt # Start the mlt system mlt.Factory().init( ) # Create the consumer c = mlt.Consumer( mlt.Profile(), "avformat" ) # Ask for video codecs supports c.set( 'vcodec', 'list' ) # Start the consumer to generate the list c.start() # Get the vcodec property codecs = mlt.Properties( c.get_data( 'vcodec' ) ) # Print the list of codecs for i in range( 0, codecs.count()): print(codecs.get( i )) mlt-7.22.0/src/swig/python/getimage.py000775 000000 000000 00000001516 14531534050 017601 0ustar00rootroot000000 000000 #!/usr/bin/env python3 # -*- coding: utf-8 -*- import mlt import sys from PIL import Image # setup mlt.Factory.init() profile = mlt.Profile('square_pal_wide') prod = mlt.Producer(profile, sys.argv[1]) # This builds a profile from the attributes of the producer: auto-profile. profile.from_producer(prod) # Ensure the image is square pixels - optional. profile.set_width(int(profile.width() * profile.sar())) profile.set_sample_aspect(1, 0) # Seek to 10% and get a Mlt frame. prod.seek(int(prod.get_length() * 0.1)) frame = prod.get_frame() # Make sure we deinterlace if input is interlaced. frame.set("consumer.progressive", 1) # Now we are ready to get the image and save it. size = (profile.width(), profile.height()) rgb = frame.get_image(mlt.mlt_image_rgb, *size) img = Image.fromstring('RGB', size, rgb) img.save(sys.argv[1] + '.png') mlt-7.22.0/src/swig/python/play.py000775 000000 000000 00000001251 14531534050 016760 0ustar00rootroot000000 000000 #!/usr/bin/env python3 # -*- coding: utf-8 -*- # Import required modules from __future__ import print_function import mlt import time import sys # Start the mlt system mlt.Factory().init( ) # Establish a profile profile = mlt.Profile( ) # Create the producer p = mlt.Producer( profile, sys.argv[1] ) if p: # Create the consumer c = mlt.Consumer( profile, "sdl" ) # Turn off the default rescaling c.set( "rescale", "none" ) # Connect the producer to the consumer c.connect( p ) # Start the consumer c.start( ) # Wait until the user stops the consumer while c.is_stopped( ) == 0: time.sleep( 1 ) else: # Diagnostics print("Unable to open ", sys.argv[ 1 ]) mlt-7.22.0/src/swig/python/switcher.py000775 000000 000000 00000003474 14531534050 017654 0ustar00rootroot000000 000000 #!/usr/bin/env python3 # -*- coding: utf-8 -*- # Import required modules import mlt import time import sys import tornado.ioloop import tornado.web # Start the mlt system mlt.mlt_log_set_level(40) # verbose mlt.Factory.init() # Establish a pipeline profile = mlt.Profile("atsc_1080i_5994") profile.set_explicit(1) tractor = mlt.Tractor() tractor.set("eof", "loop") fg_resource = "decklink:0" bg_resource = "decklink:1" if len(sys.argv) > 2: fg_resource = sys.argv[1] bg_resource = sys.argv[2] fg = mlt.Producer(profile, fg_resource) bg = mlt.Producer(profile, bg_resource) tractor.set_track(bg, 0) tractor.set_track(fg, 1) composite = mlt.Transition(profile, "composite") composite.set("fill", 1) tractor.plant_transition(composite) # Setup the consumer consumer = "decklink:2" if len(sys.argv) > 3: consumer = sys.argv[3] consumer = mlt.Consumer(profile, consumer) consumer.connect(tractor) consumer.set("real_time", -2) consumer.start() flip_flop = False def switch(): global composite, flip_flop frame = fg.frame() + 2 if flip_flop: s = "0=20%%/0:100%%x80%%; %d=20%%/0:100%%x80%%; %d=0/0:100%%x100%%" % (frame, frame + 30) composite.set("geometry", s) else: s = "0=0/0:100%%x100%%; %d=0/0:100%%x100%%; %d=20%%/0:100%%x80%%" % (frame, frame + 30) composite.set("geometry", s) flip_flop = not flip_flop def output_form(handler): handler.write('
') class SwitchHandler(tornado.web.RequestHandler): def get(self): switch() class MainHandler(tornado.web.RequestHandler): def get(self): output_form(self) def post(self): switch() output_form(self) application = tornado.web.Application([ (r"/", MainHandler), (r"/switch", SwitchHandler) ]) application.listen(8888) tornado.ioloop.IOLoop.instance().start() mlt-7.22.0/src/swig/python/test_animation.py000775 000000 000000 00000000461 14531534050 021033 0ustar00rootroot000000 000000 #!/usr/bin/env python3 # -*- coding: utf-8 -*- from __future__ import print_function import mlt p = mlt.Properties() p.anim_set("foo", "bar", 10) a = p.get_anim("bar") print("bar is valid:", a.is_valid()) a = p.get_anim("foo") print("foo is valid:", a.is_valid()) print("serialize:", a.serialize_cut()) mlt-7.22.0/src/swig/python/waveforms.py000775 000000 000000 00000000554 14531534050 020031 0ustar00rootroot000000 000000 #!/usr/bin/env python3 # -*- coding: utf-8 -*- import mlt from PIL import Image mlt.Factory.init() profile = mlt.Profile() prod = mlt.Producer(profile, 'test.wav') size = (320, 240) for i in range(0, prod.get_length()): frm = prod.get_frame() wav = frm.get_waveform(size[0], size[1]) img = Image.fromstring('L', size, wav) img.save('test-%04d.pgm' % (i)) mlt-7.22.0/src/swig/python/webvfx_generator.py000775 000000 000000 00000006660 14531534050 021373 0ustar00rootroot000000 000000 #!/usr/bin/env python3 # -*- coding: utf-8 -*- # webvfx_generator.py # Copyright (C) 2013 Dan Dennedy # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software Foundation, # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # Import required modules import mlt import time import sys import tornado.ioloop import tornado.web import shutil import tempfile import os import os.path # Start the mlt system mlt.mlt_log_set_level(40) # verbose mlt.Factory.init() # Establish a pipeline profile = mlt.Profile("atsc_1080i_5994") #profile = mlt.Profile('square_ntsc_wide') profile.set_explicit(1) tractor = mlt.Tractor() tractor.set('eof', 'loop') playlist = mlt.Playlist() playlist.append(mlt.Producer(profile, 'color:')) # Setup the consumer consumer = 'decklink:0' if len(sys.argv) > 1: consumer = sys.argv[1] consumer = mlt.Consumer(profile, consumer) consumer.connect(playlist) #consumer.set("real_time", -2) consumer.start() def switch(resource): global playlist resource = resource playlist.lock() playlist.append(mlt.Producer(profile, str(resource))) playlist.remove(0) playlist.unlock() state = {} state['tempdir'] = None class MainHandler(tornado.web.RequestHandler): def get(self): resource = self.get_argument('url', None) if resource: global state self.write('Playing %s\n' % (resource)) switch(resource) olddir = state['tempdir'] if olddir: shutil.rmtree(olddir, True) state['tempdir'] = None else: self.write('''

POST a bunch of files to / to change the output.

Or GET / with query string parameter "url" to display something from the network.

''') def post(self): if len(self.request.files) == 0: self.write('POST a bunch of files to / to change the output') else: global state olddir = state['tempdir'] resource = None state['tempdir'] = tempfile.mkdtemp() for key, items in self.request.files.iteritems(): for item in items: path = os.path.dirname(key) fn = item.filename if path: if not os.path.exists(os.path.join(state['tempdir'], path)): os.makedirs(os.path.join(state['tempdir'], path)) fn = os.path.join(path, fn) fn = os.path.join(state['tempdir'], fn) if not path and fn.endswith('.html') or fn.endswith('.qml'): resource = fn with open(fn, 'w') as fo: fo.write(item.body) self.write("Uploaded %s\n" % (fn)) if resource: self.write('Playing %s\n' % (resource)) switch(resource) if olddir: shutil.rmtree(olddir, True) application = tornado.web.Application([ (r"/", MainHandler), ]) application.listen(8888) try: tornado.ioloop.IOLoop.instance().start() except: pass consumer.stop() if state['tempdir']: shutil.rmtree(state['tempdir'], True) mlt-7.22.0/src/swig/ruby/000775 000000 000000 00000000000 14531534050 015077 5ustar00rootroot000000 000000 mlt-7.22.0/src/swig/ruby/CMakeLists.txt000664 000000 000000 00000001076 14531534050 017643 0ustar00rootroot000000 000000 set_source_files_properties(../mlt.i PROPERTIES USE_TARGET_INCLUDE_DIRECTORIES ON CPLUSPLUS ON) swig_add_library(mltruby LANGUAGE ruby SOURCES ../mlt.i) target_compile_options(mltruby PRIVATE ${MLT_COMPILE_OPTIONS}) target_link_libraries(mltruby PRIVATE mlt++) target_include_directories(mltruby PRIVATE ${RUBY_INCLUDE_DIRS}) set_target_properties(mltruby PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/ruby") set_target_properties(mltruby PROPERTIES OUTPUT_NAME "mlt") install(TARGETS mltruby DESTINATION ${CMAKE_INSTALL_LIBDIR}/ruby/vendor_ruby) mlt-7.22.0/src/swig/ruby/build000775 000000 000000 00000000754 14531534050 016132 0ustar00rootroot000000 000000 #!/usr/bin/env ruby require 'mkmf' if ARGV.shift == 'clean' system( "rm -f *.cxx *.so *.o mlt.i ../.ruby Makefile" ) exit 0 end system( "ln -sf ../mlt.i" ) system( "swig -c++ -ruby -I../../mlt++ -I../.. mlt.i" ) $CFLAGS = $CFLAGS.to_s + " -I../.. " + (ENV.has_key?('CXXFLAGS')? ENV['CXXFLAGS'] : '') $CXXFLAGS = $CXXFLAGS.to_s + " -I../.. " + (ENV.has_key?('CXXFLAGS')? ENV['CXXFLAGS'] : '') $LDFLAGS = $LDFLAGS.to_s + " -L../../mlt++ -lmlt++" create_makefile('mlt') system( "make V=1" ) mlt-7.22.0/src/swig/ruby/metadata.rb000775 000000 000000 00000006761 14531534050 017221 0ustar00rootroot000000 000000 #!/usr/bin/env ruby require 'erb' require 'yaml' require './mlt' $folder = 'markdown' $repo = Mlt::Factory::init $optional_params = [ 'minimum', 'maximum', 'default', 'unit', 'scale', 'format', 'widget' ] template = %q{--- layout: standard title: Documentation wrap_title: "<%= type_title %>: <%= yml['identifier'] %>" category: plugin --- * TOC {:toc} ## Plugin Information title: <%= yml['title'] %> % if yml['tags'] media types: % yml['tags'].each do |x| <%= x + " " %> % end <%= "\n" %> % end description: <%= ERB::Util.h(yml['description']) %> version: <%= yml['version'] %> creator: <%= yml['creator'] %> % yml['contributor'] and yml['contributor'].each do |x| contributor: <%= x %> % end <%= "copyright: #{yml['copyright']} \n" if yml['copyright'] %> <%= "license: #{yml['license']} \n" if yml['license'] %> <%= "URL: [#{yml['url']}](#{yml['url']}) \n" if yml['url'] %> % if yml['notes'] ## Notes <%= ERB::Util.h(yml['notes']) %> % end % if yml['bugs'] ## Bugs % yml['bugs'].each do |x| * <%= x %> % end % end % if yml['parameters'] ## Parameters % yml['parameters'].each do |param| ### <%= param['identifier'] %> <%= "title: #{param['title']} " if param['title'] %> % if param['description'] description: % if param['description'].include? "\n"
<%= param['description'] %>
% else <%= "#{ERB::Util.h(param['description'])} \n" %> % end % end type: <%= param['type'] %> readonly: <%= param['readonly'] and 'yes' or 'no' %> required: <%= param['required'] and 'yes' or 'no' %> <%= "animation: yes \n" if param['animation'] %> % $optional_params.each do |key| <%= "#{key}: #{param[key].to_s.gsub('](', ']\(')} \n" if param[key] %> % end % if param['values'] values: % param['values'].each do |value| * <%= value %> % end % end % end % end } $processor = ERB.new(template, 0, "%<>") def output(mlt_type, services, type_title) filename = File.join($folder, "Plugins#{type_title}s.md") index = File.open(filename, 'w') index.puts '---' index.puts 'title: Documentation' index.puts "wrap_title: #{type_title} Plugins" index.puts '---' unsorted = [] (0..(services.count - 1)).each do |i| unsorted << services.get_name(i) end unsorted.sort().each do |name| meta = $repo.metadata(mlt_type, name) if meta.is_valid if !meta.get_data('parameters') puts "No parameters for #{name} #{type_title}" end filename = File.join($folder, type_title + name.capitalize.gsub('.', '-')) # puts "Processing #{filename}" begin yml = YAML.load(meta.serialise_yaml) if yml File.open(filename + '.md', 'w') do |f| f.puts $processor.result(binding) end else puts "Failed to write file for #{filename}" end filename = type_title + name.capitalize.gsub('.', '-') index.puts "* [#{name}](../#{filename}/): #{meta.get('title')}\n" rescue SyntaxError puts "Failed to parse YAML for #{filename}" end else puts "No metadata for #{name} #{type_title}" end end index.close end Dir.mkdir($folder) if not Dir.exists?($folder) [ [Mlt::Mlt_service_consumer_type, $repo.consumers, 'Consumer'], [Mlt::Mlt_service_filter_type, $repo.filters, 'Filter'], [Mlt::Mlt_service_link_type, $repo.links, 'Link'], [Mlt::Mlt_service_producer_type, $repo.producers, 'Producer'], [Mlt::Mlt_service_transition_type, $repo.transitions, 'Transition'] ].each {|x| output *x} mlt-7.22.0/src/swig/ruby/play.rb000775 000000 000000 00000001545 14531534050 016401 0ustar00rootroot000000 000000 #!/usr/bin/env ruby # Import required modules require './mlt' # Create the mlt system Mlt::Factory::init # Establish the mlt profile profile = Mlt::Profile.new # Get and check the argument file = ARGV.shift raise "Usage: test.rb file" if file.nil? # Create the producer producer = Mlt::Factory::producer( profile, file ) raise "Unable to load #{file}" if !producer.is_valid # Create the consumer consumer = Mlt::Consumer.new( profile, "sdl" ) raise "Unable to open sdl consumer" if !consumer.is_valid # Turn off the default rescaling consumer.set( "rescale", "none" ) # Set up a 'wait for' event event = consumer.setup_wait_for( "consumer-stopped" ) # Start the consumer consumer.start # Connect the producer to the consumer consumer.connect( producer ) # Wait until the user stops the consumer consumer.wait_for( event ) # Clean up consumer consumer.stop mlt-7.22.0/src/swig/ruby/playlist.rb000775 000000 000000 00000002526 14531534050 017275 0ustar00rootroot000000 000000 #!/usr/bin/env ruby # Import required modules require './mlt' # Create the mlt system Mlt::Factory::init # Establish the mlt profile profile = Mlt::Profile.new # Get and check the argument file = ARGV.shift raise "Usage: test.rb file1 file2" if file.nil? file2 = ARGV.shift raise "Usage: test.rb file1 file2" if file2.nil? pls = Mlt::Playlist.new(profile) # Create the producer producer = Mlt::Factory::producer( profile, file ) raise "Unable to load #{file}" if !producer.is_valid producer2 = Mlt::Factory::producer( profile, file2 ) raise "Unable to load #{file2}" if !producer2.is_valid pls.append(producer) #pls.repeat(0, 2) pls.append(producer2) # Create the consumer consumer = Mlt::Consumer.new( profile, "sdl2" ) raise "Unable to open sdl consumer" if !consumer.is_valid # Turn off the default rescaling consumer.set( "rescale", "none" ) consumer.set("volume", 0.1) consumer.set("terminate_on_pause", 1) Mlt::PlaylistNextListener.new(pls, Proc.new { |i| info = pls.clip_info(i) puts "finished playing #{info.resource}\n" }) # Set up a 'wait for' event event = consumer.setup_wait_for( "consumer-stopped" ) # Start the consumer consumer.start # Connect the producer to the consumer consumer.connect( pls ) # Wait until the user stops the consumer consumer.wait_for( event ) puts "Done and closing" # Clean up consumer consumer.stop mlt-7.22.0/src/swig/ruby/thumbs.rb000775 000000 000000 00000001741 14531534050 016734 0ustar00rootroot000000 000000 #!/usr/bin/env ruby # Required modules require './mlt' # Create the mlt system Mlt::Factory::init # Establish the mlt profile profile = Mlt::Profile.new( "quarter_pal" ) # Get and check the argument file = ARGV.shift name = ARGV.shift size = ARGV.shift size = "176x144" if size.nil? raise "Usage: thumbs.rb file name [ size ]" if file.nil? || name.nil? # Create the producer producer = Mlt::Producer.new( profile, file ) raise "Unable to load #{file}" if !producer.is_valid # Construct the playlist playlist = Mlt::Playlist.new( ) # Get the out point out = producer.get_int( "out" ); # Calculate position of frames [ 0, 0.25, 0.5, 0.75, 1 ].each { |x| playlist.append( producer, Integer(x*out), Integer(x*out) ) } # Create the thumb nail generator generator = Mlt::Consumer.new( profile, "avformat", "#{name}%d.jpg" ) generator.set( "real_time", "0" ) generator.set( "progressive", "1" ) generator.set( "s", size ) # Connect the consumer generator.connect( playlist ); generator.run mlt-7.22.0/src/swig/tcl/000775 000000 000000 00000000000 14531534050 014700 5ustar00rootroot000000 000000 mlt-7.22.0/src/swig/tcl/CMakeLists.txt000664 000000 000000 00000000752 14531534050 017444 0ustar00rootroot000000 000000 set_source_files_properties(../mlt.i PROPERTIES USE_TARGET_INCLUDE_DIRECTORIES ON CPLUSPLUS ON) swig_add_library(mlttcl LANGUAGE tcl SOURCES ../mlt.i) target_compile_options(mlttcl PRIVATE ${MLT_COMPILE_OPTIONS}) target_link_libraries(mlttcl PRIVATE mlt mlt++) target_include_directories(mlttcl PRIVATE ${TCL_INCLUDE_PATH}) set_target_properties(mlttcl PROPERTIES OUTPUT_NAME "mlt") set_target_properties(mlttcl PROPERTIES LIBRARY_OUTPUT_DIRECTORY "${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/tcl") mlt-7.22.0/src/swig/tcl/build000775 000000 000000 00000001031 14531534050 015720 0ustar00rootroot000000 000000 #!/bin/sh CXX=${CXX:-g++} if [ "$1" = "clean" ] then ( cd `dirname $0`; rm -f *.cxx *.so *.o mlt.i ../.tcl ) exit 0 fi path=`which tclsh 2>/dev/null` if [ "$path" != "" ] then ln -sf ../mlt.i # Invoke swig swig -c++ -I../../mlt++ -I../.. -tcl mlt.i || exit 1 # Compile the wrapper ${CXX} -fPIC -D_GNU_SOURCE ${CXXFLAGS} -c -rdynamic -pthread -I../.. mlt_wrap.cxx || exit 1 # Create the module ${CXX} ${CXXFLAGS} -shared mlt_wrap.o -L../../mlt++ -lmlt++ -o mlt.so || exit 1 else echo "Unable to locate tclsh." exit 1 fi mlt-7.22.0/src/swig/tcl/play.tcl000775 000000 000000 00000000553 14531534050 016357 0ustar00rootroot000000 000000 #!/usr/bin/env tclsh load mlt.so Factory_init set profile [Profile] set arg1 [lindex $argv 0] set p [Factory_producer $profile loader $arg1] set c [Factory_consumer $profile sdl ""] Properties_set $c "rescale" "none" Consumer_connect $c $p Consumer_start $c while { ![Consumer_is_stopped $c] } { after 1000 } delete_Consumer $c delete_Producer $p Factory_close mlt-7.22.0/src/tests/000775 000000 000000 00000000000 14531534050 014307 5ustar00rootroot000000 000000 mlt-7.22.0/src/tests/CMakeLists.txt000664 000000 000000 00000002066 14531534050 017053 0ustar00rootroot000000 000000 set(CMAKE_AUTOMOC ON) foreach(QT_TEST_NAME animation audio events filter frame image playlist producer properties repository service tractor xml) add_executable(test_${QT_TEST_NAME} test_${QT_TEST_NAME}/test_${QT_TEST_NAME}.cpp) target_compile_options(test_${QT_TEST_NAME} PRIVATE ${MLT_COMPILE_OPTIONS}) target_link_libraries(test_${QT_TEST_NAME} PRIVATE Qt${QT_MAJOR_VERSION}::Core Qt${QT_MAJOR_VERSION}::Test mlt++) add_test(NAME "QtTest:${QT_TEST_NAME}" COMMAND test_${QT_TEST_NAME}) if(NOT WIN32) set_tests_properties("QtTest:${QT_TEST_NAME}" PROPERTIES ENVIRONMENT "LANG=en_US") endif() endforeach() file(GLOB YML_FILES "${CMAKE_SOURCE_DIR}/src/modules/*/*.yml") foreach(YML_FILE ${YML_FILES}) get_filename_component(FILE_NAME ${YML_FILE} NAME) file(RELATIVE_PATH KWALIFY_TEST_NAME "${CMAKE_SOURCE_DIR}/src/modules" ${YML_FILE}) if(NOT FILE_NAME MATCHES "resolution_scale.yml") add_test(NAME "kwalify:${KWALIFY_TEST_NAME}" COMMAND ${Kwalify_EXECUTABLE} -f "${CMAKE_SOURCE_DIR}/src/framework/metaschema.yaml" ${YML_FILE}) endif() endforeach() mlt-7.22.0/src/tests/clock16ntsc.pgm000664 000000 000000 00002506021 14531534050 017154 0ustar00rootroot000000 000000 P5 720 480 65535 ××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááááááááââââââââââââââââââââăăăăăăăăăăăăăăăăăăăăääääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććććććççççççççççççççççççččččččččččččččččééééééééééééééééęęęęęęęęęęęęęęęęëëëëëëëëëëëëëëěěěěěěěěěěěěěěěěííííííííííííííîîîîîîîîîîîîîîďďďďďďďďďďďďďďđđđđđđđđđđđđđđńńńńńńńńńńńńňňňňňňňňňňňňňňóóóóóóóóóóóóôôôôôôôôôôôôôôőőőőőőőőőőőőöööööööööööö÷÷÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřřřůůůůůůůůůůůůúúúúúúúúúúúúűűűűűűűűűűűűüüüüüüüüüüüüýýýýýýýýýýýýţţţţţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!!!!!!!""""""""""""""""""""""""""############################$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''''''''''''((((××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááááááááââââââââââââââââââââăăăăăăăăăăăăăăăăăăăăääääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććććććççççççççççççççççččččččččččččččččččééééééééééééééęęęęęęęęęęęęęęęęëëëëëëëëëëëëëëëëěěěěěěěěěěěěěěííííííííííííííîîîîîîîîîîîîîîďďďďďďďďďďďďďďđđđđđđđđđđđđđđńńńńńńńńńńńńńńňňňňňňňňňňňňóóóóóóóóóóóóôôôôôôôôôôôôôôőőőőőőőőőőőőöööööööööööö÷÷÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřřřůůůůůůůůůůůůúúúúúúúúúúúúűűűűűűűűűűűűüüüüüüüüüüüüýýýýýýýýýýýýţţţţţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!!!!!!!""""""""""""""""""""""""""############################$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''''''''''((((((((××××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááááááââââââââââââââââââââââăăăăăăăăăăăăăăăăăăääääääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććććççççççççççççççççççččččččččččččččččééééééééééééééééęęęęęęęęęęęęęęęęëëëëëëëëëëëëëëěěěěěěěěěěěěěěěěííííííííííííííîîîîîîîîîîîîîîďďďďďďďďďďďďďďđđđđđđđđđđđđńńńńńńńńńńńńńńňňňňňňňňňňňňóóóóóóóóóóóóóóôôôôôôôôôôôôőőőőőőőőőőőőöööööööööööö÷÷÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřřřřřůůůůůůůůůůůůúúúúúúúúúúűűűűűűűűűűűűüüüüüüüüüüüüýýýýýýýýýýýýţţţţţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!!!!!!!""""""""""""""""""""""""""############################$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''''''''''((((((((((××××××××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááááááââââââââââââââââââââăăăăăăăăăăăăăăăăăăăăääääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććććććççççççççççççççççççččččččččččččččččééééééééééééééééęęęęęęęęęęęęęęëëëëëëëëëëëëëëëëěěěěěěěěěěěěěěííííííííííííííîîîîîîîîîîîîîîďďďďďďďďďďďďďďđđđđđđđđđđđđđđńńńńńńńńńńńńňňňňňňňňňňňňňňóóóóóóóóóóóóôôôôôôôôôôôôőőőőőőőőőőőőöööööööööööööö÷÷÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřřřůůůůůůůůůůůůúúúúúúúúúúúúűűűűűűűűűűüüüüüüüüüüüüýýýýýýýýýýýýţţţţţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!!!!!""""""""""""""""""""""""""""############################$$$$$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''''''''''((((((((((((((××××××××××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááááááââââââââââââââââââââăăăăăăăăăăăăăăăăăăăăääääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććććććççççççççççççççççččččččččččččččččééééééééééééééééęęęęęęęęęęęęęęęęëëëëëëëëëëëëëëěěěěěěěěěěěěěěěěííííííííííííííîîîîîîîîîîîîîîďďďďďďďďďďďďđđđđđđđđđđđđđđńńńńńńńńńńńńńńňňňňňňňňňňňňóóóóóóóóóóóóôôôôôôôôôôôôôôőőőőőőőőőőőőöööööööööööö÷÷÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřřřůůůůůůůůůůůůúúúúúúúúúúúúűűűűűűűűűűűűüüüüüüüüüüýýýýýýýýýýýýţţţţţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!!!!!""""""""""""""""""""""""""############################$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''''''''''((((((((((((((((ÖÖ××××××××××××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááááááââââââââââââââââââââăăăăăăăăăăăăăăăăăăăăääääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććććććççççççççççççççççččččččččččččččččééééééééééééééééęęęęęęęęęęęęęęëëëëëëëëëëëëëëëëěěěěěěěěěěěěěěííííííííííííííîîîîîîîîîîîîîîďďďďďďďďďďďďďďđđđđđđđđđđđđńńńńńńńńńńńńńńňňňňňňňňňňňňóóóóóóóóóóóóóóôôôôôôôôôôôôőőőőőőőőőőőőöööööööööööö÷÷÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřřřůůůůůůůůůůůůúúúúúúúúúúúúűűűűűűűűűűűűüüüüüüüüüüýýýýýýýýýýýýţţţţţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!!!!!""""""""""""""""""""""""""############################$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''''''''''((((((((((((((((((((ÖÖÖÖ××××××××××××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááááááááââââââââââââââââââââăăăăăăăăăăăăăăăăăăăăääääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććććççççççççççççççççççččččččččččččččččééééééééééééééęęęęęęęęęęęęęęęęëëëëëëëëëëëëëëěěěěěěěěěěěěěěěěííííííííííííííîîîîîîîîîîîîďďďďďďďďďďďďďďđđđđđđđđđđđđđđńńńńńńńńńńńńňňňňňňňňňňňňňňóóóóóóóóóóóóôôôôôôôôôôôôőőőőőőőőőőőőöööööööööööö÷÷÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřřřůůůůůůůůůůůůúúúúúúúúúúúúűűűűűűűűűűűűüüüüüüüüüüýýýýýýýýýýýýţţţţţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!!!!!""""""""""""""""""""""""""############################$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''''''''''((((((((((((((((((((((ÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááááááááââââââââââââââââââââăăăăăăăăăăăăăăăăăăääääääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććććććççççççççççççççççččččččččččččččččééééééééééééééééęęęęęęęęęęęęęęëëëëëëëëëëëëëëëëěěěěěěěěěěěěěěííííííííííííííîîîîîîîîîîîîîîďďďďďďďďďďďďďďđđđđđđđđđđđđńńńńńńńńńńńńńńňňňňňňňňňňňňóóóóóóóóóóóóôôôôôôôôôôôôőőőőőőőőőőőőöööööööööööö÷÷÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřřřůůůůůůůůůůůůúúúúúúúúúúúúűűűűűűűűűűűűüüüüüüüüüüüüýýýýýýýýýýţţţţţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!!!!!""""""""""""""""""""""""""############################$$$$$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''''''''((((((((((((((((((((((((((ÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááááááááââââââââââââââââââââăăăăăăăăăăăăăăăăăăääääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććććććççççççççççççççççččččččččččččččččééééééééééééééééęęęęęęęęęęęęęęëëëëëëëëëëëëëëěěěěěěěěěěěěěěííííííííííííííîîîîîîîîîîîîîîďďďďďďďďďďďďďďđđđđđđđđđđđđńńńńńńńńńńńńńńňňňňňňňňňňňňóóóóóóóóóóóóôôôôôôôôôôôôôôőőőőőőőőőőőőöööööööööööö÷÷÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřůůůůůůůůůůůůúúúúúúúúúúúúűűűűűűűűűűűűüüüüüüüüüüüüýýýýýýýýýýţţţţţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!!!!!""""""""""""""""""""""""""############################$$$$$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''''''''''((((((((((((((((((((((((((((ÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááááááááââââââââââââââââââăăăăăăăăăăăăăăăăăăăăääääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććććççççççççççççççççččččččččččččččččééééééééééééééééęęęęęęęęęęęęęęęęëëëëëëëëëëëëëëěěěěěěěěěěěěěěííííííííííííííîîîîîîîîîîîîîîďďďďďďďďďďďďđđđđđđđđđđđđđđńńńńńńńńńńńńňňňňňňňňňňňňňňóóóóóóóóóóóóôôôôôôôôôôôôőőőőőőőőőőőőöööööööööööö÷÷÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřřřůůůůůůůůůůúúúúúúúúúúúúűűűűűűűűűűűűüüüüüüüüüüüüýýýýýýýýýýţţţţţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!!!!!""""""""""""""""""""""""""##########################$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''''''''((((((((((((((((((((((((((((((((ÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááááááââââââââââââââââââââăăăăăăăăăăăăăăăăăăăăääääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććććççççççççççççççççččččččččččččččččééééééééééééééééęęęęęęęęęęęęęęëëëëëëëëëëëëëëěěěěěěěěěěěěěěííííííííííííííîîîîîîîîîîîîîîďďďďďďďďďďďďďďđđđđđđđđđđđđńńńńńńńńńńńńńńňňňňňňňňňňňňóóóóóóóóóóóóôôôôôôôôôôôôőőőőőőőőőőőőöööööööööööö÷÷÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřřřůůůůůůůůůůůůúúúúúúúúúúűűűűűűűűűűűűüüüüüüüüüüüüýýýýýýýýýýţţţţţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!!!!!""""""""""""""""""""""""""##########################$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''''''''''((((((((((((((((((((((((((((((((((ÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááááááââââââââââââââââââââăăăăăăăăăăăăăăăăăăääääääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććććććççççççççççççççççččččččččččččččččééééééééééééééęęęęęęęęęęęęęęęęëëëëëëëëëëëëëëěěěěěěěěěěěěěěííííííííííííííîîîîîîîîîîîîîîďďďďďďďďďďďďđđđđđđđđđđđđđđńńńńńńńńńńńńňňňňňňňňňňňňóóóóóóóóóóóóôôôôôôôôôôôôőőőőőőőőőőőőöööööööööööö÷÷÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřřřůůůůůůůůůůůůúúúúúúúúúúűűűűűűűűűűűűüüüüüüüüüüüüýýýýýýýýýýţţţţţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!!!!!""""""""""""""""""""""""############################$$$$$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''''''''((((((((((((((((((((((((((((((((((((((ÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááááááââââââââââââââââââââăăăăăăăăăăăăăăăăăăääääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććććççççççççççççççççččččččččččččččččééééééééééééééééęęęęęęęęęęęęęęëëëëëëëëëëëëëëěěěěěěěěěěěěěěííííííííííííííîîîîîîîîîîîîîîďďďďďďďďďďďďďďđđđđđđđđđđđđńńńńńńńńńńńńňňňňňňňňňňňňóóóóóóóóóóóóóóôôôôôôôôôôôôőőőőőőőőőőőőöööööööööö÷÷÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřřřůůůůůůůůůůůůúúúúúúúúúúúúűűűűűűűűűűüüüüüüüüüüüüýýýýýýýýýýţţţţţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!!!!!!!""""""""""""""""""""""""############################$$$$$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''''''''(((((((((((((((((((((((((((((((((((((())ÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááááááââââââââââââââââââââăăăăăăăăăăăăăăăăăăääääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććććççççççççççççççççččččččččččččččččééééééééééééééęęęęęęęęęęęęęęęęëëëëëëëëëëëëëëěěěěěěěěěěěěěěííííííííííííííîîîîîîîîîîîîďďďďďďďďďďďďďďđđđđđđđđđđđđńńńńńńńńńńńńńńňňňňňňňňňňňňóóóóóóóóóóóóôôôôôôôôôôôôőőőőőőőőőőőőöööööööööööö÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřřřůůůůůůůůůůůůúúúúúúúúúúúúűűűűűűűűűűüüüüüüüüüüüüýýýýýýýýýýţţţţţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!!!!!""""""""""""""""""""""""""############################$$$$$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''''''''(((((((((((((((((((((((((((((((((((((())))))ÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááááááââââââââââââââââââââăăăăăăăăăăăăăăăăăăääääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććććććççççççççççççççççččččččččččččččééééééééééééééééęęęęęęęęęęęęęęëëëëëëëëëëëëëëěěěěěěěěěěěěěěííííííííííííííîîîîîîîîîîîîîîďďďďďďďďďďďďđđđđđđđđđđđđđđńńńńńńńńńńńńňňňňňňňňňňňňóóóóóóóóóóóóôôôôôôôôôôôôőőőőőőőőőőőőöööööööööööö÷÷÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřůůůůůůůůůůůůúúúúúúúúúúúúűűűűűűűűűűüüüüüüüüüüüüýýýýýýýýýýýýţţţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!!!!!""""""""""""""""""""""""""##########################$$$$$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''''''''(((((((((((((((((((((((((((((((((((((())))))))ÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááááááââââââââââââââââââăăăăăăăăăăăăăăăăăăăăääääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććććççççççççççççççççččččččččččččččččééééééééééééééééęęęęęęęęęęęęęęëëëëëëëëëëëëëëěěěěěěěěěěěěěěííííííííííííííîîîîîîîîîîîîďďďďďďďďďďďďďďđđđđđđđđđđđđńńńńńńńńńńńńňňňňňňňňňňňňóóóóóóóóóóóóôôôôôôôôôôôôőőőőőőőőőőőőöööööööööööö÷÷÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřřřůůůůůůůůůůúúúúúúúúúúúúűűűűűűűűűűüüüüüüüüüüüüýýýýýýýýýýýýţţţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!!!!!""""""""""""""""""""""""""##########################$$$$$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''''''''(((((((((((((((((((((((((((((((((((((())))))))))))ÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááááááââââââââââââââââââăăăăăăăăăăăăăăăăăăääääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććććççççççççççççççççččččččččččččččččééééééééééééééęęęęęęęęęęęęęęëëëëëëëëëëëëëëěěěěěěěěěěěěěěííííííííííííííîîîîîîîîîîîîîîďďďďďďďďďďďďđđđđđđđđđđđđńńńńńńńńńńńńńńňňňňňňňňňňňňóóóóóóóóóóóóôôôôôôôôôôôôőőőőőőőőőőőőöööööööööö÷÷÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřřřůůůůůůůůůůúúúúúúúúúúúúűűűűűűűűűűüüüüüüüüüüüüýýýýýýýýýýýýţţţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!!!!!""""""""""""""""""""""""""##########################$$$$$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''''''''(((((((((((((((((((((((((((((((((((())))))))))))))))ÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááááááââââââââââââââââââăăăăăăăăăăăăăăăăăăääääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććććççççççççççççççççččččččččččččččééééééééééééééééęęęęęęęęęęęęęęëëëëëëëëëëëëëëěěěěěěěěěěěěěěííííííííííííííîîîîîîîîîîîîďďďďďďďďďďďďđđđđđđđđđđđđđđńńńńńńńńńńńńňňňňňňňňňňňňóóóóóóóóóóóóôôôôôôôôôôôôőőőőőőőőőőőőöööööööööööö÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřřřůůůůůůůůůůúúúúúúúúúúúúűűűűűűűűűűűűüüüüüüüüüüýýýýýýýýýýýýţţţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!!!!!""""""""""""""""""""""""""##########################$$$$$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''''''''(((((((((((((((((((((((((((((((((((((())))))))))))))))))ŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááááââââââââââââââââââââăăăăăăăăăăăăăăăăăăääääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććććççççççççççççççççččččččččččččččččééééééééééééééęęęęęęęęęęęęęęëëëëëëëëëëëëëëěěěěěěěěěěěěěěííííííííííííííîîîîîîîîîîîîîîďďďďďďďďďďďďđđđđđđđđđđđđńńńńńńńńńńńńňňňňňňňňňňňňóóóóóóóóóóóóôôôôôôôôôôôôőőőőőőőőőőőőöööööööööööö÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřřřůůůůůůůůůůůůúúúúúúúúúúűűűűűűűűűűűűüüüüüüüüüüýýýýýýýýýýýýţţţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!!!!!""""""""""""""""""""""""##########################$$$$$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''''''''(((((((((((((((((((((((((((((((((((())))))))))))))))))))))ŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááááááââââââââââââââââââââăăăăăăăăăăăăăăăăăăääääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććććççççççççççççççççččččččččččččččččééééééééééééééęęęęęęęęęęęęęęëëëëëëëëëëëëëëěěěěěěěěěěěěěěííííííííííííîîîîîîîîîîîîîîďďďďďďďďďďďďđđđđđđđđđđđđđđńńńńńńńńńńńńňňňňňňňňňňňňóóóóóóóóóóóóôôôôôôôôôôőőőőőőőőőőőőöööööööööööö÷÷÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřůůůůůůůůůůůůúúúúúúúúúúűűűűűűűűűűűűüüüüüüüüüüýýýýýýýýýýýýţţţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!!!!!""""""""""""""""""""""""##########################$$$$$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''''''''(((((((((((((((((((((((((((((((((((())))))))))))))))))))))))ŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááááááââââââââââââââââââââăăăăăăăăăăăăăăăăăăääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććććççççççççççççççççččččččččččččččééééééééééééééęęęęęęęęęęęęęęęęëëëëëëëëëëëëëëěěěěěěěěěěěěííííííííííííííîîîîîîîîîîîîďďďďďďďďďďďďďďđđđđđđđđđđđđńńńńńńńńńńńńňňňňňňňňňňňňóóóóóóóóóóóóôôôôôôôôôôôôőőőőőőőőőőöööööööööööö÷÷÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřůůůůůůůůůůůůúúúúúúúúúúűűűűűűűűűűűűüüüüüüüüüüýýýýýýýýýýýýţţţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!!!!!""""""""""""""""""""""""##########################$$$$$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''''''''(((((((((((((((((((((((((((((((((((())))))))))))))))))))))))))))ŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááááááââââââââââââââââââăăăăăăăăăăăăăăăăăăääääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććććççççççççççççççççččččččččččččččččééééééééééééééęęęęęęęęęęęęęęëëëëëëëëëëëëëëěěěěěěěěěěěěěěííííííííííííîîîîîîîîîîîîîîďďďďďďďďďďďďđđđđđđđđđđđđńńńńńńńńńńńńňňňňňňňňňňňňóóóóóóóóóóóóôôôôôôôôôôôôőőőőőőőőőőőőöööööööööö÷÷÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřůůůůůůůůůůůůúúúúúúúúúúűűűűűűűűűűűűüüüüüüüüüüýýýýýýýýýýýýţţţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!!!!!""""""""""""""""""""""""##########################$$$$$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''''''''(((((((((((((((((((((((((((((((((((())))))))))))))))))))))))))))))ŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááááááââââââââââââââââââăăăăăăăăăăăăăăăăăăääääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććććççççççççççççççççččččččččččččččééééééééééééééééęęęęęęęęęęęęęęëëëëëëëëëëëëëëěěěěěěěěěěěěííííííííííííííîîîîîîîîîîîîďďďďďďďďďďďďđđđđđđđđđđđđđđńńńńńńńńńńńńňňňňňňňňňňňňóóóóóóóóóóôôôôôôôôôôôôőőőőőőőőőőőőöööööööööööö÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřřřůůůůůůůůůůúúúúúúúúúúúúűűűűűűűűűűüüüüüüüüüüýýýýýýýýýýýýţţţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!!!!!""""""""""""""""""""""""##########################$$$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''''''''(((((((((((((((((((((((((((((((((((())))))))))))))))))))))))))))))))))ŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááááááââââââââââââââââââăăăăăăăăăăăăăăăăăăääääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććććççççççççççççççččččččččččččččččééééééééééééééęęęęęęęęęęęęęęëëëëëëëëëëëëëëěěěěěěěěěěěěěěííííííííííííîîîîîîîîîîîîîîďďďďďďďďďďďďđđđđđđđđđđđđńńńńńńńńńńńńňňňňňňňňňňňňóóóóóóóóóóóóôôôôôôôôôôőőőőőőőőőőőőöööööööööööö÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřřřůůůůůůůůůůúúúúúúúúúúúúűűűűűűűűűűüüüüüüüüüüýýýýýýýýýýýýţţţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!!!""""""""""""""""""""""""""########################$$$$$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''''''''(((((((((((((((((((((((((((((((((())))))))))))))))))))))))))))))))))))))ŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááááááââââââââââââââââââăăăăăăăăăăăăăăăăăăääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććççççççççççççççççččččččččččččččččééééééééééééééęęęęęęęęęęęęęęëëëëëëëëëëëëëëěěěěěěěěěěěěííííííííííííííîîîîîîîîîîîîďďďďďďďďďďďďđđđđđđđđđđđđńńńńńńńńńńńńňňňňňňňňňňňňóóóóóóóóóóóóôôôôôôôôôôôôőőőőőőőőőőöööööööööööö÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřřřůůůůůůůůůůúúúúúúúúúúúúűűűűűűűűűűüüüüüüüüüüýýýýýýýýýýýýţţţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!!!""""""""""""""""""""""""##########################$$$$$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''''''(((((((((((((((((((((((((((((((((((())))))))))))))))))))))))))))))))))))))**ŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááááááââââââââââââââââââăăăăăăăăăăăăăăăăăăääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććććççççççççççççççççččččččččččččččééééééééééééééęęęęęęęęęęęęęęëëëëëëëëëëëëëëěěěěěěěěěěěěěěííííííííííííîîîîîîîîîîîîďďďďďďďďďďďďďďđđđđđđđđđđđđńńńńńńńńńńńńňňňňňňňňňňňňóóóóóóóóóóôôôôôôôôôôôôőőőőőőőőőőőőöööööööööö÷÷÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřůůůůůůůůůůůůúúúúúúúúúúűűűűűűűűűűüüüüüüüüüüüüýýýýýýýýýýţţţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!!!""""""""""""""""""""""""##########################$$$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''''''''(((((((((((((((((((((((((((((((((())))))))))))))))))))))))))))))))))))))******ŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááááââââââââââââââââââââăăăăăăăăăăăăăăăăääääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććććççççççççççççççččččččččččččččččééééééééééééééęęęęęęęęęęęęęęëëëëëëëëëëëëëëěěěěěěěěěěěěííííííííííííííîîîîîîîîîîîîďďďďďďďďďďďďđđđđđđđđđđđđńńńńńńńńńńńńňňňňňňňňňňňňóóóóóóóóóóóóôôôôôôôôôôőőőőőőőőőőőőöööööööööö÷÷÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřůůůůůůůůůůůůúúúúúúúúúúűűűűűűűűűűüüüüüüüüüüüüýýýýýýýýýýţţţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!!!""""""""""""""""""""""""##########################$$$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''''''(((((((((((((((((((((((((((((((((((())))))))))))))))))))))))))))))))))))))********ŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááááââââââââââââââââââăăăăăăăăăăăăăăăăăăääääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććććççççççççççççççččččččččččččččééééééééééééééęęęęęęęęęęęęęęëëëëëëëëëëëëëëěěěěěěěěěěěěěěííííííííííííîîîîîîîîîîîîďďďďďďďďďďďďđđđđđđđđđđđđńńńńńńńńńńńńňňňňňňňňňňňňóóóóóóóóóóóóôôôôôôôôôôôôőőőőőőőőőőöööööööööööö÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřůůůůůůůůůůůůúúúúúúúúúúűűűűűűűűűűüüüüüüüüüüüüýýýýýýýýýýţţţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!!!""""""""""""""""""""""""##########################$$$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''''''''(((((((((((((((((((((((((((((((((())))))))))))))))))))))))))))))))))))))************ŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááááââââââââââââââââââăăăăăăăăăăăăăăăăăăääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććććççççççççççççççççččččččččččččččééééééééééééééęęęęęęęęęęęęęęëëëëëëëëëëëëëëěěěěěěěěěěěěííííííííííííííîîîîîîîîîîîîďďďďďďďďďďďďđđđđđđđđđđđđńńńńńńńńńńńńňňňňňňňňňňóóóóóóóóóóóóôôôôôôôôôôôôőőőőőőőőőőöööööööööööö÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřřřůůůůůůůůůůúúúúúúúúúúűűűűűűűűűűüüüüüüüüüüüüýýýýýýýýýýţţţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!!!""""""""""""""""""""""""########################$$$$$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''''''(((((((((((((((((((((((((((((((((((())))))))))))))))))))))))))))))))))))****************ŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááááââââââââââââââââââăăăăăăăăăăăăăăăăăăääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććććççççççççççççççččččččččččččččččééééééééééééééęęęęęęęęęęęęëëëëëëëëëëëëëëěěěěěěěěěěěěěěííííííííííííîîîîîîîîîîîîďďďďďďďďďďďďđđđđđđđđđđđđńńńńńńńńńńńńňňňňňňňňňňňňóóóóóóóóóóôôôôôôôôôôôôőőőőőőőőőőőőöööööööööö÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřřřůůůůůůůůůůúúúúúúúúúúűűűűűűűűűűűűüüüüüüüüüüýýýýýýýýýýţţţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!!!""""""""""""""""""""""""########################$$$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''''''''(((((((((((((((((((((((((((((((((())))))))))))))))))))))))))))))))))))))******************ÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááááââââââââââââââââââăăăăăăăăăăăăăăăăăăääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććććççççççççççççççččččččččččččččééééééééééééééęęęęęęęęęęęęęęëëëëëëëëëëëëëëěěěěěěěěěěěěííííííííííííîîîîîîîîîîîîîîďďďďďďďďďďďďđđđđđđđđđđđđńńńńńńńńńńňňňňňňňňňňňňóóóóóóóóóóóóôôôôôôôôôôőőőőőőőőőőőőöööööööööö÷÷÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřůůůůůůůůůůúúúúúúúúúúűűűűűűűűűűűűüüüüüüüüüüýýýýýýýýýýţţţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!!!""""""""""""""""""""""""########################$$$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''''''(((((((((((((((((((((((((((((((((())))))))))))))))))))))))))))))))))))))**********************ÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááááââââââââââââââââââăăăăăăăăăăăăăăăăääääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććççççççççççççççççččččččččččččččééééééééééééééęęęęęęęęęęęęęęëëëëëëëëëëëëěěěěěěěěěěěěěěííííííííííííîîîîîîîîîîîîďďďďďďďďďďďďđđđđđđđđđđđđńńńńńńńńńńńńňňňňňňňňňňóóóóóóóóóóóóôôôôôôôôôôôôőőőőőőőőőőöööööööööö÷÷÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřůůůůůůůůůůúúúúúúúúúúúúűűűűűűűűűűüüüüüüüüüüýýýýýýýýýýţţţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!!!""""""""""""""""""""""##########################$$$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''''''(((((((((((((((((((((((((((((((((())))))))))))))))))))))))))))))))))))**************************ÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááááááââââââââââââââââââăăăăăăăăăăăăăăăăääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććććççççççççççççççččččččččččččččééééééééééééééęęęęęęęęęęęęęęëëëëëëëëëëëëëëěěěěěěěěěěěěííííííííííííîîîîîîîîîîîîďďďďďďďďďďďďđđđđđđđđđđđđńńńńńńńńńńńńňňňňňňňňňňňňóóóóóóóóóóôôôôôôôôôôôôőőőőőőőőőőöööööööööööö÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřůůůůůůůůůůúúúúúúúúúúúúűűűűűűűűűűüüüüüüüüüüýýýýýýýýýýţţţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!!!""""""""""""""""""""""########################$$$$$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''''''(((((((((((((((((((((((((((((((((())))))))))))))))))))))))))))))))))))))****************************ÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááááááââââââââââââââââăăăăăăăăăăăăăăăăăăääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććććççççççççççççççččččččččččččččééééééééééééééęęęęęęęęęęęęęęëëëëëëëëëëëëěěěěěěěěěěěěěěííííííííííííîîîîîîîîîîîîďďďďďďďďďďďďđđđđđđđđđđđđńńńńńńńńńńňňňňňňňňňňňňóóóóóóóóóóóóôôôôôôôôôôőőőőőőőőőőöööööööööööö÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřůůůůůůůůůůůůúúúúúúúúúúűűűűűűűűűűüüüüüüüüüüýýýýýýýýýýţţţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!!!""""""""""""""""""""""########################$$$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''''''(((((((((((((((((((((((((((((((((())))))))))))))))))))))))))))))))))))********************************ÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááááááââââââââââââââââăăăăăăăăăăăăăăăăăăääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććççççççççççççççççččččččččččččččééééééééééééééęęęęęęęęęęęęëëëëëëëëëëëëëëěěěěěěěěěěěěííííííííííííîîîîîîîîîîîîďďďďďďďďďďďďđđđđđđđđđđđđńńńńńńńńńńńńňňňňňňňňňňóóóóóóóóóóóóôôôôôôôôôôőőőőőőőőőőőőöööööööööö÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřůůůůůůůůůůůůúúúúúúúúúúűűűűűűűűűűüüüüüüüüüüýýýýýýýýýýţţţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!""""""""""""""""""""""""########################$$$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''''''(((((((((((((((((((((((((((((((((())))))))))))))))))))))))))))))))))))************************************ÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááááââââââââââââââââââăăăăăăăăăăăăăăăăăăääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććććççççççççççççççččččččččččččččééééééééééééééęęęęęęęęęęęęęęëëëëëëëëëëëëěěěěěěěěěěěěěěííííííííííííîîîîîîîîîîîîďďďďďďďďďďďďđđđđđđđđđđńńńńńńńńńńńńňňňňňňňňňňňňóóóóóóóóóóôôôôôôôôôôôôőőőőőőőőőőöööööööööö÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřřřůůůůůůůůůůúúúúúúúúúúűűűűűűűűűűüüüüüüüüüüýýýýýýýýýýţţţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!!!""""""""""""""""""""""""########################$$$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''''''(((((((((((((((((((((((((((((((((())))))))))))))))))))))))))))))))))))**************************************ÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááááââââââââââââââââââăăăăăăăăăăăăăăăăääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććććççççççççççççççččččččččččččččééééééééééééééęęęęęęęęęęęęëëëëëëëëëëëëëëěěěěěěěěěěěěííííííííííííîîîîîîîîîîîîďďďďďďďďďďďďđđđđđđđđđđđđńńńńńńńńńńňňňňňňňňňňňňóóóóóóóóóóôôôôôôôôôôôôőőőőőőőőőőöööööööööö÷÷÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřůůůůůůůůůůúúúúúúúúúúűűűűűűűűűűüüüüüüüüüüýýýýýýýýýýţţţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!!!""""""""""""""""""""""""########################$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''''(((((((((((((((((((((((((((((((((())))))))))))))))))))))))))))))))))))****************************************++ÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááááââââââââââââââââââăăăăăăăăăăăăăăăăääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććççççççççççççççççččččččččččččééééééééééééééęęęęęęęęęęęęęęëëëëëëëëëëëëěěěěěěěěěěěěííííííííííííííîîîîîîîîîîîîďďďďďďďďďďđđđđđđđđđđđđńńńńńńńńńńńńňňňňňňňňňňóóóóóóóóóóóóôôôôôôôôôôőőőőőőőőőőöööööööööööö÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřůůůůůůůůůůúúúúúúúúúúűűűűűűűűűűüüüüüüüüüüýýýýýýýýýýţţţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!!!""""""""""""""""""""""########################$$$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''''''(((((((((((((((((((((((((((((((())))))))))))))))))))))))))))))))))))****************************************++++++ÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááááââââââââââââââââââăăăăăăăăăăăăăăăăääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććççççççççççççççččččččččččččččééééééééééééééęęęęęęęęęęęęëëëëëëëëëëëëëëěěěěěěěěěěěěííííííííííííîîîîîîîîîîîîďďďďďďďďďďďďđđđđđđđđđđńńńńńńńńńńńńňňňňňňňňňňňňóóóóóóóóóóôôôôôôôôôôőőőőőőőőőőőőöööööööööö÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřůůůůůůůůůůúúúúúúúúúúűűűűűűűűűűüüüüüüüüüüýýýýýýýýýýţţţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!!!""""""""""""""""""""""########################$$$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''''(((((((((((((((((((((((((((((((((())))))))))))))))))))))))))))))))))))**************************************++++++++++ÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááááââââââââââââââââââăăăăăăăăăăăăăăăăääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććććççççççççççççççččččččččččččččééééééééééééęęęęęęęęęęęęęęëëëëëëëëëëëëěěěěěěěěěěěěííííííííííííîîîîîîîîîîîîďďďďďďďďďďďďđđđđđđđđđđđđńńńńńńńńńńňňňňňňňňňňňňóóóóóóóóóóôôôôôôôôôôôôőőőőőőőőőőöööööööööö÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřůůůůůůůůůůúúúúúúúúúúűűűűűűűűűűüüüüüüüüüüýýýýýýýýýýţţţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!!!""""""""""""""""""""""########################$$$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''''''(((((((((((((((((((((((((((((((())))))))))))))))))))))))))))))))))))****************************************++++++++++++ÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááááââââââââââââââââăăăăăăăăăăăăăăăăăăääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććççççççççççççççččččččččččččččééééééééééééééęęęęęęęęęęęęëëëëëëëëëëëëëëěěěěěěěěěěěěííííííííííííîîîîîîîîîîîîďďďďďďďďďďďďđđđđđđđđđđńńńńńńńńńńńńňňňňňňňňňňóóóóóóóóóóóóôôôôôôôôôôőőőőőőőőőőöööööööööö÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřůůůůůůůůůůúúúúúúúúúúűűűűűűűűűűüüüüüüüüüüýýýýýýýýýýţţţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!!!""""""""""""""""""""""########################$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''''(((((((((((((((((((((((((((((((((())))))))))))))))))))))))))))))))))****************************************++++++++++++++++ÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááááââââââââââââââââăăăăăăăăăăăăăăăăääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććççççççççççççççččččččččččččččééééééééééééééęęęęęęęęęęęęëëëëëëëëëëëëěěěěěěěěěěěěííííííííííííîîîîîîîîîîîîďďďďďďďďďďďďđđđđđđđđđđđđńńńńńńńńńńňňňňňňňňňňňňóóóóóóóóóóôôôôôôôôôôőőőőőőőőőőöööööööööö÷÷÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřůůůůůůůůůůúúúúúúúúúúűűűűűűűűűűüüüüüüüüýýýýýýýýýýţţţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!!!""""""""""""""""""""""########################$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''''(((((((((((((((((((((((((((((((())))))))))))))))))))))))))))))))))))**************************************++++++++++++++++++++ÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááááââââââââââââââââăăăăăăăăăăăăăăăăääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććććççççççççççççççččččččččččččččééééééééééééęęęęęęęęęęęęęęëëëëëëëëëëëëěěěěěěěěěěěěííííííííííííîîîîîîîîîîîîďďďďďďďďďďđđđđđđđđđđđđńńńńńńńńńńňňňňňňňňňňňňóóóóóóóóóóôôôôôôôôôôőőőőőőőőőőőőöööööööööö÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřůůůůůůůůůůúúúúúúúúúúűűűűűűűűűűüüüüüüüüýýýýýýýýýýţţţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!!!""""""""""""""""""""""######################$$$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''''(((((((((((((((((((((((((((((((((())))))))))))))))))))))))))))))))))**************************************++++++++++++++++++++++++ÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááââââââââââââââââââăăăăăăăăăăăăăăăăääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććççççççççççççççččččččččččččččééééééééééééééęęęęęęęęęęęęëëëëëëëëëëëëěěěěěěěěěěěěííííííííííííîîîîîîîîîîîîďďďďďďďďďďďďđđđđđđđđđđńńńńńńńńńńńńňňňňňňňňňňóóóóóóóóóóôôôôôôôôôôôôőőőőőőőőőőöööööööööö÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřůůůůůůůůůůúúúúúúúúúúűűűűűűűűűűüüüüüüüüüüýýýýýýýýţţţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!!!""""""""""""""""""""""######################$$$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''''(((((((((((((((((((((((((((((((())))))))))))))))))))))))))))))))))))**************************************++++++++++++++++++++++++++ÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááââââââââââââââââââăăăăăăăăăăăăăăăăääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććççççççççççççççččččččččččččččééééééééééééęęęęęęęęęęęęęęëëëëëëëëëëëëěěěěěěěěěěěěííííííííííííîîîîîîîîîîîîďďďďďďďďďďđđđđđđđđđđđđńńńńńńńńńńňňňňňňňňňňóóóóóóóóóóóóôôôôôôôôôôőőőőőőőőőőöööööööööö÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřůůůůůůůůůůúúúúúúúúúúűűűűűűűűűűüüüüüüüüüüýýýýýýýýţţţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!""""""""""""""""""""""########################$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''''(((((((((((((((((((((((((((((((())))))))))))))))))))))))))))))))))))**************************************++++++++++++++++++++++++++++++ÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááááââââââââââââââââââăăăăăăăăăăăăăăăăääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććççççççççççççççččččččččččččééééééééééééééęęęęęęęęęęęęëëëëëëëëëëëëěěěěěěěěěěěěííííííííííííîîîîîîîîîîîîďďďďďďďďďďďďđđđđđđđđđđńńńńńńńńńńňňňňňňňňňňňňóóóóóóóóóóôôôôôôôôôôőőőőőőőőőőöööööööööö÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřůůůůůůůůůůúúúúúúúúúúűűűűűűűűűűüüüüüüüüüüýýýýýýýýţţţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!""""""""""""""""""""""########################$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''''(((((((((((((((((((((((((((((((())))))))))))))))))))))))))))))))))**************************************++++++++++++++++++++++++++++++++++ÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááááââââââââââââââââăăăăăăăăăăăăăăăăääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććççççççççççççççččččččččččččččééééééééééééęęęęęęęęęęęęęęëëëëëëëëëëëëěěěěěěěěěěěěííííííííííííîîîîîîîîîîďďďďďďďďďďďďđđđđđđđđđđńńńńńńńńńńńńňňňňňňňňňňóóóóóóóóóóôôôôôôôôôôőőőőőőőőőőöööööööööö÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřůůůůůůůůůůúúúúúúúúúúűűűűűűűűűűüüüüüüüüüüýýýýýýýýţţţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!""""""""""""""""""""""########################$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''''(((((((((((((((((((((((((((((((())))))))))))))))))))))))))))))))))))************************************++++++++++++++++++++++++++++++++++++++ÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááááââââââââââââââââăăăăăăăăăăăăăăăăääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććççççççççççççççččččččččččččččééééééééééééęęęęęęęęęęęęëëëëëëëëëëëëěěěěěěěěěěěěííííííííííííîîîîîîîîîîîîďďďďďďďďďďđđđđđđđđđđđđńńńńńńńńńńňňňňňňňňňňóóóóóóóóóóóóôôôôôôôôôôőőőőőőőőőőöööööööööö÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřůůůůůůůůúúúúúúúúúúűűűűűűűűűűüüüüüüüüüüýýýýýýýýýýţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!""""""""""""""""""""""######################$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''''(((((((((((((((((((((((((((((((())))))))))))))))))))))))))))))))))************************************++++++++++++++++++++++++++++++++++++++++++ÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááááââââââââââââââââăăăăăăăăăăăăăăăăääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććççççççççççççççččččččččččččééééééééééééééęęęęęęęęęęęęëëëëëëëëëëëëěěěěěěěěěěěěííííííííííííîîîîîîîîîîďďďďďďďďďďďďđđđđđđđđđđńńńńńńńńńńňňňňňňňňňňňňóóóóóóóóóóôôôôôôôôôôőőőőőőőőőőöööööööööö÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřůůůůůůůůúúúúúúúúúúűűűűűűűűűűüüüüüüüüüüýýýýýýýýýýţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!""""""""""""""""""""""######################$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''(((((((((((((((((((((((((((((((())))))))))))))))))))))))))))))))))**************************************++++++++++++++++++++++++++++++++++++++++,,,,ÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááááââââââââââââââââăăăăăăăăăăăăăăăăääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććççççççççççççççččččččččččččččééééééééééééęęęęęęęęęęęęëëëëëëëëëëëëěěěěěěěěěěěěííííííííííííîîîîîîîîîîîîďďďďďďďďďďđđđđđđđđđđđđńńńńńńńńńńňňňňňňňňňňóóóóóóóóóóôôôôôôôôôôőőőőőőőőőőöööööööööö÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřůůůůůůůůůůúúúúúúúúűűűűűűűűűűüüüüüüüüüüýýýýýýýýýýţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!""""""""""""""""""""""######################$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''''(((((((((((((((((((((((((((((((())))))))))))))))))))))))))))))))))************************************++++++++++++++++++++++++++++++++++++++++,,,,,,,,ÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááááââââââââââââââââăăăăăăăăăăăăăăääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććççççççççççççççččččččččččččééééééééééééééęęęęęęęęęęęęëëëëëëëëëëëëěěěěěěěěěěěěííííííííííîîîîîîîîîîîîďďďďďďďďďďďďđđđđđđđđđđńńńńńńńńńńňňňňňňňňňňóóóóóóóóóóôôôôôôôôôôőőőőőőőőőőöööööööööö÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřůůůůůůůůůůúúúúúúúúúúűűűűűűűűüüüüüüüüüüýýýýýýýýýýţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!""""""""""""""""""""########################$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''(((((((((((((((((((((((((((((((())))))))))))))))))))))))))))))))))************************************++++++++++++++++++++++++++++++++++++++++,,,,,,,,,,,,ÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááââââââââââââââââââăăăăăăăăăăăăăăääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććççççççççççççççččččččččččččééééééééééééęęęęęęęęęęęęëëëëëëëëëëëëěěěěěěěěěěěěííííííííííííîîîîîîîîîîďďďďďďďďďďďďđđđđđđđđđđńńńńńńńńńńňňňňňňňňňňňňóóóóóóóóóóôôôôôôôôôôőőőőőőőőőőöööööööööö÷÷÷÷÷÷÷÷řřřřřřřřřřůůůůůůůůůůúúúúúúúúúúűűűűűűűűüüüüüüüüüüýýýýýýýýýýţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!""""""""""""""""""""######################$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''''(((((((((((((((((((((((((((((())))))))))))))))))))))))))))))))))************************************++++++++++++++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,ÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááââââââââââââââââăăăăăăăăăăăăăăăăääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććççççççççççççččččččččččččččééééééééééééęęęęęęęęęęęęëëëëëëëëëëëëěěěěěěěěěěěěííííííííííîîîîîîîîîîîîďďďďďďďďďďđđđđđđđđđđđđńńńńńńńńńńňňňňňňňňňňóóóóóóóóóóôôôôôôôôôôőőőőőőőőőőöööööööööö÷÷÷÷÷÷÷÷÷÷řřřřřřřřůůůůůůůůůůúúúúúúúúúúűűűűűűűűüüüüüüüüüüýýýýýýýýýýţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!""""""""""""""""""""######################$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''(((((((((((((((((((((((((((((((())))))))))))))))))))))))))))))))))************************************++++++++++++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,ŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááââââââââââââââââăăăăăăăăăăăăăăăăääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććççççççççççççççččččččččččččééééééééééééééęęęęęęęęęęęęëëëëëëëëëëěěěěěěěěěěěěííííííííííííîîîîîîîîîîďďďďďďďďďďďďđđđđđđđđđđńńńńńńńńńńňňňňňňňňňňóóóóóóóóóóôôôôôôôôôôőőőőőőőőőőöööööööööö÷÷÷÷÷÷÷÷÷÷řřřřřřřřůůůůůůůůůůúúúúúúúúúúűűűűűűűűüüüüüüüüüüýýýýýýýýýýţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!""""""""""""""""""""######################$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''(((((((((((((((((((((((((((((())))))))))))))))))))))))))))))))))************************************++++++++++++++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,ŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááââââââââââââââââăăăăăăăăăăăăăăăăääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććççççççççççççččččččččččččččééééééééééééęęęęęęęęęęęęëëëëëëëëëëëëěěěěěěěěěěěěííííííííííîîîîîîîîîîîîďďďďďďďďďďđđđđđđđđđđńńńńńńńńńńňňňňňňňňňňňňóóóóóóóóóóôôôôôôôôőőőőőőőőőőöööööööööö÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřůůůůůůůůúúúúúúúúúúűűűűűűűűűűüüüüüüüüýýýýýýýýýýţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!""""""""""""""""""""""######################$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''(((((((((((((((((((((((((((((((())))))))))))))))))))))))))))))))************************************++++++++++++++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,ŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááââââââââââââââââăăăăăăăăăăăăăăääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććççççççççççççččččččččččččééééééééééééééęęęęęęęęęęęęëëëëëëëëëëěěěěěěěěěěěěííííííííííííîîîîîîîîîîďďďďďďďďďďđđđđđđđđđđđđńńńńńńńńńńňňňňňňňňňňóóóóóóóóóóôôôôôôôôôôőőőőőőőőőőöööööööö÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřůůůůůůůůúúúúúúúúúúűűűűűűűűűűüüüüüüüüýýýýýýýýýýţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!""""""""""""""""""""""######################$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''(((((((((((((((((((((((((((((())))))))))))))))))))))))))))))))))**********************************++++++++++++++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,ŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááââââââââââââââââăăăăăăăăăăăăăăääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććççççççççççççççččččččččččččééééééééééééęęęęęęęęęęęęëëëëëëëëëëëëěěěěěěěěěěěěííííííííííîîîîîîîîîîîîďďďďďďďďďďđđđđđđđđđđńńńńńńńńńńňňňňňňňňňňóóóóóóóóóóôôôôôôôôôôőőőőőőőőőőöööööööööö÷÷÷÷÷÷÷÷řřřřřřřřřřůůůůůůůůůůúúúúúúúúűűűűűűűűűűüüüüüüüüýýýýýýýýýýţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!""""""""""""""""""""""####################$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''(((((((((((((((((((((((((((((((())))))))))))))))))))))))))))))))************************************++++++++++++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,ŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááââââââââââââââââăăăăăăăăăăăăăăääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććççççççççççççččččččččččččččééééééééééééęęęęęęęęęęęęëëëëëëëëëëëëěěěěěěěěěěííííííííííííîîîîîîîîîîďďďďďďďďďďđđđđđđđđđđńńńńńńńńńńńńňňňňňňňňóóóóóóóóóóôôôôôôôôôôőőőőőőőőőőöööööööööö÷÷÷÷÷÷÷÷řřřřřřřřřřůůůůůůůůůůúúúúúúúúűűűűűűűűűűüüüüüüüüýýýýýýýýýýţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!""""""""""""""""""""######################$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''(((((((((((((((((((((((((((((())))))))))))))))))))))))))))))))************************************++++++++++++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,ŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááááââââââââââââââăăăăăăăăăăăăăăăăääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććççççççççççççččččččččččččééééééééééééęęęęęęęęęęęęëëëëëëëëëëëëěěěěěěěěěěěěííííííííííîîîîîîîîîîďďďďďďďďďďďďđđđđđđđđđđńńńńńńńńńńňňňňňňňňňňóóóóóóóóóóôôôôôôôôôôőőőőőőőőöööööööööö÷÷÷÷÷÷÷÷÷÷řřřřřřřřůůůůůůůůůůúúúúúúúúűűűűűűűűűűüüüüüüüüýýýýýýýýýýţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!""""""""""""""""""""######################$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''(((((((((((((((((((((((((((((())))))))))))))))))))))))))))))))))**********************************++++++++++++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,ŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááââââââââââââââââăăăăăăăăăăăăăăăăääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććççççççççççççççččččččččččččééééééééééééęęęęęęęęęęęęëëëëëëëëëëëëěěěěěěěěěěííííííííííííîîîîîîîîîîďďďďďďďďďďđđđđđđđđđđńńńńńńńńńńňňňňňňňňňňóóóóóóóóóóôôôôôôôôôôőőőőőőőőöööööööööö÷÷÷÷÷÷÷÷÷÷řřřřřřřřůůůůůůůůůůúúúúúúúúűűűűűűűűűűüüüüüüüüýýýýýýýýýýţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!""""""""""""""""""""######################$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''(((((((((((((((((((((((((((((())))))))))))))))))))))))))))))))**********************************++++++++++++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,----ŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááââââââââââââââââăăăăăăăăăăăăăăääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććççççççççççççččččččččččččččééééééééééééęęęęęęęęęęëëëëëëëëëëëëěěěěěěěěěěěěííííííííííîîîîîîîîîîďďďďďďďďďďđđđđđđđđđđđđńńńńńńńńńńňňňňňňňňóóóóóóóóóóôôôôôôôôôôőőőőőőőőőőöööööööö÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřůůůůůůůůúúúúúúúúúúűűűűűűűűüüüüüüüüýýýýýýýýýýţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!""""""""""""""""""""######################$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''(((((((((((((((((((((((((((((())))))))))))))))))))))))))))))))************************************++++++++++++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,--------ŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááââââââââââââââââăăăăăăăăăăăăăăääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććççççççççççççččččččččččččééééééééééééęęęęęęęęęęęęëëëëëëëëëëëëěěěěěěěěěěííííííííííîîîîîîîîîîîîďďďďďďďďďďđđđđđđđđđđńńńńńńńńńńňňňňňňňňňňóóóóóóóóóóôôôôôôôôőőőőőőőőőőöööööööööö÷÷÷÷÷÷÷÷řřřřřřřřřřůůůůůůůůúúúúúúúúúúűűűűűűűűüüüüüüüüüüýýýýýýýýţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!""""""""""""""""""""####################$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''(((((((((((((((((((((((((((((())))))))))))))))))))))))))))))))**********************************++++++++++++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,------------ŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááââââââââââââââââăăăăăăăăăăăăăăääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććççççççççççççççččččččččččččééééééééééééęęęęęęęęęęëëëëëëëëëëëëěěěěěěěěěěěěííííííííííîîîîîîîîîîďďďďďďďďďďđđđđđđđđđđńńńńńńńńńńňňňňňňňňňňóóóóóóóóóóôôôôôôôôôôőőőőőőőőöööööööööö÷÷÷÷÷÷÷÷řřřřřřřřřřůůůůůůůůúúúúúúúúúúűűűűűűűűüüüüüüüüüüýýýýýýýýţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!""""""""""""""""""""####################$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''(((((((((((((((((((((((((((((())))))))))))))))))))))))))))))))**********************************++++++++++++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,----------------ŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááââââââââââââââââăăăăăăăăăăăăăăääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććççççççççççççččččččččččččééééééééééééęęęęęęęęęęęęëëëëëëëëëëëëěěěěěěěěěěííííííííííîîîîîîîîîîîîďďďďďďďďďďđđđđđđđđđđńńńńńńńńńńňňňňňňňňóóóóóóóóóóôôôôôôôôôôőőőőőőőőöööööööööö÷÷÷÷÷÷÷÷÷÷řřřřřřřřůůůůůůůůúúúúúúúúúúűűűűűűűűüüüüüüüüüüýýýýýýýýţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!""""""""""""""""""######################$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''(((((((((((((((((((((((((((((())))))))))))))))))))))))))))))**********************************++++++++++++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,--------------------ŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááââââââââââââââăăăăăăăăăăăăăăăăääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććççççççççççççččččččččččččééééééééééééęęęęęęęęęęęęëëëëëëëëëëěěěěěěěěěěěěííííííííííîîîîîîîîîîďďďďďďďďďďđđđđđđđđđđńńńńńńńńńńňňňňňňňňňňóóóóóóóóôôôôôôôôôôőőőőőőőőőőöööööööö÷÷÷÷÷÷÷÷÷÷řřřřřřřřůůůůůůůůůůúúúúúúúúűűűűűűűűüüüüüüüüüüýýýýýýýýţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!""""""""""""""""""######################$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''(((((((((((((((((((((((((((())))))))))))))))))))))))))))))))**********************************++++++++++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,------------------------ŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááââââââââââââââăăăăăăăăăăăăăăääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććççççççççççççççččččččččččččééééééééééęęęęęęęęęęęęëëëëëëëëëëëëěěěěěěěěěěííííííííííîîîîîîîîîîďďďďďďďďďďđđđđđđđđđđńńńńńńńńńńňňňňňňňňňňóóóóóóóóóóôôôôôôôôőőőőőőőőőőöööööööö÷÷÷÷÷÷÷÷÷÷řřřřřřřřůůůůůůůůůůúúúúúúúúűűűűűűűűüüüüüüüüüüýýýýýýýýţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!""""""""""""""""""""######################$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''(((((((((((((((((((((((((((())))))))))))))))))))))))))))))))**********************************++++++++++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,----------------------------ŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááââââââââââââââăăăăăăăăăăăăăăääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććççççççççççççččččččččččččééééééééééééęęęęęęęęęęęęëëëëëëëëëëěěěěěěěěěěííííííííííííîîîîîîîîîîďďďďďďďďďďđđđđđđđđđđńńńńńńńńńńňňňňňňňňóóóóóóóóóóôôôôôôôôôôőőőőőőőőöööööööööö÷÷÷÷÷÷÷÷řřřřřřřřůůůůůůůůůůúúúúúúúúűűűűűűűűűűüüüüüüüüýýýýýýýýţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!""""""""""""""""""""####################$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''(((((((((((((((((((((((((((())))))))))))))))))))))))))))))**********************************++++++++++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,------------------------------ŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááââââââââââââââăăăăăăăăăăăăăăääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććççççççççççççččččččččččččééééééééééééęęęęęęęęęęëëëëëëëëëëëëěěěěěěěěěěííííííííííîîîîîîîîîîďďďďďďďďďďđđđđđđđđđđńńńńńńńńńńňňňňňňňňňňóóóóóóóóôôôôôôôôôôőőőőőőőőöööööööööö÷÷÷÷÷÷÷÷řřřřřřřřřřůůůůůůůůúúúúúúúúűűűűűűűűűűüüüüüüüüýýýýýýýýţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!""""""""""""""""""""####################$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''(((((((((((((((((((((((((((())))))))))))))))))))))))))))))))********************************++++++++++++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,----------------------------------ŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááââââââââââââââââăăăăăăăăăăăăăăääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććççççççççççççççččččččččččééééééééééééęęęęęęęęęęęęëëëëëëëëëëěěěěěěěěěěííííííííííííîîîîîîîîîîďďďďďďďďďďđđđđđđđđńńńńńńńńńńňňňňňňňňňňóóóóóóóóóóôôôôôôôôőőőőőőőőőőöööööööö÷÷÷÷÷÷÷÷řřřřřřřřřřůůůůůůůůúúúúúúúúűűűűűűűűűűüüüüüüüüýýýýýýýýţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!""""""""""""""""""""####################$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''(((((((((((((((((((((((((((())))))))))))))))))))))))))))))**********************************++++++++++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,--------------------------------------ŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááââââââââââââââââăăăăăăăăăăăăăăääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććççççççççççççččččččččččččééééééééééééęęęęęęęęęęëëëëëëëëëëëëěěěěěěěěěěííííííííííîîîîîîîîîîďďďďďďďďďďđđđđđđđđđđńńńńńńńńńńňňňňňňňňóóóóóóóóóóôôôôôôôôőőőőőőőőőőöööööööö÷÷÷÷÷÷÷÷÷÷řřřřřřřřůůůůůůůůúúúúúúúúúúűűűűűűűűüüüüüüüüýýýýýýýýţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!""""""""""""""""""""####################$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''(((((((((((((((((((((((((((())))))))))))))))))))))))))))))**********************************++++++++++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,------------------------------------------ŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááââââââââââââââăăăăăăăăăăăăăăääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććççççççççççççččččččččččččééééééééééęęęęęęęęęęęęëëëëëëëëëëěěěěěěěěěěííííííííííííîîîîîîîîîîďďďďďďďďđđđđđđđđđđńńńńńńńńńńňňňňňňňňňňóóóóóóóóôôôôôôôôôôőőőőőőőőöööööööö÷÷÷÷÷÷÷÷÷÷řřřřřřřřůůůůůůůůúúúúúúúúúúűűűűűűűűüüüüüüüüýýýýýýýýţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!""""""""""""""""""####################$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''(((((((((((((((((((((((((((())))))))))))))))))))))))))))))********************************++++++++++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,------------------------------------------....ŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááââââââââââââââăăăăăăăăăăăăăăääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććççççççççççççččččččččččččééééééééééééęęęęęęęęęęëëëëëëëëëëëëěěěěěěěěěěííííííííííîîîîîîîîîîďďďďďďďďďďđđđđđđđđđđńńńńńńńńňňňňňňňňňňóóóóóóóóôôôôôôôôôôőőőőőőőőöööööööööö÷÷÷÷÷÷÷÷řřřřřřřřůůůůůůůůúúúúúúúúúúűűűűűűűűüüüüüüüüýýýýýýýýţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!""""""""""""""""""####################$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''(((((((((((((((((((((((((((())))))))))))))))))))))))))))))********************************++++++++++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,------------------------------------------........ŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááââââââââââââââăăăăăăăăăăăăăăääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććççççççççççççččččččččččččééééééééééęęęęęęęęęęęęëëëëëëëëëëěěěěěěěěěěííííííííííîîîîîîîîîîďďďďďďďďďďđđđđđđđđđđńńńńńńńńńńňňňňňňňňóóóóóóóóóóôôôôôôôôőőőőőőőőőőöööööööö÷÷÷÷÷÷÷÷řřřřřřřřůůůůůůůůůůúúúúúúúúűűűűűűűűüüüüüüüüýýýýýýýýţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!""""""""""""""""""####################$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''(((((((((((((((((((((((((())))))))))))))))))))))))))))))**********************************++++++++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,------------------------------------------............ŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááââââââââââââââăăăăăăăăăăăăăăääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććççççççççççççččččččččččééééééééééééęęęęęęęęęęëëëëëëëëëëëëěěěěěěěěěěííííííííííîîîîîîîîîîďďďďďďďďďďđđđđđđđđńńńńńńńńńńňňňňňňňňóóóóóóóóóóôôôôôôôôőőőőőőőőőőöööööööö÷÷÷÷÷÷÷÷řřřřřřřřřřůůůůůůůůúúúúúúúúűűűűűűűűüüüüüüüüýýýýýýýýţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!""""""""""""""""""####################$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''(((((((((((((((((((((((((((())))))))))))))))))))))))))))))********************************++++++++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,------------------------------------------................ŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááââââââââââââââăăăăăăăăăăăăăăääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććççççççççççččččččččččččééééééééééééęęęęęęęęęęëëëëëëëëëëěěěěěěěěěěííííííííííîîîîîîîîîîďďďďďďďďďďđđđđđđđđđđńńńńńńńńňňňňňňňňňňóóóóóóóóôôôôôôôôôôőőőőőőőőöööööööö÷÷÷÷÷÷÷÷÷÷řřřřřřřřůůůůůůůůúúúúúúúúűűűűűűűűüüüüüüüüýýýýýýýýţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!""""""""""""""""""####################$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''(((((((((((((((((((((((((())))))))))))))))))))))))))))))********************************++++++++++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,------------------------------------------....................ĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááââââââââââââââăăăăăăăăăăăăääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććççççççççççççččččččččččččééééééééééęęęęęęęęęęęęëëëëëëëëëëěěěěěěěěěěííííííííííîîîîîîîîîîďďďďďďďďđđđđđđđđđđńńńńńńńńńńňňňňňňňňóóóóóóóóóóôôôôôôôôőőőőőőőőöööööööö÷÷÷÷÷÷÷÷÷÷řřřřřřřřůůůůůůůůúúúúúúúúűűűűűűűűüüüüüüüüýýýýýýýýţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!""""""""""""""""""""##################$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''(((((((((((((((((((((((((((())))))))))))))))))))))))))))********************************++++++++++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,------------------------------------------........................ĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááââââââââââââââăăăăăăăăăăăăăăääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććççççççççççççččččččččččééééééééééééęęęęęęęęęęëëëëëëëëëëěěěěěěěěěěííííííííííîîîîîîîîîîďďďďďďďďďďđđđđđđđđńńńńńńńńńńňňňňňňňňóóóóóóóóóóôôôôôôôôőőőőőőőőöööööööööö÷÷÷÷÷÷÷÷řřřřřřřřůůůůůůůůúúúúúúúúűűűűűűűűüüüüüüüüýýýýýýýýţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!""""""""""""""""""""##################$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''(((((((((((((((((((((((((())))))))))))))))))))))))))))))********************************++++++++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,------------------------------------------............................ĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááââââââââââââââăăăăăăăăăăăăăăääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććççççççççççččččččččččččééééééééééęęęęęęęęęęęęëëëëëëëëëëěěěěěěěěěěííííííííííîîîîîîîîďďďďďďďďďďđđđđđđđđđđńńńńńńńńňňňňňňňňňňóóóóóóóóôôôôôôôôőőőőőőőőőőöööööööö÷÷÷÷÷÷÷÷řřřřřřřřůůůůůůůůúúúúúúúúűűűűűűűűüüüüüüüüýýýýýýýýţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!""""""""""""""""""####################$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''(((((((((((((((((((((((((((())))))))))))))))))))))))))))********************************++++++++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,----------------------------------------..................................ĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááââââââââââââââăăăăăăăăăăăăăăääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććççççççççççççččččččččččččééééééééééęęęęęęęęęęëëëëëëëëëëěěěěěěěěěěííííííííííîîîîîîîîîîďďďďďďďďďďđđđđđđđđńńńńńńńńńńňňňňňňňňóóóóóóóóôôôôôôôôôôőőőőőőőőöööööööö÷÷÷÷÷÷÷÷řřřřřřřřůůůůůůůůúúúúúúúúűűűűűűűűüüüüüüüüýýýýýýýýţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!""""""""""""""""""####################$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''(((((((((((((((((((((((((())))))))))))))))))))))))))))********************************++++++++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,----------------------------------------......................................ĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááââââââââââââââăăăăăăăăăăăăăăääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććççççççççççççččččččččččééééééééééééęęęęęęęęęęëëëëëëëëëëěěěěěěěěěěííííííííííîîîîîîîîďďďďďďďďďďđđđđđđđđđđńńńńńńńńňňňňňňňňóóóóóóóóóóôôôôôôôôőőőőőőőőöööööööö÷÷÷÷÷÷÷÷řřřřřřřřůůůůůůůůůůúúúúúúúúűűűűűűűűüüüüüüüüýýýýýýýýţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!""""""""""""""""""####################$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''(((((((((((((((((((((((((())))))))))))))))))))))))))))))******************************++++++++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,----------------------------------------..........................................ĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááââââââââââââââăăăăăăăăăăăăääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććççççççççççččččččččččččééééééééééęęęęęęęęęęëëëëëëëëëëěěěěěěěěěěííííííííííîîîîîîîîîîďďďďďďďďđđđđđđđđđđńńńńńńńńňňňňňňňňňňóóóóóóóóôôôôôôôôőőőőőőőőöööööööö÷÷÷÷÷÷÷÷÷÷řřřřřřřřůůůůůůůůúúúúúúúúűűűűűűűűüüüüüüüüýýýýýýýýţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!""""""""""""""""""##################$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''(((((((((((((((((((((((((())))))))))))))))))))))))))))********************************++++++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,----------------------------------------..............................................ĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááââââââââââââââăăăăăăăăăăăăääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććççççççççççççččččččččččééééééééééééęęęęęęęęęęëëëëëëëëëëěěěěěěěěěěííííííííííîîîîîîîîďďďďďďďďďďđđđđđđđđńńńńńńńńńńňňňňňňňňóóóóóóóóôôôôôôôôőőőőőőőőőőöööööööö÷÷÷÷÷÷÷÷řřřřřřřřůůůůůůůůúúúúúúúúűűűűűűűűüüüüüüüüýýýýýýýýţţţţţţţţ˙˙  !!!!!!!!!!!!!!!!!!""""""""""""""""""##################$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''(((((((((((((((((((((((((())))))))))))))))))))))))))))********************************++++++++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,----------------------------------------..............................................////ĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááââââââââââââăăăăăăăăăăăăăăääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććççççççççççççččččččččččééééééééééęęęęęęęęęęëëëëëëëëëëěěěěěěěěěěííííííííííîîîîîîîîîîďďďďďďďďđđđđđđđđđđńńńńńńńńňňňňňňňňóóóóóóóóóóôôôôôôôôőőőőőőőőöööööööö÷÷÷÷÷÷÷÷řřřřřřřřůůůůůůůůúúúúúúúúűűűűűűűűüüüüüüüüýýýýýýýýţţţţţţţţ˙˙  !!!!!!!!!!!!!!!!!!""""""""""""""""""##################$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''(((((((((((((((((((((((((())))))))))))))))))))))))))))******************************++++++++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,----------------------------------------............................................//////////ĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááââââââââââââăăăăăăăăăăăăăăääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććççççççççççččččččččččččééééééééééęęęęęęęęęęëëëëëëëëëëěěěěěěěěěěííííííííîîîîîîîîîîďďďďďďďďďďđđđđđđđđńńńńńńńńňňňňňňňňňňóóóóóóóóôôôôôôôôőőőőőőőőöööööööö÷÷÷÷÷÷÷÷řřřřřřřřůůůůůůůůúúúúúúúúűűűűűűűűüüüüüüüüýýýýýýýýţţţţţţţţ˙˙  !!!!!!!!!!!!!!!!!!""""""""""""""""####################$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''(((((((((((((((((((((((((())))))))))))))))))))))))))))******************************++++++++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,----------------------------------------............................................//////////////ĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááââââââââââââââăăăăăăăăăăăăăăääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺććććććććććççççççççççççččččččččččééééééééééęęęęęęęęęęëëëëëëëëëëěěěěěěěěěěííííííííííîîîîîîîîďďďďďďďďďďđđđđđđđđńńńńńńńńńńňňňňňňňňóóóóóóóóôôôôôôôôőőőőőőőőöööööööö÷÷÷÷÷÷÷÷řřřřřřřřůůůůůůůůúúúúúúúúűűűűűűűűüüüüüüüüýýýýýýýýţţţţţţţţ˙˙  !!!!!!!!!!!!!!!!!!""""""""""""""""##################$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''(((((((((((((((((((((((((())))))))))))))))))))))))))********************************++++++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,----------------------------------------............................................//////////////////ĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááââââââââââââââăăăăăăăăăăăăääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććççççççççççččččččččččččééééééééééęęęęęęęęęęëëëëëëëëëëěěěěěěěěěěííííííííîîîîîîîîîîďďďďďďďďđđđđđđđđđđńńńńńńńńňňňňňňňňóóóóóóóóôôôôôôôôőőőőőőőőőőöööööööö÷÷÷÷÷÷řřřřřřřřůůůůůůůůúúúúúúúúűűűűűűűűüüüüüüüüýýýýýýýýţţţţţţţţ˙˙  !!!!!!!!!!!!!!!!""""""""""""""""""##################$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''(((((((((((((((((((((((((())))))))))))))))))))))))))))******************************++++++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,----------------------------------------............................................//////////////////////ĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááââââââââââââââăăăăăăăăăăăăääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććççççççççççččččččččččééééééééééééęęęęęęęęęęëëëëëëëëěěěěěěěěěěííííííííííîîîîîîîîďďďďďďďďďďđđđđđđđđńńńńńńńńňňňňňňňňňňóóóóóóóóôôôôôôôôőőőőőőőőöööööööö÷÷÷÷÷÷÷÷řřřřřřřřůůůůůůúúúúúúúúűűűűűűűűüüüüüüüüýýýýýýýýţţţţţţţţ˙˙  !!!!!!!!!!!!!!!!""""""""""""""""""##################$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''(((((((((((((((((((((((())))))))))))))))))))))))))))******************************++++++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,----------------------------------------..........................................////////////////////////////ĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááââââââââââââââăăăăăăăăăăăăääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺććććććććććççççççççççççččččččččččééééééééééęęęęęęęęęęëëëëëëëëëëěěěěěěěěěěííííííííîîîîîîîîîîďďďďďďďďđđđđđđđđńńńńńńńńńńňňňňňňňňóóóóóóóóôôôôôôôôőőőőőőőőöööööööö÷÷÷÷÷÷÷÷řřřřřřřřůůůůůůůůúúúúúúűűűűűűűűüüüüüüüüýýýýýýýýţţţţţţţţ˙˙  !!!!!!!!!!!!!!!!""""""""""""""""""##################$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''(((((((((((((((((((((((((())))))))))))))))))))))))))******************************++++++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,--------------------------------------............................................////////////////////////////////ĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááââââââââââââăăăăăăăăăăăăăăääääääääääääĺĺĺĺĺĺĺĺĺĺććććććććććććççççççççççččččččččččččééééééééééęęęęęęęęęęëëëëëëëëěěěěěěěěěěííííííííííîîîîîîîîďďďďďďďďďďđđđđđđđđńńńńńńńńňňňňňňňňóóóóóóóóôôôôôôôôőőőőőőőőöööööööö÷÷÷÷÷÷÷÷řřřřřřřřůůůůůůůůúúúúúúúúűűűűűűüüüüüüüüýýýýýýýýţţţţţţţţ˙˙  !!!!!!!!!!!!!!!!""""""""""""""""""##################$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''(((((((((((((((((((((((())))))))))))))))))))))))))))****************************++++++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,--------------------------------------............................................////////////////////////////////////ĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááââââââââââââăăăăăăăăăăăăääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććççççççççççččččččččččééééééééééęęęęęęęęęęëëëëëëëëëëěěěěěěěěěěííííííííîîîîîîîîîîďďďďďďďďđđđđđđđđńńńńńńńńňňňňňňňňóóóóóóóóóóôôôôôôôôőőőőőőöööööööö÷÷÷÷÷÷÷÷řřřřřřřřůůůůůůůůúúúúúúúúűűűűűűüüüüüüüüýýýýýýýýţţţţţţţţ˙˙  !!!!!!!!!!!!!!!!""""""""""""""""""################$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''(((((((((((((((((((((((((())))))))))))))))))))))))))******************************++++++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,--------------------------------------............................................////////////////////////////////////////ĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááââââââââââââăăăăăăăăăăăăääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺććććććććććççççççççççççččččččččččééééééééééęęęęęęęęęęëëëëëëëëěěěěěěěěěěííííííííííîîîîîîîîďďďďďďďďđđđđđđđđđđńńńńńńńńňňňňňňňňóóóóóóóóôôôôôôôôőőőőőőőőöööööööö÷÷÷÷÷÷řřřřřřřřůůůůůůůůúúúúúúúúűűűűűűüüüüüüüüýýýýýýýýţţţţţţţţ˙˙  !!!!!!!!!!!!!!!!""""""""""""""""##################$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''(((((((((((((((((((((((())))))))))))))))))))))))))******************************++++++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,--------------------------------------..........................................//////////////////////////////////////////////ĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááââââââââââââăăăăăăăăăăăăääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺććććććććććççççççççççččččččččččééééééééééęęęęęęęęęęëëëëëëëëëëěěěěěěěěěěííííííííîîîîîîîîďďďďďďďďďďđđđđđđđđńńńńńńńńňňňňňňňňóóóóóóóóôôôôôôôôőőőőőőőőöööööööö÷÷÷÷÷÷÷÷řřřřřřůůůůůůůůúúúúúúúúűűűűűűűűüüüüüüýýýýýýýýţţţţţţţţ˙˙  !!!!!!!!!!!!!!!!""""""""""""""""##################$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''(((((((((((((((((((((((((())))))))))))))))))))))))))****************************++++++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,--------------------------------------..........................................////////////////////////////////////////////////00ĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááââââââââââââââăăăăăăăăăăăăääääääääääääĺĺĺĺĺĺĺĺĺĺććććććććććććççççççççççččččččččččééééééééééęęęęęęęęęęëëëëëëëëěěěěěěěěěěííííííííîîîîîîîîîîďďďďďďďďđđđđđđđđńńńńńńńńňňňňňňňňóóóóóóóóôôôôôôôôőőőőőőőőöööööööö÷÷÷÷÷÷÷÷řřřřřřřřůůůůůůúúúúúúúúűűűűűűűűüüüüüüýýýýýýýýţţţţţţţţ˙˙  !!!!!!!!!!!!!!!!""""""""""""""""##################$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''(((((((((((((((((((((((())))))))))))))))))))))))))****************************++++++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,--------------------------------------..........................................//////////////////////////////////////////////00000000ĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááââââââââââââââăăăăăăăăăăăăääääääääääĺĺĺĺĺĺĺĺĺĺĺĺććććććććććççççççççççççččččččččččééééééééééęęęęęęęęëëëëëëëëëëěěěěěěěěěěííííííííîîîîîîîîďďďďďďďďđđđđđđđđđđńńńńńńńńňňňňňňňňóóóóóóóóôôôôôôôôőőőőőőöööööööö÷÷÷÷÷÷÷÷řřřřřřřřůůůůůůúúúúúúúúűűűűűűűűüüüüüüýýýýýýýýţţţţţţţţ˙˙  !!!!!!!!!!!!!!!!""""""""""""""""##################$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''(((((((((((((((((((((((())))))))))))))))))))))))))))****************************++++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,--------------------------------------..........................................//////////////////////////////////////////////000000000000ĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááââââââââââââăăăăăăăăăăăăääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺććććććććććççççççççççččččččččččééééééééééęęęęęęęęęęëëëëëëëëëëěěěěěěěěííííííííîîîîîîîîîîďďďďďďďďđđđđđđđđńńńńńńńńňňňňňňňňóóóóóóóóôôôôôôôôőőőőőőőőöööööö÷÷÷÷÷÷÷÷řřřřřřřřůůůůůůúúúúúúúúűűűűűűűűüüüüüüýýýýýýýýţţţţţţţţ˙˙  !!!!!!!!!!!!!!!!""""""""""""""""################$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''(((((((((((((((((((((((())))))))))))))))))))))))))****************************++++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,--------------------------------------........................................//////////////////////////////////////////////000000000000000000ĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááââââââââââââăăăăăăăăăăăăääääääääääääĺĺĺĺĺĺĺĺĺĺććććććććććććççççççççççččččččččččééééééééééęęęęęęęęëëëëëëëëëëěěěěěěěěííííííííííîîîîîîîîďďďďďďďďđđđđđđđđńńńńńńńńňňňňňňňňóóóóóóóóôôôôôôôôőőőőőőőőöööööööö÷÷÷÷÷÷řřřřřřřřůůůůůůůůúúúúúúűűűűűűűűüüüüüüýýýýýýýýţţţţţţţţ˙˙  !!!!!!!!!!!!!!!!""""""""""""""""################$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''(((((((((((((((((((((((())))))))))))))))))))))))))****************************++++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,--------------------------------------........................................//////////////////////////////////////////////0000000000000000000000ÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááââââââââââââăăăăăăăăăăăăääääääääääääĺĺĺĺĺĺĺĺĺĺććććććććććççççççççççççččččččččččééééééééęęęęęęęęęęëëëëëëëëëëěěěěěěěěííííííííîîîîîîîîîîďďďďďďďďđđđđđđđđńńńńńńńńňňňňňňňňóóóóóóóóôôôôôôőőőőőőőőöööööööö÷÷÷÷÷÷řřřřřřřřůůůůůůůůúúúúúúűűűűűűűűüüüüüüýýýýýýýýţţţţţţţţ˙˙  !!!!!!!!!!!!!!""""""""""""""""##################$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''(((((((((((((((((((((((())))))))))))))))))))))))))**************************++++++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,--------------------------------------........................................//////////////////////////////////////////////00000000000000000000000000ÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááââââââââââââăăăăăăăăăăăăääääääääääĺĺĺĺĺĺĺĺĺĺĺĺććććććććććççççççççççččččččččččééééééééééęęęęęęęęęęëëëëëëëëěěěěěěěěííííííííííîîîîîîîîďďďďďďďďđđđđđđđđńńńńńńńńňňňňňňňňóóóóóóóóôôôôôôôôőőőőőőöööööööö÷÷÷÷÷÷÷÷řřřřřřůůůůůůůůúúúúúúűűűűűűűűüüüüüüüüýýýýýýţţţţţţţţ˙˙  !!!!!!!!!!!!!!""""""""""""""""##################$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''(((((((((((((((((((((((())))))))))))))))))))))))))****************************++++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,------------------------------------..........................................////////////////////////////////////////////00000000000000000000000000000000ÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááââââââââââââăăăăăăăăăăăăääääääääääĺĺĺĺĺĺĺĺĺĺĺĺććććććććććççççççççççččččččččččééééééééęęęęęęęęęęëëëëëëëëëëěěěěěěěěííííííííîîîîîîîîďďďďďďďďđđđđđđđđńńńńńńńńňňňňňňňňóóóóóóóóôôôôôôôôőőőőőőőőöööööö÷÷÷÷÷÷÷÷řřřřřřůůůůůůůůúúúúúúúúűűűűűűüüüüüüüüýýýýýýţţţţţţţţ˙˙  !!!!!!!!!!!!!!""""""""""""""""##################$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''(((((((((((((((((((((((())))))))))))))))))))))))****************************++++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,------------------------------------........................................//////////////////////////////////////////////000000000000000000000000000000000000ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááââââââââââââăăăăăăăăăăääääääääääääĺĺĺĺĺĺĺĺĺĺććććććććććććççççççççççččččččččééééééééééęęęęęęęęęęëëëëëëëëěěěěěěěěííííííííííîîîîîîîîďďďďďďďďđđđđđđđđńńńńńńńńňňňňňňňňóóóóóóôôôôôôôôőőőőőőőőöööööö÷÷÷÷÷÷÷÷řřřřřřřřůůůůůůúúúúúúúúűűűűűűüüüüüüüüýýýýýýţţţţţţţţ˙˙  !!!!!!!!!!!!!!""""""""""""""""################$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''(((((((((((((((((((((((())))))))))))))))))))))))))**************************++++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,------------------------------------........................................////////////////////////////////////////////000000000000000000000000000000000000000000ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááââââââââââăăăăăăăăăăăăääääääääääääĺĺĺĺĺĺĺĺĺĺććććććććććççççççççççččččččččččééééééééééęęęęęęęęëëëëëëëëëëěěěěěěěěííííííííîîîîîîîîďďďďďďďďđđđđđđđđńńńńńńńńňňňňňňňňóóóóóóóóôôôôôôőőőőőőőőöööööööö÷÷÷÷÷÷řřřřřřřřůůůůůůúúúúúúúúűűűűűűüüüüüüüüýýýýýýţţţţţţţţ˙˙  !!!!!!!!!!!!!!""""""""""""""""################$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''(((((((((((((((((((((())))))))))))))))))))))))))**************************++++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,------------------------------------........................................////////////////////////////////////////////0000000000000000000000000000000000000000000000ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááââââââââââââăăăăăăăăăăăăääääääääääääĺĺĺĺĺĺĺĺĺĺććććććććććççççççççççččččččččččééééééééęęęęęęęęęęëëëëëëëëěěěěěěěěííííííííííîîîîîîîîďďďďďďďďđđđđđđđđńńńńńńňňňňňňňňóóóóóóóóôôôôôôôôőőőőőőöööööööö÷÷÷÷÷÷řřřřřřřřůůůůůůúúúúúúúúűűűűűűüüüüüüüüýýýýýýţţţţţţţţ˙˙  !!!!!!!!!!!!!!""""""""""""""""################$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''(((((((((((((((((((((((())))))))))))))))))))))))****************************++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,------------------------------------........................................////////////////////////////////////////////00000000000000000000000000000000000000000000000011ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááââââââââââââăăăăăăăăăăăăääääääääääĺĺĺĺĺĺĺĺĺĺĺĺććććććććććççççççççččččččččččééééééééééęęęęęęęęëëëëëëëëëëěěěěěěěěííííííííîîîîîîîîďďďďďďďďđđđđđđđđńńńńńńńńňňňňňňňňóóóóóóôôôôôôôôőőőőőőöööööööö÷÷÷÷÷÷÷÷řřřřřřůůůůůůůůúúúúúúűűűűűűüüüüüüüüýýýýýýţţţţţţţţ˙˙  !!!!!!!!!!!!!!""""""""""""""""################$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&''''''''''''''''''''''(((((((((((((((((((((())))))))))))))))))))))))))**************************++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,------------------------------------......................................////////////////////////////////////////////00000000000000000000000000000000000000000000000000111111ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááââââââââââââăăăăăăăăăăăăääääääääääĺĺĺĺĺĺĺĺĺĺććććććććććççççççççççččččččččččééééééééęęęęęęęęęęëëëëëëëëěěěěěěěěííííííííîîîîîîîîďďďďďďďďđđđđđđđđńńńńńńńńňňňňňňňňóóóóóóóóôôôôôôőőőőőőőőöööööö÷÷÷÷÷÷÷÷řřřřřřůůůůůůůůúúúúúúűűűűűűüüüüüüüüýýýýýýţţţţţţţţ˙˙  !!!!!!!!!!!!!!""""""""""""""################$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''(((((((((((((((((((((((())))))))))))))))))))))))**************************++++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,------------------------------------......................................////////////////////////////////////////////000000000000000000000000000000000000000000000000111111111111ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááââââââââââââăăăăăăăăăăääääääääääääĺĺĺĺĺĺĺĺĺĺććććććććććççççççççççččččččččééééééééééęęęęęęęęëëëëëëëëëëěěěěěěěěííííííííîîîîîîîîďďďďďďďďđđđđđđđđńńńńńńňňňňňňňňóóóóóóóóôôôôôôőőőőőőőőöööööö÷÷÷÷÷÷÷÷řřřřřřůůůůůůůůúúúúúúűűűűűűűűüüüüüüýýýýýýţţţţţţţţ˙˙  !!!!!!!!!!!!!!!!""""""""""""""################$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''(((((((((((((((((((((())))))))))))))))))))))))**************************++++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,------------------------------------......................................//////////////////////////////////////////000000000000000000000000000000000000000000000000111111111111111111ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááââââââââââââăăăăăăăăăăääääääääääääĺĺĺĺĺĺĺĺĺĺććććććććććççççççççččččččččččééééééééééęęęęęęęęëëëëëëëëěěěěěěěěííííííííîîîîîîîîďďďďďďďďđđđđđđđđńńńńńńńńňňňňňňóóóóóóóóôôôôôôôôőőőőőőöööööööö÷÷÷÷÷÷řřřřřřřřůůůůůůúúúúúúűűűűűűűűüüüüüüýýýýýýţţţţţţţţ˙˙  !!!!!!!!!!!!!!!!""""""""""""""################$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&''''''''''''''''''''(((((((((((((((((((((((())))))))))))))))))))))))**************************++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,------------------------------------......................................//////////////////////////////////////////0000000000000000000000000000000000000000000000001111111111111111111111ÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááââââââââââââăăăăăăăăăăääääääääääĺĺĺĺĺĺĺĺĺĺććććććććććççççççççççččččččččččééééééééęęęęęęęęëëëëëëëëëëěěěěěěěěííííííííîîîîîîîîďďďďďďďďđđđđđđńńńńńńńńňňňňňňňňóóóóóóôôôôôôôôőőőőőőöööööööö÷÷÷÷÷÷řřřřřřřřůůůůůůúúúúúúűűűűűűűűüüüüüüýýýýýýţţţţţţţţ˙˙  !!!!!!!!!!!!!!""""""""""""""""################$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''(((((((((((((((((((((())))))))))))))))))))))))**************************++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,----------------------------------......................................//////////////////////////////////////////0000000000000000000000000000000000000000000000001111111111111111111111111111ÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááââââââââââăăăăăăăăăăăăääääääääääĺĺĺĺĺĺĺĺĺĺććććććććććççççççççççččččččččééééééééééęęęęęęęęëëëëëëëëěěěěěěěěííííííííîîîîîîîîďďďďďďďďđđđđđđđđńńńńńńňňňňňňňňóóóóóóóóôôôôôôőőőőőőőőöööööö÷÷÷÷÷÷řřřřřřřřůůůůůůúúúúúúúúűűűűűűüüüüüüýýýýýýţţţţţţţţ˙˙  !!!!!!!!!!!!!!""""""""""""""""################$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&''''''''''''''''''''''(((((((((((((((((((())))))))))))))))))))))))**************************++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,----------------------------------......................................//////////////////////////////////////////00000000000000000000000000000000000000000000000011111111111111111111111111111111ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááââââââââââăăăăăăăăăăăăääääääääääĺĺĺĺĺĺĺĺĺĺććććććććććççççççççččččččččččééééééééęęęęęęęęęęëëëëëëëëěěěěěěěěííííííííîîîîîîîîďďďďďďđđđđđđđđńńńńńńńńňňňňňňóóóóóóóóôôôôôôőőőőőőőőöööööö÷÷÷÷÷÷÷÷řřřřřřůůůůůůúúúúúúúúűűűűűűüüüüüüýýýýýýţţţţţţţţ˙˙  !!!!!!!!!!!!!!""""""""""""""""##############$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&''''''''''''''''''''(((((((((((((((((((((())))))))))))))))))))))))************************++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,----------------------------------......................................//////////////////////////////////////////000000000000000000000000000000000000000000000011111111111111111111111111111111111111ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááââââââââââââăăăăăăăăăăääääääääääääĺĺĺĺĺĺĺĺĺĺććććććććççççççççççččččččččééééééééééęęęęęęęęëëëëëëëëěěěěěěěěííííííííîîîîîîîîďďďďďďďďđđđđđđńńńńńńńńňňňňňňňňóóóóóóôôôôôôôôőőőőőőöööööö÷÷÷÷÷÷÷÷řřřřřřůůůůůůúúúúúúúúűűűűűűüüüüüüýýýýýýţţţţţţţţ˙˙  !!!!!!!!!!!!!!""""""""""""""################$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''(((((((((((((((((((())))))))))))))))))))))))**************************++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,----------------------------------......................................////////////////////////////////////////000000000000000000000000000000000000000000000000111111111111111111111111111111111111111111ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááââââââââââââăăăăăăăăăăääääääääääĺĺĺĺĺĺĺĺĺĺććććććććććççççççççççččččččččééééééééęęęęęęęęęęëëëëëëëëěěěěěěěěííííííííîîîîîîďďďďďďďďđđđđđđđđńńńńńńňňňňňňňňóóóóóóôôôôôôôôőőőőőőöööööööö÷÷÷÷÷÷řřřřřřůůůůůůůůúúúúúúűűűűűűüüüüüüýýýýýýţţţţţţţţ˙˙  !!!!!!!!!!!!!!""""""""""""""################$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&''''''''''''''''''''(((((((((((((((((((((())))))))))))))))))))))**************************++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,--------------------------------....................................//////////////////////////////////////////0000000000000000000000000000000000000000000000111111111111111111111111111111111111111111111111ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááââââââââââââăăăăăăăăăăääääääääääĺĺĺĺĺĺĺĺĺĺććććććććććççççççççččččččččččééééééééęęęęęęęęëëëëëëëëěěěěěěěěííííííííîîîîîîîîďďďďďďďďđđđđđđńńńńńńńńňňňňňňóóóóóóóóôôôôôôőőőőőőőőöööööö÷÷÷÷÷÷řřřřřřůůůůůůůůúúúúúúűűűűűűüüüüüüýýýýýýýýţţţţţţ˙˙  !!!!!!!!!!!!!!""""""""""""""################$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''(((((((((((((((((((())))))))))))))))))))))))************************++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,--------------------------------....................................////////////////////////////////////////0000000000000000000000000000000000000000000000111111111111111111111111111111111111111111111111111122ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááââââââââââââăăăăăăăăăăääääääääääĺĺĺĺĺĺĺĺĺĺććććććććççççççççççččččččččééééééééééęęęęęęęęëëëëëëëëěěěěěěěěííííííííîîîîîîďďďďďďďďđđđđđđđđńńńńńńňňňňňňňňóóóóóóôôôôôôőőőőőőőőöööööö÷÷÷÷÷÷řřřřřřřřůůůůůůúúúúúúűűűűűűüüüüüüýýýýýýýýţţţţţţ˙˙  !!!!!!!!!!!!!!""""""""""""""################$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&''''''''''''''''''''(((((((((((((((((((((())))))))))))))))))))))************************++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,--------------------------------....................................////////////////////////////////////////00000000000000000000000000000000000000000000001111111111111111111111111111111111111111111111111111222222ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááââââââââââăăăăăăăăăăăăääääääääääĺĺĺĺĺĺĺĺććććććććććççççççççççččččččččééééééééęęęęęęęęëëëëëëëëěěěěěěěěííííííííîîîîîîîîďďďďďďđđđđđđđđńńńńńńňňňňňňňňóóóóóóôôôôôôôôőőőőőőöööööö÷÷÷÷÷÷÷÷řřřřřřůůůůůůúúúúúúűűűűűűüüüüüüýýýýýýýýţţţţţţ˙˙  !!!!!!!!!!!!!!""""""""""""""##############$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&''''''''''''''''''''(((((((((((((((((((())))))))))))))))))))))**************************++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,--------------------------------....................................////////////////////////////////////////000000000000000000000000000000000000000000001111111111111111111111111111111111111111111111111111222222222222ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááââââââââââăăăăăăăăăăääääääääääĺĺĺĺĺĺĺĺĺĺććććććććććççççççççččččččččččééééééééęęęęęęęęëëëëëëëëěěěěěěěěííííííîîîîîîîîďďďďďďďďđđđđđđńńńńńńńńňňňňňňóóóóóóóóôôôôôôőőőőőőöööööööö÷÷÷÷÷÷řřřřřřůůůůůůúúúúúúűűűűűűüüüüüüýýýýýýýýţţţţţţ˙˙  !!!!!!!!!!!!!!""""""""""""""##############$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&''''''''''''''''''''(((((((((((((((((((())))))))))))))))))))))))************************++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,--------------------------------..................................////////////////////////////////////////000000000000000000000000000000000000000000000011111111111111111111111111111111111111111111111111222222222222222222ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááââââââââââăăăăăăăăăăääääääääääĺĺĺĺĺĺĺĺĺĺććććććććççççççççççččččččččééééééééęęęęęęęęëëëëëëëëěěěěěěěěííííííííîîîîîîîîďďďďďďđđđđđđđđńńńńńńňňňňňňňňóóóóóóôôôôôôőőőőőőöööööööö÷÷÷÷÷÷řřřřřřůůůůůůúúúúúúűűűűűűüüüüüüüüýýýýýýţţţţţţ˙˙  !!!!!!!!!!!!!!""""""""""""""##############$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&''''''''''''''''''''(((((((((((((((((((())))))))))))))))))))))************************++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,--------------------------------..................................////////////////////////////////////////0000000000000000000000000000000000000000000011111111111111111111111111111111111111111111111111222222222222222222222222ĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááââââââââââăăăăăăăăăăääääääääääĺĺĺĺĺĺĺĺĺĺććććććććççççççççččččččččččééééééééęęęęęęęęëëëëëëëëěěěěěěěěííííííîîîîîîîîďďďďďďďďđđđđđđńńńńńńňňňňňňňňóóóóóóôôôôôôőőőőőőőőöööööö÷÷÷÷÷÷řřřřřřůůůůůůúúúúúúűűűűűűűűüüüüüüýýýýýýţţţţţţ˙˙  !!!!!!!!!!!!""""""""""""""################$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&''''''''''''''''''(((((((((((((((((((())))))))))))))))))))))************************++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,------------------------------....................................//////////////////////////////////////0000000000000000000000000000000000000000000011111111111111111111111111111111111111111111111111222222222222222222222222222222ĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕááááááááááââââââââââââăăăăăăăăăăääääääääääĺĺĺĺĺĺĺĺććććććććććççççççççččččččččééééééééęęęęęęęęëëëëëëëëěěěěěěěěííííííííîîîîîîďďďďďďďďđđđđđđńńńńńńńńňňňňňňóóóóóóôôôôôôôôőőőőőőöööööö÷÷÷÷÷÷řřřřřřůůůůůůúúúúúúúúűűűűűűüüüüüüýýýýýýţţţţţţ˙˙  !!!!!!!!!!!!""""""""""""""################$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&''''''''''''''''''''(((((((((((((((((((())))))))))))))))))))))************************++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,------------------------------....................................//////////////////////////////////////00000000000000000000000000000000000000000000111111111111111111111111111111111111111111111111112222222222222222222222222222222222ĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕááááááááááââââââââââââăăăăăăăăăăääääääääĺĺĺĺĺĺĺĺĺĺććććććććççççççççççččččččččééééééééęęęęęęęęëëëëëëëëěěěěěěěěííííííîîîîîîîîďďďďďďđđđđđđđđńńńńńńňňňňňňóóóóóóóóôôôôôôőőőőőőöööööö÷÷÷÷÷÷řřřřřřřřůůůůůůúúúúúúűűűűűűüüüüüüýýýýýýţţţţţţ˙˙  !!!!!!!!!!!!""""""""""""""##############$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&''''''''''''''''''(((((((((((((((((((())))))))))))))))))))))************************++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,------------------------------..................................////////////////////////////////////////000000000000000000000000000000000000000000111111111111111111111111111111111111111111111111112222222222222222222222222222222222222222ĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕááááááááááââââââââââăăăăăăăăăăääääääääääĺĺĺĺĺĺĺĺĺĺććććććććççççççççččččččččččééééééééęęęęęęęęëëëëëëěěěěěěěěííííííííîîîîîîďďďďďďďďđđđđđđńńńńńńńńňňňňňňóóóóóóôôôôôôőőőőőőöööööööö÷÷÷÷÷÷řřřřřřůůůůůůúúúúúúűűűűűűüüüüüüýýýýýýţţţţţţ˙˙  !!!!!!!!!!!!""""""""""""""##############$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&''''''''''''''''''''(((((((((((((((((((())))))))))))))))))))************************++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,------------------------------..................................//////////////////////////////////////000000000000000000000000000000000000000000001111111111111111111111111111111111111111111111112222222222222222222222222222222222222222222222ĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕááááááááááââââââââââăăăăăăăăăăääääääääääĺĺĺĺĺĺĺĺććććććććććççççççççččččččččééééééééęęęęęęęęëëëëëëëëěěěěěěěěííííííîîîîîîîîďďďďďďđđđđđđđđńńńńńńňňňňňňóóóóóóôôôôôôôôőőőőőőöööööö÷÷÷÷÷÷řřřřřřůůůůůůúúúúúúűűűűűűüüüüüüýýýýýýţţţţţţ˙˙  !!!!!!!!!!!!""""""""""""""##############$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&''''''''''''''''''(((((((((((((((((((())))))))))))))))))))))**********************++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,------------------------------..................................//////////////////////////////////////0000000000000000000000000000000000000000001111111111111111111111111111111111111111111111112222222222222222222222222222222222222222222222222222ĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕááááááááááââââââââââăăăăăăăăăăääääääääääĺĺĺĺĺĺĺĺććććććććççççççççççččččččččééééééééęęęęęęęęëëëëëëěěěěěěěěííííííííîîîîîîďďďďďďďďđđđđđđńńńńńńňňňňňňóóóóóóóóôôôôôôőőőőőőöööööö÷÷÷÷÷÷řřřřřřůůůůůůúúúúúúűűűűűűüüüüüüýýýýýýţţţţţţ˙˙  !!!!!!!!!!!!""""""""""""""##############$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&''''''''''''''''''(((((((((((((((((())))))))))))))))))))))************************++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,------------------------------..................................////////////////////////////////////0000000000000000000000000000000000000000000011111111111111111111111111111111111111111111111122222222222222222222222222222222222222222222222222222233ĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕááááááááááââââââââââăăăăăăăăăăääääääääĺĺĺĺĺĺĺĺĺĺććććććććççççççççččččččččééééééééęęęęęęęęëëëëëëëëěěěěěěěěííííííîîîîîîîîďďďďďďđđđđđđńńńńńńńńňňňňňňóóóóóóôôôôôôőőőőőőöööööö÷÷÷÷÷÷řřřřřřůůůůůůúúúúúúűűűűűűüüüüüüýýýýýýţţţţţţ˙˙  !!!!!!!!!!!!""""""""""""##############$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&''''''''''''''''''(((((((((((((((((((())))))))))))))))))))************************++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,------------------------------................................//////////////////////////////////////00000000000000000000000000000000000000000011111111111111111111111111111111111111111111111122222222222222222222222222222222222222222222222222222233333333ĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕááááááááááááââââââââââăăăăăăăăăăääääääääĺĺĺĺĺĺĺĺĺĺććććććććççççççççččččččččééééééééęęęęęęęęëëëëëëěěěěěěěěííííííííîîîîîîďďďďďďđđđđđđđđńńńńńńňňňňňňóóóóóóôôôôôôőőőőőőöööööö÷÷÷÷÷÷řřřřřřůůůůůůúúúúúúűűűűűűüüüüüüýýýýýýţţţţţţ˙˙  !!!!!!!!!!!!""""""""""""##############$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&''''''''''''''''''(((((((((((((((((())))))))))))))))))))))**********************++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,------------------------------................................//////////////////////////////////////000000000000000000000000000000000000000011111111111111111111111111111111111111111111111122222222222222222222222222222222222222222222222222222233333333333333ĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕááááááááááááââââââââââăăăăăăăăääääääääääĺĺĺĺĺĺĺĺććććććććććççççççççččččččččééééééééęęęęęęëëëëëëëëěěěěěěííííííííîîîîîîďďďďďďďďđđđđđđńńńńńńňňňňňňóóóóóóôôôôôôôôőőőőőőöööööö÷÷÷÷÷÷řřřřůůůůůůúúúúúúűűűűűűüüüüüüýýýýýýţţţţţţ˙˙  !!!!!!!!!!!!""""""""""""##############$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&''''''''''''''''''(((((((((((((((((((())))))))))))))))))))**********************++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,------------------------------................................////////////////////////////////////000000000000000000000000000000000000000000111111111111111111111111111111111111111111111122222222222222222222222222222222222222222222222222222233333333333333333333ĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕááááááááááââââââââââăăăăăăăăăăääääääääääĺĺĺĺĺĺĺĺććććććććççççççççččččččččééééééééęęęęęęęęëëëëëëěěěěěěěěííííííîîîîîîîîďďďďďďđđđđđđńńńńńńńńňňňňňňóóóóóóôôôôôôőőőőőőöööööö÷÷÷÷÷÷řřřřřřůůůůůůúúúúűűűűűűüüüüüüýýýýýýţţţţţţ˙˙  !!!!!!!!!!!!""""""""""""##############$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&''''''''''''''''''(((((((((((((((((())))))))))))))))))))**********************++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,----------------------------..................................////////////////////////////////////0000000000000000000000000000000000000000111111111111111111111111111111111111111111111122222222222222222222222222222222222222222222222222222233333333333333333333333333ËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕááááááááááââââââââââăăăăăăăăăăääääääääĺĺĺĺĺĺĺĺĺĺććććććććççççççççččččččččééééééééęęęęęęëëëëëëëëěěěěěěííííííííîîîîîîďďďďďďďďđđđđđđńńńńńńňňňňňňóóóóóóôôôôôôőőőőőőöööööö÷÷÷÷÷÷řřřřřřůůůůůůúúúúúúűűűűüüüüüüýýýýýýţţţţţţ˙˙  !!!!!!!!!!!!!!""""""""""""##############$$$$$$$$$$$$$$%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&''''''''''''''''(((((((((((((((((((())))))))))))))))))))**********************++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,----------------------------................................////////////////////////////////////0000000000000000000000000000000000000000001111111111111111111111111111111111111111111111222222222222222222222222222222222222222222222222222233333333333333333333333333333333ËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕááááááááááââââââââââăăăăăăăăăăääääääääĺĺĺĺĺĺĺĺććććććććććççççççççččččččééééééééęęęęęęęęëëëëëëěěěěěěěěííííííîîîîîîîîďďďďďďđđđđđđńńńńńńňňňňňňóóóóóóôôôôôôőőőőőőöööööö÷÷÷÷÷÷řřřřřřůůůůůůúúúúúúűűűűüüüüüüýýýýýýţţţţţţ˙˙  !!!!!!!!!!!!""""""""""""""############$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&''''''''''''''''''(((((((((((((((((())))))))))))))))))))**********************++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,----------------------------................................////////////////////////////////////00000000000000000000000000000000000000001111111111111111111111111111111111111111111111222222222222222222222222222222222222222222222222222233333333333333333333333333333333333333ËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕááááááááááââââââââââăăăăăăăăääääääääääĺĺĺĺĺĺĺĺććććććććççççççççččččččččééééééééęęęęęęëëëëëëëëěěěěěěííííííííîîîîîîďďďďďďđđđđđđńńńńńńńńňňňňňňóóóóóóôôôôôôőőőőőőöööö÷÷÷÷÷÷řřřřřřůůůůůůúúúúúúűűűűüüüüüüýýýýýýţţţţţţ˙˙  !!!!!!!!!!!!""""""""""""""############$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&''''''''''''''''(((((((((((((((((())))))))))))))))))))**********************++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,----------------------------................................////////////////////////////////////000000000000000000000000000000000000001111111111111111111111111111111111111111111111222222222222222222222222222222222222222222222222222233333333333333333333333333333333333333333333ËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕááááááááááââââââââââăăăăăăăăääääääääääĺĺĺĺĺĺĺĺććććććććççççççççččččččččééééééęęęęęęęęëëëëëëěěěěěěěěííííííîîîîîîîîďďďďďďđđđđđđńńńńńńňňňňňňóóóóóóôôôôôôőőőőőőöööööö÷÷÷÷řřřřřřůůůůůůúúúúúúűűűűűűüüüüýýýýýýţţţţţţ˙˙  !!!!!!!!!!!!""""""""""""##############$$$$$$$$$$$$$$%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&''''''''''''''''''(((((((((((((((((())))))))))))))))))))********************++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,----------------------------................................//////////////////////////////////000000000000000000000000000000000000000011111111111111111111111111111111111111111111222222222222222222222222222222222222222222222222222233333333333333333333333333333333333333333333333333ËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕááááááááááââââââââââăăăăăăăăääääääääĺĺĺĺĺĺĺĺĺĺććććććććççççççççččččččééééééééęęęęęęęęëëëëëëěěěěěěííííííííîîîîîîďďďďďďđđđđđđńńńńńńňňňňňňóóóóóóôôôôôôőőőőőőöööööö÷÷÷÷÷÷řřřřůůůůůůúúúúúúűűűűűűüüüüýýýýýýţţţţţţ˙˙  !!!!!!!!!!!!""""""""""""##############$$$$$$$$$$$$$$%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&''''''''''''''''(((((((((((((((((())))))))))))))))))))**********************++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,----------------------------..............................////////////////////////////////////0000000000000000000000000000000000000011111111111111111111111111111111111111111111112222222222222222222222222222222222222222222222222233333333333333333333333333333333333333333333333333333333ËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕááááááááááââââââââăăăăăăăăăăääääääääĺĺĺĺĺĺĺĺććććććććççççççççččččččččééééééééęęęęęęëëëëëëëëěěěěěěííííííîîîîîîďďďďďďđđđđđđđđńńńńńńňňňňňňóóóóôôôôôôőőőőőőöööööö÷÷÷÷÷÷řřřřřřůůůůúúúúúúűűűűűűüüüüýýýýýýţţţţţţ˙˙  !!!!!!!!!!!!""""""""""""##############$$$$$$$$$$$$%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&''''''''''''''''''(((((((((((((((((())))))))))))))))))**********************++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,----------------------------..............................//////////////////////////////////0000000000000000000000000000000000000000111111111111111111111111111111111111111111112222222222222222222222222222222222222222222222222233333333333333333333333333333333333333333333333333333333334444ËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕááááááááááââââââââăăăăăăăăăăääääääääĺĺĺĺĺĺĺĺććććććććççççççççččččččččééééééęęęęęęęęëëëëëëěěěěěěííííííííîîîîîîďďďďďďđđđđđđńńńńńńňňňňňňóóóóóóôôôôôôőőőőöööööö÷÷÷÷÷÷řřřřřřůůůůúúúúúúűűűűűűüüüüýýýýýýţţţţţţ˙˙  !!!!!!!!!!!!""""""""""""############$$$$$$$$$$$$$$%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&''''''''''''''''(((((((((((((((((())))))))))))))))))))********************++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,----------------------------..............................//////////////////////////////////00000000000000000000000000000000000000111111111111111111111111111111111111111111112222222222222222222222222222222222222222222222222233333333333333333333333333333333333333333333333333333333334444444444ËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕááááááááââââââââââăăăăăăăăääääääääääĺĺĺĺĺĺĺĺććććććććççççççččččččččééééééééęęęęęęëëëëëëëëěěěěěěííííííîîîîîîďďďďďďđđđđđđńńńńńńňňňňňňóóóóóóôôôôôôőőőőőőöööö÷÷÷÷÷÷řřřřřřůůůůůůúúúúűűűűűűüüüüüüýýýýţţţţţţ˙˙  !!!!!!!!!!!!""""""""""""############$$$$$$$$$$$$$$%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&''''''''''''''''(((((((((((((((((())))))))))))))))))********************++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,----------------------------..............................//////////////////////////////////000000000000000000000000000000000000001111111111111111111111111111111111111111112222222222222222222222222222222222222222222222222233333333333333333333333333333333333333333333333333333333334444444444444444ËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕááááááááââââââââââăăăăăăăăääääääääĺĺĺĺĺĺĺĺććććććććççççççççččččččččééééééęęęęęęęęëëëëëëěěěěěěííííííííîîîîîîďďďďďďđđđđđđńńńńńńňňňňóóóóóóôôôôôôőőőőőőöööööö÷÷÷÷řřřřřřůůůůůůúúúúűűűűűűüüüüüüýýýýţţţţţţ˙˙  !!!!!!!!!!!!""""""""""""############$$$$$$$$$$$$$$%%%%%%%%%%%%%%&&&&&&&&&&&&&&''''''''''''''''(((((((((((((((((())))))))))))))))))**********************++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,----------------------------..............................////////////////////////////////000000000000000000000000000000000000001111111111111111111111111111111111111111112222222222222222222222222222222222222222222222222233333333333333333333333333333333333333333333333333333333444444444444444444444444ËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕááááááááââââââââââăăăăăăăăääääääääĺĺĺĺĺĺĺĺććććććććççççççççččččččééééééééęęęęęęëëëëëëëëěěěěěěííííííîîîîîîďďďďďďđđđđđđńńńńńńňňňňňňóóóóóóôôôôőőőőőőöööööö÷÷÷÷÷÷řřřřůůůůůůúúúúűűűűűűüüüüüüýýýýţţţţţţ˙˙  !!!!!!!!!!!!""""""""""""############$$$$$$$$$$$$%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&''''''''''''''''(((((((((((((((())))))))))))))))))))********************++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,--------------------------..............................//////////////////////////////////0000000000000000000000000000000000001111111111111111111111111111111111111111111122222222222222222222222222222222222222222222222233333333333333333333333333333333333333333333333333333333444444444444444444444444444444ĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßŕŕŕŕŕŕŕŕááááááááááââââââââââăăăăăăăăääääääääĺĺĺĺĺĺĺĺććććććććççççççččččččččééééééęęęęęęęęëëëëëëěěěěěěííííííîîîîîîďďďďďďđđđđđđńńńńńńňňňňňňóóóóóóôôôôôôőőőőöööööö÷÷÷÷÷÷řřřřůůůůůůúúúúúúűűűűüüüüüüýýýýţţţţţţ˙˙  !!!!!!!!!!!!""""""""""############$$$$$$$$$$$$$$%%%%%%%%%%%%%%&&&&&&&&&&&&&&''''''''''''''''(((((((((((((((((())))))))))))))))))********************++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,--------------------------..............................////////////////////////////////0000000000000000000000000000000000000011111111111111111111111111111111111111111122222222222222222222222222222222222222222222222233333333333333333333333333333333333333333333333333333333444444444444444444444444444444444444ĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßŕŕŕŕŕŕŕŕááááááááááââââââââăăăăăăăăăăääääääääĺĺĺĺĺĺĺĺććććććççççççççččččččččééééééęęęęęęëëëëëëëëěěěěěěííííííîîîîîîďďďďďďđđđđđđńńńńńńňňňňóóóóóóôôôôôôőőőőőőöööö÷÷÷÷÷÷řřřřůůůůůůúúúúúúűűűűüüüüüüýýýýţţţţţţ˙˙  !!!!!!!!!!""""""""""""############$$$$$$$$$$$$$$%%%%%%%%%%%%%%&&&&&&&&&&&&&&''''''''''''''''(((((((((((((((())))))))))))))))))********************++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,--------------------------..............................////////////////////////////////00000000000000000000000000000000000011111111111111111111111111111111111111111122222222222222222222222222222222222222222222222233333333333333333333333333333333333333333333333333333344444444444444444444444444444444444444444444ĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßŕŕŕŕŕŕŕŕááááááááááââââââââăăăăăăăăääääääääĺĺĺĺĺĺĺĺććććććććççççççççččččččééééééééęęęęęęëëëëëëěěěěěěííííííîîîîîîďďďďďďđđđđđđńńńńńńňňňňňňóóóóôôôôôôőőőőőőöööö÷÷÷÷÷÷řřřřřřůůůůúúúúúúűűűűüüüüüüýýýýţţţţţţ˙˙  !!!!!!!!!!""""""""""""############$$$$$$$$$$$$%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&''''''''''''''(((((((((((((((((())))))))))))))))))********************++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,--------------------------..............................////////////////////////////////000000000000000000000000000000000000111111111111111111111111111111111111111122222222222222222222222222222222222222222222222233333333333333333333333333333333333333333333333333333344444444444444444444444444444444444444444444444444ĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßŕŕŕŕŕŕŕŕááááááááááââââââââăăăăăăăăääääääääĺĺĺĺĺĺĺĺććććććććççççççččččččččééééééęęęęęęëëëëëëëëěěěěěěííííííîîîîîîďďďďďďđđđđńńńńńńňňňňňňóóóóóóôôôôőőőőőőöööööö÷÷÷÷řřřřřřůůůůúúúúúúűűűűüüüüüüýýýýţţţţţţ˙˙  !!!!!!!!!!""""""""""""############$$$$$$$$$$$$%%%%%%%%%%%%%%&&&&&&&&&&&&&&''''''''''''''''(((((((((((((((())))))))))))))))))********************++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,--------------------------............................////////////////////////////////000000000000000000000000000000000000111111111111111111111111111111111111111122222222222222222222222222222222222222222222222233333333333333333333333333333333333333333333333333333344444444444444444444444444444444444444444444444444444444ĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßŕŕŕŕŕŕŕŕááááááááááââââââââăăăăăăăăääääääääĺĺĺĺĺĺĺĺććććććççççççççččččččééééééééęęęęęęëëëëëëěěěěěěííííííîîîîîîďďďďďďđđđđđđńńńńńńňňňňóóóóóóôôôôôôőőőőöööööö÷÷÷÷řřřřřřůůůůúúúúúúűűűűüüüüüüýýýýţţţţţţ˙˙  !!!!!!!!!!""""""""""""############$$$$$$$$$$$$%%%%%%%%%%%%%%&&&&&&&&&&&&&&''''''''''''''(((((((((((((((((())))))))))))))))********************++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,--------------------------............................////////////////////////////////0000000000000000000000000000000000001111111111111111111111111111111111111111222222222222222222222222222222222222222222222233333333333333333333333333333333333333333333333333333344444444444444444444444444444444444444444444444444444444444444ĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢßßßßßßßßŕŕŕŕŕŕŕŕŕŕááááááááááââââââââăăăăăăăăääääääääĺĺĺĺĺĺććććććććççççççççččččččééééééęęęęęęęęëëëëëëěěěěěěííííííîîîîîîďďďďđđđđđđńńńńńńňňňňňňóóóóôôôôôôőőőőőőöööö÷÷÷÷÷÷řřřřůůůůůůúúúúűűűűűűüüüüýýýýţţţţţţ˙˙  !!!!!!!!!!""""""""""""##########$$$$$$$$$$$$$$%%%%%%%%%%%%&&&&&&&&&&&&&&''''''''''''''''(((((((((((((((())))))))))))))))))******************++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,--------------------------............................//////////////////////////////0000000000000000000000000000000000001111111111111111111111111111111111111111222222222222222222222222222222222222222222222233333333333333333333333333333333333333333333333333334444444444444444444444444444444444444444444444444444444444444444555555ĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢßßßßßßßßŕŕŕŕŕŕŕŕŕŕááááááááââââââââââăăăăăăăăääääääĺĺĺĺĺĺĺĺććććććććççççççččččččččééééééęęęęęęëëëëëëěěěěěěííííííîîîîîîďďďďďďđđđđđđńńńńňňňňňňóóóóóóôôôôőőőőőőöööö÷÷÷÷÷÷řřřřůůůůůůúúúúűűűűűűüüüüýýýýţţţţţţ˙˙  !!!!!!!!!!""""""""""############$$$$$$$$$$$$%%%%%%%%%%%%%%&&&&&&&&&&&&&&''''''''''''''(((((((((((((((())))))))))))))))))********************++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,--------------------------............................//////////////////////////////00000000000000000000000000000000001111111111111111111111111111111111111111222222222222222222222222222222222222222222222233333333333333333333333333333333333333333333333333334444444444444444444444444444444444444444444444444444444444444455555555555555ĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢßßßßßßßßŕŕŕŕŕŕŕŕŕŕááááááááââââââââăăăăăăăăääääääääĺĺĺĺĺĺĺĺććććććççççççççččččččééééééęęęęęęęęëëëëëëěěěěěěííííííîîîîďďďďďďđđđđđđńńńńńńňňňňóóóóóóôôôôőőőőőőöööö÷÷÷÷÷÷řřřřůůůůůůúúúúűűűűűűüüüüýýýýţţţţţţ˙˙  !!!!!!!!!!""""""""""############$$$$$$$$$$$$%%%%%%%%%%%%%%&&&&&&&&&&&&''''''''''''''''(((((((((((((((())))))))))))))))))******************++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,--------------------------..........................////////////////////////////////000000000000000000000000000000000011111111111111111111111111111111111111222222222222222222222222222222222222222222222233333333333333333333333333333333333333333333333333334444444444444444444444444444444444444444444444444444444444445555555555555555555555ĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢßßßßßßßßŕŕŕŕŕŕŕŕŕŕááááááááââââââââăăăăăăăăääääääääĺĺĺĺĺĺĺĺććććććççççççççččččččééééééęęęęęęëëëëëëěěěěěěííííííîîîîîîďďďďďďđđđđńńńńńńňňňňňňóóóóôôôôôôőőőőöööööö÷÷÷÷řřřřřřůůůůúúúúűűűűűűüüüüýýýýýýţţţţ˙˙  !!!!!!!!!!""""""""""############$$$$$$$$$$$$%%%%%%%%%%%%&&&&&&&&&&&&&&''''''''''''''(((((((((((((((())))))))))))))))))******************++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,--------------------------..........................//////////////////////////////000000000000000000000000000000000011111111111111111111111111111111111111112222222222222222222222222222222222222222222233333333333333333333333333333333333333333333333333334444444444444444444444444444444444444444444444444444444444445555555555555555555555555555ÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢßßßßßßßßŕŕŕŕŕŕŕŕŕŕááááááááââââââââăăăăăăăăääääääääĺĺĺĺĺĺććććććććççççççččččččééééééééęęęęęęëëëëëëěěěěěěííííîîîîîîďďďďďďđđđđđđńńńńňňňňňňóóóóôôôôôôőőőőöööööö÷÷÷÷řřřřřřůůůůúúúúúúűűűűüüüüýýýýýýţţţţ˙˙  !!!!!!!!!!""""""""""############$$$$$$$$$$$$%%%%%%%%%%%%&&&&&&&&&&&&&&''''''''''''''(((((((((((((((())))))))))))))))******************++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,------------------------............................//////////////////////////////0000000000000000000000000000000000111111111111111111111111111111111111112222222222222222222222222222222222222222222233333333333333333333333333333333333333333333333333444444444444444444444444444444444444444444444444444444444444555555555555555555555555555555555555ÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢßßßßßßßßŕŕŕŕŕŕŕŕŕŕááááááááââââââââăăăăăăăăääääääĺĺĺĺĺĺĺĺććććććććççççççččččččééééééęęęęęęëëëëëëěěěěěěííííííîîîîîîďďďďđđđđđđńńńńńńňňňňóóóóóóôôôôőőőőőőöööö÷÷÷÷÷÷řřřřůůůůúúúúúúűűűűüüüüýýýýýýţţţţ˙˙  !!!!!!!!!!""""""""""##########$$$$$$$$$$$$%%%%%%%%%%%%%%&&&&&&&&&&&&''''''''''''''(((((((((((((((())))))))))))))))))******************++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,------------------------............................////////////////////////////0000000000000000000000000000000000111111111111111111111111111111111111112222222222222222222222222222222222222222222233333333333333333333333333333333333333333333333333444444444444444444444444444444444444444444444444444444444444555555555555555555555555555555555555555555ÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢßßßßßßßßŕŕŕŕŕŕŕŕŕŕááááááááââââââââăăăăăăăăääääääĺĺĺĺĺĺĺĺććććććççççççččččččččééééééęęęęęęëëëëëëěěěěěěííííîîîîîîďďďďďďđđđđńńńńńńňňňňňňóóóóôôôôőőőőőőöööö÷÷÷÷÷÷řřřřůůůůúúúúúúűűűűüüüüýýýýýýţţţţ˙˙  !!!!!!!!""""""""""""##########$$$$$$$$$$$$%%%%%%%%%%%%&&&&&&&&&&&&&&''''''''''''''(((((((((((((((())))))))))))))))******************++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,------------------------..........................//////////////////////////////00000000000000000000000000000000111111111111111111111111111111111111112222222222222222222222222222222222222222222233333333333333333333333333333333333333333333333333444444444444444444444444444444444444444444444444444444444455555555555555555555555555555555555555555555555555ÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢßßßßßßßßŕŕŕŕŕŕŕŕŕŕááááááááââââââââăăăăăăääääääääĺĺĺĺĺĺĺĺććććććççççççččččččééééééęęęęęęëëëëëëěěěěěěííííííîîîîîîďďďďđđđđđđńńńńňňňňňňóóóóôôôôôôőőőőöööööö÷÷÷÷řřřřůůůůůůúúúúűűűűüüüüýýýýýýţţţţ˙˙  !!!!!!!!""""""""""""##########$$$$$$$$$$$$%%%%%%%%%%%%&&&&&&&&&&&&''''''''''''''(((((((((((((((())))))))))))))))******************++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,------------------------..........................//////////////////////////////000000000000000000000000000000001111111111111111111111111111111111112222222222222222222222222222222222222222222233333333333333333333333333333333333333333333333344444444444444444444444444444444444444444444444444444444445555555555555555555555555555555555555555555555555555555555ÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢßßßßßßßßŕŕŕŕŕŕŕŕááááááááááââââââăăăăăăăăääääääääĺĺĺĺĺĺććććććććççççççččččččééééééęęęęęęëëëëëëěěěěěěííííîîîîîîďďďďďďđđđđńńńńńńňňňňóóóóóóôôôôőőőőöööööö÷÷÷÷řřřřůůůůůůúúúúűűűűüüüüýýýýýýţţţţ˙˙  !!!!!!!!!!""""""""""############$$$$$$$$$$%%%%%%%%%%%%%%&&&&&&&&&&&&''''''''''''''(((((((((((((())))))))))))))))))****************++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,------------------------..........................////////////////////////////000000000000000000000000000000001111111111111111111111111111111111111122222222222222222222222222222222222222222233333333333333333333333333333333333333333333333344444444444444444444444444444444444444444444444444444444445555555555555555555555555555555555555555555555555555555555555555ÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢßßßßßßßßŕŕŕŕŕŕŕŕááááááááââââââââăăăăăăăăääääääääĺĺĺĺĺĺććććććççççççččččččččééééééęęęęëëëëëëěěěěěěííííííîîîîďďďďďďđđđđđđńńńńňňňňóóóóóóôôôôőőőőőőöööö÷÷÷÷řřřřřřůůůůúúúúűűűűüüüüýýýýýýţţţţ˙˙  !!!!!!!!!!""""""""""############$$$$$$$$$$%%%%%%%%%%%%&&&&&&&&&&&&&&''''''''''''(((((((((((((((())))))))))))))))******************++++++++++++++++++,,,,,,,,,,,,,,,,,,,,------------------------..........................////////////////////////////0000000000000000000000000000000011111111111111111111111111111111111122222222222222222222222222222222222222222233333333333333333333333333333333333333333333333344444444444444444444444444444444444444444444444444444444555555555555555555555555555555555555555555555555555555555555555555556666ÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢßßßßßßßßŕŕŕŕŕŕŕŕááááááááââââââââăăăăăăăăääääääĺĺĺĺĺĺĺĺććććććççççççččččččééééééęęęęęęëëëëëëěěěěěěííííîîîîîîďďďďđđđđđđńńńńňňňňňňóóóóôôôôôôőőőőöööö÷÷÷÷řřřřřřůůůůúúúúűűűűüüüüüüýýýýţţţţ˙˙  !!!!!!!!!!""""""""""##########$$$$$$$$$$$$%%%%%%%%%%%%&&&&&&&&&&&&''''''''''''''(((((((((((((())))))))))))))))******************++++++++++++++++++,,,,,,,,,,,,,,,,,,,,------------------------........................////////////////////////////0000000000000000000000000000000011111111111111111111111111111111111122222222222222222222222222222222222222222233333333333333333333333333333333333333333333333344444444444444444444444444444444444444444444444444444444555555555555555555555555555555555555555555555555555555555555555555666666666666ÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢßßßßßßßßŕŕŕŕŕŕŕŕááááááááââââââââăăăăăăăăääääääĺĺĺĺĺĺććććććććççççççččččččééééééęęęęęęëëëëěěěěěěííííííîîîîďďďďďďđđđđńńńńńńňňňňóóóóôôôôôôőőőőöööö÷÷÷÷÷÷řřřřůůůůúúúúűűűűüüüüüüýýýýţţţţ˙˙  !!!!!!!!!!""""""""""##########$$$$$$$$$$$$%%%%%%%%%%%%&&&&&&&&&&&&''''''''''''''(((((((((((((())))))))))))))))****************++++++++++++++++++,,,,,,,,,,,,,,,,,,,,------------------------........................////////////////////////////00000000000000000000000000000000111111111111111111111111111111111122222222222222222222222222222222222222222233333333333333333333333333333333333333333333334444444444444444444444444444444444444444444444444444444455555555555555555555555555555555555555555555555555555555555555555566666666666666666666ÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢßßßßßßßßŕŕŕŕŕŕŕŕááááááááââââââââăăăăăăääääääääĺĺĺĺĺĺććććććççççççččččččééééééęęęęęęëëëëëëěěěěííííííîîîîîîďďďďđđđđđđńńńńňňňňóóóóóóôôôôőőőőöööööö÷÷÷÷řřřřůůůůúúúúűűűűűűüüüüýýýýţţţţ˙˙  !!!!!!!!!!""""""""""##########$$$$$$$$$$%%%%%%%%%%%%&&&&&&&&&&&&''''''''''''''(((((((((((((())))))))))))))))****************++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,----------------------........................////////////////////////////000000000000000000000000000000111111111111111111111111111111111111222222222222222222222222222222222222222233333333333333333333333333333333333333333333334444444444444444444444444444444444444444444444444444445555555555555555555555555555555555555555555555555555555555555555556666666666666666666666666666ÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢßßßßßßßßŕŕŕŕŕŕŕŕááááááááââââââââăăăăăăääääääääĺĺĺĺĺĺććććććççççççččččččééééééęęęęęęëëëëěěěěěěííííííîîîîďďďďďďđđđđńńńńňňňňňňóóóóôôôôőőőőőőöööö÷÷÷÷řřřřůůůůúúúúűűűűűűüüüüýýýýţţţţ˙˙  !!!!!!!!!!""""""""""##########$$$$$$$$$$%%%%%%%%%%%%&&&&&&&&&&&&''''''''''''''(((((((((((((())))))))))))))******************++++++++++++++++++,,,,,,,,,,,,,,,,,,,,--------------------..........................//////////////////////////000000000000000000000000000000111111111111111111111111111111111111222222222222222222222222222222222222222233333333333333333333333333333333333333333333334444444444444444444444444444444444444444444444444444445555555555555555555555555555555555555555555555555555555555555555666666666666666666666666666666666666ČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢßßßßßßßßŕŕŕŕŕŕŕŕááááááááââââââăăăăăăăăääääääĺĺĺĺĺĺĺĺććććććççççççččččččééééęęęęęęëëëëëëěěěěííííííîîîîîîďďďďđđđđńńńńńńňňňňóóóóôôôôôôőőőőöööö÷÷÷÷řřřřůůůůúúúúúúűűűűüüüüýýýýţţţţ˙˙  !!!!!!!!!!""""""""##########$$$$$$$$$$$$%%%%%%%%%%&&&&&&&&&&&&&&''''''''''''(((((((((((((())))))))))))))))****************++++++++++++++++++,,,,,,,,,,,,,,,,,,,,--------------------........................////////////////////////////0000000000000000000000000000001111111111111111111111111111111111222222222222222222222222222222222222222233333333333333333333333333333333333333333333444444444444444444444444444444444444444444444444444444555555555555555555555555555555555555555555555555555555555555555566666666666666666666666666666666666666666666ČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢßßßßßßßßŕŕŕŕŕŕŕŕááááááááââââââăăăăăăăăääääääĺĺĺĺĺĺććććććççççççččččččééééééęęęęęęëëëëěěěěěěííííîîîîîîďďďďđđđđđđńńńńňňňňóóóóóóôôôôőőőőöööö÷÷÷÷řřřřůůůůůůúúúúűűűűüüüüýýýýţţţţ˙˙  !!!!!!!!!!""""""""##########$$$$$$$$$$$$%%%%%%%%%%&&&&&&&&&&&&''''''''''''''(((((((((((())))))))))))))))****************++++++++++++++++++,,,,,,,,,,,,,,,,,,,,--------------------........................//////////////////////////0000000000000000000000000000001111111111111111111111111111111111222222222222222222222222222222222222222233333333333333333333333333333333333333333333444444444444444444444444444444444444444444444444444444555555555555555555555555555555555555555555555555555555555555556666666666666666666666666666666666666666666666666666ČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢßßßßßßßßŕŕŕŕŕŕŕŕááááááááââââââăăăăăăăăääääääĺĺĺĺĺĺććććććççççççččččččééééééęęęęëëëëëëěěěěííííííîîîîďďďďďďđđđđńńńńňňňňňňóóóóôôôôőőőőöööö÷÷÷÷÷÷řřřřůůůůúúúúűűűűüüüüýýýýţţţţ˙˙  !!!!!!!!!!""""""""##########$$$$$$$$$$%%%%%%%%%%%%&&&&&&&&&&&&''''''''''''(((((((((((((())))))))))))))****************++++++++++++++++++,,,,,,,,,,,,,,,,,,,,--------------------........................//////////////////////////00000000000000000000000000000011111111111111111111111111111111222222222222222222222222222222222222222233333333333333333333333333333333333333333333444444444444444444444444444444444444444444444444444455555555555555555555555555555555555555555555555555555555555555666666666666666666666666666666666666666666666666666666666666ČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢßßßßßßßßŕŕŕŕŕŕŕŕááááááââââââââăăăăăăääääääääĺĺĺĺĺĺććććććççççççččččééééééęęęęęęëëëëěěěěěěííííîîîîîîďďďďđđđđńńńńńńňňňňóóóóôôôôőőőőöööööö÷÷÷÷řřřřůůůůúúúúűűűűüüüüýýýýţţţţ˙˙  !!!!!!!!""""""""""##########$$$$$$$$$$%%%%%%%%%%&&&&&&&&&&&&''''''''''''''(((((((((((())))))))))))))))****************++++++++++++++++,,,,,,,,,,,,,,,,,,,,--------------------........................//////////////////////////000000000000000000000000000011111111111111111111111111111111112222222222222222222222222222222222222233333333333333333333333333333333333333333333444444444444444444444444444444444444444444444444445555555555555555555555555555555555555555555555555555555555555566666666666666666666666666666666666666666666666666666666666666666666ČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢßßßßßßßßŕŕŕŕŕŕŕŕááááááââââââââăăăăăăääääääĺĺĺĺĺĺććććććççççççččččččééééééęęęęëëëëëëěěěěííííííîîîîďďďďďďđđđđńńńńňňňňóóóóôôôôôôőőőőöööö÷÷÷÷řřřřůůůůúúúúűűűűüüüüýýýýţţţţ˙˙  !!!!!!!!""""""""""##########$$$$$$$$$$%%%%%%%%%%&&&&&&&&&&&&''''''''''''(((((((((((((())))))))))))))****************++++++++++++++++,,,,,,,,,,,,,,,,,,,,--------------------......................//////////////////////////000000000000000000000000000000111111111111111111111111111111112222222222222222222222222222222222222233333333333333333333333333333333333333333344444444444444444444444444444444444444444444444444445555555555555555555555555555555555555555555555555555555555556666666666666666666666666666666666666666666666666666666666666666666666666666ČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢßßßßßßßßŕŕŕŕŕŕŕŕááááááââââââââăăăăăăääääääĺĺĺĺĺĺććććććççççççččččččééééęęęęęęëëëëěěěěěěííííîîîîîîďďďďđđđđńńńńňňňňňňóóóóôôôôőőőőöööö÷÷÷÷řřřřůůůůúúúúűűűűüüüüýýýýţţţţ˙˙  !!!!!!!!""""""""""########$$$$$$$$$$%%%%%%%%%%%%&&&&&&&&&&''''''''''''''(((((((((((())))))))))))))****************++++++++++++++++,,,,,,,,,,,,,,,,,,,,--------------------......................//////////////////////////0000000000000000000000000000111111111111111111111111111111112222222222222222222222222222222222222233333333333333333333333333333333333333333344444444444444444444444444444444444444444444444444555555555555555555555555555555555555555555555555555555555555666666666666666666666666666666666666666666666666666666666666666666666666667777777777ČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢßßßßßßßßŕŕŕŕŕŕŕŕááááááââââââăăăăăăăăääääääĺĺĺĺĺĺććććććççççççččččééééééęęęęëëëëëëěěěěííííííîîîîďďďďđđđđđđńńńńňňňňóóóóôôôôőőőőöööö÷÷÷÷řřřřůůůůúúúúűűűűüüüüýýýýţţţţ˙˙  !!!!!!!!""""""""""########$$$$$$$$$$%%%%%%%%%%&&&&&&&&&&&&''''''''''''(((((((((((((())))))))))))))**************++++++++++++++++++,,,,,,,,,,,,,,,,,,--------------------......................////////////////////////0000000000000000000000000000111111111111111111111111111111112222222222222222222222222222222222222233333333333333333333333333333333333333333344444444444444444444444444444444444444444444444444555555555555555555555555555555555555555555555555555555555566666666666666666666666666666666666666666666666666666666666666666666666666777777777777777777ČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢßßßßßßßßŕŕŕŕŕŕááááááááââââââăăăăăăăăääääääĺĺĺĺĺĺććććççççççččččččééééęęęęęęëëëëěěěěěěííííîîîîďďďďďďđđđđńńńńňňňňóóóóôôôôőőőőöööö÷÷÷÷řřřřůůůůúúúúűűűűüüüüýýýýţţţţ˙˙  !!!!!!!!""""""""##########$$$$$$$$$$%%%%%%%%%%&&&&&&&&&&&&''''''''''''(((((((((((())))))))))))))****************++++++++++++++++,,,,,,,,,,,,,,,,,,--------------------......................////////////////////////00000000000000000000000000001111111111111111111111111111112222222222222222222222222222222222222233333333333333333333333333333333333333334444444444444444444444444444444444444444444444444455555555555555555555555555555555555555555555555555555555556666666666666666666666666666666666666666666666666666666666666666666666667777777777777777777777777777ČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××ŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢßßßßßßßßŕŕŕŕŕŕááááááááââââââăăăăăăääääääĺĺĺĺĺĺććććććççççççččččééééééęęęęęęëëëëěěěěííííííîîîîďďďďđđđđńńńńňňňňňňóóóóôôôôőőőőöööö÷÷÷÷řřřřůůúúúúűűűűüüüüýýýýţţţţ˙˙  !!!!!!!!""""""""##########$$$$$$$$$$%%%%%%%%%%&&&&&&&&&&''''''''''''(((((((((((())))))))))))))****************++++++++++++++++,,,,,,,,,,,,,,,,,,--------------------....................//////////////////////////000000000000000000000000001111111111111111111111111111111122222222222222222222222222222222222233333333333333333333333333333333333333334444444444444444444444444444444444444444444444445555555555555555555555555555555555555555555555555555555555666666666666666666666666666666666666666666666666666666666666666666666666777777777777777777777777777777777777ÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢßßßßßßßßŕŕŕŕŕŕááááááááââââââăăăăăăääääääĺĺĺĺĺĺććććććççççččččččééééééęęęęëëëëěěěěěěííííîîîîďďďďďďđđđđńńńńňňňňóóóóôôôôőőőőöööö÷÷÷÷řřřřůůůůúúúúűűüüüüýýýýţţţţ˙˙  !!!!!!!!""""""""##########$$$$$$$$%%%%%%%%%%&&&&&&&&&&&&''''''''''''(((((((((((())))))))))))))**************++++++++++++++++,,,,,,,,,,,,,,,,,,------------------......................////////////////////////000000000000000000000000000011111111111111111111111111111122222222222222222222222222222222223333333333333333333333333333333333333333334444444444444444444444444444444444444444444444445555555555555555555555555555555555555555555555555555555555666666666666666666666666666666666666666666666666666666666666666666667777777777777777777777777777777777777777777777ÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××ŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢßßßßßßßßŕŕŕŕŕŕááááááááââââââăăăăăăääääääĺĺĺĺĺĺććććççççççččččččééééęęęęęęëëëëěěěěííííííîîîîďďďďđđđđńńńńňňňňóóóóôôôôőőőőöööö÷÷÷÷řřřřůůůůúúúúűűüüüüýýýýţţţţ˙˙  !!!!!!!!""""""""########$$$$$$$$$$%%%%%%%%%%&&&&&&&&&&''''''''''''(((((((((((())))))))))))))**************++++++++++++++++,,,,,,,,,,,,,,,,,,------------------......................////////////////////////0000000000000000000000000011111111111111111111111111111122222222222222222222222222222222223333333333333333333333333333333333333333334444444444444444444444444444444444444444444444555555555555555555555555555555555555555555555555555555555566666666666666666666666666666666666666666666666666666666666666666666777777777777777777777777777777777777777777777777777777ÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢßßßßßßßßŕŕŕŕŕŕááááááââââââââăăăăăăääääääĺĺĺĺććććććççççççččččééééééęęęęëëëëěěěěěěííííîîîîďďďďđđđđńńńńňňňňóóóóôôôôőőőőöööö÷÷÷÷řřřřůůůůúúúúűűűűüüýýýýţţţţ˙˙  !!!!!!!!""""""""########$$$$$$$$$$%%%%%%%%%%&&&&&&&&&&''''''''''''(((((((((((())))))))))))**************++++++++++++++++,,,,,,,,,,,,,,,,,,------------------......................//////////////////////0000000000000000000000000011111111111111111111111111111122222222222222222222222222222222223333333333333333333333333333333333333333444444444444444444444444444444444444444444444444555555555555555555555555555555555555555555555555555555556666666666666666666666666666666666666666666666666666666666666666666677777777777777777777777777777777777777777777777777777777777777ÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××ŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢßßßßßßßßŕŕŕŕŕŕááááááââââââăăăăăăääääääĺĺĺĺĺĺććććććççççččččččééééęęęęęęëëëëěěěěííííîîîîîîďďďďđđđđńńńńňňňňóóóóôôôôőőőőöö÷÷÷÷řřřřůůůůúúúúűűűűüüýýýýţţţţ˙˙  !!!!!!!!""""""""########$$$$$$$$$$%%%%%%%%&&&&&&&&&&&&''''''''''(((((((((((())))))))))))))**************++++++++++++++,,,,,,,,,,,,,,,,,,------------------....................////////////////////////00000000000000000000000000111111111111111111111111111111222222222222222222222222222222223333333333333333333333333333333333333333444444444444444444444444444444444444444444444455555555555555555555555555555555555555555555555555555555666666666666666666666666666666666666666666666666666666666666666666777777777777777777777777777777777777777777777777777777777777777777777777ÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××ŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝŢŢŢŢŢŢŢŢßßßßßßßßŕŕŕŕŕŕááááááââââââăăăăăăääääääĺĺĺĺĺĺććććççççççččččččééééęęęęëëëëëëěěěěííííîîîîďďďďđđđđńńńńňňňňóóóóôôôôőőőőöööö÷÷řřřřůůůůúúúúűűűűüüýýýýţţţţ˙˙  !!!!!!""""""""##########$$$$$$$$%%%%%%%%%%&&&&&&&&&&''''''''''''(((((((((((())))))))))))**************++++++++++++++++,,,,,,,,,,,,,,,,------------------....................////////////////////////000000000000000000000000111111111111111111111111111111222222222222222222222222222222223333333333333333333333333333333333333333444444444444444444444444444444444444444444445555555555555555555555555555555555555555555555555555555566666666666666666666666666666666666666666666666666666666666666666677777777777777777777777777777777777777777777777777777777777777777777777777777777ÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××ŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÝÝÝÝÝÝŢŢŢŢŢŢŢŢßßßßßßßßŕŕŕŕŕŕááááááââââââăăăăăăääääääĺĺĺĺĺĺććććççççççččččééééęęęęęęëëëëěěěěííííîîîîďďďďđđđđńńńńňňňňóóóóôôôôőőőőöööö÷÷÷÷řřůůůůúúúúűűűűüüüüýýţţţţ˙˙  !!!!!!""""""""##########$$$$$$$$%%%%%%%%%%&&&&&&&&&&''''''''''(((((((((((())))))))))))**************++++++++++++++++,,,,,,,,,,,,,,,,------------------....................//////////////////////000000000000000000000000001111111111111111111111111111222222222222222222222222222222223333333333333333333333333333333333333344444444444444444444444444444444444444444444445555555555555555555555555555555555555555555555555555556666666666666666666666666666666666666666666666666666666666666666777777777777777777777777777777777777777777777777777777777777777777777777777777777788888888ÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××ŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝŢŢŢŢŢŢßßßßßßßßŕŕŕŕŕŕááááááââââââăăăăăăääääääĺĺĺĺććććććççççččččččééééęęęęëëëëëëěěěěííííîîîîďďďďđđđđńńńńňňňňóóóóôôőőőőöööö÷÷÷÷řřřřůůúúúúűűűűüüüüýýţţţţ˙˙  !!!!!!""""""""########$$$$$$$$$$%%%%%%%%&&&&&&&&&&''''''''''''(((((((((())))))))))))))************++++++++++++++++,,,,,,,,,,,,,,,,------------------....................//////////////////////0000000000000000000000001111111111111111111111111111222222222222222222222222222222223333333333333333333333333333333333333344444444444444444444444444444444444444444444555555555555555555555555555555555555555555555555555555666666666666666666666666666666666666666666666666666666666666666677777777777777777777777777777777777777777777777777777777777777777777777777777777888888888888888888ÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝŢŢŢŢŢŢßßßßßßßßŕŕŕŕŕŕááááááââââââăăăăăăääääĺĺĺĺĺĺććććććççççččččééééééęęęęëëëëěěěěííííîîîîďďďďđđđđńńńńňňňňóóóóôôôôőőöööö÷÷÷÷řřřřůůůůúúűűűűüüüüýýţţţţ˙˙  !!!!!!""""""""########$$$$$$$$%%%%%%%%%%&&&&&&&&&&''''''''''(((((((((((())))))))))))**************++++++++++++++,,,,,,,,,,,,,,,,------------------..................//////////////////////0000000000000000000000000011111111111111111111111111222222222222222222222222222222223333333333333333333333333333333333333344444444444444444444444444444444444444444444555555555555555555555555555555555555555555555555555566666666666666666666666666666666666666666666666666666666666666777777777777777777777777777777777777777777777777777777777777777777777777777777778888888888888888888888888888ÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖ××××××××××××ŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝŢŢŢŢŢŢßßßßßßßßŕŕŕŕŕŕááááááââââââăăăăääääääĺĺĺĺĺĺććććççççççččččééééęęęęëëëëëëěěěěííííîîîîďďďďđđđđńńňňňňóóóóôôôôőőőőöö÷÷÷÷řřřřůůůůúúűűűűüüüüýýţţţţ˙˙  !!!!!!!!""""""""########$$$$$$$$%%%%%%%%%%&&&&&&&&&&''''''''''(((((((((())))))))))))**************++++++++++++++,,,,,,,,,,,,,,,,------------------..................//////////////////////00000000000000000000000011111111111111111111111111112222222222222222222222222222223333333333333333333333333333333333333344444444444444444444444444444444444444444455555555555555555555555555555555555555555555555555556666666666666666666666666666666666666666666666666666666666666677777777777777777777777777777777777777777777777777777777777777777777777777777788888888888888888888888888888888888888ĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××ŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝŢŢŢŢŢŢßßßßßßßßŕŕŕŕŕŕááááááââââââăăăăääääääĺĺĺĺććććććççççččččččééééęęęęëëëëěěěěííííîîîîďďďďđđđđńńńńňňňňóóôôôôőőőőöööö÷÷řřřřůůůůúúűűűűüüüüýýţţţţ˙˙  !!!!!!!!""""""""########$$$$$$$$%%%%%%%%&&&&&&&&&&''''''''''(((((((((((())))))))))))************++++++++++++++,,,,,,,,,,,,,,,,----------------....................////////////////////00000000000000000000000011111111111111111111111111112222222222222222222222222222223333333333333333333333333333333333334444444444444444444444444444444444444444445555555555555555555555555555555555555555555555555555666666666666666666666666666666666666666666666666666666666666777777777777777777777777777777777777777777777777777777777777777777777777777777888888888888888888888888888888888888888888888888ĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××ŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝŢŢŢŢŢŢßßßßßßŕŕŕŕŕŕŕŕááááááââââăăăăăăääääääĺĺĺĺććććććççççččččééééęęęęęęëëëëěěěěííííîîîîďďđđđđńńńńňňňňóóóóôôőőőőöööö÷÷÷÷řřůůůůúúúúűűüüüüýýţţţţ˙˙  !!!!!!!!""""""""######$$$$$$$$$$%%%%%%%%&&&&&&&&&&''''''''''(((((((((())))))))))))************++++++++++++++,,,,,,,,,,,,,,,,----------------....................////////////////////000000000000000000000000111111111111111111111111112222222222222222222222222222223333333333333333333333333333333333334444444444444444444444444444444444444444445555555555555555555555555555555555555555555555555566666666666666666666666666666666666666666666666666666666666677777777777777777777777777777777777777777777777777777777777777777777777777778888888888888888888888888888888888888888888888888888888888ĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××ŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰÜÜÜÜÜÜÝÝÝÝÝÝÝÝŢŢŢŢŢŢßßßßßßŕŕŕŕŕŕááááááââââââăăăăăăääääĺĺĺĺĺĺććććççççççččččééééęęęęëëëëěěěěííííîîîîďďďďđđđđńńňňňňóóóóôôôôőőöööö÷÷÷÷řřůůůůúúúúűűüüüüýýţţţţ˙˙  !!!!!!!!""""""########$$$$$$$$%%%%%%%%%%&&&&&&&&''''''''''(((((((((((())))))))))**************++++++++++++++,,,,,,,,,,,,,,----------------..................//////////////////////0000000000000000000000111111111111111111111111112222222222222222222222222222223333333333333333333333333333333333334444444444444444444444444444444444444444555555555555555555555555555555555555555555555555556666666666666666666666666666666666666666666666666666666666667777777777777777777777777777777777777777777777777777777777777777777777777788888888888888888888888888888888888888888888888888888888888888888888ĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××ŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÝÝÝÝÝÝŢŢŢŢŢŢßßßßßßŕŕŕŕŕŕááááááââââââăăăăăăääääĺĺĺĺĺĺććććççççččččééééééęęęęëëëëěěěěííííîîďďďďđđđđńńńńňňňňóóôôôôőőőőöö÷÷÷÷řřřřůůúúúúűűüüüüýýýýţţ˙˙  !!!!!!!!""""""########$$$$$$$$%%%%%%%%&&&&&&&&&&''''''''''(((((((((())))))))))))************++++++++++++++,,,,,,,,,,,,,,----------------..................////////////////////0000000000000000000000001111111111111111111111111122222222222222222222222222223333333333333333333333333333333333334444444444444444444444444444444444444444555555555555555555555555555555555555555555555555666666666666666666666666666666666666666666666666666666666677777777777777777777777777777777777777777777777777777777777777777777777777888888888888888888888888888888888888888888888888888888888888888888888888888888ĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖ××××××××××ŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÝÝÝÝÝÝŢŢŢŢŢŢßßßßßßŕŕŕŕŕŕááááááââââââăăăăääääääĺĺĺĺććććććççççččččééééęęęęëëëëěěěěííííîîîîďďďďđđńńńńňňňňóóóóôôőőőőöööö÷÷řřřřůůúúúúűűüüüüýýýýţţ˙˙  !!!!!!!!""""""########$$$$$$$$%%%%%%%%&&&&&&&&''''''''''(((((((((())))))))))))************++++++++++++++,,,,,,,,,,,,,,----------------..................////////////////////00000000000000000000001111111111111111111111111122222222222222222222222222223333333333333333333333333333333333444444444444444444444444444444444444444455555555555555555555555555555555555555555555555566666666666666666666666666666666666666666666666666666666667777777777777777777777777777777777777777777777777777777777777777777777778888888888888888888888888888888888888888888888888888888888888888888888888888888888888888ĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××ŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÝÝÝÝÝÝŢŢŢŢŢŢßßßßßßŕŕŕŕŕŕááááááââââââăăăăääääääĺĺĺĺććććççççččččččééééęęęęëëëëěěěěííîîîîďďďďđđđđńńňňňňóóóóôôőőőőöööö÷÷řřřřůůúúúúűűűűüüýýýýţţ˙˙  !!!!!!""""""""########$$$$$$$$%%%%%%%%&&&&&&&&''''''''''(((((((((())))))))))************++++++++++++++,,,,,,,,,,,,,,----------------..................//////////////////00000000000000000000001111111111111111111111111122222222222222222222222222223333333333333333333333333333333333444444444444444444444444444444444444444455555555555555555555555555555555555555555555556666666666666666666666666666666666666666666666666666666677777777777777777777777777777777777777777777777777777777777777777777777788888888888888888888888888888888888888888888888888888888888888888888888888888888888888888899999999ĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖ××××××××××ŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚŰŰŰŰŰŰÜÜÜÜÜÜÜÜÝÝÝÝÝÝŢŢŢŢŢŢßßßßßßŕŕŕŕŕŕááááááââââââăăăăääääĺĺĺĺĺĺććććççççččččééééęęęęëëëëěěěěííííîîîîďďđđđđńńńńňňóóóóôôôôőőöööö÷÷řřřřůůůůúúűűűűüüýýýýţţ˙˙  !!!!!!""""""""######$$$$$$$$%%%%%%%%&&&&&&&&&&''''''''(((((((((())))))))))))************++++++++++++,,,,,,,,,,,,,,----------------................////////////////////000000000000000000000011111111111111111111111122222222222222222222222222223333333333333333333333333333333333444444444444444444444444444444444444445555555555555555555555555555555555555555555555666666666666666666666666666666666666666666666666666666667777777777777777777777777777777777777777777777777777777777777777777777888888888888888888888888888888888888888888888888888888888888888888888888888888888888888899999999999999999999ĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖ××××××××ŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰÜÜÜÜÜÜÝÝÝÝÝÝŢŢŢŢŢŢßßßßßßŕŕŕŕŕŕááááááââââăăăăăăääääĺĺĺĺććććććççççččččééééęęęęëëëëěěěěííîîîîďďďďđđđđńńňňňňóóôôôôőőőőöö÷÷÷÷řřůůůůúúűűűűüüýýýýţţ˙˙  !!!!!!""""""""######$$$$$$$$%%%%%%%%&&&&&&&&''''''''''(((((((((())))))))))************++++++++++++,,,,,,,,,,,,,,----------------................////////////////////0000000000000000000011111111111111111111111122222222222222222222222222223333333333333333333333333333333344444444444444444444444444444444444444555555555555555555555555555555555555555555555566666666666666666666666666666666666666666666666666666677777777777777777777777777777777777777777777777777777777777777777777778888888888888888888888888888888888888888888888888888888888888888888888888888888888888888999999999999999999999999999999ĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖ××××××××××ŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰÜÜÜÜÜÜÝÝÝÝÝÝŢŢŢŢŢŢßßßßßßŕŕŕŕŕŕááááááââââăăăăăăääääĺĺĺĺććććççççččččééééęęęęëëëëěěěěííííîîîîďďđđđđńńńńňňóóóóôôőőőőöö÷÷÷÷řřůůůůúúűűűűüüýýýýţţ˙˙  !!!!!!""""""########$$$$$$$$%%%%%%%%&&&&&&&&''''''''(((((((((())))))))))************++++++++++++,,,,,,,,,,,,,,----------------................//////////////////0000000000000000000000111111111111111111111111222222222222222222222222223333333333333333333333333333333344444444444444444444444444444444444444555555555555555555555555555555555555555555556666666666666666666666666666666666666666666666666666667777777777777777777777777777777777777777777777777777777777777777777788888888888888888888888888888888888888888888888888888888888888888888888888888888888888999999999999999999999999999999999999999999ĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖ××××××××ŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮÚÚÚÚÚÚŰŰŰŰŰŰŰŰÜÜÜÜÜÜÝÝÝÝÝÝŢŢŢŢŢŢßßßßßßŕŕŕŕŕŕááááááââââăăăăääääääĺĺĺĺććććççççččččééééęęęęëëëëěěííííîîîîďďďďđđńńńńňňóóóóôôôôőőöööö÷÷řřůůůůúúűűűűüüýýýýţţ˙˙  !!!!!!""""""########$$$$$$%%%%%%%%&&&&&&&&''''''''''(((((((((())))))))))**********++++++++++++,,,,,,,,,,,,,,----------------................//////////////////00000000000000000000111111111111111111111111222222222222222222222222223333333333333333333333333333333344444444444444444444444444444444444455555555555555555555555555555555555555555555666666666666666666666666666666666666666666666666666666777777777777777777777777777777777777777777777777777777777777777777888888888888888888888888888888888888888888888888888888888888888888888888888888888888999999999999999999999999999999999999999999999999999999ĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖ××××××××××ŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚŰŰŰŰŰŰÜÜÜÜÜÜÝÝÝÝÝÝŢŢŢŢŢŢßßßßßßŕŕŕŕŕŕááááââââââăăăăääääĺĺĺĺĺĺććććççççččččééééęęëëëëěěěěííííîîďďďďđđđđńńňňňňóóôôôôőőöööö÷÷řřřřůůúúúúűűüüýýýýţţ˙˙  !!!!!!""""""########$$$$$$%%%%%%%%&&&&&&&&''''''''(((((((((())))))))))************++++++++++++,,,,,,,,,,,,--------------................//////////////////00000000000000000000001111111111111111111111222222222222222222222222223333333333333333333333333333333344444444444444444444444444444444444455555555555555555555555555555555555555555566666666666666666666666666666666666666666666666666667777777777777777777777777777777777777777777777777777777777777777778888888888888888888888888888888888888888888888888888888888888888888888888888888888999999999999999999999999999999999999999999999999999999999999999999ĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖ××××××××ŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚŰŰŰŰŰŰÜÜÜÜÜÜÜÜÝÝÝÝÝÝŢŢŢŢßßßßßßŕŕŕŕŕŕááááââââââăăăăääääĺĺĺĺććććççççččččééééęęęęëëëëěěííííîîîîďďđđđđńńńńňňóóóóôôőőőőöö÷÷řřřřůůúúúúűűüüýýýýţţ˙˙  !!!!!!""""""######$$$$$$$$%%%%%%%%&&&&&&&&''''''''(((((((())))))))))************++++++++++++,,,,,,,,,,,,--------------................//////////////////000000000000000000001111111111111111111111222222222222222222222222223333333333333333333333333333334444444444444444444444444444444444445555555555555555555555555555555555555555556666666666666666666666666666666666666666666666666666777777777777777777777777777777777777777777777777777777777777777788888888888888888888888888888888888888888888888888888888888888888888888888888888889999999999999999999999999999999999999999999999999999999999999999999999999999ĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖ××××××××××ŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮÚÚÚÚÚÚŰŰŰŰŰŰÜÜÜÜÜÜÜÜÝÝÝÝÝÝŢŢŢŢßßßßßßŕŕŕŕŕŕááááââââââăăăăääääĺĺĺĺććććççççččččééééęęęęëëěěěěííííîîďďďďđđńńńńňňóóóóôôőőőőöö÷÷÷÷řřůůúúúúűűüüýýýýţţ˙˙  !!!!!!""""""######$$$$$$$$%%%%%%&&&&&&&&''''''''(((((((((())))))))))**********++++++++++++,,,,,,,,,,,,--------------................//////////////////0000000000000000001111111111111111111111112222222222222222222222223333333333333333333333333333334444444444444444444444444444444444555555555555555555555555555555555555555555666666666666666666666666666666666666666666666666667777777777777777777777777777777777777777777777777777777777777777888888888888888888888888888888888888888888888888888888888888888888888888888888889999999999999999999999999999999999999999999999999999999999999999999999999999999999999999ĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖ××××××××ŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮÚÚÚÚÚÚŰŰŰŰŰŰŰŰÜÜÜÜÜÜÝÝÝÝÝÝŢŢŢŢßßßßßßŕŕŕŕŕŕááááââââăăăăăăääääĺĺĺĺććććççççččččééęęęęëëëëěěííííîîîîďďđđđđńńňňňňóóôôôôőőöö÷÷÷÷řřůůúúúúűűüüýýýýţţ˙˙  !!!!!!""""""######$$$$$$%%%%%%%%&&&&&&&&''''''''(((((((())))))))))**********++++++++++++,,,,,,,,,,,,--------------................////////////////0000000000000000000011111111111111111111112222222222222222222222223333333333333333333333333333334444444444444444444444444444444444555555555555555555555555555555555555555566666666666666666666666666666666666666666666666666777777777777777777777777777777777777777777777777777777777777778888888888888888888888888888888888888888888888888888888888888888888888888888889999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999ĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖ××××××××××ŘŘŘŘŘŘŘŘŮŮŮŮŮŮÚÚÚÚÚÚÚÚŰŰŰŰŰŰÜÜÜÜÜÜÝÝÝÝÝÝŢŢŢŢßßßßßßŕŕŕŕŕŕááááââââăăăăääääĺĺĺĺććććççççččččééééęęęęëëěěěěííííîîďďďďđđńńńńňňóóôôôôőőöööö÷÷řřůůůůúúűűüüüüýýţţ˙˙  !!!!!!""""""######$$$$$$%%%%%%%%&&&&&&''''''''(((((((((())))))))************++++++++++,,,,,,,,,,,,--------------................////////////////000000000000000000111111111111111111111122222222222222222222222233333333333333333333333333334444444444444444444444444444444444555555555555555555555555555555555555555566666666666666666666666666666666666666666666666677777777777777777777777777777777777777777777777777777777777777888888888888888888888888888888888888888888888888888888888888888888888888888888999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999::::::::ĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖ××××××××ŘŘŘŘŘŘŘŘŮŮŮŮŮŮÚÚÚÚÚÚÚÚŰŰŰŰŰŰÜÜÜÜÜÜÝÝÝÝÝÝŢŢŢŢŢŢßßßßŕŕŕŕááááááââââăăăăääääĺĺĺĺććććççççččččééęęęęëëëëěěííííîîďďďďđđńńńńňňóóóóôôőőöööö÷÷řřůůůůúúűűüüüüýýţţ˙˙  !!!!!!""""######$$$$$$$$%%%%%%&&&&&&&&''''''''(((((((())))))))))**********++++++++++,,,,,,,,,,,,--------------..............////////////////0000000000000000000011111111111111111111222222222222222222222222333333333333333333333333333344444444444444444444444444444444445555555555555555555555555555555555555566666666666666666666666666666666666666666666666677777777777777777777777777777777777777777777777777777777777788888888888888888888888888888888888888888888888888888888888888888888888888889999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999::::::::::::::::::::::ĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖ××××××××××ŘŘŘŘŘŘŮŮŮŮŮŮŮŮÚÚÚÚÚÚŰŰŰŰŰŰÜÜÜÜÜÜÝÝÝÝÝÝŢŢŢŢŢŢßßßßŕŕŕŕááááááââââăăăăääääĺĺĺĺććććççççččééééęęęęëëěěěěííîîîîďďđđđđńńňňňňóóôôőőőőöö÷÷řřřřůůúúűűüüüüýýţţ˙˙  !!!!""""""######$$$$$$%%%%%%%%&&&&&&&&''''''(((((((((())))))))**********++++++++++,,,,,,,,,,,,--------------..............////////////////00000000000000000011111111111111111111222222222222222222222222333333333333333333333333333344444444444444444444444444444444555555555555555555555555555555555555556666666666666666666666666666666666666666666666667777777777777777777777777777777777777777777777777777777777888888888888888888888888888888888888888888888888888888888888888888888888889999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999::::::::::::::::::::::::::::::::::ĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖ××××××××ŘŘŘŘŘŘŘŘŮŮŮŮŮŮÚÚÚÚÚÚŰŰŰŰŰŰÜÜÜÜÜÜÝÝÝÝÝÝŢŢŢŢŢŢßßßßŕŕŕŕááááááââââăăăăääääĺĺĺĺććççççččččééééęęëëëëěěííííîîďďďďđđńńńńňňóóôôôôőőöö÷÷řřřřůůúúűűüüüüýýţţ˙˙  !!!!""""""######$$$$$$%%%%%%%%&&&&&&''''''''(((((((())))))))**********++++++++++++,,,,,,,,,,--------------..............////////////////0000000000000000001111111111111111111122222222222222222222223333333333333333333333333333444444444444444444444444444444555555555555555555555555555555555555556666666666666666666666666666666666666666666666777777777777777777777777777777777777777777777777777777777788888888888888888888888888888888888888888888888888888888888888888888888899999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999::::::::::::::::::::::::::::::::::::::::::::::::ÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖ××××××ŘŘŘŘŘŘŘŘŮŮŮŮŮŮÚÚÚÚÚÚÚÚŰŰŰŰŰŰÜÜÜÜÝÝÝÝÝÝŢŢŢŢŢŢßßßßŕŕŕŕááááââââââăăăăääĺĺĺĺććććççççččččééęęęęëëěěěěííîîîîďďđđđđńńňňóóóóôôőőöö÷÷÷÷řřůůúúűűűűüüýýţţ˙˙  !!!!""""""######$$$$$$%%%%%%&&&&&&&&''''''(((((((())))))))))**********++++++++++,,,,,,,,,,--------------..............//////////////00000000000000000011111111111111111111222222222222222222222233333333333333333333333333444444444444444444444444444444445555555555555555555555555555555555556666666666666666666666666666666666666666666666777777777777777777777777777777777777777777777777777777778888888888888888888888888888888888888888888888888888888888888888888888889999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖ××××××××ŘŘŘŘŘŘŮŮŮŮŮŮŮŮÚÚÚÚÚÚŰŰŰŰŰŰÜÜÜÜÜÜÝÝÝÝŢŢŢŢŢŢßßßßŕŕŕŕááááââââăăăăääääĺĺĺĺććććççççččééééęęëëëëěěííííîîďďďďđđńńňňňňóóôôőőöööö÷÷řřůůúúűűűűüüýýţţ˙˙  !!!!""""""######$$$$$$%%%%%%&&&&&&''''''''(((((((())))))))**********++++++++++,,,,,,,,,,--------------............////////////////000000000000000011111111111111111111222222222222222222222233333333333333333333333333444444444444444444444444444444555555555555555555555555555555555555666666666666666666666666666666666666666666667777777777777777777777777777777777777777777777777777777788888888888888888888888888888888888888888888888888888888888888888888889999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖ××××××ŘŘŘŘŘŘŘŘŮŮŮŮŮŮÚÚÚÚÚÚŰŰŰŰŰŰÜÜÜÜÜÜÝÝÝÝŢŢŢŢŢŢßßßßŕŕŕŕááááââââăăăăääääĺĺĺĺććććççččččééęęęęëëěěěěííîîîîďďđđńńńńňňóóôôőőőőöö÷÷řřůůúúúúűűüüýýţţ˙˙  !!!!""""""####$$$$$$%%%%%%%%&&&&&&''''''(((((((())))))))))********++++++++++,,,,,,,,,,------------..............//////////////0000000000000000001111111111111111112222222222222222222222333333333333333333333333334444444444444444444444444444445555555555555555555555555555555555666666666666666666666666666666666666666666667777777777777777777777777777777777777777777777777777778888888888888888888888888888888888888888888888888888888888888888888899999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖ××××××××ŘŘŘŘŘŘŮŮŮŮŮŮÚÚÚÚÚÚŰŰŰŰŰŰÜÜÜÜÜÜÝÝÝÝŢŢŢŢŢŢßßßßŕŕŕŕááááââââăăăăääääĺĺĺĺććççççččééééęęëëëëěěííííîîďďđđđđńńňňóóôôôôőőöö÷÷řřůůůůúúűűüüýýţţ˙˙  !!!!""""""####$$$$$$%%%%%%&&&&&&''''''''(((((((())))))))********++++++++++,,,,,,,,,,------------..............//////////////000000000000000011111111111111111122222222222222222222223333333333333333333333334444444444444444444444444444445555555555555555555555555555555555666666666666666666666666666666666666666666777777777777777777777777777777777777777777777777777777888888888888888888888888888888888888888888888888888888888888888888999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖ××××××ŘŘŘŘŘŘŘŘŮŮŮŮŮŮÚÚÚÚÚÚŰŰŰŰÜÜÜÜÜÜÝÝÝÝŢŢŢŢŢŢßßßßŕŕŕŕááááââââăăăăääääĺĺććććççččččééééęęëëěěěěííîîďďďďđđńńňňóóóóôôőőöö÷÷řřřřůůúúűűüüýýţţ˙˙  !!!!!!""""######$$$$$$%%%%%%&&&&&&''''''(((((((())))))))**********++++++++,,,,,,,,,,------------..............//////////////00000000000000001111111111111111112222222222222222222233333333333333333333333344444444444444444444444444445555555555555555555555555555555555666666666666666666666666666666666666666666777777777777777777777777777777777777777777777777777788888888888888888888888888888888888888888888888888888888888888888899999999999999999999999999999999999999999999999999999999999999999999999999999999999999::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖ××××××××ŘŘŘŘŘŘŮŮŮŮŮŮÚÚÚÚÚÚŰŰŰŰÜÜÜÜÜÜÝÝÝÝŢŢŢŢŢŢßßßßŕŕŕŕááááââââăăăăääĺĺĺĺććććççččččééęęęęëëěěííííîîďďđđńńńńňňóóôôőőöö÷÷÷÷řřůůúúűűüüýýţţ˙˙  !!!!!!""""######$$$$$$%%%%%%&&&&&&''''''(((((((())))))))********++++++++,,,,,,,,,,------------............//////////////00000000000000001111111111111111112222222222222222222233333333333333333333333344444444444444444444444444445555555555555555555555555555555566666666666666666666666666666666666666666677777777777777777777777777777777777777777777777777888888888888888888888888888888888888888888888888888888888888888899999999999999999999999999999999999999999999999999999999999999999999999999999999999999::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::;;;;;;;;;;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖ××××××ŘŘŘŘŘŘŮŮŮŮŮŮÚÚÚÚÚÚŰŰŰŰŰŰÜÜÜÜÝÝÝÝŢŢŢŢŢŢßßßßŕŕŕŕááááââââăăääääĺĺĺĺććççççččééééęęëëěěěěííîîďďďďđđńńňňóóôôőőőőöö÷÷řřůůúúűűüüýýţţ˙˙  !!!!!!""""######$$$$%%%%%%&&&&&&''''''''(((((())))))))********++++++++++,,,,,,,,,,----------............//////////////0000000000000011111111111111111122222222222222222222333333333333333333333333444444444444444444444444445555555555555555555555555555555566666666666666666666666666666666666666667777777777777777777777777777777777777777777777777788888888888888888888888888888888888888888888888888888888888888999999999999999999999999999999999999999999999999999999999999999999999999999999999999::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐÖÖÖÖÖÖ××××××××ŘŘŘŘŘŘŮŮŮŮŮŮÚÚÚÚŰŰŰŰŰŰÜÜÜÜÝÝÝÝÝÝŢŢŢŢßßßßŕŕŕŕááááââăăăăääääĺĺććććççččččééęęęęëëěěííîîîîďďđđńńňňóóóóôôőőöö÷÷řřůůúúűűüüýýţţ˙˙  !!!!!!""""####$$$$$$%%%%%%&&&&&&''''''(((((())))))))********++++++++++,,,,,,,,,,----------............////////////00000000000000001111111111111111222222222222222222223333333333333333333333444444444444444444444444444455555555555555555555555555555566666666666666666666666666666666666666667777777777777777777777777777777777777777777777778888888888888888888888888888888888888888888888888888888888889999999999999999999999999999999999999999999999999999999999999999999999999999999999::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖ××××××ŘŘŘŘŘŘŮŮŮŮŮŮÚÚÚÚÚÚŰŰŰŰÜÜÜÜÝÝÝÝÝÝŢŢŢŢßßßßŕŕŕŕááââââăăăăääääĺĺććććççččééééęęëëěěěěííîîďďđđńńńńňňóóôôőőöö÷÷řřůůúúűűüüýýţţ˙˙  !!!!""""""####$$$$$$%%%%&&&&&&''''''''(((((())))))))********++++++++,,,,,,,,,,----------............////////////000000000000001111111111111111112222222222222222223333333333333333333333444444444444444444444444445555555555555555555555555555556666666666666666666666666666666666666677777777777777777777777777777777777777777777777788888888888888888888888888888888888888888888888888888888888899999999999999999999999999999999999999999999999999999999999999999999999999999999::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐÖÖÖÖÖÖ××××××ŘŘŘŘŘŘŮŮŮŮŮŮÚÚÚÚÚÚŰŰŰŰÜÜÜÜÜÜÝÝÝÝŢŢŢŢßßßßŕŕŕŕááââââăăăăääĺĺĺĺććççççččééęęęęëëěěííîîîîďďđđńńňňóóôôőőöö÷÷řřůůúúűűüüýýţţ˙˙  !!!!""""""####$$$$%%%%%%&&&&&&''''''(((((())))))))********++++++++,,,,,,,,,,----------..........//////////////00000000000000111111111111111122222222222222222233333333333333333333334444444444444444444444445555555555555555555555555555556666666666666666666666666666666666666677777777777777777777777777777777777777777777778888888888888888888888888888888888888888888888888888888888999999999999999999999999999999999999999999999999999999999999999999999999999999::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔŐŐŐŐŐŐÖÖÖÖÖÖÖÖ××××××ŘŘŘŘŘŘŮŮŮŮÚÚÚÚÚÚŰŰŰŰÜÜÜÜÜÜÝÝÝÝŢŢŢŢßßßßŕŕááááââââăăăăääĺĺĺĺććççččččééęęëëěěěěííîîďďđđńńňňóóôôőőöö÷÷řřůůúúűűüüýýţţ˙˙  !!!!""""######$$$$%%%%%%&&&&&&''''''(((((())))))********++++++++,,,,,,,,,,----------..........////////////000000000000001111111111111111222222222222222222333333333333333333334444444444444444444444444455555555555555555555555555556666666666666666666666666666666666667777777777777777777777777777777777777777777777888888888888888888888888888888888888888888888888888888889999999999999999999999999999999999999999999999999999999999999999999999999999::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔŐŐŐŐŐŐÖÖÖÖÖÖ××××××ŘŘŘŘŘŘŮŮŮŮŮŮÚÚÚÚŰŰŰŰŰŰÜÜÜÜÝÝÝÝŢŢŢŢßßßßŕŕááááââââăăääääĺĺććććççččééééęęëëěěííîîďďďďđđńńňňóóôôőőöö÷÷řřůůúúüüýýţţ˙˙  !!!!""""######$$$$%%%%&&&&&&''''''(((((())))))))******++++++++,,,,,,,,,,--------............////////////00000000000000111111111111112222222222222222223333333333333333333344444444444444444444444455555555555555555555555555555566666666666666666666666666666666667777777777777777777777777777777777777777777788888888888888888888888888888888888888888888888888888899999999999999999999999999999999999999999999999999999999999999999999999999::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔŐŐŐŐŐŐŐŐÖÖÖÖÖÖ××××××ŘŘŘŘŘŘŮŮŮŮÚÚÚÚÚÚŰŰŰŰÜÜÜÜÝÝÝÝŢŢŢŢßßßßŕŕááááââââăăääääĺĺććççççččééęęëëěěěěííîîďďđđńńňňóóôôőőöö÷÷řřůůúúűűýýţţ˙˙  !!!!""""####$$$$$$%%%%&&&&&&''''''(((((())))))******++++++++,,,,,,,,,,--------............//////////00000000000000111111111111112222222222222222223333333333333333333344444444444444444444444455555555555555555555555555556666666666666666666666666666666666777777777777777777777777777777777777777777888888888888888888888888888888888888888888888888888888999999999999999999999999999999999999999999999999999999999999999999999999::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔŐŐŐŐŐŐÖÖÖÖÖÖ××××××ŘŘŘŘŘŘŮŮŮŮÚÚÚÚÚÚŰŰŰŰÜÜÜÜÝÝÝÝŢŢŢŢßßßßŕŕááááââăăăăääĺĺĺĺććççččččééęęëëěěííîîďďđđńńňňóóôôőőöö÷÷řřůůúúűűýýţţ˙˙  !!!!""""####$$$$%%%%%%&&&&''''''(((((())))))********++++++++,,,,,,,,--------..........////////////0000000000001111111111111111222222222222222233333333333333333333444444444444444444444455555555555555555555555555556666666666666666666666666666666677777777777777777777777777777777777777777788888888888888888888888888888888888888888888888888889999999999999999999999999999999999999999999999999999999999999999999999::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔŐŐŐŐŐŐÖÖÖÖÖÖ××××××ŘŘŘŘŮŮŮŮŮŮÚÚÚÚŰŰŰŰÜÜÜÜÝÝÝÝŢŢŢŢßßßßŕŕááááââăăăăääĺĺććććççččééęęëëëëěěííîîďďđđńńňňóóőőöö÷÷řřůůúúűűüüţţ˙˙  !!!!""""####$$$$%%%%&&&&&&''''''(((())))))))******++++++++,,,,,,,,--------..........////////////000000000000111111111111112222222222222222333333333333333333444444444444444444444455555555555555555555555555556666666666666666666666666666666677777777777777777777777777777777777777778888888888888888888888888888888888888888888888888899999999999999999999999999999999999999999999999999999999999999999999::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<ĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÔÔÔÔÔÔŐŐŐŐŐŐŐŐÖÖÖÖ××××××ŘŘŘŘŘŘŮŮŮŮÚÚÚÚŰŰŰŰÜÜÜÜÝÝÝÝŢŢŢŢßßßßŕŕááááââăăääääĺĺććççççččééęęëëěěííîîďďđđńńňňóóôôőő÷÷řřůůúúűűüüţţ˙˙  !!!!""""####$$$$%%%%&&&&&&''''(((((())))))******++++++++,,,,,,,,--------..........//////////000000000000111111111111112222222222222222333333333333333333444444444444444444444455555555555555555555555555666666666666666666666666666666777777777777777777777777777777777777777788888888888888888888888888888888888888888888888888999999999999999999999999999999999999999999999999999999999999999999::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<ĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÔÔÔÔÔÔŐŐŐŐŐŐÖÖÖÖÖÖ××××××ŘŘŘŘŮŮŮŮÚÚÚÚŰŰŰŰÜÜÜÜÝÝÝÝŢŢŢŢßßŕŕŕŕááááââăăääääĺĺććççččééęęęęëëěěííîîđđńńňňóóôôőőöö÷÷ůůúúűűüüţţ˙˙  !!!!""""##$$$$$$%%%%&&&&''''''(((())))))******++++++++,,,,,,,,--------..........//////////00000000000011111111111122222222222222223333333333333333334444444444444444444455555555555555555555555555666666666666666666666666666666777777777777777777777777777777777777778888888888888888888888888888888888888888888888889999999999999999999999999999999999999999999999999999999999999999::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<ĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÔÔÔÔÔÔŐŐŐŐŐŐÖÖÖÖÖÖ××××ŘŘŘŘŮŮŮŮŮŮÚÚÚÚŰŰŰŰÜÜÜÜÝÝŢŢŢŢßßŕŕŕŕááââââăăääĺĺĺĺććççččééęęëëěěííîîďďđđńńňňôôőőöö÷÷řřúúűűüüýý˙˙  !!!!""####$$$$%%%%&&&&&&''''(((((())))))******++++++,,,,,,,,--------........//////////000000000000111111111111112222222222222233333333333333333344444444444444444444555555555555555555555555666666666666666666666666666677777777777777777777777777777777777777888888888888888888888888888888888888888888888899999999999999999999999999999999999999999999999999999999999999::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<ÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇÓÓÓÓÓÓÔÔÔÔÔÔÔÔŐŐŐŐÖÖÖÖÖÖ××××××ŘŘŘŘŮŮŮŮÚÚÚÚŰŰŰŰÜÜÜÜÝÝŢŢŢŢßßŕŕŕŕááââââăăääĺĺććççččččééęęëëěěîîďďđđńńňňóóőőöö÷÷řřúúűűüüýý˙˙  !!""""####$$$$%%%%&&&&''''''(((())))))******++++++,,,,,,,,--------........//////////00000000001111111111111122222222222222333333333333333344444444444444444444555555555555555555555555666666666666666666666666666677777777777777777777777777777777777788888888888888888888888888888888888888888888999999999999999999999999999999999999999999999999999999999999::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇÓÓÓÓÓÓÔÔÔÔÔÔŐŐŐŐŐŐÖÖÖÖÖÖ××××ŘŘŘŘŮŮŮŮÚÚÚÚŰŰŰŰÜÜÜÜÝÝŢŢŢŢßßŕŕŕŕááââăăăăääĺĺććççččééęęëëěěííîîďďđđňňóóôôőő÷÷řřůůűűüüýý˙˙  !!""""####$$$$%%%%&&&&''''(((((())))******++++++,,,,,,,,--------........//////////000000000011111111111122222222222222333333333333333344444444444444444455555555555555555555555566666666666666666666666666777777777777777777777777777777777777888888888888888888888888888888888888888888889999999999999999999999999999999999999999999999999999999999::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇÓÓÓÓÓÓÔÔÔÔÔÔŐŐŐŐŐŐÖÖÖÖ××××ŘŘŘŘŘŘŮŮŮŮÚÚŰŰŰŰÜÜÜÜÝÝŢŢŢŢßßŕŕŕŕááââăăääääĺĺććççččééęęëëííîîďďđđńńóóôôőőööřřůůűűüüýý˙˙  !!""""####$$%%%%&&&&''''''(((())))))****++++++,,,,,,,,--------........////////0000000000111111111111222222222222223333333333333333444444444444444444555555555555555555555566666666666666666666666666777777777777777777777777777777777788888888888888888888888888888888888888888899999999999999999999999999999999999999999999999999999999::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŇŇŇŇŇŇŇŇÓÓÓÓÓÓÔÔÔÔÔÔŐŐŐŐÖÖÖÖÖÖ××××ŘŘŘŘŮŮŮŮÚÚÚÚŰŰÜÜÜÜÝÝÝÝŢŢßßŕŕŕŕááââăăääĺĺććççččééęęëëěěííîîđđńńňňóóőőööřřůůúúüüýý˙˙  !!""""##$$$$%%%%&&&&''''(((())))))******++++++,,,,,,------........//////////000000000011111111112222222222222233333333333333444444444444444444555555555555555555555566666666666666666666666666777777777777777777777777777777778888888888888888888888888888888888888888999999999999999999999999999999999999999999999999999999::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐŃŃŃŃŃŃŇŇŇŇŇŇŇŇÓÓÓÓÓÓÔÔÔÔŐŐŐŐŐŐÖÖÖÖ××××ŘŘŘŘŮŮŮŮÚÚÚÚŰŰŰŰÜÜÝÝÝÝŢŢßßŕŕŕŕááââăăääĺĺććççččééęęëëííîîďďđđňňóóôôöö÷÷ůůúúüüýý˙˙  !!""""##$$$$%%%%&&&&''''(((())))******++++++,,,,,,------........////////000000000011111111111122222222222233333333333333444444444444444444555555555555555555556666666666666666666666667777777777777777777777777777777788888888888888888888888888888888888888999999999999999999999999999999999999999999999999999999::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐŃŃŃŃŃŃŇŇŇŇŇŇŇŇÓÓÓÓÔÔÔÔÔÔŐŐŐŐÖÖÖÖÖÖ××××ŘŘŘŘŮŮÚÚÚÚŰŰŰŰÜÜÝÝÝÝŢŢßßŕŕááááââăăääĺĺććççččęęëëěěííďďđđńńóóôôöö÷÷ůůúúüüýý˙˙  !!""####$$$$%%&&&&''''(((())))))****++++++,,,,,,------........////////00000000001111111111222222222222333333333333334444444444444444555555555555555555556666666666666666666666667777777777777777777777777777778888888888888888888888888888888888888899999999999999999999999999999999999999999999999999::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<==================ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐŃŃŃŃŃŃŇŇŇŇŇŇŇŇÓÓÓÓÔÔÔÔÔÔŐŐŐŐÖÖÖÖ××××ŘŘŘŘŮŮŮŮÚÚŰŰŰŰÜÜÝÝÝÝŢŢßßŕŕááááââăăääĺĺççččééęęëëííîîďďńńňňôôőő÷÷řřúúüüýý˙˙  !!""####$$%%%%&&&&''''(((())))****++++++,,,,,,------........////////0000000011111111112222222222223333333333333344444444444444555555555555555555556666666666666666666666777777777777777777777777777777888888888888888888888888888888888888999999999999999999999999999999999999999999999999::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<============================================ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĐĐĐĐĐĐŃŃŃŃŃŃŃŃŇŇŇŇÓÓÓÓÓÓÔÔÔÔÔÔŐŐŐŐÖÖÖÖ××××ŘŘŮŮŮŮÚÚÚÚŰŰÜÜÜÜÝÝŢŢßßŕŕááââăăääĺĺććççččééëëěěííďďđđňňóóőőööřřúúűűýý˙˙  !!""####$$%%%%&&''''(((())))******++++,,,,,,------......////////0000000011111111112222222222223333333333334444444444444444555555555555555555666666666666666666666677777777777777777777777777778888888888888888888888888888888888999999999999999999999999999999999999999999999999::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<========================================================================ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĐĐĐĐĐĐŃŃŃŃŃŃŃŃŇŇŇŇÓÓÓÓÓÓÔÔÔÔŐŐŐŐÖÖÖÖ××××ŘŘŘŘŮŮÚÚÚÚŰŰÜÜÜÜÝÝŢŢßßŕŕááââăăääĺĺććççééęęëëííîîđđńńóóôôööřřůůűűýý˙˙  !!""##$$$$%%&&&&''''(((())))****++++,,,,,,------......////////000000001111111122222222222233333333333344444444444444555555555555555555666666666666666666667777777777777777777777777777888888888888888888888888888888889999999999999999999999999999999999999999999999::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<==================================================================================================ÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎĎĎĎĎĎĎĐĐĐĐĐĐĐĐŃŃŃŃŃŃŇŇŇŇÓÓÓÓÓÓÔÔÔÔŐŐŐŐÖÖÖÖ××××ŘŘŮŮŮŮÚÚŰŰÜÜÜÜÝÝŢŢßßŕŕááââăăääĺĺććččééęęěěííďďńńňňôôöö÷÷ůůűűýý˙˙  !!!!""##$$$$%%&&&&''(((())))****++++,,,,,,------......//////0000000011111111112222222222333333333333444444444444445555555555555555666666666666666666667777777777777777777777777788888888888888888888888888888888999999999999999999999999999999999999999999::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<==============================================================================================================================ÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĐĐĐĐĐĐŃŃŃŃŃŃŇŇŇŇÓÓÓÓÓÓÔÔÔÔŐŐŐŐÖÖ××××ŘŘŘŘŮŮÚÚŰŰŰŰÜÜÝÝŢŢßßŕŕááââăăääććççččęęëëííîîđđňňóóőő÷÷ůůűűýý˙˙  !!!!""##$$%%%%&&''''(())))****++++++,,,,------......//////000000001111111122222222223333333333444444444444445555555555555555666666666666666666667777777777777777777777778888888888888888888888888888889999999999999999999999999999999999999999::::::::::::::::::::::::::::::::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<========================================================================================================================================================ÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎĎĎĎĎĎĎĐĐĐĐĐĐŃŃŃŃŃŃŇŇŇŇÓÓÓÓÔÔÔÔŐŐŐŐÖÖÖÖ××ŘŘŘŘŮŮÚÚÚÚŰŰÜÜÝÝŢŢßßŕŕááââăăĺĺććççééęęěěîîďďńńóóőő÷÷ůůűűýý˙˙  !!""""##$$%%&&&&''(((())))****++++,,,,------....////////0000001111111122222222223333333333444444444444555555555555555566666666666666666677777777777777777777777788888888888888888888888888889999999999999999999999999999999999999999::::::::::::::::::::::::::::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<====================================================================================================================================================================================ÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎĎĎĎĎĎĎĐĐĐĐĐĐŃŃŃŃŃŃŇŇŇŇÓÓÓÓÔÔÔÔŐŐŐŐÖÖ××××ŘŘŮŮŮŮÚÚŰŰÜÜÝÝŢŢßßŕŕááââääĺĺććččééëëííďďđđňňôôööřřúúýý˙˙  !!""####$$%%&&''''(())))****++++,,,,----......//////00000000111111222222222233333333334444444444445555555555555566666666666666666677777777777777777777778888888888888888888888888899999999999999999999999999999999999999::::::::::::::::::::::::::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<==============================================================================================================================================================================================================ÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎĎĎĎĎĎĎĐĐĐĐŃŃŃŃŃŃŇŇŇŇÓÓÓÓÔÔÔÔŐŐÖÖÖÖ××ŘŘŘŘŮŮÚÚŰŰÜÜÝÝŢŢßßŕŕááăăääĺĺççččęęěěîîđđňňôôööřřúúýý˙˙  !!""##$$%%%%&&''(((())****++++,,,,----......//////0000001111111122222222333333333344444444445555555555555566666666666666667777777777777777777777888888888888888888888888889999999999999999999999999999999999::::::::::::::::::::::::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<==========================================================================================================================================================================================================================================ÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÎÎÎÎÎÎĎĎĎĎĎĎĐĐĐĐĐĐŃŃŃŃŇŇŇŇÓÓÓÓÔÔŐŐŐŐÖÖ××××ŘŘŮŮÚÚŰŰÜÜÝÝŢŢßßŕŕááăăääććççééëëííďďńńóóőőřřúúüü˙˙  !!""##$$%%&&&&''(())))**++++,,,,----......////0000001111111122222222333333334444444444445555555555556666666666666666777777777777777777778888888888888888888888889999999999999999999999999999999999::::::::::::::::::::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<====================================================================================================================================================================================================================================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>ÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÎÎÎÎÎÎĎĎĎĎĐĐĐĐĐĐŃŃŃŃŇŇŇŇÓÓÓÓÔÔŐŐŐŐÖÖ××ŘŘŮŮŮŮÚÚŰŰÜÜŢŢßßŕŕââăăĺĺććččęęěěîîđđňňőő÷÷úúüü˙˙  !!""##$$%%&&''(((())****++,,,,----....//////000000111111222222223333333344444444445555555555556666666666666677777777777777777777888888888888888888888899999999999999999999999999999999::::::::::::::::::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<========================================================================================================================================================================================================================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>ÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘËËËËËËËËĚĚĚĚĚĚĚĚÍÍÍÍÍÍÎÎÎÎÎÎĎĎĎĎĎĎĐĐĐĐŃŃŃŃŇŇŇŇÓÓÔÔÔÔŐŐÖÖÖÖ××ŘŘŮŮÚÚŰŰÜÜŢŢßßŕŕââăăĺĺççééëëííďďňňôô÷÷ůůüü˙˙  !!""##$$%%&&''(())****++,,,,----....//////00001111112222222233333333444444445555555555556666666666666677777777777777777788888888888888888888889999999999999999999999999999::::::::::::::::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<==========================================================================================================================================================================================================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>ÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘËËËËËËËËĚĚĚĚĚĚĚĚÍÍÍÍÍÍÎÎÎÎÎÎĎĎĎĎĐĐĐĐŃŃŃŃŇŇŇŇÓÓÔÔÔÔŐŐÖÖ××ŘŘŮŮÚÚŰŰÜÜÝÝßßŕŕââääććččęęěěîîńńóóööůůüü˙˙  !!""$$%%&&''(())))**++++,,----....////000000111122222222333333444444444455555555556666666666666677777777777777778888888888888888888899999999999999999999999999::::::::::::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<==============================================================================================================================================================================================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>ŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘËËËËËËËËĚĚĚĚĚĚÍÍÍÍÍÍÎÎÎÎĎĎĎĎĐĐĐĐŃŃŃŃŇŇŇŇÓÓÔÔŐŐÖÖÖÖ××ŘŘÚÚŰŰÜÜÝÝßßááââääććččëëííđđóóööůůüü˙˙  !!##$$%%&&''(())**++++,,----....////0000111111222222333333444444445555555555666666666666777777777777777788888888888888888899999999999999999999999999::::::::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<==============================================================================================================================================================================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>ŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘËËËËËËËËĚĚĚĚĚĚÍÍÍÍÎÎÎÎÎÎĎĎĎĎĐĐŃŃŃŃŇŇÓÓÓÓÔÔŐŐÖÖ××ŘŘŮŮÚÚÜÜÝÝßßááăăĺĺççééěěďďňňőőřřűű˙˙  !!##$$%%''(())****++,,----..////00001111112222223333334444444455555555666666666666777777777777778888888888888888999999999999999999999999::::::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<==================================================================================================================================================================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>ŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘËËËËËËĚĚĚĚĚĚÍÍÍÍÎÎÎÎĎĎĎĎĐĐĐĐŃŃŇŇÓÓÓÓÔÔŐŐÖÖ××ŮŮÚÚÜÜÝÝßßááăăĺĺččęęííńńôô÷÷űű˙˙  !!##$$&&''(())**++,,----..////0000111122222233333344444455555555666666666677777777777777888888888888888899999999999999999999::::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<====================================================================================================================================================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>ŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČÉÉÉÉÉÉÉÉĘĘĘĘĘĘËËËËËËĚĚĚĚĚĚÍÍÍÍÎÎÎÎĎĎĐĐĐĐŃŃŇŇÓÓÔÔŐŐÖÖ××ŘŘÚÚŰŰÝÝßßááăăććééěěďďóó÷÷űű˙˙  ""##%%&&(())**++,,----..////00111122222233334444445555555566666666667777777777778888888888888899999999999999999999::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<========================================================================================================================================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>ŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČÉÉÉÉÉÉĘĘĘĘĘĘĘĘËËËËĚĚĚĚÍÍÍÍÎÎÎÎĎĎĎĎĐĐŃŃŇŇÓÓÔÔŐŐÖÖŘŘŮŮŰŰÝÝßßááääççęęîîňňööúú˙˙  ""$$%%''((**++,,--....//000011112222333344444455555566666666777777777777888888888888999999999999999999::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<==========================================================================================================================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>ŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇČČČČČČČČÉÉÉÉÉÉĘĘĘĘĘĘËËËËĚĚĚĚÍÍÍÍÎÎÎÎĎĎĐĐŃŃŇŇÓÓÔÔŐŐ××ŮŮÚÚÜÜßßââĺĺččěěđđőőúú˙˙  ""$$&&(())**,,--..////00111122223333444455555566666666777777777788888888889999999999999999::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<============================================================================================================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>??????????????????????????????????????????????????????????????????????????ŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇČČČČČČÉÉÉÉÉÉĘĘĘĘËËËËĚĚĚĚÍÍÍÍÎÎĎĎĐĐŃŃŇŇÓÓÔÔÖÖŘŘÚÚÜÜßßââććęęîîóóůů˙˙  ""%%''))**++--..//000011222233444444555566666666777777778888888888999999999999::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<==============================================================================================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????ŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇČČČČČČÉÉÉÉÉÉĘĘĘĘËËËËĚĚÍÍÎÎÎÎĎĎŃŃŇŇÓÓŐŐ××ŮŮÜÜßßăăççěěňňřř˙˙  ##%%((**++--..//0011222233444455556666667777777788888888999999999999::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<==================================================================================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????ŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆÇÇÇÇÇÇČČČČČČÉÉÉÉĘĘËËËËĚĚÍÍÎÎĎĎĐĐŇŇÔÔÖÖŘŘŰŰßßăăééďď÷÷˙˙ ##&&))++--..//112222334455556666667777778888889999999999::::::::::::::;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<====================================================================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????żżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆÇÇÇÇÇÇČČČČÉÉĘĘĘĘËËĚĚÍÍÎÎĐĐŇŇÔÔ××ÚÚßßĺĺěěőő˙˙  $$((**--//0011223344556666777777888899999999::::::::::::;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<======================================================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????żżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĆĆĆĆÇÇÇÇČČČČÉÉĘĘËËĚĚÎÎĎĎŇŇŐŐŮŮßßççňň˙˙  %%**--//11224455666677778888999999::::::::;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<========================================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????żżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄĹĹĹĹĆĆĆĆÇÇČČÉÉĘĘĚĚÎÎŇŇ××ßßěě˙˙ ((--002244667777889999::::::;;;;;;;;<<<<<<<<<<<<============================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????żżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂÄÄĹĹĆĆÇÇÉÉĚĚŇŇßß˙˙ --22667799::::;;;;<<<<<<==============>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????żżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżż@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@żżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽ»»»»»»şşąą¸¸··µµ˛˛¬¬źź__RRMMIIHHFFEEEEDDDDCCCCCCBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@żżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»şşşşşşąąąą¸¸¸¸··¶¶µµ´´˛˛°°¬¬§§źź’’ll__WWRROOMMKKIIHHHHGGFFFFEEEEEEDDDDDDDDCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@żżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»şşşşşşşşşşąąąąąą¸¸¸¸····¶¶¶¶µµ´´łł˛˛°°ŻŻ¬¬©©ĄĄźź——ŚŚrrgg__ZZUURRPPNNMMKKJJIIIIHHHHGGGGFFFFFFEEEEEEEEDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@żżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşąąąąąąąąąą¸¸¸¸¸¸······¶¶¶¶µµ´´´´łł˛˛±±°°®®¬¬ŞŞ§§¤¤źź™™’’‰‰uullee__[[WWUURRPPOONNMMLLKKJJIIIIHHHHHHGGGGFFFFFFFFEEEEEEEEEEEEDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşąąąąąąąąąąąą¸¸¸¸¸¸¸¸······¶¶¶¶¶¶µµµµ´´łłłł˛˛±±°°ŻŻ®®¬¬ŞŞ¨¨¦¦ŁŁźź››••ŹŹ‡‡wwppiidd__\\YYVVTTRRQQPPNNMMMMLLKKKKJJIIIIIIHHHHHHGGGGGGFFFFFFFFFFEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸········¶¶¶¶¶¶µµµµµµ´´´´łłłł˛˛±±°°°°ŻŻ­­¬¬««©©§§ĄĄ˘˘źź››——’’ŚŚ††yyrrllggcc__\\ZZWWUUTTRRQQPPOONNMMMMLLKKKKJJJJIIIIIIHHHHHHHHGGGGGGGGFFFFFFFFFFFFEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸··········¶¶¶¶¶¶µµµµµµ´´´´łłłłłł˛˛±±±±°°ŻŻ®®­­¬¬««ŞŞ¨¨¦¦¤¤˘˘źźśś””‹‹……yyttoojjffbb__]]ZZXXWWUUTTRRQQPPOOOONNMMMMLLKKKKKKJJJJIIIIIIIIHHHHHHHHGGGGGGGGGGFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸··········¶¶¶¶¶¶¶¶µµµµµµ´´´´´´łłłł˛˛˛˛±±±±°°°°ŻŻ®®­­¬¬««ŞŞ©©§§ĄĄ¤¤˘˘źźśś™™––’’ŽŽ‰‰„„zzuuqqlliieebb__]][[YYWWVVUUSSRRQQPPPPOONNNNMMMMLLLLKKKKJJJJJJIIIIIIIIHHHHHHHHHHGGGGGGGGGGFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸············¶¶¶¶¶¶¶¶¶¶µµµµµµµµ´´´´´´łłłł˛˛˛˛±±±±°°°°ŻŻŻŻ®®­­¬¬««ŞŞ©©¨¨¦¦ĄĄŁŁˇˇźźťťšš——””ŚŚ„„{{vvrrnnkkggddbb__]][[ZZXXWWUUTTSSRRQQQQPPOOOONNNNMMMMLLLLKKKKKKJJJJJJIIIIIIIIHHHHHHHHHHHHGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸············¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµ´´´´´´łłłłłł˛˛˛˛˛˛±±±±°°°°ŻŻ®®®®­­¬¬««ŞŞ©©¨¨§§¦¦¤¤ŁŁˇˇźźťť››••’’ŹŹ‹‹‡‡{{wwsspplliiffddbb__]]\\ZZYYWWVVUUTTSSRRRRQQPPPPOONNNNMMMMMMLLLLKKKKKKKKJJJJJJIIIIIIIIIIHHHHHHHHHHHHGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸··············¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµ´´´´´´´´łłłłłł˛˛˛˛˛˛±±±±°°°°ŻŻŻŻ®®®®­­¬¬««««ŞŞ©©¨¨§§ĄĄ¤¤ŁŁˇˇźźťť››™™––””‘‘ŤŤŠŠ‡‡||xxttqqnnkkhhffccaa__^^\\[[YYXXWWVVUUTTSSRRRRQQPPPPOOOONNNNMMMMMMLLLLLLKKKKKKJJJJJJJJIIIIIIIIIIHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸················¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµ´´´´´´´´łłłłłłłł˛˛˛˛˛˛±±±±°°°°°°ŻŻŻŻ®®­­­­¬¬««««ŞŞ©©¨¨§§¦¦ĄĄ¤¤˘˘ˇˇźźťť››™™——••’’ŹŹŚŚ‰‰††||yyuurroolljjggeeccaa__^^\\[[ZZXXWWVVUUUUTTSSRRRRQQPPPPOOOONNNNNNMMMMMMLLLLLLKKKKKKKKJJJJJJJJIIIIIIIIIIIIHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸················¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµ´´´´´´´´´´łłłłłłłł˛˛˛˛˛˛±±±±±±°°°°ŻŻŻŻ®®®®­­­­¬¬¬¬««ŞŞ©©©©¨¨§§¦¦ĄĄŁŁ˘˘ˇˇźźžžśśšš––““‘‘ŽŽ‹‹……‚‚||yyvvssppnnkkiiggeeccaa__^^\\[[ZZYYXXWWVVUUTTTTSSRRRRQQQQPPPPOOOONNNNNNMMMMMMLLLLLLKKKKKKKKJJJJJJJJJJIIIIIIIIIIIIHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸··················¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµ´´´´´´´´łłłłłłłłłł˛˛˛˛˛˛±±±±±±°°°°°°ŻŻŻŻ®®®®­­­­¬¬¬¬««ŞŞŞŞ©©¨¨§§¦¦ĄĄ¤¤ŁŁ˘˘ˇˇźźžžśśšš––””’’ŤŤ‹‹……‚‚||yywwttqqoolljjhhffddbbaa__^^]][[ZZYYXXWWWWVVUUTTTTSSRRRRQQQQPPPPOOOOOONNNNMMMMMMMMLLLLLLKKKKKKKKKKJJJJJJJJJJIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸····················¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµ´´´´´´´´´´łłłłłłłł˛˛˛˛˛˛˛˛±±±±±±°°°°°°ŻŻŻŻŻŻ®®®®­­­­¬¬¬¬««ŞŞŞŞ©©¨¨¨¨§§¦¦ĄĄ¤¤ŁŁ˘˘  źźžžśś››™™——••““‘‘ŹŹŚŚŠŠ‡‡……‚‚}}zzwwuurrppnnkkiiggffddbbaa__^^]]\\[[ZZYYXXWWVVUUUUTTSSSSRRRRQQQQPPPPPPOOOONNNNNNMMMMMMMMLLLLLLLLKKKKKKKKKKJJJJJJJJJJIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸····················¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµ´´´´´´´´´´´´łłłłłłłł˛˛˛˛˛˛˛˛±±±±±±±±°°°°°°ŻŻŻŻ®®®®®®­­­­¬¬¬¬««««ŞŞ©©©©¨¨§§¦¦ĄĄĄĄ¤¤ŁŁ˘˘  źźžžśś››™™––””’’ŽŽŚŚ‰‰‡‡„„‚‚}}zzxxuussqqoolljjiiggeeddbbaa__^^]]\\[[ZZYYXXWWWWVVUUUUTTSSSSRRRRQQQQPPPPPPOOOOOONNNNNNMMMMMMMMLLLLLLLLKKKKKKKKKKJJJJJJJJJJJJIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸······················¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµ´´´´´´´´´´´´łłłłłłłłłł˛˛˛˛˛˛˛˛±±±±±±±±°°°°°°ŻŻŻŻŻŻ®®®®®®­­­­¬¬¬¬««««ŞŞ©©©©¨¨¨¨§§¦¦ĄĄ¤¤ŁŁ˘˘ˇˇ  źźžžťť››šš——••““‘‘ŹŹŤŤ‹‹‰‰††„„‚‚}}{{xxvvttqqoommkkjjhhffeeccbbaa__^^]]\\[[ZZYYYYXXWWVVVVUUTTTTSSSSRRRRQQQQQQPPPPOOOOOONNNNNNNNMMMMMMMMLLLLLLLLKKKKKKKKKKKKJJJJJJJJJJJJIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAA˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸························¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµ´´´´´´´´´´´´łłłłłłłłłł˛˛˛˛˛˛˛˛±±±±±±±±°°°°°°°°ŻŻŻŻŻŻ®®®®­­­­­­¬¬¬¬««««ŞŞŞŞ©©¨¨¨¨§§¦¦¦¦ĄĄ¤¤ŁŁ˘˘ˇˇ  źźžžťť››šš™™——––””’’ŽŽŚŚŠŠ††„„‚‚}}{{yyvvttrrppnnllkkiiggffddccbbaa__^^]]\\[[ZZZZYYXXWWWWVVUUUUTTTTSSSSRRRRQQQQQQPPPPPPOOOOOONNNNNNNNMMMMMMMMLLLLLLLLLLKKKKKKKKKKJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸························¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµ´´´´´´´´´´´´łłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛±±±±±±±±°°°°°°ŻŻŻŻŻŻ®®®®®®­­­­­­¬¬¬¬««««ŞŞŞŞ©©©©¨¨§§§§¦¦ĄĄĄĄ¤¤ŁŁ˘˘ˇˇ  źźžžťťśśšš™™––••““‘‘ŹŹŽŽŚŚŠŠ††„„}}{{yywwuussqqoommlljjhhggeeddccbb``__^^]]\\\\[[ZZYYXXXXWWVVVVUUUUTTTTSSSSRRRRRRQQQQPPPPPPOOOOOOOONNNNNNMMMMMMMMMMLLLLLLLLLLKKKKKKKKKKKKJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸························¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´łłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛±±±±±±±±°°°°°°°°ŻŻŻŻŻŻ®®®®®®­­­­­­¬¬¬¬««««ŞŞŞŞ©©©©¨¨¨¨§§§§¦¦ĄĄ¤¤¤¤ŁŁ˘˘ˇˇ  źźžžťťśś››™™——••””’’ŹŹŤŤ‹‹‰‰‡‡……}}{{yywwuussrrppnnllkkiihhffeeddccbb``__^^]]]]\\[[ZZYYYYXXWWWWVVVVUUUUTTTTSSSSRRRRRRQQQQPPPPPPPPOOOOOONNNNNNNNMMMMMMMMMMLLLLLLLLLLKKKKKKKKKKKKKKJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸····························¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´łłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛±±±±±±±±°°°°°°°°ŻŻŻŻŻŻŻŻ®®®®®®­­­­­­¬¬¬¬««««««ŞŞŞŞ©©©©¨¨§§§§¦¦¦¦ĄĄ¤¤ŁŁŁŁ˘˘ˇˇ  źźžžťťśś››šš——––””““‘‘ŽŽŚŚ‹‹‰‰‡‡……}}{{yyxxvvttrrppoommlljjiiggffeeddbbaa``__^^^^]]\\[[ZZZZYYXXXXWWWWVVUUUUTTTTTTSSSSRRRRRRQQQQQQPPPPPPOOOOOOOONNNNNNNNMMMMMMMMMMLLLLLLLLLLKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸····························¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´łłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±°°°°°°°°ŻŻŻŻŻŻ®®®®®®®®­­­­­­¬¬¬¬««««««ŞŞŞŞ©©©©¨¨¨¨§§§§¦¦ĄĄĄĄ¤¤ŁŁŁŁ˘˘ˇˇ  źźžžťťśś››šš™™––••””’’‘‘ŹŹŤŤŚŚŠŠ‡‡……}}||zzxxvvttssqqoonnllkkjjhhggffeeccbbaa``__^^^^]]\\[[[[ZZYYYYXXWWWWVVVVUUUUTTTTSSSSSSRRRRRRQQQQQQPPPPPPOOOOOOOONNNNNNNNNNMMMMMMMMMMLLLLLLLLLLLLKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸····························¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´łłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±°°°°°°°°ŻŻŻŻŻŻŻŻ®®®®®®®®­­­­­­¬¬¬¬««««««ŞŞŞŞ©©©©¨¨¨¨§§§§¦¦¦¦ĄĄ¤¤¤¤ŁŁ˘˘˘˘ˇˇ  źźžžťťśś››šš™™——••””““‘‘ŽŽŤŤ‹‹ŠŠ††……}}||zzxxwwuussrrppoommlljjiihhggeeddccbbaa``____^^]]\\[[[[ZZYYYYXXXXWWWWVVVVUUUUTTTTSSSSSSRRRRRRQQQQQQPPPPPPPPOOOOOOOONNNNNNNNNNMMMMMMMMMMLLLLLLLLLLLLKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸································¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´łłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±°°°°°°°°°°ŻŻŻŻŻŻŻŻ®®®®®®­­­­­­­­¬¬¬¬««««««ŞŞŞŞŞŞ©©©©¨¨¨¨§§§§¦¦ĄĄĄĄ¤¤¤¤ŁŁ˘˘˘˘ˇˇ  źźžžťťśś››šš™™——––••““’’‘‘ŹŹŽŽŚŚ‹‹‰‰††„„~~||zzyywwuuttrrqqoonnllkkjjiiggffeeddccbbaa``____^^]]\\\\[[ZZZZYYXXXXWWWWVVVVUUUUUUTTTTSSSSSSRRRRRRQQQQQQPPPPPPPPOOOOOOOONNNNNNNNNNMMMMMMMMMMMMLLLLLLLLLLLLKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸································¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±°°°°°°°°ŻŻŻŻŻŻŻŻ®®®®®®®®­­­­­­­­¬¬¬¬¬¬««««ŞŞŞŞŞŞ©©©©¨¨¨¨§§§§¦¦¦¦ĄĄĄĄ¤¤ŁŁŁŁ˘˘ˇˇˇˇ  źźžžťťťťśś››šš™™——––••””““‘‘ŹŹŤŤŚŚŠŠ‰‰‡‡††„„‚‚~~||zzyywwvvttssqqppnnmmllkkiihhggffeeddccbbaa``____^^]]\\\\[[ZZZZYYYYXXXXWWWWVVVVUUUUTTTTTTSSSSSSRRRRRRQQQQQQQQPPPPPPPPOOOOOOOONNNNNNNNNNMMMMMMMMMMMMLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸································¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±°°°°°°°°°°ŻŻŻŻŻŻŻŻ®®®®®®®®­­­­­­¬¬¬¬¬¬¬¬««««ŞŞŞŞŞŞ©©©©©©¨¨¨¨§§§§¦¦¦¦ĄĄĄĄ¤¤ŁŁŁŁ˘˘ˇˇˇˇ  źźžžžžťťśś››šš™™——––””““’’‘‘ŹŹŽŽŤŤ‹‹ŠŠ‡‡……„„‚‚~~||{{yyxxvvuussrrppoonnllkkjjiihhggffeeddccbbaa``____^^]]\\\\[[[[ZZYYYYXXXXWWWWVVVVVVUUUUTTTTTTSSSSSSRRRRRRQQQQQQQQPPPPPPPPOOOOOOOOOONNNNNNNNNNMMMMMMMMMMMMLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸····································¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻ®®®®®®®®­­­­­­¬¬¬¬¬¬¬¬««««««ŞŞŞŞ©©©©©©¨¨¨¨§§§§¦¦¦¦ĄĄĄĄ¤¤¤¤ŁŁŁŁ˘˘ˇˇˇˇ  źźžžžžťťśś››šš™™——––••””““‘‘ŹŹŽŽŚŚ‹‹ŠŠ‡‡……„„‚‚~~||{{yyxxvvuuttrrqqppnnmmllkkjjhhggffeeddddccbbaa``____^^]]]]\\[[[[ZZZZYYYYXXXXWWWWVVVVUUUUUUTTTTTTSSSSSSRRRRRRQQQQQQQQPPPPPPPPOOOOOOOOOONNNNNNNNNNNNMMMMMMMMMMMMLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸····································¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±°°°°°°°°°°ŻŻŻŻŻŻŻŻ®®®®®®®®®®­­­­­­¬¬¬¬¬¬¬¬««««««ŞŞŞŞŞŞ©©©©¨¨¨¨¨¨§§§§¦¦¦¦ĄĄĄĄ¤¤¤¤ŁŁ˘˘˘˘ˇˇˇˇ  źźžžžžťťśś››šš™™——––••””““’’‘‘ŽŽŤŤŚŚ‹‹‰‰††……„„‚‚~~||{{yyxxwwuuttssqqppoonnllkkjjiihhggffeeddccbbbbaa``____^^]]]]\\[[[[ZZZZYYYYXXXXWWWWWWVVVVUUUUUUTTTTTTSSSSSSRRRRRRQQQQQQQQPPPPPPPPPPOOOOOOOOOONNNNNNNNNNMMMMMMMMMMMMMMLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸······································¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻ®®®®®®®®­­­­­­­­¬¬¬¬¬¬¬¬««««««ŞŞŞŞŞŞ©©©©©©¨¨¨¨§§§§¦¦¦¦¦¦ĄĄĄĄ¤¤ŁŁŁŁ˘˘˘˘ˇˇ    źźžžžžťťśś››šššš™™——––••””““’’ŹŹŽŽŤŤ‹‹ŠŠ‰‰††……‚‚~~||{{zzxxwwvvttssrrqqoonnmmllkkjjiihhggffeeddccbbbbaa``____^^]]]]\\\\[[ZZZZYYYYXXXXXXWWWWVVVVVVUUUUTTTTTTTTSSSSSSRRRRRRRRQQQQQQQQPPPPPPPPOOOOOOOOOONNNNNNNNNNNNMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸······································¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻ®®®®®®®®­­­­­­­­¬¬¬¬¬¬¬¬««««««ŞŞŞŞŞŞ©©©©©©¨¨¨¨¨¨§§§§¦¦¦¦ĄĄĄĄ¤¤¤¤ŁŁŁŁ˘˘˘˘ˇˇ    źźžžžžťťśś››››šš™™——––••””““’’‘‘ŹŹŽŽŚŚ‹‹ŠŠ‰‰‡‡††……‚‚~~}}{{zzyywwvvuussrrqqppoonnllkkjjiihhggffffeeddccbbbbaa``____^^]]]]\\\\[[[[ZZZZYYYYXXXXWWWWWWVVVVUUUUUUTTTTTTSSSSSSSSRRRRRRRRQQQQQQQQPPPPPPPPPPOOOOOOOOOONNNNNNNNNNNNMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸········································¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®­­­­­­­­¬¬¬¬¬¬¬¬««««««ŞŞŞŞŞŞŞŞ©©©©¨¨¨¨¨¨§§§§§§¦¦¦¦ĄĄĄĄ¤¤¤¤ŁŁŁŁ˘˘˘˘ˇˇ    źźžžžžťťśśśś››šš™™————––••””““’’ŹŹŽŽŤŤŚŚ‹‹ŠŠ‡‡††„„‚‚~~}}{{zzyywwvvuuttssqqppoonnmmllkkjjiihhggffeeeeddccbbaaaa``____^^^^]]\\\\[[[[ZZZZYYYYXXXXXXWWWWVVVVVVUUUUUUTTTTTTSSSSSSSSRRRRRRRRQQQQQQQQPPPPPPPPPPOOOOOOOOOOOONNNNNNNNNNNNMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸··········································¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®­­­­­­­­¬¬¬¬¬¬¬¬««««««««ŞŞŞŞŞŞ©©©©©©¨¨¨¨¨¨§§§§¦¦¦¦ĄĄĄĄĄĄ¤¤¤¤ŁŁŁŁ˘˘˘˘ˇˇ    źźžžžžťťśśśś››šš™™™™——––••””““’’‘‘ŹŹŽŽŤŤŚŚŠŠ‰‰‡‡††„„‚‚~~}}{{zzyyxxwwuuttssrrqqppoommllkkjjjjiihhggffeeddddccbbaaaa``____^^^^]]\\\\[[[[ZZZZYYYYYYXXXXWWWWWWVVVVVVUUUUUUTTTTTTSSSSSSSSRRRRRRRRQQQQQQQQPPPPPPPPPPOOOOOOOOOOOONNNNNNNNNNNNNNMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸··········································¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®­­­­­­­­¬¬¬¬¬¬¬¬««««««««ŞŞŞŞŞŞ©©©©©©¨¨¨¨¨¨§§§§§§¦¦¦¦ĄĄĄĄ¤¤¤¤¤¤ŁŁŁŁ˘˘ˇˇˇˇ    źźźźžžťťťťśś››šššš™™——––••””””““’’‘‘ŽŽŤŤŚŚ‹‹ŠŠ‰‰‡‡……„„‚‚€€~~}}||zzyyxxwwvvttssrrqqppoonnmmllkkjjiihhggggffeeddccccbbaaaa``____^^^^]]]]\\\\[[[[ZZZZYYYYXXXXXXWWWWVVVVVVUUUUUUTTTTTTTTSSSSSSSSRRRRRRRRQQQQQQQQQQPPPPPPPPPPOOOOOOOOOOOONNNNNNNNNNNNMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸············································¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®­­­­­­­­¬¬¬¬¬¬¬¬««««««««ŞŞŞŞŞŞ©©©©©©©©¨¨¨¨¨¨§§§§¦¦¦¦¦¦ĄĄĄĄ¤¤¤¤ŁŁŁŁ˘˘˘˘ˇˇˇˇ    źźźźžžťťťťśś››šššš™™————––••””““’’‘‘ŹŹŽŽŤŤŚŚ‹‹ŠŠ‰‰‡‡††……„„‚‚€€~~}}||{{yyxxwwvvuuttssqqppoonnmmllkkkkjjiihhggffffeeddccccbbaaaa``____^^^^]]]]\\\\[[[[ZZZZYYYYYYXXXXWWWWWWVVVVVVUUUUUUTTTTTTTTSSSSSSSSRRRRRRRRQQQQQQQQQQPPPPPPPPPPOOOOOOOOOOOONNNNNNNNNNNNNNMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸··············································¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®­­­­­­­­­­¬¬¬¬¬¬¬¬««««««««ŞŞŞŞŞŞŞŞ©©©©©©¨¨¨¨¨¨§§§§§§¦¦¦¦ĄĄĄĄĄĄ¤¤¤¤ŁŁŁŁ˘˘˘˘ˇˇˇˇ    źźźźžžťťťťśś››››šš™™——––••””““““’’‘‘ŹŹŽŽŤŤŚŚ‹‹‰‰‡‡††……„„‚‚€€~~}}||{{yyxxwwvvuuttssrrqqppoonnmmllkkjjiihhhhggffeeeeddccbbbbaaaa``____^^^^]]]]\\\\[[[[ZZZZYYYYYYXXXXXXWWWWWWVVVVVVUUUUUUTTTTTTTTSSSSSSSSRRRRRRRRQQQQQQQQQQPPPPPPPPPPPPOOOOOOOOOOOONNNNNNNNNNNNNNMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCC»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸··············································¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®­­­­­­­­­­¬¬¬¬¬¬¬¬««««««««ŞŞŞŞŞŞŞŞ©©©©©©¨¨¨¨¨¨§§§§§§¦¦¦¦¦¦ĄĄĄĄ¤¤¤¤¤¤ŁŁŁŁ˘˘˘˘ˇˇˇˇ    źźźźžžťťťťśś››››šš™™™™——––––••””““’’‘‘ŹŹŽŽŤŤŚŚ‹‹ŠŠ‰‰‡‡††……„„‚‚€€~~}}||{{zzyywwvvuuttssrrqqppoonnmmllllkkjjiihhggggffeeddddccbbbbaaaa``____^^^^]]]]\\\\[[[[ZZZZZZYYYYXXXXXXWWWWWWVVVVVVUUUUUUUUTTTTTTTTSSSSSSSSRRRRRRRRQQQQQQQQQQPPPPPPPPPPPPOOOOOOOOOOOONNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸················································¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬««««««««ŞŞŞŞŞŞ©©©©©©©©¨¨¨¨¨¨§§§§§§¦¦¦¦¦¦ĄĄĄĄ¤¤¤¤¤¤ŁŁŁŁ˘˘˘˘ˇˇˇˇ    źźźźžžťťťťśśśś››šššš™™————––••””““““’’‘‘ŹŹŽŽŤŤŚŚ‹‹ŠŠ‰‰‡‡††……„„€€~~}}||{{zzyyxxwwvvuuttssrrqqppoonnmmllkkjjiiiihhggffffeeddddccbbbbaaaa``____^^^^]]]]\\\\[[[[[[ZZZZYYYYYYXXXXXXWWWWWWVVVVVVUUUUUUUUTTTTTTSSSSSSSSSSRRRRRRRRQQQQQQQQQQQQPPPPPPPPPPOOOOOOOOOOOOOONNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸··················································¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬««««««««ŞŞŞŞŞŞŞŞ©©©©©©¨¨¨¨¨¨§§§§§§¦¦¦¦¦¦ĄĄĄĄĄĄ¤¤¤¤ŁŁŁŁŁŁ˘˘˘˘ˇˇˇˇ    źźźźžžťťťťśśśś››šššš™™——––••••””““’’‘‘ŹŹŹŹŽŽŤŤŚŚ‹‹ŠŠ‰‰‡‡††……„„‚‚€€~~}}||{{zzyyxxwwvvuuttssrrqqppoonnmmllllkkjjiihhhhggffeeeeddccccbbbbaa````____^^^^]]]]\\\\\\[[[[ZZZZYYYYYYXXXXXXWWWWWWVVVVVVVVUUUUUUTTTTTTTTSSSSSSSSSSRRRRRRRRRRQQQQQQQQQQPPPPPPPPPPPPOOOOOOOOOOOOOONNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸··················································¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬««««««««ŞŞŞŞŞŞŞŞ©©©©©©©©¨¨¨¨¨¨§§§§§§¦¦¦¦¦¦ĄĄĄĄĄĄ¤¤¤¤ŁŁŁŁ˘˘˘˘˘˘ˇˇˇˇ    źźźźžžžžťťśśśś››››šš™™™™——––––••””““’’’’‘‘ŹŹŽŽŤŤŚŚ‹‹ŠŠ‰‰‡‡††……„„‚‚€€~~}}||{{zzyyxxwwvvuuttssrrqqppoooonnmmllkkjjjjiihhggggffeeeeddccccbbbbaa````____^^^^]]]]\\\\\\[[[[ZZZZZZYYYYYYXXXXXXWWWWWWVVVVVVUUUUUUUUTTTTTTTTSSSSSSSSSSRRRRRRRRRRQQQQQQQQQQPPPPPPPPPPPPOOOOOOOOOOOOOONNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸··················································¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬««««««««ŞŞŞŞŞŞŞŞ©©©©©©©©¨¨¨¨¨¨§§§§§§§§¦¦¦¦ĄĄĄĄĄĄ¤¤¤¤¤¤ŁŁŁŁ˘˘˘˘˘˘ˇˇˇˇ    źźźźžžžžťťśśśś››››šš™™™™————––••””””““’’‘‘ŹŹŽŽŤŤŚŚ‹‹ŠŠ‰‰‡‡††……„„‚‚€€~~}}||{{zzyyxxwwvvuuttssssrrqqppoonnmmllllkkjjiiiihhggffffeeddddccccbbbbaa````____^^^^]]]]]]\\\\[[[[ZZZZZZYYYYYYXXXXXXWWWWWWVVVVVVVVUUUUUUUUTTTTTTTTSSSSSSSSSSRRRRRRRRRRQQQQQQQQQQPPPPPPPPPPPPPPOOOOOOOOOOOOOONNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸······················································¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬««««««««ŞŞŞŞŞŞŞŞŞŞ©©©©©©¨¨¨¨¨¨¨¨§§§§§§¦¦¦¦¦¦ĄĄĄĄĄĄ¤¤¤¤¤¤ŁŁŁŁ˘˘˘˘ˇˇˇˇˇˇ    źźźźžžžžťťśśśś››››šššš™™——––••••””““’’’’‘‘ŹŹŽŽŤŤŤŤŚŚ‹‹ŠŠ‰‰‡‡††……„„‚‚€€~~}}||{{zzyyxxwwvvvvuuttssrrqqppoonnnnmmllkkkkjjiihhhhggffffeeddddccccbbaaaa````____^^^^]]]]]]\\\\[[[[[[ZZZZZZYYYYXXXXXXXXWWWWWWVVVVVVVVUUUUUUUUTTTTTTTTSSSSSSSSSSRRRRRRRRRRQQQQQQQQQQQQPPPPPPPPPPPPOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸······················································¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬««««««««««ŞŞŞŞŞŞŞŞ©©©©©©©©¨¨¨¨¨¨§§§§§§§§¦¦¦¦¦¦ĄĄĄĄĄĄ¤¤¤¤ŁŁŁŁŁŁ˘˘˘˘ˇˇˇˇˇˇ    źźźźžžžžťťťťśś››››šššš™™——––––••””””““’’‘‘ŹŹŽŽŤŤŚŚ‹‹‹‹ŠŠ‰‰‡‡††……„„‚‚€€~~}}||{{zzyyyyxxwwvvuuttssrrqqppppoonnmmllllkkjjiiiihhggggffeeeeddddccbbbbaaaa````____^^^^^^]]]]\\\\[[[[[[ZZZZZZYYYYYYXXXXXXWWWWWWWWVVVVVVUUUUUUUUTTTTTTTTTTSSSSSSSSSSRRRRRRRRRRQQQQQQQQQQQQPPPPPPPPPPPPOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸······················································¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬««««««««««ŞŞŞŞŞŞŞŞ©©©©©©©©¨¨¨¨¨¨¨¨§§§§§§¦¦¦¦¦¦ĄĄĄĄĄĄ¤¤¤¤¤¤ŁŁŁŁŁŁ˘˘˘˘ˇˇˇˇ      źźźźžžžžťťťťśśśś››šššš™™™™————––••••””““’’’’‘‘ŹŹŹŹŽŽŤŤŚŚ‹‹ŠŠ‰‰‰‰‡‡††……„„‚‚€€~~}}||{{{{zzyyxxwwvvuuttssrrrrqqppoonnnnmmllkkkkjjiihhhhggggffeeeeddddccbbbbaaaa````____^^^^^^]]]]\\\\\\[[[[ZZZZZZYYYYYYXXXXXXXXWWWWWWVVVVVVVVUUUUUUUUTTTTTTTTTTSSSSSSSSSSRRRRRRRRRRQQQQQQQQQQQQPPPPPPPPPPPPPPOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸··························································¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬««««««««««ŞŞŞŞŞŞŞŞ©©©©©©©©¨¨¨¨¨¨¨¨§§§§§§§§¦¦¦¦¦¦ĄĄĄĄĄĄ¤¤¤¤¤¤ŁŁŁŁŁŁ˘˘˘˘ˇˇˇˇ      źźźźžžžžťťťťśśśś››››šš™™™™——––––••””””““’’‘‘‘‘ŹŹŽŽŤŤŤŤŚŚ‹‹ŠŠ‰‰‡‡‡‡††……„„‚‚€€~~}}||||{{zzyyxxwwvvuuttttssrrqqppoooonnmmllllkkjjjjiihhhhggffffeeeeddccccbbbbaaaa````____^^^^^^]]]]\\\\\\[[[[[[ZZZZZZYYYYYYXXXXXXWWWWWWWWVVVVVVVVUUUUUUUUTTTTTTTTSSSSSSSSSSSSRRRRRRRRRRQQQQQQQQQQQQPPPPPPPPPPPPPPOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸··························································¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬««««««««««ŞŞŞŞŞŞŞŞŞŞ©©©©©©©©¨¨¨¨¨¨¨¨§§§§§§¦¦¦¦¦¦¦¦ĄĄĄĄĄĄ¤¤¤¤¤¤ŁŁŁŁ˘˘˘˘˘˘ˇˇˇˇ      źźźźžžžžťťťťśśśś››››šššš™™——––––••••””““’’’’‘‘ŹŹŹŹŽŽŤŤŚŚ‹‹‹‹ŠŠ‰‰‡‡††††……„„‚‚€€~~}}}}||{{zzyyxxwwvvvvuuttssrrqqqqppoonnnnmmllkkkkjjiiiihhggggffffeeddddccccbbbbaaaa````____^^^^^^]]]]\\\\\\[[[[[[ZZZZZZYYYYYYXXXXXXXXWWWWWWVVVVVVVVUUUUUUUUUUTTTTTTTTSSSSSSSSSSSSRRRRRRRRRRQQQQQQQQQQQQQQPPPPPPPPPPPPPPOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸··························································¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬««««««««««ŞŞŞŞŞŞŞŞŞŞ©©©©©©©©¨¨¨¨¨¨¨¨§§§§§§§§¦¦¦¦¦¦ĄĄĄĄĄĄ¤¤¤¤¤¤ŁŁŁŁŁŁ˘˘˘˘˘˘ˇˇˇˇ      źźźźžžžžťťťťśśśś››››šššš™™™™————––••••””““““’’‘‘‘‘ŹŹŽŽŽŽŤŤŚŚ‹‹ŠŠŠŠ‰‰‡‡††…………„„‚‚€€~~}}}}||{{zzyyxxwwwwvvuuttssrrrrqqppoooonnmmllllkkjjjjiihhhhggggffeeeeddddccccbbbbaaaa````______^^^^]]]]]]\\\\[[[[[[ZZZZZZYYYYYYXXXXXXXXWWWWWWWWVVVVVVVVUUUUUUUUTTTTTTTTTTSSSSSSSSSSRRRRRRRRRRRRQQQQQQQQQQQQQQPPPPPPPPPPPPPPOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸······························································¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬««««««««««««ŞŞŞŞŞŞŞŞ©©©©©©©©©©¨¨¨¨¨¨¨¨§§§§§§¦¦¦¦¦¦¦¦ĄĄĄĄĄĄ¤¤¤¤¤¤ŁŁŁŁŁŁ˘˘˘˘˘˘ˇˇˇˇ      źźźźžžžžťťťťśśśś››››šššš™™™™——––––••””””““’’’’‘‘ŹŹŽŽŤŤŤŤŚŚ‹‹ŠŠ‰‰‰‰‡‡††……„„„„‚‚€€~~~~}}||{{zzyyxxxxwwvvuuttttssrrqqppppoonnmmmmllkkkkjjiiiihhhhggffffeeeeddddccccbbbbaaaa````______^^^^]]]]]]\\\\[[[[[[ZZZZZZZZYYYYYYXXXXXXXXWWWWWWVVVVVVVVVVUUUUUUUUTTTTTTTTTTSSSSSSSSSSRRRRRRRRRRRRQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸······························································¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬««««««««««««ŞŞŞŞŞŞŞŞŞŞ©©©©©©©©¨¨¨¨¨¨¨¨§§§§§§§§¦¦¦¦¦¦ĄĄĄĄĄĄĄĄ¤¤¤¤¤¤ŁŁŁŁŁŁ˘˘˘˘˘˘ˇˇˇˇ      źźźźžžžžťťťťśśśś››››šššš™™™™————––••••””““““’’‘‘‘‘ŹŹŹŹŽŽŤŤŚŚŚŚ‹‹ŠŠ‰‰‡‡††……„„‚‚€€~~~~}}||{{zzyyyyxxwwvvuuuuttssrrqqqqppoooonnmmllllkkjjjjiiiihhggggffffeeeeddddccccbbbbaaaa````______^^^^]]]]]]\\\\\\[[[[[[ZZZZZZYYYYYYXXXXXXXXWWWWWWWWVVVVVVVVUUUUUUUUUUTTTTTTTTTTSSSSSSSSSSRRRRRRRRRRRRRRQQQQQQQQQQQQPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEşşąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸······························································¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬««««««««««ŞŞŞŞŞŞŞŞŞŞ©©©©©©©©¨¨¨¨¨¨¨¨¨¨§§§§§§¦¦¦¦¦¦¦¦ĄĄĄĄĄĄ¤¤¤¤¤¤¤¤ŁŁŁŁŁŁ˘˘˘˘ˇˇˇˇˇˇ      źźźźžžžžťťťťśśśśśś››››šššš™™————––––••””””““’’’’‘‘ŹŹŽŽŽŽŤŤŚŚ‹‹‹‹ŠŠ‰‰‡‡‡‡††……„„‚‚€€~~~~}}||{{zzyyyyxxwwvvvvuuttssrrrrqqppppoonnmmmmllkkkkjjjjiihhhhggggffffeeddddccccbbbbbbaaaa````______^^^^]]]]]]\\\\\\[[[[[[ZZZZZZYYYYYYYYXXXXXXWWWWWWWWWWVVVVVVVVUUUUUUUUUUTTTTTTTTTTSSSSSSSSSSRRRRRRRRRRRRRRQQQQQQQQQQQQQQPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸································································¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬««««««««««ŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©¨¨¨¨¨¨¨¨§§§§§§§§¦¦¦¦¦¦¦¦ĄĄĄĄĄĄ¤¤¤¤¤¤ŁŁŁŁŁŁ˘˘˘˘˘˘ˇˇˇˇˇˇ      źźźźžžžžťťťťťťśśśś››››šššš™™™™————––––••••””““““’’‘‘‘‘ŹŹŹŹŽŽŤŤŤŤŚŚ‹‹ŠŠŠŠ‰‰‡‡††††……„„‚‚‚‚€€~~~~}}||{{zzzzyyxxwwvvvvuuttssssrrqqqqppoonnnnmmllllkkkkjjiiiihhhhggffffeeeeddddccccbbbbbbaaaa````______^^^^]]]]]]\\\\\\[[[[[[ZZZZZZZZYYYYYYXXXXXXXXWWWWWWWWVVVVVVVVVVUUUUUUUUTTTTTTTTTTTTSSSSSSSSSSRRRRRRRRRRRRRRQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸··································································¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬««««««««««ŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨§§§§§§§§¦¦¦¦¦¦ĄĄĄĄĄĄĄĄ¤¤¤¤¤¤ŁŁŁŁŁŁ˘˘˘˘˘˘ˇˇˇˇˇˇ      źźźźžžžžťťťťťťśśśś››››šššš™™™™————––••••””””““’’’’‘‘ŹŹŽŽŽŽŤŤŚŚŚŚ‹‹ŠŠ‰‰‰‰‡‡††††……„„‚‚‚‚€€~~~~}}||{{{{zzyyxxwwwwvvuuttttssrrqqqqppoooonnmmmmllkkkkjjjjiiiihhggggffffeeeeddddccccbbbbaaaaaa````______^^^^]]]]]]\\\\\\[[[[[[[[ZZZZZZYYYYYYYYXXXXXXWWWWWWWWWWVVVVVVVVUUUUUUUUUUTTTTTTTTTTTTSSSSSSSSSSRRRRRRRRRRRRRRQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸··································································¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©¨¨¨¨¨¨¨¨§§§§§§§§¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄ¤¤¤¤¤¤ŁŁŁŁŁŁ˘˘˘˘˘˘ˇˇˇˇˇˇ      źźźźžžžžžžťťťťśśśś››››šššš™™™™————––––••””””““““’’‘‘‘‘ŹŹŹŹŽŽŤŤŤŤŚŚ‹‹‹‹ŠŠ‰‰‡‡††…………„„‚‚‚‚€€~~~~}}||{{{{zzyyxxxxwwvvuuuuttssrrrrqqppppoonnnnmmllllkkkkjjiiiihhhhggggffffeeeeddddccccbbbbaaaaaa````______^^^^^^]]]]\\\\\\\\[[[[[[ZZZZZZYYYYYYYYXXXXXXXXWWWWWWWWVVVVVVVVVVUUUUUUUUUUTTTTTTTTTTSSSSSSSSSSSSRRRRRRRRRRRRRRQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸····································································¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««ŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©¨¨¨¨¨¨¨¨§§§§§§§§§§¦¦¦¦¦¦¦¦ĄĄĄĄĄĄ¤¤¤¤¤¤¤¤ŁŁŁŁŁŁ˘˘˘˘˘˘ˇˇˇˇˇˇ      źźźźžžžžžžťťťťśśśś››››šššš™™™™————––––••••””””““’’’’‘‘‘‘ŹŹŹŹŽŽŤŤŤŤŚŚ‹‹ŠŠŠŠ‰‰‡‡††…………„„‚‚‚‚€€~~~~}}||{{{{zzyyxxxxwwvvuuuuttssssrrqqqqppoooonnmmmmllllkkjjjjiiiihhhhggggffffeeeeddddccccbbbbaaaaaa````______^^^^^^]]]]]]\\\\\\[[[[[[ZZZZZZZZYYYYYYXXXXXXXXWWWWWWWWWWVVVVVVVVVVUUUUUUUUUUTTTTTTTTTTSSSSSSSSSSSSRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸······································································¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««ŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨§§§§§§§§¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄ¤¤¤¤¤¤ŁŁŁŁŁŁŁŁ˘˘˘˘˘˘ˇˇˇˇˇˇ      źźźźžžžžžžťťťťśśśś››››››šššš™™™™————––––••””””““““’’‘‘‘‘ŹŹŽŽŽŽŤŤŚŚŚŚ‹‹ŠŠŠŠ‰‰‡‡‡‡††……„„„„‚‚‚‚€€~~}}||||{{zzyyyyxxwwvvvvuuttttssrrrrqqppppoonnnnmmllllkkkkjjjjiihhhhggggffffeeeeddddddccccbbbbaaaaaa````______^^^^^^]]]]]]\\\\\\[[[[[[ZZZZZZZZYYYYYYYYXXXXXXXXWWWWWWWWVVVVVVVVVVUUUUUUUUUUTTTTTTTTTTTTSSSSSSSSSSSSRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸········································································¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©¨¨¨¨¨¨¨¨§§§§§§§§§§¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄ¤¤¤¤¤¤ŁŁŁŁŁŁŁŁ˘˘˘˘˘˘ˇˇˇˇˇˇ    źźźźźźžžžžžžťťťťśśśśśś››››šššš™™™™————––––••••””””““’’’’‘‘‘‘ŹŹŹŹŽŽŤŤŤŤŚŚ‹‹‹‹ŠŠ‰‰‰‰‡‡‡‡††……„„„„‚‚€€~~}}||||{{zzyyyyxxwwwwvvuuttttssrrrrqqppppoooonnmmmmllllkkjjjjiiiihhhhggggffffeeeeddddccccccbbbbaaaaaa````______^^^^^^]]]]]]\\\\\\[[[[[[[[ZZZZZZYYYYYYYYXXXXXXXXWWWWWWWWWWVVVVVVVVVVUUUUUUUUUUTTTTTTTTTTTTSSSSSSSSSSSSRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸········································································¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨§§§§§§§§¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤ŁŁŁŁŁŁ˘˘˘˘˘˘˘˘ˇˇˇˇˇˇ    źźźźźźžžžžžžťťťťśśśśśś››››šššš™™™™————––––••••””””““““’’‘‘‘‘ŹŹŽŽŽŽŤŤŤŤŚŚ‹‹‹‹ŠŠ‰‰‰‰‡‡††††……„„„„‚‚€€~~}}||||{{zzyyyyxxwwwwvvuuuuttssssrrqqqqppoooonnnnmmllllkkkkjjjjiiiihhhhggggffffeeeeddddccccbbbbbbaaaa``````______^^^^^^]]]]]]\\\\\\[[[[[[[[ZZZZZZZZYYYYYYYYXXXXXXXXWWWWWWWWWWVVVVVVVVVVUUUUUUUUUUTTTTTTTTTTTTSSSSSSSSSSSSRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸········································································¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤ŁŁŁŁŁŁ˘˘˘˘˘˘ˇˇˇˇˇˇˇˇ    źźźźźźžžžžžžťťťťśśśśśś››››šššš™™™™™™————––––••••””““““’’’’‘‘‘‘ŹŹŹŹŽŽŽŽŤŤŚŚŚŚ‹‹ŠŠŠŠ‰‰‡‡††††……„„„„‚‚€€~~}}||||{{zzzzyyxxxxwwvvuuuuttssssrrrrqqppppoonnnnmmmmllllkkjjjjiiiihhhhggggffffeeeeeeddddccccbbbbbbaaaa``````______^^^^^^]]]]]]\\\\\\\\[[[[[[ZZZZZZZZYYYYYYYYXXXXXXXXWWWWWWWWWWVVVVVVVVVVUUUUUUUUUUUUTTTTTTTTTTTTSSSSSSSSSSSSRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸············································································¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««ŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨§§§§§§§§¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄ¤¤¤¤¤¤ŁŁŁŁŁŁŁŁ˘˘˘˘˘˘ˇˇˇˇˇˇ      źźźźźźžžžžžžťťťťťťśśśś››››šššššš™™™™————––––••••””””““““’’’’‘‘ŹŹŹŹŽŽŤŤŤŤŚŚ‹‹‹‹ŠŠŠŠ‰‰‡‡††††……„„‚‚€€~~}}||||{{zzzzyyxxxxwwvvvvuuttttssrrrrqqqqppoooonnnnmmllllkkkkjjjjiiiihhhhggggffffeeeeddddddccccbbbbbbaaaa``````______^^^^^^]]]]]]\\\\\\\\[[[[[[ZZZZZZZZYYYYYYYYXXXXXXXXXXWWWWWWWWWWVVVVVVVVVVUUUUUUUUUUTTTTTTTTTTTTTTSSSSSSSSSSSSRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸············································································¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁ˘˘˘˘˘˘ˇˇˇˇˇˇ      źźźźźźžžžžžžťťťťťťśśśś››››››šššš™™™™————––––••••””””““““’’’’‘‘‘‘ŹŹŽŽŽŽŤŤŤŤŚŚ‹‹‹‹ŠŠ‰‰‰‰‡‡‡‡††…………„„‚‚€€~~}}}}||{{zzzzyyxxxxwwvvvvuuuuttssssrrqqqqppppoonnnnmmmmllllkkkkjjjjiiiihhhhggggffffeeeeddddddccccbbbbbbaaaa``````______^^^^^^]]]]]]]]\\\\\\[[[[[[[[ZZZZZZZZYYYYYYYYXXXXXXXXWWWWWWWWWWVVVVVVVVVVUUUUUUUUUUUUTTTTTTTTTTTTSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸············································································¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁ˘˘˘˘˘˘ˇˇˇˇˇˇ      źźźźźźžžžžžžťťťťťťśśśś››››››šššš™™™™————––––••••””””““““’’’’‘‘ŹŹŹŹŽŽŽŽŤŤŚŚŚŚ‹‹ŠŠŠŠ‰‰‰‰‡‡‡‡††…………„„‚‚€€~~}}}}||{{{{zzyyyyxxwwwwvvuuuuttssssrrrrqqppppoooonnnnmmllllkkkkjjjjiiiihhhhggggffffffeeeeddddccccccbbbbbbaaaa``````______^^^^^^]]]]]]]]\\\\\\[[[[[[[[ZZZZZZZZYYYYYYYYXXXXXXXXXXWWWWWWWWWWVVVVVVVVVVUUUUUUUUUUUUTTTTTTTTTTTTSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸················································································¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤ŁŁŁŁŁŁ˘˘˘˘˘˘˘˘ˇˇˇˇˇˇ      źźźźźźžžžžžžťťťťťťśśśśśś››››šššš™™™™™™————––––••••””””““““’’’’‘‘‘‘ŹŹŽŽŽŽŤŤŤŤŚŚŚŚ‹‹ŠŠŠŠ‰‰‡‡††††…………„„‚‚€€~~}}}}||{{{{zzyyyyxxwwwwvvuuuuttttssrrrrqqqqppoooonnnnmmmmllllkkkkjjjjiiiihhhhggggffffeeeeeeddddccccccbbbbaaaaaa``````______^^^^^^]]]]]]]]\\\\\\[[[[[[[[ZZZZZZZZYYYYYYYYYYXXXXXXXXWWWWWWWWWWVVVVVVVVVVVVUUUUUUUUUUUUTTTTTTTTTTTTSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFF¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸················································································¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘ˇˇˇˇˇˇ      źźźźźźžžžžžžťťťťťťśśśśśś››››šššššš™™™™——————––––••••””””““““’’’’‘‘ŹŹŹŹŽŽŽŽŤŤŤŤŚŚ‹‹‹‹ŠŠŠŠ‰‰‡‡††††……„„„„‚‚€€~~}}}}||{{{{zzyyyyxxwwwwvvvvuuttttssssrrqqqqppppoooonnmmmmllllkkkkjjjjiiiihhhhhhggggffffeeeeeeddddccccccbbbbaaaaaa``````______^^^^^^^^]]]]]]\\\\\\\\[[[[[[[[ZZZZZZZZYYYYYYYYXXXXXXXXXXWWWWWWWWWWVVVVVVVVVVUUUUUUUUUUUUTTTTTTTTTTTTTTSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFF¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸················································································¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘ˇˇˇˇˇˇ      źźźźźźžžžžžžťťťťťťśśśśśś››››šššššš™™™™————––––••••””””““““’’’’‘‘‘‘ŹŹŹŹŽŽŤŤŤŤŚŚŚŚ‹‹‹‹ŠŠ‰‰‰‰‡‡††††……„„„„‚‚‚‚€€~~}}}}||{{{{zzyyyyxxxxwwvvvvuuuuttssssrrrrqqppppoooonnnnmmmmllllkkkkjjjjiiiihhhhggggggffffeeeeddddddccccbbbbbbaaaaaa``````______^^^^^^^^]]]]]]\\\\\\\\[[[[[[[[ZZZZZZZZYYYYYYYYYYXXXXXXXXWWWWWWWWWWWWVVVVVVVVVVUUUUUUUUUUUUTTTTTTTTTTTTTTSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸····················································································¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘ˇˇˇˇˇˇ      źźźźźźžžžžžžťťťťťťśśśśśś››››››šššš™™™™™™————––––••••••””””““““’’’’‘‘‘‘ŹŹŹŹŽŽŽŽŤŤŤŤŚŚŚŚ‹‹ŠŠŠŠ‰‰‰‰‡‡‡‡††††……„„„„‚‚‚‚€€~~}}}}||{{{{zzzzyyxxxxwwwwvvuuuuttttssrrrrqqqqppppoooonnmmmmllllkkkkjjjjjjiiiihhhhggggffffffeeeeddddddccccbbbbbbaaaaaa``````______^^^^^^^^]]]]]]\\\\\\\\[[[[[[[[ZZZZZZZZYYYYYYYYYYXXXXXXXXXXWWWWWWWWWWVVVVVVVVVVVVUUUUUUUUUUUUTTTTTTTTTTTTTTSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸····················································································¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁ˘˘˘˘˘˘ˇˇˇˇˇˇˇˇ      źźźźźźžžžžžžžžťťťťśśśśśś››››››šššš™™™™™™——————––––••••””””““““’’’’‘‘‘‘ŹŹŹŹŽŽŽŽŤŤŤŤŚŚ‹‹‹‹ŠŠŠŠ‰‰‡‡‡‡††…………„„„„‚‚‚‚€€~~}}}}||||{{zzzzyyxxxxwwwwvvuuuuttttssssrrqqqqppppoooonnnnmmmmllllkkkkjjjjiiiihhhhhhggggffffeeeeeeddddccccccbbbbbbaaaaaa``````______^^^^^^^^]]]]]]\\\\\\\\[[[[[[[[ZZZZZZZZZZYYYYYYYYXXXXXXXXXXWWWWWWWWWWWWVVVVVVVVVVUUUUUUUUUUUUUUTTTTTTTTTTTTTTSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸····················································································¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁ˘˘˘˘˘˘ˇˇˇˇˇˇˇˇ      źźźźźźźźžžžžžžťťťťťťśśśś››››››šššššš™™™™————––––••••””””””““““’’’’‘‘‘‘ŹŹŽŽŽŽŤŤŤŤŚŚŚŚ‹‹‹‹ŠŠ‰‰‰‰‡‡‡‡††…………„„„„‚‚‚‚€€€€~~}}}}||||{{zzzzyyyyxxwwwwvvvvuuttttssssrrrrqqqqppoooonnnnmmmmllllkkkkkkjjjjiiiihhhhggggggffffeeeeeeddddccccccbbbbbbaaaaaa``````______^^^^^^^^]]]]]]]]\\\\\\\\[[[[[[[[ZZZZZZZZYYYYYYYYYYXXXXXXXXXXWWWWWWWWWWVVVVVVVVVVVVUUUUUUUUUUUUTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG¸¸¸¸······················································································¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇ      źźźźźźźźžžžžžžťťťťťťśśśśśś››››šššššš™™™™————––––––••••””””““““’’’’‘‘‘‘ŹŹŹŹŽŽŽŽŤŤŤŤŚŚŚŚ‹‹ŠŠŠŠ‰‰‰‰‡‡††††…………„„‚‚‚‚€€€€~~}}}}||||{{zzzzyyyyxxwwwwvvvvuuuuttssssrrrrqqqqppppoooonnnnmmmmllllkkkkjjjjiiiiiihhhhggggffffffeeeeddddddccccccbbbbbbaaaaaa``````______^^^^^^^^]]]]]]]]\\\\\\\\[[[[[[[[ZZZZZZZZYYYYYYYYYYXXXXXXXXXXWWWWWWWWWWWWVVVVVVVVVVVVUUUUUUUUUUUUTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG··················································································¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇ      źźźźźźźźžžžžžžťťťťťťśśśśśś››››šššššš™™™™™™——————––––••••””””““““““’’’’‘‘‘‘ŹŹŹŹŽŽŽŽŤŤŚŚŚŚ‹‹‹‹ŠŠŠŠ‰‰‰‰‡‡‡‡††††…………„„‚‚‚‚€€€€~~}}}}||||{{{{zzyyyyxxxxwwvvvvuuuuttttssssrrqqqqppppoooonnnnmmmmllllkkkkkkjjjjiiiihhhhhhggggffffffeeeeddddddccccccbbbbbbaaaaaa``````________^^^^^^]]]]]]]]\\\\\\\\[[[[[[[[ZZZZZZZZZZYYYYYYYYYYXXXXXXXXXXWWWWWWWWWWVVVVVVVVVVVVUUUUUUUUUUUUUUTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG········································································¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇ      źźźźźźźźžžžžžžťťťťťťśśśśśś››››››šššš™™™™™™————––––••••••””””““““’’’’‘‘‘‘ŹŹŹŹŽŽŽŽŤŤŤŤŚŚŚŚ‹‹‹‹ŠŠŠŠ‰‰‰‰‡‡‡‡††††…………„„‚‚‚‚€€€€~~}}}}||||{{{{zzyyyyxxxxwwwwvvuuuuttttssssrrrrqqqqppppoooonnnnmmmmllllkkkkjjjjiiiiiihhhhggggggffffeeeeeeddddddccccccbbbbbbaaaaaa``````________^^^^^^]]]]]]]]\\\\\\\\[[[[[[[[ZZZZZZZZZZYYYYYYYYYYXXXXXXXXXXWWWWWWWWWWWWVVVVVVVVVVVVUUUUUUUUUUUUUUTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGG······························································¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇ      źźźźźźźźžžžžžžťťťťťťśśśśśś››››››šššššš™™™™————––––––••••””””““““““’’’’‘‘‘‘ŹŹŹŹŽŽŽŽŤŤŤŤŚŚŚŚ‹‹‹‹ŠŠ‰‰‰‰‡‡‡‡††††……„„„„‚‚‚‚€€€€~~~~}}||||{{{{zzyyyyxxxxwwwwvvvvuuttttssssrrrrqqqqppppoooonnnnmmmmllllllkkkkjjjjiiiihhhhhhggggffffffeeeeeeddddddccccbbbbbbbbaaaaaa``````________^^^^^^]]]]]]]]\\\\\\\\[[[[[[[[[[ZZZZZZZZYYYYYYYYYYXXXXXXXXXXXXWWWWWWWWWWWWVVVVVVVVVVVVUUUUUUUUUUUUUUTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGG······················································¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇ      źźźźźźźźžžžžžžťťťťťťśśśśśś››››››šššššš™™™™™™——————––––••••••””””““““’’’’‘‘‘‘ŹŹŹŹŹŹŽŽŽŽŤŤŚŚŚŚ‹‹‹‹ŠŠŠŠ‰‰‰‰‡‡‡‡††…………„„„„‚‚‚‚€€€€~~~~}}||||{{{{zzzzyyxxxxwwwwvvvvuuuuttttssssrrrrqqqqppppoooonnnnmmmmllllkkkkjjjjjjiiiihhhhhhggggffffffeeeeeeddddccccccbbbbbbaaaaaaaa``````________^^^^^^]]]]]]]]\\\\\\\\\\[[[[[[[[ZZZZZZZZZZYYYYYYYYYYXXXXXXXXXXWWWWWWWWWWWWVVVVVVVVVVVVUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGG············································¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇ        źźźźźźźźžžžžžžťťťťťťśśśśśś››››››šššššš™™™™™™————––––––••••””””““““““’’’’‘‘‘‘ŹŹŹŹŽŽŽŽŤŤŤŤŚŚŚŚ‹‹‹‹ŠŠŠŠ‰‰‰‰‡‡‡‡††…………„„„„‚‚‚‚€€€€~~~~}}||||{{{{zzzzyyyyxxwwwwvvvvuuuuttttssssrrrrqqqqppppoooonnnnmmmmllllllkkkkjjjjiiiiiihhhhggggggffffffeeeeddddddccccccbbbbbbaaaaaaaa``````________^^^^^^]]]]]]]]]]\\\\\\\\[[[[[[[[ZZZZZZZZZZYYYYYYYYYYXXXXXXXXXXXXWWWWWWWWWWWWVVVVVVVVVVVVUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH··································¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇ        źźźźźźźźžžžžžžťťťťťťśśśśśśśś››››››šššš™™™™™™————––––––••••””””””““““’’’’‘‘‘‘ŹŹŹŹŽŽŽŽŤŤŤŤŚŚŚŚ‹‹‹‹ŠŠŠŠ‰‰‰‰‡‡‡‡††††…………„„„„‚‚€€€€~~~~}}||||{{{{zzzzyyyyxxxxwwvvvvuuuuttttssssrrrrqqqqppppoooonnnnnnmmmmllllkkkkjjjjjjiiiihhhhhhggggggffffeeeeeeddddddccccccbbbbbbaaaaaaaa``````________^^^^^^^^]]]]]]]]\\\\\\\\[[[[[[[[[[ZZZZZZZZYYYYYYYYYYYYXXXXXXXXXXWWWWWWWWWWWWVVVVVVVVVVVVVVUUUUUUUUUUUUUUTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH··························¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇ        źźźźźźźźžžžžžžťťťťťťťťśśśśśś››››››šššššš™™™™——————––––••••••””””““““““’’’’‘‘‘‘ŹŹŹŹŽŽŽŽŤŤŤŤŚŚŚŚ‹‹‹‹ŠŠŠŠ‰‰‰‰‡‡‡‡††††…………„„„„‚‚€€€€~~~~}}}}||{{{{zzzzyyyyxxxxwwwwvvvvuuuuttttssssrrrrqqqqppppoooonnnnmmmmllllllkkkkjjjjiiiiiihhhhhhggggffffffeeeeeeddddddccccccbbbbbbaaaaaaaa``````________^^^^^^^^]]]]]]]]\\\\\\\\[[[[[[[[[[ZZZZZZZZZZYYYYYYYYYYXXXXXXXXXXXXWWWWWWWWWWWWVVVVVVVVVVVVUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH················¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇ        źźźźźźźźžžžžžžťťťťťťťťśśśśśś››››››šššššš™™™™™™——————––––––••••””””””““““’’’’‘‘‘‘‘‘ŹŹŹŹŽŽŽŽŤŤŤŤŚŚŚŚ‹‹‹‹ŠŠŠŠ‰‰‰‰‡‡‡‡††††…………„„„„‚‚‚‚€€€€~~~~}}}}||{{{{zzzzyyyyxxxxwwwwvvvvuuuuttttssssrrrrqqqqppppoooonnnnnnmmmmllllkkkkkkjjjjiiiiiihhhhggggggffffffeeeeeeddddddccccccbbbbbbaaaaaa````````________^^^^^^^^]]]]]]]]\\\\\\\\[[[[[[[[[[ZZZZZZZZZZYYYYYYYYYYXXXXXXXXXXXXWWWWWWWWWWWWVVVVVVVVVVVVVVUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH······¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇ        źźźźźźźźžžžžžžťťťťťťťťśśśśśś››››››šššššš™™™™™™————––––––••••••””””““““’’’’’’‘‘‘‘ŹŹŹŹŹŹŽŽŽŽŤŤŤŤŚŚŚŚ‹‹‹‹ŠŠŠŠ‰‰‰‰‡‡‡‡††††…………„„„„‚‚‚‚€€€€~~~~}}}}||||{{zzzzyyyyxxxxwwwwvvvvuuuuttttssssrrrrqqqqppppppoooonnnnmmmmllllllkkkkjjjjjjiiiihhhhhhggggggffffeeeeeeddddddccccccccbbbbbbaaaaaa````````________^^^^^^^^]]]]]]]]\\\\\\\\\\[[[[[[[[ZZZZZZZZZZYYYYYYYYYYYYXXXXXXXXXXXXWWWWWWWWWWWWVVVVVVVVVVVVVVUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇ        źźźźźźźźžžžžžžťťťťťťťťśśśśśś››››››šššššš™™™™™™——————––––••••••””””””““““’’’’‘‘‘‘‘‘ŹŹŹŹŽŽŽŽŤŤŤŤŚŚŚŚŚŚ‹‹‹‹ŠŠŠŠ‰‰‰‰‡‡‡‡††††……„„„„‚‚‚‚€€€€~~~~}}}}||||{{{{zzyyyyxxxxwwwwvvvvuuuuttttssssssrrrrqqqqppppoooonnnnnnmmmmllllkkkkkkjjjjiiiiiihhhhhhggggffffffeeeeeeddddddccccccbbbbbbbbaaaaaa````````________^^^^^^^^]]]]]]]]\\\\\\\\\\[[[[[[[[[[ZZZZZZZZZZYYYYYYYYYYXXXXXXXXXXXXWWWWWWWWWWWWWWVVVVVVVVVVVVUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇ        źźźźźźźźžžžžžžžžťťťťťťśśśśśś››››››››šššššš™™™™™™——————––––––••••””””””““““’’’’’’‘‘‘‘ŹŹŹŹŹŹŽŽŽŽŤŤŤŤŚŚŚŚ‹‹‹‹ŠŠŠŠ‰‰‰‰‡‡‡‡††††…………„„„„‚‚‚‚€€€€~~~~}}}}||||{{{{zzzzyyyyxxxxwwwwvvvvuuuuttttssssrrrrqqqqppppoooooonnnnmmmmllllllkkkkjjjjjjiiiihhhhhhggggggffffffeeeeeeddddddccccccbbbbbbbbaaaaaa````````________^^^^^^^^]]]]]]]]\\\\\\\\\\[[[[[[[[[[ZZZZZZZZZZYYYYYYYYYYYYXXXXXXXXXXXXWWWWWWWWWWWWVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHH¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇ        źźźźźźźźžžžžžžžžťťťťťťśśśśśśśś››››››šššššš™™™™™™——————––––••••••””””““““““’’’’‘‘‘‘‘‘ŹŹŹŹŽŽŽŽŽŽŤŤŤŤŚŚŚŚ‹‹‹‹ŠŠŠŠ‰‰‰‰‡‡‡‡††††…………„„„„‚‚‚‚€€€€~~~~}}}}||||{{{{zzzzyyyyxxxxwwwwvvvvuuuuttttssssrrrrqqqqqqppppoooonnnnmmmmmmllllkkkkkkjjjjiiiiiihhhhhhggggggffffffeeeeeeddddddccccccbbbbbbbbaaaaaa````````________^^^^^^^^]]]]]]]]]]\\\\\\\\[[[[[[[[[[ZZZZZZZZZZYYYYYYYYYYYYXXXXXXXXXXXXWWWWWWWWWWWWWWVVVVVVVVVVVVVVUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHH¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇ        źźźźźźźźžžžžžžžžťťťťťťśśśśśśśś››››››šššššš™™™™™™——————––––––••••””””””““““’’’’’’‘‘‘‘ŹŹŹŹŽŽŽŽŤŤŤŤŚŚŚŚŚŚ‹‹‹‹ŠŠŠŠ‰‰‰‰‡‡‡‡††††…………„„„„‚‚‚‚€€€€~~~~}}}}||||{{{{zzzzyyyyxxxxwwwwvvvvuuuuttttssssssrrrrqqqqppppoooooonnnnmmmmllllllkkkkjjjjjjiiiiiihhhhhhggggffffffeeeeeeddddddddccccccbbbbbbbbaaaaaa````````________^^^^^^^^]]]]]]]]]]\\\\\\\\[[[[[[[[[[ZZZZZZZZZZZZYYYYYYYYYYYYXXXXXXXXXXXXWWWWWWWWWWWWVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHH¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇ        źźźźźźźźžžžžžžžžťťťťťťśśśśśśśś››››››šššššš™™™™™™——————––––––••••••””””““““““’’’’‘‘‘‘‘‘ŹŹŹŹŹŹŽŽŽŽŤŤŤŤŚŚŚŚ‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‡‡‡‡††††…………„„„„‚‚‚‚€€€€~~~~}}}}||||{{{{zzzzyyyyxxxxwwwwvvvvuuuuttttttssssrrrrqqqqppppppoooonnnnmmmmmmllllkkkkkkjjjjjjiiiihhhhhhggggggffffffeeeeeeddddddddccccccbbbbbbaaaaaaaa````````________^^^^^^^^]]]]]]]]]]\\\\\\\\\\[[[[[[[[[[ZZZZZZZZZZYYYYYYYYYYYYXXXXXXXXXXXXWWWWWWWWWWWWWWVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇ        źźźźźźźźžžžžžžžžťťťťťťśśśśśśśś››››››šššššššš™™™™™™——————––––••••••””””””““““’’’’’’‘‘‘‘ŹŹŹŹŽŽŽŽŤŤŤŤŤŤŚŚŚŚ‹‹‹‹ŠŠŠŠ‰‰‰‰‡‡‡‡††††…………„„„„‚‚‚‚€€€€~~~~}}}}||||{{{{zzzzyyyyxxxxwwwwvvvvvvuuuuttttssssrrrrqqqqqqppppoooonnnnnnmmmmllllllkkkkkkjjjjiiiiiihhhhhhggggggffffffeeeeeeddddddccccccccbbbbbbaaaaaaaa````````________^^^^^^^^]]]]]]]]]]\\\\\\\\\\[[[[[[[[[[ZZZZZZZZZZZZYYYYYYYYYYXXXXXXXXXXXXXXWWWWWWWWWWWWVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇ        źźźźźźźźžžžžžžžžťťťťťťťťśśśśśś››››››››šššššš™™™™™™——————––––––••••••””””““““““’’’’‘‘‘‘‘‘ŹŹŹŹŹŹŽŽŽŽŤŤŤŤŚŚŚŚŚŚ‹‹‹‹ŠŠŠŠ‰‰‰‰‡‡‡‡‡‡††††…………„„„„‚‚‚‚€€€€~~~~}}}}||||{{{{zzzzyyyyxxxxxxwwwwvvvvuuuuttttssssrrrrrrqqqqppppoooooonnnnmmmmmmllllkkkkkkjjjjjjiiiiiihhhhhhggggggffffffeeeeeeddddddccccccccbbbbbbaaaaaaaa````````________^^^^^^^^^^]]]]]]]]\\\\\\\\\\[[[[[[[[[[ZZZZZZZZZZZZYYYYYYYYYYYYXXXXXXXXXXXXWWWWWWWWWWWWWWVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇ        źźźźźźźźžžžžžžžžťťťťťťťťśśśśśś››››››››šššššš™™™™™™——————––––––••••••””””””““““’’’’’’‘‘‘‘ŹŹŹŹŽŽŽŽŽŽŤŤŤŤŚŚŚŚ‹‹‹‹‹‹ŠŠŠŠ‰‰‰‰‡‡‡‡††††………………„„„„‚‚‚‚€€€€~~~~}}}}||||{{{{zzzzyyyyyyxxxxwwwwvvvvuuuuttttttssssrrrrqqqqppppppoooonnnnnnmmmmllllllkkkkkkjjjjiiiiiihhhhhhggggggffffffeeeeeeeeddddddccccccbbbbbbbbaaaaaaaa````````________^^^^^^^^^^]]]]]]]]\\\\\\\\\\[[[[[[[[[[[[ZZZZZZZZZZYYYYYYYYYYYYXXXXXXXXXXXXXXWWWWWWWWWWWWWWVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇ        źźźźźźźźžžžžžžžžťťťťťťťťśśśśśś››››››››šššššš™™™™™™™™——————––––––••••””””””““““““’’’’‘‘‘‘‘‘ŹŹŹŹŽŽŽŽŤŤŤŤŤŤŚŚŚŚ‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‡‡‡‡††††…………„„„„„„‚‚‚‚€€€€~~~~}}}}||||{{{{{{zzzzyyyyxxxxwwwwvvvvuuuuuuttttssssrrrrqqqqqqppppoooooonnnnmmmmmmllllkkkkkkjjjjjjiiiiiihhhhhhggggggffffffeeeeeeddddddddccccccbbbbbbbbaaaaaaaa````````________^^^^^^^^^^]]]]]]]]]]\\\\\\\\\\[[[[[[[[[[ZZZZZZZZZZZZYYYYYYYYYYYYXXXXXXXXXXXXWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇ          źźźźźźźźžžžžžžžžťťťťťťťťśśśśśśśś››››››šššššššš™™™™™™——————––––––••••••””””””““““’’’’’’‘‘‘‘‘‘ŹŹŹŹŹŹŽŽŽŽŤŤŤŤŚŚŚŚŚŚ‹‹‹‹ŠŠŠŠ‰‰‰‰‰‰‡‡‡‡††††…………„„„„‚‚‚‚€€€€~~~~}}}}||||{{{{{{zzzzyyyyxxxxwwwwvvvvvvuuuuttttssssrrrrrrqqqqppppppoooonnnnnnmmmmllllllkkkkkkjjjjjjiiiihhhhhhhhggggggffffffeeeeeeddddddddccccccbbbbbbbbaaaaaaaa````````________^^^^^^^^^^]]]]]]]]]]\\\\\\\\\\[[[[[[[[[[ZZZZZZZZZZZZYYYYYYYYYYYYXXXXXXXXXXXXXXWWWWWWWWWWWWWWVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇ          źźźźźźźźžžžžžžžžťťťťťťťťśśśśśśśś››››››šššššššš™™™™™™——————––––––••••••””””””““““““’’’’’’‘‘‘‘ŹŹŹŹŽŽŽŽŽŽŤŤŤŤŚŚŚŚ‹‹‹‹‹‹ŠŠŠŠ‰‰‰‰‡‡‡‡††††…………„„„„‚‚‚‚€€€€~~~~}}}}||||||{{{{zzzzyyyyxxxxwwwwwwvvvvuuuuttttssssssrrrrqqqqqqppppoooooonnnnmmmmmmllllllkkkkjjjjjjiiiiiihhhhhhggggggffffffffeeeeeeddddddccccccccbbbbbbbbaaaaaaaa````````________^^^^^^^^^^]]]]]]]]]]\\\\\\\\\\[[[[[[[[[[ZZZZZZZZZZZZYYYYYYYYYYYYXXXXXXXXXXXXXXWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇ          źźźźźźźźžžžžžžžžťťťťťťťťśśśśśśśś››››››››šššššš™™™™™™——————––––––••••••””””””““““’’’’’’‘‘‘‘‘‘ŹŹŹŹŹŹŽŽŽŽŤŤŤŤŤŤŚŚŚŚ‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‡‡‡‡‡‡††††…………„„„„‚‚‚‚‚‚€€€€~~~~}}}}||||||{{{{zzzzyyyyxxxxxxwwwwvvvvuuuuttttttssssrrrrrrqqqqppppoooooonnnnnnmmmmllllllkkkkkkjjjjjjiiiiiihhhhhhggggggffffffeeeeeeeeddddddccccccccbbbbbbbbaaaaaaaa````````________^^^^^^^^^^]]]]]]]]]]\\\\\\\\\\[[[[[[[[[[[[ZZZZZZZZZZZZYYYYYYYYYYYYXXXXXXXXXXXXXXWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇ          źźźźźźźźžžžžžžžžťťťťťťťťśśśśśśśś››››››››šššššš™™™™™™™™——————––––––••••••””””””““““““’’’’’’‘‘‘‘ŹŹŹŹŽŽŽŽŽŽŤŤŤŤŚŚŚŚŚŚ‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‡‡‡‡††††††…………„„„„‚‚‚‚‚‚€€€€~~~~}}}}}}||||{{{{zzzzyyyyxxxxxxwwwwvvvvuuuuuuttttssssrrrrrrqqqqppppppoooonnnnnnmmmmmmllllllkkkkjjjjjjiiiiiihhhhhhggggggggffffffeeeeeeddddddddccccccccbbbbbbbbaaaaaaaa````````________^^^^^^^^^^]]]]]]]]]]\\\\\\\\\\[[[[[[[[[[[[ZZZZZZZZZZZZYYYYYYYYYYYYXXXXXXXXXXXXXXWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇ          źźźźźźźźžžžžžžžžťťťťťťťťśśśśśśśś››››››››šššššššš™™™™™™——————––––––––••••••””””““““““’’’’’’‘‘‘‘‘‘ŹŹŹŹŹŹŽŽŽŽŽŽŤŤŤŤŚŚŚŚ‹‹‹‹‹‹ŠŠŠŠ‰‰‰‰‰‰‡‡‡‡††††††…………„„„„‚‚‚‚‚‚€€€€~~~~}}}}}}||||{{{{zzzzyyyyyyxxxxwwwwvvvvvvuuuuttttssssssrrrrqqqqqqppppoooooonnnnnnmmmmllllllkkkkkkjjjjjjiiiiiihhhhhhggggggffffffffeeeeeeddddddddccccccccbbbbbbbbaaaaaaaa````````________^^^^^^^^^^]]]]]]]]]]\\\\\\\\\\\\[[[[[[[[[[ZZZZZZZZZZZZYYYYYYYYYYYYYYXXXXXXXXXXXXXXWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇ          źźźźźźźźžžžžžžžžžžťťťťťťťťśśśśśś››››››››šššššššš™™™™™™——————––––––••••••””””””““““““’’’’’’‘‘‘‘ŹŹŹŹŹŹŽŽŽŽŤŤŤŤŤŤŚŚŚŚ‹‹‹‹‹‹ŠŠŠŠ‰‰‰‰‡‡‡‡††††………………„„„„‚‚‚‚€€€€~~~~}}}}}}||||{{{{zzzzyyyyyyxxxxwwwwvvvvvvuuuuttttttssssrrrrrrqqqqppppppoooonnnnnnmmmmmmllllllkkkkkkjjjjjjiiiiiihhhhhhggggggffffffeeeeeeeeddddddddccccccbbbbbbbbaaaaaaaaaa````````__________^^^^^^^^]]]]]]]]]]\\\\\\\\\\\\[[[[[[[[[[[[ZZZZZZZZZZZZYYYYYYYYYYYYXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇ          źźźźźźźźžžžžžžžžžžťťťťťťťťśśśśśśśś››››››šššššššš™™™™™™™™——————––––––••••••””””””““““““’’’’’’‘‘‘‘‘‘ŹŹŹŹŽŽŽŽŽŽŤŤŤŤŚŚŚŚŚŚ‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‡‡‡‡††††………………„„„„‚‚‚‚€€€€~~~~}}}}}}||||{{{{zzzzzzyyyyxxxxwwwwwwvvvvuuuuttttttssssrrrrrrqqqqqqppppoooooonnnnnnmmmmllllllkkkkkkjjjjjjiiiiiihhhhhhggggggggffffffeeeeeeeeddddddccccccccbbbbbbbbaaaaaaaaaa````````__________^^^^^^^^]]]]]]]]]]]]\\\\\\\\\\[[[[[[[[[[[[ZZZZZZZZZZZZYYYYYYYYYYYYXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇ          źźźźźźźźžžžžžžžžžžťťťťťťťťśśśśśśśś››››››››šššššš™™™™™™™™————————––––––••••••””””””““““““’’’’’’‘‘‘‘ŹŹŹŹŹŹŽŽŽŽŤŤŤŤŤŤŚŚŚŚŚŚ‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‡‡‡‡‡‡††††…………„„„„„„‚‚‚‚€€€€~~~~~~}}}}||||{{{{zzzzzzyyyyxxxxwwwwwwvvvvuuuuuuttttssssssrrrrqqqqqqppppppoooonnnnnnmmmmmmllllllkkkkkkjjjjjjiiiiiihhhhhhggggggffffffffeeeeeeeeddddddccccccccbbbbbbbbaaaaaaaaaa````````__________^^^^^^^^^^]]]]]]]]]]\\\\\\\\\\[[[[[[[[[[[[ZZZZZZZZZZZZYYYYYYYYYYYYYYXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇ          źźźźźźźźžžžžžžžžžžťťťťťťťťśśśśśśśś››››››››šššššššš™™™™™™——————––––––••••••””””””““““““’’’’’’‘‘‘‘‘‘ŹŹŹŹŽŽŽŽŽŽŤŤŤŤŤŤŚŚŚŚ‹‹‹‹‹‹ŠŠŠŠ‰‰‰‰‰‰‡‡‡‡‡‡††††…………„„„„„„‚‚‚‚€€€€~~~~~~}}}}||||{{{{{{zzzzyyyyxxxxxxwwwwvvvvvvuuuuttttttssssrrrrrrqqqqppppppoooooonnnnmmmmmmllllllkkkkkkjjjjjjiiiiiiiihhhhhhggggggffffffffeeeeeeddddddddccccccccbbbbbbbbaaaaaaaa``````````__________^^^^^^^^^^]]]]]]]]]]\\\\\\\\\\[[[[[[[[[[[[ZZZZZZZZZZZZZZYYYYYYYYYYYYXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇ          źźźźźźźźžžžžžžžžžžťťťťťťťťśśśśśśśś››››››››šššššššš™™™™™™——————––––––––••••••””””””““““““’’’’’’‘‘‘‘‘‘ŹŹŹŹŹŹŽŽŽŽŽŽŤŤŤŤŚŚŚŚŚŚ‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‡‡‡‡††††††…………„„„„„„‚‚‚‚€€€€~~~~~~}}}}||||{{{{{{zzzzyyyyxxxxxxwwwwvvvvvvuuuuttttttssssrrrrrrqqqqqqppppoooooonnnnnnmmmmmmllllllkkkkkkjjjjjjiiiiiihhhhhhggggggggffffffeeeeeeeeddddddddccccccccbbbbbbbbaaaaaaaa``````````__________^^^^^^^^^^]]]]]]]]]]\\\\\\\\\\\\[[[[[[[[[[[[ZZZZZZZZZZZZYYYYYYYYYYYYYYXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇ          źźźźźźźźžžžžžžžžžžťťťťťťťťśśśśśśśś››››››››šššššššš™™™™™™™™————————––––––••••••””””””““““““’’’’’’‘‘‘‘‘‘ŹŹŹŹŹŹŽŽŽŽŤŤŤŤŤŤŚŚŚŚŚŚ‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‡‡‡‡††††††…………„„„„‚‚‚‚€€€€~~~~~~}}}}||||{{{{{{zzzzyyyyyyxxxxwwwwwwvvvvuuuuuuttttssssssrrrrqqqqqqppppppoooooonnnnmmmmmmllllllkkkkkkjjjjjjjjiiiiiihhhhhhggggggggffffffeeeeeeeeddddddddccccccccbbbbbbbbaaaaaaaa``````````__________^^^^^^^^^^]]]]]]]]]]\\\\\\\\\\\\[[[[[[[[[[[[ZZZZZZZZZZZZYYYYYYYYYYYYYYXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇ          źźźźźźźźžžžžžžžžžžťťťťťťťťśśśśśśśś››››››››šššššššš™™™™™™™™——————––––––••••••••””””””““““““’’’’’’‘‘‘‘‘‘ŹŹŹŹŹŹŽŽŽŽŽŽŤŤŤŤŤŤŚŚŚŚ‹‹‹‹‹‹ŠŠŠŠ‰‰‰‰‰‰‡‡‡‡††††………………„„„„‚‚‚‚€€€€~~~~~~}}}}||||||{{{{zzzzyyyyyyxxxxwwwwwwvvvvuuuuuuttttssssssrrrrrrqqqqppppppoooooonnnnnnmmmmmmllllllkkkkkkjjjjjjiiiiiihhhhhhhhggggggffffffffeeeeeeeeddddddddccccccccbbbbbbbbaaaaaaaa``````````__________^^^^^^^^^^]]]]]]]]]]\\\\\\\\\\\\[[[[[[[[[[[[ZZZZZZZZZZZZZZYYYYYYYYYYYYYYXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇ          źźźźźźźźžžžžžžžžžžťťťťťťťťśśśśśśśśśś››››››››šššššššš™™™™™™——————––––––––••••••””””””““““““’’’’’’‘‘‘‘‘‘ŹŹŹŹŹŹŽŽŽŽŽŽŤŤŤŤŚŚŚŚŚŚ‹‹‹‹‹‹ŠŠŠŠ‰‰‰‰‰‰‡‡‡‡‡‡††††………………„„„„‚‚‚‚€€€€€€~~~~~~}}}}||||||{{{{zzzzyyyyyyxxxxwwwwwwvvvvvvuuuuttttttssssrrrrrrqqqqqqppppppoooonnnnnnmmmmmmllllllkkkkkkjjjjjjjjiiiiiihhhhhhggggggggffffffffeeeeeeddddddddccccccccbbbbbbbbbbaaaaaaaa``````````__________^^^^^^^^^^]]]]]]]]]]\\\\\\\\\\\\[[[[[[[[[[[[ZZZZZZZZZZZZZZYYYYYYYYYYYYYYXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇ          źźźźźźźźźźžžžžžžžžťťťťťťťťťťśśśśśśśś››››››››šššššššš™™™™™™™™————————––––––••••••””””””””““““““’’’’’’‘‘‘‘‘‘ŹŹŹŹŽŽŽŽŽŽŤŤŤŤŤŤŚŚŚŚŚŚ‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‰‰‡‡‡‡‡‡††††………………„„„„‚‚‚‚€€€€€€~~~~~~}}}}||||||{{{{zzzzzzyyyyxxxxxxwwwwvvvvvvuuuuttttttssssssrrrrqqqqqqppppppoooooonnnnnnmmmmmmllllllkkkkkkjjjjjjiiiiiiiihhhhhhggggggggffffffeeeeeeeeddddddddccccccccbbbbbbbbbbaaaaaaaa``````````__________^^^^^^^^^^]]]]]]]]]]]]\\\\\\\\\\\\[[[[[[[[[[[[ZZZZZZZZZZZZYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇ          źźźźźźźźźźžžžžžžžžťťťťťťťťťťśśśśśśśś››››››››šššššššš™™™™™™™™————————––––––••••••••””””””““““““’’’’’’‘‘‘‘‘‘ŹŹŹŹŹŹŽŽŽŽŽŽŤŤŤŤŤŤŚŚŚŚ‹‹‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‡‡‡‡††††††………………„„„„‚‚‚‚‚‚€€€€€€~~~~~~}}}}||||||{{{{zzzzzzyyyyxxxxxxwwwwvvvvvvuuuuuuttttssssssrrrrrrqqqqqqppppoooooonnnnnnmmmmmmllllllkkkkkkkkjjjjjjiiiiiihhhhhhhhggggggffffffffeeeeeeeeddddddddccccccccbbbbbbbbbbaaaaaaaa``````````__________^^^^^^^^^^]]]]]]]]]]]]\\\\\\\\\\\\[[[[[[[[[[[[ZZZZZZZZZZZZZZYYYYYYYYYYYYYYXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇ            źźźźźźźźźźžžžžžžžžťťťťťťťťťťśśśśśśśś››››››››šššššššš™™™™™™™™——————––––––––••••••””””””““““““““’’’’’’‘‘‘‘‘‘ŹŹŹŹŹŹŽŽŽŽŽŽŤŤŤŤŚŚŚŚŚŚ‹‹‹‹‹‹ŠŠŠŠ‰‰‰‰‰‰‡‡‡‡††††††…………„„„„„„‚‚‚‚‚‚€€€€€€~~~~~~}}}}||||||{{{{zzzzzzyyyyxxxxxxwwwwwwvvvvuuuuuuttttttssssrrrrrrqqqqqqppppppoooooonnnnnnmmmmmmllllllkkkkkkjjjjjjjjiiiiiihhhhhhggggggggffffffffeeeeeeeeddddddddccccccccbbbbbbbbaaaaaaaaaa``````````__________^^^^^^^^^^]]]]]]]]]]]]\\\\\\\\\\\\[[[[[[[[[[[[ZZZZZZZZZZZZZZYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇ            źźźźźźźźźźžžžžžžžžťťťťťťťťťťśśśśśśśś››››››››šššššššš™™™™™™™™————————––––––••••••••””””””““““““’’’’’’‘‘‘‘‘‘ŹŹŹŹŹŹŽŽŽŽŽŽŤŤŤŤŤŤŚŚŚŚŚŚ‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‰‰‡‡‡‡‡‡††††††…………„„„„„„‚‚‚‚‚‚€€€€€€~~~~~~}}}}||||||{{{{{{zzzzyyyyyyxxxxwwwwwwvvvvvvuuuuttttttssssssrrrrqqqqqqppppppoooooonnnnnnmmmmmmllllllkkkkkkkkjjjjjjiiiiiiiihhhhhhggggggggffffffffeeeeeeeeddddddddccccccccbbbbbbbbaaaaaaaaaa``````````__________^^^^^^^^^^]]]]]]]]]]]]\\\\\\\\\\\\[[[[[[[[[[[[[[ZZZZZZZZZZZZYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇ            źźźźźźźźźźžžžžžžžžťťťťťťťťťťśśśśśśśś››››››››››šššššššš™™™™™™™™————————––––––––••••••””””””““““““““’’’’’’‘‘‘‘‘‘ŹŹŹŹŹŹŽŽŽŽŽŽŤŤŤŤŤŤŚŚŚŚ‹‹‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‰‰‡‡‡‡‡‡††††††…………„„„„„„‚‚‚‚‚‚€€€€€€~~~~~~}}}}}}||||{{{{{{zzzzyyyyyyxxxxwwwwwwvvvvvvuuuuttttttssssssrrrrrrqqqqqqppppppoooooonnnnnnmmmmmmllllllkkkkkkjjjjjjjjiiiiiihhhhhhhhggggggffffffffeeeeeeeeddddddddccccccccccbbbbbbbbaaaaaaaaaa``````````__________^^^^^^^^^^^^]]]]]]]]]]\\\\\\\\\\\\[[[[[[[[[[[[[[ZZZZZZZZZZZZZZYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇ            źźźźźźźźźźžžžžžžžžžžťťťťťťťťśśśśśśśśśś››››››››šššššššš™™™™™™™™——————––––––––••••••””””””””““““““’’’’’’‘‘‘‘‘‘ŹŹŹŹŹŹŽŽŽŽŽŽŤŤŤŤŤŤŚŚŚŚŚŚ‹‹‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‡‡‡‡‡‡††††………………„„„„„„‚‚‚‚‚‚€€€€€€~~~~~~}}}}}}||||{{{{{{zzzzyyyyyyxxxxxxwwwwvvvvvvuuuuuuttttssssssrrrrrrqqqqqqppppppoooooonnnnnnmmmmmmllllllllkkkkkkjjjjjjiiiiiiiihhhhhhggggggggffffffffeeeeeeeeddddddddccccccccccbbbbbbbbaaaaaaaaaa``````````__________^^^^^^^^^^^^]]]]]]]]]]\\\\\\\\\\\\\\[[[[[[[[[[[[ZZZZZZZZZZZZZZYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇ            źźźźźźźźźźžžžžžžžžžžťťťťťťťťśśśśśśśśśś››››››››šššššššš™™™™™™™™————————––––––••••••••””””””““““““““’’’’’’‘‘‘‘‘‘ŹŹŹŹŹŹŽŽŽŽŽŽŤŤŤŤŤŤŚŚŚŚŚŚ‹‹‹‹‹‹ŠŠŠŠ‰‰‰‰‰‰‡‡‡‡‡‡††††………………„„„„‚‚‚‚‚‚€€€€€€~~~~~~}}}}}}||||{{{{{{zzzzyyyyyyxxxxxxwwwwwwvvvvuuuuuuttttttssssssrrrrrrqqqqppppppoooooonnnnnnnnmmmmmmllllllkkkkkkjjjjjjjjiiiiiihhhhhhhhggggggggffffffffeeeeeeeeddddddddccccccccbbbbbbbbbbaaaaaaaaaa``````````__________^^^^^^^^^^^^]]]]]]]]]]]]\\\\\\\\\\\\[[[[[[[[[[[[ZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇ            źźźźźźźźźźžžžžžžžžžžťťťťťťťťśśśśśśśśśś››››››››šššššššš™™™™™™™™————————––––––––••••••””””””””““““““’’’’’’‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŽŽŽŽŽŽŤŤŤŤŤŤŚŚŚŚŚŚ‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‰‰‡‡‡‡††††††………………„„„„‚‚‚‚‚‚€€€€€€~~~~~~}}}}}}||||{{{{{{zzzzzzyyyyxxxxxxwwwwwwvvvvuuuuuuttttttssssssrrrrrrqqqqqqppppppoooooonnnnnnmmmmmmllllllllkkkkkkjjjjjjiiiiiiiihhhhhhhhggggggggffffffffeeeeeeeeddddddddccccccccbbbbbbbbbbaaaaaaaaaa``````````__________^^^^^^^^^^^^]]]]]]]]]]]]\\\\\\\\\\\\[[[[[[[[[[[[[[ZZZZZZZZZZZZZZYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇ            źźźźźźźźźźžžžžžžžžžžťťťťťťťťťťśśśśśśśś››››››››šššššššššš™™™™™™™™——————––––––––••••••••””””””““““““““’’’’’’‘‘‘‘‘‘ŹŹŹŹŹŹŽŽŽŽŽŽŤŤŤŤŤŤŚŚŚŚŚŚ‹‹‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‰‰‡‡‡‡‡‡††††††………………„„„„‚‚‚‚‚‚€€€€€€~~~~~~}}}}}}||||{{{{{{zzzzzzyyyyxxxxxxwwwwwwvvvvvvuuuuuuttttssssssrrrrrrqqqqqqppppppoooooonnnnnnnnmmmmmmllllllkkkkkkkkjjjjjjiiiiiiiihhhhhhggggggggffffffffeeeeeeeeddddddddddccccccccbbbbbbbbbbaaaaaaaaaa``````````__________^^^^^^^^^^^^]]]]]]]]]]]]\\\\\\\\\\\\[[[[[[[[[[[[[[ZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇ            źźźźźźźźźźžžžžžžžžžžťťťťťťťťťťśśśśśśśś››››››››››šššššššš™™™™™™™™————————––––––––••••••””””””””““““““’’’’’’‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŽŽŽŽŽŽŤŤŤŤŤŤŚŚŚŚŚŚ‹‹‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‡‡‡‡‡‡††††††…………„„„„„„‚‚‚‚‚‚€€€€€€~~~~}}}}}}||||||{{{{zzzzzzyyyyyyxxxxwwwwwwvvvvvvuuuuuuttttttssssssrrrrrrqqqqqqppppppoooooonnnnnnmmmmmmllllllllkkkkkkjjjjjjjjiiiiiihhhhhhhhggggggggffffffffeeeeeeeeddddddddddccccccccbbbbbbbbbbaaaaaaaaaa``````````__________^^^^^^^^^^^^]]]]]]]]]]]]\\\\\\\\\\\\[[[[[[[[[[[[[[ZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇ          źźźźźźźźźźźźžžžžžžžžžžťťťťťťťťťťśśśśśśśś››››››››››šššššššš™™™™™™™™————————––––––––••••••••””””””““““““““’’’’’’‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŽŽŽŽŽŽŤŤŤŤŤŤŚŚŚŚŚŚ‹‹‹‹‹‹ŠŠŠŠ‰‰‰‰‰‰‡‡‡‡‡‡††††††…………„„„„„„‚‚‚‚€€€€€€~~~~}}}}}}||||||{{{{zzzzzzyyyyyyxxxxxxwwwwvvvvvvuuuuuuttttttssssssrrrrrrqqqqqqppppppoooooonnnnnnmmmmmmmmllllllkkkkkkkkjjjjjjiiiiiiiihhhhhhhhggggggggffffffffeeeeeeeeddddddddccccccccccbbbbbbbbbbaaaaaaaaaa``````````__________^^^^^^^^^^^^]]]]]]]]]]]]\\\\\\\\\\\\\\[[[[[[[[[[[[ZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇ          źźźźźźźźźźźźžžžžžžžžžžťťťťťťťťťťśśśśśśśśśś››››››››šššššššš™™™™™™™™™™————————––––––••••••••””””””””““““““’’’’’’‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŽŽŽŽŽŽŤŤŤŤŤŤŚŚŚŚŚŚ‹‹‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‰‰‡‡‡‡‡‡††††………………„„„„„„‚‚‚‚€€€€€€~~~~}}}}}}||||||{{{{zzzzzzyyyyyyxxxxxxwwwwwwvvvvuuuuuuttttttssssssrrrrrrqqqqqqppppppoooooooonnnnnnmmmmmmllllllllkkkkkkjjjjjjjjiiiiiiiihhhhhhggggggggffffffffeeeeeeeeeeddddddddccccccccccbbbbbbbbbbaaaaaaaaaa``````````__________^^^^^^^^^^^^]]]]]]]]]]]]\\\\\\\\\\\\\\[[[[[[[[[[[[[[ZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇ          źźźźźźźźźźźźžžžžžžžžžžťťťťťťťťťťśśśśśśśśśś››››››››šššššššššš™™™™™™™™————————––––––––••••••””””””””““““““““’’’’’’‘‘‘‘‘‘ŹŹŹŹŹŹŽŽŽŽŽŽŤŤŤŤŤŤŚŚŚŚŚŚ‹‹‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‰‰‡‡‡‡††††††………………„„„„„„‚‚‚‚€€€€€€~~~~}}}}}}||||||{{{{{{zzzzyyyyyyxxxxxxwwwwwwvvvvvvuuuuuuttttttssssssrrrrrrqqqqqqppppppoooooonnnnnnmmmmmmmmllllllkkkkkkkkjjjjjjiiiiiiiihhhhhhhhggggggggffffffffeeeeeeeeeeddddddddccccccccccbbbbbbbbaaaaaaaaaaaa``````````__________^^^^^^^^^^^^]]]]]]]]]]]]\\\\\\\\\\\\\\[[[[[[[[[[[[[[ZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇ          źźźźźźźźźźźźžžžžžžžžžžťťťťťťťťťťśśśśśśśśśś››››››››››šššššššš™™™™™™™™————————––––––––••••••••””””””““““““““’’’’’’‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŽŽŽŽŽŽŤŤŤŤŤŤŤŤŚŚŚŚŚŚ‹‹‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‰‰‡‡‡‡‡‡††††††………………„„„„„„‚‚‚‚€€€€€€~~~~}}}}}}||||||{{{{{{zzzzyyyyyyxxxxxxwwwwwwvvvvvvuuuuuuttttttssssssrrrrrrqqqqqqppppppoooooooonnnnnnmmmmmmllllllllkkkkkkjjjjjjjjiiiiiiiihhhhhhhhggggggggffffffffeeeeeeeeddddddddddccccccccbbbbbbbbbbaaaaaaaaaa````````````__________^^^^^^^^^^^^]]]]]]]]]]]]]]\\\\\\\\\\\\[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇ          źźźźźźźźźźźźžžžžžžžžžžťťťťťťťťťťśśśśśśśśśś››››››››››šššššššš™™™™™™™™————————––––––––••••••””””””””““““““““’’’’’’‘‘‘‘‘‘ŹŹŹŹŹŹŽŽŽŽŽŽŤŤŤŤŤŤŚŚŚŚŚŚ‹‹‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‰‰‡‡‡‡‡‡††††††………………„„„„„„‚‚‚‚‚‚€€€€€€~~~~}}}}}}||||||{{{{{{zzzzzzyyyyxxxxxxwwwwwwvvvvvvuuuuuuttttttssssssrrrrrrqqqqqqppppppppoooooonnnnnnmmmmmmmmllllllkkkkkkkkjjjjjjjjiiiiiihhhhhhhhggggggggffffffffffeeeeeeeeddddddddddccccccccbbbbbbbbbbaaaaaaaaaa````````````____________^^^^^^^^^^]]]]]]]]]]]]]]\\\\\\\\\\\\[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇ          źźźźźźźźźźźźžžžžžžžžžžťťťťťťťťťťśśśśśśśśśś››››››››››šššššššš™™™™™™™™™™————————––––––––••••••••””””””““““““““’’’’’’‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŽŽŽŽŽŽŤŤŤŤŤŤŚŚŚŚŚŚ‹‹‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‰‰‡‡‡‡‡‡††††††………………„„„„„„‚‚‚‚‚‚€€€€€€~~~~}}}}}}||||||{{{{{{zzzzzzyyyyyyxxxxxxwwwwvvvvvvuuuuuuttttttssssssssrrrrrrqqqqqqppppppoooooonnnnnnnnmmmmmmllllllllkkkkkkjjjjjjjjiiiiiiiihhhhhhhhggggggggffffffffeeeeeeeeeeddddddddccccccccccbbbbbbbbbbaaaaaaaaaa````````````____________^^^^^^^^^^^^]]]]]]]]]]]]\\\\\\\\\\\\\\[[[[[[[[[[[[[[ZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇ            źźźźźźźźźźźźžžžžžžžžžžťťťťťťťťťťťťśśśśśśśś››››››››››šššššššššš™™™™™™™™————————––––––––••••••••””””””””““““““’’’’’’’’‘‘‘‘‘‘ŹŹŹŹŹŹŽŽŽŽŽŽŤŤŤŤŤŤŤŤŚŚŚŚŚŚ‹‹‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‰‰‡‡‡‡‡‡††††††………………„„„„‚‚‚‚‚‚€€€€€€~~~~}}}}}}||||||{{{{{{zzzzzzyyyyyyxxxxxxwwwwwwvvvvvvuuuuuuttttttssssssrrrrrrqqqqqqppppppppoooooonnnnnnmmmmmmmmllllllkkkkkkkkjjjjjjjjiiiiiiiihhhhhhhhggggggggffffffffeeeeeeeeeeddddddddccccccccccbbbbbbbbbbaaaaaaaaaa````````````____________^^^^^^^^^^^^]]]]]]]]]]]]\\\\\\\\\\\\\\[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLL˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇ            źźźźźźźźźźźźžžžžžžžžžžťťťťťťťťťťťťśśśśśśśśśś››››››››šššššššššš™™™™™™™™————————––––––––••••••••””””””““““““““’’’’’’’’‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŽŽŽŽŽŽŤŤŤŤŤŤŚŚŚŚŚŚ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‰‰‡‡‡‡‡‡††††††…………„„„„„„‚‚‚‚‚‚€€€€€€~~~~~~}}}}||||||{{{{{{zzzzzzyyyyyyxxxxxxwwwwwwvvvvvvuuuuuuttttttssssssrrrrrrqqqqqqqqppppppoooooonnnnnnnnmmmmmmllllllllkkkkkkkkjjjjjjiiiiiiiihhhhhhhhggggggggggffffffffeeeeeeeeddddddddddccccccccccbbbbbbbbbbaaaaaaaaaa````````````____________^^^^^^^^^^^^]]]]]]]]]]]]\\\\\\\\\\\\\\[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLL˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇ            źźźźźźźźźźźźžžžžžžžžžžžžťťťťťťťťťťśśśśśśśśśś››››››››››šššššššš™™™™™™™™™™————————––––––––••••••••””””””””““““““’’’’’’’’‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŽŽŽŽŽŽŽŽŤŤŤŤŤŤŚŚŚŚŚŚ‹‹‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‰‰‡‡‡‡‡‡††††††………………„„„„„„‚‚‚‚‚‚€€€€€€~~~~~~}}}}||||||{{{{{{zzzzzzyyyyyyxxxxxxwwwwwwvvvvvvuuuuuuttttttssssssssrrrrrrqqqqqqppppppoooooooonnnnnnmmmmmmmmllllllkkkkkkkkjjjjjjjjiiiiiiiihhhhhhhhggggggggffffffffffeeeeeeeeddddddddddccccccccccbbbbbbbbbbaaaaaaaaaa````````````____________^^^^^^^^^^^^]]]]]]]]]]]]\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLL˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇ            źźźźźźźźźźźźžžžžžžžžžžžžťťťťťťťťťťśśśśśśśśśś››››››››››šššššššš™™™™™™™™™™————————––––––––••••••••””””””””““““““““’’’’’’’’‘‘‘‘‘‘ŹŹŹŹŹŹŽŽŽŽŽŽŤŤŤŤŤŤŤŤŚŚŚŚŚŚ‹‹‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‰‰‡‡‡‡‡‡††††††………………„„„„„„‚‚‚‚‚‚€€€€€€~~~~~~}}}}}}||||{{{{{{zzzzzzyyyyyyxxxxxxwwwwwwvvvvvvuuuuuuuuttttttssssssrrrrrrqqqqqqppppppppoooooonnnnnnnnmmmmmmllllllllkkkkkkkkjjjjjjjjiiiiiiiihhhhhhhhggggggggffffffffeeeeeeeeeeddddddddddccccccccccbbbbbbbbbbaaaaaaaaaa````````````____________^^^^^^^^^^^^]]]]]]]]]]]]]]\\\\\\\\\\\\\\[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLL˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇ            źźźźźźźźźźźźžžžžžžžžžžžžťťťťťťťťťťśśśśśśśśśś››››››››››šššššššššš™™™™™™™™————————––––––––••••••••””””””””““““““’’’’’’’’‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŽŽŽŽŽŽŤŤŤŤŤŤŚŚŚŚŚŚ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‰‰‡‡‡‡‡‡††††††………………„„„„„„‚‚‚‚‚‚€€€€€€~~~~~~}}}}}}||||||{{{{{{zzzzyyyyyyxxxxxxxxwwwwwwvvvvvvuuuuuuttttttssssssrrrrrrqqqqqqqqppppppoooooooonnnnnnmmmmmmmmllllllkkkkkkkkjjjjjjjjiiiiiiiihhhhhhhhggggggggggffffffffeeeeeeeeeeddddddddccccccccccbbbbbbbbbbbbaaaaaaaaaa````````````____________^^^^^^^^^^^^]]]]]]]]]]]]]]\\\\\\\\\\\\\\[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLL˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇ            źźźźźźźźźźźźžžžžžžžžžžžžťťťťťťťťťťśśśśśśśśśś››››››››››šššššššššš™™™™™™™™————————––––––––••••••••””””””””““““““““’’’’’’’’‘‘‘‘‘‘ŹŹŹŹŹŹŽŽŽŽŽŽŽŽŤŤŤŤŤŤŚŚŚŚŚŚ‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‡‡‡‡‡‡††††††………………„„„„„„‚‚‚‚‚‚€€€€€€~~~~~~}}}}}}||||||{{{{{{zzzzzzyyyyyyxxxxxxwwwwwwvvvvvvuuuuuuttttttssssssssrrrrrrqqqqqqppppppppoooooonnnnnnnnmmmmmmllllllllkkkkkkkkjjjjjjjjiiiiiiiihhhhhhhhggggggggffffffffffeeeeeeeeddddddddddccccccccccbbbbbbbbbbbbaaaaaaaaaa````````````____________^^^^^^^^^^^^]]]]]]]]]]]]]]\\\\\\\\\\\\\\[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇ            źźźźźźźźźźźźžžžžžžžžžžžžťťťťťťťťťťśśśśśśśśśś››››››››››šššššššššš™™™™™™™™™™————————––––––––––••••••••””””””””““““““’’’’’’’’‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŽŽŽŽŽŽŤŤŤŤŤŤŤŤŚŚŚŚŚŚ‹‹‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‰‰‡‡‡‡‡‡††††††………………„„„„„„‚‚‚‚‚‚€€€€€€~~~~~~}}}}}}||||||{{{{{{zzzzzzyyyyyyxxxxxxwwwwwwvvvvvvuuuuuuttttttttssssssrrrrrrqqqqqqqqppppppoooooonnnnnnnnmmmmmmmmllllllllkkkkkkjjjjjjjjiiiiiiiihhhhhhhhhhggggggggffffffffffeeeeeeeeddddddddddccccccccccbbbbbbbbbbaaaaaaaaaaaa````````````____________^^^^^^^^^^^^]]]]]]]]]]]]]]\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇ            źźźźźźźźźźźźžžžžžžžžžžžžťťťťťťťťťťśśśśśśśśśśśś››››››››››šššššššš™™™™™™™™™™————————––––––––••••••••””””””””““““““““’’’’’’’’‘‘‘‘‘‘ŹŹŹŹŹŹŽŽŽŽŽŽŽŽŤŤŤŤŤŤŚŚŚŚŚŚŚŚ‹‹‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‰‰‡‡‡‡‡‡††††††††………………„„„„„„‚‚‚‚‚‚€€€€€€~~~~~~}}}}}}||||||{{{{{{zzzzzzyyyyyyxxxxxxwwwwwwvvvvvvuuuuuuuuttttttssssssrrrrrrrrqqqqqqppppppoooooooonnnnnnnnmmmmmmllllllllkkkkkkkkjjjjjjjjiiiiiiiihhhhhhhhggggggggggffffffffeeeeeeeeeeddddddddddccccccccccbbbbbbbbbbaaaaaaaaaaaa````````````____________^^^^^^^^^^^^]]]]]]]]]]]]]]\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇ            źźźźźźźźźźźźžžžžžžžžžžžžťťťťťťťťťťśśśśśśśśśśśś››››››››››šššššššššš™™™™™™™™————————––––––––••••••••••””””””““““““““’’’’’’’’‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŽŽŽŽŽŽŤŤŤŤŤŤŤŤŚŚŚŚŚŚ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‰‰‡‡‡‡‡‡††††††……………………„„„„„„‚‚‚‚‚‚€€€€€€~~~~~~}}}}}}||||||{{{{{{zzzzzzyyyyyyxxxxxxwwwwwwvvvvvvvvuuuuuuttttttssssssrrrrrrrrqqqqqqppppppppoooooonnnnnnnnmmmmmmmmllllllllkkkkkkkkjjjjjjjjiiiiiiiihhhhhhhhggggggggffffffffffeeeeeeeeeeddddddddddccccccccccbbbbbbbbbbaaaaaaaaaaaa````````````____________^^^^^^^^^^^^]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇ            źźźźźźźźźźźźžžžžžžžžžžžžťťťťťťťťťťťťśśśśśśśśśś››››››››››šššššššššš™™™™™™™™™™——————————––––––––••••••••””””””””““““““““’’’’’’’’‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŽŽŽŽŽŽŤŤŤŤŤŤŤŤŚŚŚŚŚŚ‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‡‡‡‡‡‡††††††………………„„„„„„‚‚‚‚‚‚€€€€€€~~~~~~}}}}}}||||||{{{{{{zzzzzzyyyyyyxxxxxxwwwwwwwwvvvvvvuuuuuuttttttssssssssrrrrrrqqqqqqqqppppppoooooooonnnnnnmmmmmmmmllllllllkkkkkkkkjjjjjjjjiiiiiiiihhhhhhhhhhggggggggffffffffffeeeeeeeeeeddddddddddccccccccccbbbbbbbbbbaaaaaaaaaaaa````````````____________^^^^^^^^^^^^^^]]]]]]]]]]]]\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMM±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇ            źźźźźźźźźźźźžžžžžžžžžžžžťťťťťťťťťťťťśśśśśśśśśś››››››››››šššššššššš™™™™™™™™™™——————————––––––––••••••••””””””””““““““““’’’’’’’’‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŽŽŽŽŽŽŽŽŤŤŤŤŤŤŚŚŚŚŚŚŚŚ‹‹‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‡‡‡‡‡‡††††††………………„„„„„„‚‚‚‚‚‚‚‚€€€€€€~~~~~~}}}}}}||||||{{{{{{zzzzzzyyyyyyxxxxxxxxwwwwwwvvvvvvuuuuuuttttttttssssssrrrrrrrrqqqqqqppppppppoooooonnnnnnnnmmmmmmmmllllllllkkkkkkkkjjjjjjjjiiiiiiiihhhhhhhhggggggggggffffffffffeeeeeeeeddddddddddccccccccccccbbbbbbbbbbaaaaaaaaaaaa````````````____________^^^^^^^^^^^^^^]]]]]]]]]]]]]]\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMM±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇ            źźźźźźźźźźźźžžžžžžžžžžžžťťťťťťťťťťťťśśśśśśśśśś››››››››››šššššššššš™™™™™™™™™™————————––––––––––••••••••””””””””““““““““’’’’’’’’‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŽŽŽŽŽŽŤŤŤŤŤŤŤŤŚŚŚŚŚŚ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‰‰‡‡‡‡‡‡††††††………………„„„„„„‚‚‚‚‚‚‚‚€€€€€€~~~~~~}}}}}}||||||{{{{{{zzzzzzyyyyyyyyxxxxxxwwwwwwvvvvvvuuuuuuuuttttttssssssrrrrrrrrqqqqqqppppppppoooooooonnnnnnmmmmmmmmllllllllkkkkkkkkjjjjjjjjiiiiiiiiiihhhhhhhhggggggggggffffffffeeeeeeeeeeddddddddddccccccccccbbbbbbbbbbbbaaaaaaaaaaaa````````````____________^^^^^^^^^^^^^^]]]]]]]]]]]]]]\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMM±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇ            źźźźźźźźźźźźžžžžžžžžžžžžťťťťťťťťťťťťśśśśśśśśśś››››››››››››šššššššššš™™™™™™™™——————————––––––––••••••••””””””””““““““““’’’’’’’’‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŚŚŚŚŚŚ‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‡‡‡‡‡‡‡‡††††††………………„„„„„„‚‚‚‚‚‚€€€€€€~~~~~~}}}}}}||||||{{{{{{zzzzzzzzyyyyyyxxxxxxwwwwwwvvvvvvvvuuuuuuttttttssssssssrrrrrrqqqqqqqqppppppoooooooonnnnnnnnmmmmmmmmllllllllkkkkkkkkjjjjjjjjiiiiiiiihhhhhhhhhhggggggggffffffffffeeeeeeeeeeddddddddddccccccccccbbbbbbbbbbbbaaaaaaaaaaaa````````````____________^^^^^^^^^^^^^^]]]]]]]]]]]]]]\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMM±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇ            źźźźźźźźźźźźžžžžžžžžžžžžťťťťťťťťťťťťśśśśśśśśśśśś››››››››››šššššššššš™™™™™™™™™™——————————––––––––••••••••••””””””””““““““““’’’’’’’’‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŤŤŤŤŤŤŚŚŚŚŚŚŚŚ‹‹‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‡‡‡‡‡‡††††††††………………„„„„„„‚‚‚‚‚‚€€€€€€~~~~~~}}}}}}||||||{{{{{{{{zzzzzzyyyyyyxxxxxxwwwwwwwwvvvvvvuuuuuuttttttttssssssrrrrrrrrqqqqqqppppppppoooooooonnnnnnmmmmmmmmllllllllkkkkkkkkjjjjjjjjjjiiiiiiiihhhhhhhhggggggggggffffffffffeeeeeeeeeeddddddddddccccccccccbbbbbbbbbbbbaaaaaaaaaaaa````````````____________^^^^^^^^^^^^^^]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMM±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇ              źźźźźźźźźźźźžžžžžžžžžžžžťťťťťťťťťťťťśśśśśśśśśśśś››››››››››šššššššššš™™™™™™™™™™————————––––––––––••••••••””””””””““““““““’’’’’’’’‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŽŽŽŽŽŽŤŤŤŤŤŤŤŤŚŚŚŚŚŚ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‡‡‡‡‡‡††††††……………………„„„„„„‚‚‚‚‚‚€€€€€€~~~~~~}}}}}}||||||{{{{{{{{zzzzzzyyyyyyxxxxxxwwwwwwwwvvvvvvuuuuuuuuttttttssssssrrrrrrrrqqqqqqqqppppppoooooooonnnnnnnnmmmmmmmmllllllllkkkkkkkkjjjjjjjjiiiiiiiihhhhhhhhhhggggggggggffffffffeeeeeeeeeeddddddddddddccccccccccbbbbbbbbbbbbaaaaaaaaaaaa````````````____________^^^^^^^^^^^^^^]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇ              źźźźźźźźźźźźžžžžžžžžžžžžžžťťťťťťťťťťśśśśśśśśśśśś››››››››››šššššššššš™™™™™™™™™™——————————––––––––••••••••””””””””””““““““““’’’’’’’’‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŚŚŚŚŚŚ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‰‰‡‡‡‡‡‡††††††……………………„„„„„„‚‚‚‚‚‚€€€€€€~~~~~~}}}}}}||||||||{{{{{{zzzzzzyyyyyyxxxxxxxxwwwwwwvvvvvvuuuuuuuuttttttssssssssrrrrrrqqqqqqqqppppppppoooooonnnnnnnnmmmmmmmmllllllllkkkkkkkkjjjjjjjjjjiiiiiiiihhhhhhhhhhggggggggffffffffffeeeeeeeeeeddddddddddccccccccccccbbbbbbbbbbbbaaaaaaaaaaaa````````````____________^^^^^^^^^^^^^^]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇ              źźźźźźźźźźźźźźžžžžžžžžžžžžťťťťťťťťťťśśśśśśśśśśśś››››››››››šššššššššššš™™™™™™™™™™——————————––––––––––••••••••””””””””““““““““’’’’’’’’‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŤŤŤŤŤŤŚŚŚŚŚŚŚŚ‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‡‡‡‡‡‡††††††………………„„„„„„„„‚‚‚‚‚‚€€€€€€€€~~~~~~}}}}}}||||||||{{{{{{zzzzzzyyyyyyyyxxxxxxwwwwwwvvvvvvvvuuuuuuttttttttssssssrrrrrrrrqqqqqqppppppppoooooooonnnnnnnnmmmmmmmmllllllllkkkkkkkkjjjjjjjjiiiiiiiiiihhhhhhhhggggggggggffffffffffeeeeeeeeeeddddddddddccccccccccccbbbbbbbbbbaaaaaaaaaaaa``````````````____________^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇ              źźźźźźźźźźźźźźžžžžžžžžžžžžťťťťťťťťťťťťśśśśśśśśśś››››››››››››šššššššššš™™™™™™™™™™————————––––––––––••••••••””””””””””““““““““’’’’’’’’‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŚŚŚŚŚŚ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡††††††………………„„„„„„„„‚‚‚‚‚‚€€€€€€€€~~~~~~}}}}}}||||||||{{{{{{zzzzzzyyyyyyyyxxxxxxwwwwwwvvvvvvvvuuuuuuttttttttssssssrrrrrrrrqqqqqqqqppppppoooooooonnnnnnnnmmmmmmmmllllllllkkkkkkkkkkjjjjjjjjiiiiiiiihhhhhhhhhhggggggggggffffffffffeeeeeeeeeeddddddddddccccccccccccbbbbbbbbbbaaaaaaaaaaaa``````````````____________^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇ              źźźźźźźźźźźźźźžžžžžžžžžžžžťťťťťťťťťťťťśśśśśśśśśś››››››››››››šššššššššš™™™™™™™™™™——————————––––––––••••••••••””””””””““““““““’’’’’’’’‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŤŤŤŤŤŤŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡††††††………………„„„„„„„„‚‚‚‚‚‚€€€€€€€€~~~~~~}}}}}}}}||||||{{{{{{zzzzzzyyyyyyyyxxxxxxwwwwwwwwvvvvvvuuuuuuuuttttttssssssssrrrrrrqqqqqqqqppppppppoooooooonnnnnnnnmmmmmmmmllllllllkkkkkkkkjjjjjjjjjjiiiiiiiihhhhhhhhhhggggggggffffffffffeeeeeeeeeeeeddddddddddccccccccccbbbbbbbbbbbbaaaaaaaaaaaa``````````````____________^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇ              źźźźźźźźźźźźźźžžžžžžžžžžžžťťťťťťťťťťťťśśśśśśśśśśśś››››››››››šššššššššš™™™™™™™™™™——————————––––––––––••••••••””””””””““““““““““’’’’’’’’‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŽŽŽŽŽŽŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚ‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‡‡‡‡‡‡††††††††………………„„„„„„‚‚‚‚‚‚€€€€€€€€~~~~~~}}}}}}}}||||||{{{{{{zzzzzzzzyyyyyyxxxxxxwwwwwwwwvvvvvvuuuuuuuuttttttssssssssrrrrrrrrqqqqqqppppppppoooooooonnnnnnnnmmmmmmmmllllllllkkkkkkkkkkjjjjjjjjiiiiiiiiiihhhhhhhhggggggggggffffffffffeeeeeeeeeeddddddddddddccccccccccbbbbbbbbbbbbaaaaaaaaaaaa``````````````____________^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNN°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇ              źźźźźźźźźźźźźźžžžžžžžžžžžžťťťťťťťťťťťťśśśśśśśśśśśś››››››››››šššššššššššš™™™™™™™™™™————————––––––––––••••••••••””””””””““““““““’’’’’’’’‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŚŚŚŚŚŚ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‡‡‡‡‡‡††††††††………………„„„„„„‚‚‚‚‚‚€€€€€€€€~~~~~~}}}}}}}}||||||{{{{{{zzzzzzzzyyyyyyxxxxxxxxwwwwwwvvvvvvvvuuuuuuttttttttssssssrrrrrrrrqqqqqqqqppppppppoooooooonnnnnnnnmmmmmmmmllllllllkkkkkkkkjjjjjjjjjjiiiiiiiihhhhhhhhhhggggggggggffffffffffeeeeeeeeeeddddddddddddccccccccccbbbbbbbbbbbbaaaaaaaaaaaa``````````````____________^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNN°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇ              źźźźźźźźźźźźźźžžžžžžžžžžžžťťťťťťťťťťťťśśśśśśśśśśśś››››››››››šššššššššššš™™™™™™™™™™——————————––––––––••••••••••””””””””““““““““““’’’’’’’’‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŤŤŤŤŤŤŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡††††††……………………„„„„„„‚‚‚‚‚‚€€€€€€€€~~~~~~}}}}}}}}||||||{{{{{{{{zzzzzzyyyyyyxxxxxxxxwwwwwwvvvvvvvvuuuuuuttttttttssssssssrrrrrrqqqqqqqqppppppppoooooooonnnnnnnnmmmmmmmmllllllllkkkkkkkkkkjjjjjjjjiiiiiiiiiihhhhhhhhhhggggggggggffffffffffeeeeeeeeeeddddddddddccccccccccccbbbbbbbbbbbbaaaaaaaaaaaa``````````````______________^^^^^^^^^^^^^^]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNN°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ              źźźźźźźźźźźźźźžžžžžžžžžžžžťťťťťťťťťťťťśśśśśśśśśśśś››››››››››››šššššššššš™™™™™™™™™™——————————––––––––––••••••••””””””””””““““““““’’’’’’’’‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡††††††……………………„„„„„„‚‚‚‚‚‚€€€€€€€€~~~~~~}}}}}}}}||||||{{{{{{{{zzzzzzyyyyyyyyxxxxxxwwwwwwwwvvvvvvuuuuuuuuttttttssssssssrrrrrrrrqqqqqqqqppppppppoooooooonnnnnnnnmmmmmmmmllllllllkkkkkkkkjjjjjjjjjjiiiiiiiiiihhhhhhhhggggggggggffffffffffeeeeeeeeeeeeddddddddddccccccccccccbbbbbbbbbbbbaaaaaaaaaaaa``````````````______________^^^^^^^^^^^^^^]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNN°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ              źźźźźźźźźźźźźźžžžžžžžžžžžžťťťťťťťťťťťťśśśśśśśśśśśś››››››››››››šššššššššš™™™™™™™™™™™™——————————––––––––••••••••••””””””””““““““““““’’’’’’’’‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚ‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡††††††……………………„„„„„„‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~}}}}}}}}||||||{{{{{{{{zzzzzzyyyyyyyyxxxxxxwwwwwwwwvvvvvvuuuuuuuuttttttttssssssrrrrrrrrqqqqqqqqppppppppoooooooonnnnnnnnmmmmmmmmllllllllllkkkkkkkkjjjjjjjjiiiiiiiiiihhhhhhhhhhggggggggggffffffffffeeeeeeeeeeddddddddddddccccccccccccbbbbbbbbbbbbaaaaaaaaaaaa``````````````______________^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNN°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ              źźźźźźźźźźźźźźžžžžžžžžžžžžťťťťťťťťťťťťśśśśśśśśśśśś››››››››››››šššššššššššš™™™™™™™™™™——————————––––––––––••••••••””””””””””““““““““’’’’’’’’‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŤŤŤŤŤŤŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‡‡‡‡‡‡††††††††………………„„„„„„„„‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}||||||{{{{{{{{zzzzzzyyyyyyyyxxxxxxwwwwwwwwvvvvvvvvuuuuuuttttttttssssssssrrrrrrrrqqqqqqppppppppoooooooonnnnnnnnnnmmmmmmmmllllllllkkkkkkkkkkjjjjjjjjiiiiiiiiiihhhhhhhhhhggggggggggffffffffffeeeeeeeeeeddddddddddddccccccccccccbbbbbbbbbbbbaaaaaaaaaaaa``````````````______________^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONN°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ              źźźźźźźźźźźźźźžžžžžžžžžžžžťťťťťťťťťťťťťťśśśśśśśśśśśś››››››››››šššššššššššš™™™™™™™™™™——————————––––––––––••••••••••””””””””““““““““““’’’’’’’’‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‡‡‡‡‡‡††††††††………………„„„„„„„„‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}||||||{{{{{{{{zzzzzzyyyyyyyyxxxxxxxxwwwwwwvvvvvvvvuuuuuuttttttttssssssssrrrrrrrrqqqqqqqqppppppppoooooooonnnnnnnnmmmmmmmmllllllllllkkkkkkkkjjjjjjjjjjiiiiiiiihhhhhhhhhhggggggggggffffffffffffeeeeeeeeeeddddddddddddccccccccccbbbbbbbbbbbbbbaaaaaaaaaaaa``````````````______________^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ              źźźźźźźźźźźźźźžžžžžžžžžžžžťťťťťťťťťťťťťťśśśśśśśśśśśś››››››››››šššššššššššš™™™™™™™™™™——————————––––––––••••••••••””””””””””““““““““’’’’’’’’‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚ‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡††††††††………………„„„„„„„„‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}||||||||{{{{{{zzzzzzzzyyyyyyxxxxxxxxwwwwwwvvvvvvvvuuuuuuuuttttttttssssssrrrrrrrrqqqqqqqqppppppppoooooooonnnnnnnnmmmmmmmmmmllllllllkkkkkkkkkkjjjjjjjjiiiiiiiiiihhhhhhhhhhggggggggggffffffffffeeeeeeeeeeeeddddddddddccccccccccccbbbbbbbbbbbbaaaaaaaaaaaaaa``````````````______________^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ              źźźźźźźźźźźźźźžžžžžžžžžžžžťťťťťťťťťťťťťťśśśśśśśśśśśś››››››››››››šššššššššš™™™™™™™™™™™™——————————––––––––––••••••••••””””””””““““““““““’’’’’’’’‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŤŤŤŤŤŤŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡††††††……………………„„„„„„„„‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}||||||||{{{{{{zzzzzzzzyyyyyyxxxxxxxxwwwwwwwwvvvvvvuuuuuuuuttttttttssssssssrrrrrrrrqqqqqqqqppppppppoooooooonnnnnnnnmmmmmmmmllllllllllkkkkkkkkjjjjjjjjjjiiiiiiiiiihhhhhhhhhhggggggggggffffffffffeeeeeeeeeeeeddddddddddccccccccccccbbbbbbbbbbbbaaaaaaaaaaaaaa``````````````______________^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ              źźźźźźźźźźźźźźžžžžžžžžžžžžžžťťťťťťťťťťťťśśśśśśśśśśśś››››››››››››šššššššššššš™™™™™™™™™™——————————––––––––––••••••••••””””””””””““““““““’’’’’’’’’’‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‡‡‡‡‡‡‡‡††††††……………………„„„„„„‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}||||||||{{{{{{zzzzzzzzyyyyyyyyxxxxxxwwwwwwwwvvvvvvvvuuuuuuttttttttssssssssrrrrrrrrqqqqqqqqppppppppoooooooonnnnnnnnmmmmmmmmmmllllllllkkkkkkkkkkjjjjjjjjiiiiiiiiiihhhhhhhhhhggggggggggffffffffffffeeeeeeeeeeddddddddddddccccccccccccbbbbbbbbbbbbaaaaaaaaaaaaaa``````````````______________^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                źźźźźźźźźźźźźźžžžžžžžžžžžžžžťťťťťťťťťťťťśśśśśśśśśśśś››››››››››››šššššššššššš™™™™™™™™™™——————————––––––––––••••••••””””””””””““““““““““’’’’’’’’‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡††††††……………………„„„„„„‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}||||||||{{{{{{zzzzzzzzyyyyyyyyxxxxxxwwwwwwwwvvvvvvvvuuuuuuuuttttttssssssssrrrrrrrrqqqqqqqqppppppppoooooooooonnnnnnnnmmmmmmmmllllllllllkkkkkkkkjjjjjjjjjjiiiiiiiiiihhhhhhhhhhggggggggggffffffffffffeeeeeeeeeeddddddddddddccccccccccccbbbbbbbbbbbbaaaaaaaaaaaaaa``````````````______________^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                źźźźźźźźźźźźźźžžžžžžžžžžžžžžťťťťťťťťťťťťśśśśśśśśśśśś››››››››››››šššššššššššš™™™™™™™™™™™™——————————––––––––––••••••••••””””””””””““““““““’’’’’’’’’’‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‡‡‡‡‡‡††††††††……………………„„„„„„‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}||||||||{{{{{{{{zzzzzzyyyyyyyyxxxxxxxxwwwwwwvvvvvvvvuuuuuuuuttttttttssssssssrrrrrrrrqqqqqqqqppppppppoooooooonnnnnnnnmmmmmmmmmmllllllllkkkkkkkkkkjjjjjjjjjjiiiiiiiiiihhhhhhhhhhggggggggggffffffffffeeeeeeeeeeeeddddddddddddccccccccccccbbbbbbbbbbbbaaaaaaaaaaaaaa``````````````______________^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                źźźźźźźźźźźźźźžžžžžžžžžžžžžžťťťťťťťťťťťťśśśśśśśśśśśśśś››››››››››››šššššššššš™™™™™™™™™™™™——————————––––––––––••••••••••””””””””””““““““““““’’’’’’’’‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡††††††††……………………„„„„„„‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}||||||||{{{{{{{{zzzzzzyyyyyyyyxxxxxxxxwwwwwwvvvvvvvvuuuuuuuuttttttttssssssssrrrrrrrrqqqqqqqqppppppppoooooooonnnnnnnnnnmmmmmmmmllllllllllkkkkkkkkjjjjjjjjjjiiiiiiiiiihhhhhhhhhhggggggggggggffffffffffeeeeeeeeeeeeddddddddddddccccccccccccbbbbbbbbbbbbaaaaaaaaaaaaaa``````````````______________^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                źźźźźźźźźźźźźźžžžžžžžžžžžžžžťťťťťťťťťťťťśśśśśśśśśśśśśś››››››››››››šššššššššššš™™™™™™™™™™————————————––––––––––••••••••••””””””””““““““““““’’’’’’’’’’‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‡‡‡‡‡‡‡‡††††††††………………„„„„„„„„‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}||||||||{{{{{{{{zzzzzzyyyyyyyyxxxxxxxxwwwwwwwwvvvvvvvvuuuuuuttttttttssssssssrrrrrrrrqqqqqqqqppppppppppoooooooonnnnnnnnmmmmmmmmmmllllllllkkkkkkkkkkjjjjjjjjjjiiiiiiiiiihhhhhhhhhhggggggggggffffffffffffeeeeeeeeeeddddddddddddccccccccccccbbbbbbbbbbbbbbaaaaaaaaaaaaaa``````````````______________^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                źźźźźźźźźźźźźźžžžžžžžžžžžžžžťťťťťťťťťťťťťťśśśśśśśśśśśś››››››››››››šššššššššššš™™™™™™™™™™——————————––––––––––••••••••••””””””””””““““““““““’’’’’’’’‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡††††††††………………„„„„„„„„‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}}}||||||{{{{{{{{zzzzzzzzyyyyyyxxxxxxxxwwwwwwwwvvvvvvvvuuuuuuuuttttttttssssssssrrrrrrrrqqqqqqqqppppppppoooooooonnnnnnnnnnmmmmmmmmllllllllllkkkkkkkkkkjjjjjjjjiiiiiiiiiihhhhhhhhhhhhggggggggggffffffffffeeeeeeeeeeeeddddddddddddccccccccccccbbbbbbbbbbbbbbaaaaaaaaaaaaaa``````````````______________^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                źźźźźźźźźźźźźźžžžžžžžžžžžžžžťťťťťťťťťťťťťťśśśśśśśśśśśś››››››››››››šššššššššššš™™™™™™™™™™™™——————————––––––––––––••••••••””””””””””““““““““““’’’’’’’’’’‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡††††††……………………„„„„„„„„‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}}}||||||{{{{{{{{zzzzzzzzyyyyyyyyxxxxxxwwwwwwwwvvvvvvvvuuuuuuuuttttttttssssssssrrrrrrrrqqqqqqqqppppppppoooooooooonnnnnnnnmmmmmmmmmmllllllllkkkkkkkkkkjjjjjjjjjjiiiiiiiiiihhhhhhhhhhggggggggggggffffffffffeeeeeeeeeeeeddddddddddddccccccccccccbbbbbbbbbbbbbbaaaaaaaaaaaa````````````````______________^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                źźźźźźźźźźźźźźžžžžžžžžžžžžžžťťťťťťťťťťťťťťśśśśśśśśśśśś››››››››››››šššššššššššš™™™™™™™™™™™™————————————––––––––––••••••••••””””””””””““““““““““’’’’’’’’‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‡‡‡‡‡‡††††††††……………………„„„„„„„„‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}}}||||||{{{{{{{{zzzzzzzzyyyyyyyyxxxxxxxxwwwwwwvvvvvvvvuuuuuuuuttttttttssssssssrrrrrrrrqqqqqqqqqqppppppppoooooooonnnnnnnnnnmmmmmmmmllllllllllkkkkkkkkkkjjjjjjjjjjiiiiiiiiiihhhhhhhhhhggggggggggffffffffffffeeeeeeeeeeeeddddddddddddccccccccccccbbbbbbbbbbbbbbaaaaaaaaaaaa````````````````______________^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                źźźźźźźźźźźźźźžžžžžžžžžžžžžžťťťťťťťťťťťťťťśśśśśśśśśśśś››››››››››››››šššššššššš™™™™™™™™™™™™——————————––––––––––••••••••••””””””””””““““““““““’’’’’’’’’’‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‡‡‡‡‡‡‡‡††††††††……………………„„„„„„„„‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}}}||||||||{{{{{{zzzzzzzzyyyyyyyyxxxxxxxxwwwwwwwwvvvvvvvvuuuuuuttttttttttssssssssrrrrrrrrqqqqqqqqppppppppoooooooooonnnnnnnnmmmmmmmmmmllllllllkkkkkkkkkkjjjjjjjjjjiiiiiiiiiihhhhhhhhhhhhggggggggggffffffffffffeeeeeeeeeeeeddddddddddddccccccccccccbbbbbbbbbbbbaaaaaaaaaaaaaa````````````````______________^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                źźźźźźźźźźźźźźžžžžžžžžžžžžžžťťťťťťťťťťťťťťśśśśśśśśśśśśśś››››››››››››šššššššššššš™™™™™™™™™™™™——————————––––––––––––••••••••••””””””””””““““““““’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡††††††††……………………„„„„„„„„‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}}}||||||||{{{{{{zzzzzzzzyyyyyyyyxxxxxxxxwwwwwwwwvvvvvvvvuuuuuuuuttttttttssssssssrrrrrrrrqqqqqqqqppppppppppoooooooonnnnnnnnnnmmmmmmmmllllllllllkkkkkkkkkkjjjjjjjjjjiiiiiiiiiihhhhhhhhhhggggggggggggffffffffffeeeeeeeeeeeeddddddddddddccccccccccccccbbbbbbbbbbbbaaaaaaaaaaaaaa````````````````______________^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                źźźźźźźźźźźźźźžžžžžžžžžžžžžžťťťťťťťťťťťťťťśśśśśśśśśśśśśś››››››››››››šššššššššššš™™™™™™™™™™™™————————————––––––––––••••••••••””””””””””““““““““““’’’’’’’’’’‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡††††††††……………………„„„„„„‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}}}||||||||{{{{{{{{zzzzzzyyyyyyyyxxxxxxxxwwwwwwwwvvvvvvvvuuuuuuuuttttttttssssssssrrrrrrrrqqqqqqqqqqppppppppoooooooooonnnnnnnnmmmmmmmmmmllllllllkkkkkkkkkkjjjjjjjjjjiiiiiiiiiiiihhhhhhhhhhggggggggggffffffffffffeeeeeeeeeeeeddddddddddddccccccccccccccbbbbbbbbbbbbaaaaaaaaaaaaaa````````````````______________^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                źźźźźźźźźźźźźźžžžžžžžžžžžžžžťťťťťťťťťťťťťťśśśśśśśśśśśśśś››››››››››››šššššššššššš™™™™™™™™™™™™——————————––––––––––••••••••••••””””””””””““““““““’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡††††††††………………„„„„„„„„‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}}}||||||||{{{{{{{{zzzzzzyyyyyyyyxxxxxxxxwwwwwwwwvvvvvvvvuuuuuuuuttttttttssssssssssrrrrrrrrqqqqqqqqppppppppoooooooooonnnnnnnnnnmmmmmmmmllllllllllkkkkkkkkkkjjjjjjjjjjiiiiiiiiiihhhhhhhhhhhhggggggggggffffffffffffeeeeeeeeeeeeddddddddddddccccccccccccbbbbbbbbbbbbbbaaaaaaaaaaaaaa````````````````______________^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                źźźźźźźźźźźźźźžžžžžžžžžžžžžžťťťťťťťťťťťťťťśśśśśśśśśśśśśś››››››››››››šššššššššššš™™™™™™™™™™™™————————————––––––––––••••••••••””””””””””““““““““““’’’’’’’’’’‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡††††††……………………„„„„„„„„‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}}}||||||||{{{{{{{{zzzzzzzzyyyyyyyyxxxxxxwwwwwwwwvvvvvvvvuuuuuuuuuuttttttttssssssssrrrrrrrrqqqqqqqqppppppppppoooooooonnnnnnnnnnmmmmmmmmmmllllllllllkkkkkkkkjjjjjjjjjjjjiiiiiiiiiihhhhhhhhhhggggggggggggffffffffffffeeeeeeeeeeeeddddddddddddccccccccccccbbbbbbbbbbbbbbaaaaaaaaaaaaaa````````````````______________^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPP®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                źźźźźźźźźźźźźźžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťśśśśśśśśśśśś››››››››››››››šššššššššššš™™™™™™™™™™™™————————————––––––––––••••••••••””””””””””””““““““““’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡††††††††……………………„„„„„„„„‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}}}||||||||{{{{{{{{zzzzzzzzyyyyyyyyxxxxxxxxwwwwwwwwvvvvvvvvuuuuuuuuttttttttssssssssrrrrrrrrqqqqqqqqqqppppppppoooooooooonnnnnnnnmmmmmmmmmmllllllllllkkkkkkkkkkjjjjjjjjjjiiiiiiiiiihhhhhhhhhhhhggggggggggggffffffffffeeeeeeeeeeeeddddddddddddddccccccccccccbbbbbbbbbbbbbbaaaaaaaaaaaaaa````````````````______________^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPP®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                źźźźźźźźźźźźźźžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťśśśśśśśśśśśś››››››››››››››šššššššššššš™™™™™™™™™™™™——————————––––––––––––••••••••••””””””””””““““““““““’’’’’’’’’’‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡††††††††……………………„„„„„„„„‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}}}||||||||{{{{{{{{zzzzzzzzyyyyyyyyxxxxxxxxwwwwwwwwvvvvvvvvuuuuuuuuttttttttssssssssssrrrrrrrrqqqqqqqqppppppppppoooooooonnnnnnnnnnmmmmmmmmmmllllllllllkkkkkkkkkkjjjjjjjjjjiiiiiiiiiihhhhhhhhhhhhggggggggggffffffffffffeeeeeeeeeeeeddddddddddddccccccccccccccbbbbbbbbbbbbbbaaaaaaaaaaaaaa````````````````______________^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPP®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                  źźźźźźźźźźźźźźžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťśśśśśśśśśśśśśś››››››››››››šššššššššššš™™™™™™™™™™™™————————————––––––––––••••••••••””””””””””““““““““““’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡††††††††……………………„„„„„„„„‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}}}||||||||{{{{{{{{zzzzzzzzyyyyyyyyxxxxxxxxwwwwwwwwvvvvvvvvuuuuuuuuttttttttttssssssssrrrrrrrrqqqqqqqqqqppppppppoooooooooonnnnnnnnmmmmmmmmmmllllllllllkkkkkkkkkkjjjjjjjjjjiiiiiiiiiiiihhhhhhhhhhggggggggggggffffffffffffeeeeeeeeeeeeddddddddddddccccccccccccccbbbbbbbbbbbbbbaaaaaaaaaaaaaa````````````````________________^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPP®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                  źźźźźźźźźźźźźźžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťśśśśśśśśśśśśśś››››››››››››šššššššššššš™™™™™™™™™™™™————————————––––––––––••••••••••••””””””””””““““““““““’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡††††††††……………………„„„„„„„„‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}}}||||||||{{{{{{{{zzzzzzzzyyyyyyyyxxxxxxxxwwwwwwwwvvvvvvvvuuuuuuuuuuttttttttssssssssrrrrrrrrrrqqqqqqqqppppppppppoooooooonnnnnnnnnnmmmmmmmmmmllllllllllkkkkkkkkkkjjjjjjjjjjiiiiiiiiiihhhhhhhhhhhhggggggggggggffffffffffffeeeeeeeeeeeeddddddddddddccccccccccccccbbbbbbbbbbbbbbaaaaaaaaaaaaaa````````````````________________^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPP®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                  źźźźźźźźźźźźźźžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťśśśśśśśśśśśśśś››››››››››››šššššššššššššš™™™™™™™™™™™™——————————––––––––––––••••••••••””””””””””““““““““““’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡††††††††……………………„„„„„„„„‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}}}||||||||{{{{{{{{zzzzzzzzyyyyyyyyxxxxxxxxwwwwwwwwwwvvvvvvvvuuuuuuuuttttttttssssssssssrrrrrrrrqqqqqqqqppppppppppoooooooooonnnnnnnnmmmmmmmmmmllllllllllkkkkkkkkkkjjjjjjjjjjjjiiiiiiiiiihhhhhhhhhhhhggggggggggffffffffffffeeeeeeeeeeeeddddddddddddddccccccccccccccbbbbbbbbbbbbbbaaaaaaaaaaaaaa````````````````________________^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                  źźźźźźźźźźźźźźžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťśśśśśśśśśśśśśś››››››››››››››šššššššššššš™™™™™™™™™™™™————————————––––––––––••••••••••••””””””””””““““““““““’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡††††††††††……………………„„„„„„„„‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}}}||||||||{{{{{{{{zzzzzzzzyyyyyyyyyyxxxxxxxxwwwwwwwwvvvvvvvvuuuuuuuuttttttttssssssssssrrrrrrrrqqqqqqqqqqppppppppoooooooooonnnnnnnnnnmmmmmmmmmmllllllllllkkkkkkkkkkjjjjjjjjjjiiiiiiiiiiiihhhhhhhhhhggggggggggggffffffffffffeeeeeeeeeeeeddddddddddddddccccccccccccbbbbbbbbbbbbbbbbaaaaaaaaaaaaaa````````````````________________^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                  źźźźźźźźźźźźźźžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťśśśśśśśśśśśśśś››››››››››››››šššššššššššš™™™™™™™™™™™™————————————––––––––––––••••••••••””””””””””““““““““““’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡††††††††……………………„„„„„„„„‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}}}||||||||{{{{{{{{zzzzzzzzyyyyyyyyxxxxxxxxwwwwwwwwvvvvvvvvuuuuuuuuttttttttttssssssssrrrrrrrrrrqqqqqqqqppppppppppoooooooonnnnnnnnnnmmmmmmmmmmllllllllllkkkkkkkkkkjjjjjjjjjjjjiiiiiiiiiihhhhhhhhhhhhggggggggggggffffffffffffeeeeeeeeeeeeddddddddddddddccccccccccccbbbbbbbbbbbbbbaaaaaaaaaaaaaaaa````````````````________________^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                  źźźźźźźźźźźźźźžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťśśśśśśśśśśśśśś››››››››››››››šššššššššššš™™™™™™™™™™™™™™——————————––––––––––––••••••••••””””””””””””““““““““““’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡††††††††……………………„„„„„„„„‚‚‚‚‚‚‚‚€€€€€€€€€€~~~~~~~~}}}}}}}}||||||||{{{{{{{{zzzzzzzzyyyyyyyyxxxxxxxxwwwwwwwwvvvvvvvvuuuuuuuuuuttttttttssssssssrrrrrrrrrrqqqqqqqqqqppppppppoooooooooonnnnnnnnnnmmmmmmmmmmllllllllllkkkkkkkkkkjjjjjjjjjjiiiiiiiiiiiihhhhhhhhhhhhggggggggggffffffffffffeeeeeeeeeeeeeeddddddddddddccccccccccccccbbbbbbbbbbbbbbaaaaaaaaaaaaaaaa````````````````________________^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                źźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžťťťťťťťťťťťťťťśśśśśśśśśśśśśś››››››››››››››šššššššššššššš™™™™™™™™™™™™————————————––––––––––••••••••••••””””””””””““““““““““’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡††††††††……………………„„„„„„„„‚‚‚‚‚‚‚‚€€€€€€€€€€~~~~~~~~}}}}}}}}||||||||{{{{{{{{zzzzzzzzyyyyyyyyxxxxxxxxwwwwwwwwvvvvvvvvvvuuuuuuuuttttttttssssssssssrrrrrrrrqqqqqqqqqqppppppppppoooooooonnnnnnnnnnmmmmmmmmmmllllllllllkkkkkkkkkkkkjjjjjjjjjjiiiiiiiiiihhhhhhhhhhhhggggggggggggffffffffffffeeeeeeeeeeeeeeddddddddddddccccccccccccccbbbbbbbbbbbbbbaaaaaaaaaaaaaaaa````````````````________________^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQ®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                źźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťśśśśśśśśśśśśśś››››››››››››šššššššššššššš™™™™™™™™™™™™————————————––––––––––––••••••••••””””””””””””““““““““““’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡††††††††……………………„„„„„„„„‚‚‚‚‚‚‚‚€€€€€€€€€€~~~~~~~~}}}}}}}}||||||||{{{{{{{{zzzzzzzzyyyyyyyyxxxxxxxxwwwwwwwwwwvvvvvvvvuuuuuuuuttttttttttssssssssrrrrrrrrrrqqqqqqqqppppppppppoooooooooonnnnnnnnnnmmmmmmmmmmllllllllllkkkkkkkkkkjjjjjjjjjjjjiiiiiiiiiihhhhhhhhhhhhggggggggggggffffffffffffeeeeeeeeeeeeddddddddddddddccccccccccccccbbbbbbbbbbbbbbaaaaaaaaaaaaaaaa````````````````________________^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQ­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                źźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťśśśśśśśśśśśśśś››››››››››››››šššššššššššš™™™™™™™™™™™™——————————––––––––––––••••••••••••””””””””””““““““““““’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡††††††††……………………„„„„„„„„‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}}}||||||||{{{{{{{{zzzzzzzzyyyyyyyyxxxxxxxxxxwwwwwwwwvvvvvvvvuuuuuuuuuuttttttttssssssssrrrrrrrrrrqqqqqqqqqqppppppppoooooooooonnnnnnnnnnmmmmmmmmmmllllllllllkkkkkkkkkkkkjjjjjjjjjjiiiiiiiiiiiihhhhhhhhhhhhggggggggggggffffffffffffeeeeeeeeeeeeddddddddddddddccccccccccccccbbbbbbbbbbbbbbaaaaaaaaaaaaaaaa````````````````________________^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQ­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                źźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťśśśśśśśśśśśśśś››››››››››››››šššššššššššš™™™™™™™™™™™™™™————————————––––––––––––••••••••••””””””””””””““““““““““’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡††††††††††……………………„„„„„„„„‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}}}||||||||{{{{{{{{zzzzzzzzyyyyyyyyyyxxxxxxxxwwwwwwwwvvvvvvvvuuuuuuuuuuttttttttssssssssssrrrrrrrrqqqqqqqqqqppppppppppoooooooooonnnnnnnnnnmmmmmmmmmmllllllllllkkkkkkkkkkjjjjjjjjjjjjiiiiiiiiiihhhhhhhhhhhhggggggggggggffffffffffffeeeeeeeeeeeeeeddddddddddddddccccccccccccccbbbbbbbbbbbbbbaaaaaaaaaaaaaaaa````````````````________________^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQ­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                źźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťśśśśśśśśśśśśśś››››››››››››››šššššššššššššš™™™™™™™™™™™™————————————––––––––––––••••••••••••””””””””””““““““““““’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡††††††††…………………………„„„„„„„„‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}}}||||||||{{{{{{{{zzzzzzzzyyyyyyyyyyxxxxxxxxwwwwwwwwvvvvvvvvvvuuuuuuuuttttttttttssssssssrrrrrrrrrrqqqqqqqqppppppppppoooooooooonnnnnnnnnnmmmmmmmmmmllllllllllkkkkkkkkkkkkjjjjjjjjjjiiiiiiiiiiiihhhhhhhhhhhhggggggggggggffffffffffffeeeeeeeeeeeeeeddddddddddddccccccccccccccbbbbbbbbbbbbbbbbaaaaaaaaaaaaaa``````````````````________________^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQ­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                źźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťśśśśśśśśśśśśśś››››››››››››››šššššššššššššš™™™™™™™™™™™™————————————––––––––––••••••••••••””””””””””““““““““““““’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡††††††††…………………………„„„„„„„„‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}}}||||||||{{{{{{{{zzzzzzzzzzyyyyyyyyxxxxxxxxwwwwwwwwwwvvvvvvvvuuuuuuuuttttttttttssssssssrrrrrrrrrrqqqqqqqqqqppppppppppoooooooooonnnnnnnnnnmmmmmmmmmmllllllllllkkkkkkkkkkjjjjjjjjjjjjiiiiiiiiiiiihhhhhhhhhhhhggggggggggggffffffffffffeeeeeeeeeeeeddddddddddddddccccccccccccccbbbbbbbbbbbbbbbbaaaaaaaaaaaaaa``````````````````________________^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQ­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                źźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťśśśśśśśśśśśśśś››››››››››››››šššššššššššššš™™™™™™™™™™™™™™————————————––––––––––––••••••••••””””””””””””““““““““““’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡††††††††……………………„„„„„„„„„„‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}}}||||||||{{{{{{{{{{zzzzzzzzyyyyyyyyxxxxxxxxwwwwwwwwwwvvvvvvvvuuuuuuuuuuttttttttssssssssssrrrrrrrrqqqqqqqqqqppppppppppoooooooooonnnnnnnnnnmmmmmmmmmmllllllllllkkkkkkkkkkkkjjjjjjjjjjiiiiiiiiiiiihhhhhhhhhhhhggggggggggggffffffffffffffeeeeeeeeeeeeddddddddddddddccccccccccccccbbbbbbbbbbbbbbbbaaaaaaaaaaaaaa````````````````__________________^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQ­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                źźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťśśśśśśśśśśśśśśśś››››››››››››››šššššššššššš™™™™™™™™™™™™™™————————————––––––––––––••••••••••••””””””””””““““““““““““’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡††††††††……………………„„„„„„„„„„‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}}}||||||||{{{{{{{{{{zzzzzzzzyyyyyyyyxxxxxxxxxxwwwwwwwwvvvvvvvvuuuuuuuuuuttttttttssssssssssrrrrrrrrrrqqqqqqqqqqppppppppoooooooooonnnnnnnnnnmmmmmmmmmmmmllllllllllkkkkkkkkkkjjjjjjjjjjjjiiiiiiiiiiiihhhhhhhhhhhhggggggggggggffffffffffffeeeeeeeeeeeeeeddddddddddddddccccccccccccccbbbbbbbbbbbbbbbbaaaaaaaaaaaaaa````````````````__________________^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                  źźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťśśśśśśśśśśśśśśśś››››››››››››››šššššššššššššš™™™™™™™™™™™™——————————————––––––––––••••••••••••””””””””””””““““““““““’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡††††††††††……………………„„„„„„„„‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}}}||||||||{{{{{{{{{{zzzzzzzzyyyyyyyyxxxxxxxxxxwwwwwwwwvvvvvvvvvvuuuuuuuuttttttttttssssssssrrrrrrrrrrqqqqqqqqqqppppppppppoooooooooonnnnnnnnnnmmmmmmmmmmllllllllllllkkkkkkkkkkjjjjjjjjjjjjiiiiiiiiiihhhhhhhhhhhhhhggggggggggggffffffffffffeeeeeeeeeeeeeeddddddddddddddccccccccccccccbbbbbbbbbbbbbbaaaaaaaaaaaaaaaa````````````````__________________^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                  źźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťśśśśśśśśśśśśśś››››››››››››››šššššššššššššš™™™™™™™™™™™™————————————––––––––––––••••••••••••””””””””””““““““““““““’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡††††††††††……………………„„„„„„„„‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}}}||||||||||{{{{{{{{zzzzzzzzyyyyyyyyyyxxxxxxxxwwwwwwwwvvvvvvvvvvuuuuuuuuttttttttttssssssssssrrrrrrrrqqqqqqqqqqppppppppppoooooooooonnnnnnnnnnmmmmmmmmmmmmllllllllllkkkkkkkkkkkkjjjjjjjjjjiiiiiiiiiiiihhhhhhhhhhhhggggggggggggffffffffffffffeeeeeeeeeeeeeeddddddddddddddccccccccccccccbbbbbbbbbbbbbbaaaaaaaaaaaaaaaa````````````````__________________^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                  źźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťśśśśśśśśśśśśśś››››››››››››››šššššššššššššš™™™™™™™™™™™™™™————————————––––––––––––••••••••••••””””””””””””““““““““““’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡††††††††††……………………„„„„„„„„‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}}}||||||||||{{{{{{{{zzzzzzzzyyyyyyyyyyxxxxxxxxwwwwwwwwwwvvvvvvvvuuuuuuuuuuttttttttssssssssssrrrrrrrrrrqqqqqqqqqqppppppppppoooooooooonnnnnnnnnnmmmmmmmmmmllllllllllllkkkkkkkkkkjjjjjjjjjjjjiiiiiiiiiiiihhhhhhhhhhhhggggggggggggffffffffffffffeeeeeeeeeeeeddddddddddddddccccccccccccccccbbbbbbbbbbbbbbaaaaaaaaaaaaaaaa````````````````__________________^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                  źźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťśśśśśśśśśśśśśś››››››››››››››šššššššššššššš™™™™™™™™™™™™™™——————————————––––––––––––••••••••••””””””””””””““““““““““““’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡††††††††…………………………„„„„„„„„‚‚‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}}}||||||||||{{{{{{{{zzzzzzzzyyyyyyyyyyxxxxxxxxwwwwwwwwwwvvvvvvvvuuuuuuuuuuttttttttttssssssssrrrrrrrrrrqqqqqqqqqqppppppppppoooooooooonnnnnnnnnnmmmmmmmmmmmmllllllllllkkkkkkkkkkkkjjjjjjjjjjiiiiiiiiiiiihhhhhhhhhhhhggggggggggggggffffffffffffeeeeeeeeeeeeeeddddddddddddddccccccccccccccbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaa````````````````__________________^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRR­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                  źźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťśśśśśśśśśśśśśś››››››››››››››››šššššššššššššš™™™™™™™™™™™™————————————––––––––––––••••••••••••””””””””””””““““““““““’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡††††††††…………………………„„„„„„„„‚‚‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}}}||||||||||{{{{{{{{zzzzzzzzzzyyyyyyyyxxxxxxxxxxwwwwwwwwvvvvvvvvvvuuuuuuuuttttttttttssssssssssrrrrrrrrrrqqqqqqqqppppppppppoooooooooooonnnnnnnnnnmmmmmmmmmmllllllllllllkkkkkkkkkkjjjjjjjjjjjjiiiiiiiiiiiihhhhhhhhhhhhggggggggggggffffffffffffffeeeeeeeeeeeeeeddddddddddddddccccccccccccccbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaa````````````````__________________^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRR¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                  źźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśś››››››››››››››šššššššššššššš™™™™™™™™™™™™™™————————————––––––––––––••••••••••••””””””””””””““““““““““““’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡††††††††……………………„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}}}}}||||||||{{{{{{{{zzzzzzzzzzyyyyyyyyxxxxxxxxxxwwwwwwwwvvvvvvvvvvuuuuuuuuuuttttttttssssssssssrrrrrrrrrrqqqqqqqqqqppppppppppoooooooooonnnnnnnnnnmmmmmmmmmmmmllllllllllkkkkkkkkkkkkjjjjjjjjjjjjiiiiiiiiiiiihhhhhhhhhhhhggggggggggggffffffffffffffeeeeeeeeeeeeeeddddddddddddddccccccccccccccbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaa````````````````__________________^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRR¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                  źźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśś››››››››››››››šššššššššššššš™™™™™™™™™™™™™™——————————————––––––––––––••••••••••••””””””””””””““““““““““’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡††††††††††……………………„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}}}}}||||||||{{{{{{{{zzzzzzzzzzyyyyyyyyxxxxxxxxxxwwwwwwwwwwvvvvvvvvuuuuuuuuuuttttttttttssssssssrrrrrrrrrrqqqqqqqqqqppppppppppoooooooooonnnnnnnnnnnnmmmmmmmmmmllllllllllllkkkkkkkkkkjjjjjjjjjjjjiiiiiiiiiiiihhhhhhhhhhhhggggggggggggggffffffffffffffeeeeeeeeeeeeddddddddddddddddccccccccccccccbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaa````````````````__________________^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRR¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                  źźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśś››››››››››››››šššššššššššššš™™™™™™™™™™™™™™————————————––––––––––––••••••••••••””””””””””””““““““““““““’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡††††††††††……………………„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}}}}}||||||||{{{{{{{{{{zzzzzzzzyyyyyyyyyyxxxxxxxxwwwwwwwwwwvvvvvvvvuuuuuuuuuuttttttttttssssssssssrrrrrrrrrrqqqqqqqqqqppppppppppoooooooooonnnnnnnnnnmmmmmmmmmmmmllllllllllkkkkkkkkkkkkjjjjjjjjjjjjiiiiiiiiiiiihhhhhhhhhhhhggggggggggggggffffffffffffeeeeeeeeeeeeeeddddddddddddddccccccccccccccccbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaa````````````````__________________^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRR¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                  źźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśś››››››››››››››››šššššššššššššš™™™™™™™™™™™™————————————––––––––––––––••••••••••••””””””””””““““““““““““’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡††††††††††……………………„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}}}}}||||||||{{{{{{{{{{zzzzzzzzyyyyyyyyyyxxxxxxxxwwwwwwwwwwvvvvvvvvvvuuuuuuuuttttttttttssssssssssrrrrrrrrrrqqqqqqqqqqppppppppppoooooooooonnnnnnnnnnnnmmmmmmmmmmllllllllllllkkkkkkkkkkkkjjjjjjjjjjiiiiiiiiiiiiiihhhhhhhhhhhhggggggggggggffffffffffffffeeeeeeeeeeeeeeddddddddddddddccccccccccccccccbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaa````````````````__________________^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRR¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                  źźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťťťśśśśśśśśśśśśśś››››››››››››››››šššššššššššššš™™™™™™™™™™™™™™——————————————––––––––––––••••••••••••””””””””””””““““““““““““’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡††††††††…………………………„„„„„„„„‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}}}}}||||||||{{{{{{{{{{zzzzzzzzyyyyyyyyyyxxxxxxxxxxwwwwwwwwvvvvvvvvvvuuuuuuuuuuttttttttssssssssssrrrrrrrrrrqqqqqqqqqqppppppppppppoooooooooonnnnnnnnnnmmmmmmmmmmmmllllllllllkkkkkkkkkkkkjjjjjjjjjjjjiiiiiiiiiiiihhhhhhhhhhhhggggggggggggggffffffffffffffeeeeeeeeeeeeeeddddddddddddddccccccccccccccccbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaa````````````````____________________^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRR¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                  źźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťśśśśśśśśśśśśśś››››››››››››››››šššššššššššššš™™™™™™™™™™™™™™————————————––––––––––––••••••••••••””””””””””””““““““““““““’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡††††††††…………………………„„„„„„„„‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}}}}}||||||||{{{{{{{{{{zzzzzzzzyyyyyyyyyyxxxxxxxxxxwwwwwwwwvvvvvvvvvvuuuuuuuuuuttttttttttssssssssssrrrrrrrrrrqqqqqqqqqqppppppppppoooooooooonnnnnnnnnnnnmmmmmmmmmmllllllllllllkkkkkkkkkkkkjjjjjjjjjjjjiiiiiiiiiiiihhhhhhhhhhhhggggggggggggggffffffffffffeeeeeeeeeeeeeeeeddddddddddddddccccccccccccccbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaa````````````````____________________^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                  źźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśś››››››››››››››šššššššššššššš™™™™™™™™™™™™™™————————————––––––––––––––••••••••••••””””””””””””““““““““““““’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡††††††††††…………………………„„„„„„„„‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}}}}}||||||||{{{{{{{{{{zzzzzzzzzzyyyyyyyyxxxxxxxxxxwwwwwwwwwwvvvvvvvvuuuuuuuuuuttttttttttssssssssssrrrrrrrrrrqqqqqqqqqqppppppppppoooooooooooonnnnnnnnnnmmmmmmmmmmmmllllllllllkkkkkkkkkkkkjjjjjjjjjjjjiiiiiiiiiiiihhhhhhhhhhhhhhggggggggggggffffffffffffffeeeeeeeeeeeeeeddddddddddddddddccccccccccccccbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaa````````````````____________________^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                  źźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśś››››››››››››››šššššššššššššššš™™™™™™™™™™™™™™——————————————––––––––––––••••••••••••””””””””””””““““““““““““’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡††††††††††…………………………„„„„„„„„‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}}}}}||||||||||{{{{{{{{zzzzzzzzzzyyyyyyyyxxxxxxxxxxwwwwwwwwwwvvvvvvvvvvuuuuuuuuttttttttttssssssssssrrrrrrrrrrqqqqqqqqqqqqppppppppppoooooooooonnnnnnnnnnnnmmmmmmmmmmllllllllllllkkkkkkkkkkkkjjjjjjjjjjjjiiiiiiiiiiiihhhhhhhhhhhhggggggggggggggffffffffffffffeeeeeeeeeeeeeeddddddddddddddccccccccccccccccbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaa````````````````____________________^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                  źźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśś››››››››››››››››šššššššššššššš™™™™™™™™™™™™™™————————————––––––––––––––••••••••••••””””””””””””““““““““““““’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡††††††††††……………………„„„„„„„„„„‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~~~}}}}}}}}||||||||||{{{{{{{{zzzzzzzzzzyyyyyyyyyyxxxxxxxxwwwwwwwwwwvvvvvvvvvvuuuuuuuuuuttttttttttssssssssssrrrrrrrrrrqqqqqqqqqqppppppppppoooooooooooonnnnnnnnnnmmmmmmmmmmmmllllllllllkkkkkkkkkkkkjjjjjjjjjjjjiiiiiiiiiiiiiihhhhhhhhhhhhggggggggggggggffffffffffffffeeeeeeeeeeeeeeddddddddddddddccccccccccccccccbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaa````````````````____________________^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                    źźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśś››››››››››››››››šššššššššššššš™™™™™™™™™™™™™™——————————————––––––––––––••••••••••••””””””””””””““““““““““““’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡††††††††††……………………„„„„„„„„„„‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~~~}}}}}}}}||||||||||{{{{{{{{zzzzzzzzzzyyyyyyyyyyxxxxxxxxwwwwwwwwwwvvvvvvvvvvuuuuuuuuuuttttttttttssssssssssrrrrrrrrrrqqqqqqqqqqppppppppppppoooooooooonnnnnnnnnnmmmmmmmmmmmmllllllllllllkkkkkkkkkkkkjjjjjjjjjjjjiiiiiiiiiiiihhhhhhhhhhhhhhggggggggggggffffffffffffffeeeeeeeeeeeeeeeeddddddddddddddccccccccccccccccbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaa````````````````____________________^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSS¬¬¬¬««««««««««««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                    źźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśś››››››››››››››››šššššššššššššš™™™™™™™™™™™™™™——————————————––––––––––––••••••••••••••””””””””””””““““““““““““’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡††††††††…………………………„„„„„„„„„„‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~~~}}}}}}}}||||||||||{{{{{{{{{{zzzzzzzzyyyyyyyyyyxxxxxxxxxxwwwwwwwwvvvvvvvvvvuuuuuuuuuuttttttttttssssssssssrrrrrrrrrrqqqqqqqqqqqqppppppppppoooooooooonnnnnnnnnnnnmmmmmmmmmmmmllllllllllkkkkkkkkkkkkjjjjjjjjjjjjjjiiiiiiiiiiiihhhhhhhhhhhhggggggggggggggffffffffffffffeeeeeeeeeeeeeeddddddddddddddddccccccccccccccccbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaa``````````````````____________________^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSS««««««««««««««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                    źźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśś››››››››››››››››šššššššššššššššš™™™™™™™™™™™™™™————————————––––––––––––––••••••••••••””””””””””””““““““““““““’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡††††††††…………………………„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~~~}}}}}}}}||||||||||{{{{{{{{{{zzzzzzzzyyyyyyyyyyxxxxxxxxxxwwwwwwwwwwvvvvvvvvvvuuuuuuuuuuttttttttttssssssssssrrrrrrrrrrqqqqqqqqqqppppppppppoooooooooooonnnnnnnnnnmmmmmmmmmmmmllllllllllllkkkkkkkkkkkkjjjjjjjjjjjjiiiiiiiiiiiiiihhhhhhhhhhhhggggggggggggggffffffffffffffeeeeeeeeeeeeeeddddddddddddddddccccccccccccccccbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaa``````````````````____________________^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSS««««««««««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                    źźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśś››››››››››››››šššššššššššššššš™™™™™™™™™™™™™™——————————————––––––––––––••••••••••••””””””””””””””““““““““““’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡††††††††††…………………………„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~~~}}}}}}}}||||||||||{{{{{{{{{{zzzzzzzzyyyyyyyyyyxxxxxxxxxxwwwwwwwwwwvvvvvvvvvvuuuuuuuuuuttttttttttssssssssssrrrrrrrrrrqqqqqqqqqqppppppppppppoooooooooonnnnnnnnnnnnmmmmmmmmmmmmllllllllllllkkkkkkkkkkkkjjjjjjjjjjjjiiiiiiiiiiiihhhhhhhhhhhhhhggggggggggggggffffffffffffffeeeeeeeeeeeeeeddddddddddddddddccccccccccccccbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaa``````````````````____________________^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSS««««««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                    źźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśś››››››››››››››››šššššššššššššš™™™™™™™™™™™™™™——————————————––––––––––––––••••••••••••””””””””””””““““““““““““’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡††††††††††…………………………„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~~~}}}}}}}}||||||||||{{{{{{{{{{zzzzzzzzzzyyyyyyyyxxxxxxxxxxwwwwwwwwwwvvvvvvvvvvuuuuuuuuuuttttttttttssssssssssrrrrrrrrrrqqqqqqqqqqqqppppppppppoooooooooooonnnnnnnnnnmmmmmmmmmmmmllllllllllllkkkkkkkkkkkkjjjjjjjjjjjjiiiiiiiiiiiiiihhhhhhhhhhhhggggggggggggggffffffffffffffeeeeeeeeeeeeeeeeddddddddddddddccccccccccccccccbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaa``````````````````____________________^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSS««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                    źźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśś››››››››››››››››šššššššššššššš™™™™™™™™™™™™™™™™————————————––––––––––––––••••••••••••””””””””””””““““““““““““’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡††††††††††…………………………„„„„„„„„‚‚‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~~~}}}}}}}}||||||||||{{{{{{{{{{zzzzzzzzzzyyyyyyyyyyxxxxxxxxwwwwwwwwwwvvvvvvvvvvuuuuuuuuuuttttttttttssssssssssssrrrrrrrrrrqqqqqqqqqqppppppppppppoooooooooonnnnnnnnnnnnmmmmmmmmmmmmllllllllllllkkkkkkkkkkkkjjjjjjjjjjjjiiiiiiiiiiiihhhhhhhhhhhhhhggggggggggggggffffffffffffffeeeeeeeeeeeeeeeeddddddddddddddccccccccccccccccbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaa``````````````````____________________^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSS««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                    źźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśś››››››››››››››››šššššššššššššššš™™™™™™™™™™™™™™——————————————––––––––––––••••••••••••••””””””””””””““““““““““““’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡††††††††††…………………………„„„„„„„„‚‚‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~~~}}}}}}}}}}||||||||{{{{{{{{{{zzzzzzzzzzyyyyyyyyyyxxxxxxxxxxwwwwwwwwwwvvvvvvvvvvuuuuuuuuuuttttttttttssssssssssrrrrrrrrrrqqqqqqqqqqqqppppppppppoooooooooooonnnnnnnnnnmmmmmmmmmmmmllllllllllllkkkkkkkkkkkkjjjjjjjjjjjjjjiiiiiiiiiiiihhhhhhhhhhhhhhggggggggggggggffffffffffffffeeeeeeeeeeeeeeddddddddddddddddccccccccccccccccbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaa``````````````````____________________^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSS««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                    źźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśś››››››››››››››››šššššššššššššššš™™™™™™™™™™™™™™——————————————––––––––––––––••••••••••••””””””””””””““““““““““““’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡††††††††††……………………„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~~~}}}}}}}}}}||||||||{{{{{{{{{{zzzzzzzzzzyyyyyyyyyyxxxxxxxxxxwwwwwwwwwwvvvvvvvvvvuuuuuuuuuuttttttttttssssssssssrrrrrrrrrrrrqqqqqqqqqqppppppppppoooooooooooonnnnnnnnnnnnmmmmmmmmmmmmllllllllllllkkkkkkkkkkkkjjjjjjjjjjjjiiiiiiiiiiiiiihhhhhhhhhhhhggggggggggggggffffffffffffffffeeeeeeeeeeeeeeddddddddddddddddccccccccccccccccbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaa``````````````````____________________^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                    źźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśś››››››››››››››››šššššššššššššššš™™™™™™™™™™™™™™————————————––––––––––––––••••••••••••••””””””””””””““““““““““““’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡††††††††††…………………………„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~~~}}}}}}}}}}||||||||||{{{{{{{{zzzzzzzzzzyyyyyyyyyyxxxxxxxxxxwwwwwwwwwwvvvvvvvvvvuuuuuuuuuuttttttttttssssssssssssrrrrrrrrrrqqqqqqqqqqppppppppppppoooooooooonnnnnnnnnnnnmmmmmmmmmmmmllllllllllllkkkkkkkkkkkkjjjjjjjjjjjjjjiiiiiiiiiiiihhhhhhhhhhhhhhggggggggggggggffffffffffffffeeeeeeeeeeeeeeeeddddddddddddddddccccccccccccccccbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaa``````````````````____________________^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                    źźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśśśś››››››››››››››››šššššššššššššš™™™™™™™™™™™™™™™™——————————————––––––––––––••••••••••••••””””””””””””““““““““““““’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡††††††††††…………………………„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~~~}}}}}}}}}}||||||||||{{{{{{{{zzzzzzzzzzyyyyyyyyyyxxxxxxxxxxwwwwwwwwwwvvvvvvvvvvuuuuuuuuuuttttttttttttssssssssssrrrrrrrrrrqqqqqqqqqqqqppppppppppoooooooooooonnnnnnnnnnnnmmmmmmmmmmmmllllllllllllkkkkkkkkkkkkjjjjjjjjjjjjiiiiiiiiiiiiiihhhhhhhhhhhhhhggggggggggggggffffffffffffffeeeeeeeeeeeeeeeeddddddddddddddccccccccccccccccccbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaa``````````````````____________________^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                    źźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśśśś››››››››››››››››šššššššššššššššš™™™™™™™™™™™™™™——————————————––––––––––––––••••••••••••””””””””””””””““““““““““““’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡††††††††††…………………………„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€~~~~~~~~~~}}}}}}}}}}||||||||||{{{{{{{{{{zzzzzzzzyyyyyyyyyyxxxxxxxxxxwwwwwwwwwwvvvvvvvvvvvvuuuuuuuuuuttttttttttssssssssssrrrrrrrrrrrrqqqqqqqqqqppppppppppppoooooooooonnnnnnnnnnnnmmmmmmmmmmmmllllllllllllkkkkkkkkkkkkjjjjjjjjjjjjjjiiiiiiiiiiiiiihhhhhhhhhhhhggggggggggggggffffffffffffffffeeeeeeeeeeeeeeddddddddddddddddccccccccccccccccbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaa``````````````````____________________^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                    źźźźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśś››››››››››››››››šššššššššššššššš™™™™™™™™™™™™™™——————————————––––––––––––––••••••••••••••””””””””””””““““““““““““’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡††††††††††…………………………„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€~~~~~~~~~~}}}}}}}}}}||||||||||{{{{{{{{{{zzzzzzzzzzyyyyyyyyyyxxxxxxxxxxwwwwwwwwwwvvvvvvvvvvuuuuuuuuuuttttttttttssssssssssssrrrrrrrrrrqqqqqqqqqqppppppppppppoooooooooooonnnnnnnnnnnnmmmmmmmmmmmmllllllllllllkkkkkkkkkkkkjjjjjjjjjjjjjjiiiiiiiiiiiihhhhhhhhhhhhhhggggggggggggggffffffffffffffffeeeeeeeeeeeeeeddddddddddddddddccccccccccccccccbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaa``````````````````____________________^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTT««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                    źźźźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśś››››››››››››››››šššššššššššššššš™™™™™™™™™™™™™™™™——————————————––––––––––––––••••••••••••””””””””””””””““““““““““““’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡††††††††††…………………………„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€~~~~~~~~~~}}}}}}}}}}||||||||||{{{{{{{{{{zzzzzzzzzzyyyyyyyyyyxxxxxxxxxxwwwwwwwwwwvvvvvvvvvvuuuuuuuuuuttttttttttttssssssssssrrrrrrrrrrqqqqqqqqqqqqppppppppppoooooooooooonnnnnnnnnnnnmmmmmmmmmmmmllllllllllllkkkkkkkkkkkkkkjjjjjjjjjjjjiiiiiiiiiiiiiihhhhhhhhhhhhhhggggggggggggggffffffffffffffeeeeeeeeeeeeeeeeddddddddddddddddccccccccccccccccbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaa``````````````````____________________^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTT««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                    źźźźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśś››››››››››››››››šššššššššššššššš™™™™™™™™™™™™™™™™——————————————––––––––––––––••••••••••••••””””””””””””““““““““““““’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡††††††††††…………………………„„„„„„„„„„‚‚‚‚‚‚‚‚€€€€€€€€€€~~~~~~~~~~}}}}}}}}}}||||||||||{{{{{{{{{{zzzzzzzzzzyyyyyyyyyyxxxxxxxxxxwwwwwwwwwwvvvvvvvvvvuuuuuuuuuuuuttttttttttssssssssssrrrrrrrrrrrrqqqqqqqqqqppppppppppppoooooooooooonnnnnnnnnnnnmmmmmmmmmmmmllllllllllllkkkkkkkkkkkkjjjjjjjjjjjjjjiiiiiiiiiiiihhhhhhhhhhhhhhggggggggggggggggffffffffffffffeeeeeeeeeeeeeeeeddddddddddddddddccccccccccccccccbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaa``````````````````____________________^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                    źźźźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśś››››››››››››››››››šššššššššššššššš™™™™™™™™™™™™™™————————————————––––––––––––••••••••••••••””””””””””””””““““““““““““’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡††††††††††………………………………„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€~~~~~~~~~~}}}}}}}}}}||||||||||{{{{{{{{{{zzzzzzzzzzyyyyyyyyyyxxxxxxxxxxwwwwwwwwwwvvvvvvvvvvvvuuuuuuuuuuttttttttttssssssssssssrrrrrrrrrrqqqqqqqqqqqqppppppppppoooooooooooonnnnnnnnnnnnmmmmmmmmmmmmllllllllllllkkkkkkkkkkkkkkjjjjjjjjjjjjiiiiiiiiiiiiiihhhhhhhhhhhhhhggggggggggggggffffffffffffffffeeeeeeeeeeeeeeddddddddddddddddddccccccccccccccccbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaa``````````````````____________________^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                      źźźźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśśśś››››››››››››››››šššššššššššššššš™™™™™™™™™™™™™™——————————————––––––––––––––••••••••••••””””””””””””””““““““““““““’’’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡††††††††††…………………………„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€~~~~~~~~~~}}}}}}}}}}||||||||||{{{{{{{{{{zzzzzzzzzzyyyyyyyyyyxxxxxxxxxxwwwwwwwwwwwwvvvvvvvvvvuuuuuuuuuuttttttttttssssssssssssrrrrrrrrrrqqqqqqqqqqqqppppppppppppoooooooooooonnnnnnnnnnnnmmmmmmmmmmmmllllllllllllkkkkkkkkkkkkjjjjjjjjjjjjjjiiiiiiiiiiiiiihhhhhhhhhhhhhhggggggggggggggffffffffffffffeeeeeeeeeeeeeeeeddddddddddddddddccccccccccccccccccbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaa``````````````````____________________^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                      źźźźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśśśś››››››››››››››››šššššššššššššššš™™™™™™™™™™™™™™™™——————————————––––––––––––––••••••••••••••””””””””””””““““““““““““““’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡††††††††††…………………………„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€~~~~~~~~~~}}}}}}}}}}||||||||||{{{{{{{{{{zzzzzzzzzzyyyyyyyyyyxxxxxxxxxxxxwwwwwwwwwwvvvvvvvvvvuuuuuuuuuuttttttttttttssssssssssrrrrrrrrrrrrqqqqqqqqqqppppppppppppoooooooooooonnnnnnnnnnnnmmmmmmmmmmmmllllllllllllkkkkkkkkkkkkkkjjjjjjjjjjjjjjiiiiiiiiiiiihhhhhhhhhhhhhhggggggggggggggggffffffffffffffeeeeeeeeeeeeeeeeddddddddddddddddccccccccccccccccccbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaa``````````````````____________________^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                      źźźźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśśśś››››››››››››››››šššššššššššššššš™™™™™™™™™™™™™™™™————————————————––––––––––––––••••••••••••””””””””””””””““““““““““““’’’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡††††††††††…………………………„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€~~~~~~~~~~}}}}}}}}}}||||||||||{{{{{{{{{{zzzzzzzzzzyyyyyyyyyyyyxxxxxxxxxxwwwwwwwwwwvvvvvvvvvvuuuuuuuuuuuuttttttttttssssssssssrrrrrrrrrrrrqqqqqqqqqqqqppppppppppppoooooooooonnnnnnnnnnnnmmmmmmmmmmmmmmllllllllllllkkkkkkkkkkkkjjjjjjjjjjjjjjiiiiiiiiiiiiiihhhhhhhhhhhhhhggggggggggggggffffffffffffffffeeeeeeeeeeeeeeeeddddddddddddddddccccccccccccccccbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaa``````````````````____________________^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                      źźźźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśśśś››››››››››››››››šššššššššššššššš™™™™™™™™™™™™™™™™——————————————––––––––––––––••••••••••••••””””””””””””““““““““““““““’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡‡‡††††††††††…………………………„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€~~~~~~~~~~}}}}}}}}}}||||||||||{{{{{{{{{{{{zzzzzzzzzzyyyyyyyyyyxxxxxxxxxxwwwwwwwwwwvvvvvvvvvvvvuuuuuuuuuuttttttttttssssssssssssrrrrrrrrrrqqqqqqqqqqqqppppppppppppoooooooooooonnnnnnnnnnnnmmmmmmmmmmmmllllllllllllkkkkkkkkkkkkkkjjjjjjjjjjjjjjiiiiiiiiiiiiiihhhhhhhhhhhhhhggggggggggggggffffffffffffffffeeeeeeeeeeeeeeeeddddddddddddddddccccccccccccccccbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaa``````````````````______________________^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                      źźźźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśśśś››››››››››››››››››šššššššššššššššš™™™™™™™™™™™™™™™™——————————————––––––––––––––••••••••••••••””””””””””””””““““““““““““’’’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡‡‡††††††††††…………………………„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€~~~~~~~~~~}}}}}}}}}}||||||||||||{{{{{{{{{{zzzzzzzzzzyyyyyyyyyyxxxxxxxxxxwwwwwwwwwwvvvvvvvvvvvvuuuuuuuuuuttttttttttttssssssssssrrrrrrrrrrrrqqqqqqqqqqppppppppppppoooooooooooonnnnnnnnnnnnmmmmmmmmmmmmmmllllllllllllkkkkkkkkkkkkkkjjjjjjjjjjjjiiiiiiiiiiiiiihhhhhhhhhhhhhhggggggggggggggggffffffffffffffeeeeeeeeeeeeeeeeddddddddddddddddddccccccccccccccccbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaa````````````````````______________________^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                      źźźźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśś››››››››››››››››››šššššššššššššššš™™™™™™™™™™™™™™™™————————————————––––––––––––––••••••••••••••””””””””””””““““““““““““““’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡††††††††††††…………………………„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€~~~~~~~~~~}}}}}}}}}}}}||||||||||{{{{{{{{{{zzzzzzzzzzyyyyyyyyyyxxxxxxxxxxwwwwwwwwwwwwvvvvvvvvvvuuuuuuuuuuttttttttttttssssssssssrrrrrrrrrrrrqqqqqqqqqqqqppppppppppppoooooooooooonnnnnnnnnnnnmmmmmmmmmmmmllllllllllllllkkkkkkkkkkkkjjjjjjjjjjjjjjiiiiiiiiiiiiiihhhhhhhhhhhhhhggggggggggggggffffffffffffffffeeeeeeeeeeeeeeeeddddddddddddddddccccccccccccccccccbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaa````````````````````______________________^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                      źźźźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśśśś››››››››››››››››šššššššššššššššš™™™™™™™™™™™™™™™™——————————————––––––––––––––••••••••••••••””””””””””””””““““““““““““’’’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡††††††††††………………………………„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€~~~~~~~~~~}}}}}}}}}}}}||||||||||{{{{{{{{{{zzzzzzzzzzyyyyyyyyyyxxxxxxxxxxxxwwwwwwwwwwvvvvvvvvvvuuuuuuuuuuuuttttttttttssssssssssssrrrrrrrrrrqqqqqqqqqqqqppppppppppppoooooooooooonnnnnnnnnnnnmmmmmmmmmmmmmmllllllllllllkkkkkkkkkkkkkkjjjjjjjjjjjjiiiiiiiiiiiiiihhhhhhhhhhhhhhhhggggggggggggggffffffffffffffffeeeeeeeeeeeeeeeeddddddddddddddddccccccccccccccccccbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaa````````````````````______________________^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                      źźźźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśśśś››››››››››››››››šššššššššššššššš™™™™™™™™™™™™™™™™——————————————––––––––––––––––••••••••••••””””””””””””””““““““““““““““’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡††††††††††…………………………„„„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€~~~~~~~~~~~~}}}}}}}}}}||||||||||{{{{{{{{{{zzzzzzzzzzyyyyyyyyyyxxxxxxxxxxxxwwwwwwwwwwvvvvvvvvvvuuuuuuuuuuuuttttttttttssssssssssssrrrrrrrrrrrrqqqqqqqqqqqqppppppppppppoooooooooooonnnnnnnnnnnnmmmmmmmmmmmmllllllllllllllkkkkkkkkkkkkjjjjjjjjjjjjjjiiiiiiiiiiiiiihhhhhhhhhhhhhhggggggggggggggggffffffffffffffffeeeeeeeeeeeeeeeeddddddddddddddddccccccccccccccccccbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaa````````````````````______________________^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                      źźźźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśśśś››››››››››››››››šššššššššššššššššš™™™™™™™™™™™™™™™™————————————————––––––––––––––••••••••••••••””””””””””””””““““““““““““’’’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡‡‡††††††††††…………………………„„„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€~~~~~~~~~~~~}}}}}}}}}}||||||||||{{{{{{{{{{zzzzzzzzzzyyyyyyyyyyyyxxxxxxxxxxwwwwwwwwwwvvvvvvvvvvvvuuuuuuuuuuttttttttttttssssssssssrrrrrrrrrrrrqqqqqqqqqqqqppppppppppppoooooooooooonnnnnnnnnnnnmmmmmmmmmmmmmmllllllllllllkkkkkkkkkkkkkkjjjjjjjjjjjjjjiiiiiiiiiiiiiihhhhhhhhhhhhhhggggggggggggggggffffffffffffffeeeeeeeeeeeeeeeeddddddddddddddddddccccccccccccccccccbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaa````````````````````______________________^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                      źźźźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśśśś››››››››››››››››››šššššššššššššššš™™™™™™™™™™™™™™™™——————————————––––––––––––––••••••••••••••””””””””””””””““““““““““““““’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡‡‡††††††††††…………………………„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€~~~~~~~~~~~~}}}}}}}}}}||||||||||{{{{{{{{{{zzzzzzzzzzyyyyyyyyyyyyxxxxxxxxxxwwwwwwwwwwwwvvvvvvvvvvuuuuuuuuuuttttttttttttssssssssssssrrrrrrrrrrrrqqqqqqqqqqppppppppppppoooooooooooonnnnnnnnnnnnnnmmmmmmmmmmmmllllllllllllllkkkkkkkkkkkkjjjjjjjjjjjjjjiiiiiiiiiiiiiihhhhhhhhhhhhhhhhggggggggggggggffffffffffffffffeeeeeeeeeeeeeeeeddddddddddddddddddccccccccccccccccbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaa````````````````````______________________^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUU©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                      źźźźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśśśś››››››››››››››››››šššššššššššššššš™™™™™™™™™™™™™™™™——————————————––––––––––––––––••••••••••••••””””””””””””””““““““““““““’’’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡††††††††††††…………………………„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€~~~~~~~~~~~~}}}}}}}}}}||||||||||{{{{{{{{{{zzzzzzzzzzzzyyyyyyyyyyxxxxxxxxxxwwwwwwwwwwwwvvvvvvvvvvuuuuuuuuuuuuttttttttttssssssssssssrrrrrrrrrrrrqqqqqqqqqqqqppppppppppppoooooooooooonnnnnnnnnnnnmmmmmmmmmmmmmmllllllllllllkkkkkkkkkkkkkkjjjjjjjjjjjjjjiiiiiiiiiiiiiihhhhhhhhhhhhhhggggggggggggggggffffffffffffffffeeeeeeeeeeeeeeeeddddddddddddddddccccccccccccccccccbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaa````````````````````______________________^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUU©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                      źźźźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśśśś››››››››››››››››››šššššššššššššššš™™™™™™™™™™™™™™™™————————————————––––––––––––––••••••••••••••””””””””””””””““““““““““““““’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡††††††††††††…………………………„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€~~~~~~~~~~~~}}}}}}}}}}||||||||||{{{{{{{{{{zzzzzzzzzzzzyyyyyyyyyyxxxxxxxxxxxxwwwwwwwwwwvvvvvvvvvvuuuuuuuuuuuuttttttttttttssssssssssrrrrrrrrrrrrqqqqqqqqqqqqppppppppppppoooooooooooonnnnnnnnnnnnnnmmmmmmmmmmmmllllllllllllllkkkkkkkkkkkkjjjjjjjjjjjjjjiiiiiiiiiiiiiiiihhhhhhhhhhhhhhggggggggggggggggffffffffffffffffeeeeeeeeeeeeeeeeddddddddddddddddccccccccccccccccccbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaa````````````````````______________________^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUU©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                      źźźźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśśśśśś››››››››››››››››šššššššššššššššššš™™™™™™™™™™™™™™™™——————————————––––––––––––––••••••••••••••••””””””””””””““““““““““““““’’’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡††††††††††………………………………„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€~~~~~~~~~~~~}}}}}}}}}}||||||||||{{{{{{{{{{{{zzzzzzzzzzyyyyyyyyyyxxxxxxxxxxxxwwwwwwwwwwvvvvvvvvvvvvuuuuuuuuuuttttttttttttssssssssssssrrrrrrrrrrrrqqqqqqqqqqqqppppppppppppoooooooooooonnnnnnnnnnnnmmmmmmmmmmmmmmllllllllllllkkkkkkkkkkkkkkjjjjjjjjjjjjjjiiiiiiiiiiiiiihhhhhhhhhhhhhhhhggggggggggggggffffffffffffffffeeeeeeeeeeeeeeeeeeddddddddddddddddccccccccccccccccccbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaa````````````````````______________________^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUU©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                      źźźźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśśśś››››››››››››››››šššššššššššššššššš™™™™™™™™™™™™™™™™——————————————––––––––––––––––••••••••••••••””””””””””””””““““““““““““““’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡‡‡††††††††††………………………………„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€~~~~~~~~~~~~}}}}}}}}}}||||||||||{{{{{{{{{{{{zzzzzzzzzzyyyyyyyyyyxxxxxxxxxxxxwwwwwwwwwwvvvvvvvvvvvvuuuuuuuuuuuuttttttttttssssssssssssrrrrrrrrrrrrqqqqqqqqqqqqppppppppppppoooooooooooonnnnnnnnnnnnnnmmmmmmmmmmmmllllllllllllllkkkkkkkkkkkkkkjjjjjjjjjjjjjjiiiiiiiiiiiiiihhhhhhhhhhhhhhggggggggggggggggffffffffffffffffeeeeeeeeeeeeeeeeddddddddddddddddddccccccccccccccccccbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaa````````````````````______________________^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUU©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                        źźźźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśśśś››››››››››››››››››šššššššššššššššš™™™™™™™™™™™™™™™™————————————————––––––––––––––••••••••••••••””””””””””””””““““““““““““““’’’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡‡‡††††††††††………………………………„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€~~~~~~~~~~}}}}}}}}}}||||||||||{{{{{{{{{{{{zzzzzzzzzzyyyyyyyyyyyyxxxxxxxxxxwwwwwwwwwwwwvvvvvvvvvvuuuuuuuuuuuuttttttttttttssssssssssrrrrrrrrrrrrqqqqqqqqqqqqppppppppppppoooooooooooooonnnnnnnnnnnnmmmmmmmmmmmmmmllllllllllllkkkkkkkkkkkkkkjjjjjjjjjjjjjjiiiiiiiiiiiiiihhhhhhhhhhhhhhhhggggggggggggggggffffffffffffffffeeeeeeeeeeeeeeeeddddddddddddddddddccccccccccccccccccbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaa````````````````````______________________^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUU©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                        źźźźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśśśś››››››››››››››››››šššššššššššššššš™™™™™™™™™™™™™™™™™™——————————————––––––––––––––––••••••••••••••””””””””””””””““““““““““““““’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡‡‡††††††††††…………………………„„„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€~~~~~~~~~~}}}}}}}}}}||||||||||||{{{{{{{{{{zzzzzzzzzzyyyyyyyyyyyyxxxxxxxxxxwwwwwwwwwwwwvvvvvvvvvvuuuuuuuuuuuuttttttttttttssssssssssssrrrrrrrrrrrrqqqqqqqqqqqqppppppppppppoooooooooooonnnnnnnnnnnnnnmmmmmmmmmmmmllllllllllllllkkkkkkkkkkkkkkjjjjjjjjjjjjjjiiiiiiiiiiiiiihhhhhhhhhhhhhhhhggggggggggggggffffffffffffffffffeeeeeeeeeeeeeeeeddddddddddddddddddccccccccccccccccccbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaa````````````````````______________________^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUU©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                        źźźźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśśśś››››››››››››››››››šššššššššššššššššš™™™™™™™™™™™™™™™™————————————————––––––––––––––••••••••••••••””””””””””””””““““““““““““““’’’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡††††††††††††…………………………„„„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€~~~~~~~~~~}}}}}}}}}}||||||||||||{{{{{{{{{{zzzzzzzzzzyyyyyyyyyyyyxxxxxxxxxxwwwwwwwwwwwwvvvvvvvvvvvvuuuuuuuuuuttttttttttttssssssssssssrrrrrrrrrrrrqqqqqqqqqqqqppppppppppppoooooooooooooonnnnnnnnnnnnmmmmmmmmmmmmmmllllllllllllkkkkkkkkkkkkkkjjjjjjjjjjjjjjiiiiiiiiiiiiiiiihhhhhhhhhhhhhhggggggggggggggggffffffffffffffffeeeeeeeeeeeeeeeeeeddddddddddddddddccccccccccccccccccbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaa````````````````````______________________^^^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                      źźźźźźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśśśś››››››››››››››››››šššššššššššššššššš™™™™™™™™™™™™™™™™————————————————––––––––––––––••••••••••••••••””””””””””””””““““““““““““““’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡††††††††††††…………………………„„„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€~~~~~~~~~~}}}}}}}}}}||||||||||||{{{{{{{{{{zzzzzzzzzzzzyyyyyyyyyyxxxxxxxxxxxxwwwwwwwwwwvvvvvvvvvvvvuuuuuuuuuuuuttttttttttssssssssssssrrrrrrrrrrrrqqqqqqqqqqqqppppppppppppppoooooooooooonnnnnnnnnnnnmmmmmmmmmmmmmmllllllllllllllkkkkkkkkkkkkkkjjjjjjjjjjjjjjiiiiiiiiiiiiiihhhhhhhhhhhhhhhhggggggggggggggggffffffffffffffffeeeeeeeeeeeeeeeeddddddddddddddddddccccccccccccccccccbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaa````````````````````______________________^^^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                      źźźźźźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśśśśśś››››››››››››››››››šššššššššššššššš™™™™™™™™™™™™™™™™——————————————––––––––––––––––••••••••••••••””””””””””””””““““““““““““““’’’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡‡‡††††††††††††…………………………„„„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€~~~~~~~~~~}}}}}}}}}}||||||||||||{{{{{{{{{{zzzzzzzzzzzzyyyyyyyyyyxxxxxxxxxxxxwwwwwwwwwwvvvvvvvvvvvvuuuuuuuuuuuuttttttttttttssssssssssssrrrrrrrrrrrrqqqqqqqqqqqqppppppppppppoooooooooooonnnnnnnnnnnnnnmmmmmmmmmmmmmmllllllllllllkkkkkkkkkkkkkkjjjjjjjjjjjjjjjjiiiiiiiiiiiiiihhhhhhhhhhhhhhhhggggggggggggggggffffffffffffffffeeeeeeeeeeeeeeeeddddddddddddddddddccccccccccccccccccbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaa````````````````````______________________^^^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                      źźźźźźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśśśśśś››››››››››››››››››šššššššššššššššš™™™™™™™™™™™™™™™™™™————————————————––––––––––––––••••••••••••••••””””””””””””””““““““““““““““’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡‡‡††††††††††………………………………„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€~~~~~~~~~~}}}}}}}}}}||||||||||||{{{{{{{{{{zzzzzzzzzzzzyyyyyyyyyyxxxxxxxxxxxxwwwwwwwwwwwwvvvvvvvvvvuuuuuuuuuuuuttttttttttttssssssssssssrrrrrrrrrrrrqqqqqqqqqqqqppppppppppppoooooooooooooonnnnnnnnnnnnmmmmmmmmmmmmmmllllllllllllllkkkkkkkkkkkkkkjjjjjjjjjjjjjjiiiiiiiiiiiiiiiihhhhhhhhhhhhhhggggggggggggggggffffffffffffffffeeeeeeeeeeeeeeeeeeddddddddddddddddddccccccccccccccccccbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaa````````````````````______________________^^^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVV©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                      źźźźźźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśśśśśś››››››››››››››››››šššššššššššššššššš™™™™™™™™™™™™™™™™————————————————––––––––––––––––••••••••••••••””””””””””””””““““““““““““““’’’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡‡‡††††††††††………………………………„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€~~~~~~~~~~}}}}}}}}}}}}||||||||||{{{{{{{{{{{{zzzzzzzzzzyyyyyyyyyyyyxxxxxxxxxxwwwwwwwwwwwwvvvvvvvvvvvvuuuuuuuuuuttttttttttttssssssssssssrrrrrrrrrrrrqqqqqqqqqqqqqqppppppppppppoooooooooooonnnnnnnnnnnnnnmmmmmmmmmmmmmmllllllllllllkkkkkkkkkkkkkkkkjjjjjjjjjjjjjjiiiiiiiiiiiiiihhhhhhhhhhhhhhhhggggggggggggggggffffffffffffffffeeeeeeeeeeeeeeeeeeddddddddddddddddddccccccccccccccccccbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaa````````````````````______________________^^^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVV©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                      źźźźźźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśśśś››››››››››››››››››šššššššššššššššššš™™™™™™™™™™™™™™™™————————————————––––––––––––––––••••••••••••••””””””””””””””””““““““““““““““’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡††††††††††††………………………………„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€~~~~~~~~~~}}}}}}}}}}}}||||||||||{{{{{{{{{{{{zzzzzzzzzzyyyyyyyyyyyyxxxxxxxxxxwwwwwwwwwwwwvvvvvvvvvvvvuuuuuuuuuuuuttttttttttttssssssssssssrrrrrrrrrrrrqqqqqqqqqqqqppppppppppppoooooooooooooonnnnnnnnnnnnmmmmmmmmmmmmmmllllllllllllllkkkkkkkkkkkkkkjjjjjjjjjjjjjjiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhggggggggggggggggffffffffffffffffeeeeeeeeeeeeeeeeeeddddddddddddddddccccccccccccccccccccbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaaaa````````````````````______________________^^^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVV¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                      źźźźźźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśśśś››››››››››››››››››šššššššššššššššššš™™™™™™™™™™™™™™™™™™————————————————––––––––––––––••••••••••••••••””””””””””””””““““““““““““““’’’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡††††††††††††………………………………„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€~~~~~~~~~~}}}}}}}}}}}}||||||||||{{{{{{{{{{{{zzzzzzzzzzyyyyyyyyyyyyxxxxxxxxxxxxwwwwwwwwwwvvvvvvvvvvvvuuuuuuuuuuuuttttttttttttssssssssssssrrrrrrrrrrrrqqqqqqqqqqqqppppppppppppppoooooooooooonnnnnnnnnnnnnnmmmmmmmmmmmmmmllllllllllllllkkkkkkkkkkkkkkjjjjjjjjjjjjjjiiiiiiiiiiiiiiiihhhhhhhhhhhhhhggggggggggggggggffffffffffffffffffeeeeeeeeeeeeeeeeddddddddddddddddddccccccccccccccccccccbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaaaa````````````````````______________________^^^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVV¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                      źźźźźźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśśśś››››››››››››››››››››šššššššššššššššš™™™™™™™™™™™™™™™™™™————————————————––––––––––––––––••••••••••••••””””””””””””””““““““““““““““““’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡‡‡††††††††††††…………………………„„„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€~~~~~~~~~~}}}}}}}}}}}}||||||||||{{{{{{{{{{{{zzzzzzzzzzyyyyyyyyyyyyxxxxxxxxxxxxwwwwwwwwwwwwvvvvvvvvvvuuuuuuuuuuuuttttttttttttssssssssssssrrrrrrrrrrrrqqqqqqqqqqqqqqppppppppppppoooooooooooooonnnnnnnnnnnnmmmmmmmmmmmmmmllllllllllllllkkkkkkkkkkkkkkjjjjjjjjjjjjjjjjiiiiiiiiiiiiiihhhhhhhhhhhhhhhhggggggggggggggggffffffffffffffffeeeeeeeeeeeeeeeeeeddddddddddddddddddccccccccccccccccccbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaa``````````````````````______________________^^^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVV¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                      źźźźźźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśśśśśś››››››››››››››››››šššššššššššššššššš™™™™™™™™™™™™™™™™————————————————––––––––––––––––••••••••••••••••””””””””””””””““““““““““““““’’’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡‡‡††††††††††††…………………………„„„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€~~~~~~~~~~}}}}}}}}}}}}||||||||||{{{{{{{{{{{{zzzzzzzzzzzzyyyyyyyyyyxxxxxxxxxxxxwwwwwwwwwwwwvvvvvvvvvvvvuuuuuuuuuuuuttttttttttttssssssssssssrrrrrrrrrrrrqqqqqqqqqqqqppppppppppppppoooooooooooonnnnnnnnnnnnnnmmmmmmmmmmmmmmllllllllllllllkkkkkkkkkkkkkkjjjjjjjjjjjjjjiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhggggggggggggggggffffffffffffffffeeeeeeeeeeeeeeeeeeddddddddddddddddddccccccccccccccccccbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaa``````````````````````______________________^^^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVV¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                      źźźźźźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśśśśśś››››››››››››››››››šššššššššššššššššš™™™™™™™™™™™™™™™™————————————————––––––––––––––––••••••••••••••””””””””””””””““““““““““““““““’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡‡‡††††††††††………………………………„„„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€~~~~~~~~~~}}}}}}}}}}}}||||||||||{{{{{{{{{{{{zzzzzzzzzzzzyyyyyyyyyyxxxxxxxxxxxxwwwwwwwwwwwwvvvvvvvvvvvvuuuuuuuuuuuuttttttttttttssssssssssssrrrrrrrrrrrrqqqqqqqqqqqqppppppppppppppoooooooooooonnnnnnnnnnnnnnmmmmmmmmmmmmmmllllllllllllllkkkkkkkkkkkkkkjjjjjjjjjjjjjjjjiiiiiiiiiiiiiihhhhhhhhhhhhhhhhggggggggggggggggffffffffffffffffffeeeeeeeeeeeeeeeeeeddddddddddddddddddccccccccccccccccccbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaa``````````````````````________________________^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVV¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                      źźźźźźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśśśśśś››››››››››››››››››šššššššššššššššššš™™™™™™™™™™™™™™™™™™————————————————––––––––––––––––••••••••••••••””””””””””””””””““““““““““““““’’’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡‡‡††††††††††………………………………„„„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€~~~~~~~~~~}}}}}}}}}}}}||||||||||||{{{{{{{{{{zzzzzzzzzzzzyyyyyyyyyyyyxxxxxxxxxxwwwwwwwwwwwwvvvvvvvvvvvvuuuuuuuuuuuuttttttttttttssssssssssssrrrrrrrrrrrrrrqqqqqqqqqqqqppppppppppppoooooooooooooonnnnnnnnnnnnnnmmmmmmmmmmmmmmllllllllllllllkkkkkkkkkkkkkkjjjjjjjjjjjjjjiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhggggggggggggggggffffffffffffffffffeeeeeeeeeeeeeeeeddddddddddddddddddddccccccccccccccccccbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaa``````````````````````________________________^^^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVV¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                      źźźźźźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśśśśśś››››››››››››››››››šššššššššššššššššš™™™™™™™™™™™™™™™™™™————————————————––––––––––––––––••••••••••••••••””””””””””””””““““““““““““““’’’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡††††††††††††………………………………„„„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€~~~~~~~~~~}}}}}}}}}}}}||||||||||||{{{{{{{{{{zzzzzzzzzzzzyyyyyyyyyyyyxxxxxxxxxxxxwwwwwwwwwwvvvvvvvvvvvvuuuuuuuuuuuuttttttttttttssssssssssssssrrrrrrrrrrrrqqqqqqqqqqqqppppppppppppppoooooooooooonnnnnnnnnnnnnnmmmmmmmmmmmmmmllllllllllllllkkkkkkkkkkkkkkjjjjjjjjjjjjjjjjiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhggggggggggggggggffffffffffffffffeeeeeeeeeeeeeeeeeeddddddddddddddddddccccccccccccccccccccbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaa``````````````````````________________________^^^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVV¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                        źźźźźźźźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśśśśśś››››››››››››››››››››šššššššššššššššššš™™™™™™™™™™™™™™™™————————————————––––––––––––––––••••••••••••••””””””””””””””””““““““““““““““’’’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡‡‡††††††††††††………………………………„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€€€~~~~~~~~~~}}}}}}}}}}}}||||||||||||{{{{{{{{{{zzzzzzzzzzzzyyyyyyyyyyyyxxxxxxxxxxxxwwwwwwwwwwwwvvvvvvvvvvvvuuuuuuuuuuuuttttttttttttssssssssssssrrrrrrrrrrrrqqqqqqqqqqqqqqppppppppppppoooooooooooooonnnnnnnnnnnnnnmmmmmmmmmmmmmmllllllllllllllkkkkkkkkkkkkkkjjjjjjjjjjjjjjjjiiiiiiiiiiiiiihhhhhhhhhhhhhhhhggggggggggggggggggffffffffffffffffeeeeeeeeeeeeeeeeeeddddddddddddddddddccccccccccccccccccccbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaa``````````````````````________________________^^^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                        źźźźźźźźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśśśś››››››››››››››››››››šššššššššššššššššš™™™™™™™™™™™™™™™™™™————————————————––––––––––––––––••••••••••••••••””””””””””””””““““““““““““““’’’’’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡‡‡††††††††††††………………………………„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€€€~~~~~~~~~~}}}}}}}}}}}}||||||||||||{{{{{{{{{{{{zzzzzzzzzzyyyyyyyyyyyyxxxxxxxxxxxxwwwwwwwwwwwwvvvvvvvvvvvvuuuuuuuuuuuuttttttttttttssssssssssssrrrrrrrrrrrrrrqqqqqqqqqqqqppppppppppppppoooooooooooonnnnnnnnnnnnnnmmmmmmmmmmmmmmllllllllllllllkkkkkkkkkkkkkkkkjjjjjjjjjjjjjjiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhggggggggggggggggffffffffffffffffffeeeeeeeeeeeeeeeeeeddddddddddddddddddccccccccccccccccccccbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaa``````````````````````________________________^^^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                        źźźźźźźźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśśśśśś››››››››››››››››››šššššššššššššššššš™™™™™™™™™™™™™™™™™™——————————————————––––––––––––––••••••••••••••••””””””””””””””””““““““““““““““’’’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡‡‡††††††††††††…………………………„„„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€€€~~~~~~~~~~}}}}}}}}}}}}||||||||||||{{{{{{{{{{{{zzzzzzzzzzyyyyyyyyyyyyxxxxxxxxxxxxwwwwwwwwwwwwvvvvvvvvvvvvuuuuuuuuuuuuttttttttttttssssssssssssssrrrrrrrrrrrrqqqqqqqqqqqqppppppppppppppoooooooooooooonnnnnnnnnnnnnnmmmmmmmmmmmmmmllllllllllllllkkkkkkkkkkkkkkjjjjjjjjjjjjjjjjiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhggggggggggggggggffffffffffffffffffeeeeeeeeeeeeeeeeddddddddddddddddddddccccccccccccccccccbbbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaa``````````````````````________________________^^^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                        źźźźźźźźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśśśśśś››››››››››››››››››šššššššššššššššššš™™™™™™™™™™™™™™™™™™————————————————––––––––––––––––••••••••••••••••””””””””””””””““““““““““““““’’’’’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡‡‡††††††††††††…………………………„„„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€€€~~~~~~~~~~~~}}}}}}}}}}||||||||||||{{{{{{{{{{{{zzzzzzzzzzzzyyyyyyyyyyxxxxxxxxxxxxwwwwwwwwwwwwvvvvvvvvvvvvuuuuuuuuuuuuttttttttttttttssssssssssssrrrrrrrrrrrrqqqqqqqqqqqqqqppppppppppppoooooooooooooonnnnnnnnnnnnnnmmmmmmmmmmmmmmllllllllllllllkkkkkkkkkkkkkkkkjjjjjjjjjjjjjjiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhggggggggggggggggggffffffffffffffffeeeeeeeeeeeeeeeeeeddddddddddddddddddddccccccccccccccccccbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaaaa``````````````````````________________________^^^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWW¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                        źźźźźźźźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśśśśśś››››››››››››››››››šššššššššššššššššššš™™™™™™™™™™™™™™™™————————————————––––––––––––––––••••••••••••••••””””””””””””””””““““““““““““““’’’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡††††††††††††………………………………„„„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€€€~~~~~~~~~~~~}}}}}}}}}}||||||||||||{{{{{{{{{{{{zzzzzzzzzzzzyyyyyyyyyyyyxxxxxxxxxxxxwwwwwwwwwwwwvvvvvvvvvvvvuuuuuuuuuuuuttttttttttttssssssssssssrrrrrrrrrrrrrrqqqqqqqqqqqqppppppppppppppoooooooooooooonnnnnnnnnnnnmmmmmmmmmmmmmmmmllllllllllllllkkkkkkkkkkkkkkjjjjjjjjjjjjjjjjiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhggggggggggggggggffffffffffffffffffeeeeeeeeeeeeeeeeeeddddddddddddddddddccccccccccccccccccccbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaaaa``````````````````````________________________^^^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWW¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                        źźźźźźźźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśśśśśś››››››››››››››››››››šššššššššššššššššš™™™™™™™™™™™™™™™™™™——————————————————––––––––––––––––••••••••••••••””””””””””””””””““““““““““““““’’’’’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡‡‡††††††††††††………………………………„„„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€€€~~~~~~~~~~~~}}}}}}}}}}||||||||||||{{{{{{{{{{{{zzzzzzzzzzzzyyyyyyyyyyyyxxxxxxxxxxxxwwwwwwwwwwwwvvvvvvvvvvvvuuuuuuuuuuuuttttttttttttssssssssssssssrrrrrrrrrrrrqqqqqqqqqqqqqqppppppppppppoooooooooooooonnnnnnnnnnnnnnmmmmmmmmmmmmmmllllllllllllllkkkkkkkkkkkkkkkkjjjjjjjjjjjjjjjjiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhggggggggggggggggffffffffffffffffffeeeeeeeeeeeeeeeeeeddddddddddddddddddccccccccccccccccccccbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaaaa``````````````````````________________________^^^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWW¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                        źźźźźźźźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśśśśśś››››››››››››››››››››šššššššššššššššššš™™™™™™™™™™™™™™™™™™————————————————––––––––––––––––••••••••••••••••””””””””””””””““““““““““““““““’’’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡‡‡††††††††††††………………………………„„„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€€€~~~~~~~~~~~~}}}}}}}}}}||||||||||||{{{{{{{{{{{{zzzzzzzzzzzzyyyyyyyyyyyyxxxxxxxxxxxxwwwwwwwwwwwwvvvvvvvvvvvvuuuuuuuuuuuuttttttttttttttssssssssssssrrrrrrrrrrrrqqqqqqqqqqqqqqppppppppppppppoooooooooooonnnnnnnnnnnnnnmmmmmmmmmmmmmmmmllllllllllllllkkkkkkkkkkkkkkjjjjjjjjjjjjjjjjiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhggggggggggggggggggffffffffffffffffffeeeeeeeeeeeeeeeeeeddddddddddddddddddccccccccccccccccccccbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaaaa``````````````````````________________________^^^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWW§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                        źźźźźźźźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśśśśśś››››››››››››››››››››šššššššššššššššššš™™™™™™™™™™™™™™™™™™————————————————––––––––––––––––••••••••••••••••””””””””””””””””““““““““““““““’’’’’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡‡‡††††††††††††………………………………„„„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€€€~~~~~~~~~~~~}}}}}}}}}}}}||||||||||{{{{{{{{{{{{zzzzzzzzzzzzyyyyyyyyyyyyxxxxxxxxxxxxwwwwwwwwwwwwvvvvvvvvvvvvuuuuuuuuuuuuuuttttttttttttssssssssssssrrrrrrrrrrrrrrqqqqqqqqqqqqppppppppppppppoooooooooooooonnnnnnnnnnnnnnmmmmmmmmmmmmmmllllllllllllllkkkkkkkkkkkkkkkkjjjjjjjjjjjjjjjjiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhggggggggggggggggffffffffffffffffffeeeeeeeeeeeeeeeeeeddddddddddddddddddddccccccccccccccccccccbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaaaa``````````````````````________________________^^^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWW§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                        źźźźźźźźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśśśśśśśś››››››››››››››››››šššššššššššššššššššš™™™™™™™™™™™™™™™™™™——————————————————––––––––––––––––••••••••••••••••””””””””””””””““““““““““““““““’’’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡‡‡††††††††††††………………………………„„„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€€€~~~~~~~~~~~~}}}}}}}}}}}}||||||||||{{{{{{{{{{{{zzzzzzzzzzzzyyyyyyyyyyyyxxxxxxxxxxxxwwwwwwwwwwwwvvvvvvvvvvvvvvuuuuuuuuuuuuttttttttttttssssssssssssssrrrrrrrrrrrrqqqqqqqqqqqqqqppppppppppppoooooooooooooonnnnnnnnnnnnnnmmmmmmmmmmmmmmmmllllllllllllllkkkkkkkkkkkkkkkkjjjjjjjjjjjjjjiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhggggggggggggggggffffffffffffffffffeeeeeeeeeeeeeeeeeeddddddddddddddddddddccccccccccccccccccccbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaaaa``````````````````````________________________^^^^^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWW§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                        źźźźźźźźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśśśśśś››››››››››››››››››šššššššššššššššššššš™™™™™™™™™™™™™™™™™™————————————————––––––––––––––––••••••••••••••••””””””””””””””””““““““““““““““’’’’’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡‡‡‡‡††††††††††††………………………………„„„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€€€~~~~~~~~~~~~}}}}}}}}}}}}||||||||||||{{{{{{{{{{{{zzzzzzzzzzyyyyyyyyyyyyxxxxxxxxxxxxxxwwwwwwwwwwwwvvvvvvvvvvvvuuuuuuuuuuuuttttttttttttssssssssssssssrrrrrrrrrrrrqqqqqqqqqqqqqqppppppppppppppoooooooooooooonnnnnnnnnnnnnnmmmmmmmmmmmmmmllllllllllllllkkkkkkkkkkkkkkkkjjjjjjjjjjjjjjjjiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhggggggggggggggggggffffffffffffffffffeeeeeeeeeeeeeeeeeeddddddddddddddddddccccccccccccccccccccbbbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaaaa``````````````````````________________________^^^^^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWW§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                        źźźźźźźźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśśśśśś››››››››››››››››››››šššššššššššššššššš™™™™™™™™™™™™™™™™™™————————————————––––––––––––––––––••••••••••••••••””””””””””””””““““““““““““““““’’’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡‡‡††††††††††††………………………………„„„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€€€~~~~~~~~~~~~}}}}}}}}}}}}||||||||||||{{{{{{{{{{{{zzzzzzzzzzzzyyyyyyyyyyyyxxxxxxxxxxxxwwwwwwwwwwwwvvvvvvvvvvvvuuuuuuuuuuuuttttttttttttttssssssssssssrrrrrrrrrrrrrrqqqqqqqqqqqqppppppppppppppoooooooooooooonnnnnnnnnnnnnnmmmmmmmmmmmmmmmmllllllllllllllkkkkkkkkkkkkkkkkjjjjjjjjjjjjjjiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhggggggggggggggggggffffffffffffffffeeeeeeeeeeeeeeeeeeeeddddddddddddddddddccccccccccccccccccccbbbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaaaa``````````````````````________________________^^^^^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWmlt-7.22.0/src/tests/clock16pal.pgm000664 000000 000000 00003124021 14531534050 016756 0ustar00rootroot000000 000000 P5 720 576 65535 ××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááááááááââââââââââââââââââââăăăăăăăăăăăăăăăăăăăăääääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććććććççççççççççççççççççččččččččččččččččééééééééééééééééęęęęęęęęęęęęęęęęëëëëëëëëëëëëëëěěěěěěěěěěěěěěěěííííííííííííííîîîîîîîîîîîîîîďďďďďďďďďďďďďďđđđđđđđđđđđđđđńńńńńńńńńńńńňňňňňňňňňňňňňňóóóóóóóóóóóóôôôôôôôôôôôôôôőőőőőőőőőőőőöööööööööööö÷÷÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřřřůůůůůůůůůůůůúúúúúúúúúúúúűűűűűűűűűűűűüüüüüüüüüüüüýýýýýýýýýýýýţţţţţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!!!!!!!""""""""""""""""""""""""""############################$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''''''''''''((((××××××××××××××××××××××××Ř Ř ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮ ŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚ ÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰ ŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕáááááááááááááááááááááâââââââââââââââââââăăăăăăăăăăăăăăăăăăăäääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺćććććććććććććććććçççççççççççççççççččččččččččččččččéééééééééééééééęęęęęęęęęęęęęęęęëëëëëëëëëëëëëëěěěěěěěěěěěěěěěííííííííííííííîîîîîîîîîîîîîîďďďďďďďďďďďďďďđđđđđđđđđđđđđđńńńńńńńńńńńńň$ňňňňňňňňňňňňóóóóóóóóóóóóôôôôôôôôôôôôôôőőőőőőőőőőőőöööööööööööö÷÷÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřřřůůůůůůůůůůůůúúúúúúúúúúúúűűűűűűűűűűűűüüüüüüüüüüüüýýýýýýýýýýýýţţţţţţţţţţţţ˙˙˙˙ Ř ÜÝŢßŕăäćçéęëí î!!!!!!!!!!!!!!!!!!!!!!!!!ď"""""""""""""""""""""""""đ###########################ń$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ň%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%ó&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&ô'''''''''''''''''''''''''''''''''''ő'ő((((××××××××××××××××××××××××××××Ř>ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮ?Ů?ŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚ@ÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰAŰAŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜBÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝCÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢDŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßEßßßßßßßßßßßßßßßßßßßßßßŕFŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕáGááááááááááááááááááááââââââââââââââââââââăIăăăăăăăăăăăăăăăăăăääääääääääääääääääĺKĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺćLććććććććććććććććççççççççççççççççčNččččččččččččččččééééééééééééééęPęęęęęęęęęęęęęęëQëëëëëëëëëëëëëëěěěěěěěěěěěěěěíSííííííííííííîTîîîîîîîîîîîîďUďďďďďďďďďďďďđVđđđđđđđđđđđđńńńńńńńńńńńńńńňňňňňňňňňňňňóóóóóóóóóóóóôZôôôôôôôôôôôôőőőőőőőőőőőőöööööööööööö÷÷÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřřřů_ůůůůůůůůůůú`úúúúúúúúúúűűűűűűűűűűűűüüüüüüüüüüüüýýýýýýýýýýýýţţţţţţţţţţţţ˙˙˙˙žź ˇ Ą §­®±´µ¶·¸ą ş!!!!!!!!!!!!!!!!!!!!!!!!!»"""""""""""""""""""""""""Ľ###########################˝$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ľ%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%ż&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&Ŕ'''''''''''''''''''''''''''''''''''Á((((((((××××××××××××××××××××××××××××××ŘqŘqŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮrŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚsÚsÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰtŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜuÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝvÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢwŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßxßßßßßßßßßßßßßßßßßßßßßßŕyŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕázááááááááááááááááááâ{ââââââââââââââââââââăăăăăăăăăăăăăăăăăăä}ääääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććććç€ççççççççççççççççčččččččččččččččé‚ééééééééééééééęęęęęęęęęęęęęęęëëëëëëëëëëëëëëě…ěěěěěěěěěěěěěěííííííííííííííîîîîîîîîîîîîîîďďďďďďďďďďďďďďđđđđđđđđđđđđńŠńńńńńńńńńńńńňňňňňňňňňňňňóŚóóóóóóóóóóóóôôôôôôôôôôôôőőőőőőőőőőőőöööööööööööö÷÷÷÷÷÷÷÷÷÷÷ř‘řřřřřřřřřřřřůůůůůůůůůůůůúúúúúúúúúúű”űűűűűűűűűűüüüüüüüüüüüüýýýýýýýýýýýýţţţţţţţţţţţţ˙˙˙˙jo s vwxy{|}~€„…† ‡!!!!!!!!!!!!!!!!!!!!!!!!"""""""""""""""""""""""""‰###########################Š$$$$$$$$$$$$$$$$$$$$$$$$$$$‹$‹%%%%%%%%%%%%%%%%%%%%%%%%%%%%%Ś%Ś&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&Ť&Ť'''''''''''''''''''''''''''''''''Ž'Ž((((((((((××××××××××××××××××××××××××××××××××ŘĄŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘ٦٦ŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚ§ÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚۨŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰܩܩÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝŞÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢ«ŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢ߬ßßßßßßßßßßßßßßßßßßßßßßŕ­ŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕá®ááááááááááááááááááâŻââââââââââââââââââă°ăăăăăăăăăăăăăăăăăăä±ääääääääääääääääĺ˛ĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺćłććććććććććććććććç´ççççççççççççççççččččččččččččččččééééééééééééééééęęęęęęęęęęęęęęë¸ëëëëëëëëëëëëëëěěěěěěěěěěěěěěíşííííííííííííî»îîîîîîîîîîîîďĽďďďďďďďďďďďďđđđđđđđđđđđđđđńńńńńńńńńńńńňżňňňňňňňňňňňňóóóóóóóóóóóóôôôôôôôôôôôôőÂőőőőőőőőőőöĂöööööööööööö÷÷÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřřřůůůůůůůůůůůůúúúúúúúúúúúúűűűűűűűűűűüÉüüüüüüüüüüýýýýýýýýýýýýţţţţţţţţţţţţ˙˙˙˙5 < = AFKLMNPQR S!!!!!!!!!!!!!!!!!!!!!!!T"""""""""""""""""""""""""U"U#########################V#V$$$$$$$$$$$$$$$$$$$$$$$$$$$W%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%X&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&Y'''''''''''''''''''''''''''''''''''Z((((((((((((((××××××××××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááááááââââââââââââââââââââăăăăăăăăăăăăăăăăăăăăääääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććććććççççççççççççççççččččččččččččččččééééééééééééééééęęęęęęęęęęęęęęęęëëëëëëëëëëëëëëěěěěěěěěěěěěěěěěííííííííííííííîîîîîîîîîîîîîîďďďďďďďďďďďďđđđđđđđđđđđđđđńńńńńńńńńńńńńńňňňňňňňňňňňňóóóóóóóóóóóóôôôôôôôôôôôôôôőőőőőőőőőőőőöööööööööööö÷÷÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřřřůůůůůůůůůůůůúúúúúúúúúúúúűűűűűűűűűűűűüüüüüüüüüüýýýýýýýýýýýýţţţţţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!!!!!""""""""""""""""""""""""""############################$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''''''''''((((((((((((((((ÖÖ××××××××××××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááááááââââââââââââââââââââăăăăăăăăăăăăăăăăăăăăääääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććććććççççççççççççççççččččččččččččččččééééééééééééééééęęęęęęęęęęęęęęëëëëëëëëëëëëëëëëěěěěěěěěěěěěěěííííííííííííííîîîîîîîîîîîîîîďďďďďďďďďďďďďďđđđđđđđđđđđđńńńńńńńńńńńńńńňňňňňňňňňňňňóóóóóóóóóóóóóóôôôôôôôôôôôôőőőőőőőőőőőőöööööööööööö÷÷÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřřřůůůůůůůůůůůůúúúúúúúúúúúúűűűűűűűűűűűűüüüüüüüüüüýýýýýýýýýýýýţţţţţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!!!!!""""""""""""""""""""""""""############################$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''''''''''((((((((((((((((((((ÖÖ× ××××××××××××××××××××××××××××××××××××Ř ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮ ŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚ ÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰ Ű ŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááááááâââââââââââââââââââăăăăăăăăăăăăăăăăăăăäääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺćććććććććććććććććççççççççççççççççčččččččččččččččéééééééééééééééęęęęęęęęęęęęęęëëëëëëëëëëëëëëëěěěěěěěěěěěěěěíííííííííííííî îîîîîîîîîîîîďďďďďďďďďďďďďďđđđđđđđđđđđđń#ńńńńńńńńńńńńňňňňňňňňňňňňó%óóóóóóóóóóóóôôôôôôôôôôôôőőőőőőőőőőőőöööööööööööö÷÷÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřřřůůůůůůůůůůůůúúúúúúúúúúúúűűűűűűűűűűűűüüüüüüüüüüýýýýýýýýýýýýţţţţţţţţţţţţ˙˙˙˙ Ú ÜÝáăäćčéęëěí î!!!!!!!!!!!!!!!!!!!!!!!ď"""""""""""""""""""""""""đ###########################ń$$$$$$$$$$$$$$$$$$$$$$$$$$$$$ň%%%%%%%%%%%%%%%%%%%%%%%%%%%%%ó%ó&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&ô'''''''''''''''''''''''''''''''''''ő((((((((((((((((((((ÖÖÖÖ×=×=××××××××××××××××××××××××××××××××××Ř>Ř>ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮ?Ů?ŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚ@Ú@ÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰAŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜBÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝCÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢDŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßEßßßßßßßßßßßßßßßßßßßßßßŕFŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕáGááááááááááááááááááááâHââââââââââââââââââăIăăăăăăăăăăăăăăăăăăääääääääääääääääääĺKĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććććçMççççççççççççççççččččččččččččččččééééééééééééééęPęęęęęęęęęęęęęęëëëëëëëëëëëëëëěRěěěěěěěěěěěěěěííííííííííííííîîîîîîîîîîîîďUďďďďďďďďďďďďđVđđđđđđđđđđđđńńńńńńńńńńńńňXňňňňňňňňňňňňóóóóóóóóóóóóôôôôôôôôôôôôőőőőőőőőőőőőöööööööööööö÷÷÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřřřůůůůůůůůůůůůúúúúúúúúúúúúűűűűűűűűűűűűüüüüüüüüüüýcýýýýýýýýýýţţţţţţţţţţţţ˙˙˙˙˘ Ş«¬®±˛ł´µ¶·¸ą ş!!!!!!!!!!!!!!!!!!!!!!!»"""""""""""""""""""""""""Ľ###########################˝$$$$$$$$$$$$$$$$$$$$$$$$$$$ľ$ľ%%%%%%%%%%%%%%%%%%%%%%%%%%%%%ż&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&Ŕ'''''''''''''''''''''''''''''''''Á'Á((((((((((((((((((((((ÖÖÖÖÖÖÖÖ×p×p××××××××××××××××××××××××××××××××××ŘqŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮrŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚsÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰtŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜuÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝvÝvÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢwŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßxßßßßßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕázááááááááááááááááááááâ{ââââââââââââââââââă|ăăăăăăăăăăăăăăăăä}ääääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺćććććććććććććććććç€ççççççççççççççčččččččččččččččé‚ééééééééééééééęęęęęęęęęęęęęë„ëëëëëëëëëëëëëëěěěěěěěěěěěěěěííííííííííííííîîîîîîîîîîîîîîďďďďďďďďďďďďďďđđđđđđđđđđđđńńńńńńńńńńńńńńňňňňňňňňňňňňóóóóóóóóóóóóôôôôôôôôôôôôőŽőőőőőőőőőőöŹöööööööööö÷÷÷÷÷÷÷÷÷÷÷ř‘řřřřřřřřřřůůůůůůůůůůůůúúúúúúúúúúúúűűűűűűűűűűűűüüüüüüüüüüüüýýýýýýýýýýţţţţţţţţţţţţ˙˙˙˙hmn p q tvz|}~€„…† ‡!!!!!!!!!!!!!!!!!!!!!!!"""""""""""""""""""""""""‰###########################Š$$$$$$$$$$$$$$$$$$$$$$$$$$$‹%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%Ś&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&Ť&Ť'''''''''''''''''''''''''''''''''Ž((((((((((((((((((((((((((ÖÖÖÖÖÖÖÖÖÖÖÖפ××××××××××××××××××××××××××××××××××ŘĄŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘ٦ŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚ§ÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚۨŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜ©ÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝŞÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢ«ŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢ߬ßßßßßßßßßßßßßßßßßßßßŕ­ŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕá®ááááááááááááááááááááâŻââââââââââââââââââăăăăăăăăăăăăăăăăăăä±ääääääääääääääääĺ˛ĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺćłććććććććććććććććççççççççççççççççččččččččččččččččééééééééééééééééęęęęęęęęęęęęęęë¸ëëëëëëëëëëëëěąěěěěěěěěěěěěíşííííííííííííî»îîîîîîîîîîîîďĽďďďďďďďďďďďďđđđđđđđđđđđđńľńńńńńńńńńńńńňňňňňňňňňňňňóŔóóóóóóóóóóôÁôôôôôôôôôôôôőőőőőőőőőőőőöööööööööööö÷÷÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřůĆůůůůůůůůůůúúúúúúúúúúúúűűűűűűűűűűűűüüüüüüüüüüüüýýýýýýýýýýţţţţţţţţţţţţ˙˙˙˙8 > ADEGIKLMNOPQR S!!!!!!!!!!!!!!!!!!!!!!!T"""""""""""""""""""""""""U#########################V#V$$$$$$$$$$$$$$$$$$$$$$$$$$$W%%%%%%%%%%%%%%%%%%%%%%%%%%%%%X%X&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&Y'''''''''''''''''''''''''''''''''Z'Z((((((((((((((((((((((((((((ÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááááááááââââââââââââââââââăăăăăăăăăăăăăăăăăăăăääääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććććççççççççççççççççččččččččččččččččééééééééééééééééęęęęęęęęęęęęęęęęëëëëëëëëëëëëëëěěěěěěěěěěěěěěííííííííííííííîîîîîîîîîîîîîîďďďďďďďďďďďďđđđđđđđđđđđđđđńńńńńńńńńńńńňňňňňňňňňňňňňňóóóóóóóóóóóóôôôôôôôôôôôôőőőőőőőőőőőőöööööööööööö÷÷÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřřřůůůůůůůůůůúúúúúúúúúúúúűűűűűűűűűűűűüüüüüüüüüüüüýýýýýýýýýýţţţţţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!!!!!""""""""""""""""""""""""""##########################$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''''''''((((((((((((((((((((((((((((((((ÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááááááââââââââââââââââââââăăăăăăăăăăăăăăăăăăăăääääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććććççççççççççççççççččččččččččččččččééééééééééééééééęęęęęęęęęęęęęęëëëëëëëëëëëëëëěěěěěěěěěěěěěěííííííííííííííîîîîîîîîîîîîîîďďďďďďďďďďďďďďđđđđđđđđđđđđńńńńńńńńńńńńńńňňňňňňňňňňňňóóóóóóóóóóóóôôôôôôôôôôôôőőőőőőőőőőőőöööööööööööö÷÷÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřřřůůůůůůůůůůůůúúúúúúúúúúűűűűűűűűűűűűüüüüüüüüüüüüýýýýýýýýýýţţţţţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!!!!!""""""""""""""""""""""""""##########################$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''''''''''((((((((((((((((((((((((((((((((((ÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ× ××××××××××××××××××××××××××××××××××Ř ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮ ŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚ ÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰ ŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕáááááááááááááááááááâââââââââââââââââââăăăăăăăăăăăăăăăăăăăääääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććććçççççççççççççççčččččččččččččččéééééééééééééééęęęęęęęęęęęęęęëëëëëëëëëëëëëěěěěěěěěěěěěěíííííííííííííî îîîîîîîîîîîîď!ďďďďďďďďďďďďđđđđđđđđđđđđń#ńńńńńńńńńńńńňňňňňňňňňňňňóóóóóóóóóóóóôôôôôôôôôôôôőőőőőőőőőőőőöööööööööööö÷÷÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřřřůůůůůůůůůůůůúúúúúúúúúúűűűűűűűűűűűűüüüüüüüüüüüüýýýýýýýýýýţţţţţţţţţţţţ˙˙˙˙Ň Űŕâăäĺćçčéęëěí î!!!!!!!!!!!!!!!!!!!!!!!ď"""""""""""""""""""""""đ"đ#########################ń$$$$$$$$$$$$$$$$$$$$$$$$$$$ň$ň%%%%%%%%%%%%%%%%%%%%%%%%%%%%%ó&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&ô'''''''''''''''''''''''''''''''''ő'ő((((((((((((((((((((((((((((((((((ÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ×=×=××××××××××××××××××××××××××××××××Ř>Ř>ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮ?Ů?ŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚ@ÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰAŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜBÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝCÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢDŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßEßßßßßßßßßßßßßßßßßßßßŕFŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕáGááááááááááááááááááâHââââââââââââââââââăIăăăăăăăăăăăăăăăăäJääääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺćLććććććććććććććććççççççççççççççççččččččččččččččččééééééééééééééęPęęęęęęęęęęęęęęëëëëëëëëëëëëëëěěěěěěěěěěěěěěííííííííííííííîîîîîîîîîîîîîîďďďďďďďďďďďďđVđđđđđđđđđđđđńńńńńńńńńńńńňňňňňňňňňňňňóóóóóóóóóóóóôZôôôôôôôôôôő[őőőőőőőőőőö\öööööööööö÷÷÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřřřůůůůůůůůůůůůúúúúúúúúúúűaűűűűűűűűűűüüüüüüüüüüüüýýýýýýýýýýţţţţţţţţţţţţ˙˙˙˙˘ Ł ¤ Ą ¨Ş«­®ł´µ¶·¸ą ş ş!!!!!!!!!!!!!!!!!!!!!!!»"""""""""""""""""""""""Ľ###########################˝$$$$$$$$$$$$$$$$$$$$$$$$$$$ľ%%%%%%%%%%%%%%%%%%%%%%%%%%%%%ż%ż&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&Ŕ'''''''''''''''''''''''''''''''''Á(((((((((((((((((((((((((((((((((((((ÂÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ×p××××××××××××××××××××××××××××××××××ŘqŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮrŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚsÚsÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰtŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜuÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝvÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢwŢwŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßxßßßßßßßßßßßßßßßßßßßßŕyŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕázááááááááááááááááááâ{ââââââââââââââââââă|ăăăăăăăăăăăăăăăăä}ääääääääääääääääĺ~ĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺćććććććććććććććç€ççççççççççççççčččččččččččččččé‚ééééééééééééééęęęęęęęęęęęęęęë„ëëëëëëëëëëëëě…ěěěěěěěěěěěěí†ííííííííííííî‡îîîîîîîîîîîîďďďďďďďďďďďďďďđđđđđđđđđđđđńńńńńńńńńńńńň‹ňňňňňňňňňňóŚóóóóóóóóóóóóôôôôôôôôôôôôőőőőőőőőőőőőöööööööööö÷÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřřřůůůůůůůůůůůůúúúúúúúúúúúúűűűűűűűűűűüüüüüüüüüüüüýýýýýýýýýýţţţţţţţţţţţţ˙˙˙˙n s tvy|}~‚„…† ‡!!!!!!!!!!!!!!!!!!!!!!!!"""""""""""""""""""""""‰###########################Š$$$$$$$$$$$$$$$$$$$$$$$$$$$‹%%%%%%%%%%%%%%%%%%%%%%%%%%%%%Ś&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&Ť&Ť'''''''''''''''''''''''''''''''Ž'Ž(((((((((((((((((((((((((((((((((((Ź(Ź))ÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖפפ××××××××××××××××××××××××××××××××ŘĄŘĄŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘ٦ŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚ§ÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚۨۨŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜ©ÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝŞÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢ«ŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢ߬ßßßßßßßßßßßßßßßßßßßßŕ­ŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕá®ááááááááááááááááááâŻââââââââââââââââââă°ăăăăăăăăăăăăăăăăä±ääääääääääääääääĺ˛ĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććććç´ççççççççççççççčµččččččččččččččééééééééééééééę·ęęęęęęęęęęęęęęëëëëëëëëëëëëëëěěěěěěěěěěěěěěííííííííííííííîîîîîîîîîîîîďĽďďďďďďďďďďďďđđđđđđđđđđđđńľńńńńńńńńńńńńňňňňňňňňňňňňóóóóóóóóóóóóôôôôôôôôôôôôőőőőőőőőőőőőöööööööööööö÷÷÷÷÷÷÷÷÷÷řĹřřřřřřřřřřůůůůůůůůůůůůúúúúúúúúúúúúűűűűűűűűűűüüüüüüüüüüüüýýýýýýýýýýţËţţţţţţţţţţ˙˙˙˙69 ACDFGIJKLOPQR S!!!!!!!!!!!!!!!!!!!!!!!T"""""""""""""""""""""""""U#########################V#V$$$$$$$$$$$$$$$$$$$$$$$$$W$W%%%%%%%%%%%%%%%%%%%%%%%%%%%%%X&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&Y'''''''''''''''''''''''''''''''''Z((((((((((((((((((((((((((((((((((((([))))))ÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááááááââââââââââââââââââââăăăăăăăăăăăăăăăăăăääääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććććććççççççççççççççççččččččččččččččééééééééééééééééęęęęęęęęęęęęęęëëëëëëëëëëëëëëěěěěěěěěěěěěěěííííííííííííííîîîîîîîîîîîîîîďďďďďďďďďďďďđđđđđđđđđđđđđđńńńńńńńńńńńńňňňňňňňňňňňňóóóóóóóóóóóóôôôôôôôôôôôôőőőőőőőőőőőőöööööööööööö÷÷÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřůůůůůůůůůůůůúúúúúúúúúúúúűűűűűűűűűűüüüüüüüüüüüüýýýýýýýýýýýýţţţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!!!!!""""""""""""""""""""""""""##########################$$$$$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''''''''(((((((((((((((((((((((((((((((((((((())))))))ÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááááááââââââââââââââââââăăăăăăăăăăăăăăăăăăăăääääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććććççççççççççççççççččččččččččččččččééééééééééééééééęęęęęęęęęęęęęęëëëëëëëëëëëëëëěěěěěěěěěěěěěěííííííííííííííîîîîîîîîîîîîďďďďďďďďďďďďďďđđđđđđđđđđđđńńńńńńńńńńńńňňňňňňňňňňňňóóóóóóóóóóóóôôôôôôôôôôôôőőőőőőőőőőőőöööööööööööö÷÷÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřřřůůůůůůůůůůúúúúúúúúúúúúűűűűűűűűűűüüüüüüüüüüüüýýýýýýýýýýýýţţţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!!!!!""""""""""""""""""""""""""##########################$$$$$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''''''''(((((((((((((((((((((((((((((((((((((())))))))))))ÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ× × ××××××××××××××××××××××××××××××××Ř Ř ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮ ŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚ Ú ÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰ ŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕáááááááááááááááááááâââââââââââââââââăăăăăăăăăăăăăăăăăăăääääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺćććććććććććććććçççççççççççççççčččččččččččččččéééééééééééééééęęęęęęęęęęęęęęëëëëëëëëëëëëëëěěěěěěěěěěěěěěííííííííííííííîîîîîîîîîîîîď!ďďďďďďďďďďďďđđđđđđđđđđđđńńńńńńńńńńńńň$ňňňňňňňňňňó%óóóóóóóóóóô&ôôôôôôôôôôő'őőőőőőőőőőö(öööööööööö÷÷÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřřřůůůůůůůůůůúúúúúúúúúúúúűűűűűűűűűűüüüüüüüüüüüüýýýýýýýýýýýýţţţţţţţţţţ˙˙˙˙Ó × Ř Ú Űßŕáâăäĺćçčéęëěí î!!!!!!!!!!!!!!!!!!!!!!!ď"""""""""""""""""""""""""đ#########################ń$$$$$$$$$$$$$$$$$$$$$$$$$$$ň%%%%%%%%%%%%%%%%%%%%%%%%%%%%%ó&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&ô'''''''''''''''''''''''''''''''''ő(((((((((((((((((((((((((((((((((((ö(ö))))))))))))ÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ×=××××××××××××××××××××××××××××××××××Ř>ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮ?ŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚ@ÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰAŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜBÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝCÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢDŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßEßßßßßßßßßßßßßßßßßßßßŕFŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕáGááááááááááááááááááâHââââââââââââââââăIăăăăăăăăăăăăăăăăäJääääääääääääääääĺKĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺćLććććććććććććććçMççççççççççççççčNččččččččččččččééééééééééééééęPęęęęęęęęęęęęëQëëëëëëëëëëëëěRěěěěěěěěěěěěíSííííííííííííîTîîîîîîîîîîîîďďďďďďďďďďďďđđđđđđđđđđđđńWńńńńńńńńńńńńňňňňňňňňňňňňóóóóóóóóóóóóôôôôôôôôôôôôőőőőőőőőőőőőöööööööööö÷]÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřřřůůůůůůůůůůúúúúúúúúúúúúűűűűűűűűűűübüüüüüüüüüüýýýýýýýýýýýýţţţţţţţţţţ˙˙˙˙˘ ¨Ş­°±˛´µ·¸ą ş!!!!!!!!!!!!!!!!!!!!!!!»"""""""""""""""""""""""""Ľ#########################˝$$$$$$$$$$$$$$$$$$$$$$$$$$$ľ%%%%%%%%%%%%%%%%%%%%%%%%%%%%%ż&&&&&&&&&&&&&&&&&&&&&&&&&&&&&Ŕ&Ŕ'''''''''''''''''''''''''''''''Á'Á(((((((((((((((((((((((((((((((((((Â))))))))))))))))ÖoÖoÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ×p×p××××××××××××××××××××××××××××××××ŘqŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮrŮrŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚsÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰtŰtŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜuÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝvÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢwŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßxßßßßßßßßßßßßßßßßßßßßŕyŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕázááááááááááááááááááââââââââââââââââââă|ăăăăăăăăăăăăăăăăä}ääääääääääääääääĺ~ĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććććççççççççççççççççččččččččččččččé‚ééééééééééééééęęęęęęęęęęęęęęëëëëëëëëëëëëëëěěěěěěěěěěěěěěííííííííííííííîîîîîîîîîîîîďďďďďďďďďďďđ‰đđđđđđđđđđđđńńńńńńńńńńńńňňňňňňňňňňňňóóóóóóóóóóóóôôôôôôôôôôôôőőőőőőőőőőőőöööööööööööö÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřřřůůůůůůůůůůú“úúúúúúúúúúűűűűűűűűűűűűüüüüüüüüüüýýýýýýýýýýýýţţţţţţţţţţ˙˙˙˙n vxy{|€‚„…† ‡!!!!!!!!!!!!!!!!!!!!!!!"""""""""""""""""""""""‰"‰#######################Š#Š$$$$$$$$$$$$$$$$$$$$$$$$$‹$‹%%%%%%%%%%%%%%%%%%%%%%%%%%%Ś%Ś&&&&&&&&&&&&&&&&&&&&&&&&&&&&&Ť'''''''''''''''''''''''''''''''''Ž(((((((((((((((((((((((((((((((((((Ź(Ź))))))))))))))))))ŐŐŐŐÖŁÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖפ××××××××××××××××××××××××××××××××ŘĄŘĄŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘ٦ŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚ§ÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚۨŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜ©ÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝŞÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢ«ŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢ߬ßßßßßßßßßßßßßßßßßßßßŕ­ŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááááâŻââââââââââââââââââă°ăăăăăăăăăăăăăăăăä±ääääääääääääääääĺ˛ĺĺĺĺĺĺĺĺĺĺĺĺĺĺćłććććććććććććććç´ççççççççççççççčµččččččččččččččé¶ééééééééééééę·ęęęęęęęęęęęęë¸ëëëëëëëëëëëëěąěěěěěěěěěěěěíşííííííííííííîîîîîîîîîîîîîîďďďďďďďďďďďďđđđđđđđđđđđđńľńńńńńńńńńńňżňňňňňňňňňňóŔóóóóóóóóóóôÁôôôôôôôôôôőőőőőőőőőőőőöööööööööööö÷÷÷÷÷÷÷÷÷÷řĹřřřřřřřřřřůůůůůůůůůůůůúúúúúúúúúúűűűűűűűűűűűűüüüüüüüüüüýýýýýýýýýýýýţţţţţţţţţţ˙˙˙˙57 = > ? @CFGIJKLOPR S!!!!!!!!!!!!!!!!!!!!!!!T"""""""""""""""""""""""U#########################V$$$$$$$$$$$$$$$$$$$$$$$$$$$W%%%%%%%%%%%%%%%%%%%%%%%%%%%%%X&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&Y'''''''''''''''''''''''''''''''''Z((((((((((((((((((((((((((((((((((([))))))))))))))))))))))ŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááááááââââââââââââââââââââăăăăăăăăăăăăăăăăăăääääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććććççççççççççççççççččččččččččččččččééééééééééééééęęęęęęęęęęęęęęëëëëëëëëëëëëëëěěěěěěěěěěěěěěííííííííííííîîîîîîîîîîîîîîďďďďďďďďďďďďđđđđđđđđđđđđđđńńńńńńńńńńńńňňňňňňňňňňňňóóóóóóóóóóóóôôôôôôôôôôőőőőőőőőőőőőöööööööööööö÷÷÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřůůůůůůůůůůůůúúúúúúúúúúűűűűűűűűűűűűüüüüüüüüüüýýýýýýýýýýýýţţţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!!!!!""""""""""""""""""""""""##########################$$$$$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''''''''(((((((((((((((((((((((((((((((((((())))))))))))))))))))))))ŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááááááââââââââââââââââââââăăăăăăăăăăăăăăăăăăääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććććççççççççççççççççččččččččččččččééééééééééééééęęęęęęęęęęęęęęęęëëëëëëëëëëëëëëěěěěěěěěěěěěííííííííííííííîîîîîîîîîîîîďďďďďďďďďďďďďďđđđđđđđđđđđđńńńńńńńńńńńńňňňňňňňňňňňňóóóóóóóóóóóóôôôôôôôôôôôôőőőőőőőőőőöööööööööööö÷÷÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřůůůůůůůůůůůůúúúúúúúúúúűűűűűűűűűűűűüüüüüüüüüüýýýýýýýýýýýýţţţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!!!!!""""""""""""""""""""""""##########################$$$$$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''''''''(((((((((((((((((((((((((((((((((((())))))))))))))))))))))))))))ŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ× ××××××××××××××××××××××××××××××××Ř Ř ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮ ŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚ ÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰ ŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕáááááááááááááááááááâââââââââââââââââââăăăăăăăăăăăăăăăăăăääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććććççççççççççççççççččččččččččččččéééééééééééééęęęęęęęęęęęęęęęëëëëëëëëëëëëëëěěěěěěěěěěěěíííííííííííííîîîîîîîîîîîîď!ďďďďďďďďďďďďđđđđđđđđđđđđńńńńńńńńńńńńňňňňňňňňňňňňóóóóóóóóóóóóôôôôôôôôôôôôőőőőőőőőőőö(öööööööööö÷÷÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřůůůůůůůůůůůůúúúúúúúúúúűűűűűűűűűűűűüüüüüüüüüüýýýýýýýýýýýýţţţţţţţţţţ˙˙˙˙Ö Ţáâäćčéęëěí î!!!!!!!!!!!!!!!!!!!!!!!ď"""""""""""""""""""""""đ#########################ń$$$$$$$$$$$$$$$$$$$$$$$$$$$ň%%%%%%%%%%%%%%%%%%%%%%%%%%%ó%ó&&&&&&&&&&&&&&&&&&&&&&&&&&&&&ô'''''''''''''''''''''''''''''''''ő(((((((((((((((((((((((((((((((((((ö))))))))))))))))))))))))))))ŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖ<ÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ×=×=××××××××××××××××××××××××××××××××Ř>ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮ?ŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚ@ÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰAŰAŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜBÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝCÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢDŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßEßßßßßßßßßßßßßßßßßßßßŕFŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕáGááááááááááááááááááâHââââââââââââââââăIăăăăăăăăăăăăăăăăäJääääääääääääääääĺKĺĺĺĺĺĺĺĺĺĺĺĺĺĺćLććććććććććććććçMççççççççççççççčNččččččččččččččééééééééééééééęPęęęęęęęęęęęęëQëëëëëëëëëëëëěRěěěěěěěěěěěěííííííííííííîTîîîîîîîîîîîîďďďďďďďďďďďďđđđđđđđđđđđđńWńńńńńńńńńńňXňňňňňňňňňňóYóóóóóóóóóóôôôôôôôôôôôôőőőőőőőőőőőőöööööööööö÷]÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřů_ůůůůůůůůůůúúúúúúúúúúűaűűűűűűűűűűüüüüüüüüüüýýýýýýýýýýýýţţţţţţţţţţ˙˙˙˙ Ą ¦ §¨«¬Ż°±ł´µ¶·¸ą ş!!!!!!!!!!!!!!!!!!!!!!!»"""""""""""""""""""""""Ľ#########################˝$$$$$$$$$$$$$$$$$$$$$$$$$ľ$ľ%%%%%%%%%%%%%%%%%%%%%%%%%%%ż&&&&&&&&&&&&&&&&&&&&&&&&&&&&&Ŕ&Ŕ'''''''''''''''''''''''''''''''Á'Á(((((((((((((((((((((((((((((((((Â(Â))))))))))))))))))))))))))))))ŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖoÖoÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ×p×p××××××××××××××××××××××××××××××ŘqŘqŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮrŮrŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚsÚsÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰtŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜuÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝvÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢwŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßxßßßßßßßßßßßßßßßßßßßßŕyŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕázááááááááááááááááááâ{ââââââââââââââââă|ăăăăăăăăăăăăăăăăä}ääääääääääääääääĺ~ĺĺĺĺĺĺĺĺĺĺĺĺĺĺćććććććććććććććç€ççççççççççççççččččččččččččččé‚ééééééééééééééęęęęęęęęęęęęęęëëëëëëëëëëëëëëěěěěěěěěěěěěí†ííííííííííííîîîîîîîîîîîîďďďďďďďďďďďđ‰đđđđđđđđđđđđńńńńńńńńńńńńňňňňňňňňňňňňóóóóóóóóóóôŤôôôôôôôôôôőőőőőőőőőőőőöööööööööööö÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřřřůůůůůůůůůůúúúúúúúúúúúúűűűűűűűűűűüüüüüüüüüüýýýýýýýýýýýýţţţţţţţţţţ˙˙˙˙gln q vwz{|~€‚„…† ‡!!!!!!!!!!!!!!!!!!!!!!"""""""""""""""""""""""‰#######################Š#Š$$$$$$$$$$$$$$$$$$$$$$$$$‹%%%%%%%%%%%%%%%%%%%%%%%%%%%Ś%Ś&&&&&&&&&&&&&&&&&&&&&&&&&&&&&Ť'''''''''''''''''''''''''''''''''Ž(((((((((((((((((((((((((((((((((Ź(Ź))))))))))))))))))))))))))))))))))ŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖŁÖŁÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖפ××××××××××××××××××××××××××××××××ŘĄŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘ٦ŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚ§ÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚۨŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜ©ÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝŞÝŞÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢ«ŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢ߬ßßßßßßßßßßßßßßßßßßßßŕ­ŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕá®ááááááááááááááááááâŻââââââââââââââââă°ăăăăăăăăăăăăăăăăä±ääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺćłććććććććććććććççççççççççççççčµččččččččččččččé¶ééééééééééééę·ęęęęęęęęęęęęë¸ëëëëëëëëëëëëěąěěěěěěěěěěěěííííííííííííî»îîîîîîîîîîîîďďďďďďďďďďďďđđđđđđđđđđđđńńńńńńńńńńńńňňňňňňňňňňňňóóóóóóóóóóóóôôôôôôôôôôőÂőőőőőőőőőőöööööööööööö÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřřřůůůůůůůůůůúúúúúúúúúúúúűűűűűűűűűűüüüüüüüüüüýýýýýýýýýýýýţţţţţţţţţţ˙˙˙˙6 < DEIKLMNPQR S!!!!!!!!!!!!!!!!!!!!!T"""""""""""""""""""""""U"U#######################V$$$$$$$$$$$$$$$$$$$$$$$$$$$W%%%%%%%%%%%%%%%%%%%%%%%%%%%X&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&Y'''''''''''''''''''''''''''''''Z'Z((((((((((((((((((((((((((((((((([)))))))))))))))))))))))))))))))))))))\ŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááááááââââââââââââââââââăăăăăăăăăăăăăăăăăăääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććççççççççççççççççččččččččččččččččééééééééééééééęęęęęęęęęęęęęęëëëëëëëëëëëëëëěěěěěěěěěěěěííííííííííííííîîîîîîîîîîîîďďďďďďďďďďďďđđđđđđđđđđđđńńńńńńńńńńńńňňňňňňňňňňňňóóóóóóóóóóóóôôôôôôôôôôôôőőőőőőőőőőöööööööööööö÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřřřůůůůůůůůůůúúúúúúúúúúúúűűűűűűűűűűüüüüüüüüüüýýýýýýýýýýýýţţţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!!!""""""""""""""""""""""""##########################$$$$$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''''''(((((((((((((((((((((((((((((((((((())))))))))))))))))))))))))))))))))))))**ŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááááááââââââââââââââââââăăăăăăăăăăăăăăăăăăääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććććççççççççççççççççččččččččččččččééééééééééééééęęęęęęęęęęęęęęëëëëëëëëëëëëëëěěěěěěěěěěěěěěííííííííííííîîîîîîîîîîîîďďďďďďďďďďďďďďđđđđđđđđđđđđńńńńńńńńńńńńňňňňňňňňňňňňóóóóóóóóóóôôôôôôôôôôôôőőőőőőőőőőőőöööööööööö÷÷÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřůůůůůůůůůůůůúúúúúúúúúúűűűűűűűűűűüüüüüüüüüüüüýýýýýýýýýýţţţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!!!""""""""""""""""""""""""##########################$$$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''''''''(((((((((((((((((((((((((((((((((())))))))))))))))))))))))))))))))))))))******ŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ× ××××××××××××××××××××××××××××××××Ř ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮ ŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚ Ú ÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰ ŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕáááááááááááááááááááââââââââââââââââââăăăăăăăăăăăăăăăăăääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺćććććććććććććććçççççççççççççççččččččččččččččéééééééééééééęęęęęęęęęęęęęëëëëëëëëëëëëëěěěěěěěěěěěěěííííííííííííî îîîîîîîîîîď!ďďďďďďďďďďďďđđđđđđđđđđđđńńńńńńńńńńńńňňňňňňňňňňňňóóóóóóóóóóô&ôôôôôôôôôôőőőőőőőőőőőőöööööööööö÷÷÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřůůůůůůůůůůůůúúúúúúúúúúűűűűűűűűűűüüüüüüüüüüüüýýýýýýýýýýţţţţţţţţţţ˙˙˙˙ÔÖ Ů Ţŕĺćçčéęëěí î!!!!!!!!!!!!!!!!!!!!!ď"""""""""""""""""""""""đ#########################ń$$$$$$$$$$$$$$$$$$$$$$$$$ň%%%%%%%%%%%%%%%%%%%%%%%%%%%ó%ó&&&&&&&&&&&&&&&&&&&&&&&&&&&&&ô'''''''''''''''''''''''''''''''ő'ő(((((((((((((((((((((((((((((((((ö)))))))))))))))))))))))))))))))))))))÷******ŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖ<ÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ×=×=××××××××××××××××××××××××××××××Ř>Ř>ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮ?ŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚ@ÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰAŰAŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜBÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝCÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢDŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßEßßßßßßßßßßßßßßßßßßßßŕFŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕáGááááááááááááááááâHââââââââââââââââââăăăăăăăăăăăăăăăăäJääääääääääääääääĺKĺĺĺĺĺĺĺĺĺĺĺĺĺĺćLććććććććććććććçMççççççççççççčNččččččččččččččééééééééééééééęęęęęęęęęęęęęęëëëëëëëëëëëëëëěěěěěěěěěěěěíSííííííííííííîîîîîîîîîîîîďďďďďďďďďďďďđđđđđđđđđđđđńńńńńńńńńńńńňňňňňňňňňňňňóóóóóóóóóóóóôôôôôôôôôôő[őőőőőőőőőőöööööööööö÷]÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřůůůůůůůůůůůůúúúúúúúúúúűűűűűűűűűűüüüüüüüüüüüüýýýýýýýýýýţţţţţţţţţţ˙˙˙˙ ¤ ¨«­®Ż°˛ł´µ¶·¸ą ş!!!!!!!!!!!!!!!!!!!!!»"""""""""""""""""""""""Ľ#########################˝$$$$$$$$$$$$$$$$$$$$$$$$$ľ%%%%%%%%%%%%%%%%%%%%%%%%%%%ż&&&&&&&&&&&&&&&&&&&&&&&&&&&&&Ŕ&Ŕ'''''''''''''''''''''''''''''''Á(((((((((((((((((((((((((((((((((Â(Â)))))))))))))))))))))))))))))))))))Ă)Ă********ŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖoÖoÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ×p××××××××××××××××××××××××××××××××ŘqŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮrŮrŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚsÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰtŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜuÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝvÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢwŢwŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßxßßßßßßßßßßßßßßßßßßßßŕyŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕázááááááááááááááááâ{ââââââââââââââââă|ăăăăăăăăăăăăăăăăä}ääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććććççççççççççççççčččččččččččččé‚ééééééééééééęęęęęęęęęęęęęë„ëëëëëëëëëëëëě…ěěěěěěěěěěěěííííííííííííî‡îîîîîîîîîîďďďďďďďďďďďđ‰đđđđđđđđđđńŠńńńńńńńńńńň‹ňňňňňňňňňňóóóóóóóóóóóóôôôôôôôôôôôôőőőőőőőőőőöööööööööööö÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřů’ůůůůůůůůůůúúúúúúúúúúűűűűűűűűűűüüüüüüüüüüüüýýýýýýýýýýţţţţţţţţţţ˙˙˙˙kn p s tvwy{|~€‚„…† ‡!!!!!!!!!!!!!!!!!!!!!"""""""""""""""""""""""‰#######################Š#Š$$$$$$$$$$$$$$$$$$$$$$$$$‹%%%%%%%%%%%%%%%%%%%%%%%%%%%Ś&&&&&&&&&&&&&&&&&&&&&&&&&&&&&Ť'''''''''''''''''''''''''''''''Ž'Ž(((((((((((((((((((((((((((((((((Ź))))))))))))))))))))))))))))))))))))************ŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖŁÖŁÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖפפ××××××××××××××××××××××××××××××ŘĄŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘ٦ŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚ§Ú§ÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚۨŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜ©ÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝŞÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢ«ŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢ߬߬ßßßßßßßßßßßßßßßßßßŕ­ŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕá®ááááááááááááááááâŻââââââââââââââââă°ăăăăăăăăăăăăăăăăä±ääääääääääääääĺ˛ĺĺĺĺĺĺĺĺĺĺĺĺĺĺćłććććććććććććććç´ççççççççççççççččččččččččččččé¶ééééééééééééę·ęęęęęęęęęęęęëëëëëëëëëëëëëëěěěěěěěěěěěěíşííííííííííííîîîîîîîîîîîîďďďďďďďďďďďďđđđđđđđđđđđđńńńńńńńńńńńńňňňňňňňňňňóŔóóóóóóóóóóôôôôôôôôôôôôőőőőőőőőőőöĂöööööööööö÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřřřůůůůůůůůůůúúúúúúúúúúűűűűűűűűűűüÉüüüüüüüüüüýýýýýýýýýýţţţţţţţţţţ˙˙˙˙ > DFIJKMNOPQR S!!!!!!!!!!!!!!!!!!!!!T"""""""""""""""""""""""U#######################V$$$$$$$$$$$$$$$$$$$$$$$$$W$W%%%%%%%%%%%%%%%%%%%%%%%%%X%X&&&&&&&&&&&&&&&&&&&&&&&&&&&Y&Y'''''''''''''''''''''''''''''''Z((((((((((((((((((((((((((((((((([([)))))))))))))))))))))))))))))))))))\****************ŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááááââââââââââââââââââăăăăăăăăăăăăăăăăăăääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććććççççççççççççççččččččččččččččččééééééééééééééęęęęęęęęęęęęëëëëëëëëëëëëëëěěěěěěěěěěěěěěííííííííííííîîîîîîîîîîîîďďďďďďďďďďďďđđđđđđđđđđđđńńńńńńńńńńńńňňňňňňňňňňňňóóóóóóóóóóôôôôôôôôôôôôőőőőőőőőőőőőöööööööööö÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřřřůůůůůůůůůůúúúúúúúúúúűűűűűűűűűűűűüüüüüüüüüüýýýýýýýýýýţţţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!!!""""""""""""""""""""""""########################$$$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''''''''(((((((((((((((((((((((((((((((((())))))))))))))))))))))))))))))))))))))******************ÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááááââââââââââââââââââăăăăăăăăăăăăăăăăăăääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććććççççççççççççççččččččččččččččééééééééééééééęęęęęęęęęęęęęęëëëëëëëëëëëëëëěěěěěěěěěěěěííííííííííííîîîîîîîîîîîîîîďďďďďďďďďďďďđđđđđđđđđđđđńńńńńńńńńńňňňňňňňňňňňňóóóóóóóóóóóóôôôôôôôôôôőőőőőőőőőőőőöööööööööö÷÷÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřůůůůůůůůůůúúúúúúúúúúűűűűűűűűűűűűüüüüüüüüüüýýýýýýýýýýţţţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!!!""""""""""""""""""""""""########################$$$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''''''(((((((((((((((((((((((((((((((((())))))))))))))))))))))))))))))))))))))**********************ÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ× × ××××××××××××××××××××××××××××××Ř ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮ Ů ŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚ ÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰ Ű ŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕáááááááááááááááááâââââââââââââââââăăăăăăăăăăăăăăăăăääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺćććććććććććććććççççççççççççççčččččččččččččéééééééééééééęęęęęęęęęęęęęëëëëëëëëëëëëëěěěěěěěěěěěěíííííííííííî îîîîîîîîîîîîďďďďďďďďďďďďđđđđđđđđđđđđńńńńńńńńńńň$ňňňňňňňňňňóóóóóóóóóóóóôôôôôôôôôôő'őőőőőőőőőőöööööööööö÷÷÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřůůůůůůůůůůúúúúúúúúúúű-űűűűűűűűűűüüüüüüüüüüýýýýýýýýýýţţţţţţţţţţ˙˙˙˙Ď Ú ßáĺćçčéëěí î!!!!!!!!!!!!!!!!!!!!!ď"""""""""""""""""""""đ"đ#######################ń$$$$$$$$$$$$$$$$$$$$$$$$$ň%%%%%%%%%%%%%%%%%%%%%%%%%%%ó&&&&&&&&&&&&&&&&&&&&&&&&&&&&&ô'''''''''''''''''''''''''''''''ő(((((((((((((((((((((((((((((((((ö)))))))))))))))))))))))))))))))))))÷)÷**********************ÔÔÔÔÔÔŐ;Ő;ŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖ<ÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ×=××××××××××××××××××××××××××××××Ř>Ř>ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮ?ŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚ@ÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰAŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜBÜBÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝCÝCÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢDŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßEßßßßßßßßßßßßßßßßßßŕFŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááááâHââââââââââââââââăIăăăăăăăăăăăăăăäJääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććçMççççççççççççççččččččččččččččééééééééééééééęęęęęęęęęęęęęęëëëëëëëëëëëëěRěěěěěěěěěěěěííííííííííííîîîîîîîîîîîîďďďďďďďďďďďďđđđđđđđđđđđđńńńńńńńńńńńńňňňňňňňňňňóYóóóóóóóóóóôôôôôôôôôôôôőőőőőőőőőőöööööööööö÷]÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřůůůůůůůůůůúúúúúúúúúúúúűűűűűűűűűűüüüüüüüüüüýýýýýýýýýýţţţţţţţţţţ˙˙˙˙  Ł Ą ¬®Ż°±µ¶·¸ą ş!!!!!!!!!!!!!!!!!!!!!»"""""""""""""""""""""Ľ#######################˝#˝$$$$$$$$$$$$$$$$$$$$$$$$$ľ%%%%%%%%%%%%%%%%%%%%%%%%%ż%ż&&&&&&&&&&&&&&&&&&&&&&&&&&&Ŕ&Ŕ'''''''''''''''''''''''''''''Á'Á(((((((((((((((((((((((((((((((Â(Â)))))))))))))))))))))))))))))))))))Ă**************************ÔÔÔÔÔÔÔÔÔÔŐnŐnŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖoÖoÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ×p×p××××××××××××××××××××××××××××××ŘqŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮrŮrŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚsÚsÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰtŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜuÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝvÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢwŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßxßßßßßßßßßßßßßßßßßßŕyŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕázááááááááááááááááááâ{ââââââââââââââââăăăăăăăăăăăăăăăăä}ääääääääääääääĺ~ĺĺĺĺĺĺĺĺĺĺĺĺĺĺćććććććććććććććç€ççççççççççççčččččččččččččé‚ééééééééééééęęęęęęęęęęęęęë„ëëëëëëëëëëëëěěěěěěěěěěěěí†ííííííííííî‡îîîîîîîîîîďďďďďďďďďďďđ‰đđđđđđđđđđńŠńńńńńńńńńńňňňňňňňňňňňňóóóóóóóóóóôŤôôôôôôôôôôőőőőőőőőőőöööööööööööö÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřůůůůůůůůůůú“úúúúúúúúúúűűűűűűűűűűüüüüüüüüüüýýýýýýýýýýţţţţţţţţţţ˙˙˙˙n tuvwxz|}€‚„…† ‡!!!!!!!!!!!!!!!!!!!!!"""""""""""""""""""""‰#######################Š$$$$$$$$$$$$$$$$$$$$$$$$$‹$‹%%%%%%%%%%%%%%%%%%%%%%%%%Ś&&&&&&&&&&&&&&&&&&&&&&&&&&&&&Ť'''''''''''''''''''''''''''''''Ž(((((((((((((((((((((((((((((((((Ź))))))))))))))))))))))))))))))))))))****************************ÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐ˘Ő˘ŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖŁÖŁÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖפ××××××××××××××××××××××××××××××ŘĄŘĄŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘ٦ŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚ§ÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚۨŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜ©ÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝŞÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢ«ŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢ߬ßßßßßßßßßßßßßßßßßßŕ­ŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕá®ááááááááááááááááááâŻââââââââââââââă°ăăăăăăăăăăăăăăăăä±ääääääääääääääĺ˛ĺĺĺĺĺĺĺĺĺĺĺĺĺĺćłććććććććććććććççççççççççççççčµččččččččččččé¶ééééééééééééę·ęęęęęęęęęęęęëëëëëëëëëëëëěąěěěěěěěěěěěěííííííííííííîîîîîîîîîîîîďďďďďďďďďďďďđđđđđđđđđđđđńńńńńńńńńńňżňňňňňňňňňňóóóóóóóóóóóóôôôôôôôôôôőőőőőőőőőőöĂöööööööööö÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřůůůůůůůůůůůůúúúúúúúúúúűűűűűűűűűűüüüüüüüüüüýýýýýýýýýýţţţţţţţţţţ˙˙˙˙5 = ? EFGJKLMNOPQR S!!!!!!!!!!!!!!!!!!!T!T"""""""""""""""""""""U#######################V$$$$$$$$$$$$$$$$$$$$$$$$$W%%%%%%%%%%%%%%%%%%%%%%%%%%%X&&&&&&&&&&&&&&&&&&&&&&&&&&&Y&Y'''''''''''''''''''''''''''''Z'Z((((((((((((((((((((((((((((((([([)))))))))))))))))))))))))))))))))\)\********************************ÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááááááââââââââââââââââăăăăăăăăăăăăăăăăăăääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććççççççççççççççççččččččččččččččééééééééééééééęęęęęęęęęęęęëëëëëëëëëëëëëëěěěěěěěěěěěěííííííííííííîîîîîîîîîîîîďďďďďďďďďďďďđđđđđđđđđđđđńńńńńńńńńńńńňňňňňňňňňňóóóóóóóóóóóóôôôôôôôôôôőőőőőőőőőőőőöööööööööö÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřůůůůůůůůůůůůúúúúúúúúúúűűűűűűűűűűüüüüüüüüüüýýýýýýýýýýţţţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!""""""""""""""""""""""""########################$$$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''''''(((((((((((((((((((((((((((((((((())))))))))))))))))))))))))))))))))))************************************ÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááááââââââââââââââââââăăăăăăăăăăăăăăăăăăääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććććççççççççççççççččččččččččččččééééééééééééééęęęęęęęęęęęęęęëëëëëëëëëëëëěěěěěěěěěěěěěěííííííííííííîîîîîîîîîîîîďďďďďďďďďďďďđđđđđđđđđđńńńńńńńńńńńńňňňňňňňňňňňňóóóóóóóóóóôôôôôôôôôôôôőőőőőőőőőőöööööööööö÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřřřůůůůůůůůůůúúúúúúúúúúűűűűűűűűűűüüüüüüüüüüýýýýýýýýýýţţţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!!!""""""""""""""""""""""""########################$$$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''''''(((((((((((((((((((((((((((((((((())))))))))))))))))))))))))))))))))))**************************************ÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ× ××××××××××××××××××××××××××××××Ř Ř ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮ Ů ŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚ Ú ÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰ ŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕáááááááááááááááááâââââââââââââââââăăăăăăăăăăăăăăăăăääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺćććććććććććććććçççççççççççççčččččččččččččéééééééééééééęęęęęęęęęęęęęëëëëëëëëëëëëěěěěěěěěěěěěěííííííííííííîîîîîîîîîîîîďďďďďďďďďďďďđđđđđđđđđđń#ńńńńńńńńńńňňňňňňňňňňňňóóóóóóóóóóôôôôôôôôôôôôőőőőőőőőőőöööööööööö÷÷÷÷÷÷÷÷÷÷ř*řřřřřřřřřřůůůůůůůůůůúúúúúúúúúúűűűűűűűűűűüüüüüüüüüüýýýýýýýýýýţţţţţţţţţţ˙˙˙˙Ň × Ů Űŕáćčéęëěí î!!!!!!!!!!!!!!!!!!!!!ď"""""""""""""""""""""""đ#######################ń$$$$$$$$$$$$$$$$$$$$$$$ň$ň%%%%%%%%%%%%%%%%%%%%%%%%%ó&&&&&&&&&&&&&&&&&&&&&&&&&&&&&ô'''''''''''''''''''''''''''''ő'ő(((((((((((((((((((((((((((((((ö(ö)))))))))))))))))))))))))))))))))÷)÷*************************************řÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐ;Ő;ŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖ<Ö<ÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ×=×=××××××××××××××××××××××××××××××Ř>ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮ?ŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚ@ÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰAŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜBÜBÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝCÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢDŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßEßßßßßßßßßßßßßßßßßßŕFŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕáGááááááááááááááááâHââââââââââââââââăIăăăăăăăăăăăăăăäJääääääääääääääĺKĺĺĺĺĺĺĺĺĺĺĺĺĺĺćLććććććććććććććççççççççççççççčNččččččččččččééééééééééééééęęęęęęęęęęęęëQëëëëëëëëëëëëěěěěěěěěěěěěííííííííííííîTîîîîîîîîîîďUďďďďďďďďďďđđđđđđđđđđđđńńńńńńńńńńňXňňňňňňňňňňóóóóóóóóóóôZôôôôôôôôôôőőőőőőőőőőöööööööööö÷]÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřůůůůůůůůůůúúúúúúúúúúűűűűűűűűűűüüüüüüüüüüýýýýýýýýýýţţţţţţţţţţ˙˙˙˙ź ¦ ©Ş«®Ż°±˛łµ¶·¸ą ş!!!!!!!!!!!!!!!!!!!!!»"""""""""""""""""""""Ľ"Ľ#####################˝#˝$$$$$$$$$$$$$$$$$$$$$$$ľ%%%%%%%%%%%%%%%%%%%%%%%%%%%ż&&&&&&&&&&&&&&&&&&&&&&&&&&&Ŕ&Ŕ'''''''''''''''''''''''''''''Á(((((((((((((((((((((((((((((((Â(Â)))))))))))))))))))))))))))))))))Ă)Ă*************************************Ä*Ä++ÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐnŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖoÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ×p××××××××××××××××××××××××××××××ŘqŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮrŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚsÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰtŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜuÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝvÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢwŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßxßßßßßßßßßßßßßßßßßßŕyŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕázááááááááááááááááâ{ââââââââââââââââă|ăăăăăăăăăăăăăăä}ääääääääääääääĺ~ĺĺĺĺĺĺĺĺĺĺĺĺĺĺćććććććććććććç€ççççççççççççççččččččččččččé‚ééééééééééééęęęęęęęęęęęęęëëëëëëëëëëëëě…ěěěěěěěěěěí†ííííííííííííîîîîîîîîîîîîďďďďďďďďďďđ‰đđđđđđđđđđńńńńńńńńńńńńňňňňňňňňňňóŚóóóóóóóóóóôôôôôôôôôôőőőőőőőőőőöŹöööööööööö÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřůůůůůůůůůůúúúúúúúúúúűűűűűűűűűűüüüüüüüüüüýýýýýýýýýýţţţţţţţţţţ˙˙˙˙m q uyz|}~€‚„† ‡!!!!!!!!!!!!!!!!!!!!!"""""""""""""""""""""‰#######################Š$$$$$$$$$$$$$$$$$$$$$$$$$‹%%%%%%%%%%%%%%%%%%%%%%%%%Ś%Ś&&&&&&&&&&&&&&&&&&&&&&&&&&&Ť'''''''''''''''''''''''''''''Ž'Ž(((((((((((((((((((((((((((((((Ź)))))))))))))))))))))))))))))))))))*************************************‘*‘++++++ÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐ˘Ő˘ŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖŁÖŁÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖפפ××××××××××××××××××××××××××××ŘĄŘĄŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘ٦٦ŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚ§Ú§ÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚۨۨŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜ©ÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝŞÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢ«ŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢ߬ßßßßßßßßßßßßßßßßßßŕ­ŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕá®ááááááááááááááááâŻââââââââââââââââă°ăăăăăăăăăăăăăăä±ääääääääääääääĺ˛ĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććç´ççççççççççççčµččččččččččččé¶ééééééééééééęęęęęęęęęęęęë¸ëëëëëëëëëëëëěěěěěěěěěěěěííííííííííííîîîîîîîîîîîîďďďďďďďďďďďďđđđđđđđđđđńľńńńńńńńńńńňňňňňňňňňňňňóóóóóóóóóóôôôôôôôôôôőÂőőőőőőőőőőöööööööööö÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřůůůůůůůůůůúúúúúúúúúúűűűűűűűűűűüüüüüüüüüüýýýýýýýýýýţţţţţţţţţţ˙˙˙˙:; > @CDGKLMNOPQR S!!!!!!!!!!!!!!!!!!!!!T"""""""""""""""""""""U#######################V$$$$$$$$$$$$$$$$$$$$$$$$$W%%%%%%%%%%%%%%%%%%%%%%%%%X&&&&&&&&&&&&&&&&&&&&&&&&&&&Y&Y'''''''''''''''''''''''''''''Z((((((((((((((((((((((((((((((([([)))))))))))))))))))))))))))))))))\)\*************************************]++++++++++ÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááááââââââââââââââââââăăăăăăăăăăăăăăăăääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććććççççççççççççççččččččččččččččééééééééééééęęęęęęęęęęęęęęëëëëëëëëëëëëěěěěěěěěěěěěííííííííííííîîîîîîîîîîîîďďďďďďďďďďďďđđđđđđđđđđđđńńńńńńńńńńňňňňňňňňňňňňóóóóóóóóóóôôôôôôôôôôôôőőőőőőőőőőöööööööööö÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřůůůůůůůůůůúúúúúúúúúúűűűűűűűűűűüüüüüüüüüüýýýýýýýýýýţţţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!!!""""""""""""""""""""""########################$$$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''''''(((((((((((((((((((((((((((((((())))))))))))))))))))))))))))))))))))****************************************++++++++++++ÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááááââââââââââââââââăăăăăăăăăăăăăăăăăăääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććççççççççççççççččččččččččččččééééééééééééééęęęęęęęęęęęęëëëëëëëëëëëëëëěěěěěěěěěěěěííííííííííííîîîîîîîîîîîîďďďďďďďďďďďďđđđđđđđđđđńńńńńńńńńńńńňňňňňňňňňňóóóóóóóóóóóóôôôôôôôôôôőőőőőőőőőőöööööööööö÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřůůůůůůůůůůúúúúúúúúúúűűűűűűűűűűüüüüüüüüüüýýýýýýýýýýţţţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!!!""""""""""""""""""""""########################$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''''(((((((((((((((((((((((((((((((((())))))))))))))))))))))))))))))))))****************************************++++++++++++++++ÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ× ××××××××××××××××××××××××××××Ř Ř ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮ Ů ŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚ ÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰ ŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕáááááááááááááááááâââââââââââââââăăăăăăăăăăăăăăăăăääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺćććććććććććććçççççççççççççčččččččččččččéééééééééééééęęęęęęęęęęęëëëëëëëëëëëëëěěěěěěěěěěěěííííííííííííîîîîîîîîîîîîďďďďďďďďďďďďđđđđđđđđđđń#ńńńńńńńńńńňňňňňňňňňňó%óóóóóóóóóóôôôôôôôôôôőőőőőőőőőőöööööööööö÷÷÷÷÷÷÷÷÷÷ř*řřřřřřřřů+ůůůůůůůůú,úúúúúúúúű-űűűűűűűűü.üüüüüüüüýýýýýýýýýýţţţţţţţţţţ˙˙˙˙ Ř Üâäĺçęëěí î!!!!!!!!!!!!!!!!!!!!!ď"""""""""""""""""""""đ#######################ń$$$$$$$$$$$$$$$$$$$$$$$ň%%%%%%%%%%%%%%%%%%%%%%%%%ó%ó&&&&&&&&&&&&&&&&&&&&&&&&&&&ô'''''''''''''''''''''''''''''ő(((((((((((((((((((((((((((((((ö(ö)))))))))))))))))))))))))))))))))÷*************************************ř*ř++++++++++++++++ÓÓÔ:ÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐ;ŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖ<Ö<ÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ×=×=××××××××××××××××××××××××××××Ř>ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮ?ŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚ@Ú@ÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰAŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜBÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝCÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢDŢDŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßEßßßßßßßßßßßßßßßßŕFŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕáGááááááááááááááááâHââââââââââââââăIăăăăăăăăăăăăăăäJääääääääääääääĺKĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććçMççççççççççççčNččččččččččččéOééééééééééééęęęęęęęęęęęęëQëëëëëëëëëëěRěěěěěěěěěěíSííííííííííîTîîîîîîîîîîďUďďďďďďďďďďđđđđđđđđđđđđńńńńńńńńńńňňňňňňňňňňňňóóóóóóóóóóôôôôôôôôôôőőőőőőőőőőö\öööööööö÷]÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřůůůůůůůůůůúúúúúúúúúúűűűűűűűűűűüüüüüüüüýýýýýýýýýýţţţţţţţţţţ˙˙˙˙ Ą §©Ş«¬­Ż˛ł´µ¶·¸ą ş!!!!!!!!!!!!!!!!!!!!!»"""""""""""""""""""""Ľ#####################˝#˝$$$$$$$$$$$$$$$$$$$$$$$ľ%%%%%%%%%%%%%%%%%%%%%%%%%ż&&&&&&&&&&&&&&&&&&&&&&&&&&&Ŕ&Ŕ'''''''''''''''''''''''''''Á'Á(((((((((((((((((((((((((((((((Â)))))))))))))))))))))))))))))))))Ă)Ă***********************************Ä*Ä++++++++++++++++++++ÓÓÓÓÔmÔmÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐnŐnŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖoÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ×p××××××××××××××××××××××××××××ŘqŘqŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮrŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚsÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰtŰtŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜuÜuÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝvÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢwŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßxßßßßßßßßßßßßßßßßŕyŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕázááááááááááááááááââââââââââââââââă|ăăăăăăăăăăăăăăä}ääääääääääääääĺ~ĺĺĺĺĺĺĺĺĺĺĺĺćććććććććććććććççççççççççççççččččččččččččččééééééééééééęęęęęęęęęęęęęëëëëëëëëëëëëěěěěěěěěěěěěííííííííííííîîîîîîîîîîîîďďďďďďďďďďđ‰đđđđđđđđđđńńńńńńńńńńň‹ňňňňňňňňňňóóóóóóóóóóôôôôôôôôôôőŽőőőőőőőőőőöööööööööö÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřůůůůůůůůůůúúúúúúúúúúűűűűűűűűűűüüüüüüüüý–ýýýýýýýýţţţţţţţţţţ˙˙˙˙ s u{|}~€‚„…† ‡!!!!!!!!!!!!!!!!!!!!!"""""""""""""""""""""‰#####################Š$$$$$$$$$$$$$$$$$$$$$$$$$‹%%%%%%%%%%%%%%%%%%%%%%%%%Ś&&&&&&&&&&&&&&&&&&&&&&&&&&&Ť'''''''''''''''''''''''''''''Ž(((((((((((((((((((((((((((((((Ź(Ź)))))))))))))))))))))))))))))))))*************************************‘++++++++++++++++++++++++ÓÓÓÓÓÓÓÓÔˇÔˇÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐ˘Ő˘ŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖŁÖŁÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖפפ××××××××××××××××××××××××××××ŘĄŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘ٦٦ŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚ§ÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚۨŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜ©ÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝŞÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢ«ŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢ߬ßßßßßßßßßßßßßßßßŕ­ŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕá®ááááááááááááááâŻââââââââââââââââă°ăăăăăăăăăăăăăăä±ääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺćłććććććććććććç´ççççççççççççčµččččččččččččé¶ééééééééééééęęęęęęęęęęęęë¸ëëëëëëëëëëěąěěěěěěěěěěíşííííííííííî»îîîîîîîîîîďĽďďďďďďďďďďđđđđđđđđđđńľńńńńńńńńńńňňňňňňňňňňóóóóóóóóóóôÁôôôôôôôôôôőőőőőőőőőőöööööööööö÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřůůůůůůůůůůúúúúúúúúúúűűűűűűűűűűüüüüüüüüüüýýýýýýýýţţţţţţţţţţ˙˙˙˙89:; < CDEFGJKMNOPQR S!!!!!!!!!!!!!!!!!!!T!T"""""""""""""""""""U"U#####################V$$$$$$$$$$$$$$$$$$$$$$$W$W%%%%%%%%%%%%%%%%%%%%%%%X%X&&&&&&&&&&&&&&&&&&&&&&&&&Y&Y'''''''''''''''''''''''''''Z'Z((((((((((((((((((((((((((((([([)))))))))))))))))))))))))))))))))\)\***********************************]*]++++++++++++++++++++++++++ÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááââââââââââââââââââăăăăăăăăăăăăăăăăääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććççççççççççççççččččččččččččččééééééééééééęęęęęęęęęęęęęęëëëëëëëëëëëëěěěěěěěěěěěěííííííííííííîîîîîîîîîîîîďďďďďďďďďďđđđđđđđđđđđđńńńńńńńńńńňňňňňňňňňňóóóóóóóóóóóóôôôôôôôôôôőőőőőőőőőőöööööööööö÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřůůůůůůůůůůúúúúúúúúúúűűűűűűűűűűüüüüüüüüüüýýýýýýýýţţţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!""""""""""""""""""""""########################$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''''(((((((((((((((((((((((((((((((())))))))))))))))))))))))))))))))))))**************************************++++++++++++++++++++++++++++++ÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááááââââââââââââââââââăăăăăăăăăăăăăăăăääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććççççççççççççççččččččččččččééééééééééééééęęęęęęęęęęęęëëëëëëëëëëëëěěěěěěěěěěěěííííííííííííîîîîîîîîîîîîďďďďďďďďďďďďđđđđđđđđđđńńńńńńńńńńňňňňňňňňňňňňóóóóóóóóóóôôôôôôôôôôőőőőőőőőőőöööööööööö÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřůůůůůůůůůůúúúúúúúúúúűűűűűűűűűűüüüüüüüüüüýýýýýýýýţţţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!""""""""""""""""""""""########################$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''''(((((((((((((((((((((((((((((((())))))))))))))))))))))))))))))))))**************************************++++++++++++++++++++++++++++++++++ÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ× × ××××××××××××××××××××××××××××Ř ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮ ŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚ Ú ÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰ ŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕáááááááááááááááááâââââââââââââââââăăăăăăăăăăăăăăăăääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććççççççççççççççččččččččččččéééééééééééééęęęęęęęęęęęęëëëëëëëëëëëěěěěěěěěěěěíííííííííííî îîîîîîîîîîďďďďďďďďďďďďđđđđđđđđđđńńńńńńńńńńň$ňňňňňňňňňňóóóóóóóóóóôôôôôôôôôôőőőőőőőőőőöööööööööö÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřůůůůůůůůůůúúúúúúúúúúűűűűűűűűűűüüüüüüüüüüýýýýýýýýţţţţţţţţţţ˙˙˙˙Ń Ů ÜŢßŕáâäĺćçčęëěí î!!!!!!!!!!!!!!!!!!!ď"""""""""""""""""""""đ#######################ń$$$$$$$$$$$$$$$$$$$$$$$ň%%%%%%%%%%%%%%%%%%%%%%%%%ó&&&&&&&&&&&&&&&&&&&&&&&&&ô&ô'''''''''''''''''''''''''''ő'ő(((((((((((((((((((((((((((((ö(ö)))))))))))))))))))))))))))))))))÷***********************************ř*ř++++++++++++++++++++++++++++++++++ÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔ:ÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐ;Ő;ŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖ<Ö<ÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ×=××××××××××××××××××××××××××××Ř>Ř>ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮ?Ů?ŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚ@ÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰAŰAŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜBÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝCÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢDŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßEßßßßßßßßßßßßßßßßŕFŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕáGááááááááááááááááâHââââââââââââââăIăăăăăăăăăăăăăăäJääääääääääääääĺKĺĺĺĺĺĺĺĺĺĺĺĺćLććććććććććććçMççççççççççççčNččččččččččččéOééééééééééęPęęęęęęęęęęęęëëëëëëëëëëëëěěěěěěěěěěěěííííííííííííîîîîîîîîîîďUďďďďďďďďďďđđđđđđđđđđńWńńńńńńńńńńňňňňňňňňňňóóóóóóóóóóôZôôôôôôôôő[őőőőőőőőö\öööööööö÷]÷÷÷÷÷÷÷÷ř^řřřřřřřřů_ůůůůůůůůúúúúúúúúúúűűűűűűűűűűüüüüüüüüüüýýýýýýýýţdţţţţţţţţ˙˙˙˙ ¦ §©Ż±˛´µ¶·¸ą ş!!!!!!!!!!!!!!!!!!!»"""""""""""""""""""""Ľ#####################˝#˝$$$$$$$$$$$$$$$$$$$$$ľ$ľ%%%%%%%%%%%%%%%%%%%%%%%ż%ż&&&&&&&&&&&&&&&&&&&&&&&&&Ŕ'''''''''''''''''''''''''''''Á(((((((((((((((((((((((((((((((Â)))))))))))))))))))))))))))))))))Ă)Ă*********************************Ä*Ä++++++++++++++++++++++++++++++++++++++ÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔmÔmÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐnŐnŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖoÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ×p×p××××××××××××××××××××××××××××ŘqŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮrŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚsÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰtŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜuÜuÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝvÝvÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢwŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßxßßßßßßßßßßßßßßßßŕyŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕázááááááááááááááááâ{ââââââââââââââă|ăăăăăăăăăăăăăăä}ääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺćććććććććććććç€ççççççççççççčččččččččččččééééééééééééęęęęęęęęęęęë„ëëëëëëëëëëě…ěěěěěěěěěěí†ííííííííííî‡îîîîîîîîîîďďďďďďďďďďđ‰đđđđđđđđđđńńńńńńńńńńňňňňňňňňňňóŚóóóóóóóóóóôôôôôôôôôôőőőőőőőőőőöööööööööö÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřůůůůůůůůúúúúúúúúúúűűűűűűűűűűüüüüüüüüüüýýýýýýýýýýţţţţţţţţ˙˙˙˙ uxyz{|}€‚„…† ‡!!!!!!!!!!!!!!!!!!!"""""""""""""""""""""‰#####################Š$$$$$$$$$$$$$$$$$$$$$$$‹%%%%%%%%%%%%%%%%%%%%%%%%%Ś&&&&&&&&&&&&&&&&&&&&&&&&&&&Ť'''''''''''''''''''''''''''Ž'Ž(((((((((((((((((((((((((((((Ź(Ź))))))))))))))))))))))))))))))))***********************************‘+++++++++++++++++++++++++++++++++++++++’+’ÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔˇÔˇÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐ˘ŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖŁÖŁÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖפ××××××××××××××××××××××××××××ŘĄŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘ٦٦ŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚ§Ú§ÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚۨŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜ©ÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝŞÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢ«ŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢ߬ßßßßßßßßßßßßßßßßŕ­ŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕá®ááááááááááááááááâŻââââââââââââââă°ăăăăăăăăăăăăăăä±ääääääääääääĺ˛ĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććççççççççççççççččččččččččččé¶ééééééééééééęęęęęęęęęęęęëëëëëëëëëëëëěěěěěěěěěěěěííííííííííííîîîîîîîîîîďĽďďďďďďďďďďđđđđđđđđđđńľńńńńńńńńňżňňňňňňňňňňóóóóóóóóóóôôôôôôôôôôőőőőőőőőőőöööööööööö÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřůůůůůůůůúÇúúúúúúúúűűűűűűűűűűüüüüüüüüüüýýýýýýýýýýţţţţţţţţ˙˙˙˙5:; < = > CIJKLMNOPQR S!!!!!!!!!!!!!!!!!!!T"""""""""""""""""""""U#####################V$$$$$$$$$$$$$$$$$$$$$$$W%%%%%%%%%%%%%%%%%%%%%%%%%X&&&&&&&&&&&&&&&&&&&&&&&&&Y&Y'''''''''''''''''''''''''''Z((((((((((((((((((((((((((((((([)))))))))))))))))))))))))))))))))\***********************************]*]+++++++++++++++++++++++++++++++++++++^+^,,,,ÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááááââââââââââââââââăăăăăăăăăăăăăăăăääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććççççççççççççççččččččččččččččééééééééééééęęęęęęęęęęęęëëëëëëëëëëëëěěěěěěěěěěěěííííííííííííîîîîîîîîîîîîďďďďďďďďďďđđđđđđđđđđđđńńńńńńńńńńňňňňňňňňňňóóóóóóóóóóôôôôôôôôôôőőőőőőőőőőöööööööööö÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřůůůůůůůůůůúúúúúúúúűűűűűűűűűűüüüüüüüüüüýýýýýýýýýýţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!""""""""""""""""""""""######################$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''''(((((((((((((((((((((((((((((((())))))))))))))))))))))))))))))))))************************************++++++++++++++++++++++++++++++++++++++++,,,,,,,,ÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááááââââââââââââââââăăăăăăăăăăăăăăääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććççççççççççççççččččččččččččééééééééééééééęęęęęęęęęęęęëëëëëëëëëëëëěěěěěěěěěěěěííííííííííîîîîîîîîîîîîďďďďďďďďďďďďđđđđđđđđđđńńńńńńńńńńňňňňňňňňňňóóóóóóóóóóôôôôôôôôôôőőőőőőőőőőöööööööööö÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřůůůůůůůůůůúúúúúúúúúúűűűűűűűűüüüüüüüüüüýýýýýýýýýýţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!""""""""""""""""""""########################$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''(((((((((((((((((((((((((((((((())))))))))))))))))))))))))))))))))************************************++++++++++++++++++++++++++++++++++++++++,,,,,,,,,,,,ÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ× ××××××××××××××××××××××××××××Ř ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮ ŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚ ÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰ Ű ŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕáááááááááááááááááââââââââââââââââăăăăăăăăăăăăăäääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺćććććććććććććçççççççççççççčččččččččččéééééééééééééęęęęęęęęęęęęëëëëëëëëëëëëěěěěěěěěěěěěííííííííííî îîîîîîîîîîďďďďďďďďďďďďđđđđđđđđđđńńńńńńńńńńňňňňňňňňňňó%óóóóóóóóô&ôôôôôôôôő'őőőőőőőőö(öööööööö÷)÷÷÷÷÷÷÷÷řřřřřřřřřřůůůůůůůůůůúúúúúúúúúúűűűűűűűűüüüüüüüüüüýýýýýýýýýýţţţţţţţţ˙˙˙˙Ó ÜŢáäčéęëěí î!!!!!!!!!!!!!!!!!!!ď"""""""""""""""""""đ#####################ń#ń$$$$$$$$$$$$$$$$$$$$$ň$ň%%%%%%%%%%%%%%%%%%%%%%%ó&&&&&&&&&&&&&&&&&&&&&&&&&ô&ô'''''''''''''''''''''''''''ő(((((((((((((((((((((((((((((ö(ö)))))))))))))))))))))))))))))))÷)÷*********************************ř*ř+++++++++++++++++++++++++++++++++++++ů+ů,,,,,,,,,,,,ÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔ:ÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐ;ŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖ<Ö<ÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ×=×=××××××××××××××××××××××××××Ř>Ř>ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮ?Ů?ŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚ@Ú@ÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰAŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜBÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝCÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢDŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßEßßßßßßßßßßßßßßßßŕFŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕáGááááááááááááááâHââââââââââââââââăăăăăăăăăăăăăăäJääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺćLććććććććććććçMççççççççççççččččččččččččéOééééééééééęPęęęęęęęęęęëQëëëëëëëëëëěRěěěěěěěěěěíSííííííííííîîîîîîîîîîďUďďďďďďďďďďđđđđđđđđđđńWńńńńńńńńňXňňňňňňňňňňóóóóóóóóóóôôôôôôôôôôőőőőőőőőőőöööööööööö÷÷÷÷÷÷÷÷ř^řřřřřřřřůůůůůůůůůůúúúúúúúúúúűűűűűűűűüüüüüüüüüüýýýýýýýýýýţţţţţţţţ˙˙˙˙ ¤ Ą ©«¬®Ż°±˛ł´·¸ą ş!!!!!!!!!!!!!!!!!!!»"""""""""""""""""""Ľ#####################˝$$$$$$$$$$$$$$$$$$$$$$$ľ%%%%%%%%%%%%%%%%%%%%%%%%%ż&&&&&&&&&&&&&&&&&&&&&&&&&Ŕ'''''''''''''''''''''''''''Á'Á(((((((((((((((((((((((((((((Â)))))))))))))))))))))))))))))))))Ă***********************************Ä+++++++++++++++++++++++++++++++++++++Ĺ+Ĺ,,,,,,,,,,,,,,,,ÓlÓlÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔmÔmÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐnŐnŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖoÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ×p××××××××××××××××××××××××××××ŘqŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮrŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚsÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰtŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜuÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝvÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢwŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßxßßßßßßßßßßßßßßßßŕyŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕázááááááááááááááâ{ââââââââââââââă|ăăăăăăăăăăăăăăä}ääääääääääääĺ~ĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććççççççççççççčččččččččččččééééééééééééęęęęęęęęęęęë„ëëëëëëëëëëěěěěěěěěěěěěííííííííííî‡îîîîîîîîîîďďďďďďďďďďđ‰đđđđđđđđđđńńńńńńńńńńňňňňňňňňňňóóóóóóóóóóôôôôôôôôôôőőőőőőőőőőöööööööööö÷÷÷÷÷÷÷÷÷÷řřřřřřřřůůůůůůůůůůúúúúúúúúúúűűűűűűűűüüüüüüüüüüýýýýýýýýýýţţţţţţţţ˙˙˙˙ko p s twz{~€‚„…† ‡!!!!!!!!!!!!!!!!!!!"""""""""""""""""""‰#####################Š$$$$$$$$$$$$$$$$$$$$$$$‹%%%%%%%%%%%%%%%%%%%%%%%Ś%Ś&&&&&&&&&&&&&&&&&&&&&&&&&Ť'''''''''''''''''''''''''''Ž(((((((((((((((((((((((((((((Ź(Ź))))))))))))))))))))))))))))))))*********************************‘*‘+++++++++++++++++++++++++++++++++++++’,,,,,,,,,,,,,,,,,,,,ŇŇŇŇÓ Ó ÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔˇÔˇÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐ˘Ő˘ŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖŁÖŁÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖפפ××××××××××××××××××××××××××ŘĄŘĄŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘ٦ŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚ§ÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚۨŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰܩܩÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝŞÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢ«ŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢ߬ßßßßßßßßßßßßßßßßŕ­ŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕá®ááááááááááááááâŻââââââââââââââă°ăăăăăăăăăăăăăăä±ääääääääääääĺ˛ĺĺĺĺĺĺĺĺĺĺĺĺćłććććććććććććç´ççççççççççççččččččččččččé¶ééééééééééééęęęęęęęęęęęęëëëëëëëëëëěąěěěěěěěěěěíşííííííííííîîîîîîîîîîďĽďďďďďďďďďďđđđđđđđđđđńńńńńńńńńńňňňňňňňňňňóŔóóóóóóóóôÁôôôôôôôôőőőőőőőőőőöööööööööö÷÷÷÷÷÷÷÷÷÷řřřřřřřřůĆůůůůůůůůúúúúúúúúúúűűűűűűűűüÉüüüüüüüüýýýýýýýýýýţţţţţţţţ˙˙˙˙: ADEHIKLNOPQR S!!!!!!!!!!!!!!!!!T!T"""""""""""""""""""U#####################V$$$$$$$$$$$$$$$$$$$$$$$W%%%%%%%%%%%%%%%%%%%%%%%X&&&&&&&&&&&&&&&&&&&&&&&&&Y&Y'''''''''''''''''''''''''Z'Z((((((((((((((((((((((((((((([)))))))))))))))))))))))))))))))\)\*********************************]*]+++++++++++++++++++++++++++++++++++++^+^,,,,,,,,,,,,,,,,,,,,,,ŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááââââââââââââââââăăăăăăăăăăăăăăăăääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććççççççççççççččččččččččččččééééééééééééęęęęęęęęęęęęëëëëëëëëëëëëěěěěěěěěěěěěííííííííííîîîîîîîîîîîîďďďďďďďďďďđđđđđđđđđđńńńńńńńńńńňňňňňňňňňňňňóóóóóóóóóóôôôôôôôôőőőőőőőőőőöööööööööö÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřůůůůůůůůúúúúúúúúúúűűűűűűűűűűüüüüüüüüýýýýýýýýýýţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!""""""""""""""""""""""######################$$$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''(((((((((((((((((((((((((((((((())))))))))))))))))))))))))))))))************************************++++++++++++++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,ŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááââââââââââââââââăăăăăăăăăăăăăăääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććççççççççççççččččččččččččééééééééééééééęęęęęęęęęęęęëëëëëëëëëëěěěěěěěěěěěěííííííííííííîîîîîîîîîîďďďďďďďďďďđđđđđđđđđđđđńńńńńńńńńńňňňňňňňňňňóóóóóóóóóóôôôôôôôôôôőőőőőőőőőőöööööööö÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřůůůůůůůůúúúúúúúúúúűűűűűűűűűűüüüüüüüüýýýýýýýýýýţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!""""""""""""""""""""""######################$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''(((((((((((((((((((((((((((((())))))))))))))))))))))))))))))))))**********************************++++++++++++++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,ŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ× × ××××××××××××××××××××××××××Ř Ř ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮ ŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚ ÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰ ŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕáááááááááááááááâââââââââââââââăăăăăăăăăăăăăäääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺćććććććććććććççççççççççççčččččččččččéééééééééééééęęęęęęęęęęęęëëëëëëëëëëěěěěěěěěěěěíííííííííííîîîîîîîîîîď!ďďďďďďďďđ"đđđđđđđđđđńńńńńńńńńńňňňňňňňňňňóóóóóóóóóóôôôôôôôôôôőőőőőőőőőőöööööööö÷)÷÷÷÷÷÷÷÷řřřřřřřřřřůůůůůůůůú,úúúúúúúúűűűűűűűűűűüüüüüüüüýýýýýýýýýýţţţţţţţţ˙˙˙˙Ô Ř Ů Ú ŰÜŕäĺćçčéęëí î!!!!!!!!!!!!!!!!!ď"""""""""""""""""""""đ###################ń#ń$$$$$$$$$$$$$$$$$$$$$ň%%%%%%%%%%%%%%%%%%%%%%%ó%ó&&&&&&&&&&&&&&&&&&&&&&&ô&ô'''''''''''''''''''''''''ő'ő(((((((((((((((((((((((((((((ö)))))))))))))))))))))))))))))))÷)÷*********************************ř+++++++++++++++++++++++++++++++++++++ů+ů,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,ŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓ9Ó9ÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔ:ÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐ;Ő;ŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖ<Ö<ÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ×=×=××××××××××××××××××××××××××Ř>ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮ?Ů?ŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚ@ÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰAŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜBÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝCÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢDŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßEßßßßßßßßßßßßßßßßŕFŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕáGááááááááááááááâHââââââââââââââăIăăăăăăăăăăăăäJääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććçMççççççççççççččččččččččččéOééééééééééęPęęęęęęęęęęëQëëëëëëëëëëěRěěěěěěěěěěííííííííííîTîîîîîîîîîîďďďďďďďďďďđđđđđđđđđđńńńńńńńńńńňXňňňňňňňňóóóóóóóóóóôôôôôôôôôôőőőőőőőőőőöööööööööö÷÷÷÷÷÷÷÷řřřřřřřřřřůůůůůůůůůůúúúúúúúúűűűűűűűűűűüüüüüüüüýýýýýýýýýýţţţţţţţţ˙˙˙˙ť Ł ©«­®Ż°˛ł´¶·¸ą ş!!!!!!!!!!!!!!!!!»"""""""""""""""""""Ľ"Ľ###################˝$$$$$$$$$$$$$$$$$$$$$$$ľ%%%%%%%%%%%%%%%%%%%%%%%ż&&&&&&&&&&&&&&&&&&&&&&&&&Ŕ'''''''''''''''''''''''''''Á(((((((((((((((((((((((((((((Â(Â)))))))))))))))))))))))))))))Ă)Ă*********************************Ä*Ä+++++++++++++++++++++++++++++++++++Ĺ+Ĺ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,ŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓlÓlÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔmÔmÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐnŐnŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖoÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ×p××××××××××××××××××××××××××ŘqŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮrŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚsÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰtŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜuÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝvÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢwŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßxßßßßßßßßßßßßßßßßŕyŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááâ{ââââââââââââââăăăăăăăăăăăăăăä}ääääääääääääĺ~ĺĺĺĺĺĺĺĺĺĺĺĺćććććććććććććç€ççççççççççčččččččččččččééééééééééééęęęęęęęęęęęęëëëëëëëëëëëëěěěěěěěěěěí†ííííííííííîîîîîîîîîîďďďďďďďďďďđ‰đđđđđđđđńŠńńńńńńńńńńňňňňňňňňóŚóóóóóóóóôŤôôôôôôôôőŽőőőőőőőőöööööööööö÷÷÷÷÷÷÷÷ř‘řřřřřřřřůůůůůůůůůůúúúúúúúúűűűűűűűűűűüüüüüüüüýýýýýýýýýýţţţţţţţţ˙˙˙˙lo wy~€‚„…† ‡ ‡!!!!!!!!!!!!!!!!!"""""""""""""""""""‰#####################Š$$$$$$$$$$$$$$$$$$$$$‹$‹%%%%%%%%%%%%%%%%%%%%%%%Ś&&&&&&&&&&&&&&&&&&&&&&&&&Ť'''''''''''''''''''''''''Ž'Ž(((((((((((((((((((((((((((Ź(Ź)))))))))))))))))))))))))))))))*********************************‘*‘+++++++++++++++++++++++++++++++++++’+’,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,ŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓ Ó ÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔˇÔˇÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐ˘ŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖŁÖŁÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖפפ××××××××××××××××××××××××ŘĄŘĄŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘ٦ŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚ§Ú§ÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚۨۨŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰܩܩÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝŞÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢ«ŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢ߬ßßßßßßßßßßßßßßßßŕ­ŕŕŕŕŕŕŕŕŕŕŕŕŕŕá®ááááááááááááááááââââââââââââââă°ăăăăăăăăăăăăăăä±ääääääääääääĺ˛ĺĺĺĺĺĺĺĺĺĺĺĺćłććććććććććććççççççççççççčµččččččččččé¶ééééééééééę·ęęęęęęęęęęë¸ëëëëëëëëëëěąěěěěěěěěěěííííííííííî»îîîîîîîîďĽďďďďďďďďďďđđđđđđđđđđńńńńńńńńńńňňňňňňňňňňóóóóóóóóóóôôôôôôôôôôőőőőőőőőöööööööööö÷÷÷÷÷÷÷÷÷÷řřřřřřřřůůůůůůůůůůúúúúúúúúűűűűűűűűűűüüüüüüüüýýýýýýýýýýţţţţţţţţ˙˙˙˙: > ? @ABDFGHIJKNPQR S!!!!!!!!!!!!!!!!!!!T"""""""""""""""""""U#####################V$$$$$$$$$$$$$$$$$$$$$W%%%%%%%%%%%%%%%%%%%%%%%X%X&&&&&&&&&&&&&&&&&&&&&&&Y&Y'''''''''''''''''''''''''Z((((((((((((((((((((((((((((([)))))))))))))))))))))))))))))))\)\*******************************]*]+++++++++++++++++++++++++++++++++++^+^,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,_,_ŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááââââââââââââââââăăăăăăăăăăăăăăăăääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććççççççççççççççččččččččččččééééééééééééęęęęęęęęęęęęëëëëëëëëëëëëěěěěěěěěěěííííííííííííîîîîîîîîîîďďďďďďďďďďđđđđđđđđđđńńńńńńńńńńňňňňňňňňňňóóóóóóóóóóôôôôôôôôôôőőőőőőőőöööööööööö÷÷÷÷÷÷÷÷÷÷řřřřřřřřůůůůůůůůůůúúúúúúúúűűűűűűűűűűüüüüüüüüýýýýýýýýýýţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!""""""""""""""""""""######################$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''(((((((((((((((((((((((((((((())))))))))))))))))))))))))))))))**********************************++++++++++++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,----ŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááââââââââââââââââăăăăăăăăăăăăăăääääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććççççççççççççččččččččččččččééééééééééééęęęęęęęęęęëëëëëëëëëëëëěěěěěěěěěěěěííííííííííîîîîîîîîîîďďďďďďďďďďđđđđđđđđđđđđńńńńńńńńńńňňňňňňňňóóóóóóóóóóôôôôôôôôôôőőőőőőőőőőöööööööö÷÷÷÷÷÷÷÷÷÷řřřřřřřřřřůůůůůůůůúúúúúúúúúúűűűűűűűűüüüüüüüüýýýýýýýýýýţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!""""""""""""""""""""######################$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''(((((((((((((((((((((((((((((())))))))))))))))))))))))))))))))************************************++++++++++++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,--------ŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ× × ××××××××××××××××××××××××Ř Ř ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮ ŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚ ÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰ ŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕáááááááááááááááâââââââââââââââăăăăăăăăăăăăăäääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺćććććććććććććçççççççççççčččččččččččččééééééééééééęęęęęęęęęęëëëëëëëëëëëěěěěěěěěěěěííííííííííîîîîîîîîîîď!ďďďďďďďďđ"đđđđđđđđđđńńńńńńńńńńňňňňňňňňó%óóóóóóóóô&ôôôôôôôôőőőőőőőőőőöööööööö÷)÷÷÷÷÷÷÷÷řřřřřřřřřřůůůůůůůůúúúúúúúúúúűűűűűűűűüüüüüüüüý/ýýýýýýýýţţţţţţţţ˙˙˙˙ × ßáĺçčéęëěí î!!!!!!!!!!!!!!!!!!!ď"""""""""""""""""""đ###################ń#ń$$$$$$$$$$$$$$$$$$$$$ň%%%%%%%%%%%%%%%%%%%%%%%ó&&&&&&&&&&&&&&&&&&&&&&&ô&ô'''''''''''''''''''''''''ő(((((((((((((((((((((((((((((ö)))))))))))))))))))))))))))))))÷*********************************ř*ř+++++++++++++++++++++++++++++++++++ů+ů,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,ú,ú--------ŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓ9Ó9ÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔ:Ô:ÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐ;ŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖ<Ö<ÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ×=××××××××××××××××××××××××××Ř>ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮ?ŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚ@Ú@ÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰAŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜBÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝCÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢDŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßEßßßßßßßßßßßßßßßßŕFŕŕŕŕŕŕŕŕŕŕŕŕŕŕáGááááááááááááááâHââââââââââââââăIăăăăăăăăăăăăäJääääääääääääĺKĺĺĺĺĺĺĺĺĺĺĺĺćLććććććććććććççççççççççççčNččččččččččéOééééééééééęPęęęęęęęęęęëëëëëëëëëëëëěěěěěěěěěěíSííííííííîTîîîîîîîîîîďďďďďďďďďďđđđđđđđđđđńńńńńńńńńńňňňňňňňňňňóóóóóóóóóóôôôôôôôôő[őőőőőőőőöööööööööö÷÷÷÷÷÷÷÷řřřřřřřřřřůůůůůůůůúúúúúúúúúúűűűűűűűűüüüüüüüüüüýýýýýýýýţţţţţţţţ˙˙˙˙ž ˘ ¦ §¨©Ş¬®Ż°±˛ł´¶·¸ą ş!!!!!!!!!!!!!!!!!!!»"""""""""""""""""""Ľ###################˝$$$$$$$$$$$$$$$$$$$$$ľ$ľ%%%%%%%%%%%%%%%%%%%%%ż%ż&&&&&&&&&&&&&&&&&&&&&&&Ŕ'''''''''''''''''''''''''Á'Á(((((((((((((((((((((((((((Â(Â)))))))))))))))))))))))))))))Ă)Ă*******************************Ä*Ä+++++++++++++++++++++++++++++++++++Ĺ+Ĺ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,Ć,Ć------------ŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓlÓlÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔmÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐnŐnŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖoÖoÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ×p×p××××××××××××××××××××××××ŘqŘqŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮrŮrŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚsÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰtŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜuÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝvÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢwŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßxßßßßßßßßßßßßßßßßŕyŕŕŕŕŕŕŕŕŕŕŕŕŕŕázááááááááááááááâ{ââââââââââââââă|ăăăăăăăăăăăăä}ääääääääääääĺ~ĺĺĺĺĺĺĺĺĺĺĺĺćććććććććććç€ççççççççççççččččččččččččééééééééééééęęęęęęęęęęë„ëëëëëëëëëëě…ěěěěěěěěěěííííííííííîîîîîîîîîîďďďďďďďďďđ‰đđđđđđđđńŠńńńńńńńńň‹ňňňňňňňňóóóóóóóóóóôôôôôôôôôôőőőőőőőőöööööööööö÷÷÷÷÷÷÷÷ř‘řřřřřřřřůůůůůůůůúúúúúúúúúúűűűűűűűűüüüüüüüüüüýýýýýýýýţţţţţţţţ˙˙˙˙i q r xz‚„…† ‡!!!!!!!!!!!!!!!!!!!"""""""""""""""""‰"‰###################Š$$$$$$$$$$$$$$$$$$$$$‹%%%%%%%%%%%%%%%%%%%%%%%Ś&&&&&&&&&&&&&&&&&&&&&&&Ť&Ť'''''''''''''''''''''''''Ž(((((((((((((((((((((((((((((Ź))))))))))))))))))))))))))))))*******************************‘*‘+++++++++++++++++++++++++++++++++++’+’,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,“,“----------------ŇźŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓ Ó ÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔˇÔˇÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐ˘Ő˘ŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖŁÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖפ××××××××××××××××××××××××××ŘĄŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘ٦ŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚ§ÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚۨۨŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜ©ÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝŞÝŞÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢ«ŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢ߬ßßßßßßßßßßßßßßßßŕ­ŕŕŕŕŕŕŕŕŕŕŕŕŕŕá®ááááááááááááááâŻââââââââââââââăăăăăăăăăăăăăăä±ääääääääääääĺ˛ĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććç´ççççççççççčµččččččččččé¶ééééééééééę·ęęęęęęęęęęë¸ëëëëëëëëëëěěěěěěěěěěíşííííííííî»îîîîîîîîîîďďďďďďďďďďđđđđđđđđđđńńńńńńńńńńňňňňňňňňóŔóóóóóóóóôôôôôôôôôôőőőőőőőőöĂöööööööö÷÷÷÷÷÷÷÷÷÷řřřřřřřřůůůůůůůůúÇúúúúúúúúűűűűűűűűüüüüüüüüüüýýýýýýýýţţţţţţţţ˙˙˙˙ @ABEGHIJKLMNOPQR S!!!!!!!!!!!!!!!!!!!T"""""""""""""""""U#####################V$$$$$$$$$$$$$$$$$$$$$W%%%%%%%%%%%%%%%%%%%%%%%X&&&&&&&&&&&&&&&&&&&&&&&Y'''''''''''''''''''''''''''Z((((((((((((((((((((((((((([([)))))))))))))))))))))))))))))\*********************************]+++++++++++++++++++++++++++++++++++^+^,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,_,_--------------------ŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááââââââââââââââăăăăăăăăăăăăăăăăääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććççççççççççççččččččččččččééééééééééééęęęęęęęęęęęęëëëëëëëëëëěěěěěěěěěěěěííííííííííîîîîîîîîîîďďďďďďďďďďđđđđđđđđđđńńńńńńńńńńňňňňňňňňňňóóóóóóóóôôôôôôôôôôőőőőőőőőőőöööööööö÷÷÷÷÷÷÷÷÷÷řřřřřřřřůůůůůůůůůůúúúúúúúúűűűűűűűűüüüüüüüüüüýýýýýýýýţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!!!""""""""""""""""""######################$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''(((((((((((((((((((((((((((())))))))))))))))))))))))))))))))**********************************++++++++++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,------------------------ŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááââââââââââââââăăăăăăăăăăăăăăääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććççççççççççççççččččččččččččééééééééééęęęęęęęęęęęęëëëëëëëëëëëëěěěěěěěěěěííííííííííîîîîîîîîîîďďďďďďďďďďđđđđđđđđđđńńńńńńńńńńňňňňňňňňňňóóóóóóóóóóôôôôôôôôőőőőőőőőőőöööööööö÷÷÷÷÷÷÷÷÷÷řřřřřřřřůůůůůůůůůůúúúúúúúúűűűűűűűűüüüüüüüüüüýýýýýýýýţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!""""""""""""""""""""######################$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''(((((((((((((((((((((((((((())))))))))))))))))))))))))))))))**********************************++++++++++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,----------------------------ŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ× ××××××××××××××××××××××××××Ř ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮ ŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚ ÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰ ŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕáááááááááááááááâââââââââââââăăăăăăăăăăăăăäääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺćććććććććććçççççççççççççččččččččččččééééééééééęęęęęęęęęęęëëëëëëëëëëëěěěěěěěěěěííííííííííî îîîîîîîîď!ďďďďďďďďđ"đđđđđđđđń#ńńńńńńńńň$ňňňňňňňňóóóóóóóóóóôôôôôôôôő'őőőőőőőőöööööööö÷)÷÷÷÷÷÷÷÷řřřřřřřřůůůůůůůůůůúúúúúúúúűűűűűűűűü.üüüüüüüüýýýýýýýýţţţţţţţţ˙˙˙˙ Ů ŕâćčéëěí î!!!!!!!!!!!!!!!!!ď"""""""""""""""""""đ###################ń#ń$$$$$$$$$$$$$$$$$$$ň%%%%%%%%%%%%%%%%%%%%%%%ó&&&&&&&&&&&&&&&&&&&&&&&ô&ô'''''''''''''''''''''''''ő(((((((((((((((((((((((((((ö)))))))))))))))))))))))))))))÷)÷*******************************ř*ř+++++++++++++++++++++++++++++++++ů+ů,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,ú----------------------------ŃŃŃŃŃŃŃŃŃŃŇ8Ň8Ň8ŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓ9ÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔ:Ô:ÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐ;Ő;ŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖ<Ö<ÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ×=×=××××××××××××××××××××××××Ř>Ř>ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮ?ŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚ@ÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰAŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜBÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝCÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢDŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßEßßßßßßßßßßßßßßßßŕFŕŕŕŕŕŕŕŕŕŕŕŕŕŕáGááááááááááááááâHââââââââââââăIăăăăăăăăăăăăäJääääääääääääĺKĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććçMççççççççççčNččččččččččéOééééééééééęPęęęęęęęęęęëëëëëëëëëëěRěěěěěěěěíSííííííííííîîîîîîîîîîďďďďďďďďďďđđđđđđđđđđńńńńńńńńńńňňňňňňňňóYóóóóóóóóôôôôôôôôôôőőőőőőőőöööööööööö÷÷÷÷÷÷÷÷řřřřřřřřů_ůůůůůůůůúúúúúúúúűűűűűűűűűűüüüüüüüüýýýýýýýýţţţţţţţţ˙˙˙˙˘ ¤ §¨©Ş«­Ż°±˛ł´µ¶·¸ą ş!!!!!!!!!!!!!!!!!»"""""""""""""""""""Ľ###################˝$$$$$$$$$$$$$$$$$$$$$ľ%%%%%%%%%%%%%%%%%%%%%ż%ż&&&&&&&&&&&&&&&&&&&&&&&Ŕ'''''''''''''''''''''''''Á'Á(((((((((((((((((((((((((Â(Â)))))))))))))))))))))))))))))Ă*******************************Ä*Ä+++++++++++++++++++++++++++++++++++Ĺ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,Ć,Ć------------------------------ŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇkŇkŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓlÓlÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔmÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐnŐnŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖoÖoÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ×p××××××××××××××××××××××××××ŘqŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮrŮrŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚsÚsÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰtŰtŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜuÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝvÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢwŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßxßßßßßßßßßßßßßßßßŕyŕŕŕŕŕŕŕŕŕŕŕŕŕŕázááááááááááááááââââââââââââââă|ăăăăăăăăăăăăä}ääääääääääääĺ~ĺĺĺĺĺĺĺĺĺĺćććććććććććććççççççççççççčččččččččččééééééééééééęęęęęęęęęęë„ëëëëëëëëëëěěěěěěěěěěííííííííííî‡îîîîîîîîďďďďďďďďďđ‰đđđđđđđđńńńńńńńńńńňňňňňňňňňňóóóóóóóóôŤôôôôôôôôőőőőőőőőöŹöööööööö÷÷÷÷÷÷÷÷řřřřřřřřřřůůůůůůůůúúúúúúúúűűűűűűűűűűüüüüüüüüýýýýýýýýţţţţţţţţ˙˙˙˙jm s yz{}~€„…† ‡!!!!!!!!!!!!!!!!!"""""""""""""""""""‰###################Š$$$$$$$$$$$$$$$$$$$$$‹%%%%%%%%%%%%%%%%%%%%%Ś&&&&&&&&&&&&&&&&&&&&&&&Ť&Ť'''''''''''''''''''''''''Ž(((((((((((((((((((((((((((Ź))))))))))))))))))))))))))))))*******************************‘+++++++++++++++++++++++++++++++++++’+’,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,“,“----------------------------------ŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃҟҟŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓ Ó ÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔˇÔˇÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐ˘ŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖŁÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖפפ××××××××××××××××××××××××ŘĄŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘ٦ŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚ§ÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚۨŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜ©ÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝŞÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢ«ŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢ߬ßßßßßßßßßßßßßßßßŕ­ŕŕŕŕŕŕŕŕŕŕŕŕŕŕá®ááááááááááááâŻââââââââââââââă°ăăăăăăăăăăăăä±ääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺćłććććććććććç´ççççççççççççččččččččččé¶ééééééééééę·ęęęęęęęęęęëëëëëëëëëëěąěěěěěěěěíşííííííííííîîîîîîîîîîďďďďďďďďďďđđđđđđđđńľńńńńńńńńňżňňňňňňňňóóóóóóóóóóôôôôôôôôőőőőőőőőőőöööööööö÷÷÷÷÷÷÷÷řĹřřřřřřřřůůůůůůůůúúúúúúúúűČűűűűűűűűüüüüüüüüýýýýýýýýţţţţţţţţ˙˙˙˙ < > BCDGHKLMNOPQR S!!!!!!!!!!!!!!!!!T"""""""""""""""""""U###################V$$$$$$$$$$$$$$$$$$$W$W%%%%%%%%%%%%%%%%%%%%%X&&&&&&&&&&&&&&&&&&&&&&&Y'''''''''''''''''''''''''Z'Z((((((((((((((((((((((((([([)))))))))))))))))))))))))))\)\*******************************]*]+++++++++++++++++++++++++++++++++^+^,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,_,_--------------------------------------ŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááââââââââââââââââăăăăăăăăăăăăăăääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććççççççççççççččččččččččččééééééééééééęęęęęęęęęęëëëëëëëëëëëëěěěěěěěěěěííííííííííîîîîîîîîîîďďďďďďďďďďđđđđđđđđđđńńńńńńńńńńňňňňňňňňóóóóóóóóóóôôôôôôôôőőőőőőőőőőöööööööö÷÷÷÷÷÷÷÷÷÷řřřřřřřřůůůůůůůůúúúúúúúúúúűűűűűűűűüüüüüüüüýýýýýýýýţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!""""""""""""""""""""####################$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''(((((((((((((((((((((((((((())))))))))))))))))))))))))))))**********************************++++++++++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,------------------------------------------ŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááââââââââââââââăăăăăăăăăăăăăăääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććççççççççççççččččččččččččééééééééééęęęęęęęęęęęęëëëëëëëëëëěěěěěěěěěěííííííííííííîîîîîîîîîîďďďďďďďďđđđđđđđđđđńńńńńńńńńńňňňňňňňňňňóóóóóóóóôôôôôôôôôôőőőőőőőőöööööööö÷÷÷÷÷÷÷÷÷÷řřřřřřřřůůůůůůůůúúúúúúúúúúűűűűűűűűüüüüüüüüýýýýýýýýţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!""""""""""""""""""####################$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''(((((((((((((((((((((((((((())))))))))))))))))))))))))))))********************************++++++++++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,------------------------------------------....ŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ× × ××××××××××××××××××××××××Ř ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮ Ů ŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚ ÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰ ŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááâââââââââââââăăăăăăăăăăăăăäääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććççççççççççççččččččččččččééééééééééęęęęęęęęęęęëëëëëëëëëëěěěěěěěěěíííííííííííîîîîîîîîîîďďďďďďďďđ"đđđđđđđđń#ńńńńńńńńňňňňňňňňňňóóóóóóóóôôôôôôôôôôőőőőőőőőöööööööö÷)÷÷÷÷÷÷÷÷řřřřřřřřůůůůůůůůúúúúúúúúúúűűűűűűűűüüüüüüüüýýýýýýýýţţţţţţţţ˙˙˙˙Ö Ř Ú Ţßâĺçčéęëěí î!!!!!!!!!!!!!!!!!ď"""""""""""""""""đ###################ń$$$$$$$$$$$$$$$$$$$$$ň%%%%%%%%%%%%%%%%%%%%%ó&&&&&&&&&&&&&&&&&&&&&&&ô&ô'''''''''''''''''''''''ő'ő(((((((((((((((((((((((((ö(ö)))))))))))))))))))))))))))÷)÷*****************************ř*ř+++++++++++++++++++++++++++++++++ů+ů,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,ú,ú---------------------------------------ű-ű....ŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇ8Ň8ŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓ9Ó9ÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔ:Ô:ÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐ;Ő;ŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖ<Ö<ÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ×=××××××××××××××××××××××××Ř>Ř>ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮ?ŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚ@Ú@ÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰAŰAŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜBÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝCÝCÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢDŢŢŢŢŢŢŢŢŢŢŢŢŢŢßEßßßßßßßßßßßßßßßßŕFŕŕŕŕŕŕŕŕŕŕŕŕáGááááááááááááááâHââââââââââââăIăăăăăăăăăăăăäJääääääääääääĺKĺĺĺĺĺĺĺĺĺĺćLććććććććććçMççççççççççčNččččččččččéOééééééééééęęęęęęęęęęëQëëëëëëëëëëěěěěěěěěěěííííííííííîîîîîîîîîîďďďďďďďďďďđđđđđđđđđđńńńńńńńńňXňňňňňňňňóóóóóóóóôZôôôôôôôôőőőőőőőőö\öööööööö÷÷÷÷÷÷÷÷řřřřřřřřůůůůůůůůú`úúúúúúúúűűűűűűűűüüüüüüüüýýýýýýýýţţţţţţţţ˙˙˙˙ź ¨©¬­Ż°˛łµ¶·¸ą ş!!!!!!!!!!!!!!!!!»"""""""""""""""""Ľ###################˝$$$$$$$$$$$$$$$$$$$$$ľ%%%%%%%%%%%%%%%%%%%%%ż&&&&&&&&&&&&&&&&&&&&&&&Ŕ'''''''''''''''''''''''''Á(((((((((((((((((((((((((Â(Â)))))))))))))))))))))))))))Ă)Ă*******************************Ä+++++++++++++++++++++++++++++++++Ĺ+Ĺ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,Ć,Ć---------------------------------------Ç-Ç........ŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇkŇkŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓlÓlÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔmÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐnŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖoÖoÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ×p×p××××××××××××××××××××××××ŘqŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮrŮrŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚsÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰtŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜuÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝvÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢwŢŢŢŢŢŢŢŢŢŢŢŢŢŢßxßßßßßßßßßßßßßßßßŕyŕŕŕŕŕŕŕŕŕŕŕŕázááááááááááááááâ{ââââââââââââă|ăăăăăăăăăăăăä}ääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺćććććććććććç€ççççççççççčččččččččččééééééééééęęęęęęęęęęęëëëëëëëëëëě…ěěěěěěěěí†ííííííííî‡îîîîîîîîďďďďďďďďďđ‰đđđđđđđđńńńńńńńńńńňňňňňňňňóóóóóóóóóóôôôôôôôôőőőőőőőőőőöööööööö÷÷÷÷÷÷÷÷řřřřřřřřů’ůůůůůůůůúúúúúúúúűűűűűűűűüüüüüüüüýýýýýýýýţţţţţţţţ˙˙˙˙ p r t{}~€„…† ‡!!!!!!!!!!!!!!!!!"""""""""""""""""‰###################Š$$$$$$$$$$$$$$$$$$$‹$‹%%%%%%%%%%%%%%%%%%%Ś%Ś&&&&&&&&&&&&&&&&&&&&&Ť&Ť'''''''''''''''''''''''Ž'Ž(((((((((((((((((((((((((Ź)))))))))))))))))))))))))))))*******************************‘*‘+++++++++++++++++++++++++++++++’+’,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,“,“---------------------------------------”-”............ŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃҟҟŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓ Ó ÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔˇÔˇÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐ˘Ő˘ŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖŁÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖפפ××××××××××××××××××××××ŘĄŘĄŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘ٦ŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚ§ÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚۨŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜ©ÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝŞÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢ«ŢŢŢŢŢŢŢŢŢŢŢŢŢŢ߬ßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕá®ááááááááááááááâŻââââââââââââă°ăăăăăăăăăăăăä±ääääääääääĺ˛ĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććç´ççççççççççččččččččččé¶ééééééééééę·ęęęęęęęęë¸ëëëëëëëëëëěěěěěěěěěěííííííííííîîîîîîîîîîďďďďďďďďďďđđđđđđđđńľńńńńńńńńňňňňňňňňóŔóóóóóóóóôôôôôôôôőÂőőőőőőőőöööööööö÷÷÷÷÷÷÷÷řĹřřřřřřřřůůůůůůůůúúúúúúúúűűűűűűűűüüüüüüüüýýýýýýýýţţţţţţţţ˙˙˙˙9 ? BCDEFHJKMNOPR S!!!!!!!!!!!!!!!!!T"""""""""""""""""U###################V$$$$$$$$$$$$$$$$$$$W%%%%%%%%%%%%%%%%%%%%%X&&&&&&&&&&&&&&&&&&&&&&&Y'''''''''''''''''''''''''Z((((((((((((((((((((((((([([)))))))))))))))))))))))))))\)\*****************************]*]+++++++++++++++++++++++++++++++++^,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,_,_---------------------------------------`-`................ŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááââââââââââââââăăăăăăăăăăăăăăääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććććççççççççççččččččččččččééééééééééééęęęęęęęęęęëëëëëëëëëëěěěěěěěěěěííííííííííîîîîîîîîîîďďďďďďďďďďđđđđđđđđđđńńńńńńńńňňňňňňňňňňóóóóóóóóôôôôôôôôôôőőőőőőőőöööööööö÷÷÷÷÷÷÷÷÷÷řřřřřřřřůůůůůůůůúúúúúúúúűűűűűűűűüüüüüüüüýýýýýýýýţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!!!""""""""""""""""""####################$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''(((((((((((((((((((((((((())))))))))))))))))))))))))))))********************************++++++++++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,------------------------------------------....................ĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááááââââââââââââââăăăăăăăăăăăăääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććççççççççççççččččččččččččééééééééééęęęęęęęęęęęęëëëëëëëëëëěěěěěěěěěěííííííííííîîîîîîîîîîďďďďďďďďđđđđđđđđđđńńńńńńńńńńňňňňňňňňóóóóóóóóóóôôôôôôôôőőőőőőőőöööööööö÷÷÷÷÷÷÷÷÷÷řřřřřřřřůůůůůůůůúúúúúúúúűűűűűűűűüüüüüüüüýýýýýýýýţţţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!""""""""""""""""""""##################$$$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''(((((((((((((((((((((((((((())))))))))))))))))))))))))))********************************++++++++++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,------------------------------------------........................ĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ× × ××××××××××××××××××××××Ř Ř ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮ Ů ŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚ ÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰ Ű ŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕáááááááááááááááââââââââââââââăăăăăăăăăăăăäääääääääääääĺĺĺĺĺĺĺĺĺĺĺćććććććććććçççççççççççčččččččččččééééééééééęęęęęęęęęęęëëëëëëëëëëěěěěěěěěěěííííííííííîîîîîîîîîîďďďďďďďďđ"đđđđđđđđńńńńńńńńńńňňňňňňňňóóóóóóóóóóôôôôôôôôőőőőőőőőöööööööö÷)÷÷÷÷÷÷÷÷řřřřřřřřůůůůůůůůúúúúúúúúűűűűűűűűüüüüüüüüýýýýýýýýţţţţţţţţ˙˙˙˙Ö Ů ŰŢßŕáâäĺćçéęëěí î!!!!!!!!!!!!!!!ď"""""""""""""""""""đ#################ń$$$$$$$$$$$$$$$$$$$ň$ň%%%%%%%%%%%%%%%%%%%ó%ó&&&&&&&&&&&&&&&&&&&&&ô&ô'''''''''''''''''''''''ő(((((((((((((((((((((((((ö(ö)))))))))))))))))))))))))))÷*******************************ř+++++++++++++++++++++++++++++++++ů+ů,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,ú,ú---------------------------------------ű-ű........................ĐĐĐĐĐĐĐĐŃ7Ń7ŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇ8Ň8ŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓ9Ó9ÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔ:Ô:ÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐ;Ő;ŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖ<ÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ×=××××××××××××××××××××××××Ř>ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮ?ŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚ@ÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰAŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜBÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝCÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢDŢŢŢŢŢŢŢŢŢŢŢŢŢŢßEßßßßßßßßßßßßßßŕFŕŕŕŕŕŕŕŕŕŕŕŕŕŕáGááááááááááááâHââââââââââââăIăăăăăăăăăăăăäJääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺćLććććććććććçMççççççççççččččččččččéOééééééééééęęęęęęęęęęëQëëëëëëëëěRěěěěěěěěíSííííííííîTîîîîîîîîďďďďďďďďďďđđđđđđđđńWńńńńńńńńňňňňňňňňóYóóóóóóóóôôôôôôôôőőőőőőőőö\öööööööö÷÷÷÷÷÷÷÷řřřřřřřřůůůůůůůůúúúúúúúúűűűűűűűűüüüüüüüüýýýýýýýýţţţţţţţţ˙˙˙˙š Ł ¦ ¨©Ż±˛ł´µ¶·¸ą ş!!!!!!!!!!!!!!!»"""""""""""""""""Ľ"Ľ#################˝$$$$$$$$$$$$$$$$$$$ľ%%%%%%%%%%%%%%%%%%%%%ż&&&&&&&&&&&&&&&&&&&&&&&Ŕ'''''''''''''''''''''''Á'Á(((((((((((((((((((((((((Â)))))))))))))))))))))))))))Ă)Ă*****************************Ä*Ä+++++++++++++++++++++++++++++++Ĺ+Ĺ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,Ć,Ć-------------------------------------Ç-Ç-Ç............................ĐĐĐĐĐĐĐĐĐĐĐĐŃjŃjŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇkŇkŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓlÓlÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔmÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐnŐnŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖoÖoÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ×p×p××××××××××××××××××××××ŘqŘqŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮrŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚsÚsÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰtŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜuÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝvÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢwŢŢŢŢŢŢŢŢŢŢŢŢŢŢßxßßßßßßßßßßßßßßŕyŕŕŕŕŕŕŕŕŕŕŕŕŕŕázááááááááááááâ{ââââââââââââă|ăăăăăăăăăăăăä}ääääääääääĺ~ĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććççççççççççčččččččččččé‚ééééééééęęęęęęęęęęęëëëëëëëëëëěěěěěěěěěěííííííííííîîîîîîîîďďďďďďďďďđ‰đđđđđđđđńńńńńńńńň‹ňňňňňňňňóóóóóóóóôôôôôôôôőŽőőőőőőőőöööööööö÷÷÷÷÷÷÷÷řřřřřřřřůůůůůůůůúúúúúúúúűűűűűűűűüüüüüüüüýýýýýýýýţţţţţţţţ˙˙˙˙hij q wxyz{}‚„…† ‡!!!!!!!!!!!!!!!"""""""""""""""""‰###################Š$$$$$$$$$$$$$$$$$$$‹%%%%%%%%%%%%%%%%%%%%%Ś&&&&&&&&&&&&&&&&&&&&&Ť&Ť'''''''''''''''''''''''Ž(((((((((((((((((((((((((Ź(Ź))))))))))))))))))))))))))*****************************‘*‘+++++++++++++++++++++++++++++++’+’,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,“,“-------------------------------------”-”..................................ĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐўўŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃҟҟŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓ Ó ÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔˇÔˇÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐ˘ŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖŁÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖפ××××××××××××××××××××××××ŘĄŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘ٦٦ŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚ§ÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚۨŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜ©ÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝŞÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢ«ŢŢŢŢŢŢŢŢŢŢŢŢŢŢ߬ßßßßßßßßßßßßßßŕ­ŕŕŕŕŕŕŕŕŕŕŕŕŕŕá®ááááááááááááâŻââââââââââââă°ăăăăăăăăăăăăä±ääääääääääĺ˛ĺĺĺĺĺĺĺĺĺĺćłććććććććććç´ççççççççççčµččččččččččééééééééééę·ęęęęęęęęë¸ëëëëëëëëěąěěěěěěěěíşííííííííî»îîîîîîîîďďďďďďďďďďđđđđđđđđńľńńńńńńńńňňňňňňňňóóóóóóóóôÁôôôôôôôôőőőőőőőőöööööööö÷÷÷÷÷÷÷÷řřřřřřřřůůůůůůůůúÇúúúúúúűČűűűűűűüÉüüüüüüýĘýýýýýýţËţţţţţţ˙˙˙˙78 > @BHJKLMOPQR S!!!!!!!!!!!!!!!T"""""""""""""""""U###################V$$$$$$$$$$$$$$$$$$$W%%%%%%%%%%%%%%%%%%%X%X&&&&&&&&&&&&&&&&&&&&&Y'''''''''''''''''''''''Z'Z((((((((((((((((((((((([([)))))))))))))))))))))))))))\*****************************]*]+++++++++++++++++++++++++++++++^+^,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,_,_-------------------------------------`-`......................................ĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááââââââââââââââăăăăăăăăăăăăăăääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććççççççççççççččččččččččééééééééééééęęęęęęęęęęëëëëëëëëëëěěěěěěěěěěííííííííííîîîîîîîîďďďďďďďďďďđđđđđđđđđđńńńńńńńńňňňňňňňňóóóóóóóóóóôôôôôôôôőőőőőőőőöööööööö÷÷÷÷÷÷÷÷řřřřřřřřůůůůůůůůůůúúúúúúúúűűűűűűűűüüüüüüüüýýýýýýýýţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!""""""""""""""""""####################$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''(((((((((((((((((((((((((())))))))))))))))))))))))))))))******************************++++++++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,----------------------------------------..........................................ĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááââââââââââââââăăăăăăăăăăăăääääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććççççççççççččččččččččččééééééééééęęęęęęęęęęëëëëëëëëëëěěěěěěěěěěííííííííííîîîîîîîîîîďďďďďďďďđđđđđđđđđđńńńńńńńńňňňňňňňňňňóóóóóóóóôôôôôôôôőőőőőőőőöööööööö÷÷÷÷÷÷÷÷÷÷řřřřřřřřůůůůůůůůúúúúúúúúűűűűűűűűüüüüüüüüýýýýýýýýţţţţţţ˙˙˙˙  !!!!!!!!!!!!!!!!""""""""""""""""""##################$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''(((((((((((((((((((((((((())))))))))))))))))))))))))))********************************++++++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,----------------------------------------..............................................ĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ× ××××××××××××××××××××××Ř Ř ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮ Ů ŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚ Ú ÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰ ŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕŕáááááááááááááâââââââââââââăăăăăăăăăăăäääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććççççççççççčččččččččččééééééééééęęęęęęęęęëëëëëëëëëěěěěěěěěěíííííííííî îîîîîîîîďďďďďďďďđ"đđđđđđđđńńńńńńńńň$ňňňňňňňňóóóóóóóóôôôôôôôôőőőőőőőőö(öööööö÷)÷÷÷÷÷÷÷÷řřřřřřřřůůůůůůůůúúúúúúúúűűűűűűűűüüüüüüüüýýýýýýýýţţţţţţ˙1˙˙Ö × ÝŢäçéęëěí î î!!!!!!!!!!!!!!!ď"""""""""""""""""đ#################ń$$$$$$$$$$$$$$$$$$$ň%%%%%%%%%%%%%%%%%%%%%ó&&&&&&&&&&&&&&&&&&&&&ô'''''''''''''''''''''''ő'ő(((((((((((((((((((((((ö(ö)))))))))))))))))))))))))÷)÷*****************************ř*ř+++++++++++++++++++++++++++++++ů,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,ú,ú-------------------------------------ű-ű...........................................ü.üĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃ7Ń7ŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇ8Ň8ŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓ9Ó9ÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔ:Ô:ÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐ;Ő;ŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖ<ÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ×=×=××××××××××××××××××××××Ř>ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮ?ŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚ@ÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰAŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜBÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝCÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢDŢŢŢŢŢŢŢŢŢŢŢŢŢŢßEßEßßßßßßßßßßßßŕFŕŕŕŕŕŕŕŕŕŕŕŕŕŕáGááááááááááááâHââââââââââââăăăăăăăăăăăăäJääääääääääĺKĺĺĺĺĺĺĺĺĺĺćLććććććććććçMççççççççççčNččččččččéOééééééééééęęęęęęęęęęëëëëëëëëëëěěěěěěěěěěííííííííííîîîîîîîîďUďďďďďďďďđđđđđđđđńWńńńńńńńńňňňňňňňňóóóóóóóóôZôôôôôôő[őőőőőőőőöööööööö÷÷÷÷÷÷÷÷řřřřřřřřůůůůůůůůúúúúúúúúűűűűűűűűüüüüüüüüýýýýýýýýţţţţţţţţ˙˙ ¤ §«¬­®Ż±˛´µ¶·¸ą ş!!!!!!!!!!!!!!!!!»"""""""""""""""""Ľ#################˝$$$$$$$$$$$$$$$$$$$ľ%%%%%%%%%%%%%%%%%%%ż%ż&&&&&&&&&&&&&&&&&&&&&Ŕ'''''''''''''''''''''''Á(((((((((((((((((((((((((Â)))))))))))))))))))))))))))Ă*****************************Ä*Ä+++++++++++++++++++++++++++++++Ĺ+Ĺ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,Ć,Ć-------------------------------------Ç-Ç.........................................Č.Č.Č////ĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃjŃjŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇkŇkŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓlÓlÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔmÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐnŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖoÖoÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ×p××××××××××××××××××××××ŘqŘqŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮrŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚsÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰtŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜuÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝvÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢwŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßxßßßßßßßßßßßßŕyŕŕŕŕŕŕŕŕŕŕŕŕŕŕázááááááááááááâ{ââââââââââă|ăăăăăăăăăăăăä}ääääääääääĺ~ĺĺĺĺĺĺĺĺĺĺćććććććććććç€ççççççççççččččččččččé‚ééééééééęęęęęęęęęë„ëëëëëëëëě…ěěěěěěěěí†ííííííííîîîîîîîîîîďďďďďďďďđ‰đđđđđđđđńńńńńńńńňňňňňňňňóŚóóóóóóóóôôôôôôôôőőőőőőőőöööööööö÷÷÷÷÷÷÷÷řřřřřřřřůůůůůůůůúúúúúúúúűűűűűűűűüüüüüüüüýýýýýýýýţţţţţţţţ˙˙ r s uw}€‚„…† ‡!!!!!!!!!!!!!!!!!"""""""""""""""‰"‰#################Š$$$$$$$$$$$$$$$$$$$‹%%%%%%%%%%%%%%%%%%%Ś&&&&&&&&&&&&&&&&&&&&&Ť&Ť'''''''''''''''''''''Ž'Ž(((((((((((((((((((((((Ź(Ź))))))))))))))))))))))))))***************************‘*‘+++++++++++++++++++++++++++++++’+’,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,“,“-------------------------------------”-”.........................................•.•//////////ĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐўўŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃҟҟŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓ Ó ÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔˇÔˇÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐ˘Ő˘ŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖŁÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖפפ××××××××××××××××××××××ŘĄŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘ٦٦ŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚ§ÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚۨۨŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜ©ÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝŞÝÝÝÝÝÝÝÝÝÝÝÝÝÝޫޫŢŢŢŢŢŢŢŢŢŢŢŢŢŢ߬ßßßßßßßßßßßßŕ­ŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááââââââââââââă°ăăăăăăăăăăăăä±ääääääääääĺ˛ĺĺĺĺĺĺĺĺĺĺćłććććććććććççççççççççčµččččččččččééééééééééęęęęęęęęęęëëëëëëëëëëěěěěěěěěěěííííííííî»îîîîîîîîďďďďďďďďďďđđđđđđđđńńńńńńńńňżňňňňňňňňóóóóóóóóôôôôôôôôőőőőőőőőöööööööö÷÷÷÷÷÷÷÷řřřřřřřřůůůůůůůůúúúúúúúúűűűűűűűűüüüüüüüüýýýýýýýýţţţţţţţţ˙˙ @BDEFGHIJKLMOPQ S!!!!!!!!!!!!!!!!!T"""""""""""""""U#################V#V$$$$$$$$$$$$$$$$$W$W%%%%%%%%%%%%%%%%%%%X&&&&&&&&&&&&&&&&&&&&&Y'''''''''''''''''''''''Z((((((((((((((((((((((((([)))))))))))))))))))))))))\)\*****************************]+++++++++++++++++++++++++++++++^+^,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,_,_-------------------------------------`-`.........................................a.a//////////////ĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááââââââââââââââăăăăăăăăăăăăăăääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺććććććććććççççççççççççččččččččččééééééééééęęęęęęęęęęëëëëëëëëëëěěěěěěěěěěííííííííííîîîîîîîîďďďďďďďďďďđđđđđđđđńńńńńńńńńńňňňňňňňňóóóóóóóóôôôôôôôôőőőőőőőőöööööööö÷÷÷÷÷÷÷÷řřřřřřřřůůůůůůůůúúúúúúúúűűűűűűűűüüüüüüüüýýýýýýýýţţţţţţţţ˙˙  !!!!!!!!!!!!!!!!!!""""""""""""""""##################$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''(((((((((((((((((((((((((())))))))))))))))))))))))))********************************++++++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,----------------------------------------............................................//////////////////ĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááââââââââââââââăăăăăăăăăăăăääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććççççççççççččččččččččččééééééééééęęęęęęęęęęëëëëëëëëëëěěěěěěěěěěííííííííîîîîîîîîîîďďďďďďďďđđđđđđđđđđńńńńńńńńňňňňňňňňóóóóóóóóôôôôôôôôőőőőőőőőőőöööööööö÷÷÷÷÷÷řřřřřřřřůůůůůůůůúúúúúúúúűűűűűűűűüüüüüüüüýýýýýýýýţţţţţţţţ˙˙  !!!!!!!!!!!!!!!!""""""""""""""""""##################$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''(((((((((((((((((((((((((())))))))))))))))))))))))))))******************************++++++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,----------------------------------------............................................//////////////////////ĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ× × ××××××××××××××××××××××Ř ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮ ŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚ ÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰ ŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕáááááááááááááâââââââââââââăăăăăăăăăăăäääääääääääĺĺĺĺĺĺĺĺĺĺĺćććććććććććçççççççççčččččččččččééééééééééęęęęęęęęęëëëëëëëëëěěěěěěěěěěííííííííî îîîîîîîîďďďďďďďďđ"đđđđđđđđńńńńńńńńňňňňňňňňó%óóóóóóô&ôôôôôôő'őőőőőőőőöööööööö÷÷÷÷÷÷ř*řřřřřřů+ůůůůůůúúúúúúúúűűűűűűűűüüüüüüüüýýýýýýýýţţţţţţţţ˙˙ÓÔ Ř Ů Ýßŕăäćçčéęëěí î!!!!!!!!!!!!!!!ď"""""""""""""""""đ#################ń$$$$$$$$$$$$$$$$$$$ň%%%%%%%%%%%%%%%%%%%ó&&&&&&&&&&&&&&&&&&&&&ô'''''''''''''''''''''''ő(((((((((((((((((((((((ö(ö)))))))))))))))))))))))))÷)÷***************************ř*ř+++++++++++++++++++++++++++++ů+ů,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,ú,ú-------------------------------------ű-ű.......................................ü.ü.ü//////////////////////ĎĎĎĎĎĎĐ6Đ6ĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃ7Ń7ŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇ8Ň8ŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓ9Ó9ÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔ:Ô:ÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐ;Ő;ŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖ<ÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ×=××××××××××××××××××××××Ř>Ř>ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮ?Ů?ŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚ@ÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰAŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜBÜBÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝCÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢDŢŢŢŢŢŢŢŢŢŢŢŢŢŢßEßßßßßßßßßßßßŕFŕŕŕŕŕŕŕŕŕŕŕŕáGááááááááááááâHââââââââââââăIăăăăăăăăăăäJääääääääääĺKĺĺĺĺĺĺĺĺĺĺćLććććććććććççççççççççčNččččččččéOééééééééééęęęęęęęęęęëëëëëëëëěRěěěěěěěěíSííííííííîîîîîîîîďUďďďďďďďďđđđđđđđđńńńńńńńńňXňňňňňňňňóóóóóóóóôôôôôôôôőőőőőőőőöööööööö÷÷÷÷÷÷÷÷řřřřřřřřůůůůůůú`úúúúúúűűűűűűűűüüüüüüüüýýýýýýýýţţţţţţţţ˙˙ž ¦ §Ş­®Ż±˛ł´µ·¸ą ş!!!!!!!!!!!!!!!»"""""""""""""""""Ľ#################˝$$$$$$$$$$$$$$$$$ľ$ľ%%%%%%%%%%%%%%%%%%%ż&&&&&&&&&&&&&&&&&&&&&Ŕ'''''''''''''''''''''Á'Á(((((((((((((((((((((((Â)))))))))))))))))))))))))Ă)Ă***************************Ä*Ä+++++++++++++++++++++++++++++Ĺ+Ĺ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,Ć,Ć-----------------------------------Ç-Ç-Ç.......................................Č.Č////////////////////////////ĎĎĎĎĎĎĎĎĎĎĐiĐiĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃjŃjŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇkŇkŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓlÓlÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔmÔmÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐnŐnŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖoÖoÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ×p×p××××××××××××××××××××××ŘqŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮrŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚsÚsÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰtŰtŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜuÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝvÝvÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢwŢŢŢŢŢŢŢŢŢŢŢŢŢŢßxßßßßßßßßßßßßŕyŕŕŕŕŕŕŕŕŕŕŕŕázááááááááááááâ{ââââââââââââăăăăăăăăăăăăä}ääääääääääĺ~ĺĺĺĺĺĺĺĺĺĺććććććććććç€ççççççççççččččččččččé‚ééééééééęęęęęęęęęë„ëëëëëëëëěěěěěěěěěěííííííííî‡îîîîîîîîďďďďďďďďđ‰đđđđđđńŠńńńńńńńńňňňňňňňňóóóóóóóóôôôôôôôôőőőőőőőőöööööööö÷÷÷÷÷÷÷÷řřřřřřřřůůůůůůůůúúúúúúű”űűűűűűüüüüüüüüýýýýýýýýţţţţţţţţ˙˙ uy}~€‚„…† ‡!!!!!!!!!!!!!!!"""""""""""""""""‰#################Š$$$$$$$$$$$$$$$$$‹%%%%%%%%%%%%%%%%%%%Ś%Ś&&&&&&&&&&&&&&&&&&&Ť&Ť'''''''''''''''''''''Ž(((((((((((((((((((((((Ź(Ź)))))))))))))))))))))))))***************************‘*‘+++++++++++++++++++++++++++++’+’,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,“,“-----------------------------------”-”.........................................•.•////////////////////////////////ĎĎĎĎĎĎĎĎĎĎĎĎĎĎНННĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐўўŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃҟҟŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓ Ó ÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔˇÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐ˘Ő˘ŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖŁÖŁÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖפ××××××××××××××××××××××ŘĄŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘ٦٦ŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚ§ÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚۨŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜ©ÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝŞÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢ«ŢŢŢŢŢŢŢŢŢŢŢŢŢŢ߬ßßßßßßßßßßßßŕ­ŕŕŕŕŕŕŕŕŕŕŕŕá®ááááááááááááâŻââââââââââă°ăăăăăăăăăăăăääääääääääääĺĺĺĺĺĺĺĺĺĺćłććććććććććç´ççççççççčµččččččččččééééééééééęęęęęęęęęęëëëëëëëëěąěěěěěěěěíşííííííííîîîîîîîîďĽďďďďďďďďđđđđđđđđńńńńńńńńňňňňňňňňóóóóóóóóôÁôôôôôôőÂőőőőőőöööööööö÷÷÷÷÷÷÷÷řřřřřřřřůůůůůůůůúúúúúúúúűűűűűűüüüüüüüüýýýýýýýýţţţţţţţţ˙˙6 < = > BDFGHIKMOPQR S!!!!!!!!!!!!!!!T"""""""""""""""""U###############V#V$$$$$$$$$$$$$$$$$W%%%%%%%%%%%%%%%%%%%X&&&&&&&&&&&&&&&&&&&&&Y'''''''''''''''''''''Z'Z((((((((((((((((((((((([)))))))))))))))))))))))))\)\***************************]+++++++++++++++++++++++++++++++^,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,_,_-----------------------------------`-`.........................................a.a////////////////////////////////////ĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááââââââââââââăăăăăăăăăăăăääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺććććććććććććççççççççççččččččččččééééééééééęęęęęęęęęęëëëëëëëëëëěěěěěěěěěěííííííííîîîîîîîîîîďďďďďďďďđđđđđđđđńńńńńńńńňňňňňňňňóóóóóóóóóóôôôôôôôôőőőőőőöööööööö÷÷÷÷÷÷÷÷řřřřřřřřůůůůůůůůúúúúúúúúűűűűűűüüüüüüüüýýýýýýýýţţţţţţţţ˙˙  !!!!!!!!!!!!!!!!""""""""""""""""""################$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''(((((((((((((((((((((((((())))))))))))))))))))))))))******************************++++++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,--------------------------------------............................................////////////////////////////////////////ĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááââââââââââââăăăăăăăăăăăăääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺććććććććććççççççççççççččččččččččééééééééééęęęęęęęęęęëëëëëëëëěěěěěěěěěěííííííííííîîîîîîîîďďďďďďďďđđđđđđđđđđńńńńńńńńňňňňňňňňóóóóóóóóôôôôôôôôőőőőőőőőöööööööö÷÷÷÷÷÷řřřřřřřřůůůůůůůůúúúúúúúúűűűűűűüüüüüüüüýýýýýýýýţţţţţţţţ˙˙  !!!!!!!!!!!!!!!!""""""""""""""""##################$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''(((((((((((((((((((((((())))))))))))))))))))))))))******************************++++++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,--------------------------------------..........................................//////////////////////////////////////////////ĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ× × ××××××××××××××××××××Ř ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮ ŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚ Ú ÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰ ŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕáááááááááááááâââââââââââăăăăăăăăăăăäääääääääääĺĺĺĺĺĺĺĺĺĺĺćććććććććçççççççççççččččččččččééééééééééęęęęęęęęęęëëëëëëëëěěěěěěěěěíííííííííîîîîîîîîďďďďďďďďđ"đđđđđđđđńńńńńńńńňňňňňňňňóóóóóóóóôôôôôôôôőőőőőőőőöööööööö÷÷÷÷÷÷ř*řřřřřřůůůůůůůůúúúúúúúúűűűűűűü.üüüüüüýýýýýýýýţţţţţţţţ˙˙Ô ÜÝßáâăäĺçčéęëěí î!!!!!!!!!!!!!!!ď"""""""""""""""đ#################ń$$$$$$$$$$$$$$$$$ň$ň%%%%%%%%%%%%%%%%%ó%ó&&&&&&&&&&&&&&&&&&&ô'''''''''''''''''''''ő'ő(((((((((((((((((((((((ö)))))))))))))))))))))))))÷***************************ř*ř+++++++++++++++++++++++++++++ů+ů,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,ú,ú-----------------------------------ű-ű.......................................ü.ü/////////////////////////////////////////////ýĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐ6Đ6Đ6ĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃ7Ń7ŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇ8Ň8Ň8ŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓ9ÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔ:Ô:ÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐ;Ő;ŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖ<ÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ×=××××××××××××××××××××Ř>Ř>ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮ?Ů?ŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚ@ÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰAŰAŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜBÜBÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝCÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢDŢŢŢŢŢŢŢŢŢŢŢŢŢŢßEßßßßßßßßßßßßŕFŕŕŕŕŕŕŕŕŕŕŕŕáGááááááááááááââââââââââââăIăăăăăăăăăăäJääääääääääĺKĺĺĺĺĺĺĺĺĺĺććććććććććçMççççççççčNččččččččéOééééééééęPęęęęęęęęëQëëëëëëëëěěěěěěěěěěííííííííîîîîîîîîďUďďďďďďďďđđđđđđđđńńńńńńńńňňňňňňňňóóóóóóóóôôôôôôôôőőőőőőőőöööööööö÷÷÷÷÷÷÷÷řřřřřřů_ůůůůůůúúúúúúúúűűűűűűűűüüüüüüýýýýýýýýţţţţţţţţ˙˙ Ş¬Ż°˛ł´µ¶·¸ą ş!!!!!!!!!!!!!!!»"""""""""""""""Ľ#################˝$$$$$$$$$$$$$$$$$ľ%%%%%%%%%%%%%%%%%%%ż&&&&&&&&&&&&&&&&&&&&&Ŕ'''''''''''''''''''''Á(((((((((((((((((((((((Â(Â)))))))))))))))))))))))Ă)Ă*************************Ä*Ä+++++++++++++++++++++++++++++Ĺ+Ĺ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,Ć,Ć-----------------------------------Ç-Ç.......................................Č.Č///////////////////////////////////////////É/É/É00ĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐiĐiĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃjŃjŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇkŇkŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓlÓlÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔmÔmÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐnŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖoÖoÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ×p×p××××××××××××××××××××ŘqŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮrŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚsÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰtŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜuÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝvÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢwŢŢŢŢŢŢŢŢŢŢŢŢŢŢßxßßßßßßßßßßßßŕyŕŕŕŕŕŕŕŕŕŕŕŕázááááááááááâ{ââââââââââââă|ăăăăăăăăăăä}ääääääääääĺĺĺĺĺĺĺĺĺĺćććććććććććççççççççççčččččččččé‚ééééééééęęęęęęęęęëëëëëëëëě…ěěěěěěěěí†ííííííî‡îîîîîîîîďďďďďďďďđđđđđđđđńŠńńńńńńň‹ňňňňňňóŚóóóóóóôŤôôôôôôőŽőőőőőőöööööööö÷÷÷÷÷÷÷÷řřřřřřřřůůůůůůúúúúúúúúűűűűűűűűüüüüüüýýýýýýýýţţţţţţţţ˙˙il p q r s tuxz{~‚„† ‡!!!!!!!!!!!!!!!"""""""""""""""‰#################Š$$$$$$$$$$$$$$$$$‹%%%%%%%%%%%%%%%%%%%Ś&&&&&&&&&&&&&&&&&&&Ť&Ť'''''''''''''''''''Ž'Ž(((((((((((((((((((((Ź(Ź)))))))))))))))))))))))))***************************‘+++++++++++++++++++++++++++++’+’,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,“,“-----------------------------------”-”.......................................•.•///////////////////////////////////////////–/–00000000ĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎННĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐўўŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃҟҟŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓ Ó ÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔˇÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐ˘Ő˘ŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖŁÖŁÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖפ××××××××××××××××××××ŘĄŘĄŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘ٦ŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚ§Ú§ÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚۨŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜ©ÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝŞÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢ«ŢŢŢŢŢŢŢŢŢŢŢŢŢŢ߬ßßßßßßßßßßßßŕ­ŕŕŕŕŕŕŕŕŕŕŕŕá®ááááááááááâŻââââââââââââăăăăăăăăăăăăääääääääääĺ˛ĺĺĺĺĺĺĺĺĺĺćłććććććććç´ççççççççççččččččččččééééééééééęęęęęęęęë¸ëëëëëëëëěąěěěěěěěěííííííííîîîîîîîîďĽďďďďďďđ˝đđđđđđđđńńńńńńńńňňňňňňňňóóóóóóóóôôôôôôôôőőőőőőöĂöööööö÷÷÷÷÷÷÷÷řřřřřřřřůůůůůůúúúúúúúúűűűűűűűűüüüüüüýýýýýýýýţţţţţţţţ˙˙; BCEGHIJKLNPQR S!!!!!!!!!!!!!!!T"""""""""""""""U###############V#V$$$$$$$$$$$$$$$$$W%%%%%%%%%%%%%%%%%X%X&&&&&&&&&&&&&&&&&&&Y'''''''''''''''''''''Z((((((((((((((((((((((([)))))))))))))))))))))))))\)\*************************]*]+++++++++++++++++++++++++++^+^,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,_,_-----------------------------------`-`.....................................a.a.a/////////////////////////////////////////b/b/b000000000000ĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááââââââââââââăăăăăăăăăăăăääääääääääääĺĺĺĺĺĺĺĺĺĺĺĺććććććććććççççççççççččččččččččééééééééééęęęęęęęęęęëëëëëëëëëëěěěěěěěěííííííííîîîîîîîîîîďďďďďďďďđđđđđđđđńńńńńńńńňňňňňňňňóóóóóóóóôôôôôôôôőőőőőőőőöööööö÷÷÷÷÷÷÷÷řřřřřřřřůůůůůůúúúúúúúúűűűűűűűűüüüüüüýýýýýýýýţţţţţţţţ˙˙  !!!!!!!!!!!!!!!!""""""""""""""""################$$$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''(((((((((((((((((((((((())))))))))))))))))))))))))****************************++++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,--------------------------------------........................................//////////////////////////////////////////////000000000000000000ĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááââââââââââââăăăăăăăăăăăăääääääääääääĺĺĺĺĺĺĺĺĺĺććććććććććććççççççççççččččččččččééééééééééęęęęęęęęëëëëëëëëëëěěěěěěěěííííííííííîîîîîîîîďďďďďďďďđđđđđđđđńńńńńńńńňňňňňňňňóóóóóóóóôôôôôôôôőőőőőőőőöööööööö÷÷÷÷÷÷řřřřřřřřůůůůůůůůúúúúúúűűűűűűűűüüüüüüýýýýýýýýţţţţţţţţ˙˙  !!!!!!!!!!!!!!!!""""""""""""""""################$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''(((((((((((((((((((((((())))))))))))))))))))))))))****************************++++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,--------------------------------------........................................//////////////////////////////////////////////0000000000000000000000ĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ× ××××××××××××××××××××Ř Ř ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮ ŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚ ÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰ Ű ŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕáááááááááááâââââââââââăăăăăăăăăăăäääääääääääĺĺĺĺĺĺĺĺĺćććććććććććççççççççççčččččččččéééééééééęęęęęęęęëëëëëëëëëěěěěěěěíííííííííîîîîîîîîď!ďďďďďďđ"đđđđđđń#ńńńńńńň$ňňňňňňó%óóóóóóô&ôôôôôôőőőőőőőőöööööööö÷÷÷÷÷÷řřřřřřřřůůůůůůůůúúúúúúűűűűűűűűüüüüüüýýýýýýýýţţţţţţţţ˙˙ŇŐ Ů Ú ŰÜÝŕâăĺćçčéęëěí î!!!!!!!!!!!!!ď!ď"""""""""""""đ"đ###############ń$$$$$$$$$$$$$$$$$ň%%%%%%%%%%%%%%%%%%%ó&&&&&&&&&&&&&&&&&&&ô'''''''''''''''''''''ő(((((((((((((((((((((((ö)))))))))))))))))))))))))÷*************************ř*ř+++++++++++++++++++++++++++++ů,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,ú,ú-----------------------------------ű-ű.....................................ü.ü///////////////////////////////////////////ý/ý0000000000000000000000ÎÎÎÎĎ5Ď5ĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐ6Đ6ĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃ7Ń7Ń7ŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇ8Ň8ŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓ9Ó9ÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔ:Ô:ÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐ;ŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖ<ÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ×=×=××××××××××××××××××××Ř>ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮ?Ů?ŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚ@ÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰAŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜBÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝCÝÝÝÝÝÝÝÝÝÝÝÝŢDŢŢŢŢŢŢŢŢŢŢŢŢŢŢßEßßßßßßßßßßßßŕFŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááâHââââââââââăIăăăăăăăăăăäJääääääääääĺĺĺĺĺĺĺĺĺĺćLććććććććçMççççççççççččččččččččééééééééęPęęęęęęęęëQëëëëëëëëěěěěěěěěííííííííîTîîîîîîîîďďďďďďďďđđđđđđđđńńńńńńńńňňňňňňňňóóóóóóóóôôôôôôő[őőőőőőöööööööö÷÷÷÷÷÷ř^řřřřřřůůůůůůůůúúúúúúűűűűűűűűüüüüüüýcýýýýýýţţţţţţţţ˙˙  ¤ Ş«­Ż°±˛´µ¶·¸ą ş!!!!!!!!!!!!!»"""""""""""""""Ľ#################˝$$$$$$$$$$$$$$$$$ľ%%%%%%%%%%%%%%%%%ż%ż&&&&&&&&&&&&&&&&&Ŕ&Ŕ'''''''''''''''''''Á'Á(((((((((((((((((((((Â(Â)))))))))))))))))))))))Ă)Ă*************************Ä+++++++++++++++++++++++++++++Ĺ+Ĺ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,Ć,Ć---------------------------------Ç-Ç-Ç.....................................Č.Č/////////////////////////////////////////É/É/É00000000000000000000000000ÎÎÎÎÎÎÎÎĎhĎhĎhĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐiĐiĐiĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃjŃjŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇkŇkŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓlÓlÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔmÔmÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐnŐnŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖoÖoÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ×p××××××××××××××××××××ŘqŘqŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮrŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚsÚsÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰtŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜuÜuÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝvÝÝÝÝÝÝÝÝÝÝÝÝŢwŢŢŢŢŢŢŢŢŢŢŢŢŢŢßxßßßßßßßßßßßßŕyŕŕŕŕŕŕŕŕŕŕázááááááááááááâ{ââââââââââă|ăăăăăăăăăăä}ääääääääĺ~ĺĺĺĺĺĺĺĺĺĺćććććććććç€ççççççççčččččččččé‚ééééééééęęęęęęęęęęëëëëëëëëě…ěěěěěěí†ííííííííîîîîîîîîďďďďďďďďđđđđđđđđńńńńńńńńňňňňňňňňóóóóóóóóôôôôôôôôőőőőőőöŹöööööö÷÷÷÷÷÷÷÷řřřřřřůůůůůůůůúúúúúúű”űűűűűűüüüüüüüüýýýýýýţţţţţţţţ˙˙ p uy{€‚„…† ‡!!!!!!!!!!!!!"""""""""""""""‰#################Š$$$$$$$$$$$$$$$‹$‹%%%%%%%%%%%%%%%%%Ś&&&&&&&&&&&&&&&&&&&Ť'''''''''''''''''''''Ž(((((((((((((((((((((((Ź))))))))))))))))))))))))*************************‘*‘+++++++++++++++++++++++++++’+’,,,,,,,,,,,,,,,,,,,,,,,,,,,,,“,“---------------------------------”-”.....................................•.•.•/////////////////////////////////////////–/–00000000000000000000000000000000ÎÎÎÎÎÎÎÎÎÎÎÎÎÎϜϜĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎННĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐўўŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃҟҟŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓ Ó ÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔˇÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐ˘Ő˘ŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖŁÖŁÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖפפ××××××××××××××××××××ŘĄŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘ٦ŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚ§ÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚۨŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜ©ÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝŞÝÝÝÝÝÝÝÝÝÝÝÝޫޫŢŢŢŢŢŢŢŢŢŢŢŢ߬ßßßßßßßßßßßßŕ­ŕŕŕŕŕŕŕŕŕŕá®ááááááááááááâŻââââââââââă°ăăăăăăăăăăääääääääääĺ˛ĺĺĺĺĺĺĺĺĺĺććććććććććç´ççççççççčµččččččččééééééééę·ęęęęęęęęë¸ëëëëëëëëěěěěěěěěííííííííî»îîîîîîďĽďďďďďďđ˝đđđđđđńľńńńńńńňżňňňňňňóŔóóóóóóôôôôôôôôőőőőőőőőöööööö÷÷÷÷÷÷÷÷řřřřřřůĆůůůůůůúúúúúúúúűűűűűűüüüüüüüüýýýýýýţţţţţţţţ˙˙; > ? @BCDFHIJKLNOPQR S!!!!!!!!!!!!!T"""""""""""""""U###############V#V$$$$$$$$$$$$$$$W%%%%%%%%%%%%%%%%%%%X&&&&&&&&&&&&&&&&&&&Y'''''''''''''''''''Z'Z((((((((((((((((((((([([)))))))))))))))))))))))\*************************]*]+++++++++++++++++++++++++++^+^,,,,,,,,,,,,,,,,,,,,,,,,,,,,,_,_---------------------------------`-`.....................................a.a/////////////////////////////////////////b/b/b000000000000000000000000000000000000ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááââââââââââââăăăăăăăăăăääääääääääääĺĺĺĺĺĺĺĺĺĺććććććććććććççççççççççččččččččééééééééééęęęęęęęęęęëëëëëëëëěěěěěěěěííííííííííîîîîîîîîďďďďďďďďđđđđđđđđńńńńńńńńňňňňňňňňóóóóóóôôôôôôôôőőőőőőőőöööööö÷÷÷÷÷÷÷÷řřřřřřřřůůůůůůúúúúúúúúűűűűűűüüüüüüüüýýýýýýţţţţţţţţ˙˙  !!!!!!!!!!!!!!""""""""""""""""################$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''(((((((((((((((((((((((())))))))))))))))))))))))))**************************++++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,------------------------------------........................................////////////////////////////////////////////000000000000000000000000000000000000000000ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááááââââââââââăăăăăăăăăăăăääääääääääääĺĺĺĺĺĺĺĺĺĺććććććććććççççççççççččččččččččééééééééééęęęęęęęęëëëëëëëëëëěěěěěěěěííííííííîîîîîîîîďďďďďďďďđđđđđđđđńńńńńńńńňňňňňňňňóóóóóóóóôôôôôôőőőőőőőőöööööööö÷÷÷÷÷÷řřřřřřřřůůůůůůúúúúúúúúűűűűűűüüüüüüüüýýýýýýţţţţţţţţ˙˙  !!!!!!!!!!!!!!""""""""""""""""################$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''(((((((((((((((((((((())))))))))))))))))))))))))**************************++++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,------------------------------------........................................////////////////////////////////////////////0000000000000000000000000000000000000000000000ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ× × ××××××××××××××××××Ř Ř ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮ ŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚ Ú ÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰ Ű ŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕáááááááááááááââââââââââăăăăăăăăăăăäääääääääääĺĺĺĺĺĺĺĺĺćććććććććçççççççççčččččččččéééééééééęęęęęęęęëëëëëëëëëěěěěěěěěííííííííî îîîîîîď!ďďďďďďđ"đđđđđđń#ńńńńńńňňňňňňňňóóóóóóóóôôôôôôő'őőőőőőöööööööö÷÷÷÷÷÷řřřřřřřřůůůůůůúúúúúúúúűűűűűűüüüüüüüüýýýýýýţţţţţţţţ˙˙ĎŐ ŰÜÝŢßáâăçčéëěí î!!!!!!!!!!!!!ď"""""""""""""""đ###############ń$$$$$$$$$$$$$$$$$ň%%%%%%%%%%%%%%%%%ó&&&&&&&&&&&&&&&&&&&ô'''''''''''''''''''ő'ő(((((((((((((((((((((ö)))))))))))))))))))))))÷)÷*************************ř+++++++++++++++++++++++++++ů+ů,,,,,,,,,,,,,,,,,,,,,,,,,,,,,ú,ú---------------------------------ű-ű.....................................ü.ü/////////////////////////////////////////ý/ý000000000000000000000000000000000000000000000ţÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎ5Ď5Ď5ĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐ6Đ6ĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃ7Ń7ŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇ8Ň8ŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓ9Ó9ÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔ:Ô:ÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐ;Ő;ŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖ<Ö<ÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ×=××××××××××××××××××××Ř>ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮ?Ů?ŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚ@ÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰAŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜBÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝCÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢDŢŢŢŢŢŢŢŢŢŢŢŢßEßßßßßßßßßßßßŕFŕŕŕŕŕŕŕŕŕŕáGááááááááááâHââââââââââăIăăăăăăăăăăäJääääääääääĺĺĺĺĺĺĺĺĺĺćLććććććććçMççççççççččččččččččééééééééęPęęęęęęęęëëëëëëëëěRěěěěěěíSííííííííîîîîîîîîďďďďďďďďđđđđđđđđńńńńńńňXňňňňňňóYóóóóóóôôôôôôôôőőőőőőöööööööö÷÷÷÷÷÷ř^řřřřřřůůůůůůú`úúúúúúűűűűűűüüüüüüüüýýýýýýţţţţţţţţ˙˙ Ł ¦ ¬­Ż°±˛ł´µ¶·¸ą ş!!!!!!!!!!!!!»"""""""""""""""Ľ###############˝$$$$$$$$$$$$$$$ľ$ľ%%%%%%%%%%%%%%%%%ż&&&&&&&&&&&&&&&&&Ŕ&Ŕ'''''''''''''''''''Á(((((((((((((((((((((Â(Â)))))))))))))))))))))))Ă*************************Ä*Ä+++++++++++++++++++++++++Ĺ+Ĺ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,Ć,Ć---------------------------------Ç-Ç...................................Č.Č.Č///////////////////////////////////////É/É/É000000000000000000000000000000000000000000000Ę0Ę11ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎhĎhĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐiĐiĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃjŃjŃjŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇkŇkŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓlÓlÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔmÔmÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐnŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖoÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ×p×p××××××××××××××××××ŘqŘqŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮrŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚsÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰtŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜuÜuÜÜÜÜÜÜÜÜÜÜÜÜÝvÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢwŢŢŢŢŢŢŢŢŢŢŢŢßxßßßßßßßßßßßßŕyŕŕŕŕŕŕŕŕŕŕázááááááááááâ{ââââââââââă|ăăăăăăăăăăä}ääääääääĺ~ĺĺĺĺĺĺĺĺĺĺććććććććććççççççççčččččččččé‚ééééééééęęęęęęęęë„ëëëëëëëëěěěěěěěěííííííííîîîîîîîîďďďďďďďďđđđđđđđđńńńńńńńńňňňňňňňňóóóóóóôŤôôôôôôőőőőőőöŹöööööö÷÷÷÷÷÷÷÷řřřřřřůůůůůůůůúúúúúúűűűűűűüüüüüüüüýýýýýýţţţţţţţţ˙˙mo r vw{}~€‚„…† ‡!!!!!!!!!!!!!"""""""""""""‰"‰#############Š#Š$$$$$$$$$$$$$$$‹%%%%%%%%%%%%%%%%%Ś%Ś&&&&&&&&&&&&&&&&&Ť'''''''''''''''''''Ž'Ž(((((((((((((((((((((Ź))))))))))))))))))))))))***********************‘*‘+++++++++++++++++++++++++++’,,,,,,,,,,,,,,,,,,,,,,,,,,,,,“,“---------------------------------”-”...................................•.•/////////////////////////////////////////–/–000000000000000000000000000000000000000000000—0—0—111111ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎϜϜϜĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎНННĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐўўŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃҟҟŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓ ÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔˇÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐ˘Ő˘ŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖŁÖŁÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖפ××××××××××××××××××××ŘĄŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘ٦ŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚ§Ú§ÚÚÚÚÚÚÚÚÚÚÚÚÚÚۨŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜ©ÜÜÜÜÜÜÜÜÜÜÜÜÝŞÝŞÝÝÝÝÝÝÝÝÝÝÝÝŢ«ŢŢŢŢŢŢŢŢŢŢŢŢ߬ßßßßßßßßßßßßŕ­ŕŕŕŕŕŕŕŕŕŕá®ááááááááááâŻââââââââââă°ăăăăăăăăăăääääääääääĺ˛ĺĺĺĺĺĺĺĺćłććććććććç´ççççççççčµččččččččééééééééę·ęęęęęęęęëëëëëëëëěąěěěěěěíşííííííî»îîîîîîďĽďďďďďďđ˝đđđđđđńľńńńńńńňňňňňňňňóóóóóóóóôôôôôôőőőőőőőőöööööö÷÷÷÷÷÷÷÷řřřřřřůůůůůůůůúúúúúúűűűűűűüÉüüüüüüýýýýýýţţţţţţţţ˙˙7 = @ADEFHKMNOPQR S S!!!!!!!!!!!!!T"""""""""""""U###############V$$$$$$$$$$$$$$$$$W%%%%%%%%%%%%%%%%%X&&&&&&&&&&&&&&&&&&&Y'''''''''''''''''''Z((((((((((((((((((((([([)))))))))))))))))))))\)\***********************]*]+++++++++++++++++++++++++++^+^,,,,,,,,,,,,,,,,,,,,,,,,,,,_,_---------------------------------`-`...................................a.a///////////////////////////////////////b/b/b0000000000000000000000000000000000000000000c0c0c111111111111ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááââââââââââââăăăăăăăăăăääääääääääääĺĺĺĺĺĺĺĺĺĺććććććććććççççççççççččččččččééééééééééęęęęęęęęëëëëëëëëëëěěěěěěěěííííííííîîîîîîîîďďďďďďďďđđđđđđđđńńńńńńňňňňňňňňóóóóóóóóôôôôôôőőőőőőőőöööööö÷÷÷÷÷÷÷÷řřřřřřůůůůůůůůúúúúúúűűűűűűűűüüüüüüýýýýýýţţţţţţţţ˙˙  !!!!!!!!!!!!!!!!""""""""""""""################$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''(((((((((((((((((((((())))))))))))))))))))))))**************************++++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,------------------------------------......................................//////////////////////////////////////////000000000000000000000000000000000000000000000000111111111111111111ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááââââââââââââăăăăăăăăăăääääääääääääĺĺĺĺĺĺĺĺĺĺććććććććććççççççççččččččččččééééééééééęęęęęęęęëëëëëëëëěěěěěěěěííííííííîîîîîîîîďďďďďďďďđđđđđđđđńńńńńńńńňňňňňňóóóóóóóóôôôôôôôôőőőőőőöööööööö÷÷÷÷÷÷řřřřřřřřůůůůůůúúúúúúűűűűűűűűüüüüüüýýýýýýţţţţţţţţ˙˙  !!!!!!!!!!!!!!!!""""""""""""""################$$$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&''''''''''''''''''''(((((((((((((((((((((((())))))))))))))))))))))))**************************++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,------------------------------------......................................//////////////////////////////////////////0000000000000000000000000000000000000000000000001111111111111111111111ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ× ××××××××××××××××××××Ř ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮ ŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚ ÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰ ŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕáááááááááááâââââââââââăăăăăăăăăäääääääääääĺĺĺĺĺĺĺĺĺĺććććććććććççççççççčččččččččéééééééééęęęęęęęęëëëëëëëëěěěěěěěíííííííî îîîîîîď!ďďďďďďđ"đđđđđđńńńńńńńńňňňňňňó%óóóóóóôôôôôôôôőőőőőőöööööööö÷÷÷÷÷÷řřřřřřřřůůůůůůúúúúúúűűűűűűűűüüüüüüýýýýýýţţţţţţţţ˙˙Ő × Ů ÜÝŢßŕáăĺćçčęëěí î!!!!!!!!!!!!!ď!ď"""""""""""""đ###############ń$$$$$$$$$$$$$$$ň$ň%%%%%%%%%%%%%%%ó%ó&&&&&&&&&&&&&&&&&ô'''''''''''''''''''ő(((((((((((((((((((((ö(ö)))))))))))))))))))))÷)÷***********************ř*ř+++++++++++++++++++++++++ů+ů,,,,,,,,,,,,,,,,,,,,,,,,,,,ú,ú-------------------------------ű-ű-ű.................................ü.ü.ü/////////////////////////////////////ý/ý/ý0000000000000000000000000000000000000000000ţ0ţ0ţ1111111111111111111111ÍÍÎ4Î4Î4ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎ5Ď5ĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐ6Đ6ĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃ7Ń7ŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇ8Ň8ŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓ9Ó9ÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔ:Ô:ÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐ;ŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖ<Ö<ÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ×=×=××××××××××××××××××Ř>Ř>ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮ?ŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚ@Ú@ÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰAŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜBÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝCÝÝÝÝÝÝÝÝÝÝÝÝŢDŢŢŢŢŢŢŢŢŢŢŢŢßEßßßßßßßßßßßßŕFŕŕŕŕŕŕŕŕŕŕáGááááááááááâHââââââââââăăăăăăăăăăäJääääääääĺKĺĺĺĺĺĺĺĺćLććććććććçMççççççççčNččččččččééééééééęPęęęęęęëQëëëëëëëëěěěěěěěěííííííííîîîîîîîîďďďďďďďďđđđđđđńWńńńńńńňňňňňňňňóóóóóóôZôôôôôôőőőőőőö\öööööö÷÷÷÷÷÷řřřřřřřřůůůůůůúúúúúúűaűűűűűűüüüüüüýýýýýýţţţţţţţţ˙˙ź §®Ż°˛łµ¶·¸ą ş!!!!!!!!!!!!!»"""""""""""""""Ľ###############˝$$$$$$$$$$$$$$$ľ%%%%%%%%%%%%%%%%%ż&&&&&&&&&&&&&&&&&Ŕ&Ŕ'''''''''''''''''''Á(((((((((((((((((((Â(Â)))))))))))))))))))))Ă)Ă***********************Ä*Ä+++++++++++++++++++++++++Ĺ+Ĺ,,,,,,,,,,,,,,,,,,,,,,,,,,,Ć,Ć-------------------------------Ç-Ç...................................Č.Č///////////////////////////////////////É/É000000000000000000000000000000000000000000000Ę0Ę1111111111111111111111111111ÍÍÍÍÍÍÍÍÎgÎgÎgÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎhĎhĎhĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐiĐiĐiĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃjŃjŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇkŇkŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓlÓlÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔmÔmÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐnŐnŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖoÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ×p×p××××××××××××××××××ŘqŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮrŮrŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚsÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰtŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜuÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝvÝÝÝÝÝÝÝÝÝÝÝÝŢwŢŢŢŢŢŢŢŢŢŢŢŢßxßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕázááááááááááâ{ââââââââă|ăăăăăăăăăăä}ääääääääĺ~ĺĺĺĺĺĺĺĺćććććććććç€ççççççççččččččččé‚ééééééééęęęęęęęęë„ëëëëëëě…ěěěěěěí†ííííííî‡îîîîîîďďďďďďďđđđđđđđđńńńńńńň‹ňňňňňňóóóóóóóóôôôôôôőőőőőőőőöööööö÷÷÷÷÷÷ř‘řřřřřřůůůůůůúúúúúúúúűűűűűűüüüüüüýýýýýýţţţţţţţţ˙˙io q s vwxyz}~‚„…† ‡!!!!!!!!!!!!!"""""""""""""""‰#############Š#Š$$$$$$$$$$$$$$$‹%%%%%%%%%%%%%%%%%Ś&&&&&&&&&&&&&&&&&Ť'''''''''''''''''''Ž'Ž(((((((((((((((((((Ź)))))))))))))))))))))))***********************‘*‘+++++++++++++++++++++++++’+’,,,,,,,,,,,,,,,,,,,,,,,,,,,“,“-------------------------------”-”...................................•.•///////////////////////////////////////–/–0000000000000000000000000000000000000000000—0—0—11111111111111111111111111111111ÍÍÍÍÍÍÍÍÍÍÍÍÍÍΛΛÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎϜϜĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎННĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐўўўŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃҟҟŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓ Ó ÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔˇÔˇÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐ˘Ő˘ŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖŁÖŁÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖפ××××××××××××××××××ŘĄŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘ٦ŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚ§ÚÚÚÚÚÚÚÚÚÚÚÚÚÚۨۨŰŰŰŰŰŰŰŰŰŰŰŰܩܩÜÜÜÜÜÜÜÜÜÜÜÜÝŞÝÝÝÝÝÝÝÝÝÝÝÝŢ«ŢŢŢŢŢŢŢŢŢŢŢŢ߬ßßßßßßßßßßŕ­ŕŕŕŕŕŕŕŕŕŕŕŕá®ááááááááááââââââââââă°ăăăăăăăăăăääääääääääĺ˛ĺĺĺĺĺĺĺĺćłććććććććççççççççčµččččččččééééééééę·ęęęęęęęęëëëëëëëëěěěěěěěěííííííííîîîîîîîîďďďďďďđ˝đđđđđđńńńńńńńńňňňňňňóŔóóóóóóôôôôôôőÂőőőőőőöööööö÷÷÷÷÷÷÷÷řřřřřřůůůůůůúúúúúúúúűűűűűűüüüüüüýýýýýýţţţţţţţţ˙˙9 AGHJKLMNOPQR S!!!!!!!!!!!!!T"""""""""""""U"U#############V$$$$$$$$$$$$$$$W$W%%%%%%%%%%%%%%%X%X&&&&&&&&&&&&&&&&&Y'''''''''''''''''''Z((((((((((((((((((([([)))))))))))))))))))))\)\***********************]+++++++++++++++++++++++++^+^,,,,,,,,,,,,,,,,,,,,,,,,,,,_,_-------------------------------`-`...................................a.a/////////////////////////////////////b/b/b0000000000000000000000000000000000000000000c0c11111111111111111111111111111111111111ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕŕŕááááááááááââââââââââââăăăăăăăăăăääääääääääääĺĺĺĺĺĺĺĺĺĺććććććććççççççççççččččččččééééééééééęęęęęęęęëëëëëëëëěěěěěěěěííííííííîîîîîîîîďďďďďďďďđđđđđđńńńńńńńńňňňňňňňňóóóóóóôôôôôôôôőőőőőőöööööö÷÷÷÷÷÷÷÷řřřřřřůůůůůůúúúúúúúúűűűűűűüüüüüüýýýýýýţţţţţţţţ˙˙  !!!!!!!!!!!!!!""""""""""""""################$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''(((((((((((((((((((())))))))))))))))))))))))**************************++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,----------------------------------......................................////////////////////////////////////////000000000000000000000000000000000000000000000000111111111111111111111111111111111111111111ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááââââââââââââăăăăăăăăăăääääääääääĺĺĺĺĺĺĺĺĺĺććććććććććççççççççççččččččččééééééééęęęęęęęęęęëëëëëëëëěěěěěěěěííííííííîîîîîîďďďďďďďďđđđđđđđđńńńńńńňňňňňňňňóóóóóóôôôôôôôôőőőőőőöööööööö÷÷÷÷÷÷řřřřřřůůůůůůůůúúúúúúűűűűűűüüüüüüýýýýýýţţţţţţţţ˙˙  !!!!!!!!!!!!!!""""""""""""""################$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&''''''''''''''''''''(((((((((((((((((((((())))))))))))))))))))))**************************++++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,--------------------------------....................................//////////////////////////////////////////0000000000000000000000000000000000000000000000111111111111111111111111111111111111111111111111ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ× ××××××××××××××××××Ř ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮ ŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚ Ú ÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰ ŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕáááááááááááâââââââââââăăăăăăăăăäääääääääĺĺĺĺĺĺĺĺĺćććććććććçççççççççččččččččéééééééęęęęęęęęęëëëëëëëëěěěěěěěěííííííííîîîîîîď!ďďďďďďđ"đđđđđđńńńńńńň$ňňňňňňóóóóóóô&ôôôôôôőőőőőőö(öööööö÷÷÷÷÷÷řřřřřřůůůůůůůůúúúúúúűűűűűűüüüüüüýýýýýýţ0ţţţţţţ˙˙ŃŐ Ř Ú Ýăäĺçčęëí î!!!!!!!!!!!!!ď"""""""""""""đ###############ń$$$$$$$$$$$$$$$ň%%%%%%%%%%%%%%%ó%ó&&&&&&&&&&&&&&&&&ô'''''''''''''''''''ő(((((((((((((((((((ö(ö)))))))))))))))))))))÷***********************ř*ř+++++++++++++++++++++++++ů+ů,,,,,,,,,,,,,,,,,,,,,,,,,,,ú,ú-----------------------------ű-ű.................................ü.ü/////////////////////////////////////ý/ý/ý00000000000000000000000000000000000000000ţ0ţ0ţ11111111111111111111111111111111111111111111111˙ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎ4Î4ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎ5Ď5Ď5ĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐ6Đ6ĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃ7Ń7ŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇ8Ň8ŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓ9Ó9ÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔ:Ô:ÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐ;Ő;ŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖ<Ö<ÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ×=×=××××××××××××××××Ř>Ř>ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮ?ŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚ@ÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰAŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜBÜÜÜÜÜÜÜÜÜÜÜÜÝCÝCÝÝÝÝÝÝÝÝÝÝŢDŢŢŢŢŢŢŢŢŢŢŢŢßEßßßßßßßßßßŕFŕŕŕŕŕŕŕŕŕŕáGááááááááááâHââââââââââăIăăăăăăăăäJääääääääĺKĺĺĺĺĺĺĺĺćLććććććććççççççççčNččččččččééééééééęPęęęęęęëQëëëëëëěRěěěěěěíSííííííîTîîîîîîďďďďďďďďđđđđđđńWńńńńńńňňňňňňóYóóóóóóôôôôôôőőőőőőőőöööööö÷÷÷÷÷÷řřřřřřů_ůůůůůůúúúúúúűűűűűűüüüüüüýýýýýýýýţţţţţţ˙˙ ¨«¬­®Ż±˛µ¶·¸ą ş!!!!!!!!!!!!!»"""""""""""""Ľ###############˝$$$$$$$$$$$$$$$ľ%%%%%%%%%%%%%%%ż&&&&&&&&&&&&&&&&&Ŕ&Ŕ'''''''''''''''''Á'Á(((((((((((((((((((Â)))))))))))))))))))))Ă)Ă*********************Ä*Ä+++++++++++++++++++++++++Ĺ+Ĺ,,,,,,,,,,,,,,,,,,,,,,,,,,,Ć,Ć-----------------------------Ç-Ç.................................Č.Č/////////////////////////////////////É/É0000000000000000000000000000000000000000000Ę0Ę1111111111111111111111111111111111111111111111111Ë1Ë22ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎgÎgÎgÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎhĎhĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐiĐiĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃjŃjŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇkŇkŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓlÓlÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔmÔmÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐnŐnŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖoÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ×p××××××××××××××××××ŘqŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮrŮrŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚsÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰtŰtŰŰŰŰŰŰŰŰŰŰŰŰÜuÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝvÝÝÝÝÝÝÝÝÝÝŢwŢwŢŢŢŢŢŢŢŢŢŢßxßßßßßßßßßßŕyŕŕŕŕŕŕŕŕŕŕázááááááááááâ{ââââââââââăăăăăăăăăăä}ääääääääĺ~ĺĺĺĺĺĺĺĺććććććććç€ççççççççčččččččé‚ééééééééęęęęęęęęëëëëëëëëěěěěěěěěííííííííîîîîîîďďďďďďďđđđđđđđđńńńńńńňňňňňňňňóóóóóóôôôôôôőŽőőőőőőöööööö÷÷÷÷÷÷ř‘řřřřřřůůůůůůúúúúúúűűűűűűüüüüüüýýýýýýýýţţţţţţ˙˙ko r tvw}€‚„…† ‡!!!!!!!!!!!!!"""""""""""""‰#############Š#Š$$$$$$$$$$$$$‹$‹%%%%%%%%%%%%%%%Ś&&&&&&&&&&&&&&&&&Ť'''''''''''''''''''Ž(((((((((((((((((((Ź(Ź))))))))))))))))))))***********************‘+++++++++++++++++++++++++’+’,,,,,,,,,,,,,,,,,,,,,,,,,,,“,“-----------------------------”-”.................................•.•/////////////////////////////////////–/–00000000000000000000000000000000000000000—0—0—1111111111111111111111111111111111111111111111111222222ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍΛΛΛÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎϜϜϜĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎНННĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐўўўŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃҟҟŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓ Ó ÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔˇÔˇÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐ˘ŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖŁÖŁÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖפפ××××××××××××××××ŘĄŘĄŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘ٦ŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚ§Ú§ÚÚÚÚÚÚÚÚÚÚÚÚÚÚۨŰŰŰŰŰŰŰŰŰŰŰŰܩܩÜÜÜÜÜÜÜÜÜÜÜÜÝŞÝÝÝÝÝÝÝÝÝÝÝÝŢ«ŢŢŢŢŢŢŢŢŢŢ߬ßßßßßßßßßßŕ­ŕŕŕŕŕŕŕŕŕŕá®ááááááááááâŻââââââââă°ăăăăăăăăăăääääääääääĺĺĺĺĺĺĺĺćłććććććććç´ççççççççččččččččé¶ééééééę·ęęęęęęë¸ëëëëëëěąěěěěěěíşííííííîîîîîîîîďďďďďďđ˝đđđđđđńńńńńńňżňňňňňňóóóóóóôÁôôôôôôőőőőőőöööööö÷Ä÷÷÷÷÷÷řřřřřřůůůůůůúúúúúúűűűűűűüüüüüüýýýýýýýýţţţţţţ˙˙8 < ? ADEFGHJLMNOPQR S!!!!!!!!!!!!!T"""""""""""""U#############V$$$$$$$$$$$$$$$W%%%%%%%%%%%%%%%X%X&&&&&&&&&&&&&&&Y&Y'''''''''''''''''Z'Z((((((((((((((((([([)))))))))))))))))))))\***********************]*]+++++++++++++++++++++++^+^,,,,,,,,,,,,,,,,,,,,,,,,,,,_,_-----------------------------`-`...............................a.a.a///////////////////////////////////b/b/b00000000000000000000000000000000000000000c0c11111111111111111111111111111111111111111111111d1d1d222222222222ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááââââââââââăăăăăăăăăăääääääääääĺĺĺĺĺĺĺĺĺĺććććććććććççççççççččččččččččééééééééęęęęęęęęëëëëëëëëěěěěěěěěííííííîîîîîîîîďďďďďďďďđđđđđđńńńńńńńńňňňňňňóóóóóóóóôôôôôôőőőőőőöööööööö÷÷÷÷÷÷řřřřřřůůůůůůúúúúúúűűűűűűüüüüüüýýýýýýýýţţţţţţ˙˙  !!!!!!!!!!!!!!""""""""""""""##############$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&''''''''''''''''''''(((((((((((((((((((())))))))))))))))))))))))************************++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,--------------------------------..................................////////////////////////////////////////000000000000000000000000000000000000000000000011111111111111111111111111111111111111111111111111222222222222222222ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕááááááááááááââââââââââăăăăăăăăăăääääääääääĺĺĺĺĺĺĺĺĺĺććććććććççççççççççččččččččééééééééęęęęęęęęëëëëëëëëěěěěěěěěííííííííîîîîîîîîďďďďďďđđđđđđđđńńńńńńňňňňňňňňóóóóóóôôôôôôőőőőőőöööööööö÷÷÷÷÷÷řřřřřřůůůůůůúúúúúúűűűűűűüüüüüüüüýýýýýýţţţţţţ˙˙  !!!!!!!!!!!!!!""""""""""""""##############$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&''''''''''''''''''''(((((((((((((((((((())))))))))))))))))))))************************++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,--------------------------------..................................////////////////////////////////////////0000000000000000000000000000000000000000000011111111111111111111111111111111111111111111111111222222222222222222222222Ě˙ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ× × ××××××××××××××××Ř Ř ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮ Ů ŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚ ÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰ ŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕáááááááááááâââââââââăăăăăăăăăäääääääääĺĺĺĺĺĺĺĺĺćććććććçççççççççččččččččéééééééęęęęęęęëëëëëëëěěěěěěěíííííííîîîîîîîîďďďďďďđ"đđđđđđńńńńńńňňňňňňňňóóóóóóôôôôôôőőőőőőö(öööööö÷÷÷÷÷÷řřřřřřůůůůůůúúúúúúűűűűűűü.üüüüüüýýýýýýţţţţţţ˙˙Ő Ů ŰÝßŕáâăäćęëěí î!!!!!!!!!!!ď!ď"""""""""""đ"đ#############ń$$$$$$$$$$$$$$$ň%%%%%%%%%%%%%%%ó&&&&&&&&&&&&&&&&&ô'''''''''''''''''ő'ő(((((((((((((((((ö(ö)))))))))))))))))))÷)÷*********************ř*ř+++++++++++++++++++++++ů+ů,,,,,,,,,,,,,,,,,,,,,,,,,,,ú,ú---------------------------ű-ű-ű...............................ü.ü///////////////////////////////////ý/ý/ý000000000000000000000000000000000000000ţ0ţ0ţ111111111111111111111111111111111111111111111˙1˙1˙222222222222222222222222ĚĚÍ3Í3Í3ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎ4Î4Î4ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎ5Ď5ĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐ6Đ6Đ6ĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃ7Ń7ŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇ8Ň8ŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓ9ÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔ:Ô:ÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐ;ŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖ<Ö<ÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ×=××××××××××××××××××Ř>ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮ?ŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚ@Ú@ÚÚÚÚÚÚÚÚÚÚÚÚŰAŰAŰŰŰŰŰŰŰŰŰŰŰŰÜBÜÜÜÜÜÜÜÜÜÜÜÜÝCÝÝÝÝÝÝÝÝÝÝÝÝŢDŢŢŢŢŢŢŢŢŢŢßEßßßßßßßßßßŕFŕŕŕŕŕŕŕŕŕŕáGááááááááááââââââââââăIăăăăăăăăäJääääääääĺKĺĺĺĺĺĺĺĺććććććććçMççççççčNččččččččééééééééęęęęęęęęëëëëëëëëěěěěěěěěííííííîTîîîîîîďďďďďďďďđđđđđđńńńńńńňXňňňňňňóóóóóóôôôôôôő[őőőőőőöööööö÷÷÷÷÷÷řřřřřřůůůůůůúúúúúúűaűűűűűűüüüüüüýýýýýýţţţţţţ˙˙˘ ¦ ¨Ş®±˛ł´µ¶·¸ą ş!!!!!!!!!!!»"""""""""""""Ľ###############˝$$$$$$$$$$$$$ľ$ľ%%%%%%%%%%%%%ż%ż&&&&&&&&&&&&&&&Ŕ&Ŕ'''''''''''''''''Á(((((((((((((((((((Â)))))))))))))))))))))Ă***********************Ä+++++++++++++++++++++++Ĺ+Ĺ,,,,,,,,,,,,,,,,,,,,,,,,,,,Ć,Ć---------------------------Ç-Ç.................................Č.Č///////////////////////////////////É/É00000000000000000000000000000000000000000Ę0Ę11111111111111111111111111111111111111111111111Ë1Ë222222222222222222222222222222ĚĚĚĚĚĚĚĚÍfÍfÍfÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎgÎgÎgÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎhĎhĎhĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐiĐiĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃjŃjŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇkŇkŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓlÓlÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔmÔmÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐnŐnŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖoÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ×p×p××××××××××××××××ŘqŘqŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮrŮrŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚsÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰtŰŰŰŰŰŰŰŰŰŰŰŰÜuÜÜÜÜÜÜÜÜÜÜÜÜÝvÝÝÝÝÝÝÝÝÝÝÝÝŢwŢŢŢŢŢŢŢŢŢŢßxßßßßßßßßßßŕyŕŕŕŕŕŕŕŕŕŕázááááááááâ{ââââââââââă|ăăăăăăăăä}ääääääääĺĺĺĺĺĺĺĺćććććććććççççççççčččččččé‚ééééééęęęęęęęë„ëëëëëëě…ěěěěěěí†ííííííîîîîîîďďďďďďďđđđđđđńŠńńńńńńňňňňňňóóóóóóôŤôôôôôôőőőőőőöööööö÷÷÷÷÷÷řřřřřřů’ůůůůú“úúúúúúűűűűűűüüüüüüýýýýýýţţţţţţ˙˙ p tvyz|}~€‚„…† ‡!!!!!!!!!!!"""""""""""""‰#############Š#Š$$$$$$$$$$$$$‹%%%%%%%%%%%%%%%Ś&&&&&&&&&&&&&&&&&Ť'''''''''''''''''Ž'Ž(((((((((((((((((Ź(Ź))))))))))))))))))))*********************‘*‘+++++++++++++++++++++++’,,,,,,,,,,,,,,,,,,,,,,,,,,,“,“---------------------------”-”...............................•.•.•///////////////////////////////////–/–000000000000000000000000000000000000000—0—0—111111111111111111111111111111111111111111111112222222222222222222222222222222222ĚĚĚĚĚĚĚĚĚĚĚĚĚĚ͚͚͚ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍΛΛÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎϜϜĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎННĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐўўŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃҟҟŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓ Ó ÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔˇÔˇÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐ˘Ő˘ŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖŁÖŁÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖפ××××××××××××××××××ŘĄŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘ٦ŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚ§ÚÚÚÚÚÚÚÚÚÚÚÚÚÚۨŰŰŰŰŰŰŰŰŰŰŰŰÜ©ÜÜÜÜÜÜÜÜÜÜÜÜÝŞÝÝÝÝÝÝÝÝÝÝÝÝŢ«ŢŢŢŢŢŢŢŢŢŢ߬ßßßßßßßßßßŕ­ŕŕŕŕŕŕŕŕŕŕá®ááááááááâŻââââââââââăăăăăăăăăăääääääääĺ˛ĺĺĺĺĺĺĺĺćłććććććç´ççççççççččččččččé¶ééééééę·ęęęęęęë¸ëëëëëëěěěěěěěěííííííî»îîîîîîďďďďďďđ˝đđđđđđńńńńńńňżňňňňóŔóóóóóóôôôôôôőőőőőőöööööö÷Ä÷÷÷÷řĹřřřřřřůůůůůůúúúúúúűűűűűűüüüüüüýýýýýýţţţţţţ˙˙ = CDFGHIKMNOPQR S!!!!!!!!!!!T"""""""""""""U#############V$$$$$$$$$$$$$$$W%%%%%%%%%%%%%%%X&&&&&&&&&&&&&&&Y&Y'''''''''''''''''Z((((((((((((((((((([)))))))))))))))))))\)\*********************]*]+++++++++++++++++++++++^+^,,,,,,,,,,,,,,,,,,,,,,,,,_,_---------------------------`-`...............................a.a///////////////////////////////////b/b/b000000000000000000000000000000000000000c0c111111111111111111111111111111111111111111111d1d1d2222222222222222222222222222222222222222ĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕááááááááááââââââââââăăăăăăăăăăääääääääääĺĺĺĺĺĺĺĺĺĺććććććććççççççççččččččččččééééééééęęęęęęęęëëëëëëěěěěěěěěííííííííîîîîîîďďďďďďďďđđđđđđńńńńńńńńňňňňňňóóóóóóôôôôôôőőőőőőöööööööö÷÷÷÷÷÷řřřřřřůůůůůůúúúúúúűűűűűűüüüüüüýýýýýýţţţţţţ˙˙  !!!!!!!!!!!!""""""""""""""##############$$$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&''''''''''''''''''''(((((((((((((((((((())))))))))))))))))))************************++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,------------------------------..................................//////////////////////////////////////000000000000000000000000000000000000000000001111111111111111111111111111111111111111111111112222222222222222222222222222222222222222222222ĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕŕááááááááááââââââââââăăăăăăăăăăääääääääääĺĺĺĺĺĺĺĺććććććććććççççççççččččččččééééééééęęęęęęęęëëëëëëëëěěěěěěěěííííííîîîîîîîîďďďďďďđđđđđđđđńńńńńńňňňňňňóóóóóóôôôôôôôôőőőőőőöööööö÷÷÷÷÷÷řřřřřřůůůůůůúúúúúúűűűűűűüüüüüüýýýýýýţţţţţţ˙˙  !!!!!!!!!!!!""""""""""""""##############$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&&&''''''''''''''''''(((((((((((((((((((())))))))))))))))))))))**********************++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,------------------------------..................................//////////////////////////////////////0000000000000000000000000000000000000000001111111111111111111111111111111111111111111111112222222222222222222222222222222222222222222222222222ĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚ˙Ě˙Ě˙ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ× ××××××××××××××××Ř Ř ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮ Ů ŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚ ÚÚÚÚÚÚÚÚÚÚÚÚŰ Ű ŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕŕáááááááááâââââââââăăăăăăăăăäääääääääĺĺĺĺĺĺĺćććććććććççççççççčččččččéééééééęęęęęęęëëëëëëëěěěěěěěěííííííî îîîîîîďďďďďďđ"đđđđđđńńńńńńňňňňňňóóóóóóô&ôôôôôôőőőőőőöööööö÷÷÷÷÷÷řřřřřřůůůůůůúúúúúúűűűűűűüüüüüüýýýýýýţţţţţţ˙˙ŇÓÔŐ ŰÝßáâăäĺçéęëěí î!!!!!!!!!!!ď"""""""""""""đ#############ń$$$$$$$$$$$$$ň%%%%%%%%%%%%%%%ó&&&&&&&&&&&&&&&&&ô'''''''''''''''''ő(((((((((((((((((ö(ö)))))))))))))))))))÷)÷*********************ř+++++++++++++++++++++++ů+ů,,,,,,,,,,,,,,,,,,,,,,,,,ú,ú---------------------------ű-ű...............................ü.ü/////////////////////////////////ý/ý/ý000000000000000000000000000000000000000ţ0ţ111111111111111111111111111111111111111111111˙1˙222222222222222222222222222222222222222222222222223ĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍ3Í3Í3ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎ4Î4ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎ5Ď5Ď5ĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐ6Đ6ĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃ7Ń7ŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇ8Ň8ŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓ9Ó9ÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔ:Ô:ÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐ;Ő;ŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖ<Ö<ÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ×=×=××××××××××××××××Ř>ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮ?ŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚ@ÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰAŰŰŰŰŰŰŰŰŰŰŰŰÜBÜÜÜÜÜÜÜÜÜÜÜÜÝCÝÝÝÝÝÝÝÝÝÝŢDŢŢŢŢŢŢŢŢŢŢßEßßßßßßßßßßŕFŕŕŕŕŕŕŕŕŕŕáGááááááááâHââââââââăIăăăăăăăăäJääääääääĺĺĺĺĺĺĺĺćLććććććçMççççççççččččččččééééééééęęęęęęęęëëëëëëěRěěěěěěíSííííííîîîîîîďUďďďďďďđđđđđđńńńńńńňXňňňňóYóóóóóóôôôôôôőőőőőőöööööö÷÷÷÷÷÷řřřřřřůůůůůůúúúúúúűűűűűűüüüüüüýýýýýýţţţţţţ˙˙śť˘ Ł ¤ ¨Ş¬˛ł´·¸ą ş!!!!!!!!!!!»"""""""""""Ľ"Ľ###########˝#˝$$$$$$$$$$$$$ľ%%%%%%%%%%%%%%%ż&&&&&&&&&&&&&&&Ŕ&Ŕ'''''''''''''''Á'Á(((((((((((((((((Â)))))))))))))))))))Ă)Ă*********************Ä*Ä+++++++++++++++++++++Ĺ+Ĺ,,,,,,,,,,,,,,,,,,,,,,,,,Ć,Ć---------------------------Ç-Ç.............................Č.Č.Č/////////////////////////////////É/É000000000000000000000000000000000000000Ę0Ę0Ę1111111111111111111111111111111111111111111Ë1Ë1Ë2222222222222222222222222222222222222222222222222Ě2Ě2Ě33ĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍfÍfÍfÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎgÎgÎgÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎhĎhĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐiĐiĐiĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃjŃjŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇkŇkŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓlÓlÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔmÔmÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐnŐnŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖoÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ×p××××××××××××××××ŘqŘqŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮrŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚsÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰtŰŰŰŰŰŰŰŰŰŰŰŰÜuÜÜÜÜÜÜÜÜÜÜÜÜÝvÝÝÝÝÝÝÝÝÝÝŢwŢŢŢŢŢŢŢŢŢŢßxßßßßßßßßßßŕyŕŕŕŕŕŕŕŕŕŕááááááááááâ{ââââââââă|ăăăăăăăăä}ääääääĺ~ĺĺĺĺĺĺĺĺćććććććç€ççççççčččččččé‚ééééééęęęęęęęë„ëëëëëëěěěěěěěěííííííî‡îîîîîîďďďďďďđđđđđđńŠńńńńńńňňňňňňóóóóóóôôôôôôőőőőőőöööööö÷÷÷÷÷÷řřřřřřůůůůůůúúúúúúűűűűűűüüüüüüýýýýýýţţţţţţ˙˙ r s vxz{|}~‚„…† ‡!!!!!!!!!!!"""""""""""‰#############Š$$$$$$$$$$$$$$$‹%%%%%%%%%%%%%Ś%Ś&&&&&&&&&&&&&&&Ť'''''''''''''''''Ž(((((((((((((((((Ź(Ź)))))))))))))))))))*********************‘*‘+++++++++++++++++++++’+’,,,,,,,,,,,,,,,,,,,,,,,,,“,“---------------------------”-”.............................•.•///////////////////////////////////–/–0000000000000000000000000000000000000—0—0—1111111111111111111111111111111111111111111112222222222222222222222222222222222222222222222222™2™2™33333333ĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚ͚͚͚ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍΛΛΛÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎϜϜϜĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎННĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐўўŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃҟҟŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓ Ó ÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔˇÔˇÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐ˘ŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖŁÖŁÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖפפ××××××××××××××××ŘĄŘŘŘŘŘŘŘŘŘŘŘŘŘŘ٦٦ŮŮŮŮŮŮŮŮŮŮŮŮÚ§Ú§ÚÚÚÚÚÚÚÚÚÚÚÚۨŰŰŰŰŰŰŰŰŰŰŰŰÜ©ÜÜÜÜÜÜÜÜÜÜÜÜÝŞÝÝÝÝÝÝÝÝÝÝŢ«ŢŢŢŢŢŢŢŢŢŢ߬ßßßßßßßßßßŕ­ŕŕŕŕŕŕŕŕá®ááááááááááâŻââââââââă°ăăăăăăăăääääääääĺ˛ĺĺĺĺĺĺĺĺććććććććç´ççççççčµččččččé¶ééééééę·ęęęęęęëëëëëëěąěěěěěěííííííííîîîîîîďďďďďďđ˝đđđđđđńńńńńńňňňňňňóóóóóóôôôôôôőÂőőőőöĂöööö÷Ä÷÷÷÷řĹřřřřůůůůůůúúúúúúűűűűűűüüüüüüýýýýýýţţţţţţ˙˙4 @CEHIJLMNOPQR S!!!!!!!!!!!T"""""""""""U#############V$$$$$$$$$$$$$W$W%%%%%%%%%%%%%X&&&&&&&&&&&&&&&Y&Y'''''''''''''''Z'Z((((((((((((((((([)))))))))))))))))))\)\*******************]*]+++++++++++++++++++++^+^,,,,,,,,,,,,,,,,,,,,,,,,,_,_---------------------------`-`.............................a.a/////////////////////////////////b/b/b0000000000000000000000000000000000000c0c1111111111111111111111111111111111111111111d1d1d2222222222222222222222222222222222222222222222222e2e2e33333333333333ĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕááááááááááááââââââââââăăăăăăăăääääääääääĺĺĺĺĺĺĺĺććććććććććççççççççččččččččééééééééęęęęęęëëëëëëëëěěěěěěííííííííîîîîîîďďďďďďďďđđđđđđńńńńńńňňňňňňóóóóóóôôôôôôôôőőőőőőöööööö÷÷÷÷÷÷řřřřůůůůůůúúúúúúűűűűűűüüüüüüýýýýýýţţţţţţ˙˙  !!!!!!!!!!!!""""""""""""##############$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&''''''''''''''''''(((((((((((((((((((())))))))))))))))))))**********************++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,,,------------------------------................................////////////////////////////////////000000000000000000000000000000000000000000111111111111111111111111111111111111111111111122222222222222222222222222222222222222222222222222222233333333333333333333ĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕááááááááááââââââââââăăăăăăăăăăääääääääääĺĺĺĺĺĺĺĺććććććććççççççççččččččččééééééééęęęęęęęęëëëëëëěěěěěěěěííííííîîîîîîîîďďďďďďđđđđđđńńńńńńńńňňňňňňóóóóóóôôôôôôőőőőőőöööööö÷÷÷÷÷÷řřřřřřůůůůůůúúúúűűűűűűüüüüüüýýýýýýţţţţţţ˙˙  !!!!!!!!!!!!""""""""""""##############$$$$$$$$$$$$$$%%%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&''''''''''''''''''(((((((((((((((((())))))))))))))))))))**********************++++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,----------------------------..................................////////////////////////////////////0000000000000000000000000000000000000000111111111111111111111111111111111111111111111122222222222222222222222222222222222222222222222222222233333333333333333333333333ËţËţĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚ˙Ě˙Ě˙ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ× ××××××××××××××××Ř ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮ Ů ŮŮŮŮŮŮŮŮŮŮŮŮÚ ÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰ ŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßŕŕŕŕŕŕŕŕŕáááááááááâââââââââăăăăăăăăăäääääääääĺĺĺĺĺĺĺĺćććććććçççççççčččččččéééééééęęęęęęęëëëëëëěěěěěěěííííííî îîîîîîďďďďďďđ"đđđđń#ńńńńńńňňňňňňóóóóóóôôôôôôőőőőőőöööööö÷÷÷÷÷÷řřřřřřůůůůůůúúúúű-űűűűüüüüüüýýýýýýţţţţţţ˙˙Ö × Ř Ů Ţŕâăäĺćçčéęëěí î î!!!!!!!!!!!ď"""""""""""đ#############ń$$$$$$$$$$$$$ň%%%%%%%%%%%%%ó%ó&&&&&&&&&&&&&&&ô'''''''''''''''ő'ő(((((((((((((((((ö)))))))))))))))))))÷*********************ř+++++++++++++++++++++++ů+ů,,,,,,,,,,,,,,,,,,,,,,,ú,ú-------------------------ű-ű.............................ü.ü.ü///////////////////////////////ý/ý/ý0000000000000000000000000000000000000ţ0ţ1111111111111111111111111111111111111111111˙1˙22222222222222222222222222222222222222222222222233333333333333333333333333333ËËËËĚ2Ě2Ě2ĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍ3Í3Í3ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎ4Î4Î4ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎ5Ď5ĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐ6Đ6ĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃ7Ń7ŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇ8Ň8ŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓ9Ó9ÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔ:Ô:ÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐ;ŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖ<Ö<ÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ×=×=××××××××××××××Ř>Ř>ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮ?ŮŮŮŮŮŮŮŮŮŮŮŮÚ@Ú@ÚÚÚÚÚÚÚÚÚÚÚÚŰAŰŰŰŰŰŰŰŰŰŰŰŰÜBÜÜÜÜÜÜÜÜÜÜÝCÝÝÝÝÝÝÝÝÝÝŢDŢDŢŢŢŢŢŢŢŢßEßßßßßßßßßßŕFŕŕŕŕŕŕŕŕáGááááááááâHââââââââăIăăăăăăăăäJääääääĺKĺĺĺĺĺĺĺĺććććććććçMççççççčNččččččééééééééęęęęęęëQëëëëëëěěěěěěíSííííííîîîîîîďUďďďďďďđđđđđđńńńńńńňňňňňňóóóóóóôôôôôôőőőőőőöööööö÷÷÷÷÷÷řřřřřřůůůůůůúúúúúúűűűűüüüüüüýýýýýýţţţţţţ˙˙ ˇ ¦ §¨«­°±˛´¶·¸ą ş!!!!!!!!!!!»!»"""""""""""Ľ###########˝#˝$$$$$$$$$$$ľ$ľ%%%%%%%%%%%%%ż&&&&&&&&&&&&&&&Ŕ&Ŕ'''''''''''''''Á(((((((((((((((((Â(Â)))))))))))))))))Ă)Ă*******************Ä*Ä+++++++++++++++++++++Ĺ+Ĺ,,,,,,,,,,,,,,,,,,,,,,,Ć,Ć-------------------------Ç-Ç.............................Č.Č/////////////////////////////////É/É0000000000000000000000000000000000000Ę0Ę0Ę11111111111111111111111111111111111111111Ë1Ë1Ë22222222222222222222222222222222222222222222222Ě2Ě2Ě33333333333333333333333333333333ËËËËËËËËËËĚeĚeĚeĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍfÍfÍfÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎgÎgÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎhĎhĎhĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐiĐiĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃjŃjŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇkŇkŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓlÓlÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔmÔmÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐnŐnŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖoÖoÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ×p××××××××××××××××ŘqŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮrŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚsÚÚÚÚÚÚÚÚÚÚÚÚŰtŰŰŰŰŰŰŰŰŰŰŰŰÜuÜÜÜÜÜÜÜÜÜÜÝvÝvÝÝÝÝÝÝÝÝÝÝŢwŢŢŢŢŢŢŢŢßxßßßßßßßßßßŕyŕŕŕŕŕŕŕŕázááááááááâ{ââââââââă|ăăăăăăăăääääääääĺ~ĺĺĺĺĺĺćććććććććççççççççččččččé‚ééééééęęęęęęęëëëëëëě…ěěěěěěííííííî‡îîîîîîďďďďďďđđđđđđńńńńńńň‹ňňňňóŚóóóóôŤôôôôőŽőőőőöŹöööö÷÷÷÷÷÷řřřřřřůůůůůůúúúúúúűűűűüüüüüüýýýýýýţţţţţţ˙˙l vy{|€‚„…† ‡!!!!!!!!!!!"""""""""""""‰###########Š$$$$$$$$$$$$$‹%%%%%%%%%%%%%%%Ś&&&&&&&&&&&&&&&Ť'''''''''''''''Ž'Ž(((((((((((((((Ź(Ź))))))))))))))))))*******************‘*‘+++++++++++++++++++++’+’,,,,,,,,,,,,,,,,,,,,,,,“,“-------------------------”-”.............................•.•/////////////////////////////////–/–00000000000000000000000000000000000—0—0—111111111111111111111111111111111111111111122222222222222222222222222222222222222222222222™2™2™33333333333333333333333333333333333333ËËËËËËËËËËËËËËËË̙̙̙ĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚ͚͚͚ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍΛΛΛÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎϜϜĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎНННĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐўўŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃҟҟŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓ ÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔˇÔˇÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐ˘Ő˘ŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖŁÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖפפ××××××××××××××ŘĄŘŘŘŘŘŘŘŘŘŘŘŘŘŘ٦٦ŮŮŮŮŮŮŮŮŮŮŮŮÚ§ÚÚÚÚÚÚÚÚÚÚÚÚۨۨŰŰŰŰŰŰŰŰŰŰÜ©ÜÜÜÜÜÜÜÜÜÜÜÜÝŞÝÝÝÝÝÝÝÝÝÝŢ«ŢŢŢŢŢŢŢŢ߬ßßßßßßßßßßŕ­ŕŕŕŕŕŕŕŕá®ááááááááâŻââââââââă°ăăăăăăä±ääääääääĺ˛ĺĺĺĺĺĺćłććććććç´ççççççčµččččččé¶ééééééęęęęęęë¸ëëëëëëěěěěěěíşííííííîîîîîîďĽďďďďđ˝đđđđńľńńńńńńňňňňňňóóóóóóôôôôôôőőőőőőöööö÷Ä÷÷÷÷řřřřřřůůůůůůúúúúúúűűűűüÉüüüüýýýýýýţţţţţţ˙˙ CDFHIJKLMNPQR S!!!!!!!!!!!T"""""""""""U"U###########V$$$$$$$$$$$$$W%%%%%%%%%%%%%X%X&&&&&&&&&&&&&Y&Y'''''''''''''''Z((((((((((((((((([)))))))))))))))))))\*******************]*]+++++++++++++++++++++^+^,,,,,,,,,,,,,,,,,,,,,,,_,_-------------------------`-`.............................a.a///////////////////////////////b/b/b00000000000000000000000000000000000c0c11111111111111111111111111111111111111111d1d1d22222222222222222222222222222222222222222222222e2e2e33333333333333333333333333333333333333333333ËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕááááááááááââââââââââăăăăăăăăääääääääääĺĺĺĺĺĺĺĺććććććććççççççççččččččččééééééęęęęęęęęëëëëëëěěěěěěěěííííííîîîîîîîîďďďďďďđđđđđđńńńńńńňňňňňňóóóóóóôôôôôôőőőőőőöööööö÷÷÷÷řřřřřřůůůůůůúúúúúúűűűűűűüüüüýýýýýýţţţţţţ˙˙  !!!!!!!!!!!!""""""""""""##############$$$$$$$$$$$$$$%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&''''''''''''''''''(((((((((((((((((())))))))))))))))))))********************++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,----------------------------................................//////////////////////////////////000000000000000000000000000000000000000011111111111111111111111111111111111111111111222222222222222222222222222222222222222222222222222233333333333333333333333333333333333333333333333333ËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕááááááááááââââââââââăăăăăăăăääääääääĺĺĺĺĺĺĺĺĺĺććććććććççççççççččččččééééééééęęęęęęęęëëëëëëěěěěěěííííííííîîîîîîďďďďďďđđđđđđńńńńńńňňňňňňóóóóóóôôôôôôőőőőőőöööööö÷÷÷÷÷÷řřřřůůůůůůúúúúúúűűűűűűüüüüýýýýýýţţţţţţ˙˙  !!!!!!!!!!!!""""""""""""##############$$$$$$$$$$$$$$%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&''''''''''''''''(((((((((((((((((())))))))))))))))))))**********************++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,----------------------------..............................////////////////////////////////////0000000000000000000000000000000000000011111111111111111111111111111111111111111111112222222222222222222222222222222222222222222222222233333333333333333333333333333333333333333333333333333333ËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËţËţËţĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚ˙Ě˙Ě˙ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ× × ××××××××××××××Ř ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮ ŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚ ÚÚÚÚÚÚÚÚÚÚÚÚŰ ŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢßßßßßßßßßßßŕŕŕŕŕŕŕŕŕáááááááááâââââââââăăăăăăăăäääääääĺĺĺĺĺĺĺĺĺććććććććççççççççččččččéééééééęęęęęęęëëëëëëěěěěěíííííííîîîîîîďďďďďďđđđđđđń#ńńńńň$ňňňňó%óóóóôôôôôôőőőőőőöööööö÷÷÷÷÷÷řřřřů+ůůůůúúúúúúűűűűűűüüüüýýýýýýţţţţţţ˙˙Ö Ţßáăäčéęëěí î!!!!!!!!!!!ď"""""""""""đ#############ń$$$$$$$$$$$ň$ň%%%%%%%%%%%%%ó&&&&&&&&&&&&&ô&ô'''''''''''''''ő(((((((((((((((((ö)))))))))))))))))÷)÷*******************ř*ř+++++++++++++++++++ů+ů,,,,,,,,,,,,,,,,,,,,,,,ú,ú-------------------------ű-ű...........................ü.ü///////////////////////////////ý/ý/ý00000000000000000000000000000000000ţ0ţ11111111111111111111111111111111111111111˙1˙1˙22222222222222222222222222222222222222222222333333333333333333333333333333333333333333333333333333344ËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚ2Ě2Ě2ĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍ3Í3Í3ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎ4Î4Î4ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎ5Ď5Ď5ĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐ6Đ6Đ6ĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃ7Ń7ŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇ8Ň8ŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓ9Ó9ÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔ:Ô:ÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐ;Ő;ŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖ<Ö<ÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ×=××××××××××××××Ř>Ř>ŘŘŘŘŘŘŘŘŘŘŘŘŮ?Ů?ŮŮŮŮŮŮŮŮŮŮŮŮÚ@ÚÚÚÚÚÚÚÚÚÚÚÚŰAŰŰŰŰŰŰŰŰŰŰŰŰÜBÜÜÜÜÜÜÜÜÜÜÝCÝÝÝÝÝÝÝÝÝÝŢDŢŢŢŢŢŢŢŢßEßßßßßßßßßßŕFŕŕŕŕŕŕŕŕáGááááááááâHââââââăIăăăăăăăăäJääääääĺKĺĺĺĺĺĺćLććććććçMççççççčNččččččéOééééééęęęęęęëQëëëëëëěěěěěěííííííîTîîîîďUďďďďđVđđđđđđńńńńńńňňňňňňóóóóôZôôôôő[őőőőöööööö÷÷÷÷÷÷řřřřřřůůůůúúúúúúűűűűűűüüüüýýýýýýţţţţţţ˙˙šťˇ ¬®°±˛ł´µ¶·¸ą ş!!!!!!!!!!!»"""""""""""Ľ###########˝#˝$$$$$$$$$$$ľ%%%%%%%%%%%%%ż%ż&&&&&&&&&&&&&Ŕ'''''''''''''''Á'Á(((((((((((((((Â(Â)))))))))))))))))Ă*******************Ä*Ä+++++++++++++++++++++Ĺ,,,,,,,,,,,,,,,,,,,,,,,Ć,Ć-------------------------Ç-Ç...........................Č.Č///////////////////////////////É/É00000000000000000000000000000000000Ę0Ę0Ę111111111111111111111111111111111111111Ë1Ë1Ë222222222222222222222222222222222222222222222Ě2Ě2Ě33333333333333333333333333333333333333333333333333333Í3Í3Í4444ËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚeĚeĚeĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍfÍfÍfÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎgÎgÎgÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎhĎhĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐiĐiĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃjŃjŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇkŇkŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓlÓlÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔmÔmÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐnŐnŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖoÖoÖÖÖÖÖÖÖÖÖÖÖÖÖÖ×p×p××××××××××××××ŘqŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮrŮŮŮŮŮŮŮŮŮŮŮŮÚsÚsÚÚÚÚÚÚÚÚÚÚŰtŰŰŰŰŰŰŰŰŰŰŰŰÜuÜÜÜÜÜÜÜÜÜÜÝvÝÝÝÝÝÝÝÝÝÝŢwŢŢŢŢŢŢŢŢßxßßßßßßßßßßŕyŕŕŕŕŕŕŕŕázááááááááââââââââă|ăăăăăăăăääääääääĺ~ĺĺĺĺĺĺćććććććç€ççççççččččččččééééééęęęęęęęëëëëëëě…ěěěěí†ííííííîîîîîîďďďďďďđđđđđđńńńńńńňňňňňňóóóóóóôôôôôôőőőőöŹöööö÷÷÷÷÷÷řřřřřřůůůůú“úúúúűűűűűűüüüüý–ýýýýţţţţţţ˙˙m r s tuvwz|~€„…† ‡!!!!!!!!!!!"""""""""""‰###########Š$$$$$$$$$$$$$‹%%%%%%%%%%%%%Ś&&&&&&&&&&&&&&&Ť'''''''''''''''Ž(((((((((((((((((Ź))))))))))))))))))*****************‘*‘+++++++++++++++++++++’+’,,,,,,,,,,,,,,,,,,,,,“,“-------------------------”-”...........................•.•///////////////////////////////–/–00000000000000000000000000000000000—0—11111111111111111111111111111111111111111222222222222222222222222222222222222222222222™2™2™33333333333333333333333333333333333333333333333333333š3š3š4444444444ËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËË̙̙̙ĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚ͚͚͚ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍΛΛÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎϜϜĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎННĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐўўŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃҟҟŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓ Ó ÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔˇÔˇÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐ˘ŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖŁÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖפ××××××××××××××ŘĄŘĄŘŘŘŘŘŘŘŘŘŘŘŘ٦٦ŮŮŮŮŮŮŮŮŮŮŮŮÚ§ÚÚÚÚÚÚÚÚÚÚۨۨŰŰŰŰŰŰŰŰŰŰÜ©ÜÜÜÜÜÜÜÜÜÜÝŞÝÝÝÝÝÝÝÝÝÝŢ«ŢŢŢŢŢŢŢŢ߬ßßßßßßßßßßŕ­ŕŕŕŕŕŕŕŕá®ááááááâŻââââââââă°ăăăăăăä±ääääääääĺĺĺĺĺĺĺĺććććććććççççççčµččččččé¶ééééééęęęęęęë¸ëëëëëëěěěěěěííííííî»îîîîďĽďďďďđ˝đđđđńľńńńńňżňňňňóóóóóóôôôôôôőőőőőőöööö÷Ä÷÷÷÷řřřřřřůůůůůůúúúúűűűűűűüüüüüüýýýýţţţţţţ˙˙ < = DEGHILNOPQR S!!!!!!!!!!!T"""""""""""U###########V$$$$$$$$$$$$$W%%%%%%%%%%%%%X&&&&&&&&&&&&&Y&Y'''''''''''''Z'Z((((((((((((((([([)))))))))))))))\)\*******************]+++++++++++++++++++++^+^,,,,,,,,,,,,,,,,,,,,,_,_-------------------------`-`...........................a.a/////////////////////////////b/b/b000000000000000000000000000000000c0c0c1111111111111111111111111111111111111d1d1d222222222222222222222222222222222222222222222e2e2e333333333333333333333333333333333333333333333333333f3f3f3f4444444444444444ËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕááááááááââââââââââăăăăăăăăääääääääĺĺĺĺĺĺĺĺććććććććççççççççččččččččééééééęęęęęęęęëëëëëëěěěěěěííííííííîîîîîîďďďďďďđđđđđđńńńńńńňňňňóóóóóóôôôôôôőőőőőőöööööö÷÷÷÷řřřřřřůůůůůůúúúúűűűűűűüüüüüüýýýýţţţţţţ˙˙  !!!!!!!!!!!!""""""""""""############$$$$$$$$$$$$$$%%%%%%%%%%%%%%&&&&&&&&&&&&&&''''''''''''''''(((((((((((((((((())))))))))))))))))**********************++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,----------------------------..............................////////////////////////////////000000000000000000000000000000000000001111111111111111111111111111111111111111112222222222222222222222222222222222222222222222222233333333333333333333333333333333333333333333333333333333444444444444444444444444ËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßŕŕŕŕŕŕŕŕŕŕááááááááââââââââââăăăăăăăăääääääääĺĺĺĺĺĺĺĺććććććććççççççççččččččééééééééęęęęęęëëëëëëëëěěěěěěííííííîîîîîîďďďďďďđđđđđđńńńńńńňňňňňňóóóóóóôôôôőőőőőőöööööö÷÷÷÷÷÷řřřřůůůůůůúúúúűűűűűűüüüüüüýýýýţţţţţţ˙˙  !!!!!!!!!!!!""""""""""""############$$$$$$$$$$$$%%%%%%%%%%%%%%&&&&&&&&&&&&&&&&''''''''''''''''(((((((((((((((())))))))))))))))))))********************++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,--------------------------..............................//////////////////////////////////0000000000000000000000000000000000001111111111111111111111111111111111111111111122222222222222222222222222222222222222222222222233333333333333333333333333333333333333333333333333333333444444444444444444444444444444ĘýĘýĘýËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËţËţËţĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚ˙Ě˙Ě˙ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ× ××××××××××××××Ř Ř ŘŘŘŘŘŘŘŘŘŘŘŘŮ ŮŮŮŮŮŮŮŮŮŮŮŮÚ ÚÚÚÚÚÚÚÚÚÚÚÚŰ ŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßŕŕŕŕŕŕŕŕŕááááááááâââââââââăăăăăăăäääääääĺĺĺĺĺĺĺćććććććçççççççččččččéééééééęęęęęęëëëëëëëěěěěěěííííííîîîîîîďďďďďďđđđđđđńńńńńńňňňňňňóóóóóóôôôôő'őőőőöööööö÷÷÷÷÷÷řřřřůůůůůůúúúúű-űűűűüüüüüüýýýýţţţţţţ˙˙ Ů Ú ßŕăĺćçčéëěí î!!!!!!!!!!!ď"""""""""đ"đ#########ń#ń$$$$$$$$$$$ň%%%%%%%%%%%%%ó&&&&&&&&&&&&&ô&ô'''''''''''''ő'ő(((((((((((((((ö)))))))))))))))))÷)÷*****************ř*ř+++++++++++++++++++ů+ů,,,,,,,,,,,,,,,,,,,,,ú,ú-----------------------ű-ű...........................ü.ü/////////////////////////////ý/ý/ý000000000000000000000000000000000ţ0ţ111111111111111111111111111111111111111˙1˙1˙22222222222222222222222222222222222222222233333333333333333333333333333333333333333333333333333444444444444444444444444444444444ĘĘĘĘĘĘË1Ë1Ë1Ë1ËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚ2Ě2Ě2Ě2ĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍ3Í3Í3ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎ4Î4ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎ5Ď5Ď5ĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐ6Đ6ĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃ7Ń7Ń7ŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇ8Ň8ŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓ9Ó9ÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔ:Ô:ÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐ;Ő;ŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖ<Ö<ÖÖÖÖÖÖÖÖÖÖÖÖÖÖ×=×=××××××××××××××Ř>ŘŘŘŘŘŘŘŘŘŘŘŘŮ?Ů?ŮŮŮŮŮŮŮŮŮŮÚ@Ú@ÚÚÚÚÚÚÚÚÚÚŰAŰŰŰŰŰŰŰŰŰŰŰŰÜBÜÜÜÜÜÜÜÜÜÜÝCÝÝÝÝÝÝÝÝŢDŢŢŢŢŢŢŢŢŢŢßEßßßßßßßßŕFŕŕŕŕŕŕáGááááááááâHââââââââăăăăăăăăäJääääääĺKĺĺĺĺĺĺćLććććććççççççčNččččččéOééééęPęęęęęęëëëëëëěRěěěěíSííííîTîîîîďUďďďďđVđđđđńWńńńńňXňňňňóóóóóóôôôôôôőőőőö\öööö÷÷÷÷÷÷řřřřůůůůůůúúúúúúűűűűüüüüüüýýýýţţţţţţ˙˙śˇ ¤ ­®°˛ł´µ¶·¸ą ş!!!!!!!!!»!»"""""""""Ľ###########˝$$$$$$$$$$$$$ľ%%%%%%%%%%%%%ż&&&&&&&&&&&&&Ŕ'''''''''''''''Á(((((((((((((((Â(Â)))))))))))))))Ă)Ă*****************Ä*Ä+++++++++++++++++++Ĺ+Ĺ,,,,,,,,,,,,,,,,,,,,,Ć,Ć-----------------------Ç-Ç...........................Č.Č/////////////////////////////É/É000000000000000000000000000000000Ę0Ę0Ę1111111111111111111111111111111111111Ë1Ë1Ë2222222222222222222222222222222222222222222Ě2Ě2Ě3333333333333333333333333333333333333333333333333Í3Í3Í3Í444444444444444444444444444444444444ĘĘĘĘĘĘĘĘĘĘĘĘĘĘËdËdËdËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚeĚeĚeĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍfÍfÍfÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎgÎgÎgÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎhĎhĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐiĐiĐiĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃjŃjŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇkŇkŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓlÓlÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔmÔmÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐnŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖoÖoÖÖÖÖÖÖÖÖÖÖÖÖÖÖ×p××××××××××××××ŘqŘqŘŘŘŘŘŘŘŘŘŘŘŘŮrŮŮŮŮŮŮŮŮŮŮŮŮÚsÚÚÚÚÚÚÚÚÚÚŰtŰtŰŰŰŰŰŰŰŰŰŰÜuÜÜÜÜÜÜÜÜÜÜÝvÝÝÝÝÝÝÝÝŢwŢŢŢŢŢŢŢŢŢŢßxßßßßßßßßŕyŕŕŕŕŕŕázááááááááâ{ââââââă|ăăăăăăăăääääääääĺĺĺĺĺĺĺĺććććććç€ççççççčččččččééééééęęęęęë„ëëëëëëěěěěěěííííííîîîîîîďďďďďďđđđđđđńńńńńńňňňňóŚóóóóôôôôôôőőőőőőöööö÷÷÷÷÷÷řřřřů’ůůůůúúúúúúűűűűüüüüüüýýýýţţţţţţ˙˙k p tuvwxyz|~„…† ‡!!!!!!!!!"""""""""""‰###########Š$$$$$$$$$$$‹$‹%%%%%%%%%%%Ś%Ś&&&&&&&&&&&&&Ť'''''''''''''Ž'Ž(((((((((((((((Ź)))))))))))))))))*******************‘+++++++++++++++++++’+’,,,,,,,,,,,,,,,,,,,,,“,“-----------------------”-”...........................•.•/////////////////////////////–/–000000000000000000000000000000000—0—1111111111111111111111111111111111111112222222222222222222222222222222222222222222™2™2™3333333333333333333333333333333333333333333333333š3š3š44444444444444444444444444444444444444444444ĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËË̙̙̙ĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚ͚͚͚ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍΛΛΛÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎϜϜϜĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎННĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐўўŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃҟҟŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓ Ó ÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔˇÔˇÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐ˘Ő˘ŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖŁÖÖÖÖÖÖÖÖÖÖÖÖÖÖפפ××××××××××××××ŘĄŘŘŘŘŘŘŘŘŘŘŘŘ٦ŮŮŮŮŮŮŮŮŮŮŮŮÚ§ÚÚÚÚÚÚÚÚÚÚÚÚۨŰŰŰŰŰŰŰŰŰŰÜ©ÜÜÜÜÜÜÜÜÜÜÝŞÝÝÝÝÝÝÝÝŢ«ŢŢŢŢŢŢŢŢŢŢ߬ßßßßßßßßŕ­ŕŕŕŕŕŕá®ááááááááâŻââââââă°ăăăăăăä±ääääääĺ˛ĺĺĺĺĺĺćłććććććç´ççççççččččččé¶ééééééęęęęęęëëëëëëěąěěěěíşííííî»îîîîďĽďďďďđ˝đđđđńńńńńńňňňňňňóóóóôÁôôôôőőőőőőöööö÷Ä÷÷÷÷řřřřřřůůůůúúúúúúűűűűüüüüüüýýýýţţţţţţ˙˙9 ? GHIJKLNOPQR S!!!!!!!!!T"""""""""""U###########V$$$$$$$$$$$W%%%%%%%%%%%%%X&&&&&&&&&&&&&Y&Y'''''''''''''Z((((((((((((((([([)))))))))))))))\)\*****************]*]+++++++++++++++++++^,,,,,,,,,,,,,,,,,,,,,_,_-----------------------`-`.........................a.a.a///////////////////////////b/b/b0000000000000000000000000000000c0c0c11111111111111111111111111111111111d1d1d2222222222222222222222222222222222222222222e2e2e3333333333333333333333333333333333333333333333333f3f3f44444444444444444444444444444444444444444444444444ĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßŕŕŕŕŕŕŕŕááááááááááââââââââăăăăăăăăääääääääĺĺĺĺĺĺĺĺććććććććççççççččččččččééééééęęęęęęëëëëëëëëěěěěěěííííííîîîîîîďďďďďďđđđđńńńńńńňňňňňňóóóóóóôôôôőőőőőőöööööö÷÷÷÷řřřřřřůůůůúúúúúúűűűűüüüüüüýýýýţţţţţţ˙˙  !!!!!!!!!!""""""""""""############$$$$$$$$$$$$%%%%%%%%%%%%%%&&&&&&&&&&&&&&''''''''''''''''(((((((((((((((())))))))))))))))))********************++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,--------------------------............................////////////////////////////////000000000000000000000000000000000000111111111111111111111111111111111111111122222222222222222222222222222222222222222222222233333333333333333333333333333333333333333333333333333344444444444444444444444444444444444444444444444444444444ĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢŢŢßßßßßßßßßßŕŕŕŕŕŕŕŕááááááááááââââââââăăăăăăăăääääääääĺĺĺĺĺĺĺĺććććććççççççççččččččééééééééęęęęęęëëëëëëěěěěěěííííííîîîîîîďďďďďďđđđđđđńńńńńńňňňňóóóóóóôôôôôôőőőőöööööö÷÷÷÷řřřřřřůůůůúúúúúúűűűűüüüüüüýýýýţţţţţţ˙˙  !!!!!!!!!!""""""""""""############$$$$$$$$$$$$%%%%%%%%%%%%%%&&&&&&&&&&&&&&''''''''''''''(((((((((((((((((())))))))))))))))********************++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,--------------------------............................////////////////////////////////0000000000000000000000000000000000001111111111111111111111111111111111111111222222222222222222222222222222222222222222222233333333333333333333333333333333333333333333333333333344444444444444444444444444444444444444444444444444444444444444ĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘýĘýĘýËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËţËţËţĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚ˙Ě˙Ě˙ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ× ××××××××××××Ř Ř ŘŘŘŘŘŘŘŘŘŘŘŘŮ ŮŮŮŮŮŮŮŮŮŮŮŮÚ ÚÚÚÚÚÚÚÚÚÚŰ ŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢßßßßßßßßßŕŕŕŕŕŕŕŕáááááááááâââââââăăăăăăăäääääääĺĺĺĺĺĺĺććććććçççççççčččččéééééééęęęęęęëëëëëěěěěěíííííî îîîîď!ďďďďđđđđđđńńńńńńňňňňó%óóóóôôôôôôőőőőö(öööö÷÷÷÷ř*řřřřůůůůú,úúúúűűűűü.üüüüýýýýţţţţţţ˙˙ Ř ŰÜâăĺçčéęëěí î!!!!!!!!!ď"""""""""""đ#########ń#ń$$$$$$$$$$$ň%%%%%%%%%%%ó%ó&&&&&&&&&&&ô&ô'''''''''''''ő(((((((((((((((ö(ö)))))))))))))))÷*****************ř*ř+++++++++++++++++++ů+ů,,,,,,,,,,,,,,,,,,,ú,ú-----------------------ű-ű.........................ü.ü///////////////////////////ý/ý/ý0000000000000000000000000000000ţ0ţ0ţ11111111111111111111111111111111111˙1˙1˙22222222222222222222222222222222222222223333333333333333333333333333333333333333333333333444444444444444444444444444444444444444444444444444444444444555ĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘË1Ë1Ë1Ë1ËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚ2Ě2Ě2ĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍ3Í3Í3ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎ4Î4Î4ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎ5Ď5ĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐ6Đ6Đ6ĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃ7Ń7ŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇ8Ň8ŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓ9ÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔ:Ô:ÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐ;Ő;ŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖ<ÖÖÖÖÖÖÖÖÖÖÖÖÖÖ×=×=××××××××××××Ř>ŘŘŘŘŘŘŘŘŘŘŘŘŮ?Ů?ŮŮŮŮŮŮŮŮŮŮÚ@ÚÚÚÚÚÚÚÚÚÚŰAŰAŰŰŰŰŰŰŰŰÜBÜBÜÜÜÜÜÜÜÜÝCÝÝÝÝÝÝÝÝÝÝŢDŢŢŢŢŢŢŢŢßEßßßßßßŕFŕŕŕŕŕŕŕŕáGááááááááââââââââăIăăăăăăäJääääääĺĺĺĺĺĺćLććććććçMççççççččččččéOééééęPęęęęęęëëëëëëěěěěěěííííííîîîîîîďďďďđVđđđđńWńńńńňňňňňňóóóóôZôôôôőőőőőőöööö÷÷÷÷÷÷řřřřůůůůůůúúúúűűűűűűüüüüýýýýţţţţţţ˙˙źˇ Ł ¦ Ş«¬­®°±˛´µ¶·¸ą ş!!!!!!!!!»"""""""""Ľ"Ľ#########˝$$$$$$$$$$$ľ$ľ%%%%%%%%%%%ż&&&&&&&&&&&&&Ŕ'''''''''''''Á'Á(((((((((((((Â(Â)))))))))))))))Ă)Ă*****************Ä+++++++++++++++++++Ĺ+Ĺ,,,,,,,,,,,,,,,,,,,Ć,Ć-----------------------Ç-Ç.........................Č.Č///////////////////////////É/É0000000000000000000000000000000Ę0Ę0Ę11111111111111111111111111111111111Ë1Ë1Ë22222222222222222222222222222222222222222Ě2Ě2Ě33333333333333333333333333333333333333333333333Í3Í3Í444444444444444444444444444444444444444444444444444444444Î4Î4Î4Î555555ĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËdËdËdËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚeĚeĚeĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍfÍfÍfÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎgÎgÎgÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎhĎhĎhĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐiĐiĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃjŃjŃjŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇkŇkŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓlÓlÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔmÔmÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐnŐnŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖoÖoÖÖÖÖÖÖÖÖÖÖÖÖÖÖ×p××××××××××××ŘqŘqŘŘŘŘŘŘŘŘŘŘŘŘŮrŮŮŮŮŮŮŮŮŮŮÚsÚsÚÚÚÚÚÚÚÚÚÚŰtŰŰŰŰŰŰŰŰŰŰÜuÜÜÜÜÜÜÜÜÝvÝÝÝÝÝÝÝÝÝÝŢwŢŢŢŢŢŢŢŢßxßßßßßßŕyŕŕŕŕŕŕŕŕázááááááâ{ââââââââăăăăăăăăääääääĺ~ĺĺĺĺĺĺćććććććççççççčččččččééééééęęęęęęë„ëëëëě…ěěěěí†ííííî‡îîîîďďďďďďđđđđđđńńńńň‹ňňňňóóóóóóôôôôőőőőőőöööö÷÷÷÷÷÷řřřřůůůůůůúúúúűűűűűűüüüüýýýýţţţţţţ˙˙j r uv|}€„…† ‡!!!!!!!!!"""""""""‰###########Š$$$$$$$$$$$‹%%%%%%%%%%%%%Ś&&&&&&&&&&&Ť&Ť'''''''''''''Ž(((((((((((((((Ź)))))))))))))))))*****************‘*‘+++++++++++++++++’+’,,,,,,,,,,,,,,,,,,,“,“-----------------------”-”.......................•.•.•///////////////////////////–/–0000000000000000000000000000000—0—111111111111111111111111111111111111122222222222222222222222222222222222222222™2™2™33333333333333333333333333333333333333333333333š3š3š4444444444444444444444444444444444444444444444444444444›4›4›4›55555555555555ĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËË̙̙̙̙ĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚ͚͚͚ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍΛΛÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎϜϜĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎННĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐўўŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃҟҟŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓ Ó ÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔˇÔˇÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐ˘Ő˘ŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖŁÖÖÖÖÖÖÖÖÖÖÖÖÖÖפפ××××××××××××ŘĄŘŘŘŘŘŘŘŘŘŘŘŘ٦ŮŮŮŮŮŮŮŮŮŮŮŮÚ§ÚÚÚÚÚÚÚÚÚÚۨŰŰŰŰŰŰŰŰŰŰÜ©ÜÜÜÜÜÜÜÜÝŞÝŞÝÝÝÝÝÝÝÝŢ«ŢŢŢŢŢŢŢŢ߬ßßßßßßŕ­ŕŕŕŕŕŕŕŕá®ááááááâŻââââââă°ăăăăăăä±ääääääĺ˛ĺĺĺĺĺĺćłććććç´ççççççčµččččé¶ééééę·ęęęęęęëëëëëëěěěěěěííííííîîîîďĽďďďďđ˝đđđđńńńńńńňňňňóŔóóóóôôôôőÂőőőőöööö÷Ä÷÷÷÷řřřřůĆůůůůúúúúűűűűűűüüüüýýýýţËţţţţ˙˙ @DEFGHJLMNOPQR S!!!!!!!!!T"""""""""U###########V$$$$$$$$$$$W%%%%%%%%%%%X%X&&&&&&&&&&&Y'''''''''''''Z'Z((((((((((((([([)))))))))))))))\)\***************]*]+++++++++++++++++^+^,,,,,,,,,,,,,,,,,,,_,_-----------------------`-`.......................a.a///////////////////////////b/b/b00000000000000000000000000000c0c0c11111111111111111111111111111111111d1d22222222222222222222222222222222222222222e2e2e33333333333333333333333333333333333333333333333f3f3f4444444444444444444444444444444444444444444444444444444g4g4g5555555555555555555555ĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢßßßßßßßßŕŕŕŕŕŕŕŕŕŕááááááááââââââââăăăăăăăăääääääääĺĺĺĺĺĺĺĺććććććççççççççččččččééééééęęęęęęëëëëëëěěěěěěííííííîîîîîîďďďďďďđđđđńńńńńńňňňňňňóóóóôôôôôôőőőőöööööö÷÷÷÷řřřřřřůůůůúúúúűűűűűűüüüüýýýýýýţţţţ˙˙  !!!!!!!!!!""""""""""############$$$$$$$$$$$$%%%%%%%%%%%%&&&&&&&&&&&&&&''''''''''''''(((((((((((((((())))))))))))))))))******************++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,--------------------------..........................//////////////////////////////000000000000000000000000000000000011111111111111111111111111111111111111112222222222222222222222222222222222222222222233333333333333333333333333333333333333333333333333334444444444444444444444444444444444444444444444444444444444445555555555555555555555555555ÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢßßßßßßßßŕŕŕŕŕŕŕŕŕŕááááááááââââââââăăăăăăăăääääääääĺĺĺĺĺĺććććććććççççççččččččééééééééęęęęęęëëëëëëěěěěěěííííîîîîîîďďďďďďđđđđđđńńńńňňňňňňóóóóôôôôôôőőőőöööööö÷÷÷÷řřřřřřůůůůúúúúúúűűűűüüüüýýýýýýţţţţ˙˙  !!!!!!!!!!""""""""""############$$$$$$$$$$$$%%%%%%%%%%%%&&&&&&&&&&&&&&''''''''''''''(((((((((((((((())))))))))))))))******************++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,------------------------............................//////////////////////////////0000000000000000000000000000000000111111111111111111111111111111111111112222222222222222222222222222222222222222222233333333333333333333333333333333333333333333333333444444444444444444444444444444444444444444444444444444444444555555555555555555555555555555555555ÉÉÉüÉüÉüÉüĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘýĘýĘýĘýËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËţËţËţĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚ˙Ě˙Ě˙ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖ× × ××××××××××××Ř ŘŘŘŘŘŘŘŘŘŘŘŘŮ ŮŮŮŮŮŮŮŮŮŮÚ Ú ÚÚÚÚÚÚÚÚŰ Ű ŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢßßßßßßßŕŕŕŕŕŕŕŕŕáááááááâââââââăăăăăăăäääääääĺĺĺĺĺĺćććććććçççççčččččéééééééęęęęęęëëëëëëěěěěěěííííî îîîîď!ďďďďđđđđđđńńńńň$ňňňňóóóóô&ôôôôőőőőö(öööö÷÷÷÷ř*řřřřůůůůúúúúúúűűűűüüüüýýýýýýţţţţ˙˙ Ů Üßŕáâăäćčéęëěí î!!!!!!!!!ď"""""""""đ#########ń#ń$$$$$$$$$ň$ň%%%%%%%%%%%ó&&&&&&&&&&&ô&ô'''''''''''ő'ő(((((((((((((ö(ö)))))))))))))))÷*****************ř+++++++++++++++++ů+ů,,,,,,,,,,,,,,,,,,,ú,ú---------------------ű-ű.........................ü.ü/////////////////////////ý/ý/ý00000000000000000000000000000ţ0ţ0ţ111111111111111111111111111111111˙1˙1˙2222222222222222222222222222222222222233333333333333333333333333333333333333333333333444444444444444444444444444444444444444444444444444444444555555555555555555555555555555555555555ÉÉÉÉÉÉÉÉÉÉĘ0Ę0Ę0Ę0ĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘË1Ë1Ë1ËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚ2Ě2Ě2ĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍ3Í3Í3ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎ4Î4ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎ5Ď5Ď5ĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐ6Đ6ĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃ7Ń7ŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇ8Ň8ŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓ9Ó9ÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔ:Ô:ÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐ;Ő;ŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖ<ÖÖÖÖÖÖÖÖÖÖÖÖÖÖ×=××××××××××××Ř>Ř>ŘŘŘŘŘŘŘŘŘŘŮ?ŮŮŮŮŮŮŮŮŮŮŮŮÚ@ÚÚÚÚÚÚÚÚÚÚŰAŰŰŰŰŰŰŰŰÜBÜBÜÜÜÜÜÜÜÜÝCÝÝÝÝÝÝÝÝŢDŢŢŢŢŢŢŢŢßEßßßßßßŕFŕŕŕŕŕŕŕŕáGááááááâHââââââăIăăăăăăäJääääĺKĺĺĺĺĺĺćLććććććççççççččččččéOééééęPęęęęëQëëëëěRěěěěíSííííîîîîîîďďďďđVđđđđńńńńńńňňňňóYóóóóôôôôőőőőőőöööö÷÷÷÷÷÷řřřřůůůůúúúúúúűűűűüüüüýýýýýýţţţţ˙˙žˇ Ł §Ş±˛łµ¶·¸ą ş!!!!!!!»!»"""""""""Ľ#########˝$$$$$$$$$$$ľ%%%%%%%%%%%ż%ż&&&&&&&&&&&Ŕ'''''''''''''Á(((((((((((((((Â)))))))))))))))Ă)Ă***************Ä*Ä+++++++++++++++++Ĺ,,,,,,,,,,,,,,,,,,,Ć,Ć---------------------Ç-Ç.......................Č.Č.Č/////////////////////////É/É00000000000000000000000000000Ę0Ę0Ę111111111111111111111111111111111Ë1Ë1Ë222222222222222222222222222222222222222Ě2Ě2Ě333333333333333333333333333333333333333333333Í3Í3Í44444444444444444444444444444444444444444444444444444Î4Î4Î4Î555555555555555555555555555555555555555555ÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘcĘcĘcĘcĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËdËdËdËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚeĚeĚeĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍfÍfÍfÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎgÎgÎgÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎhĎhĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐiĐiĐiĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃjŃjŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇkŇkŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓlÓlÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔmÔmÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐnŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖoÖoÖÖÖÖÖÖÖÖÖÖÖÖ×p×p××××××××××××ŘqŘŘŘŘŘŘŘŘŘŘŮrŮrŮŮŮŮŮŮŮŮŮŮÚsÚÚÚÚÚÚÚÚÚÚŰtŰŰŰŰŰŰŰŰŰŰÜuÜÜÜÜÜÜÜÜÝvÝÝÝÝÝÝÝÝŢwŢŢŢŢŢŢŢŢßxßßßßßßŕyŕŕŕŕŕŕŕŕázááááááâ{ââââââă|ăăăăăăääääääĺ~ĺĺĺĺĺĺćććććç€ççççčččččččééééééęęęęęęëëëëëëěěěěěěííííî‡îîîîďďďďďđđđđńŠńńńńňňňňňňóóóóôôôôőŽőőőőöööö÷÷÷÷÷řřřřůůůůú“úúúúűűűűüüüüýýýýýýţţţţ˙˙h q s uvyz{|}~€‚„† ‡!!!!!!!"""""""""""‰#########Š$$$$$$$$$$$‹%%%%%%%%%%%Ś&&&&&&&&&&&Ť&Ť'''''''''''Ž'Ž(((((((((((((Ź(Ź))))))))))))))***************‘*‘+++++++++++++++++’+’,,,,,,,,,,,,,,,,,“,“---------------------”-”.......................•.•///////////////////////////–/–00000000000000000000000000000—0—11111111111111111111111111111111111222222222222222222222222222222222222222™2™2™3333333333333333333333333333333333333333333š3š3š3š444444444444444444444444444444444444444444444444444›4›4›4›55555555555555555555555555555555555555555555555555ÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘ—Ę—Ę—ĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËË̙̙̙ĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚ͚͚͚ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍΛΛΛÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎϜϜϜĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎННĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐўўўŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃҟҟŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓ Ó ÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔˇÔˇÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐ˘Ő˘ŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖŁÖÖÖÖÖÖÖÖÖÖÖÖÖÖפ××××××××××××ŘĄŘŘŘŘŘŘŘŘŘŘŘŘ٦ŮŮŮŮŮŮŮŮŮŮÚ§ÚÚÚÚÚÚÚÚÚÚۨŰŰŰŰŰŰŰŰŰŰÜ©ÜÜÜÜÜÜÜÜÝŞÝÝÝÝÝÝÝÝŢ«ŢŢŢŢŢŢŢŢ߬ßßßßßßŕ­ŕŕŕŕŕŕŕŕááááááááâŻââââââăăăăăăä±ääääääĺ˛ĺĺĺĺĺĺććććććç´ççççčµččččé¶ééééę·ęęęęë¸ëëëëěąěěěěíşííííîîîîîîďďďďđ˝đđđđńńńńňżňňňňóóóóôÁôôôôőőőőöööööö÷÷÷÷řřřřůůůůůůúúúúűűűűüüüüýýýýýýţţţţ˙˙8; CDKLMOPQR S S!!!!!!!T"""""""""U"U#########V$$$$$$$$$W$W%%%%%%%%%%%X&&&&&&&&&&&Y'''''''''''''Z((((((((((((([([)))))))))))))))\***************]*]+++++++++++++++++^+^,,,,,,,,,,,,,,,,,_,_---------------------`-`.......................a.a/////////////////////////b/b/b000000000000000000000000000c0c0c111111111111111111111111111111111d1d222222222222222222222222222222222222222e2e2e3333333333333333333333333333333333333333333f3f3f44444444444444444444444444444444444444444444444444444g4g4g5555555555555555555555555555555555555555555555555555555555ÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢßßßßßßßßŕŕŕŕŕŕŕŕááááááááááââââââăăăăăăăăääääääääĺĺĺĺĺĺććććććććççççççččččččééééééęęęęęęëëëëëëěěěěěěííííîîîîîîďďďďďďđđđđńńńńńńňňňňóóóóóóôôôôőőőőöööööö÷÷÷÷řřřřůůůůůůúúúúűűűűüüüüýýýýýýţţţţ˙˙  !!!!!!!!!!""""""""""############$$$$$$$$$$%%%%%%%%%%%%%%&&&&&&&&&&&&''''''''''''''(((((((((((((())))))))))))))))))****************++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,------------------------..........................////////////////////////////000000000000000000000000000000001111111111111111111111111111111111111122222222222222222222222222222222222222222233333333333333333333333333333333333333333333333344444444444444444444444444444444444444444444444444444444445555555555555555555555555555555555555555555555555555555555555555ÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢßßßßßßßßŕŕŕŕŕŕŕŕááááááááââââââââăăăăăăăăääääääääĺĺĺĺĺĺććććććççççççččččččččééééééęęęęëëëëëëěěěěěěííííííîîîîďďďďďďđđđđđđńńńńňňňňóóóóóóôôôôőőőőőőöööö÷÷÷÷řřřřřřůůůůúúúúűűűűüüüüýýýýýýţţţţ˙˙  !!!!!!!!!!""""""""""############$$$$$$$$$$%%%%%%%%%%%%&&&&&&&&&&&&&&''''''''''''(((((((((((((((())))))))))))))))******************++++++++++++++++++,,,,,,,,,,,,,,,,,,,,------------------------..........................////////////////////////////0000000000000000000000000000000011111111111111111111111111111111111122222222222222222222222222222222222222222233333333333333333333333333333333333333333333333344444444444444444444444444444444444444444444444444444444555555555555555555555555555555555555555555555555555555555555555555556666ÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉüÉüÉüÉüĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘýĘýĘýĘýËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËţËţËţËţĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚ˙Ě˙Ě˙ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖ× ××××××××××××Ř ŘŘŘŘŘŘŘŘŘŘŘŘŮ ŮŮŮŮŮŮŮŮŮŮÚ ÚÚÚÚÚÚÚÚÚÚŰ ŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢßßßßßßßŕŕŕŕŕŕŕáááááááâââââââăăăăăăăäääääääĺĺĺĺĺĺćććććçççççčččččččééééééęęęęëëëëëěěěěěíííííîîîîď!ďďďďđđđđđđńńńńňňňňó%óóóóôôôôő'őőőőöööö÷÷÷÷řřřřřřůůůůúúúúűűűűüüüüý/ýýýýţţţţ˙˙Ô × Ú ÜŢßâăäĺçčéęëěí î!!!!!!!!!ď"""""""""đ#########ń#ń$$$$$$$$$ň%%%%%%%%%%%ó&&&&&&&&&&&ô&ô'''''''''''ő(((((((((((((ö(ö)))))))))))))÷)÷***************ř*ř+++++++++++++++ů+ů,,,,,,,,,,,,,,,,,ú,ú---------------------ű-ű.....................ü.ü.ü///////////////////////ý/ý/ý000000000000000000000000000ţ0ţ0ţ1111111111111111111111111111111˙1˙1˙2222222222222222222222222222222222223333333333333333333333333333333333333333333334444444444444444444444444444444444444444444444444444455555555555555555555555555555555555555555555555555555555555555566666666ÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘ0Ę0Ę0Ę0ĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘË1Ë1Ë1ËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚ2Ě2Ě2ĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍ3Í3Í3ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎ4Î4Î4ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎ5Ď5ĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐ6Đ6ĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃ7Ń7ŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇ8Ň8ŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓ9Ó9ÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔ:ÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐ;Ő;ŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖ<ÖÖÖÖÖÖÖÖÖÖÖÖ×=×=××××××××××Ř>Ř>ŘŘŘŘŘŘŘŘŘŘŮ?ŮŮŮŮŮŮŮŮŮŮÚ@ÚÚÚÚÚÚÚÚÚÚŰAŰŰŰŰŰŰŰŰÜBÜÜÜÜÜÜÜÜÝCÝCÝÝÝÝÝÝŢDŢŢŢŢŢŢŢŢßEßßßßßßŕFŕŕŕŕŕŕáGááááááâHââââââăIăăăăăăäJääääĺKĺĺĺĺĺĺććććććçMççççčNččččéOééééęPęęęęëQëëëëěěěěěěííííîTîîîîďďďďđVđđđđńńńńňXňňňňóóóóôôôôôôőőőőöööö÷÷÷÷ř^řřřřůůůůúúúúűűűűüüüüüüýýýýţţţţ˙˙ś ¤ §©¬­˛łµ¶·¸ą ş!!!!!!!!!»"""""""""Ľ#########˝$$$$$$$$$$$ľ%%%%%%%%%%%ż&&&&&&&&&&&Ŕ'''''''''''''Á(((((((((((((Â)))))))))))))))Ă***************Ä*Ä+++++++++++++++Ĺ+Ĺ,,,,,,,,,,,,,,,,,Ć,Ć---------------------Ç-Ç.....................Č.Č/////////////////////////É/É00000000000000000000000000000Ę0Ę1111111111111111111111111111111Ë1Ë1Ë2222222222222222222222222222222222222Ě2Ě2Ě33333333333333333333333333333333333333333Í3Í3Í3Í4444444444444444444444444444444444444444444444444Î4Î4Î4Î55555555555555555555555555555555555555555555555555555555555Ď5Ď5Ď5Ď666666666666ÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘcĘcĘcĘcĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËdËdËdËdËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚeĚeĚeĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍfÍfÍfÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎgÎgÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎhĎhĎhĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐiĐiĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃjŃjŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇkŇkŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓlÓlÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔmÔmÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐnŐnŐŐŐŐŐŐŐŐŐŐŐŐÖoÖoÖÖÖÖÖÖÖÖÖÖÖÖ×p××××××××××××ŘqŘŘŘŘŘŘŘŘŘŘŮrŮrŮŮŮŮŮŮŮŮÚsÚsÚÚÚÚÚÚÚÚŰtŰŰŰŰŰŰŰŰÜuÜuÜÜÜÜÜÜÜÜÝvÝÝÝÝÝÝŢwŢŢŢŢŢŢŢŢßxßßßßßßŕyŕŕŕŕŕŕázááááááâ{ââââââă|ăăăăăăääääääĺ~ĺĺĺĺćććććććççççççččččččééééééęęęęęęëëëëě…ěěěěííííííîîîîďďďďďđđđđńŠńńńńňňňňóóóóôŤôôôôőőőőöööö÷÷÷÷÷řřřřůůůůúúúúűűűűü•üüüüýýýýţţţţ˙˙n r x{|}~‚„…† ‡!!!!!!!!!"""""""""‰#########Š$$$$$$$$$‹$‹%%%%%%%%%Ś%Ś&&&&&&&&&Ť&Ť'''''''''''Ž'Ž(((((((((((Ź(Ź))))))))))))))*************‘*‘+++++++++++++++’+’,,,,,,,,,,,,,,,,,,,“---------------------”-”.....................•.•/////////////////////////–/–000000000000000000000000000—0—0—111111111111111111111111111111112222222222222222222222222222222222222™2™2™33333333333333333333333333333333333333333š3š3š4444444444444444444444444444444444444444444444444›4›4›4›55555555555555555555555555555555555555555555555555555555555ś5ś5ś5ś66666666666666666666ÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘ—Ę—Ę—ĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËË̙̙̙ĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚ͚͚ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍΛΛΛÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎϜϜĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎНННĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐўўўŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃҟҟŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓ ÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔˇÔˇÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐ˘Ő˘ŐŐŐŐŐŐŐŐŐŐŐŐÖŁÖŁÖÖÖÖÖÖÖÖÖÖפפ××××××××××ŘĄŘĄŘŘŘŘŘŘŘŘŘŘ٦ŮŮŮŮŮŮŮŮŮŮÚ§ÚÚÚÚÚÚÚÚۨۨŰŰŰŰŰŰŰŰÜ©ÜÜÜÜÜÜÜÜÝŞÝÝÝÝÝÝŢ«ŢŢŢŢŢŢŢŢ߬ßßßßßßŕ­ŕŕŕŕŕŕá®ááááááâŻââââââă°ăăăăä±ääääääĺ˛ĺĺĺĺćłććććç´ççççčµččččé¶ééééę·ęęęęë¸ëëëëěěěěíşííííî»îîîîďďďďđ˝đđđđńńńńňňňňóŔóóóóôôôôőőőőöĂöööö÷÷÷÷řřřřůůůůúúúúűűűűűűüüüüýýýýţţţţ˙˙6; ? ACEFIJLMOPQR S!!!!!!!!!T"""""""""U#########V$$$$$$$$$W%%%%%%%%%%%X&&&&&&&&&&&Y'''''''''''''Z((((((((((((([)))))))))))))\)\***************]+++++++++++++++++^,,,,,,,,,,,,,,,,,,,_,_-----------------`-`-`.....................a.a///////////////////////b/b/b0000000000000000000000000c0c0c1111111111111111111111111111111d1d1d22222222222222222222222222222222222e2e2e33333333333333333333333333333333333333333f3f3f4444444444444444444444444444444444444444444444444g4g4g55555555555555555555555555555555555555555555555555555555555h5h5h5h6666666666666666666666666666ÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢŢŢßßßßßßßßŕŕŕŕŕŕŕŕááááááááââââââââăăăăăăääääääääĺĺĺĺĺĺććććććççççççččččččééééééęęęęęęëëëëěěěěěěííííííîîîîďďďďďďđđđđńńńńňňňňňňóóóóôôôôőőőőőőöööö÷÷÷÷řřřřůůůůúúúúűűűűűűüüüüýýýýţţţţ˙˙  !!!!!!!!!!""""""""""##########$$$$$$$$$$%%%%%%%%%%%%&&&&&&&&&&&&''''''''''''''(((((((((((((())))))))))))))******************++++++++++++++++++,,,,,,,,,,,,,,,,,,,,--------------------..........................//////////////////////////000000000000000000000000000000111111111111111111111111111111111111222222222222222222222222222222222222222233333333333333333333333333333333333333333333334444444444444444444444444444444444444444444444444444445555555555555555555555555555555555555555555555555555555555555555666666666666666666666666666666666666ČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢßßßßßßßßŕŕŕŕŕŕŕŕááááááááââââââăăăăăăăăääääääĺĺĺĺĺĺĺĺććććććççççççččččččééééęęęęęęëëëëëëěěěěííííííîîîîîîďďďďđđđđńńńńńńňňňňóóóóôôôôôôőőőőöööö÷÷÷÷řřřřůůůůúúúúúúűűűűüüüüýýýýţţţţ˙˙  !!!!!!!!!!""""""""##########$$$$$$$$$$$$%%%%%%%%%%&&&&&&&&&&&&&&''''''''''''(((((((((((((())))))))))))))))****************++++++++++++++++++,,,,,,,,,,,,,,,,,,,,--------------------........................////////////////////////////0000000000000000000000000000001111111111111111111111111111111111222222222222222222222222222222222222222233333333333333333333333333333333333333333333444444444444444444444444444444444444444444444444444444555555555555555555555555555555555555555555555555555555555555555566666666666666666666666666666666666666666666ČČČČČČČČČűČűČűČűÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉüÉüÉüÉüĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘýĘýĘýĘýËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËţËţËţĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚ˙Ě˙Ě˙ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖ× × ××××××××××Ř Ř ŘŘŘŘŘŘŘŘŮ Ů ŮŮŮŮŮŮŮŮÚ Ú ÚÚÚÚÚÚÚÚŰ ŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢßßßßßßßŕŕŕŕŕŕŕáááááááâââââăăăăăăăäääääĺĺĺĺĺĺĺććććććççççççččččččééééęęęęęëëëëëěěěěíííííîîîîîîďďďďđđđđń#ńńńńňňňňóóóóô&ôôôôőőőőöööö÷÷÷÷řřřřůůůůú,úúúúűűűűüüüüýýýýţţţţ˙˙Ó Ř ÜŢŕăäĺćçéęëěí î!!!!!!!!!ď"""""""đ#########ń$$$$$$$$$$$ň%%%%%%%%%ó&&&&&&&&&&&ô&ô'''''''''''ő(((((((((((ö(ö)))))))))))))÷)÷*************ř*ř+++++++++++++++ů+ů,,,,,,,,,,,,,,,,,ú,ú-----------------ű-ű.....................ü.ü///////////////////////ý/ý/ý0000000000000000000000000ţ0ţ0ţ11111111111111111111111111111˙1˙1˙22222222222222222222222222222222223333333333333333333333333333333333333333344444444444444444444444444444444444444444444444444455555555555555555555555555555555555555555555555555555555555666666666666666666666666666666666666666666666666ČČČČČČČČČČČČČČČČÉ/É/É/É/ÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘ0Ę0Ę0Ę0ĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘË1Ë1Ë1ËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚ2Ě2Ě2ĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍ3Í3Í3ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎ4Î4Î4ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎ5Ď5Ď5ĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐ6Đ6Đ6ĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃ7Ń7ŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇ8Ň8ŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓ9Ó9ÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔ:ÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐ;Ő;ŐŐŐŐŐŐŐŐŐŐŐŐÖ<ÖÖÖÖÖÖÖÖÖÖÖÖ×=×=××××××××××Ř>ŘŘŘŘŘŘŘŘŘŘŮ?ŮŮŮŮŮŮŮŮŮŮÚ@ÚÚÚÚÚÚÚÚŰAŰŰŰŰŰŰŰŰÜBÜÜÜÜÜÜÜÜÝCÝÝÝÝÝÝÝÝŢDŢŢŢŢŢŢßEßßßßßßŕFŕŕŕŕŕŕáGááááááâHââââăIăăăăăăäJääääĺKĺĺĺĺćLććććçMççççčNččččéOééééęPęęęęëëëëěRěěěěííííîTîîîîďďďďđVđđđđńńńńňňňňóYóóóóôôôôőőőőöööö÷÷÷÷ř^řřů_ůůůůúúúúűűűűüüüüýýýýţţţţ˙˙  Ą ¦ ©«­®±˛´µ·¸ą ş!!!!!!!!!»"""""""Ľ#########˝$$$$$$$$$ľ$ľ%%%%%%%%%ż&&&&&&&&&&&Ŕ'''''''''''Á'Á(((((((((((Â)))))))))))))Ă)Ă*************Ä*Ä+++++++++++++++Ĺ+Ĺ,,,,,,,,,,,,,,,,,Ć,Ć-----------------Ç-Ç.....................Č.Č///////////////////////É/É000000000000000000000000000Ę0Ę11111111111111111111111111111Ë1Ë1Ë22222222222222222222222222222222222Ě2Ě2Ě333333333333333333333333333333333333333Í3Í3Í44444444444444444444444444444444444444444444444Î4Î4Î4Î5555555555555555555555555555555555555555555555555555555Ď5Ď5Ď5Ď6666666666666666666666666666666666666666666666666666ČČČČČČČČČČČČČČČČČČČČČČČČÉbÉbÉbÉbÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘcĘcĘcĘcĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËdËdËdËdËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚeĚeĚeĚeĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍfÍfÍfÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎgÎgÎgÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎhĎhĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐiĐiĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃjŃjŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇkŇkŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓlÓlÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔmÔmÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐnŐŐŐŐŐŐŐŐŐŐŐŐÖoÖoÖÖÖÖÖÖÖÖÖÖÖÖ×p××××××××××ŘqŘqŘŘŘŘŘŘŘŘŮrŮrŮŮŮŮŮŮŮŮÚsÚÚÚÚÚÚÚÚŰtŰtŰŰŰŰŰŰÜuÜuÜÜÜÜÜÜÝvÝÝÝÝÝÝÝÝŢwŢŢŢŢŢŢßxßßßßßßŕyŕŕŕŕŕŕázááááááââââââă|ăăăăăăääääääĺ~ĺĺĺĺćććććç€ççççčččččééééééęęęęë„ëëëëěěěěí†ííííîîîîďďďďďđđđđńńńńň‹ňňňňóóóóôôôôőőőőöööö÷÷÷÷÷řřřřůůůůúúúúűűűűüüüüýýýýţţţţ˙˙no twy{|}€‚„…† ‡!!!!!!!!"""""""‰#########Š$$$$$$$$$‹%%%%%%%%%Ś%Ś&&&&&&&&&Ť&Ť'''''''''''Ž(((((((((((Ź(Ź)))))))))))))***************‘+++++++++++++++’+’,,,,,,,,,,,,,,,,,“,“-----------------”-”.....................•.•///////////////////////–/–0000000000000000000000000—0—0—11111111111111111111111111111122222222222222222222222222222222222™2™2™333333333333333333333333333333333333333š3š3š444444444444444444444444444444444444444444444›4›4›4›5555555555555555555555555555555555555555555555555555555ś5ś5ś5ś666666666666666666666666666666666666666666666666666666666666ČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉ–É–É–É–É–ÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘ—Ę—Ę—ĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËË̙̙̙ĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚ͚͚͚ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍΛΛÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎϜϜϜĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎННĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐўўŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃҟҟŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓ Ó ÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔˇÔˇÔÔÔÔÔÔÔÔÔÔÔÔŐ˘Ő˘ŐŐŐŐŐŐŐŐŐŐŐŐÖŁÖŁÖÖÖÖÖÖÖÖÖÖפפ××××××××××ŘĄŘŘŘŘŘŘŘŘŘŘ٦ŮŮŮŮŮŮŮŮÚ§Ú§ÚÚÚÚÚÚÚÚۨŰŰŰŰŰŰŰŰÜ©ÜÜÜÜÜÜÝŞÝÝÝÝÝÝÝÝŢ«ŢŢŢŢŢŢ߬ßßßßßßŕ­ŕŕŕŕŕŕá®ááááâŻââââââă°ăăăăä±ääääääĺĺĺĺĺĺććććććççççççččččé¶ééééę·ęęęęëëëëěąěěěěííííî»îîîîďďďďđ˝đđńľńńńńňňňňóóóóôôôôőÂőőöĂöööö÷÷÷÷řřřřůůůůúúúúűűűűüüüüýýýýţţţţ˙˙ < = ADFIJKLMNOPQR S!!!!!!!T"""""""""U#########V$$$$$$$$$W%%%%%%%%%X&&&&&&&&&&&Y'''''''''''Z'Z((((((((((([)))))))))))))\)\*************]*]+++++++++++++^+^,,,,,,,,,,,,,,,,,_,_-----------------`-`...................a.a.a/////////////////////b/b/b0000000000000000000000000c0c11111111111111111111111111111d1d1d222222222222222222222222222222222e2e2e3333333333333333333333333333333333333f3f3f3f444444444444444444444444444444444444444444444g4g4g5555555555555555555555555555555555555555555555555555555h5h5h5h66666666666666666666666666666666666666666666666666666666666666666666ČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢßßßßßßßßŕŕŕŕŕŕŕŕááááááââââââââăăăăăăääääääĺĺĺĺĺĺććććććççççççččččččééééééęęęęëëëëëëěěěěííííííîîîîďďďďďďđđđđńńńńňňňňóóóóôôôôôôőőőőöööö÷÷÷÷řřřřůůůůúúúúűűűűüüüüýýýýţţţţ˙˙  !!!!!!!!""""""""""##########$$$$$$$$$$%%%%%%%%%%&&&&&&&&&&&&''''''''''''(((((((((((((())))))))))))))****************++++++++++++++++,,,,,,,,,,,,,,,,,,,,--------------------......................//////////////////////////000000000000000000000000000000111111111111111111111111111111112222222222222222222222222222222222222233333333333333333333333333333333333333333344444444444444444444444444444444444444444444444444445555555555555555555555555555555555555555555555555555555555556666666666666666666666666666666666666666666666666666666666666666666666666666ČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢßßßßßßßßŕŕŕŕŕŕŕŕááááááââââââââăăăăăăääääääĺĺĺĺĺĺććććććççççççččččččééééęęęęęęëëëëěěěěěěííííîîîîîîďďďďđđđđńńńńňňňňňňóóóóôôôôőőőőöööö÷÷÷÷řřřřůůůůúúúúűűűűüüüüýýýýţţţţ˙˙  !!!!!!!!""""""""""########$$$$$$$$$$%%%%%%%%%%%%&&&&&&&&&&''''''''''''''(((((((((((())))))))))))))****************++++++++++++++++,,,,,,,,,,,,,,,,,,,,--------------------......................//////////////////////////0000000000000000000000000000111111111111111111111111111111112222222222222222222222222222222222222233333333333333333333333333333333333333333344444444444444444444444444444444444444444444444444555555555555555555555555555555555555555555555555555555555555666666666666666666666666666666666666666666666666666666666666666666666666667777777777ČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČűČűČűČűÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉüÉüÉüÉüĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘýĘýĘýËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËţËţËţĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚ˙Ě˙Ě˙ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖ× × ××××××××Ř Ř ŘŘŘŘŘŘŘŘŮ Ů ŮŮŮŮŮŮŮŮÚ ÚÚÚÚÚÚÚÚŰ ŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝŢŢŢŢŢŢŢßßßßßßßŕŕŕŕŕŕŕáááááâââââââăăăăăăäääääĺĺĺĺĺćććććçççççčččččééééęęęęęëëëëěěěěěííííî îîîîďďďďđđđđń#ńńň$ňňňňóóóóôôôôőőőőöööö÷÷÷÷řřřřůůůůúúúúűűűűüüüüýýýýţţţţ˙˙ ŰŢßáăäéęëěí î!!!!!!!ď"""""""""đ#######ń$$$$$$$$$ň%%%%%%%%%ó%ó&&&&&&&&&ô'''''''''''ő'ő(((((((((((ö)))))))))))))÷*************ř*ř+++++++++++++++ů,,,,,,,,,,,,,,,,,ú,ú-----------------ű-ű...................ü.ü/////////////////////ý/ý/ý00000000000000000000000ţ0ţ0ţ111111111111111111111111111˙1˙1˙22222222222222222222222222222222333333333333333333333333333333333333333444444444444444444444444444444444444444444444445555555555555555555555555555555555555555555555555555555666666666666666666666666666666666666666666666666666666666666666666666677777777777777ČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉ/É/É/É/ÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘ0Ę0Ę0Ę0ĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘË1Ë1Ë1Ë1ËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚ2Ě2Ě2ĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍ3Í3Í3ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎ4Î4ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎ5Ď5ĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐ6Đ6ĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃ7Ń7ŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇ8Ň8ŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓ9Ó9ÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔ:ÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐ;ŐŐŐŐŐŐŐŐŐŐŐŐÖ<ÖÖÖÖÖÖÖÖÖÖÖÖ×=××××××××××Ř>ŘŘŘŘŘŘŘŘŘŘŮ?ŮŮŮŮŮŮŮŮÚ@ÚÚÚÚÚÚÚÚŰAŰAŰŰŰŰŰŰÜBÜÜÜÜÜÜÜÜÝCÝÝÝÝÝÝŢDŢŢŢŢŢŢßEßßßßßßŕFŕŕŕŕŕŕááááááâHââââăIăăăăăăäJääääĺKĺĺĺĺćLććććççççççččččéOééééęęęęëQëëëëěěěěíSííííîîîîďďďďđVđđđđńńńńňňňňóóóóôôôôőőőőöööö÷÷÷÷řřřřůůůůúúúúűűűűüüüüýýýýţţţţ˙˙ ¨©¬®°±˛ł´µ¶·¸ą ş!!!!!!!»"""""""Ľ"Ľ#######˝$$$$$$$$$ľ%%%%%%%%%ż&&&&&&&&&&&Ŕ'''''''''''Á(((((((((((Â(Â)))))))))))Ă)Ă*************Ä+++++++++++++++Ĺ+Ĺ,,,,,,,,,,,,,,,Ć,Ć-----------------Ç-Ç...................Č.Č/////////////////////É/É0000000000000000000000000Ę0Ę111111111111111111111111111Ë1Ë1Ë222222222222222222222222222222222Ě2Ě2Ě33333333333333333333333333333333333Í3Í3Í3Í4444444444444444444444444444444444444444444Î4Î4Î4Î555555555555555555555555555555555555555555555555555Ď5Ď5Ď5Ď66666666666666666666666666666666666666666666666666666666666666666Đ6Đ6Đ6Đ6Đ777777777777777777ČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉbÉbÉbÉbÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘcĘcĘcĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËdËdËdËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚeĚeĚeĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍfÍfÍfÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎgÎgÎgÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎhĎhĎhĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐiĐiĐiĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃjŃjŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇkŇkŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓlÓlÓÓÓÓÓÓÓÓÓÓÓÓÔmÔmÔÔÔÔÔÔÔÔÔÔÔÔŐnŐnŐŐŐŐŐŐŐŐŐŐÖoÖoÖÖÖÖÖÖÖÖÖÖ×p×p××××××××ŘqŘqŘŘŘŘŘŘŘŘŮrŮŮŮŮŮŮŮŮÚsÚsÚÚÚÚÚÚÚÚŰtŰŰŰŰŰŰÜuÜÜÜÜÜÜÜÜÝvÝÝÝÝÝÝŢwŢŢŢŢŢŢßxßßßßßßŕyŕŕŕŕázááááááâ{ââââă|ăăăăăăääääääĺĺĺĺĺĺććććç€ççççčččččééééęęęęęë„ëëě…ěěěěííííî‡îîďďďďďđđđđńńńńňňňňóŚóóôŤôôőŽőőöŹöö÷÷÷ř‘řřů’ůůúúúúűűűűüüüüýýýýţţţţ˙˙lmno p q r wxz|€‚„…† ‡!!!!!!!"""""""‰#########Š$$$$$$$$$‹%%%%%%%%%Ś&&&&&&&&&Ť&Ť'''''''''Ž'Ž(((((((((Ź(Ź))))))))))))*************‘*‘+++++++++++++’+’,,,,,,,,,,,,,,,“,“-----------------”-”.................•.•.•/////////////////////–/–00000000000000000000000—0—0—1111111111111111111111111111222222222222222222222222222222222™2™2™33333333333333333333333333333333333š3š3š4444444444444444444444444444444444444444444›4›4›4›555555555555555555555555555555555555555555555555555ś5ś5ś5ś66666666666666666666666666666666666666666666666666666666666666666ť6ť6ť6ť7777777777777777777777777777ȕȕȕČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉ–É–É–É–É–ÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘ—Ę—Ę—Ę—ĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËË̙̙̙ĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚ͚͚ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍΛΛΛÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎϜϜĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎННĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐўўŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃҟҟŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓ Ó ÓÓÓÓÓÓÓÓÓÓÓÓÔˇÔˇÔÔÔÔÔÔÔÔÔÔÔÔŐ˘Ő˘ŐŐŐŐŐŐŐŐŐŐÖŁÖŁÖÖÖÖÖÖÖÖÖÖפ××××××××××ŘĄŘŘŘŘŘŘŘŘ٦٦ŮŮŮŮŮŮŮŮÚ§ÚÚÚÚÚÚÚÚۨŰŰŰŰŰŰܩܩÜÜÜÜÜÜÝŞÝÝÝÝÝÝŢ«ŢŢŢŢŢŢ߬ßßßßßßŕ­ŕŕŕŕá®ááááááâŻââââă°ăăăăä±ääääĺ˛ĺĺĺĺćłććććç´ççççččččé¶ééééę·ęęęęëëëëěěěěíşííííîîîîďďďďđ˝đđńľńńňżňňňňóóóóôôôôőőőőöööö÷÷÷÷řřřřůůúÇúúűČűűüüüüýýýýţţţţ˙˙7 ? @AEGIJLMNPQR S!!!!!!!T"""""""U#########V$$$$$$$W$W%%%%%%%X%X&&&&&&&&&Y'''''''''''Z((((((((((([)))))))))))))\*************]*]+++++++++++++^+^,,,,,,,,,,,,,,,_,_---------------`-`-`.................a.a/////////////////////b/b/b00000000000000000000000c0c111111111111111111111111111d1d1d22222222222222222222222222222e2e2e2e33333333333333333333333333333333333f3f3f4444444444444444444444444444444444444444444g4g4g55555555555555555555555555555555555555555555555555555h5h5h666666666666666666666666666666666666666666666666666666666666666i6i6i6i6i777777777777777777777777777777777777ÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××ŘŘŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢßßßßßßßßŕŕŕŕŕŕááááááááââââââăăăăăăääääääĺĺĺĺĺĺććććććççççččččččééééééęęęęëëëëěěěěěěííííîîîîďďďďďďđđđđńńńńňňňňóóóóôôôôőőőőöööö÷÷÷÷řřřřůůůůúúúúűűüüüüýýýýţţţţ˙˙  !!!!!!!!""""""""##########$$$$$$$$%%%%%%%%%%&&&&&&&&&&&&''''''''''''(((((((((((())))))))))))))**************++++++++++++++++,,,,,,,,,,,,,,,,,,------------------......................////////////////////////000000000000000000000000000011111111111111111111111111111122222222222222222222222222222222223333333333333333333333333333333333333333334444444444444444444444444444444444444444444444445555555555555555555555555555555555555555555555555555555555666666666666666666666666666666666666666666666666666666666666666666667777777777777777777777777777777777777777777777ÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××ŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝŢŢŢŢŢŢŢŢßßßßßßßßŕŕŕŕŕŕááááááááââââââăăăăăăääääääĺĺĺĺĺĺććććççççççččččččééééęęęęęęëëëëěěěěííííííîîîîďďďďđđđđńńńńňňňňóóóóôôôôőőőőöööö÷÷÷÷řřřřůůůůúúúúűűüüüüýýýýţţţţ˙˙  !!!!!!!!""""""""########$$$$$$$$$$%%%%%%%%%%&&&&&&&&&&''''''''''''(((((((((((())))))))))))))**************++++++++++++++++,,,,,,,,,,,,,,,,,,------------------......................////////////////////////0000000000000000000000000011111111111111111111111111111122222222222222222222222222222222223333333333333333333333333333333333333333334444444444444444444444444444444444444444444444555555555555555555555555555555555555555555555555555555555566666666666666666666666666666666666666666666666666666666666666666666777777777777777777777777777777777777777777777777777777ÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇúÇúÇúÇúÇúČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČűČűČűČűÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉüÉüÉüÉüĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘýĘýĘýËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËţËţËţĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚ˙Ě˙Ě˙ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖ× ××××××××××Ř ŘŘŘŘŘŘŘŘŮ Ů ŮŮŮŮŮŮÚ Ú ÚÚÚÚÚÚŰ Ű ŰŰŰŰŰŰÜÜÜÜÜÜÜÝÝÝÝÝÝÝŢŢŢŢŢŢŢßßßßßßßŕŕŕŕŕáááááááââââââăăăăăäääääĺĺĺĺĺććććçççççčččččééééęęęęęëëëëěěěěíííííîîîîďďďďđđđđńńńńňňňňóóóóôôôôőőőőöööö÷÷÷÷řřřřůůůůúúúúűűü.üüýýýýţţţţ˙˙ ßŕâăäćçčéęëěí î!!!!!!!ď"""""""đ#######ń$$$$$$$$$ň%%%%%%%%%ó&&&&&&&&&ô'''''''''''ő(((((((((((ö)))))))))))÷)÷***********ř*ř+++++++++++++ů+ů,,,,,,,,,,,,,,,ú,ú---------------ű-ű...................ü.ü///////////////////ý/ý/ý000000000000000000000ţ0ţ0ţ1111111111111111111111111˙1˙1˙22222222222222222222222222223333333333333333333333333333333333333444444444444444444444444444444444444444444445555555555555555555555555555555555555555555555555555566666666666666666666666666666666666666666666666666666666666666667777777777777777777777777777777777777777777777777777777777ÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČ.Č.Č.Č.Č.ČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉ/É/É/É/ÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘ0Ę0Ę0Ę0ĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘË1Ë1Ë1Ë1ËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚ2Ě2Ě2ĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍ3Í3Í3ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎ4Î4Î4ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎ5Ď5Ď5ĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐ6Đ6ĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃ7Ń7ŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇ8Ň8ŇŇŇŇŇŇŇŇŇŇŇŇÓ9Ó9ÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔ:ÔÔÔÔÔÔÔÔÔÔÔÔŐ;Ő;ŐŐŐŐŐŐŐŐŐŐÖ<Ö<ÖÖÖÖÖÖÖÖ×=×=××××××××Ř>Ř>ŘŘŘŘŘŘŘŘŮ?ŮŮŮŮŮŮŮŮÚ@ÚÚÚÚÚÚÚÚŰAŰŰŰŰŰŰÜBÜÜÜÜÜÜÝCÝÝÝÝÝÝŢDŢŢŢŢŢŢßEßßßßßßŕFŕŕŕŕáGááááâHââââââăăăăăăääääääĺĺĺĺćLććććçMççççččččéOééééęęęęëQëëěRěěěěííííîîîîďUďďđVđđńWńńňXňňóYóóôZôôő[őőö\öö÷÷÷÷řřřřůůůůúúúúűűűűüüýýýýţţţţ˙˙ś˘ Ł ¤ Ą ¦ §¨©Ş­®°±˛ł´µ¶·ą ş!!!!!!!»"""""""Ľ#######˝$$$$$$$$$ľ%%%%%%%ż%ż&&&&&&&&&Ŕ'''''''''Á'Á(((((((((Â(Â)))))))))))Ă*************Ä+++++++++++++Ĺ+Ĺ,,,,,,,,,,,,,,,Ć,Ć---------------Ç-Ç.................Č.Č.Č///////////////////É/É00000000000000000000000Ę0Ę111111111111111111111111111Ë1Ë22222222222222222222222222222Ě2Ě2Ě33333333333333333333333333333333333Í3Í3Í44444444444444444444444444444444444444444Î4Î4Î4Î5555555555555555555555555555555555555555555555555Ď5Ď5Ď5Ď66666666666666666666666666666666666666666666666666666666666Đ6Đ6Đ6Đ6Đ77777777777777777777777777777777777777777777777777777777777777ÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČaČaČaČaČaČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉbÉbÉbÉbÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘcĘcĘcĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËdËdËdËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚeĚeĚeĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍfÍfÍfÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎgÎgÎgÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎhĎhĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐiĐiĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃjŃjŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇkŇkŇŇŇŇŇŇŇŇŇŇŇŇÓlÓlÓÓÓÓÓÓÓÓÓÓÓÓÔmÔmÔÔÔÔÔÔÔÔÔÔÔÔŐnŐnŐŐŐŐŐŐŐŐŐŐÖoÖÖÖÖÖÖÖÖÖÖ×p××××××××××ŘqŘŘŘŘŘŘŘŘŮrŮŮŮŮŮŮŮŮÚsÚÚÚÚÚÚÚÚŰtŰŰŰŰŰŰÜuÜÜÜÜÜÜÝvÝvÝÝÝÝŢwŢŢŢŢŢŢßxßßßßßßŕyŕŕŕŕázááááâ{ââââă|ăăăăä}ääääĺ~ĺĺĺĺćććććççççčččččé‚ééęęęęęëëëëě…ěěí†ííî‡îîîîďďďďđđđđńńńńňňňňóóóóôôôôőőőőöö÷÷÷řřřřůůůůúúúúűűűűüüýýýýţţţţ˙˙n xy|~€‚„…† ‡!!!!!!"""""‰"‰#######Š$$$$$$$‹$‹%%%%%%%Ś&&&&&&&&&Ť&Ť'''''''''Ž(((((((((((Ź))))))))))))***********‘*‘+++++++++++++’,,,,,,,,,,,,,,,“,“---------------”-”.................•.•/////////////////////–/–000000000000000000000—0—0—111111111111111111111111111222222222222222222222222222™2™2™33333333333333333333333333333333333š3š3š444444444444444444444444444444444444444›4›4›4›5555555555555555555555555555555555555555555555555ś5ś5ś5ś66666666666666666666666666666666666666666666666666666666666ť6ť6ť6ť777777777777777777777777777777777777777777777777777777777777777777777777ÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇȕȕȕȕČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉ–É–É–É–É–ÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘ—Ę—Ę—Ę—ĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËË̙̙̙ĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚ͚͚͚ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍΛΛÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎϜϜϜĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎНННĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐўўŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃҟҟŇŇŇŇŇŇŇŇŇŇŇŇÓ Ó ÓÓÓÓÓÓÓÓÓÓÓÓÔˇÔˇÔÔÔÔÔÔÔÔÔÔÔÔŐ˘ŐŐŐŐŐŐŐŐŐŐÖŁÖŁÖÖÖÖÖÖÖÖפפ××××××××ŘĄŘĄŘŘŘŘŘŘ٦٦ŮŮŮŮŮŮÚ§Ú§ÚÚÚÚÚÚۨŰŰŰŰŰŰܩܩÜÜÜÜÜÜÝŞÝÝÝÝŢ«ŢŢŢŢŢŢ߬ßßßßßßŕ­ŕŕŕŕá®ááááâŻââââă°ăăăăä±ääääĺ˛ĺĺĺĺćłććç´ççççčµččččééééęęęęë¸ëëëëěěěěííííîîîîďďďďđđđđńńńńňňňňóóóóôôôôőőőőöööö÷÷řĹřřůůůůúúúúűűűűüüýĘýýţţţţ˙˙9 AFGIKMNOPQR S!!!!!T"""""""U#########V$$$$$$$W%%%%%%%%%X&&&&&&&&&Y'''''''''Z'Z((((((((([([)))))))))\)\***********]*]+++++++++++++^+^,,,,,,,,,,,,,_,_---------------`-`.................a.a///////////////////b/b/b000000000000000000000c0c1111111111111111111111111d1d1d222222222222222222222222222e2e2e333333333333333333333333333333333f3f3f3f444444444444444444444444444444444444444g4g4g5555555555555555555555555555555555555555555555555h5h5h5h666666666666666666666666666666666666666666666666666666666i6i6i6i6i7777777777777777777777777777777777777777777777777777777777777777777777777j7j7j7jÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××ŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÝÝÝÝÝÝŢŢŢŢŢŢŢŢßßßßßßßßŕŕŕŕŕŕááááááââââââăăăăăăääääääĺĺĺĺĺĺććććççççççččččééééęęęęęęëëëëěěěěííííîîîîďďďďđđđđńńńńňňňňóóóóôôôôőőőőöööö÷÷÷÷řřůůůůúúúúűűűűüüüüýýţţţţ˙˙  !!!!!!""""""""##########$$$$$$$$%%%%%%%%%%&&&&&&&&&&''''''''''(((((((((((())))))))))))**************++++++++++++++++,,,,,,,,,,,,,,,,------------------....................//////////////////////000000000000000000000000001111111111111111111111111111222222222222222222222222222222223333333333333333333333333333333333333344444444444444444444444444444444444444444444445555555555555555555555555555555555555555555555555555556666666666666666666666666666666666666666666666666666666666666666777777777777777777777777777777777777777777777777777777777777777777777777777777777788888888ÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××××ŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝŢŢŢŢŢŢßßßßßßßßŕŕŕŕŕŕááááááââââââăăăăăăääääääĺĺĺĺććććććççççččččččééééęęęęëëëëëëěěěěííííîîîîďďďďđđđđńńńńňňňňóóóóôôőőőőöööö÷÷÷÷řřřřůůúúúúűűűűüüüüýýţţţţ˙˙  !!!!!!""""""""########$$$$$$$$$$%%%%%%%%&&&&&&&&&&''''''''''''(((((((((())))))))))))))************++++++++++++++++,,,,,,,,,,,,,,,,------------------....................//////////////////////0000000000000000000000001111111111111111111111111111222222222222222222222222222222223333333333333333333333333333333333333344444444444444444444444444444444444444444444555555555555555555555555555555555555555555555555555555666666666666666666666666666666666666666666666666666666666666666677777777777777777777777777777777777777777777777777777777777777777777777777777777888888888888888888ÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇúÇúÇúÇúÇúČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČűČűČűČűÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉüÉüÉüÉüĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘýĘýĘýËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËţËţËţËţĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚ˙Ě˙Ě˙ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖ× × ××××××××Ř ŘŘŘŘŘŘŘŘŮ Ů ŮŮŮŮŮŮÚ ÚÚÚÚÚÚŰ Ű ŰŰŰŰŰŰÜÜÜÜÜÜÜÝÝÝÝÝÝÝŢŢŢŢŢßßßßßßßŕŕŕŕŕáááááâââââăăăăăäääääĺĺĺĺćććććçççčččččééééęęęëëëëëěěěěííííîîîîďďďďđđđđńńńńňňňňóóóóôôő'őőöööö÷÷÷÷řřřřůůú,úúűűűűüüüüýýţţţţ˙˙Ď × áâăĺçčéëěí î!!!!!ď"""""""đ#######ń$$$$$$$ň$ň%%%%%%%ó&&&&&&&&&ô'''''''''ő'ő(((((((((ö)))))))))))÷)÷***********ř+++++++++++++ů+ů,,,,,,,,,,,,,ú,ú---------------ű-ű...............ü.ü.ü/////////////////ý/ý/ý000000000000000000000ţ0ţ11111111111111111111111˙1˙1˙2222222222222222222222222233333333333333333333333333333333333444444444444444444444444444444444444444445555555555555555555555555555555555555555555555555666666666666666666666666666666666666666666666666666666666677777777777777777777777777777777777777777777777777777777777777777777777777788888888888888888888888ÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČ.Č.Č.Č.ČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉ/É/É/É/ÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘ0Ę0Ę0ĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘË1Ë1Ë1Ë1ËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚ2Ě2Ě2ĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍ3Í3Í3ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎ4Î4ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎ5Ď5ĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐ6Đ6Đ6ĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃ7Ń7Ń7ŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇ8Ň8ŇŇŇŇŇŇŇŇŇŇŇŇÓ9Ó9ÓÓÓÓÓÓÓÓÓÓÓÓÔ:ÔÔÔÔÔÔÔÔÔÔÔÔŐ;ŐŐŐŐŐŐŐŐŐŐÖ<Ö<ÖÖÖÖÖÖÖÖ×=××××××××Ř>Ř>ŘŘŘŘŘŘŘŘŮ?ŮŮŮŮŮŮÚ@Ú@ÚÚÚÚÚÚŰAŰŰŰŰŰŰÜBÜÜÜÜÜÜÝCÝÝÝÝÝÝŢDŢŢŢŢßEßßßßßßŕFŕŕŕŕáGááááâHââââăIăăăăääääĺKĺĺĺĺćLććććççççčNččéOééééęęęęëëëëěRěěíSííîTîîďUďďđVđđńWńńňňňňóóóóôôôôőőö\öö÷÷÷÷řřřřůůůůúúűűűűüüüüýýţţţţ˙˙ž˘ §¨©Ş«¬­°˛´µ¶·¸ą ş ş!!!!!»"""""""Ľ#######˝$$$$$$$ľ%%%%%%%%%ż&&&&&&&&&Ŕ'''''''''Á(((((((((Â(Â)))))))))Ă)Ă***********Ä*Ä+++++++++++Ĺ+Ĺ,,,,,,,,,,,,,Ć,Ć---------------Ç-Ç...............Č.Č///////////////////É/É000000000000000000000Ę0Ę0Ę11111111111111111111111Ë1Ë222222222222222222222222222Ě2Ě2Ě333333333333333333333333333333333Í3Í3Í4444444444444444444444444444444444444Î4Î4Î4Î555555555555555555555555555555555555555555555Ď5Ď5Ď5Ď6666666666666666666666666666666666666666666666666666666Đ6Đ6Đ6Đ77777777777777777777777777777777777777777777777777777777777777777777777Ń7Ń7Ń7Ń7Ń8888888888888888888888888888Ç`Ç`ÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČaČaČaČaČaČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉbÉbÉbÉbÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘcĘcĘcĘcĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËdËdËdËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚeĚeĚeĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍfÍfÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎgÎgÎgÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎhĎhĎhĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐiĐiĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃjŃjŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇkŇkŇŇŇŇŇŇŇŇŇŇŇŇÓlÓlÓÓÓÓÓÓÓÓÓÓÔmÔmÔÔÔÔÔÔÔÔÔÔŐnŐnŐŐŐŐŐŐŐŐŐŐÖoÖÖÖÖÖÖÖÖ×p×p××××××××ŘqŘŘŘŘŘŘŘŘŮrŮŮŮŮŮŮŮŮÚsÚÚÚÚÚÚŰtŰŰŰŰŰŰÜuÜÜÜÜÜÜÝvÝÝÝÝÝÝŢwŢŢŢŢßxßßßßßßŕyŕŕŕŕázááááâ{ââââă|ăăä}ääääĺ~ĺĺĺĺććććç€ççççččččé‚ééęęęë„ëëëëěěěěííííîîîîďďďďđđđđńńň‹ňňóŚóóôôôôőőőőöö÷÷÷řřřřůůůůúúűűűűüüüüýýţţţţ˙˙n r s {|~€‚„…† ‡!!!!!!!"""""""‰#######Š$$$$$$$‹%%%%%%%Ś%Ś&&&&&&&Ť&Ť'''''''Ž'Ž(((((((((Ź)))))))))))***********‘*‘+++++++++++’+’,,,,,,,,,,,,,“,“-------------”-”-”...............•.•/////////////////–/–/–0000000000000000000—0—0—11111111111111111111111112222222222222222222222222™2™2™3333333333333333333333333333333š3š3š3š44444444444444444444444444444444444›4›4›4›555555555555555555555555555555555555555555555ś5ś5ś5ś66666666666666666666666666666666666666666666666666666ť6ť6ť6ť6ť777777777777777777777777777777777777777777777777777777777777777777777ž7ž7ž7ž7ž88888888888888888888888888888888888888ĆĆĆĆǔǔǔǔǔÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇȕȕȕȕȕČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉ–É–É–É–É–ÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘ—Ę—Ę—Ę—ĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËË̙̙̙ĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚ͚͚͚ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍΛΛΛÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎϜϜĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎННĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐўўŃŃŃŃŃŃŃŃŃŃŃŃŃŃҟҟŇŇŇŇŇŇŇŇŇŇŇŇÓ Ó ÓÓÓÓÓÓÓÓÓÓÔˇÔˇÔÔÔÔÔÔÔÔÔÔŐ˘Ő˘ŐŐŐŐŐŐŐŐÖŁÖŁÖÖÖÖÖÖÖÖפפ××××××ŘĄŘĄŘŘŘŘŘŘ٦٦ŮŮŮŮŮŮÚ§ÚÚÚÚÚÚۨŰŰŰŰŰŰÜ©ÜÜÜÜÜÜÝŞÝÝÝÝÝÝŢ«ŢŢŢŢ߬ßßßßßßŕŕŕŕŕŕá®ááááâŻââââăăăăä±ääääĺ˛ĺĺćłććććç´ççčµččččééééęęęęë¸ëëěąěěíşííî»îîďĽďďđđđđńńńńňňňňóóôÁôôőőőőöööö÷÷řĹřřůůůůúúűČűűüüüüýýţţţţ˙˙ = BCDEFIJLNOPQR S!!!!!!!T"""""""U#####V#V$$$$$$$W%%%%%%%X&&&&&&&&&Y'''''''''Z((((((((([([)))))))))\)\*********]*]+++++++++++^+^,,,,,,,,,,,,,_,_-------------`-`.................a.a/////////////////b/b000000000000000000000c0c11111111111111111111111d1d1d2222222222222222222222222e2e2e3333333333333333333333333333333f3f3f4444444444444444444444444444444444444g4g4g555555555555555555555555555555555555555555555h5h5h5h66666666666666666666666666666666666666666666666666666i6i6i6i777777777777777777777777777777777777777777777777777777777777777777777j7j7j7j7j888888888888888888888888888888888888888888888888ĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××ŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰÜÜÜÜÜÜÜÜÝÝÝÝÝÝÝÝŢŢŢŢŢŢßßßßßßŕŕŕŕŕŕŕŕááááááââââăăăăăăääääääĺĺĺĺććććććççççččččééééęęęęęęëëëëěěěěííííîîîîďďđđđđńńńńňňňňóóóóôôőőőőöööö÷÷÷÷řřůůůůúúúúűűüüüüýýţţţţ˙˙  !!!!!!!!""""""""######$$$$$$$$$$%%%%%%%%&&&&&&&&&&''''''''''(((((((((())))))))))))************++++++++++++++,,,,,,,,,,,,,,,,----------------....................////////////////////000000000000000000000000111111111111111111111111112222222222222222222222222222223333333333333333333333333333333333334444444444444444444444444444444444444444445555555555555555555555555555555555555555555555555566666666666666666666666666666666666666666666666666666666666677777777777777777777777777777777777777777777777777777777777777777777777777778888888888888888888888888888888888888888888888888888888888ĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖÖÖ××××××××××ŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰÜÜÜÜÜÜÝÝÝÝÝÝÝÝŢŢŢŢŢŢßßßßßßŕŕŕŕŕŕááááááââââââăăăăăăääääĺĺĺĺĺĺććććççççççččččééééęęęęëëëëěěěěííííîîîîďďďďđđđđńńňňňňóóóóôôôôőőöööö÷÷÷÷řřůůůůúúúúűűüüüüýýţţţţ˙˙  !!!!!!!!""""""########$$$$$$$$%%%%%%%%%%&&&&&&&&''''''''''(((((((((((())))))))))**************++++++++++++++,,,,,,,,,,,,,,----------------..................//////////////////////0000000000000000000000111111111111111111111111112222222222222222222222222222223333333333333333333333333333333333334444444444444444444444444444444444444444555555555555555555555555555555555555555555555555556666666666666666666666666666666666666666666666666666666666667777777777777777777777777777777777777777777777777777777777777777777777777788888888888888888888888888888888888888888888888888888888888888888888ĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆůĆůĆůĆůĆůÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇúÇúÇúÇúČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČűČűČűČűÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉüÉüÉüÉüĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘýĘýĘýĘýËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËţËţËţĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚ˙Ě˙Ě˙ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖ× × ××××××Ř Ř ŘŘŘŘŘŘŮ ŮŮŮŮŮŮÚ Ú ÚÚÚÚÚÚŰ ŰŰŰŰŰŰÜÜÜÜÜÝÝÝÝÝÝŢŢŢŢŢßßßßßŕŕŕŕŕáááááâââââăăăăăäääĺĺĺĺĺćććçççççččččééééęęęëëëěěěíííî îîďďďďđđđđńńň$ňňó%óóôôôôőőö(öö÷÷÷÷řřů+ůůúúúúűűüüüüýýţ0ţţ˙˙Ń Ú ßŕáâăĺćčęëěí î!!!!!!!ď"""""đ#######ń$$$$$$$ň%%%%%%%ó%ó&&&&&&&ô'''''''''ő(((((((((ö(ö)))))))))÷***********ř*ř+++++++++++ů+ů,,,,,,,,,,,ú,ú-------------ű-ű...............ü.ü/////////////////ý/ý/ý0000000000000000000ţ0ţ11111111111111111111111˙1˙22222222222222222222222233333333333333333333333333333333344444444444444444444444444444444444445555555555555555555555555555555555555555555556666666666666666666666666666666666666666666666666666667777777777777777777777777777777777777777777777777777777777777777777778888888888888888888888888888888888888888888888888888888888888888888888888ĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇ-Ç-Ç-Ç-Ç-ÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČ.Č.Č.Č.Č.ČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉ/É/É/É/ÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘ0Ę0Ę0ĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘË1Ë1Ë1ËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚ2Ě2Ě2ĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍ3Í3Í3ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎ4Î4Î4ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎ5Ď5Ď5ĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐ6Đ6ĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃ7Ń7Ń7ŃŃŃŃŃŃŃŃŃŃŃŃŇ8Ň8ŇŇŇŇŇŇŇŇŇŇŇŇÓ9Ó9ÓÓÓÓÓÓÓÓÓÓÔ:Ô:ÔÔÔÔÔÔÔÔŐ;Ő;ŐŐŐŐŐŐŐŐÖ<Ö<ÖÖÖÖÖÖÖÖ×=××××××××Ř>ŘŘŘŘŘŘŮ?Ů?ŮŮŮŮŮŮÚ@ÚÚÚÚÚÚŰAŰŰŰŰŰŰÜBÜÜÜÜÜÜÝCÝÝÝÝŢDŢŢŢŢßEßßßßŕFŕŕŕŕáGááááâHââââăIăăăăääääĺKĺĺĺĺććććçMççčNččéOééééęęęęëëëëěěěěííííîîďUďďđVđđńńńńňňňňóóôZôôőőőőöö÷]÷÷řřřřůůúúúúűűüüüüýýýýţţ˙˙ź˘ Ą ¨©Ş°±ł´µ¶·¸ą ş!!!!!!!»"""""Ľ#######˝$$$$$$$ľ%%%%%%%ż&&&&&&&Ŕ&Ŕ'''''''Á'Á(((((((Â(Â)))))))))Ă)Ă*********Ä*Ä+++++++++++Ĺ+Ĺ,,,,,,,,,,,Ć,Ć-------------Ç-Ç...............Č.Č/////////////////É/É0000000000000000000Ę0Ę0Ę111111111111111111111Ë1Ë1Ë22222222222222222222222Ě2Ě2Ě33333333333333333333333333333Í3Í3Í3Í444444444444444444444444444444444Î4Î4Î4Î55555555555555555555555555555555555555555Ď5Ď5Ď5Ď666666666666666666666666666666666666666666666666666Đ6Đ6Đ6Đ77777777777777777777777777777777777777777777777777777777777777777Ń7Ń7Ń7Ń7Ń888888888888888888888888888888888888888888888888888888888888888888888888888888ĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇ`Ç`Ç`Ç`Ç`Ç`ÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČaČaČaČaČaČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉbÉbÉbÉbÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘcĘcĘcĘcĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËdËdËdËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚeĚeĚeĚeĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍfÍfÍfÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎgÎgÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎhĎhĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐiĐiĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃjŃjŃŃŃŃŃŃŃŃŃŃŃŃŇkŇkŇŇŇŇŇŇŇŇŇŇŇŇÓlÓlÓÓÓÓÓÓÓÓÓÓÔmÔÔÔÔÔÔÔÔÔÔŐnŐnŐŐŐŐŐŐŐŐÖoÖÖÖÖÖÖÖÖ×p×p××××××ŘqŘqŘŘŘŘŘŘŮrŮŮŮŮŮŮÚsÚÚÚÚÚÚŰtŰŰŰŰŰŰÜuÜÜÜÜÜÜÝvÝÝÝÝŢwŢŢŢŢßxßßßßŕyŕŕŕŕázááááâ{ââââă|ăăä}ääääĺ~ĺĺćććććççççččččé‚ééęęęë„ëëě…ěěí†ííîîîîďďďďđđńŠńńňňňňóóóóôôőőőőöööö÷÷řřřřůůúúúúűűü•üüýýýýţţ˙˙n q txyz{|}€‚„…† ‡!!!!!!"""""‰#######Š$$$$$$$‹%%%%%%%Ś&&&&&&&Ť'''''''''Ž(((((((((Ź))))))))))*********‘*‘+++++++++++’+’,,,,,,,,,,,“,“-------------”-”...............•.•///////////////–/–/–00000000000000000—0—0—1111111111111111111111122222222222222222222222™2™2™33333333333333333333333333333š3š3š44444444444444444444444444444444444›4›4›55555555555555555555555555555555555555555ś5ś5ś5ś6666666666666666666666666666666666666666666666666ť6ť6ť6ť6ť777777777777777777777777777777777777777777777777777777777777777ž7ž7ž7ž7ž888888888888888888888888888888888888888888888888888888888888888888888888888888888ź8ź8ź8źĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆǔǔǔǔǔÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇȕȕȕȕČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉ–É–É–É–É–ÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘ—Ę—Ę—Ę—ĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËË̙̙̙ĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚ͚͚͚ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍΛΛΛÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎϜϜϜĎĎĎĎĎĎĎĎĎĎĎĎĎĎНННĐĐĐĐĐĐĐĐĐĐĐĐĐĐўўŃŃŃŃŃŃŃŃŃŃŃŃҟҟŇŇŇŇŇŇŇŇŇŇŇŇÓ ÓÓÓÓÓÓÓÓÓÓÔˇÔˇÔÔÔÔÔÔÔÔÔÔŐ˘ŐŐŐŐŐŐŐŐÖŁÖŁÖÖÖÖÖÖÖÖפ××××××××ŘĄŘŘŘŘŘŘ٦٦ŮŮŮŮÚ§Ú§ÚÚÚÚۨۨŰŰŰŰÜ©ÜÜÜÜÜÜÝŞÝÝÝÝŢ«ŢŢŢŢ߬ßßßßŕ­ŕŕŕŕá®ááááâŻââââă°ăăä±ääääĺĺĺĺćłććç´ççčµččččééééęęęęëëëëěěěěííî»îîďĽďďđđđđńńňżňňóóóóôôőÂőőöööö÷÷řřřřůůúÇúúűűűűüüýýýýţţ˙˙ < ? BCJKLMNOPQR S!!!!!T"""""""U#####V#V$$$$$W$W%%%%%X%X&&&&&&&Y'''''''Z'Z((((((([([)))))))))\***********]+++++++++++^+^,,,,,,,,,,,_,_-------------`-`.............a.a.a///////////////b/b0000000000000000000c0c111111111111111111111d1d1d22222222222222222222222e2e2e33333333333333333333333333333f3f3f444444444444444444444444444444444g4g4g4g555555555555555555555555555555555555555h5h5h5h6666666666666666666666666666666666666666666666666i6i6i6i777777777777777777777777777777777777777777777777777777777777777j7j7j7j7j8888888888888888888888888888888888888888888888888888888888888888888888888888888k8k8k8k8k8k99999999ĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖ××××××××××ŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚŰŰŰŰŰŰÜÜÜÜÜÜÜÜÝÝÝÝÝÝŢŢŢŢŢŢßßßßßßŕŕŕŕŕŕááááááââââââăăăăääääĺĺĺĺĺĺććććççççččččééééęęęęëëëëěěěěííííîîîîďďđđđđńńńńňňóóóóôôôôőőöööö÷÷řřřřůůůůúúűűűűüüýýýýţţ˙˙  !!!!!!""""""""######$$$$$$$$%%%%%%%%&&&&&&&&&&''''''''(((((((((())))))))))))************++++++++++++,,,,,,,,,,,,,,----------------................////////////////////000000000000000000000011111111111111111111111122222222222222222222222222223333333333333333333333333333333333444444444444444444444444444444444444445555555555555555555555555555555555555555555555666666666666666666666666666666666666666666666666666666667777777777777777777777777777777777777777777777777777777777777777777777888888888888888888888888888888888888888888888888888888888888888888888888888888888888888899999999999999999999ĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖ××××××××ŘŘŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚŰŰŰŰŰŰŰŰÜÜÜÜÜÜÝÝÝÝÝÝŢŢŢŢŢŢßßßßßßŕŕŕŕŕŕááááááââââăăăăăăääääĺĺĺĺććććććççççččččééééęęęęëëëëěěěěííîîîîďďďďđđđđńńňňňňóóôôôôőőőőöö÷÷÷÷řřůůůůúúűűűűüüýýýýţţ˙˙  !!!!!!""""""""######$$$$$$$$%%%%%%%%&&&&&&&&''''''''''(((((((((())))))))))************++++++++++++,,,,,,,,,,,,,,----------------................////////////////////0000000000000000000011111111111111111111111122222222222222222222222222223333333333333333333333333333333344444444444444444444444444444444444444555555555555555555555555555555555555555555555566666666666666666666666666666666666666666666666666666677777777777777777777777777777777777777777777777777777777777777777777778888888888888888888888888888888888888888888888888888888888888888888888888888888888888888999999999999999999999999999999ĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆůĆůĆůĆůĆůĆůÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇúÇúÇúÇúÇúČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČűČűČűČűÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉüÉüÉüĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘýĘýĘýĘýËËËËËËËËËËËËËËËËËËËËËËËËËËËËËţËţËţĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚ˙Ě˙Ě˙ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖ× ××××××Ř Ř ŘŘŘŘŘŘŮ ŮŮŮŮŮŮÚ ÚÚÚÚÚÚŰ ŰŰŰŰŰŰÜÜÜÜÜÝÝÝÝÝŢŢŢŢŢßßßßßŕŕŕŕŕáááááâââăăăăăäääĺĺĺćććććççççččččééééęęęęëëëëěěěěííî îîď!ďďđđđđńńň$ňňóóô&ôôőőőőöö÷÷÷÷řřůůůůúúűűűűüüýýýýţţ˙˙ĐŇÔÖ Ř ŰŢßçčęëěí î!!!!!ď"""""đ"đ#####ń$$$$$$$ň%%%%%%%ó&&&&&&&ô'''''''ő'ő(((((((ö(ö)))))))÷)÷*********ř*ř+++++++++ů+ů,,,,,,,,,,,ú,ú-------------ű-ű.............ü.ü///////////////ý/ý/ý00000000000000000ţ0ţ111111111111111111111˙1˙2222222222222222222222333333333333333333333333333334444444444444444444444444444444444455555555555555555555555555555555555555555666666666666666666666666666666666666666666666666667777777777777777777777777777777777777777777777777777777777777777888888888888888888888888888888888888888888888888888888888888888888888888888888888999999999999999999999999999999999999Ć,ĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇ-Ç-Ç-Ç-Ç-ÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČ.Č.Č.Č.ČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉ/É/É/É/ÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘ0Ę0Ę0Ę0ĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘË1Ë1Ë1ËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚ2Ě2Ě2ĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍ3Í3Í3ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎ4Î4Î4ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎ5Ď5ĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐ6Đ6Đ6ĐĐĐĐĐĐĐĐĐĐĐĐŃ7Ń7ŃŃŃŃŃŃŃŃŃŃŃŃŇ8Ň8ŇŇŇŇŇŇŇŇŇŇÓ9Ó9ÓÓÓÓÓÓÓÓÓÓÔ:Ô:ÔÔÔÔÔÔÔÔŐ;ŐŐŐŐŐŐŐŐÖ<Ö<ÖÖÖÖÖÖ×=×=××××××Ř>ŘŘŘŘŘŘŮ?Ů?ŮŮŮŮÚ@Ú@ÚÚÚÚŰAŰŰŰŰŰŰÜBÜÜÜÜÝCÝÝÝÝŢDŢŢŢŢßEßßßßŕFŕŕŕŕáGááááâHââăIăăăăääääĺKĺĺćLććçMççčNččéOééęPęęëQëëěRěěííííîîîîďďđVđđńńńńňňóóóóôôő[őőöö÷]÷÷řřůůůůúúűűűűüüýýýýţţ˙˙ ¦ ©¬­®Ż°±˛ł´µ¶·¸ą ş!!!!!»"""""Ľ#######˝$$$$$ľ$ľ%%%%%ż%ż&&&&&Ŕ&Ŕ'''''''Á(((((((((Â)))))))))Ă*********Ä*Ä+++++++++Ĺ+Ĺ,,,,,,,,,,,Ć,Ć-------------Ç-Ç.............Č.Č///////////////É/É00000000000000000Ę0Ę0Ę1111111111111111111Ë1Ë1Ë222222222222222222222Ě2Ě2Ě333333333333333333333333333Í3Í3Í4444444444444444444444444444444Î4Î4Î4Î5555555555555555555555555555555555555Ď5Ď5Ď5Ď66666666666666666666666666666666666666666666666Đ6Đ6Đ6Đ77777777777777777777777777777777777777777777777777777777777Ń7Ń7Ń7Ń7Ń888888888888888888888888888888888888888888888888888888888888888888888888888Ň8Ň8Ň8Ň8Ň8Ň999999999999999999999999999999999999999999ĹĹĆ_Ć_Ć_Ć_Ć_Ć_ĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇ`Ç`Ç`Ç`Ç`ÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČaČaČaČaČaČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉbÉbÉbÉbÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘcĘcĘcĘcĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËdËdËdËdËËËËËËËËËËËËËËËËËËËËËËËËËËĚeĚeĚeĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍfÍfÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎgÎgÎgÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎhĎhĎhĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐiĐiĐĐĐĐĐĐĐĐĐĐĐĐŃjŃjŃjŃŃŃŃŃŃŃŃŃŃŇkŇkŇŇŇŇŇŇŇŇŇŇÓlÓlÓÓÓÓÓÓÓÓÓÓÔmÔÔÔÔÔÔÔÔŐnŐnŐŐŐŐŐŐŐŐÖoÖÖÖÖÖÖÖÖ×p××××××ŘqŘqŘŘŘŘŘŘŮrŮŮŮŮŮŮÚsÚÚÚÚŰtŰtŰŰŰŰÜuÜÜÜÜÝvÝÝÝÝŢwŢŢŢŢßxßßßßŕyŕŕŕŕázááááââââă|ăăä}ääääĺĺĺĺćććç€ççčččé‚ééęęęëëëëěěí†ííî‡îîďďďďđđńŠńńňňóŚóóôôôôőőöööö÷÷řřů’ůůúúű”űűüüýýýýţţ˙˙ p r uwx{|}~€‚„…† ‡!!!!!"""""‰#######Š$$$$$‹%%%%%%%Ś&&&&&&&Ť'''''''Ž'Ž(((((((Ź(Ź))))))))*********‘+++++++++++’,,,,,,,,,,,“,“-----------”-”-”...........•.•.•/////////////–/–/–00000000000000000—0—111111111111111111111222222222222222222222™2™2™333333333333333333333333333š3š3š4444444444444444444444444444444›4›4›5555555555555555555555555555555555555ś5ś5ś5ś666666666666666666666666666666666666666666666ť6ť6ť6ť6ť777777777777777777777777777777777777777777777777777777777ž7ž7ž7ž7ž8888888888888888888888888888888888888888888888888888888888888888888888888ź8ź8ź8ź8ź8ź999999999999999999999999999999999999999999999999999999ĹĹĹĹĹĹĹĹĹĹĹĹĹĹƓƓƓƓƓƓƓĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆǔǔǔǔǔǔÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇȕȕȕȕȕČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉ–É–É–É–É–ÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘ—Ę—Ę—Ę—ĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËË̙̙̙ĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚ͚͚͚ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍΛΛÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎϜϜĎĎĎĎĎĎĎĎĎĎĎĎĎĎННĐĐĐĐĐĐĐĐĐĐĐĐĐĐўўŃŃŃŃŃŃŃŃŃŃҟҟŇŇŇŇŇŇŇŇŇŇÓ Ó ÓÓÓÓÓÓÓÓÔˇÔˇÔÔÔÔÔÔÔÔŐ˘Ő˘ŐŐŐŐŐŐÖŁÖŁÖÖÖÖÖÖפפ××××××ŘĄŘŘŘŘŘŘ٦ŮŮŮŮŮŮÚ§ÚÚÚÚÚÚۨŰŰŰŰÜ©ÜÜÜÜÝŞÝŞÝÝޫޫŢŢ߬ßßßßŕ­ŕŕŕŕá®ááâŻââââă°ăăä±ääĺ˛ĺĺĺĺććććççççččččééééęęë¸ëëěąěěííííîîďĽďďđđđđńńňżňňóóôÁôôőőöĂöö÷÷řřřřůůúúúúűűüüýýýýţţ˙˙8: @BEFMNOPQR S!!!!!T"""""U#####V#V$$$$$W%%%%%%%X&&&&&&&Y'''''''Z((((((([([)))))))\)\*********]*]+++++++++^+^,,,,,,,,,_,_-----------`-`.............a.a///////////////b/b00000000000000000c0c0c11111111111111111d1d1d222222222222222222222e2e2e3333333333333333333333333f3f3f3f44444444444444444444444444444g4g4g4g55555555555555555555555555555555555h5h5h5h666666666666666666666666666666666666666666666i6i6i6i777777777777777777777777777777777777777777777777777777777j7j7j7j7j8888888888888888888888888888888888888888888888888888888888888888888888888k8k8k8k8k999999999999999999999999999999999999999999999999999999999999999999ĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖÖÖ××××××××ŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮÚÚÚÚÚÚÚÚŰŰŰŰŰŰÜÜÜÜÜÜÜÜÝÝÝÝÝÝŢŢŢŢßßßßßßŕŕŕŕŕŕááááââââââăăăăääääĺĺĺĺććććççççččččééééęęęęëëëëěěííííîîîîďďđđđđńńńńňňóóóóôôőőőőöö÷÷řřřřůůúúúúűűüüýýýýţţ˙˙  !!!!!!""""""######$$$$$$$$%%%%%%%%&&&&&&&&''''''''(((((((())))))))))************++++++++++++,,,,,,,,,,,,--------------................//////////////////000000000000000000001111111111111111111111222222222222222222222222223333333333333333333333333333334444444444444444444444444444444444445555555555555555555555555555555555555555556666666666666666666666666666666666666666666666666666777777777777777777777777777777777777777777777777777777777777777788888888888888888888888888888888888888888888888888888888888888888888888888888888889999999999999999999999999999999999999999999999999999999999999999999999999999ĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖ××××××××××ŘŘŘŘŘŘŘŘŮŮŮŮŮŮŮŮÚÚÚÚÚÚŰŰŰŰŰŰÜÜÜÜÜÜÜÜÝÝÝÝÝÝŢŢŢŢßßßßßßŕŕŕŕŕŕááááââââââăăăăääääĺĺĺĺććććççççččččééééęęęęëëěěěěííííîîďďďďđđńńńńňňóóóóôôőőőőöö÷÷÷÷řřůůúúúúűűüüýýýýţţ˙˙  !!!!!!""""""######$$$$$$$$%%%%%%&&&&&&&&''''''''(((((((((())))))))))**********++++++++++++,,,,,,,,,,,,--------------................//////////////////0000000000000000001111111111111111111111112222222222222222222222223333333333333333333333333333334444444444444444444444444444444444555555555555555555555555555555555555555555666666666666666666666666666666666666666666666666667777777777777777777777777777777777777777777777777777777777777777888888888888888888888888888888888888888888888888888888888888888888888888888888889999999999999999999999999999999999999999999999999999999999999999999999999999999999999999ĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹřĹřĹřĹřĹřĹřĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆůĆůĆůĆůĆůÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇúÇúÇúÇúČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČűČűČűČűÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉüÉüÉüĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘýĘýĘýËËËËËËËËËËËËËËËËËËËËËËËËËËËţËţËţĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚ˙Ě˙Ě˙ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖ× × ××××××Ř ŘŘŘŘŘŘŮ ŮŮŮŮŮŮÚ ÚÚÚÚŰ ŰŰŰŰÜÜÜÜÜÜÝÝÝÝÝŢŢŢßßßßßŕŕŕŕŕáááâââââăăăăäääĺĺĺćććçççčččéééęęęęëëěěěííííîîď!ďďđđń#ńńňňó%óóôôő'őőöö÷÷÷÷řřůůúúúúűűüüýýýýţţ˙˙ÎŃÖ Ţŕáäĺćçčéęëěí î!!!!!ď"""""đ#####ń$$$$$ň$ň%%%%%ó&&&&&&&ô'''''''ő(((((((ö(ö)))))))÷)÷*******ř*ř+++++++++ů+ů,,,,,,,,,ú,ú-----------ű-ű.............ü.ü/////////////ý/ý/ý000000000000000ţ0ţ1111111111111111111˙1˙1˙222222222222222222333333333333333333333333333444444444444444444444444444444455555555555555555555555555555555555556666666666666666666666666666666666666666666666777777777777777777777777777777777777777777777777777777777788888888888888888888888888888888888888888888888888888888888888888888888889999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999ĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆ,Ć,Ć,Ć,Ć,Ć,ĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇ-Ç-Ç-Ç-Ç-Ç-ÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČ.Č.Č.Č.Č.ČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉ/É/É/É/ÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘ0Ę0Ę0Ę0ĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘË1Ë1Ë1ËËËËËËËËËËËËËËËËËËËËËËËËËËĚ2Ě2Ě2ĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍ3Í3Í3ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎ4Î4ÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎ5Ď5ĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐ6Đ6ĐĐĐĐĐĐĐĐĐĐĐĐŃ7Ń7ŃŃŃŃŃŃŃŃŃŃŇ8Ň8ŇŇŇŇŇŇŇŇŇŇÓ9Ó9ÓÓÓÓÓÓÓÓÔ:Ô:ÔÔÔÔÔÔŐ;Ő;ŐŐŐŐŐŐÖ<Ö<ÖÖÖÖÖÖ×=××××××Ř>Ř>ŘŘŘŘŮ?Ů?ŮŮŮŮÚ@ÚÚÚÚŰAŰAŰŰŰŰÜBÜÜÜÜÝCÝÝÝÝŢDŢŢßEßßßßŕFŕŕŕŕáGááâHââăIăăăăääääĺĺĺĺććććççççččččééęPęęëQëëěěíSííîTîîďďđVđđńńňXňňóóôôôôőőöö÷]÷÷řřůůú`úúűűüüýcýýţţ˙˙  Ł Ą §©«®Żł¶·¸ą ş!!!!!»"""""Ľ#####˝$$$$$ľ%%%%%%%ż&&&&&Ŕ&Ŕ'''''Á'Á(((((((Â)))))))Ă)Ă*********Ä+++++++++Ĺ+Ĺ,,,,,,,,,Ć,Ć-----------Ç-Ç.............Č.Č/////////////É/É000000000000000Ę0Ę0Ę11111111111111111Ë1Ë1Ë2222222222222222222Ě2Ě2Ě33333333333333333333333Í3Í3Í3Í444444444444444444444444444Î4Î4Î4Î555555555555555555555555555555555Ď5Ď5Ď5Ď66666666666666666666666666666666666666666Đ6Đ6Đ6Đ6Đ77777777777777777777777777777777777777777777777777777Ń7Ń7Ń7Ń7Ń888888888888888888888888888888888888888888888888888888888888888888888Ň8Ň8Ň8Ň8Ň999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999Ó9Ó9Ó9ÓĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆ_Ć_Ć_Ć_Ć_Ć_ĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇ`Ç`Ç`Ç`Ç`ÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČaČaČaČaČaČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉbÉbÉbÉbÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘcĘcĘcĘcĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËdËdËdËdËËËËËËËËËËËËËËËËËËËËËËËËĚeĚeĚeĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍfÍfÍfÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎgÎgÎgÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎhĎhĎhĎĎĎĎĎĎĎĎĎĎĎĎĐiĐiĐiĐĐĐĐĐĐĐĐĐĐŃjŃjŃŃŃŃŃŃŃŃŃŃŇkŇkŇŇŇŇŇŇŇŇŇŇÓlÓlÓÓÓÓÓÓÓÓÔmÔÔÔÔÔÔÔÔŐnŐnŐŐŐŐŐŐÖoÖÖÖÖÖÖ×p×p××××××ŘqŘŘŘŘŘŘŮrŮŮŮŮÚsÚÚÚÚÚÚŰtŰŰŰŰÜuÜÜÜÜÝvÝÝÝÝŢwŢŢßxßxßßŕyŕŕŕŕááááâ{ââă|ăăä}ääĺ~ĺĺćććç€ççčččé‚ééęęęęëëě…ěěííííîîďďďďđđńńńńňňóóôŤôôőőöööö÷÷řřůůůůúúűűüüüüýýţţ˙˙ s uwyz|}~‚„…† ‡!!!!!"""‰"‰###Š#Š$$$$$‹%%%%%Ś%Ś&&&&&Ť'''''''Ž(((((((Ź(Ź)))))))*********‘*‘+++++++’+’,,,,,,,,,“,“-----------”-”...........•.•.•///////////–/–/–000000000000000—0—11111111111111111112222222222222222222™2™2™33333333333333333333333š3š3š44444444444444444444444444444›4›4›555555555555555555555555555555555ś5ś5ś5ś66666666666666666666666666666666666666666ť6ť6ť6ť77777777777777777777777777777777777777777777777777777ž7ž7ž7ž7ž8888888888888888888888888888888888888888888888888888888888888888888ź8ź8ź8ź8ź8ź99999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 9 9 9 9 9 9 ::::::::ĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹƓƓƓƓƓƓƓĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆǔǔǔǔǔÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇȕȕȕȕČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉ–É–É–É–É–ÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘ—Ę—Ę—Ę—ĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËË̙̙̙ĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚ͚͚͚ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍΛΛΛÎÎÎÎÎÎÎÎÎÎÎÎÎÎϜϜĎĎĎĎĎĎĎĎĎĎĎĎĎĎННĐĐĐĐĐĐĐĐĐĐўўўŃŃŃŃŃŃŃŃҟҟŇŇŇŇŇŇŇŇŇŇÓ Ó ÓÓÓÓÓÓÔˇÔˇÔÔÔÔÔÔÔÔŐ˘ŐŐŐŐŐŐÖŁÖŁÖÖÖÖÖÖפ××××××ŘĄŘĄŘŘŘŘ٦ŮŮŮŮÚ§Ú§ÚÚÚÚۨŰŰŰŰÜ©ÜÜÜÜÝŞÝÝÝÝŢ«ŢŢŢŢ߬ßßŕ­ŕŕá®ááááâŻââă°ăăä±ääĺ˛ĺĺćłććç´ççčµččééę·ęęë¸ëëěěíşííîîďĽďďđđńľńńňňóŔóóôôőőöĂöö÷÷řřůĆůůúúűűüüüüýýţţ˙˙7: = @BDFGJKLMNOPQR S!!!T!T"""U#####V$$$$$W$W%%%%%X&&&&&&&Y'''''Z'Z((((((([)))))))\)\*******]*]+++++++^+^,,,,,,,,,_,_-----------`-`...........a.a/////////////b/b000000000000000c0c0c111111111111111d1d1d2222222222222222222e2e2e33333333333333333333333f3f3f444444444444444444444444444g4g4g4g5555555555555555555555555555555h5h5h5h66666666666666666666666666666666666666666i6i6i6i777777777777777777777777777777777777777777777777777j7j7j7j7j88888888888888888888888888888888888888888888888888888888888888888k8k8k8k8k8k99999999999999999999999999999999999999999999999999999999999999999999999999999999999999999l9l9l9l9l9l::::::::::::::::::::::ĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖ××××××××××ŘŘŘŘŘŘŮŮŮŮŮŮŮŮÚÚÚÚÚÚŰŰŰŰŰŰÜÜÜÜÜÜÝÝÝÝÝÝŢŢŢŢŢŢßßßßŕŕŕŕááááááââââăăăăääääĺĺĺĺććććççççččééééęęęęëëěěěěííîîîîďďđđđđńńňňňňóóôôőőőőöö÷÷řřřřůůúúűűüüüüýýţţ˙˙  !!!!""""""######$$$$$$%%%%%%%%&&&&&&&&''''''(((((((((())))))))**********++++++++++,,,,,,,,,,,,--------------..............////////////////00000000000000000011111111111111111111222222222222222222222222333333333333333333333333333344444444444444444444444444444444555555555555555555555555555555555555556666666666666666666666666666666666666666666666667777777777777777777777777777777777777777777777777777777777888888888888888888888888888888888888888888888888888888888888888888888888889999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999::::::::::::::::::::::::::::::::::ĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖ××××××××ŘŘŘŘŘŘŘŘŮŮŮŮŮŮÚÚÚÚÚÚŰŰŰŰŰŰÜÜÜÜÜÜÝÝÝÝÝÝŢŢŢŢŢŢßßßßŕŕŕŕááááááââââăăăăääääĺĺĺĺććççççččččééééęęëëëëěěííííîîďďďďđđńńńńňňóóôôôôőőöö÷÷řřřřůůúúűűüüüüýýţţ˙˙  !!!!""""""######$$$$$$%%%%%%%%&&&&&&''''''''(((((((())))))))**********++++++++++++,,,,,,,,,,--------------..............////////////////0000000000000000001111111111111111111122222222222222222222223333333333333333333333333333444444444444444444444444444444555555555555555555555555555555555555556666666666666666666666666666666666666666666666777777777777777777777777777777777777777777777777777777777788888888888888888888888888888888888888888888888888888888888888888888888899999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999::::::::::::::::::::::::::::::::::::::::::::::::Ä÷ĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹřĹřĹřĹřĹřĹřĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆůĆůĆůĆůĆůÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇúÇúÇúÇúÇúČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČűČűČűČűÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉüÉüÉüÉüĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘýĘýĘýËËËËËËËËËËËËËËËËËËËËËËËţËţËţĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚ˙Ě˙Ě˙ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖ× × ××××Ř ŘŘŘŘŘŘŮ ŮŮŮŮÚ ÚÚÚÚŰ Ű ŰŰÜÜÜÜÝÝÝÝÝŢŢŢŢŢßßßŕŕŕáááááââââăăăäääĺĺĺĺććçççčččéééęęëëëěěíííîîď!ďďđđń#ńńňňóóô&ôôőőöö÷÷ř*řřůůúúűűü.üüýýţţ˙˙Ó × Ú Üßáâäĺčéęëěí î!!!ď"""""đ#####ń$$$$$ň%%%%%ó%ó&&&&&ô'''''ő'ő(((((ö(ö)))))))÷*********ř+++++++++ů+ů,,,,,,,ú,ú-----------ű-ű...........ü.ü///////////ý/ý/ý0000000000000ţ0ţ0ţ111111111111111˙1˙1˙22222222222222223333333333333333333333344444444444444444444444444445555555555555555555555555555555556666666666666666666666666666666666666666667777777777777777777777777777777777777777777777777777888888888888888888888888888888888888888888888888888888888888888888899999999999999999999999999999999999999999999999999999999999999999999999999999999999999999:::::::::::::::::::::::::::::::::::::::::::::::::::::::ÄÄĹ+Ĺ+Ĺ+Ĺ+Ĺ+Ĺ+Ĺ+ĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆ,Ć,Ć,Ć,Ć,Ć,ĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇ-Ç-Ç-Ç-Ç-Ç-ÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČ.Č.Č.Č.Č.ČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉ/É/É/É/ÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘ0Ę0Ę0Ę0ĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘË1Ë1Ë1Ë1ËËËËËËËËËËËËËËËËËËËËĚ2Ě2Ě2Ě2ĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍ3Í3Í3ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎ4Î4Î4ÎÎÎÎÎÎÎÎÎÎÎÎĎ5Ď5Ď5ĎĎĎĎĎĎĎĎĎĎĎĎĐ6Đ6ĐĐĐĐĐĐĐĐĐĐŃ7Ń7ŃŃŃŃŃŃŃŃŃŃŇ8Ň8ŇŇŇŇŇŇÓ9Ó9ÓÓÓÓÓÓÓÓÔ:Ô:ÔÔÔÔÔÔŐ;Ő;ŐŐŐŐÖ<Ö<ÖÖÖÖÖÖ×=××××Ř>Ř>ŘŘŘŘŮ?ŮŮŮŮÚ@Ú@ÚÚÚÚŰAŰŰŰŰÜBÜÜÝCÝCÝÝŢDŢŢŢŢßEßßŕFŕŕáGááâHââââăăăăääĺKĺĺćLććçMççčNččééęPęęëëěRěěííîTîîďďđVđđńńňňóYóóôôőőöö÷]÷÷řřůůúúűűűűüüýýţţ˙˙ §©¬®Ż±˛ł·¸ą ş!!!»"""""Ľ#####˝$$$$$ľ%%%%%ż&&&&&Ŕ&Ŕ'''''Á(((((((Â)))))))Ă)Ă*******Ä*Ä+++++++Ĺ+Ĺ,,,,,,,Ć,Ć-----------Ç-Ç.........Č.Č.Č///////////É/É0000000000000Ę0Ę0Ę111111111111111Ë1Ë1Ë22222222222222222Ě2Ě2Ě333333333333333333333Í3Í3Í4444444444444444444444444Î4Î4Î4Î55555555555555555555555555555Ď5Ď5Ď5Ď6666666666666666666666666666666666666Đ6Đ6Đ6Đ6Đ77777777777777777777777777777777777777777777777Ń7Ń7Ń7Ń7Ń8888888888888888888888888888888888888888888888888888888888888Ň8Ň8Ň8Ň8Ň8Ň99999999999999999999999999999999999999999999999999999999999999999999999999999999999Ó9Ó9Ó9Ó9Ó9Ó::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹ^Ĺ^Ĺ^Ĺ^Ĺ^Ĺ^Ĺ^ĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆ_Ć_Ć_Ć_Ć_Ć_ĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇ`Ç`Ç`Ç`Ç`ÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČaČaČaČaČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉbÉbÉbÉbÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘcĘcĘcĘcĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËdËdËdËËËËËËËËËËËËËËËËËËËËËËĚeĚeĚeĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍfÍfÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎgÎgÎgÎÎÎÎÎÎÎÎÎÎÎÎĎhĎhĎĎĎĎĎĎĎĎĎĎĎĎĐiĐiĐĐĐĐĐĐĐĐĐĐŃjŃjŃŃŃŃŃŃŃŃŃŃŇkŇkŇŇŇŇŇŇÓlÓlÓÓÓÓÓÓÓÓÔmÔÔÔÔÔÔÔÔŐnŐŐŐŐŐŐÖoÖoÖÖÖÖ×p×p××××ŘqŘŘŘŘŮrŮrŮŮŮŮÚsÚÚÚÚŰtŰŰŰŰÜuÜÜÜÜÝvÝÝŢwŢŢŢŢßxßßŕyŕŕázááâ{ââă|ăăä}ääĺ~ĺĺćććç€ççččé‚ééęęë„ëëěěí†ííîîďďďđđńńň‹ňňóóôôőőöŹöö÷÷řřůůúúű”űűüüýýţţ˙˙m q r uwxz|}€‚„…† ‡!!!"""""‰###Š#Š$$$‹$‹%%%%%Ś&&&&&Ť'''''Ž'Ž(((((Ź(Ź)))))))*******‘*‘+++++++’+’,,,,,,,“,“---------”-”-”.........•.•///////////–/–/–0000000000000—0—1111111111111111122222222222222222™2™2™333333333333333333333š3š3š4444444444444444444444444›4›4›55555555555555555555555555555ś5ś5ś5ś6666666666666666666666666666666666666ť6ť6ť6ť77777777777777777777777777777777777777777777777ž7ž7ž7ž7ž88888888888888888888888888888888888888888888888888888888888ź8ź8ź8ź8ź8ź999999999999999999999999999999999999999999999999999999999999999999999999999999999 9 9 9 9 9 9 ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹ’Ĺ’Ĺ’Ĺ’Ĺ’Ĺ’Ĺ’Ĺ’ĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹƓƓƓƓƓƓƓĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆǔǔǔǔǔÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇȕȕȕȕȕČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉ–É–É–É–ÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘ—Ę—Ę—Ę—ĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËË̙̙̙ĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚ͚͚͚ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍΛΛÎÎÎÎÎÎÎÎÎÎÎÎϜϜϜĎĎĎĎĎĎĎĎĎĎНННĐĐĐĐĐĐĐĐўўŃŃŃŃŃŃŃŃŃŃҟҟŇŇŇŇŇŇÓ Ó ÓÓÓÓÓÓÔˇÔˇÔÔÔÔÔÔŐ˘Ő˘ŐŐŐŐŐŐÖŁÖÖÖÖÖÖפ××××ŘĄŘĄŘŘŘŘ٦ŮŮŮŮÚ§ÚÚÚÚۨŰŰŰŰÜ©ÜÜÜÜÝŞÝÝŢ«ŢŢŢŢ߬ßßŕ­ŕŕá®ááâŻââă°ăăä±ääĺ˛ĺĺćłććççčµččééę·ęęëëěąěěííî»îîďďđđńľńńňňóóôôőÂőőöö÷÷řřůůúÇúúűűüüýýţţ˙˙4: ? BEGIJLMNOPQR S!!!T"""""U###V$$$$$W%%%%%X%X&&&Y&Y'''''Z((((((([)))))))\)\*****]*]+++++++^+^,,,,,,,_,_---------`-`...........a.a///////////b/b0000000000000c0c0c1111111111111d1d1d22222222222222222e2e2e3333333333333333333f3f3f3f44444444444444444444444g4g4g4g555555555555555555555555555h5h5h5h66666666666666666666666666666666666i6i6i6i6i777777777777777777777777777777777777777777777j7j7j7j7j888888888888888888888888888888888888888888888888888888888k8k8k8k8k8k9999999999999999999999999999999999999999999999999999999999999999999999999999999l9l9l9l9l9l9l::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖ××××××××ŘŘŘŘŘŘŮŮŮŮŮŮÚÚÚÚÚÚŰŰŰŰŰŰÜÜÜÜÜÜÝÝÝÝŢŢŢŢŢŢßßßßŕŕŕŕááááââââăăăăääääĺĺĺĺććççççččééééęęëëëëěěííííîîďďđđđđńńňňóóôôôôőőöö÷÷řřůůůůúúűűüüýýţţ˙˙  !!!!""""""####$$$$$$%%%%%%&&&&&&''''''''(((((((())))))))********++++++++++,,,,,,,,,,------------..............//////////////000000000000000011111111111111111122222222222222222222223333333333333333333333334444444444444444444444444444445555555555555555555555555555555555666666666666666666666666666666666666666666777777777777777777777777777777777777777777777777777777888888888888888888888888888888888888888888888888888888888888888888999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖ××××××ŘŘŘŘŘŘŘŘŮŮŮŮŮŮÚÚÚÚÚÚŰŰŰŰÜÜÜÜÜÜÝÝÝÝŢŢŢŢŢŢßßßßŕŕŕŕááááââââăăăăääääĺĺććććççččččééééęęëëěěěěííîîďďďďđđńńňňóóóóôôőőöö÷÷řřřřůůúúűűüüýýţţ˙˙  !!!!!!""""######$$$$$$%%%%%%&&&&&&''''''(((((((())))))))**********++++++++,,,,,,,,,,------------..............//////////////00000000000000001111111111111111112222222222222222222233333333333333333333333344444444444444444444444444445555555555555555555555555555555555666666666666666666666666666666666666666666777777777777777777777777777777777777777777777777777788888888888888888888888888888888888888888888888888888888888888888899999999999999999999999999999999999999999999999999999999999999999999999999999999999999::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ÷Ä÷Ä÷Ä÷Ä÷Ä÷Ä÷Ä÷ĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹřĹřĹřĹřĹřĹřĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆůĆůĆůĆůĆůÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇúÇúÇúÇúČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČűČűČűČűÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉüÉüÉüÉüĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘýĘýĘýËËËËËËËËËËËËËËËËËËËËËţËţËţĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚ˙Ě˙Ě˙ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐÖÖÖÖÖÖÖ× ××××Ř Ř ŘŘŘŘŮ ŮŮŮŮÚ ÚÚÚÚŰ ŰŰÜÜÜÜÜÝÝÝŢŢŢŢŢßßßŕŕŕáááâââăăăäääĺĺćććçčččéééęęëěěěííî ď!ďďđđńńň$ó%óóôôőőöö÷÷ř*řřůůúúűűüüýýţţ˙˙Đ × Ř ÜÝßŕâäćçéęëěí î!!!!!ď"""đ#####ń$$$$$ň%%%%%ó&&&&&ô'''''ő(((((((ö)))))))÷*******ř*ř+++++ů+ů,,,,,,,ú,ú---------ű-ű.........ü.ü.ü/////////ý/ý/ý00000000000ţ0ţ0ţ1111111111111˙1˙1˙222222222222223333333333333333333334444444444444444444444444555555555555555555555555555556666666666666666666666666666666666666677777777777777777777777777777777777777777777778888888888888888888888888888888888888888888888888888888888899999999999999999999999999999999999999999999999999999999999999999999999999999999::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::;;;;;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹ+Ĺ+Ĺ+Ĺ+Ĺ+Ĺ+Ĺ+ĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆ,Ć,Ć,Ć,Ć,Ć,ĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇ-Ç-Ç-Ç-Ç-ÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČ.Č.Č.Č.Č.ČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉ/É/É/É/ÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘ0Ę0Ę0Ę0ĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘË1Ë1Ë1Ë1ËËËËËËËËËËËËËËËËËËĚ2Ě2Ě2ĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍ3Í3Í3ÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎ4Î4ÎÎÎÎÎÎÎÎÎÎÎÎĎ5Ď5ĎĎĎĎĎĎĎĎĎĎĐ6Đ6Đ6ĐĐĐĐĐĐĐĐŃ7Ń7ŃŃŃŃŃŃŃŃŇ8Ň8ŇŇŇŇŇŇÓ9Ó9ÓÓÓÓÓÓÔ:Ô:ÔÔÔÔŐ;Ő;ŐŐŐŐÖ<Ö<ÖÖÖÖ×=×=××××Ř>ŘŘŘŘŮ?ŮŮŮŮÚ@ÚÚÚÚŰAŰŰÜBÜBÜÜÝCÝÝŢDŢŢŢŢßEßßŕFŕŕáGááâHââăIăăääĺKĺĺćLććççčNččééęPęęëëěěíSííîîďďđVńWńńňňóóôôőőö\÷]÷÷řřůůúúűűüüýýţţ˙˙ť Ą ¦ Ş­Ż°±ł´¶·¸ą ş!!!!!»"""Ľ#####˝$$$ľ$ľ%%%ż%ż&&&Ŕ&Ŕ'''''Á(((((Â(Â)))))Ă)Ă*****Ä*Ä+++++++Ĺ,,,,,,,,,Ć---------Ç-Ç.........Č.Č///////////É/É00000000000Ę0Ę0Ę1111111111111Ë1Ë1Ë222222222222222Ě2Ě2Ě3333333333333333333Í3Í3Í444444444444444444444Î4Î4Î4Î5555555555555555555555555Ď5Ď5Ď5Ď666666666666666666666666666666666Đ6Đ6Đ6Đ6Đ77777777777777777777777777777777777777777Ń7Ń7Ń7Ń7Ń88888888888888888888888888888888888888888888888888888Ň8Ň8Ň8Ň8Ň8Ň9999999999999999999999999999999999999999999999999999999999999999999999999Ó9Ó9Ó9Ó9Ó9Ó9Ó:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::Ô:Ô:Ô:Ô:Ô:Ô:Ô:Ô;;;;;;;;;;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹ^Ĺ^Ĺ^Ĺ^Ĺ^Ĺ^Ĺ^ĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆ_Ć_Ć_Ć_Ć_Ć_ĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇ`Ç`Ç`Ç`Ç`Ç`ÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČaČaČaČaČaČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉbÉbÉbÉbÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘcĘcĘcĘcĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËdËdËdËËËËËËËËËËËËËËËËËËĚeĚeĚeĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍfÍfÍfÍÍÍÍÍÍÍÍÍÍÍÍÎgÎgÎgÎÎÎÎÎÎÎÎÎÎĎhĎhĎhĎĎĎĎĎĎĎĎĎĎĐiĐiĐĐĐĐĐĐĐĐŃjŃjŃŃŃŃŃŃŃŃŇkŇkŇŇŇŇŇŇÓlÓlÓÓÓÓÓÓÔmÔmÔÔÔÔŐnŐnŐŐŐŐÖoÖoÖÖÖÖ×p××××ŘqŘqŘŘŮrŮrŮŮÚsÚsÚÚŰtŰŰŰŰÜuÜÜÝvÝÝŢwŢwŢŢßxßßŕyŕŕázááâ{ââăăä}ääĺ~ĺĺććç€ççččé‚ééęęë„ě…ěěííîîďďďđđńńňňóóôŤőŽőőöö÷÷řřůůúúűűüüýýţţ˙˙kl tuxy{|~€‚„…† ‡!!!!!"""‰###Š#Š$$$‹%%%%%Ś&&&&&Ť'''''Ž'Ž(((Ź(Ź))))))*****‘*‘+++++++’+’,,,,,,,“,“-------”-”.........•.•/////////–/–/–00000000000—0—111111111111111222222222222222™2™2™33333333333333333š3š3š3š444444444444444444444›4›4›5555555555555555555555555ś5ś5ś5ś666666666666666666666666666666666ť6ť6ť6ť77777777777777777777777777777777777777777ž7ž7ž7ž7ž888888888888888888888888888888888888888888888888888ź8ź8ź8ź8ź8ź99999999999999999999999999999999999999999999999999999999999999999999999 9 9 9 9 9 9 :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::ˇ:ˇ:ˇ:ˇ:ˇ:ˇ:ˇ:ˇ;;;;;;;;;;;;;;;;;;;;;;;;;;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹ’Ĺ’Ĺ’Ĺ’Ĺ’Ĺ’Ĺ’Ĺ’ĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹƓƓƓƓƓƓĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆǔǔǔǔǔÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇȕȕȕȕČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉ–É–É–É–ÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘ—Ę—Ę—Ę—ĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËË̙̙̙̙ĚĚĚĚĚĚĚĚĚĚĚĚĚĚ͚͚͚ÍÍÍÍÍÍÍÍÍÍÍÍΛΛΛÎÎÎÎÎÎÎÎÎÎϜϜĎĎĎĎĎĎĎĎĎĎННĐĐĐĐĐĐĐĐўўŃŃŃŃŃŃŃŃҟҟŇŇŇŇŇŇÓ Ó ÓÓÓÓÓÓÔˇÔÔÔÔÔÔŐ˘ŐŐŐŐŐŐÖŁÖÖÖÖפפ××××ŘĄŘŘŘŘ٦ŮŮŮŮÚ§ÚÚۨۨŰŰÜ©ÜÜÝŞÝÝÝÝŢ«ŢŢ߬ßßŕ­ŕŕá®ááââă°ăăä±ääĺ˛ćłććç´čµččééę·ęęëëěěíşî»îîďďđđńńňżóŔóóôôőőöö÷÷řřůůúúűűüüýýţţ˙˙9:; BCFGIKMNPQR S!!!T!T"""U###V$$$$$W%%%X%X&&&Y&Y'''''Z((((([)))))))\*******]+++++++^+^,,,,,,,_,_-------`-`.........a.a/////////b/b00000000000c0c0c1111111111111d1d222222222222222e2e2e33333333333333333f3f3f444444444444444444444g4g4g4g55555555555555555555555h5h5h5h6666666666666666666666666666666i6i6i6i6i777777777777777777777777777777777777777j7j7j7j7j888888888888888888888888888888888888888888888888888k8k8k8k8k99999999999999999999999999999999999999999999999999999999999999999999999l9l9l9l9l9l:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::m:m:m:m:m:m:m:m;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐÖÖÖÖÖÖÖÖ××××××ŘŘŘŘŘŘŮŮŮŮŮŮÚÚÚÚÚÚŰŰŰŰÜÜÜÜÝÝÝÝÝÝŢŢŢŢßßßßŕŕŕŕááââââăăăăääääĺĺććććççččééééęęëëěěěěííîîďďđđńńńńňňóóôôőőöö÷÷řřůůúúűűüüýýţţ˙˙  !!!!""""""####$$$$$$%%%%&&&&&&''''''''(((((())))))))********++++++++,,,,,,,,,,----------............////////////000000000000001111111111111111112222222222222222223333333333333333333333444444444444444444444444445555555555555555555555555555556666666666666666666666666666666666666677777777777777777777777777777777777777777777777788888888888888888888888888888888888888888888888888888888888899999999999999999999999999999999999999999999999999999999999999999999999999999999::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔŐŐŐŐŐŐŐŐÖÖÖÖÖÖ××××××ŘŘŘŘŘŘŮŮŮŮŮŮÚÚÚÚÚÚŰŰŰŰÜÜÜÜÜÜÝÝÝÝŢŢŢŢßßßßŕŕŕŕááââââăăăăääĺĺĺĺććççççččééęęęęëëěěííîîîîďďđđńńňňóóôôőőöö÷÷řřůůúúűűüüýýţţ˙˙  !!!!""""""####$$$$%%%%%%&&&&&&''''''(((((())))))))********++++++++,,,,,,,,,,----------..........//////////////00000000000000111111111111111122222222222222222233333333333333333333334444444444444444444444445555555555555555555555555555556666666666666666666666666666666666666677777777777777777777777777777777777777777777778888888888888888888888888888888888888888888888888888888888999999999999999999999999999999999999999999999999999999999999999999999999999999::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ĂĂĂöĂöĂöĂöĂöĂöĂöĂöĂöÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ÷Ä÷Ä÷Ä÷Ä÷Ä÷Ä÷ĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹřĹřĹřĹřĹřĹřĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆůĆůĆůĆůĆůĆůÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇúÇúÇúÇúÇúČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČűČűČűČűÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉüÉüÉüÉüĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘýĘýĘýĘýËËËËËËËËËËËËËËËËËţËţËţĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚ˙Ě˙Ě˙ÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇÓÓÓÓÓÓÔÔÔÔÔÔŐŐŐŐŐŐÖÖÖÖÖ× × ××Ř Ř ŘŘŮ Ů ŮŮÚ ÚÚÚÚŰ ŰŰÜÜÜÜÜÝÝÝŢŢŢßßßŕŕŕááâââăăăäĺĺĺćçççččéęęęëëěěíî îîďďđđńńňňóóôôőőöö÷÷řřůůúúűűüüýýţţ˙˙ ŰÜÝáâäĺçéęëěí î!!!ď"""đ"đ###ń$$$ň%%%%%ó&&&&&ô'''''ő(((((ö)))))÷)÷*****ř*ř+++++ů+ů,,,,,,,ú,ú-------ű-ű.......ü.ü/////////ý/ý/ý000000000ţ0ţ0ţ11111111111˙1˙1˙222222222222333333333333333334444444444444444444444555555555555555555555555566666666666666666666666666666666777777777777777777777777777777777777777778888888888888888888888888888888888888888888888888889999999999999999999999999999999999999999999999999999999999999999999999:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄ*Ä*Ä*Ä*Ä*Ä*Ä*Ä*Ä*ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹ+Ĺ+Ĺ+Ĺ+Ĺ+Ĺ+Ĺ+Ĺ+ĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆ,Ć,Ć,Ć,Ć,Ć,ĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇ-Ç-Ç-Ç-Ç-ÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČ.Č.Č.Č.Č.ČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉ/É/É/É/ÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘ0Ę0Ę0Ę0ĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘË1Ë1Ë1ËËËËËËËËËËËËËËËËĚ2Ě2Ě2ĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍ3Í3ÍÍÍÍÍÍÍÍÍÍÍÍÎ4Î4Î4ÎÎÎÎÎÎÎÎĎ5Ď5Ď5ĎĎĎĎĎĎĎĎĐ6Đ6ĐĐĐĐĐĐĐĐŃ7Ń7ŃŃŃŃŃŃŇ8Ň8ŇŇŇŇŇŇÓ9Ó9ÓÓÓÓÔ:Ô:ÔÔÔÔŐ;Ő;ŐŐÖ<Ö<ÖÖÖÖ×=××××Ř>ŘŘŘŘŮ?ŮŮÚ@Ú@ÚÚŰAŰŰÜBÜBÜÜÝCÝÝŢDŢŢßEßßŕFáGááâHââăIăăääĺKĺĺććçMčNččééęPëQěRěěííîîďďđVńWňXóYôZő[ö\÷]ř^ů_ú`űaüüýýţţ˙˙ Ş«¬Ż°˛ł´µ¶·¸ą ş!!!»"""Ľ#####˝$$$ľ%%%ż%ż&&&Ŕ&Ŕ'''Á'Á(((Â(Â)))))Ă*****Ä*Ä+++++Ĺ+Ĺ,,,,,,,Ć,Ć-----Ç-Ç-Ç.......Č.Č/////////É/É00000000000Ę0Ę11111111111Ë1Ë1Ë2222222222222Ě2Ě2Ě333333333333333Í3Í3Í4444444444444444444Î4Î4Î4Î55555555555555555555555Ď5Ď5Ď66666666666666666666666666666Đ6Đ6Đ6Đ7777777777777777777777777777777777777Ń7Ń7Ń7Ń7Ń888888888888888888888888888888888888888888888Ň8Ň8Ň8Ň8Ň8Ň999999999999999999999999999999999999999999999999999999999999999Ó9Ó9Ó9Ó9Ó9Ó9Ó:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::Ô:Ô:Ô:Ô:Ô:Ô:Ô:Ô;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄ]Ä]Ä]Ä]Ä]Ä]Ä]Ä]Ä]ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹ^Ĺ^Ĺ^Ĺ^Ĺ^Ĺ^Ĺ^ĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆ_Ć_Ć_Ć_Ć_Ć_ĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇ`Ç`Ç`Ç`Ç`ÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČaČaČaČaČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉbÉbÉbÉbÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘcĘcĘcĘcĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËdËdËdËËËËËËËËËËËËËËËËĚeĚeĚeĚĚĚĚĚĚĚĚĚĚĚĚÍfÍfÍfÍÍÍÍÍÍÍÍÍÍÍÍÎgÎgÎÎÎÎÎÎÎÎÎÎĎhĎhĎĎĎĎĎĎĎĎĐiĐiĐiĐĐĐĐĐĐŃjŃjŃŃŃŃŃŃŇkŇkŇŇŇŇŇŇÓlÓÓÓÓÓÓÔmÔmÔÔÔÔŐnŐŐŐŐÖoÖoÖÖ×p×p××ŘqŘqŘŘŮrŮrŮŮÚsÚÚŰtŰtŰŰÜuÜÜÝvÝÝŢwŢŢßxßßŕyázááâ{ââă|ä}ääĺ~ćććççčé‚ééęęëëěěí†î‡ďďďđđńńňňóóôôőőöö÷÷řřůůúúü•ýýţţ˙˙ z{|~„…† ‡!!!"""‰###Š#Š$$$‹%%%Ś&&&&&Ť'''''Ž(((((Ź))))))***‘*‘+++++’+’,,,,,,,“,“-----”-”.........•.•///////–/–/–000000000—0—0—111111111112222222222222™2™2™333333333333333š3š3š4444444444444444444›4›4›55555555555555555555555ś5ś5ś5ś666666666666666666666666666ť6ť6ť6ť77777777777777777777777777777777777ž7ž7ž7ž7ž888888888888888888888888888888888888888888888ź8ź8ź8ź8ź999999999999999999999999999999999999999999999999999999999999999 9 9 9 9 9 :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::ˇ:ˇ:ˇ:ˇ:ˇ:ˇ:ˇ:ˇ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂđđđđđđđđđÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹ’Ĺ’Ĺ’Ĺ’Ĺ’Ĺ’Ĺ’ĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹƓƓƓƓƓƓĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆǔǔǔǔǔǔÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇȕȕȕȕȕČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉ–É–É–É–ÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘ—Ę—Ę—ĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËË̙̙̙ĚĚĚĚĚĚĚĚĚĚĚĚ͚͚͚ÍÍÍÍÍÍÍÍÍÍΛΛΛÎÎÎÎÎÎÎÎϜϜϜĎĎĎĎĎĎĎĎННĐĐĐĐĐĐўўŃŃŃŃŃŃҟҟŇŇŇŇÓ Ó ÓÓÓÓÓÓÔˇÔÔÔÔŐ˘Ő˘ŐŐŐŐÖŁÖÖÖÖפ××××ŘĄŘŘŘŘ٦ŮŮÚ§ÚÚÚÚۨŰŰÜ©ÜÜÝŞÝÝŢ«ŢŢ߬ßßŕ­á®ááâŻââăăä±ääĺĺćłç´ççččé¶ę·ë¸ěąěěííîîďďđđńńňňóóôôőőöö÷÷řřůůúúűűýýţţ˙˙:; < = > ? @ABCDEIJLMNOPR S!!!T"""U###V$$$W$W%%%X&&&Y&Y'''Z'Z((([([)))\)\*****]+++++++^,,,,,,,_,_-----`-`.......a.a.a///////b/b000000000c0c0c11111111111d1d2222222222222e2e2e333333333333333f3f3f44444444444444444g4g4g4g555555555555555555555h5h5h5h6666666666666666666666666i6i6i6i6i777777777777777777777777777777777j7j7j7j7j8888888888888888888888888888888888888888888k8k8k8k8k8k99999999999999999999999999999999999999999999999999999999999l9l9l9l9l9l9l:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::m:m:m:m:m:m:m:m;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔŐŐŐŐŐŐÖÖÖÖÖÖ××××××ŘŘŘŘŘŘŮŮŮŮÚÚÚÚÚÚŰŰŰŰÜÜÜÜÝÝÝÝŢŢŢŢßßßßŕŕááááââăăăăääĺĺĺĺććççččččééęęëëěěííîîďďđđńńňňóóôôőőöö÷÷řřůůúúűűýýţţ˙˙  !!!!""""####$$$$%%%%%%&&&&''''''(((((())))))********++++++++,,,,,,,,--------..........////////////0000000000001111111111111111222222222222222233333333333333333333444444444444444444444455555555555555555555555555556666666666666666666666666666666677777777777777777777777777777777777777777788888888888888888888888888888888888888888888888888889999999999999999999999999999999999999999999999999999999999999999999999::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇÓÓÓÓÓÓÓÓÔÔÔÔÔÔÔÔŐŐŐŐŐŐÖÖÖÖÖÖ××××××ŘŘŘŘŮŮŮŮŮŮÚÚÚÚŰŰŰŰÜÜÜÜÝÝÝÝŢŢŢŢßßßßŕŕááááââăăăăääĺĺććććççččééęęëëëëěěííîîďďđđńńňňóóőőöö÷÷řřůůúúűűüüţţ˙˙  !!!!""""####$$$$%%%%&&&&&&''''''(((())))))))******++++++++,,,,,,,,--------..........////////////000000000000111111111111112222222222222222333333333333333333444444444444444444444455555555555555555555555555556666666666666666666666666666666677777777777777777777777777777777777777778888888888888888888888888888888888888888888888888899999999999999999999999999999999999999999999999999999999999999999999::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<ĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂöĂöĂöĂöĂöĂöĂöĂöĂöÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ÷Ä÷Ä÷Ä÷Ä÷Ä÷Ä÷ĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹřĹřĹřĹřĹřĹřĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆůĆůĆůĆůĆůÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇúÇúÇúÇúČČČČČČČČČČČČČČČČČČČČČČČČČČČČČűČűČűČűÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉüÉüÉüÉüĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘýĘýĘýĘýËËËËËËËËËËËËËţËţËţËţĚĚĚĚĚĚĚĚĚĚĚ˙Ě˙Ě˙ÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐŃŃŃŃŃŃŃŇŇŇŇŇŇÓÓÓÓÓÓÔÔÔÔÔÔŐŐŐŐŐÖÖÖÖ× ××××Ř ŘŘŮ Ů ŮŮÚ ÚÚŰ ŰŰÜÜÜÝÝÝŢŢŢßßßŕáááâăăăääĺćććççčéęëëëěěííîîďďđđńńňňóóő'ö(÷÷řřůůúúűűüüţţ˙˙Ň ŰÜÝŢßŕáĺćčéëěí î!!!ď"""đ###ń$$$ň%%%ó&&&&&ô'''ő'ő(((ö)))))÷)÷***ř*ř+++++ů+ů,,,,,ú,ú-----ű-ű.......ü.ü///////ý/ý/ý0000000ţ0ţ0ţ111111111˙1˙1˙222222222233333333333333344444444444444444445555555555555555555555566666666666666666666666666777777777777777777777777777777777778888888888888888888888888888888888888888888889999999999999999999999999999999999999999999999999999999999999::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;< < < < < < < < < < <<<<<<<<<<<<ĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄ*Ä*Ä*Ä*Ä*Ä*Ä*Ä*Ä*ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹ+Ĺ+Ĺ+Ĺ+Ĺ+Ĺ+Ĺ+ĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆ,Ć,Ć,Ć,Ć,Ć,ĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇ-Ç-Ç-Ç-Ç-Ç-ÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČ.Č.Č.Č.Č.ČČČČČČČČČČČČČČČČČČČČČČČČČČÉ/É/É/É/ÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘ0Ę0Ę0Ę0ĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘË1Ë1Ë1ËËËËËËËËËËËËËËĚ2Ě2Ě2ĚĚĚĚĚĚĚĚĚĚÍ3Í3Í3ÍÍÍÍÍÍÍÍÎ4Î4Î4ÎÎÎÎÎÎÎÎĎ5Ď5ĎĎĎĎĎĎĎĎĐ6Đ6ĐĐĐĐĐĐŃ7Ń7ŃŃŃŃŇ8Ň8ŇŇŇŇÓ9Ó9ÓÓÓÓÔ:Ô:ÔÔŐ;Ő;ŐŐŐŐÖ<ÖÖ×=×=××Ř>Ř>ŘŘŮ?ŮŮÚ@ÚÚŰAŰŰÜBÜÜÝCÝÝŢDŢŢßEßßŕŕáGááâHăIäJääĺKćLçMççččééęęëQěRíSîTďUđđńńňňóóôôőő÷]ř^ůůúúűűüüţţ˙˙ ¤ Ą ¦®Ż°±ł´µ¶·¸ą ş!!!»"""Ľ#˝#˝$$$ľ%%%ż&&&Ŕ&Ŕ'''Á(((Â(Â)))Ă)Ă***Ä*Ä+++++Ĺ+Ĺ,,,,,Ć,Ć-----Ç-Ç.......Č.Č///////É/É000000000Ę0Ę111111111Ë1Ë1Ë22222222222Ě2Ě2Ě3333333333333Í3Í3Í444444444444444Î4Î4Î4Î5555555555555555555Ď5Ď5Ď5Ď66666666666666666666666Đ6Đ6Đ6Đ7777777777777777777777777777777Ń7Ń7Ń7Ń7Ń888888888888888888888888888888888888888Ň8Ň8Ň8Ň8Ň8Ň99999999999999999999999999999999999999999999999999999Ó9Ó9Ó9Ó9Ó9Ó9Ó:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::Ô:Ô:Ô:Ô:Ô:Ô:Ô:Ô;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;Ő;Ő;Ő;Ő;Ő;Ő;Ő;Ő;Ő;Ő<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<ĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄ]Ä]Ä]Ä]Ä]Ä]Ä]Ä]Ä]ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹ^Ĺ^Ĺ^Ĺ^Ĺ^Ĺ^Ĺ^Ĺ^ĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆ_Ć_Ć_Ć_Ć_Ć_ĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇ`Ç`Ç`Ç`Ç`ÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČaČaČaČaČaČČČČČČČČČČČČČČČČČČČČČČČČÉbÉbÉbÉbÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘcĘcĘcĘcĘĘĘĘĘĘĘĘĘĘĘĘĘĘËdËdËdËdËËËËËËËËËËËËĚeĚeĚeĚĚĚĚĚĚĚĚĚĚÍfÍfÍfÍÍÍÍÍÍÍÍÎgÎgÎgÎÎÎÎÎÎĎhĎhĎhĎĎĎĎĎĎĐiĐiĐĐĐĐĐĐŃjŃjŃŃŃŃŇkŇkŇŇŇŇÓlÓlÓÓÓÓÔmÔmÔÔŐnŐnŐŐÖoÖoÖÖ×p×p××ŘqŘŘŮrŮŮÚsÚsŰtŰtÜuÜuÝvÝvŢwŢŢßxŕyŕŕázááââă|ä}ääĺĺćç€čé‚ęęęëëěěííîîđ‰ńŠň‹óŚôôőőöö÷÷ů’úúűűüüţ—˙˙j p xyz{|€„…† ‡!!!"‰"‰#Š$$$‹$‹%Ś%Ś&&&Ť'''Ž'Ž(((Ź)))))*****‘+++++’+’,,,,,“,“-----”-”.....•.•.•/////–/–/–0000000—0—0—111111111122222222222™2™2™3333333333333š3š3š444444444444444›4›4›5555555555555555555ś5ś5ś5ś666666666666666666666ť6ť6ť6ť6ť77777777777777777777777777777ž7ž7ž7ž7ž8888888888888888888888888888888888888ź8ź8ź8ź8ź8ź999999999999999999999999999999999999999999999999999 9 9 9 9 9 9 :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::ˇ:ˇ:ˇ:ˇ:ˇ:ˇ:ˇ:ˇ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;˘;˘;˘;˘;˘;˘;˘;˘;˘;˘<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<ĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂđđđđđđđđđÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹ’Ĺ’Ĺ’Ĺ’Ĺ’Ĺ’Ĺ’ĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹƓƓƓƓƓƓĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆǔǔǔǔǔǔÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇȕȕȕȕȕČČČČČČČČČČČČČČČČČČČČČČÉ–É–É–É–ÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘ—Ę—Ę—ĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËË̙̙̙ĚĚĚĚĚĚĚĚĚĚ͚͚͚ÍÍÍÍÍÍÍÍΛΛÎÎÎÎÎÎÎÎϜϜĎĎĎĎĎĎННĐĐĐĐĐĐўўŃŃŃŃҟҟŇŇŇŇÓ Ó ÓÓÓÓÔˇÔÔÔÔŐ˘Ő˘ŐŐÖŁÖÖÖÖפ××إإ٦٦ŮŮÚ§ÚÚۨŰŰÜ©ÜÜÝŞŢ«ŢŢ߬ŕ­ŕŕá®âŻââă°ä±ĺ˛ĺĺććççččé¶ę·ë¸ěąíşîîďďđđńńňňôÁőőöö÷÷řřúúűűüüýý˙˙:; @ABCIJKLNOPQR S!T!T"U###V$$$W%%%X&&&Y&Y'''Z((([([)))\)\***]*]+++^+^,,,,,_,_-----`-`.....a.a///////b/b0000000c0c0c111111111d1d1d222222222e2e2e33333333333f3f3f3f4444444444444g4g4g4g55555555555555555h5h5h5h666666666666666666666i6i6i6i77777777777777777777777777777j7j7j7j7j88888888888888888888888888888888888k8k8k8k8k8k9999999999999999999999999999999999999999999999999l9l9l9l9l9l9l:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::m:m:m:m:m:m:m:m;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;n;n;n;n;n;n;n;n;n;n<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<ÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇÓÓÓÓÓÓÔÔÔÔÔÔÔÔŐŐŐŐÖÖÖÖÖÖ××××××ŘŘŘŘŮŮŮŮÚÚÚÚŰŰŰŰÜÜÜÜÝÝŢŢŢŢßßŕŕŕŕááââââăăääĺĺććççččččééęęëëěěîîďďđđńńňňóóőőöö÷÷řřúúűűüüýý˙˙  !!""""####$$$$%%%%&&&&''''''(((())))))******++++++,,,,,,,,--------........//////////00000000001111111111111122222222222222333333333333333344444444444444444444555555555555555555555555666666666666666666666666666677777777777777777777777777777777777788888888888888888888888888888888888888888888999999999999999999999999999999999999999999999999999999999999::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËËËËËËËĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĎĎĐĐĐĐĐĐĐĐŃŃŃŃŃŃŃŃŇŇŇŇŇŇŇŇÓÓÓÓÓÓÔÔÔÔÔÔŐŐŐŐŐŐÖÖÖÖÖÖ××××ŘŘŘŘŮŮŮŮÚÚÚÚŰŰŰŰÜÜÜÜÝÝŢŢŢŢßßŕŕŕŕááââăăăăääĺĺććççččééęęëëěěííîîďďđđňňóóôôőő÷÷řřůůűűüüýý˙˙  !!""""####$$$$%%%%&&&&''''(((((())))******++++++,,,,,,,,--------........//////////000000000011111111111122222222222222333333333333333344444444444444444455555555555555555555555566666666666666666666666666777777777777777777777777777777777777888888888888888888888888888888888888888888889999999999999999999999999999999999999999999999999999999999::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂőÂőÂőÂőÂőÂőÂőÂőÂőÂőÂőÂőĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂöĂöĂöĂöĂöĂöĂöĂöĂöÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ÷Ä÷Ä÷Ä÷Ä÷Ä÷Ä÷Ä÷ĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹřĹřĹřĹřĹřĹřĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆůĆůĆůĆůĆůÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇúÇúÇúÇúÇúČČČČČČČČČČČČČČČČČČČČČČČűČűČűČűÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉüÉüÉüÉüĘĘĘĘĘĘĘĘĘĘĘĘĘýĘýĘýĘýËËËËËËËËËËËţËţËţĚĚĚĚĚĚĚĚĚĚĚ˙Ě˙Ě˙ÍÍÍÍÍÍÎÎÎÎÎÎÎÎÎĎĎĎĎĎĎĎĎĐĐĐĐĐĐŃŃŃŃŃŃŇŇŇŇŇŇÓÓÓÓÔÔÔÔŐŐŐŐÖÖÖÖ× ××Ř ŘŘŮ Ů Ú Ú Ű ŰŰÜÜÜÝŢŢŢßŕŕŕáâăăăääĺćçčéęëěííîîďďđđň$óóôôőő÷)řřůůűűüüýý˙˙ĐÔ Ř ÝŢßçčéęëěí î!ď"""đ###ń$ň$ň%ó%ó&ô&ô'''ő(((ö(ö)))÷***ř*ř+++ů+ů,,,,,ú,ú-----ű-ű.....ü.ü/////ý/ý/ý00000ţ0ţ0ţ1111111˙1˙1˙22222222333333333333344444444444444455555555555555555556666666666666666666666777777777777777777777777777777888888888888888888888888888888888888899999999999999999999999999999999999999999999999999:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;< < < < < < < < < < < <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂ)Ă)Ă)Ă)Ă)Ă)Ă)Ă)Ă)Ă)Ă)ĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄ*Ä*Ä*Ä*Ä*Ä*Ä*Ä*Ä*ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹ+Ĺ+Ĺ+Ĺ+Ĺ+Ĺ+Ĺ+ĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆ,Ć,Ć,Ć,Ć,Ć,ĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇ-Ç-Ç-Ç-Ç-Ç-ÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČ.Č.Č.Č.Č.ČČČČČČČČČČČČČČČČČČČČÉ/É/É/É/ÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘ0Ę0Ę0Ę0ĘĘĘĘĘĘĘĘĘĘĘĘË1Ë1Ë1ËËËËËËËËËËĚ2Ě2Ě2ĚĚĚĚĚĚĚĚĚĚÍ3Í3ÍÍÍÍÍÍÍÍÎ4Î4ÎÎÎÎÎÎĎ5Ď5Ď5ĎĎĎĎĐ6Đ6ĐĐĐĐŃ7Ń7Ń7ŃŃŇ8Ň8ŇŇŇŇÓ9Ó9ÓÓÔ:Ô:ÔÔŐ;Ő;ŐŐÖ<ÖÖ×=×=Ř>Ř>ŘŘŮ?ŮŮÚ@ŰAŰAÜBÜÜÝCŢDŢDßEŕFŕŕáGâHăIäJääĺĺććççččééęęëëíSîTďUđđńńóYôZőőööřřůůűaüüýý˙˙ź Ł §¨¬­®Ż°±˛ł´µ¶·¸ą ş!»"""Ľ#˝#˝$ľ%%%ż&&&Ŕ'''Á'Á(Â(Â)))Ă)Ă***Ä+++++Ĺ,,,,,Ć,Ć---Ç-Ç-Ç...Č.Č.Č/////É/É0000000Ę0Ę1111111Ë1Ë1Ë222222222Ě2Ě2Ě333333333Í3Í3Í3Í44444444444Î4Î4Î4Î555555555555555Ď5Ď5Ď5Ď6666666666666666666Đ6Đ6Đ6Đ7777777777777777777777777Ń7Ń7Ń7Ń7Ń8888888888888888888888888888888Ň8Ň8Ň8Ň8Ň8Ň9999999999999999999999999999999999999999999Ó9Ó9Ó9Ó9Ó9Ó9Ó:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::Ô:Ô:Ô:Ô:Ô:Ô:Ô:Ô;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;Ő;Ő;Ő;Ő;Ő;Ő;Ő;Ő;Ő;Ő<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂ\Ă\Ă\Ă\Ă\Ă\Ă\Ă\Ă\Ă\Ă\Ă\ĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄ]Ä]Ä]Ä]Ä]Ä]Ä]Ä]Ä]ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹ^Ĺ^Ĺ^Ĺ^Ĺ^Ĺ^Ĺ^ĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆ_Ć_Ć_Ć_Ć_Ć_ĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇ`Ç`Ç`Ç`Ç`ÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČaČaČaČaČČČČČČČČČČČČČČČČČČČČÉbÉbÉbÉbÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘcĘcĘcĘĘĘĘĘĘĘĘĘĘĘĘËdËdËdËdËËËËËËËËĚeĚeĚeĚeĚĚĚĚĚĚÍfÍfÍfÍÍÍÍÍÍÎgÎgÎgÎÎÎÎÎÎĎhĎhĎĎĎĎĐiĐiĐiĐĐĐĐŃjŃjŃŃŇkŇkŇŇŇŇÓlÓlÓÓÔmÔmÔÔŐnŐŐÖoÖoÖÖ×p××ŘqŘŘŮrŮŮÚsÚÚŰtÜuÜuÝvÝÝŢwßxŕyŕŕázâ{ă|ä}ĺ~ćç€čé‚ęë„ě…ííîîđ‰ńŠňňóóőŽööř‘ůůúúüüýý˙˙o swx}~€‚„…† ‡!"""‰#Š$$$‹%%%Ś&&&Ť'''Ž(((Ź))))***‘*‘+++’+’,,,“,“---”-”.....•.•/////–/–/–00000—0—0—11111111222222222™2™2™333333333š3š3š4444444444444›4›4›555555555555555ś5ś5ś5ś66666666666666666ť6ť6ť6ť6ť77777777777777777777777ž7ž7ž7ž7ž88888888888888888888888888888ź8ź8ź8ź8ź8ź9999999999999999999999999999999999999999999 9 9 9 9 9 :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::ˇ:ˇ:ˇ:ˇ:ˇ:ˇ:ˇ:ˇ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;˘;˘;˘;˘;˘;˘;˘;˘;˘;˘<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂđđđđđđđđđÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹ’Ĺ’Ĺ’Ĺ’Ĺ’Ĺ’Ĺ’Ĺ’ĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹƓƓƓƓƓƓĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆǔǔǔǔǔÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇȕȕȕȕȕČČČČČČČČČČČČČČČČČČÉ–É–É–É–ÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘ—Ę—Ę—Ę—ĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËËËËËË̙̙̙ĚĚĚĚĚĚ͚͚͚ÍÍÍÍÍÍΛΛΛÎÎÎÎϜϜϜĎĎĎĎННĐĐĐĐўўŃŃҟҟŇŇŇŇÓ Ó ÓÓÔˇÔÔŐ˘Ő˘ŐŐÖŁÖÖפפإإ٦٦ڧÚÚۨŰŰܩݪÝÝޫ߬ŕ­ŕŕááâŻă°ä±ĺ˛ćłç´čµé¶ęęëëíşî»ďďđđňżóóôôöö÷÷ůůúúüüýý˙˙ > ABEFGHOPQR S!T"U"U#V$$$W%X%X&Y&Y'Z'Z([([)))\***]*]+++^+^,,,_,_---`-`.....a.a/////b/b0000000c0c1111111d1d1d2222222e2e2e333333333f3f3f44444444444g4g4g4g5555555555555h5h5h5h66666666666666666i6i6i6i77777777777777777777777j7j7j7j7j88888888888888888888888888888k8k8k8k8k99999999999999999999999999999999999999999l9l9l9l9l9l9l:::::::::::::::::::::::::::::::::::::::::::::::::::::::::m:m:m:m:m:m:m:m;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;n;n;n;n;n;n;n;n;n;n<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<Ů?Ů?Ú@ÚÚŰAÜBÜÜÝCŢDßEŕFáGâHăIäJĺKćLçMčNééëQěRííďUđđňXóóő[ööřřú`űűýý˙˙›  Ą ¨«®Ż°ł´µ¶·¸ą ş!»"Ľ#˝#˝$ľ%ż%ż&Ŕ'''Á(((Â)))Ă***Ä*Ä+Ĺ+Ĺ,,,Ć,Ć---Ç-Ç...Č.Č/////É/É00000Ę0Ę11111Ë1Ë1Ë2222222Ě2Ě2Ě3333333Í3Í3Í444444444Î4Î4Î4Î55555555555Ď5Ď5Ď5Ď6666666666666Đ6Đ6Đ6Đ6Đ7777777777777777777Ń7Ń7Ń7Ń7Ń88888888888888888888888Ň8Ň8Ň8Ň8Ň8Ň99999999999999999999999999999999999Ó9Ó9Ó9Ó9Ó9Ó9Ó:::::::::::::::::::::::::::::::::::::::::::::::::Ô:Ô:Ô:Ô:Ô:Ô:Ô:Ô;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;Ő;Ő;Ő;Ő;Ő;Ő;Ő;Ő;Ő;Ő<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<Ö<Ö<Ö<Ö<Ö<Ö<Ö<Ö<Ö<Ö<Ö<Ö<Ö========================================================================Â[Â[Â[ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂ\Ă\Ă\Ă\Ă\Ă\Ă\Ă\Ă\Ă\Ă\Ă\ĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄ]Ä]Ä]Ä]Ä]Ä]Ä]Ä]Ä]ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹ^Ĺ^Ĺ^Ĺ^Ĺ^Ĺ^Ĺ^ĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆ_Ć_Ć_Ć_Ć_Ć_ĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇ`Ç`Ç`Ç`Ç`Ç`ÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČaČaČaČaČaČČČČČČČČČČČČČČÉbÉbÉbÉbÉÉÉÉÉÉÉÉÉÉÉÉĘcĘcĘcĘĘĘĘĘĘĘĘËdËdËdËdËËËËËËĚeĚeĚeĚĚĚĚĚĚÍfÍfÍfÍÍÍÍÎgÎgÎgÎÎĎhĎhĎhĎĎĐiĐiĐĐŃjŃjŃjŃŃŇkŇkÓlÓlÓÓÔmÔmŐnŐnÖoÖo×p×pŘqŘqŮrÚsÚsŰtÜuÜÜÝvŢwßxŕyázâ{ă|ä}ĺ~ćç€é‚ęë„í†î‡đ‰ńńóŚôôööř‘ůůűűýý˙˙o q tvwyz}~‚„…† ‡ ‡!"‰#Š$$$‹%Ś&&&Ť'Ž'Ž(Ź(Ź))*‘*‘+’+’,,,“,“---”-”...•.•///–/–/–000—0—0—1111112222222™2™2™3333333š3š3š444444444›4›4›55555555555ś5ś5ś5ś6666666666666ť6ť6ť6ť7777777777777777777ž7ž7ž7ž7ž88888888888888888888888ź8ź8ź8ź8ź999999999999999999999999999999999 9 9 9 9 9 9 :::::::::::::::::::::::::::::::::::::::::::::::ˇ:ˇ:ˇ:ˇ:ˇ:ˇ:ˇ:ˇ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;˘;˘;˘;˘;˘;˘;˘;˘;˘;˘<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<Ł<Ł<Ł<Ł<Ł<Ł<Ł<Ł<Ł<Ł<Ł<Ł<Ł<Ł==================================================================================================ÁÁÁÁÁÁÂŹÂŹÂŹÂŹÂŹÂŹÂŹÂŹÂŹÂŹÂŹÂŹÂŹÂŹÂŹÂŹÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂđđđđđđđđđÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹ’Ĺ’Ĺ’Ĺ’Ĺ’Ĺ’Ĺ’ĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹƓƓƓƓƓƓĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆǔǔǔǔǔÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇȕȕȕȕȕČČČČČČČČČČČČÉ–É–É–É–ÉÉÉÉÉÉÉÉÉÉĘ—Ę—Ę—Ę—ĘĘĘĘĘĘĘĘËËËËËËËËË̙̙̙ĚĚĚĚĚĚ͚͚͚ÍÍÍÍΛΛÎÎÎÎϜϜĎĎНННĐĐўўŃŃҟҟӠӠÓÓԡԡբբ֣֣פ××إ٦٦ڧۨܩܩݪޫ߬ŕ­á®âŻă°ä±ĺ˛ććčµé¶ęęěąííďĽńľňňôÁöĂ÷÷ůůűűýý˙˙68 ?ADGHJKLMOPQR S!!!T"U#V$W$W%X&Y&Y'Z([([)\)\*]*]+++^,,,_,_---`-`...a.a///b/b00000c0c11111d1d1d22222e2e2e33333f3f3f3f4444444g4g4g4g555555555h5h5h5h6666666666666i6i6i6i77777777777777777j7j7j7j7j888888888888888888888k8k8k8k8k8k99999999999999999999999999999l9l9l9l9l9l9l:::::::::::::::::::::::::::::::::::::::::::::m:m:m:m:m:m:m:m;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;n;n;n;n;n;n;n;n;n;n<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<Ů?Ů?Ú@ŰAÜBÝCŢDßEŕFáGâHäJĺKćLčNéOëQíSďUđđňňôôööřřúúýý˙˙˘ ¤ ¦¨Ş¬­Ż±˛´µ¶·ą ş!»"Ľ#˝$W$ľ%ż&Ŕ'Á'Á(Â)Ă)Ă*Ä*Ä+Ĺ+Ĺ,Ć,Ć-Ç-Ç...Č.Č///É/É000Ę0Ę0Ę111Ë1Ë22222Ě2Ě2Ě33333Í3Í3Í44444Î4Î4Î4Î5555555Ď5Ď5Ď5Ď666666666Đ6Đ6Đ6Đ6Đ7777777777777Ń7Ń7Ń7Ń7Ń88888888888888888Ň8Ň8Ň8Ň8Ň9999999999999999999999999Ó9Ó9Ó9Ó9Ó9Ó9Ó:::::::::::::::::::::::::::::::::::::Ô:Ô:Ô:Ô:Ô:Ô:Ô:Ô;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;Ő;Ő;Ő;Ő;Ő;Ő;Ő;Ő;Ő;Ő<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<Ö<Ö<Ö<Ö<Ö<Ö<Ö<Ö<Ö<Ö<Ö<Ö<Ö<Ö==============================================================================================================================================================================================================ÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂ[Â[Â[Â[Â[Â[Â[Â[Â[Â[Â[Â[Â[Â[Â[Â[ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂ\Ă\Ă\Ă\Ă\Ă\Ă\Ă\Ă\Ă\Ă\Ă\ĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄ]Ä]Ä]Ä]Ä]Ä]Ä]Ä]Ä]ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹ^Ĺ^Ĺ^Ĺ^Ĺ^Ĺ^Ĺ^Ĺ^ĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆ_Ć_Ć_Ć_Ć_Ć_ĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇ`Ç`Ç`Ç`Ç`ÇÇÇÇÇÇÇÇÇÇÇÇÇÇČaČaČaČaČČČČČČČČČČÉbÉbÉbÉbÉÉÉÉÉÉĘcĘcĘcĘcĘĘĘĘĘĘËdËdËdËËËËĚeĚeĚeĚĚÍfÍfÍfÍÍÎgÎgÎgÎÎĎhĎhĎĎĐiĐiŃjŃjŃjŇkŇkÓlÓlÔmÔmŐnÖoÖo×pŘqŘqŮrÚsŰtÜuÝvŢwßxŕyázăä}ĺ~ç€čęě…î‡đ‰ň‹ôŤöŹřřúúý–˙˙km tvxz{}€‚„ě ‡!"‰#Š$‹%Ś%Ś&Ť'Ž(Ź(Ź)*‘*‘+’+’,“,“-”-”...•.•/–/–/–0—0—0—11111222™2™2™333š3š3š3š44444›4›4›5555555ś5ś5ś5ś666666666ť6ť6ť6ť7777777777777ž7ž7ž7ž7ž888888888888888ź8ź8ź8ź8ź8ź99999999999999999999999 9 9 9 9 9 :::::::::::::::::::::::::::::::::::ˇ:ˇ:ˇ:ˇ:ˇ:ˇ:ˇ:ˇ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;˘;˘;˘;˘;˘;˘;˘;˘;˘;˘<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<Ł<Ł<Ł<Ł<Ł<Ł<Ł<Ł<Ł<Ł<Ł<Ł<Ł===========================================================================================================================================================================================================¤=¤=¤=¤=¤=¤=¤=¤=¤=¤=¤=¤=¤=¤=¤=¤ÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂŹÂŹÂŹÂŹÂŹÂŹÂŹÂŹÂŹÂŹÂŹÂŹÂŹÂŹÂŹÂŹÂŹÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂđđđđđđđđđÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹ’Ĺ’Ĺ’Ĺ’Ĺ’Ĺ’Ĺ’ĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹƓƓƓƓƓƓĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆǔǔǔǔǔǔÇÇÇÇÇÇÇÇÇÇȕȕȕȕȕČČČČČČČČÉ–É–É–É–ÉÉÉÉÉÉĘ—Ę—Ę—Ę—ĘĘĘĘËËËËËË̙̙̙ĚĚ͚͚͚ÍÍΛΛΛϜϜϜННĐĐўўҟҟӠӠԡŐnŐ˘ÖŁ×pפإ٦ڧŰtÜuÝvŢw߬ŕ­á®ă°ä±ćłç´é¶ë¸íşďĽńľóŔőőřĹúúüü˙˙ < >EGHJLMOP… S!T"U#V$W%X&Y&Ť'Z([)\)\*]+^+^,_,_-`-`.a.a.a/b/b000c0c111d1d1d222e2e2e333f3f3f44444g4g4g4g55555h5h5h5h6666666i6i6i6i6i77777777777j7j7j7j7j8888888888888k8k8k8k8k8k999999999999999999999l9l9l9l9l9l9l:::::::::::::::::::::::::::::::m:m:m:m:m:m:m:m;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;n;n;n;n;n;n;n;n;n;n<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>ÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘĘĘËËËËËËËËĚĚĚĚĚĚĚĚÍÍÍÍÍÍÍÍÎÎÎÎÎÎĎĎĎĎĐĐĐĐĐĐŃŃŃŃŇŇŇŇÓÓÓÓÔÔŐŐŐŐÖÖ××ŘŘŮŮŮŮÚÚŰŰÜÜŢŢßßŕŕââăăĺĺććččęęěěîîđđňňőő÷÷úúüü˙˙  !!""##$$%%&&''(((())****++,,,,----....//////000000111111222222223333333344444444445555555555556666666666666677777777777777777777888888888888888888888899999999999999999999999999999999::::::::::::::::::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<========================================================================================================================================================================================================================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>ÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘĘĘËËËËËËËËĚĚĚĚĚĚĚĚÍÍÍÍÍÍÎÎÎÎÎÎĎĎĎĎĎĎĐĐĐĐŃŃŃŃŇŇŇŇÓÓÔÔÔÔŐŐÖÖÖÖ××ŘŘŮŮÚÚŰŰÜÜŢŢßßŕŕââăăĺĺççééëëííďďňňôô÷÷ůůüü˙˙  !!""##$$%%&&''(())****++,,,,----....//////00001111112222222233333333444444445555555555556666666666666677777777777777777788888888888888888888889999999999999999999999999999::::::::::::::::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<==========================================================================================================================================================================================================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>ÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁôÁôÁôÁôÁôÁôÁôÁôÁôÁôÁôÁôÁôÁôÁôÁôÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂőÂőÂőÂőÂőÂőÂőÂőÂőÂőÂőĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂöĂöĂöĂöĂöĂöĂöĂöĂöÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ÷Ä÷Ä÷Ä÷Ä÷Ä÷Ä÷Ä÷ĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹřĹřĹřĹřĹřĹřĆĆĆĆĆĆĆĆĆĆĆĆĆůĆůĆůĆůĆůĆůÇÇÇÇÇÇÇÇÇúÇúÇúÇúÇúČČČČČČČűČűČűČűÉÉÉÉÉüÉüÉüÉüĘĘĘýĘýĘýËËËţËţËţĚĚĚ˙Ě˙Ě˙ÎÎÎĎĎĎĐĐŃŃŇŇÓÓ9ÔŐŐ;Ö× Ř Ů Ú Ű ÜÝCŢDŕáGăĺçéëíď!ň$ô&÷)ůůüü˙˙ĐÓ Ř Űݬâäć´¶ëą î!ď#˝$ľ%ż&Ŕ'Á(Â(ö)÷*ř*ř+ů,ú,ú-ű-ű.ü.ü/ý/ý/ý0ţ0ţ1˙1˙1˙223334444445555555666666666677777777777788888888888888899999999999999999999:::::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;< < < < < < < < < < <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<= = = = = = = = = = = = = = ==================================================================================================================================================================> > > > > > > > > > > > > > > > > > > > >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Á'Á'Á'Á'Á'Á'Á'Á'ÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂ(Â(Â(Â(Â(Â(Â(Â(Â(Â(Â(Â(Â(Â(Â(Â(ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂ)Ă)Ă)Ă)Ă)Ă)Ă)Ă)Ă)Ă)Ă)Ă)ĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄ*Ä*Ä*Ä*Ä*Ä*Ä*Ä*Ä*ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹ+Ĺ+Ĺ+Ĺ+Ĺ+Ĺ+Ĺ+ĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆ,Ć,Ć,Ć,Ć,Ć,ĆĆĆĆĆĆĆĆĆĆĆĆÇ-Ç-Ç-Ç-Ç-ÇÇÇÇÇÇÇÇČ.Č.Č.Č.ČČČČČČÉ/É/É/É/ÉÉÉÉĘ0Ę0Ę0ĘĘË1Ë1Ë1Ë1Ě2Ě2Ě2Ě2Í3Í3Í3Î4Î4Î4Ď5Ď5Đ6Đ6Ń7Ń7Ň8Ň8Ó9Ó Ô:Ő;Ö<×=פإ٦ŰAÜBÝCŢ«ŕFâHă°ĺ˛ç´é¶ěRîTńWóóööůůüü˙˙˘ Ą¨Ş­Ż±łµ·R ş"U#V$ľ%ż&Ŕ'Á(Â)Ă*]*Ä+Ĺ,_,Ć-Ç-Ç.Č.Č/É/É0Ę0Ę0Ę1Ë1Ë222Ě2Ě2Ě3Í3Í3Í444Î4Î4Î4Î555Ď5Ď5Ď5Ď66666Đ6Đ6Đ6Đ6Đ7777777Ń7Ń7Ń7Ń7Ń888888888Ň8Ň8Ň8Ň8Ň8Ň999999999999999Ó9Ó9Ó9Ó9Ó9Ó:::::::::::::::::::::::Ô:Ô:Ô:Ô:Ô:Ô:Ô:Ô;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;Ő;Ő;Ő;Ő;Ő;Ő;Ő;Ő;Ő;Ő<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<Ö<Ö<Ö<Ö<Ö<Ö<Ö<Ö<Ö<Ö<Ö<Ö<Ö=====================================================================================================================================================×=×=×=×=×=×=×=×=×=×=×=×=×=×=×=×=×=×=×=×=×>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>ŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁ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ÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂ[Â[Â[Â[Â[Â[Â[Â[Â[Â[Â[Â[Â[Â[Â[Â[Â[ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂ\Ă\Ă\Ă\Ă\Ă\Ă\Ă\Ă\Ă\Ă\ĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄ]Ä]Ä]Ä]Ä]Ä]Ä]Ä]Ä]ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹ^Ĺ^Ĺ^Ĺ^Ĺ^Ĺ^Ĺ^ĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆ_Ć_Ć_Ć_Ć_Ć_ĆĆĆĆĆĆĆĆĆĆÇ`Ç`Ç`Ç`Ç`ÇÇÇÇÇÇČaČaČaČaČaČČČČÉbÉbÉbÉbÉÉĘcĘcĘcĘcĘĘËdËdËdËËĚeĚeĚeÍfÍfÍfÎgÎgĎhĎhĐĐiŃŃjŇkŇkÓlÔmŐÖÖo×pŘqÚ Ű ÜÝvßáâ{ä}ćčëí†đ‰óŚöŹů’ü•˙˙ p svß{äćčęě ‡!ď#Š$‹%ó&ô'ő(ö)*‘+’+ů,“-”-ű.•.ü/–/ý0—0—1112™2™2™3š3š3š444›4›4›555ś5ś5ś5ś66666ť6ť6ť6ť7777777ž7ž7ž7ž7ž8888888ź8ź8ź8ź8ź8ź9999999999999 9 9 9 9 9 9 :::::::::::::::::::ˇ:ˇ:ˇ:ˇ:ˇ:ˇ:ˇ:ˇ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;˘;˘;˘;˘;˘;˘;˘;˘;˘;˘<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<Ł<Ł<Ł<Ł<Ł<Ł<Ł<Ł<Ł<Ł<Ł<Ł<Ł<Ł=======================================================================================================================================¤=¤=¤=¤=¤=¤=¤=¤=¤=¤=¤=¤=¤=¤=¤=¤=¤=¤=¤=¤>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>ŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁŽÁŽÁŽÁŽÁŽÁŽÁŽÁŽÁŽÁŽÁŽÁŽÁŽÁŽÁŽÁŽÁŽÁŽÁŽÁŽÁŽÁŽÁŽÁŽÁŽÁŽÁŽÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂŹÂŹÂŹÂŹÂŹÂŹÂŹÂŹÂŹÂŹÂŹÂŹÂŹÂŹÂŹÂŹÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂđđđđđđđđđÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹ’Ĺ’Ĺ’Ĺ’Ĺ’Ĺ’Ĺ’Ĺ’ĹĹĹĹĹĹĹĹĹĹĹĹƓƓƓƓƓƓĆĆĆĆĆĆĆĆǔǔǔǔǔǔÇÇÇÇȕȕȕȕȕČČÉ–É–É–É–ÉÉĘ—Ę—Ę—Ę—ËËËË̙̙̙͚͚ÎgΛΛϜϜНŃjŃžŇkÓlÓ ÔˇŐnÖo×pŘqŮrÚ§ÜuÝŞßxáză|ĺ~ç€é¶ě…ďňżőÂřĹűű˙˙9 = @CF|K… S!#V$‹%Ś'Z([)\*]*‘+’,_-`-”.a/b/b0c0c1d1d1d2e2e2e3f3f3f4g4g4g4g5h5h5h5h666i6i6i6i6i77777j7j7j7j7j8888888k8k8k8k8k99999999999l9l9l9l9l9l9l:::::::::::::::::m:m:m:m:m:m:m:m;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;n;n;n;n;n;n;n;n;n;n<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>ŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČÉÉÉÉÉÉÉÉÉÉĘĘĘĘĘĘĘĘËËËËËËĚĚĚĚĚĚÍÍÍÍÎÎÎÎĎĎĎĎĐĐĐĐŃŃŇŇÓÓÓÓÔÔŐŐÖÖ××ŮŮÚÚÜÜÝÝßßááăăĺĺččęęííńńôô÷÷űű˙˙  !!##$$&&''(())**++,,----..////0000111122222233333344444455555555666666666677777777777777888888888888888899999999999999999999::::::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<====================================================================================================================================================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>ŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇÇÇÇÇČČČČČČČČČČČČÉÉÉÉÉÉÉÉĘĘĘĘĘĘËËËËËËĚĚĚĚĚĚÍÍÍÍÎÎÎÎĎĎĐĐĐĐŃŃŇŇÓÓÔÔŐŐÖÖ××ŘŘÚÚŰŰÝÝßßááăăććééěěďďóó÷÷űű˙˙  ""##%%&&(())**++,,----..////00111122222233334444445555555566666666667777777777778888888888888899999999999999999999::::::::::::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<========================================================================================================================================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>ŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔóŔóŔóŔóŔóŔóŔóŔóŔóŔóŔóŔóŔóŔóŔóŔóŔóŔóŔóŔóŔóŔóŔóŔóŔóŔóŔóÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁôÁôÁôÁôÁôÁôÁôÁôÁôÁôÁôÁôÁôÁôÁôÁôÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂőÂőÂőÂőÂőÂőÂőÂőÂőÂőÂőĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂöĂöĂöĂöĂöĂöĂöĂöĂöÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ÷Ä÷Ä÷Ä÷Ä÷Ä÷Ä÷ĹĹĹĹĹĹĹĹĹĹĹřĹřĹřĹřĹřĹřĆĆĆĆĆůĆůĆůĆůĆůÇÇÇúÇúÇúÇúÇúČČČűČűČűČűÉüÉüÉüĘýĘýĘýË1ËţËţĚ˙Ě˙ÎÎĎĎ5ĐĐ6Ń7Ň8Ó9Ô:Ő;Ö<×=Ů?Ú@ÜBŢDŕFâHĺKčNëQď!ó%÷)ű-˙˙Ö ÚŞá±´¶†!»#˝$ľ&Ŕ'Á)Ă*Ä+Ĺ,Ć-Ç-ű.Č/É/ý0Ę0ţ1˙1˙333Í44555666677777778888888999999999999:::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;;;;;< < < < < < < < < < < <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<= = = = = = = = = = = = = ================================================================================================> > > > > > > > > > > > > > > > > > > > >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>ŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁ'Á'Á'Á'Á'Á'Á'Á'Á'Á'Á'Á'Á'Á'Á'Á'Á'Á'Á'Á'Á'Á'Á'Á'Á'Á'Á'ÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂ(Â(Â(Â(Â(Â(Â(Â(Â(Â(Â(Â(Â(Â(Â(Â(Â(ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂ)Ă)Ă)Ă)Ă)Ă)Ă)Ă)Ă)Ă)Ă)Ă)ĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄ*Ä*Ä*Ä*Ä*Ä*Ä*Ä*Ä*ÄÄÄÄÄÄÄÄÄÄĹ+Ĺ+Ĺ+Ĺ+Ĺ+Ĺ+Ĺ+Ĺ+ĹĹĹĹĹĹĆ,Ć,Ć,Ć,Ć,Ć,ĆĆÇ-Ç-Ç-Ç-Ç-Ç-Č.Č.Č.Č.Č.É/É/É/É–Ę0Ę0Ę0Ë1Ë1ËĚ2Ě™Í3ÍšÎ4ΛĎ5ĎśĐťŃžŇźÓ ÔˇŐ˘× ŘĄÚ§ÜŢŕ­ă°ćłé¶íşńľö\úú˙˙ž Ł §EHLOě!T#V%X&ô([)\+^,_-`.a.Č/b0c0Ę1d1Ë2e2Ě3f3Í4g4Î4Î5h5Ď5Ď5Ď6Đ6Đ6Đ6Đ777Ń7Ń7Ń7Ń7Ń8Ň8Ň8Ň8Ň8Ň8Ň99999Ó9Ó9Ó9Ó9Ó9Ó9Ó:::::::::Ô:Ô:Ô:Ô:Ô:Ô:Ô:Ô;;;;;;;;;;;;;;;;;Ő;Ő;Ő;Ő;Ő;Ő;Ő;Ő;Ő;Ő<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<Ö<Ö<Ö<Ö<Ö<Ö<Ö<Ö<Ö<Ö<Ö<Ö<Ö<Ö=================================================================================×=×=×=×=×=×=×=×=×=×=×=×=×=×=×=×=×=×=×=×=×>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Ř>Ř>Ř>Ř>Ř>Ř>Ř>Ř>Ř>Ř>Ř>Ř>Ř>Ř>Ř>Ř>Ř>Ř>Ř>Ř>Ř>Ř>Ř>Ř>Ř>Ř>Ř>Ř>Ř>Ř>Ř>Ř>Ř>Ř>Ř>Ř>ŘŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁ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ÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂ[Â[Â[Â[Â[Â[Â[Â[Â[Â[Â[Â[Â[Â[Â[Â[ÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂ\Ă\Ă\Ă\Ă\Ă\Ă\Ă\Ă\Ă\Ă\ĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄ]Ä]Ä]Ä]Ä]Ä]Ä]Ä]Ä]ÄÄÄÄÄÄÄÄĹ^Ĺ^Ĺ^Ĺ^Ĺ^Ĺ^Ĺ^ĹĹĹĹĆ_Ć_Ć_Ć_Ć_Ć_ĆĆÇ`Ç`Ç`Ç`Ç`ČaČaČaČaČűÉbÉbÉüĘcĘcĘýËdËţĚeĚ˙ÍfÎÎgĎĐŃŇÓÔŐÖŁŘĄÚ ÜŢ«á®ä±čěđ"ő'ú“˙˙l qÜáJNQ î#V%X'Z(ö)÷+^,ú-ű.ü/–/ý0ţ11˙333š44›55ś5ś5ś6ť6ť6ť6ť7ž7ž7ž7ž7ž8ź8ź8ź8ź8ź999 9 9 9 9 9 9 :::::::ˇ:ˇ:ˇ:ˇ:ˇ:ˇ:ˇ:ˇ;;;;;;;;;;;;;˘;˘;˘;˘;˘;˘;˘;˘;˘;˘<<<<<<<<<<<<<<<<<<<<<<<<<<<<<Ł<Ł<Ł<Ł<Ł<Ł<Ł<Ł<Ł<Ł<Ł<Ł<Ł=====================================================================¤=¤=¤=¤=¤=¤=¤=¤=¤=¤=¤=¤=¤=¤=¤=¤=¤=¤=¤=¤>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Ą>Ą>Ą>Ą>Ą>Ą>Ą>Ą>Ą>Ą>Ą>Ą>Ą>Ą>Ą>Ą>Ą>Ą>Ą>Ą>Ą>Ą>Ą>Ą>Ą>Ą>Ą>Ą>Ą>Ą>Ą>Ą>Ą>Ą>Ą>Ą>Ą>Ą>Ą>Ą??????????????????????????????????????????????????????????????????????????ŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁŽÁŽÁŽÁŽÁŽÁŽÁŽÁŽÁŽÁŽÁŽÁŽÁŽÁŽÁŽÁŽÁŽÁŽÁŽÁŽÁŽÁŽÁŽÁŽÁŽÁŽÁŽÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂŹÂŹÂŹÂŹÂŹÂŹÂŹÂŹÂŹÂŹÂŹÂŹÂŹÂŹÂŹÂŹÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂđđđđđđđđđÄÄÄÄĹ’Ĺ’Ĺ’Ĺ’Ĺ’Ĺ’Ĺ’ĹĹƓƓƓƓƓƓǔǔǔǔǔȕȕȕÉbÉ–É–ĘcĘ—ËdËdĚeĚeÍfÍfÎgĎhĐiŃ7Ň8ÓlÔmÖ<Ř>Ú@ÜBßEâHćLęPî‡óŔůĆ˙˙ rŞŻł· ş"Ľ%ż'Á)*Ä+Ĺ-”.•/–0—0—12™2™3š4g4›4›5h5ś6i6i6i6ť7j7j7j7ž8k8k8k8k8k9l9l9l9l9l9l:::::m:m:m:m:m:m:m:m;;;;;;;;;n;n;n;n;n;n;n;n;n;n<<<<<<<<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>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??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????ŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆĆĆÇÇÇÇÇÇÇÇČČČČČČÉÉÉÉÉÉĘĘĘĘËËËËĚĚÍÍÎÎÎÎĎĎŃŃŇŇÓÓŐŐ××ŮŮÜÜßßăăççěěňňřř˙˙  ##%%((**++--..//0011222233444455556666667777777788888888999999999999::::::::::::::::;;;;;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<==================================================================================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????ŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂĂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄĹĹĹĹĹĹĹĹĹĹĹĹĆĆĆĆĆĆĆĆÇÇÇÇÇÇČČČČČČÉÉÉÉĘĘËËËËĚĚÍÍÎÎĎĎĐĐŇŇÔÔÖÖŘŘŰŰßßăăééďď÷÷˙˙ ##&&))++--..//112222334455556666667777778888889999999999::::::::::::::;;;;;;;;;;;;;;;;;;;;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<====================================================================>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????żňżňżňżňżňżňżňżňżňżňżňżňżňżňżňżňżňżňżňżňżňżňżňżňżňżňżňżňżňżňżňżňżňŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔóŔóŔóŔóŔóŔóŔóŔóŔóŔóŔóŔóŔóŔóŔóŔóŔóŔóŔóŔóŔóŔóŔóŔóŔóŔóŔóÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁôÁôÁôÁôÁôÁôÁôÁôÁôÁôÁôÁôÁôÁôÁôÁôÁôÂÂÂÂÂÂÂÂÂÂÂÂÂÂÂőÂőÂőÂőÂőÂőÂőÂőÂőÂőÂőĂĂĂĂĂöĂöĂöĂöĂöĂöĂöĂöÄ*Ä÷Ä÷Ä÷Ä÷Ä÷Ĺ+ĹřĹřĹřĆ,Ć,ĆůÇ-Ç-ÇúČ.Č.É/ÉbĘ0Ë1ËdĚeÍfÎgϜўӠբإŰŰŕ­ćłí†ö\˙˙ ŁEL#V'')\,_.a/–0—12Ě3Í4›5ś5Ď6Đ6Đ77Ń7Ń8Ň8Ň8Ň99Ó9Ó:::::Ô;;;;;;;< < < < < < < < < < <<<<<<= = = = = = = = = = = = = = ==========================> > > > > > > > > > > > > > > > > > > > > >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????żżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżŔ&Ŕ&Ŕ&Ŕ&Ŕ&Ŕ&Ŕ&Ŕ&Ŕ&Ŕ&Ŕ&Ŕ&Ŕ&Ŕ&Ŕ&Ŕ&Ŕ&Ŕ&Ŕ&Ŕ&Ŕ&Ŕ&Ŕ&Ŕ&Ŕ&Ŕ&Ŕ&Ŕ&Ŕ&Ŕ&Ŕ&Ŕ&Ŕ&Ŕ&Ŕ&Ŕ&Ŕ&Ŕ&Ŕ&Ŕ&Ŕ&Ŕ&Ŕ&Ŕ&Ŕ&Ŕ&Ŕ&Ŕ&Ŕ&Ŕ&Ŕ&Ŕ&Ŕ&Ŕ&Ŕ&Ŕ&Ŕ&Ŕ&Ŕ&Ŕ&Ŕ&Ŕ&Ŕ&Ŕ&Ŕ&Ŕ&Ŕ&Ŕ&Ŕ&Ŕ&Ŕ&Ŕ&Ŕ&Ŕ&Ŕ&Ŕ&Ŕ&Ŕ&Ŕ&Ŕ&Ŕ&Ŕ&ŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁ'Á'Á'Á'Á'Á'Á'Á'Á'Á'Á'Á'Á'Á'Á'Á'Á'Á'Á'Á'Á'Á'Á'Á'Á'Á'Á'ÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂ(Â(Â(Â(Â(Â(Â(Â(Â(Â(Â(Â(Â(Â(Â(Â(ÂÂÂÂĂ)Ă)Ă)Ă)Ă)Ă)Ă)Ă)Ă)Ă)Ă)ĂÄ*Ä*Ä*Ä*Ä*đđđĹ+Ĺ+Ĺ’Ĺ’Ć,ƓƓƓǔǔȕČűÉ–ÉüĘýËţĚ˙ÎgĐiŃŃÔÔ××ÜBâHééô&˙˙ Ů·##'Á++--/–0ţ2™3š4›566i7j7j8k8k8k9l9l9Ó9Ó:m:m:Ô:Ô:Ô:Ô;n;n;Ő;Ő;Ő;Ő;Ő;Ő;Ő;Ő<Ö<Ö<Ö<Ö<Ö<Ö<Ö<Ö<Ö<Ö<Ö<Ö<Ö===============×=×=×=×=×=×=×=×=×=×=×=×=×=×=×=×=×=×=×=×>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Ř>Ř>Ř>Ř>Ř>Ř>Ř>Ř>Ř>Ř>Ř>Ř>Ř>Ř>Ř>Ř>Ř>Ř>Ř>Ř>Ř>Ř>Ř>Ř>Ř>Ř>Ř>Ř>Ř>Ř>Ř>Ř>Ř>Ř>Ř>Ř>Ř>Ř>Ř>Ř????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????żżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżŔ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Ŕ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ŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁ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ÁÁÁÁÁÁÁÁÁÁÁÁÂ[Â[Â[Â[Â[Â[Â[Â[Â[Â[Â[Â[Â[Â[ÂőÂőĂ\Ă\Ă\Ă\Ă\Ă\ĂöĂöĂöÄ]Ä÷Ä÷Ä÷Ä÷Ĺ’ĹřƓƓǔǔȕÉ/Ę0Ë1ĚĚÍÍĐiÓ× Ü©ä±đ‰˙˙vN#V([,“//1133445h6i7j7j8k8k99l:::;;;;;˘;˘< < < < <Ł<Ł<Ł<Ł<Ł<Ł<Ł<Ł<Ł<Ł=¤=¤=¤=¤=¤=¤=¤=¤=¤=¤=¤=¤=¤=¤=¤=¤=¤=¤=¤=¤>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>Ą>Ą>Ą>Ą>Ą>Ą>Ą>Ą>Ą>Ą>Ą>Ą>Ą>Ą>Ą>Ą>Ą>Ą>Ą>Ą>Ą>Ą>Ą>Ą>Ą>Ą>Ą>Ą>Ą>Ą>Ą>Ą>Ą>Ą>Ą>Ą>Ą>Ą>Ą>Ą>Ą????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????żżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżŔŤŔŤŔŤŔŤŔŤŔŤŔŤŔŤŔŤŔŤŔŤŔŤŔŤŔŤŔŤŔŤŔŤŔŤŔŤŔŤŔŤŔŤŔŤŔŤŔŤŔŤŔŤŔŤŔŤŔŤŔŤŔŤŔŤŔŤŔŤŔŤŔŤŔŤŔŤŔŤŔŤŔŤŔŤŔŤŔŤŔŤŔŤŔŤŔŤŔŤŔŤŔŤŔŤŔŤŔŤŔŤŔŤŔŤŔŤŔŤŔŤŔŤŔŤŔŤŔŤŔŤŔŤŔŤŔŤŔŤŔŤŔŤŔŤŔŤŔŤŔŤŔŤŔŤŔŤŔŤŔŤŔŤŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁŽÁŽÁŽÁŽÁŽÁŽÁŽÁŽÁŽÁŽÁŽÁŽÁŽÁŽÁŽÁŽÁŽÁŽÁŽÁŽÁŽÁŽÂ[Â[Â[Â[Â[ÂŹÂŹÂŹÂŹĂ\Ă\Ă\Ă\Ă\Ă\Ä*Ä]Ä]Ĺ+Ĺ+ĹřĆ,ĆůÇÇČČÉÉËÍfŃŐ˘ÝCęP˙˙Ż"Ľ**.ü13š5h78899Ó9Ó:Ô:Ô:Ô;˘;˘;˘;˘<Ł<Ł<Ł<Ł<Ł<Ł=p=p=p=p=p=p=p=p=p=p=p=p=p=p>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??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????żżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔŔÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÁÂÂÂÂÂÂÂÂÂÂĂĂĂĂĂĂÄÄĹĹĆĆÇÇÉÉĚĚŇŇßß˙˙ --22667799::::;;;;<<<<<<==============>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????żżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżż@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@żżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżľńľńľńľńľńľńľńľńľńľńľńľńľńľńľńľńľńľńľńľńľńľńľńľńľńľńľńľńľńľńľńľńľńľńľńľńľńľńľńľńľńľńľńľńľńľńľńľńľńľńľńľńľńľńľ$ľ$ľ$ľ$ľ$ľ$ľ$ľ$ľ$ľ$˝V˝V˝V˝V˝VĽ‰Ľ‰Ľ‰»»şíş ąR··µN°}¦ Y&NµJ±GzF­EDDDDCvCvB©B©B©AŰAŰAŰAŰAŰAŰAŰAAAAAAAAAAAAAAAAAAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@żżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżż%ż%ż%ż%ż%ż%ż%ż%ż%ż%ż%ż%ż%ż%ż%ż%ż%ż%ż%ż%ż%ż%ż%ż%ż%ż%ż%ż%ż%ż%ż%ż%ż%ż%ż%ż%ż%ż%ż%ż%ż%ż%ż%ż%ż%ż%ż%ż%ż%ż%ż%ż%ż%ż%ż%ż%ż%ż%ż%ż%ż%ż%ż%ż%ż%ż%ż%ż%ż%ż%ż%ż%ż%ż%ż%ż%ż%ż%ż%ż%ľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ$ľ$ľ$ľ$ľ$ľ$ľ$ľ$ľ$ľ$ľ$ľ$ľ$ľ$ľ$ľ$ľ$ľ$ľ$ľ$ľ$˝Š˝Š˝Š˝Š˝Š˝Š˝#˝#˝#Ľ‰Ľ‰Ľ‰Ľ‰Ľ‰Ľ‰»î»»şíşíşSąěąR¸¸··¶¶µł€°I¬¤×—Ęg4Z'SSNµLJ}HâG®F­F­E¬EEDDDCvCvCvCvBuBuBuBuBuBuBuAŰAŰAŰAŰAŰAŰAŰAŰAŰAŰAŰAŰAŰ@Ú@Ú@Ú@Ú@Ú@Ú@Ú@Ú@Ú@Ú@Ú@Ú@Ú@Ú@Ú@Ú@Ú@Ú@Ú@Ú@Ú@Ú@Ú@Ú@Ú@Ú@Ú@Ú@Ú@Ú@Ú@Ú@Ú@Ú@Ú@Ú@Ú@Ú@Ú@Ú@Ú@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@żżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżż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ż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ľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ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˝˝˝˝˝˝˝˝˝˝˝V˝V˝V˝V˝V˝V˝V˝V˝V˝V˝V˝V˝V˝VĽďĽďĽUĽUĽUĽUĽUĽU»î»î»î»T»Tşíşíşíş‡ąěą†ą†¸…¸…·„·¶µłł˛˛°I­ŕ©Ü¤qśi)nŐb•ZŤUQ¸OOMMKKJJI|H{GzGzFyFyFExEEEDDDDCŞCŞCŞCCCB©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¨AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@§@§@§@§@§@§@§@§@§@§@§@§@§@§@§@§@§@§@§@§@§@§@§@§@§@§@§@§@§@§@§@§@§@§@§@§@§@§@§@§@§@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@żżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżŚżŚżŚżŚżŚżŚżŚżŚżŚżŚżŚżŚżŚżŚżŚżŚżŚżŚżŚżŚżŚżŚżŚżŚżŚżŚżŚżŚżŚżŚżŚżŚżŚżŚżŚżŚżŚżŚżŚżŚżŚżŚżŚżŚżŚżŚżŚżŚżŚżŚżŚżŚżŚżŚżŚżŚżŚżŚżŚżŚżŚżŚżŚżŚżŚżŚżŚżŚżŚżŚżŚżŚżŚżŚżŚżŚżŚżŚżŚżŚľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ‹ľ‹ľ‹ľ‹ľ‹ľ‹ľ‹ľ‹ľ‹ľ‹ľ‹ľ‹ľ‹ľ‹ľ‹ľ‹ľ‹ľ‹ľ‹ľ‹ľ‹ľ‹ľ‹ľ‹ľ‹ľ‹ľ‹˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝Š˝Š˝Š˝Š˝Š˝Š˝Š˝Š˝Š˝Š˝Š˝Š˝Š˝Š˝Š˝ŠĽĽĽĽĽ‰Ľ‰Ľ‰Ľ‰Ľ‰Ľ‰Ľ‰Ľ‰Ľ‰Ľ‰Ľ‰ĽU»»»»»»T»T»Tş‡ş‡şSşSşSąRąRąR¸Q¸Q·P·¶O¶µ´ł±äŻâ®®««¨¨¤qžk––‹ňs hh`“[[V‰SSQQOMçLKJIăHâHŻG®G®F­F­F­E¬E¬ExExD«D«DwDwDwDwCŞCŞCvCvCvCvCvCvCvCvBuBuBuBuBuBuBuBuBuBuBuBuBuBuBBBBBBBBBBBBAtAtAtAtAtAtAtAtAtAtAtAtAtAtAtAtAtAtAtAtAtAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@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@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@żżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżżľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşąąąąąąąąąą¸¸¸¸¸¸······¶¶¶¶µµ´´´´łł˛˛±±°°®®¬¬ŞŞ§§¤¤źź™™’’‰‰uullee__[[WWUURRPPOONNMMLLKKJJIIIIHHHHHHGGGGFFFFFFFFEEEEEEEEEEEEDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşąąąąąąąąąąąą¸¸¸¸¸¸¸¸······¶¶¶¶¶¶µµµµ´´łłłł˛˛±±°°ŻŻ®®¬¬ŞŞ¨¨¦¦ŁŁźź››••ŹŹ‡‡wwppiidd__\\YYVVTTRRQQPPNNMMMMLLKKKKJJIIIIIIHHHHHHGGGGGGFFFFFFFFFFEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝đ˝đ˝đ˝đ˝đ˝đ˝đ˝đ˝đ˝đ˝đ˝đ˝đ˝đ˝đ˝đ˝đ˝đ˝đ˝đ˝đ˝đ˝đ˝đ˝đ˝đ˝đ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽďĽďĽďĽďĽďĽďĽďĽďĽďĽďĽďĽďĽďĽďĽďĽďĽďĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»î»î»î»î»î»î»î»î»î»î»î»î»»»»»»»»şíşíşíşíşíşíşíşíşíşşąěąěąěąěąěąěą¸ë¸ë¸ë¸ë¸¸·ę·ę··¶é¶¶µčµµ´´ł˛±J±°®G­F¬EŞC¨A¦?Łp mśie“,Ť&†ąyr kŇfÍb•^Ĺ[ÂYŚV˝T»SşQëP·O¶NčMçLćLćKĺJäJäJIăIIHâHHGáGáGGGFŕFFFFFFEEEEEEEEDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ$ľ$ľ$ľ$ľ$ľ$ľ$ľ$ľ$ľ$ľ$ľ$ľ$ľ$ľ$ľ$ľ$ľ$ľ$ľ$ľ$ľ$ľ$ľ$ľ$ľ$ľ$˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝#˝#˝#˝#˝#˝#˝#˝#˝#˝#˝#˝#˝#˝#˝#˝#ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ"Ľ"Ľ"Ľ"Ľ"Ľ"Ľ"Ľ"Ľ"Ľ"Ľ"»»»»»»»»»»»»»»»!»!»!»!»!»!»!»!»!şşşşş ş ş ş ş ş ş ş ąąąąąą¸¸¸¸¸···¶¶¶µ‚µ´´´ł€˛˛±~°}Ż|®á­ŕ¬y«x©Ü§ÚĄŘŁÖ ÓťĐ™Ě•Č‘^‹ň…ěyys¦n;i6e2a.^+\)Y&W$V#T!S Q„PO‚NNM€LLK~JäJ}J}IăI|HâHâHâH{GáGáGáGzFŕFŕFŕFŕFŕEßEßEßEßEßEßEßEEDŢDŢDŢDŢDŢDŢDŢDŢDDDDDDDDCÝCÝCÝCÝCÝCÝCÝCÝCÝCÝCCCCCCCCCCCCCCCCCCCCCCBÜBÜBÜBÜBÜBÜBÜBÜBÜBÜBÜBÜBÜBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAŰAŰAŰAŰAŰAŰAŰAŰAŰAŰAŰAŰAŰAŰAŰAŰAŰAŰAŰAŰAŰAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@Ú@Ú@Ú@Ú@Ú@Ú@Ú@Ú@Ú@Ú@Ú@Ú@Ú@Ú@Ú@Ú@Ú@Ú@Ú@Ú@Ú@Ú@Ú@Ú@Ú@Ú@Ú@Ú@Ú@Ú@Ú@Ú@Ú@Ú@Ú@Ú@Ú@Ú@Ú@Ú@Ú@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ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˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝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»»»»»»»»»»»»»»»»»»»T»T»T»T»T»T»T»T»TşşşşşşşşşSşSşSşSşSşSşSąąąąąRąRąRąRąRąR¸¸¸Q¸Q¸Q¸Q¸Q·P·P·P·P¶é¶O¶OµčµNµN´ç´MłćłL˛ĺ˛ĺ±ä±J°ăŻâ®á­ŕ¬ß«ŢŞÝ©v§tĄŘŁÖˇnžk›h—ʓƏŠ˝…yŕtŰp=k8gšc–`“^+[ŽYŚX%V#U"S†RQPO¶ONM´MLLK˛KJ±JI°I°I°HŻHŻHŻHŻG®G®G®G®G®F­F­F­F­F­FFFFE¬E¬E¬E¬E¬E¬EEEEEED«D«D«D«D«D«D«D«DDDDDDDDDDDDCŞCŞCŞCŞCŞCŞCŞCŞCŞCŞCŞCCCCCCCCCCCCCCCCCCCCCCCCCCB©B©B©B©B©B©B©B©B©B©B©B©B©B©BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBA¨A¨A¨A¨A¨A¨A¨A¨A¨A¨A¨A¨A¨A¨A¨A¨A¨A¨A¨A¨AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@§@§@§@§@§@§@§@§@§@§@§@§@§@§@§@§@§@§@§@§@§@§@§@§@§@§@§@§@§@§@§@§@§@§@§@§@§@§@§@§@§@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ‹ľ‹ľ‹ľ‹ľ‹ľ‹ľ‹ľ‹ľ‹ľ‹ľ‹ľ‹ľ‹ľ‹ľ‹ľ‹ľ‹ľ‹ľ‹ľ‹ľ‹ľ‹ľ‹ľ‹ľ‹ľ‹ľ‹˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝Š˝Š˝Š˝Š˝Š˝Š˝Š˝Š˝Š˝Š˝Š˝Š˝Š˝Š˝Š˝ŠĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ‰Ľ‰Ľ‰Ľ‰Ľ‰Ľ‰Ľ‰Ľ‰Ľ‰Ľ‰Ľ‰»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşş‡ş‡ş‡ş‡ş‡ş‡ş‡ąąąąąąą†ą†ą†ą†ą†ą†¸¸¸…¸…¸…¸…¸…¸…·„·„·„·„·„¶¶¶¶µ‚µ‚µ‚´´´Mł€łL˛˛K±~±J°}°IŻH®G­F¬E«DŞC©§@Ą>¤ ˘ź8ś5™2–/’+Ž'‰V„„z­u¨q¤lÓiĐeĚbÉ_ů]Ä[ÂYŔWńV˝UĽSşRąQ¸P·PO¶NµNM´M€LłLK˛K~J±J}J}I°I|I|I|H{H{H{H{HHGzGzGzGzGzFyFyFyFyFyFyFFFFExExExExExExExEEEEEEEEDwDwDwDwDwDwDwDwDDDDDDDDDDDDDDDDDDCvCvCvCvCvCvCvCvCvCvCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBuBuBuBuBuBuBuBuBuBuBuBuBuBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAtAtAtAtAtAtAtAtAtAtAtAtAtAtAtAtAtAtAtAtAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA@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ľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸············¶¶¶¶¶¶¶¶¶¶µµµµµµµµ´´´´´´łłłł˛˛˛˛±±±±°°°°ŻŻŻŻ®®­­¬¬««ŞŞ©©¨¨¦¦ĄĄŁŁˇˇźźťťšš——””ŚŚ„„{{vvrrnnkkggddbb__]][[ZZXXWWUUTTSSRRQQQQPPOOOONNNNMMMMLLLLKKKKKKJJJJJJIIIIIIIIHHHHHHHHHHHHGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸············¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµ´´´´´´łłłłłł˛˛˛˛˛˛±±±±°°°°ŻŻ®®®®­­¬¬««ŞŞ©©¨¨§§¦¦¤¤ŁŁˇˇźźťť››••’’ŹŹ‹‹‡‡{{wwsspplliiffddbb__]]\\ZZYYWWVVUUTTSSRRRRQQPPPPOONNNNMMMMMMLLLLKKKKKKKKJJJJJJIIIIIIIIIIHHHHHHHHHHHHGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ˝đ˝đ˝đ˝đ˝đ˝đ˝đ˝đ˝đ˝đ˝đ˝đ˝đ˝đ˝đ˝đ˝đ˝đ˝đ˝đ˝đ˝đ˝đ˝đ˝đ˝đ˝đ˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽďĽďĽďĽďĽďĽďĽďĽďĽďĽďĽďĽďĽďĽďĽďĽďĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»î»î»î»î»î»î»î»î»î»î»î»î»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şíşíşíşíşíşíşíşíşíşşşşşşşşşşşşşşşşşşąěąěąěąěąěąěąěąąąąąąąąąą¸ë¸ë¸ë¸ë¸ë¸ë¸ë¸¸¸¸¸¸·ę·ę·ę·ę·ę····¶é¶é¶é¶é¶¶µčµčµčµčµč´ç´ç´ç´çłćłćłć˛ĺ˛ĺ˛ĺ±ä±ä±°ă°ŻâŻ®á­ŕ­¬«ŢŞÝ©Ü¨Ű§Ú¦ Ą ŁÖ˘ žśš–ý”ǑčôŠ˝‡‡|IxEtAq>nkhe˙c0`ú^ř]÷[őZôXňWńVđUďTîSíS RQëQPOéONčNNMMLćLLLKKKJJJJIIIIIIHHHHHHHHHGGGGGGGGGGFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľľ$ľ$ľ$ľ$ľ$ľ$ľ$ľ$ľ$ľ$ľ$ľ$ľ$ľ$ľ$ľ$ľ$ľ$ľ$ľ$ľ$ľ$ľ$ľ$ľ$ľ$ľ$˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝#˝#˝#˝#˝#˝#˝#˝#˝#˝#˝#˝#˝#˝#˝#˝#˝#ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ"Ľ"Ľ"Ľ"Ľ"Ľ"Ľ"Ľ"Ľ"Ľ"Ľ"»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»!»!»!»!»!»!»!»!»!şşşşşşşşşşşşşşşşşşşşşşş ş ş ş ş ş ş ąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸·········¶¶¶¶¶¶µµµµµµ´´´´łłł˛˛±~±±°°Ż®{®­z¬y¬«Şw©v¨u§t¦sĄ Łp˘o mžkśišge•ü“`]Śó‰đ†í||yur oliśgd—b•`“^ř][őZŤYŚWńVđUďTîT‡S†RěQëQ„PęOéOéNčNčMçMçMçLćLćLćKĺKĺKĺJäJäJäJäIăIăIăIăIIHâHâHâHâHâHHHHGáGáGáGáGáGGGGGGFŕFŕFŕFŕFŕFFFFFFFFFFEßEßEßEßEßEßEßEEEEEEEEEEEEEEEEDŢDŢDŢDŢDŢDŢDŢDŢDŢDDDDDDDDDDDDDDDDDDDDDDDDDDDDCÝCÝCÝCÝCÝCÝCÝCÝCÝCÝCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBÜBÜBÜBÜBÜBÜBÜBÜBÜBÜBÜBÜBÜBÜBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAŰAŰAŰAŰAŰAŰAŰAŰAŰAŰAŰAŰAŰAŰAŰAŰAŰAŰAŰAŰAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAľľľľľľľľľľľľľľľľľľľľľ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˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝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»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»T»T»T»T»T»T»T»T»TşşşşşşşşşşşşşşşşşşşşşşşşşSşSşSşSşSşSşSşSąąąąąąąąąąąąąąąRąRąRąRąRąR¸¸¸¸¸¸¸¸¸¸¸Q¸Q¸Q¸Q¸Q·······P·P·P·P·P¶¶¶¶¶O¶O¶O¶OµµµNµNµNµN´´´M´M´MłłłLłLłL˛K˛K˛K±J±J°I°IŻâŻH®á®G­F­F¬E«DŞÝŞC©B¨A§@¦?¤×ŁÖ˘; Óź8ť6›4™2—0”Ç’+Ź(Ś%‰"†||yyuÜrŮoÖm:jŃh5f3d1b/`-^Ĺ]*[ÂZÁY&X%W$V#UĽT»SşS RąQ¸QP·PO¶ONµNµM´M´M´LłLłLłK˛K˛K˛KKJ±J±J±JJI°I°I°I°IIIIHŻHŻHŻHŻHHHHHHG®G®G®G®G®GGGGGGF­F­F­F­F­F­FFFFFFFFFFFFE¬E¬E¬E¬E¬E¬E¬EEEEEEEEEEEEEEEEEEEED«D«D«D«D«D«D«D«DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCŞCŞCŞCŞCŞCŞCŞCŞCŞCŞCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCB©B©B©B©B©B©B©B©B©B©B©B©B©BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBA¨A¨A¨A¨A¨A¨A¨A¨A¨A¨A¨A¨A¨A¨A¨A¨A¨A¨A¨A¨A¨AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAľ‹ľ‹ľ‹ľ‹ľ‹ľ‹ľ‹ľ‹ľ‹ľ‹˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝Š˝Š˝Š˝Š˝Š˝Š˝Š˝Š˝Š˝Š˝Š˝Š˝Š˝Š˝Š˝ŠĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ‰Ľ‰Ľ‰Ľ‰Ľ‰Ľ‰Ľ‰Ľ‰Ľ‰Ľ‰Ľ‰»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşş‡ş‡ş‡ş‡ş‡ş‡ş‡ąąąąąąąąąąąąąąąąąąą†ą†ą†ą†ą†ą†¸¸¸¸¸¸¸¸¸¸¸¸¸…¸…¸…¸…¸…·········„·„·„·„¶¶¶¶¶¶¶¶¶¶µµµµµ‚µ‚µ‚´´´´´´łłł€ł€ł€˛˛˛±~±~±~°}°}Ż|Ż|®{®{­z­z¬y¬E«xŞw©v©B¨A§@¦?Ą>Łp˘oˇ:źlž7ś5š31–/“`‘^Ž[‹‹……‚‚||yyv©s¦pŁnˇkžiśgšec–a”_Ć^‘\Ă[ÂZŤYŚX‹WŠV‰UĽT»T‡S†RąR…Q„Q„PPO‚O‚NNNM€M€MMLLLK~K~K~KKJ}J}J}J}JJI|I|I|I|IIIIH{H{H{H{H{HHHHHHGzGzGzGzGzGGGGGGGGFyFyFyFyFyFyFFFFFFFFFFFFFFExExExExExExExEEEEEEEEEEEEEEEEEEEEEEDwDwDwDwDwDwDwDwDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCvCvCvCvCvCvCvCvCvCvCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBuBuBuBuBuBuBuBuBuBuBuBuBuBuBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAtAtAtAtAtAtAtAtAtAtAtAtAtAtAtAtAtAtAtAtAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸··················¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµ´´´´´´´´łłłłłłłłłł˛˛˛˛˛˛±±±±±±°°°°°°ŻŻŻŻ®®®®­­­­¬¬¬¬««ŞŞŞŞ©©¨¨§§¦¦ĄĄ¤¤ŁŁ˘˘ˇˇźźžžśśšš––””’’ŤŤ‹‹……‚‚||yywwttqqoolljjhhffddbbaa__^^]][[ZZYYXXWWWWVVUUTTTTSSRRRRQQQQPPPPOOOOOONNNNMMMMMMMMLLLLLLKKKKKKKKKKJJJJJJJJJJIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸····················¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµ´´´´´´´´´´łłłłłłłł˛˛˛˛˛˛˛˛±±±±±±°°°°°°ŻŻŻŻŻŻ®®®®­­­­¬¬¬¬««ŞŞŞŞ©©¨¨¨¨§§¦¦ĄĄ¤¤ŁŁ˘˘  źźžžśś››™™——••““‘‘ŹŹŚŚŠŠ‡‡……‚‚}}zzwwuurrppnnkkiiggffddbbaa__^^]]\\[[ZZYYXXWWVVUUUUTTSSSSRRRRQQQQPPPPPPOOOONNNNNNMMMMMMMMLLLLLLLLKKKKKKKKKKJJJJJJJJJJIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽďĽďĽďĽďĽďĽďĽďĽďĽďĽďĽďĽďĽďĽďĽďĽďĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»î»î»î»î»î»î»î»î»î»î»î»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şíşíşíşíşíşíşíşíşíşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąěąěąěąěąěąěąěąěąąąąąąąąąąąąąąąąąąąąąą¸ë¸ë¸ë¸ë¸ë¸ë¸¸¸¸¸¸¸¸¸¸¸¸¸¸·ę·ę·ę·ę·ę··········¶é¶é¶é¶é¶é¶¶¶¶¶¶µčµčµčµčµčµµµµ´ç´ç´ç´ç´´´´łćłćłćłł˛ĺ˛ĺ˛ĺ˛˛±ä±ä±ä±±°ă°ă°°ŻâŻâ®á®á®á­ŕ­ŕ¬ß¬ß«Ţ«ŢŞÝ©Ü©Ü¨Ű§Ú§ ¦ ĄŘ¤×ŁÖ˘ŐˇźŇžŃť›ÎšË–ɔǒĹĂŽÁŚŚ‰Ľ‡‡„·‚‚}}zzxEuus@q>o¤=Ł<˘;ˇ: 9ź8ž7śĎ›4š31—0•.“,‘*Ź(Ť&‹$‰"††„„‚‚}}{{xßvvttqŘoÖmÔkŇjŃhĎfÍeĚcĘbÉaČ`-^Ĺ]Ä\Ă[ÂZÁYŔYŔXżWľV˝V˝UĽT»T»SşSşRąRąQ¸Q¸QQP·P·O¶O¶O¶NµNµNµNNM´M´M´MMLłLłLłLLK˛K˛K˛K˛KKKKJ±J±J±JJJJJJI°I°I°I°IIIIIIIIHŻHŻHŻHŻHHHHHHHHHHHHG®G®G®G®G®GGGGGGGGGGGGGGF­F­F­F­F­F­FFFFFFFFFFFFFFFFFFFFFFE¬E¬E¬E¬E¬E¬EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEED«D«D«D«D«D«D«D«DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCŞCŞCŞCŞCŞCŞCŞCŞCŞCŞCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCB©B©B©B©B©B©B©B©B©B©B©B©B©B©BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBA¨A¨A¨A¨A¨A¨A¨A¨A¨A¨A¨A¨A¨A¨˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝Š˝Š˝Š˝Š˝Š˝Š˝Š˝Š˝Š˝Š˝Š˝Š˝Š˝Š˝Š˝ŠĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ‰Ľ‰Ľ‰Ľ‰Ľ‰Ľ‰Ľ‰Ľ‰Ľ‰Ľ‰Ľ‰Ľ‰»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşş‡ş‡ş‡ş‡ş‡ş‡ş‡ş‡ąąąąąąąąąąąąąąąąąąąąąąąąąąąąą†ą†ą†ą†ą†ą†¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸…¸…¸…¸…¸…···············„·„·„·„·„¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµ‚µ‚µ‚µ‚´´´´´´´´łłłłł€ł€ł€˛˛˛˛˛˛±±±±±~±~°°°}°}°}Ż|Ż|Ż|®{®{­­­z­z¬y¬y«x«xŞwŞw©v¨u¨u§t¦s¦sĄr¤qŁp˘oˇn mźlžkťj›hšg™f—d–c”a’_]ŽŽŚŚŠŠ††„„‚O}}{{yyv©t§rĄpŁnˇlźkžiśgšf™d—c–b•a”_’^‘]\Ź[ŽZÁZŤYŚX‹WŠWŠV‰UUT‡T‡S†S†R…R…Q„Q„Q„PPPPO‚O‚OONNNNNM€M€MMMMLLLLLLLK~K~K~KKKKJ}J}J}J}JJJJJJI|I|I|I|IIIIIIIIH{H{H{H{H{HHHHHHHHHHHHGzGzGzGzGzGGGGGGGGGGGGGGGGFyFyFyFyFyFyFFFFFFFFFFFFFFFFFFFFFFExExExExExExExEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDwDwDwDwDwDwDwDwDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCvCvCvCvCvCvCvCvCvCvCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBuBuBuBuBuBuBuBuBuBuBuBuBuBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸························¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµ´´´´´´´´´´´´łłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛±±±±±±±±°°°°°°ŻŻŻŻŻŻ®®®®®®­­­­­­¬¬¬¬««««ŞŞŞŞ©©©©¨¨§§§§¦¦ĄĄĄĄ¤¤ŁŁ˘˘ˇˇ  źźžžťťśśšš™™––••““‘‘ŹŹŽŽŚŚŠŠ††„„}}{{yywwuussqqoommlljjhhggeeddccbb``__^^]]\\\\[[ZZYYXXXXWWVVVVUUUUTTTTSSSSRRRRRRQQQQPPPPPPOOOOOOOONNNNNNMMMMMMMMMMLLLLLLLLLLKKKKKKKKKKKKJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸························¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´łłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛±±±±±±±±°°°°°°°°ŻŻŻŻŻŻ®®®®®®­­­­­­¬¬¬¬««««ŞŞŞŞ©©©©¨¨¨¨§§§§¦¦ĄĄ¤¤¤¤ŁŁ˘˘ˇˇ  źźžžťťśś››™™——••””’’ŹŹŤŤ‹‹‰‰‡‡……}}{{yywwuussrrppnnllkkiihhffeeddccbb``__^^]]]]\\[[ZZYYYYXXWWWWVVVVUUUUTTTTSSSSRRRRRRQQQQPPPPPPPPOOOOOONNNNNNNNMMMMMMMMMMLLLLLLLLLLKKKKKKKKKKKKKKJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝˝ĽďĽďĽďĽďĽďĽďĽďĽďĽďĽďĽďĽďĽďĽďĽďĽďĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»î»î»î»î»î»î»î»î»î»î»î»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şíşíşíşíşíşíşíşíşíşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąěąěąěąěąěąěąěąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸ë¸ë¸ë¸ë¸ë¸ë¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸·ę·ę·ę·ę·ę·ę················¶é¶é¶é¶é¶¶¶¶¶¶¶¶¶¶¶¶µčµčµčµčµčµµµµµµµµ´ç´ç´ç´ç´´´´´´łćłćłćłćłłłłłł˛ĺ˛ĺ˛ĺ˛˛˛˛±ä±ä±ä±±°ă°ă°ă°°ŻâŻâŻâŻŻ®á®á®®­ŕ­ŕ­­¬ß¬ß«Ţ«Ţ««ŞÝŞŞ©Ü©©¨Ű§Ú§Ú¦Ů¦ŮĄŘ¤×ŁÖŁÖ˘ŐˇÔ ÓźŇžŃťĐśĎ›ÎšÍ˗ʖɔǓƑĎÁŚż‹‹‰‰‡‡……}}{{yyxEvCtArrppoĄ>¤=Ł<Ł<˘;ˇ: 9ź8ž7ť6ś5›4š3™21–/•.”-’+‘*Ź(ŤŤŚ%ŠŠ‡ ……}}||zzxxvÝtŰssqŘoÖnŐlÓkŇjjhĎgÎfÍeecĘbÉaČ`Ç_Ć^Ĺ^Ĺ]Ä\Ă[Â[[ZÁYŔYYXżWľWľV˝V˝UĽUĽT»T»SşSşSSRąRąRRQ¸Q¸QQP·P·PPO¶O¶O¶OONµNµNµNNNNM´M´M´MMMMLłLłLłLLLLLLK˛K˛K˛KKKKKKKKJ±J±J±JJJJJJJJJJI°I°I°I°IIIIIIIIIIIIHŻHŻHŻHŻHŻHHHHHHHHHHHHHHHHG®G®G®G®G®GGGGGGGGGGGGGGGGGGGGGGF­F­F­F­F­F­FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE¬E¬E¬E¬E¬E¬E¬EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEED«D«D«D«D«D«D«D«DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCŞCŞCŞCŞCŞCŞCŞCŞCŞCŞCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCB©B©B©B©B©B©B©B©B©B©B©B©B©B©BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ‰Ľ‰Ľ‰Ľ‰Ľ‰Ľ‰Ľ‰Ľ‰Ľ‰Ľ‰Ľ‰Ľ‰»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşş‡ş‡ş‡ş‡ş‡ş‡ş‡ąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą†ą†ą†ą†ą†ą†¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸…¸…¸…¸…¸…¸…·····················„·„·„·„¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµ‚µ‚µ‚µ‚´´´´´´´´´´´´łłłłłłł€ł€ł€˛˛˛˛˛˛˛˛˛±±±±±~±~±~°°°°°}°}ŻŻŻŻŻ|Ż|®®®{®{®{­­­z­z¬y¬y«««x«xŞŞŞw©©©v¨¨¨u§§§t¦s¦sĄr¤¤¤qŁp˘˘˘oˇn mźlžkťjśi›hšg™fe—d••”a“`‘‘]ŽŽŤZ‹‹ŠW††…R}°||zzx«wwuus¦rrpŁoom lljťiśh›gged—c–b•a”`“_’__^‘]\Ź[Ž[ŽZŤYŚYŚX‹XXWŠWWV‰VVUUUT‡T‡S†S†SSR…R…RRQ„Q„QQPPPPPPO‚O‚OOOONNNNNNNNM€M€MMMMMMLLLLLLLLLK~K~K~KKKKKKKKJ}J}J}J}JJJJJJJJJJI|I|I|I|IIIIIIIIIIIIIIH{H{H{H{HHHHHHHHHHHHHHHHHHGzGzGzGzGzGGGGGGGGGGGGGGGGGGGGGGGGFyFyFyFyFyFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFExExExExExExExEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDwDwDwDwDwDwDwDwDwDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCvCvCvCvCvCvCvCvCvCvCvCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBuBuBuBuBuBuBuBuBuBuBuBuBuBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸································¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´łłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±°°°°°°°°°°ŻŻŻŻŻŻŻŻ®®®®®®­­­­­­­­¬¬¬¬««««««ŞŞŞŞŞŞ©©©©¨¨¨¨§§§§¦¦ĄĄĄĄ¤¤¤¤ŁŁ˘˘˘˘ˇˇ  źźžžťťśś››šš™™——––••““’’‘‘ŹŹŽŽŚŚ‹‹‰‰††„„~~||zzyywwuuttrrqqoonnllkkjjiiggffeeddccbbaa``____^^]]\\\\[[ZZZZYYXXXXWWWWVVVVUUUUUUTTTTSSSSSSRRRRRRQQQQQQPPPPPPPPOOOOOOOONNNNNNNNNNMMMMMMMMMMMMLLLLLLLLLLLLKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸································¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±°°°°°°°°ŻŻŻŻŻŻŻŻ®®®®®®®®­­­­­­­­¬¬¬¬¬¬««««ŞŞŞŞŞŞ©©©©¨¨¨¨§§§§¦¦¦¦ĄĄĄĄ¤¤ŁŁŁŁ˘˘ˇˇˇˇ  źźžžťťťťśś››šš™™——––••””““‘‘ŹŹŤŤŚŚŠŠ‰‰‡‡††„„‚‚~~||zzyywwvvttssqqppnnmmllkkiihhggffeeddccbbaa``____^^]]\\\\[[ZZZZYYYYXXXXWWWWVVVVUUUUTTTTTTSSSSSSRRRRRRQQQQQQQQPPPPPPPPOOOOOOOONNNNNNNNNNMMMMMMMMMMMMLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»î»î»î»î»î»î»î»î»î»î»î»î»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şíşíşíşíşíşíşíşíşíşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąěąěąěąěąěąěąěąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸ë¸ë¸ë¸ë¸ë¸ë¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸·ę·ę·ę·ę·ę······················¶é¶é¶é¶é¶é¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µčµčµčµčµµµµµµµµµµµµ´ç´ç´ç´ç´´´´´´´´´´łćłćłćłćłłłłłłłł˛ĺ˛ĺ˛ĺ˛˛˛˛˛˛±ä±ä±ä±±±±±±°ă°ă°ă°°°°ŻâŻâŻŻŻŻ®á®á®®®®­ŕ­ŕ­­¬ß¬ß¬ß¬¬«Ţ«ŢŞÝŞÝŞŞ©Ü©Ü©©¨Ű¨¨§Ú§§¦Ů¦¦ĄŘĄĄ¤×ŁÖŁŁ˘ŐˇÔˇˇ ÓźŇžŃžžťťśĎ›ÎšÍ™ĚË——––”Ǔƒő‘ŹÂŽÁŤŤ‹ľŠŠ»‡‡…¸„„‚‚~~||{HyyxEvvuBssr?ppo¤¤¤=Ł<Ł<˘;ˇˇˇ: 9ź8žžž7ť6ś5›4š3™21—0–/•.”-“,‘‘Ź(Ž'ŚŚ‹‹Š#‡ ……„„‚‚~~||{{yyxxvÝuuttrŮqqppnŐmÔllkkjjhĎgÎfÍeĚdËddccbbaČ`Ç_Ć__^Ĺ]Ä]]\Ă[Â[[ZÁZZYŔYYXżXXWľWWV˝V˝UĽUĽUUT»T»TTSşSşSSRąRąRRQ¸Q¸QQQQP·P·PPPPO¶O¶O¶OOOONµNµNµNNNNNNM´M´MMMMMMMMLłLłLłLLLLLLLLK˛K˛K˛KKKKKKKKKKKKJ±J±J±JJJJJJJJJJJJJJI°I°I°I°IIIIIIIIIIIIIIIIHŻHŻHŻHŻHŻHHHHHHHHHHHHHHHHHHHHHHG®G®G®G®G®GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGF­F­F­F­F­FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE¬E¬E¬E¬E¬E¬E¬EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEED«D«D«D«D«D«D«D«DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCŞCŞCŞCŞCŞCŞCŞCŞCŞCŞCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ‰Ľ‰Ľ‰Ľ‰Ľ‰Ľ‰Ľ‰Ľ‰Ľ‰Ľ‰Ľ‰»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşş‡ş‡ş‡ş‡ş‡ş‡ş‡ąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą†ą†ą†ą†ą†ą†¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸…¸…¸…¸…¸…¸…···························„·„·„·„·„¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµ‚µ‚µ‚µ‚´´´´´´´´´´´´´´´łłłłłłłłłłł€ł€ł€ł€˛˛˛˛˛˛˛˛˛˛±±±±±±±±±~±~±~°°°°°}°}°}ŻŻŻŻŻ|Ż|®®®®®{®{®{­­­z­z¬¬¬¬¬y¬y«««x«xŞŞŞwŞw©©©v¨¨¨u¨u§t§t¦¦¦sĄĄĄr¤q¤qŁp˘˘˘oˇnˇn mźlžžžkťjśi›hšš™™——––••””““’’‘^]ŽŽŤŤŚY‹X‰‰††……„Q‚‚~~||{{y¬xxwwu¨ttssq¤pŁoonnlźkžjťiśh›gšf™ed—c–b•bba”`“_’__^‘]]]\Ź[Ž[ŽZŤZZYŚYYX‹XXWŠWŠWWV‰VVUUUUT‡TTTTS†S†SSR…R…RRQ„Q„Q„QQPPPPPPPO‚O‚OOOOOONNNNNNNNM€M€M€MMMMMMMMLLLLLLLLLLLK~K~K~K~KKKKKKKKKKJ}J}J}J}JJJJJJJJJJJJJJI|I|I|I|IIIIIIIIIIIIIIIIIIH{H{H{H{HHHHHHHHHHHHHHHHHHHHHHHHGzGzGzGzGzGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFyFyFyFyFyFyFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFExExExExExExEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDwDwDwDwDwDwDwDwDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCvCvCvCvCvCvCvCvCvCvCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸······································¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻ®®®®®®®®­­­­­­­­¬¬¬¬¬¬¬¬««««««ŞŞŞŞŞŞ©©©©©©¨¨¨¨§§§§¦¦¦¦¦¦ĄĄĄĄ¤¤ŁŁŁŁ˘˘˘˘ˇˇ    źźžžžžťťśś››šššš™™——––••””““’’ŹŹŽŽŤŤ‹‹ŠŠ‰‰††……‚‚~~||{{zzxxwwvvttssrrqqoonnmmllkkjjiihhggffeeddccbbbbaa``____^^]]]]\\\\[[ZZZZYYYYXXXXXXWWWWVVVVVVUUUUTTTTTTTTSSSSSSRRRRRRRRQQQQQQQQPPPPPPPPOOOOOOOOOONNNNNNNNNNNNMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽĽ»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸······································¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻ®®®®®®®®­­­­­­­­¬¬¬¬¬¬¬¬««««««ŞŞŞŞŞŞ©©©©©©¨¨¨¨¨¨§§§§¦¦¦¦ĄĄĄĄ¤¤¤¤ŁŁŁŁ˘˘˘˘ˇˇ    źźžžžžťťśś››››šš™™——––••””““’’‘‘ŹŹŽŽŚŚ‹‹ŠŠ‰‰‡‡††……‚‚~~}}{{zzyywwvvuussrrqqppoonnllkkjjiihhggffffeeddccbbbbaa``____^^]]]]\\\\[[[[ZZZZYYYYXXXXWWWWWWVVVVUUUUUUTTTTTTSSSSSSSSRRRRRRRRQQQQQQQQPPPPPPPPPPOOOOOOOOOONNNNNNNNNNNNMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCĽĽĽĽ»î»î»î»î»î»î»î»î»î»î»î»î»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şíşíşíşíşíşíşíşíşíşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąěąěąěąěąěąěąěąěąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸ë¸ë¸ë¸ë¸ë¸ë¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸·ę·ę·ę·ę·ę······························¶é¶é¶é¶é¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µčµčµčµčµµµµµµµµµµµµµµµµ´ç´ç´ç´ç´´´´´´´´´´´´´´łćłćłćłćłłłłłłłłłł˛ĺ˛ĺ˛ĺ˛˛˛˛˛˛˛˛˛˛±ä±ä±ä±±±±±±°ă°ă°ă°°°°°°ŻâŻâŻâŻŻŻŻŻŻ®á®á®®®®­ŕ­ŕ­­­­¬ß¬ß¬¬¬¬«Ţ«Ţ««ŞÝŞÝŞŞŞŞ©Ü©©¨Ű¨Ű¨¨§Ú§Ú§§¦Ů¦¦ĄŘĄĄ¤×¤¤ŁÖŁŁ˘Ő˘˘ˇÔ Ó  źŇžŃžžťĐśĎśś››šÍ™ĚË—Ę——––••””““’’ĂŹÂŽÁŤŔŚŚ‹‹ŠŠ»‡‡††„·‚‚~~}}{{zzyywwvvuutAs@qqppoonnm:l9k8j7i6h5g4ffeee2d1c0bbaaa.`-___,^+^+]*\\\)[[[(ZZZ'YYY&XXX%X%WWW$VVV#V#UUU"U"TTT!T!SSSSS S RRRRRRQQQQQQPPPPPPPPOOOOOOOOONNNNNNNNNMMMMMMMMMMMLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCĽ"Ľ"»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»!»!»!»!»!»!»!»!»!şşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşş ş ş ş ş ş ş ąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸···································¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±°°°°°°°°°°ŻŻŻŻŻŻŻŻŻ®®®®®®­­­­­­¬¬¬¬¬¬«««««ŞŞŞŞŞŞ©©©¨¨¨¨¨§§§ § ¦ ¦ ĄĄĄ ¤¤¤ ŁŁŁ ˘˘˘ˇ   źžžžťśśś›š™™——–ý•ü”ű“ú’ů‘řŹŹŽŽŤŤŚŚŠń‰đ‡‡††„„‚‚~~}}{{zzyyxwuuttssr q p ommllkkjjjihge˙eeddcýbüaűaa`ú_ů__^ř^^]÷\ö\\[ő[[ZôZZYóYYXňXňXXWńWWVđVđVVUďUďUUTîTîTTSíSíSSSSRěRěRRRRQëQëQQQQPęPęPPPPPPOéOéOOOOOOOONčNčNNNNNNNNMçMçMçMMMMMMMMLćLćLćLLLLLLLLLLKĺKĺKĺKĺKKKKKKKKKKKKJäJäJäJäJJJJJJJJJJJJJJJJIăIăIăIăIIIIIIIIIIIIIIIIIIIIHâHâHâHâHâHHHHHHHHHHHHHHHHHHHHHHHHHHGáGáGáGáGáGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFŕFŕFŕFŕFŕFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEßEßEßEßEßEßEßEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDŢDŢDŢDŢDŢDŢDŢDŢDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCÝCÝCÝCÝCÝCÝCÝCÝCÝCÝCÝCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»T»T»T»T»T»T»T»T»TşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşSşSşSşSşSşSşSąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąRąRąRąRąRąRąR¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸Q¸Q¸Q¸Q¸Q·································P·P·P·P·P¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶O¶O¶O¶OµµµµµµµµµµµµµµµµµµµµµNµNµNµN´´´´´´´´´´´´´´´M´M´M´MłłłłłłłłłłłłłLłLłL˛˛˛˛˛˛˛˛˛˛˛K˛K˛K±±±±±±±±±J±J±J°°°°°°°I°I°IŻŻŻŻŻŻŻHŻH®®®®®®®G®G­­­­­F­F¬¬¬¬¬E¬E«««««D«DŞŞŞCŞC©©©B©B¨¨¨A¨A§§§@¦¦¦?ĄĄĄ>Ą>¤¤¤=ŁŁŁ<˘;˘;ˇ:   9źźžžž7ťťśśś5›4šš™™™21—0–/•.””““’’‘‘Ź(Ž'Ť&Ś%ŠŠ‰‰‡‡†„„‚‚~~}}{âzzyyxxwwuÜttssrrqqppoomÔlÓkŇjŃjjiihhggfÍeĚdËddccbÉaČaa`Ç_Ć__^Ĺ^^]Ä\Ă\Ă[Â[ÂZÁZÁYŔYŔYYXżXXWľWľWWV˝VVVVUĽUUUUT»TTTTSşSşSSSSRąRąRRRRQ¸Q¸QQQQP·P·P·PPPPO¶O¶O¶OOOOOONµNµNµNNNNNNNNM´M´MMMMMMMMMMLłLłLłLLLLLLLLLLLLK˛K˛K˛KKKKKKKKKKKKKKJ±J±J±JJJJJJJJJJJJJJJJJJI°I°I°I°IIIIIIIIIIIIIIIIIIIIIIHŻHŻHŻHŻHHHHHHHHHHHHHHHHHHHHHHHHHHHHG®G®G®G®G®GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGF­F­F­F­F­F­FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE¬E¬E¬E¬E¬E¬EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEED«D«D«D«D«D«D«D«DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCŞCŞCŞCŞCŞCŞCŞCŞCŞCŞCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşş‡ş‡ş‡ş‡ş‡ş‡ş‡ş‡ąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą†ą†ą†ą†ą†ą†¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸…¸…¸…¸…¸…···································„·„·„·„¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµ‚µ‚µ‚µ‚´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłł€ł€ł€˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±~±~±~°°°°°°°°°}°}ŻŻŻŻŻŻŻŻŻ|Ż|®®®®®®®{®{­­­­­z­z¬¬¬¬¬y¬y«««««x«xŞŞŞwŞw©©©©©v¨¨¨¨¨u§§§t§t¦¦¦sĄĄĄr¤¤¤q¤qŁpŁp˘oˇˇˇn   mźźźlžkťťťjśi›hšššg™fe——––••”””a“`’_‘^]ŽŽŤŤŚŚ‹‹ŠŠ‰‰U‡T……„„‚‚€€~~}}||z­yyxxwwvvt§s¦rĄqqppoonnmmllkkjťiśh›gšggffed—c–ccb•a”aa`“_’__^‘^^]]]\Ź\\[Ž[[ZŤZZYŚYYX‹X‹XXWŠWWV‰V‰VVUUUUT‡T‡TTTTS†S†SSSSR…R…RRRRQ„Q„QQQQQQPPPPPPPPO‚O‚OOOOOOOONNNNNNNNNNM€M€M€MMMMMMMMMMLLLLLLLLLLLLLLLK~K~K~KKKKKKKKKKKKKKJ}J}J}J}JJJJJJJJJJJJJJJJJJI|I|I|I|IIIIIIIIIIIIIIIIIIIIIIH{H{H{H{HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGzGzGzGzGzGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFyFyFyFyFyFyFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFExExExExExExExEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDwDwDwDwDwDwDwDwDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCvCvCvCvCvCvCvCvCvCvCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸············································¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®­­­­­­­­¬¬¬¬¬¬¬¬««««««««ŞŞŞŞŞŞ©©©©©©©©¨¨¨¨¨¨§§§§¦¦¦¦¦¦ĄĄĄĄ¤¤¤¤ŁŁŁŁ˘˘˘˘ˇˇˇˇ    źźźźžžťťťťśś››šššš™™————––••””““’’‘‘ŹŹŽŽŤŤŚŚ‹‹ŠŠ‰‰‡‡††……„„‚‚€€~~}}||{{yyxxwwvvuuttssqqppoonnmmllkkkkjjiihhggffffeeddccccbbaaaa``____^^^^]]]]\\\\[[[[ZZZZYYYYYYXXXXWWWWWWVVVVVVUUUUUUTTTTTTTTSSSSSSSSRRRRRRRRQQQQQQQQQQPPPPPPPPPPOOOOOOOOOOOONNNNNNNNNNNNNNMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸··············································¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®­­­­­­­­­­¬¬¬¬¬¬¬¬««««««««ŞŞŞŞŞŞŞŞ©©©©©©¨¨¨¨¨¨§§§§§§¦¦¦¦ĄĄĄĄĄĄ¤¤¤¤ŁŁŁŁ˘˘˘˘ˇˇˇˇ    źźźźžžťťťťśś››››šš™™——––••””““““’’‘‘ŹŹŽŽŤŤŚŚ‹‹‰‰‡‡††……„„‚‚€€~~}}||{{yyxxwwvvuuttssrrqqppoonnmmllkkjjiihhhhggffeeeeddccbbbbaaaa``____^^^^]]]]\\\\[[[[ZZZZYYYYYYXXXXXXWWWWWWVVVVVVUUUUUUTTTTTTTTSSSSSSSSRRRRRRRRQQQQQQQQQQPPPPPPPPPPPPOOOOOOOOOOOONNNNNNNNNNNNNNMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDCCCCCCCCCC»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şíşíşíşíşíşíşíşíşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąěąěąěąěąěąěąěąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸ë¸ë¸ë¸ë¸ë¸ë¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸·ę·ę·ę·ę·ę····································¶é¶é¶é¶é¶é¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µčµčµčµčµµµµµµµµµµµµµµµµµµµµµµ´ç´ç´ç´´´´´´´´´´´´´´´´´´łćłćłćłłłłłłłłłłłłłł˛ĺ˛ĺ˛ĺ˛ĺ˛˛˛˛˛˛˛˛˛˛±ä±ä±ä±±±±±±±±±±°ă°ă°ă°°°°°°°°ŻâŻâŻŻŻŻŻŻŻŻ®á®á®®®®®®­ŕ­ŕ­­­­­­¬ß¬ß¬¬¬¬«Ţ«Ţ««««ŞÝŞÝŞŞŞŞ©Ü©Ü©©¨Ű¨Ű¨¨§Ú§Ú§§¦Ů¦Ů¦¦ĄŘĄĄ¤×¤×¤¤ŁÖŁŁ˘Ő˘˘ˇÔˇˇ Ó  źŇźźžŃťĐťťśĎ›Î››šÍ™Ě™™—Ę–É––••””““’Ĺ‘ÄĂŹÂŽÁŤŔŚż‹ľŠ˝‰‰‡‡††……„„‚‚€€~~}}||{{zGyFwwvvuuttssrrqqppoonnmmlll9k8j7i6hhggg4f3eeddd1c0bbb/aaa.`-___,^^^+]]]*\\\)[[[(ZZZ'Z'YYY&XXXXX%WWWWW$VVVVV#UUUUU"U"TTTTT!T!SSSSS S RRRRRRQQQQQQQQPPPPPPPPPPOOOOOOOOOONNNNNNNNNNNNNMMMMMMMMMMMMMLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»!»!»!»!»!»!»!»!»!şşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşş ş ş ş ş ş ş ş ąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸··········································¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻ®®®®®®®®­­­­­­­­¬¬¬¬¬¬¬«««««««ŞŞŞŞŞŞ©©©©©¨¨¨¨¨§§§§§ ¦¦¦¦¦ ĄĄĄ ¤¤¤¤¤ ŁŁŁ ˘˘˘ˇˇˇ   źźźžťťťśś›››šš™™˙—ţ——––•ü”ű“ú““’’‘‘ŹŹŽŽŤŤŚŚ‹‹ŠŠ‰‰‡‡††……„„耀~~}}||{{zzyyxwvut s r q p onmllkkjjiiihggffe˙dţddcýbübbaűaa`ú_ů__^ř^^]÷]]\ö\\[ő[[ZôZôZZYóYYXňXňXXWńWńWWVđVđVVUďUďUUUUTîTîTTTTSíSSSSSSRěRěRRRRQëQëQQQQQQPęPęPęPPPPPPOéOéOOOOOOOONčNčNčNNNNNNNNNNMçMçMçMMMMMMMMMMLćLćLćLLLLLLLLLLLLLLKĺKĺKĺKKKKKKKKKKKKKKKKJäJäJäJäJJJJJJJJJJJJJJJJJJJJIăIăIăIăIIIIIIIIIIIIIIIIIIIIIIIIHâHâHâHâHâHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGáGáGáGáGáGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFŕFŕFŕFŕFŕFŕFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEßEßEßEßEßEßEßEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDŢDŢDŢDŢDŢDŢDŢDŢDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»T»T»T»T»T»T»T»T»TşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşSşSşSşSşSşSşSąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąRąRąRąRąRąRąR¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸Q¸Q¸Q¸Q¸Q¸Q·······································P·P·P·P·P¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶O¶O¶O¶OµµµµµµµµµµµµµµµµµµµµµµµµµNµNµNµN´´´´´´´´´´´´´´´´´´´M´M´MłłłłłłłłłłłłłłłłłLłLłL˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛K˛K±±±±±±±±±±±±±J±J°°°°°°°°°°°I°IŻŻŻŻŻŻŻŻŻHŻHŻH®®®®®®®G®G­­­­­­­F­F¬¬¬¬¬¬¬E¬E«««««D«DŞŞŞŞŞC©©©©©B©B¨¨¨A¨A§§§@§@¦¦¦?¦?ĄĄĄ>¤¤¤=¤=ŁŁŁ<˘˘˘;ˇˇˇ:   9źźź8ž7ťťť6śśś5›4ššš3™2———0–/••””“““,’+‘*)ŹŹŽŽŤŤŚŚ‹‹ŠŠ‰‰‡‡††……„„€€~~}}||{{zzyyxxwwvvuuttssrrqqppoonnmmllkŇjŃiĐiihhgÎfÍffeedËddccbÉbbaČaa``_Ć__^Ĺ^^]Ä]]\Ă\\[Â[Â[[ZÁZZYŔYYYYXżXXXXWľWWWWV˝VVVVUĽUĽUUUUT»TTTTSşSşSSSSSSRąRąRRRRQ¸Q¸Q¸QQQQQQP·P·PPPPPPO¶O¶O¶OOOOOOOONµNµNµNNNNNNNNNNM´M´MMMMMMMMMMMMLłLłLłLLLLLLLLLLLLLLK˛K˛K˛KKKKKKKKKKKKKKKKKKJ±J±J±JJJJJJJJJJJJJJJJJJJJJJI°I°I°I°IIIIIIIIIIIIIIIIIIIIIIIIIIHŻHŻHŻHŻHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHG®G®G®G®G®GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGF­F­F­F­F­F­FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE¬E¬E¬E¬E¬E¬E¬EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEED«D«D«D«D«D«D«D«DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşş‡ş‡ş‡ş‡ş‡ş‡ş‡ąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą†ą†ą†ą†ą†ą†¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸…¸…¸…¸…¸…·········································„·„·„·„·„¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµ‚µ‚µ‚µ‚´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłł€ł€ł€˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±~±~±~°°°°°°°°°}°}°}ŻŻŻŻŻŻŻŻŻ|Ż|®®®®®®®®®{®{­­­­­­­z­z¬¬¬¬¬¬¬y¬y«««««x«xŞŞŞŞŞwŞw©©©©©v¨¨¨¨¨u§§§§§t¦¦¦¦¦sĄĄĄĄĄr¤¤¤qŁŁŁpŁp˘˘˘oˇˇˇn   mźźźlžžťťťjśśśi››šššg™™e—d––•••b”a“`’’‘‘ŹŹŹ\Ž[ŤZŚY‹XŠW‰VU‡T†S…R„Q‚‚€€~~}}||{{zzyyxxwwvvuuttssrrqqppoonˇm lźllkkjjiśh›hhggf™eeed—c–ccb•bba”`“``_’__^‘^^]]]\Ź\\\\[Ž[[ZŤZZYŚYŚYYX‹X‹XXWŠWŠWWV‰V‰VVVVUUUUUT‡T‡TTTTS†S†SSSSSSR…R…RRRRRRQ„Q„QQQQQQPPPPPPPPPPO‚O‚OOOOOOOOOONNNNNNNNNNNNM€M€M€MMMMMMMMMMMMLLLLLLLLLLLLLLLLLK~K~K~K~KKKKKKKKKKKKKKKKJ}J}J}J}JJJJJJJJJJJJJJJJJJJJJJI|I|I|I|IIIIIIIIIIIIIIIIIIIIIIIIIIH{H{H{H{H{HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGzGzGzGzGzGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFyFyFyFyFyFyFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFExExExExExExExEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDwDwDwDwDwDwDwDwDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD»»»»»»»»»»»»»»»»»»»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸··················································¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬««««««««ŞŞŞŞŞŞŞŞ©©©©©©©©¨¨¨¨¨¨§§§§§§¦¦¦¦¦¦ĄĄĄĄĄĄ¤¤¤¤ŁŁŁŁ˘˘˘˘˘˘ˇˇˇˇ    źźźźžžžžťťśśśś››››šš™™™™——––––••””““’’’’‘‘ŹŹŽŽŤŤŚŚ‹‹ŠŠ‰‰‡‡††……„„‚‚€€~~}}||{{zzyyxxwwvvuuttssrrqqppoooonnmmllkkjjjjiihhggggffeeeeddccccbbbbaa````____^^^^]]]]\\\\\\[[[[ZZZZZZYYYYYYXXXXXXWWWWWWVVVVVVUUUUUUUUTTTTTTTTSSSSSSSSSSRRRRRRRRRRQQQQQQQQQQPPPPPPPPPPPPOOOOOOOOOOOOOONNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD»»»»şşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸··················································¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬««««««««ŞŞŞŞŞŞŞŞ©©©©©©©©¨¨¨¨¨¨§§§§§§§§¦¦¦¦ĄĄĄĄĄĄ¤¤¤¤¤¤ŁŁŁŁ˘˘˘˘˘˘ˇˇˇˇ    źźźźžžžžťťśśśś››››šš™™™™————––••””””““’’‘‘ŹŹŽŽŤŤŚŚ‹‹ŠŠ‰‰‡‡††……„„‚‚€€~~}}||{{zzyyxxwwvvuuttssssrrqqppoonnmmllllkkjjiiiihhggffffeeddddccccbbbbaa````____^^^^]]]]]]\\\\[[[[ZZZZZZYYYYYYXXXXXXWWWWWWVVVVVVVVUUUUUUUUTTTTTTTTSSSSSSSSSSRRRRRRRRRRQQQQQQQQQQPPPPPPPPPPPPPPOOOOOOOOOOOOOONNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDşíşíşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąěąěąěąěąěąěąěąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸ë¸ë¸ë¸ë¸ë¸ë¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸·ę·ę·ę·ę·ę·ę··········································¶é¶é¶é¶é¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µčµčµčµčµµµµµµµµµµµµµµµµµµµµµµµµµµ´ç´ç´ç´´´´´´´´´´´´´´´´´´´´´´łćłćłćłłłłłłłłłłłłłłłłłł˛ĺ˛ĺ˛ĺ˛˛˛˛˛˛˛˛˛˛˛˛˛˛±ä±ä±ä±±±±±±±±±±±±°ă°ă°°°°°°°°°°ŻâŻâŻâŻŻŻŻŻŻŻŻ®á®á®á®®®®®®­ŕ­ŕ­ŕ­­­­­­¬ß¬ß¬¬¬¬¬¬«Ţ«Ţ««««ŞÝŞÝŞŞŞŞŞŞ©Ü©©©©¨Ű¨Ű¨¨¨¨§Ú§§§§¦Ů¦Ů¦¦ĄŘĄĄĄĄ¤×¤¤¤¤ŁÖŁŁ˘Ő˘˘ˇÔˇÔˇˇ Ó  źŇźźžŃžžťĐśĎśś›Î››šÍšš™™Ë——–ɕȕ•””“Ć’Ĺ’’‘‘ŹÂŽÁŤŔŤŤŚŚ‹‹ŠŠ‰‰‡‡††……„„‚‚€€~~}}||{{zzyyxxwwvvvCuBtAssrrqqppoonnn;m:llkkk8j7iihhh5g4fff3e2ddd1ccc0bbaaa.```-___,^^^+]]]]]*\\\)[[[([(ZZZ'Z'YYY&XXXXX%X%WWW$W$VVVVV#V#UUUUU"U"TTTTT!T!SSSSSSS S RRRRRRRRQQQQQQQQQPPPPPPPPPPOOOOOOOOOOOONNNNNNNNNNNNNNNMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşş ş ş ş ş ş ş ąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸·················································¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®­­­­­­­­­­¬¬¬¬¬¬¬¬«««««««ŞŞŞŞŞŞŞŞ©©©©©¨¨¨¨¨¨§§§§§ ¦¦¦¦¦ ĄĄĄĄĄ ¤¤¤ ¤ ŁŁŁ ˘˘˘ˇˇˇˇˇ   źźźžžžťťśśś›››ššš˙—ţ–ý––••”ű””““’’‘ř÷ŹŹŽŽŤŤŚó‹ň‹‹ŠŠ‰‰‡‡††……„„‚‚€€~~}}||{{zzyyyxwvvuuttssrrqqppp onnmmlllkkjjiiihhgggffeedţddcýccbbaűaa`ú``_ů__^ř^^]÷]÷]]\ö\\[ő[[[[ZôZZZZYóYYXňXňXXXXWńWWWWVđVđVVVVUďUUUUUUTîTTTTTTSíSíSSSSSSRěRěRRRRRRQëQëQQQQQQQQPęPęPPPPPPPPOéOéOOOOOOOOOONčNčNčNNNNNNNNNNNNMçMçMMMMMMMMMMMMMMLćLćLćLLLLLLLLLLLLLLLLKĺKĺKĺKKKKKKKKKKKKKKKKKKKKJäJäJäJäJJJJJJJJJJJJJJJJJJJJJJJJIăIăIăIăIIIIIIIIIIIIIIIIIIIIIIIIIIIIHâHâHâHâHâHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGáGáGáGáGáGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFŕFŕFŕFŕFŕFŕFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEßEßEßEßEßEßEßEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDŢDŢDŢDŢDŢDŢDŢDŢDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşSşSşSşSşSşSşSşSąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąRąRąRąRąRąR¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸Q¸Q¸Q¸Q¸Q·············································P·P·P·P·P¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶O¶O¶O¶OµµµµµµµµµµµµµµµµµµµµµµµµµµµµµNµNµNµN´´´´´´´´´´´´´´´´´´´´´´´M´M´MłłłłłłłłłłłłłłłłłłłLłLłLłL˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛K˛K˛K±±±±±±±±±±±±±J±J±J°°°°°°°°°°°I°I°IŻŻŻŻŻŻŻŻŻŻŻHŻH®®®®®®®®®G®G­­­­­­­­­F­F¬¬¬¬¬¬¬E¬E«««««««D«DŞŞŞŞŞCŞC©©©©©B©B¨¨¨¨¨A§§§§§@§@¦¦¦?¦?ĄĄĄ>Ą>¤¤¤=ŁŁŁŁŁ<˘˘˘;ˇˇˇ:ˇ:   9źźź8žžž7ťťť6śś›››4ššš3™™1——–––/••”””-“,’’‘‘)ŹŹŽŽŤŤŚŚ‹‹‹$Š#‰‰‡‡††……„„‚‚€€~~}}||{{záyŕyyxxwwvvuuttssrrqŘp×ppoonnmÔlÓllkkjŃiĐiihhgÎggfÍeĚeedËddcĘbÉbbaČaa`Ç``_Ć__^Ĺ^^^^]Ä]]\Ă\\[Â[Â[[ZÁZZZZYŔYYYYXżXXXXWľWľWWWWV˝VVVVUĽUĽUUUUT»T»TTTTTTSşSşSSSSSSRąRąRRRRRRQ¸Q¸QQQQQQQQP·P·PPPPPPPPO¶O¶O¶OOOOOOOOOONµNµNµNNNNNNNNNNM´M´M´MMMMMMMMMMMMMMLłLłLłLLLLLLLLLLLLLLLLK˛K˛K˛K˛KKKKKKKKKKKKKKKKKKKKJ±J±J±JJJJJJJJJJJJJJJJJJJJJJJJJJI°I°I°I°IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHŻHŻHŻHŻHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHG®G®G®G®G®GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGF­F­F­F­F­F­FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE¬E¬E¬E¬E¬E¬EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEED«D«D«D«D«D«D«D«DDDDDDDDDDDDDDDDDDDDDDDDşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşş‡ş‡ş‡ş‡ş‡ş‡ş‡ąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą†ą†ą†ą†ą†ą†ą†¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸…¸…¸…¸…¸…¸…···············································„·„·„·„¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµ‚µ‚µ‚µ‚´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłł€ł€ł€˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±~±~°°°°°°°°°°°°°}°}ŻŻŻŻŻŻŻŻŻŻŻ|Ż|Ż|®®®®®®®®®{®{­­­­­­­­­z­z¬¬¬¬¬¬¬y¬y«««««««x«xŞŞŞŞŞwŞw©©©©©v©v¨¨¨¨¨u¨u§§§§§t¦¦¦¦¦sĄĄĄĄĄr¤¤¤¤¤qŁŁŁŁŁp˘˘˘oˇˇˇn     mźźźlžžžkťťťjśśśi››šššg™™™f———d––•••b””““’’’_‘‘ŹŹŹ\Ž[ŤŤŚŚ‹‹ŠŠ‰‰‰VU‡‡††……„„‚‚€€~~}}||{®{{zzyyxxwwvvuutts¦rĄrrqqppoonˇnnmmllkžkkjjiśh›hhgšggffeeed—ddccb•bba”aa`“``_’__^‘^^^^]]]\Ź\\\\[Ž[[ZŤZŤZZYŚYŚYYX‹X‹XXXXWŠWWWWV‰V‰VVVVUUUUUUT‡T‡TTTTTTS†SSSSSSSSR…R…RRRRRRQ„Q„QQQQQQQQPPPPPPPPPPPPO‚O‚OOOOOOOOOOOONNNNNNNNNNNNNNM€M€M€MMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLK~K~K~KKKKKKKKKKKKKKKKKKKKJ}J}J}J}JJJJJJJJJJJJJJJJJJJJJJJJJJI|I|I|I|IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIH{H{H{H{H{HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGzGzGzGzGzGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFyFyFyFyFyFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFExExExExExExExEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDwDwDwDwDwDwDwDwDDDDDDDDşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸··························································¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬««««««««««ŞŞŞŞŞŞŞŞ©©©©©©©©¨¨¨¨¨¨¨¨§§§§§§§§¦¦¦¦¦¦ĄĄĄĄĄĄ¤¤¤¤¤¤ŁŁŁŁŁŁ˘˘˘˘ˇˇˇˇ      źźźźžžžžťťťťśśśś››››šš™™™™——––––••””””““’’‘‘‘‘ŹŹŽŽŤŤŤŤŚŚ‹‹ŠŠ‰‰‡‡‡‡††……„„‚‚€€~~}}||||{{zzyyxxwwvvuuttttssrrqqppoooonnmmllllkkjjjjiihhhhggffffeeeeddccccbbbbaaaa````____^^^^^^]]]]\\\\\\[[[[[[ZZZZZZYYYYYYXXXXXXWWWWWWWWVVVVVVVVUUUUUUUUTTTTTTTTSSSSSSSSSSSSRRRRRRRRRRQQQQQQQQQQQQPPPPPPPPPPPPPPOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEDDDDDDDDşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸··························································¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬««««««««««ŞŞŞŞŞŞŞŞŞŞ©©©©©©©©¨¨¨¨¨¨¨¨§§§§§§¦¦¦¦¦¦¦¦ĄĄĄĄĄĄ¤¤¤¤¤¤ŁŁŁŁ˘˘˘˘˘˘ˇˇˇˇ      źźźźžžžžťťťťśśśś››››šššš™™——––––••••””““’’’’‘‘ŹŹŹŹŽŽŤŤŚŚ‹‹‹‹ŠŠ‰‰‡‡††††……„„‚‚€€~~}}}}||{{zzyyxxwwvvvvuuttssrrqqqqppoonnnnmmllkkkkjjiiiihhggggffffeeddddccccbbbbaaaa````____^^^^^^]]]]\\\\\\[[[[[[ZZZZZZYYYYYYXXXXXXXXWWWWWWVVVVVVVVUUUUUUUUUUTTTTTTTTSSSSSSSSSSSSRRRRRRRRRRQQQQQQQQQQQQQQPPPPPPPPPPPPPPOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşşąěąěąěąěąěąěąěąěąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸ë¸ë¸ë¸ë¸ë¸ë¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸·ę·ę·ę·ę·ę················································¶é¶é¶é¶é¶é¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µčµčµčµčµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´ç´ç´ç´ç´´´´´´´´´´´´´´´´´´´´´´´´łćłćłćłłłłłłłłłłłłłłłłłłłł˛ĺ˛ĺ˛ĺ˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±ä±ä±ä±±±±±±±±±±±±±±°ă°ă°ă°°°°°°°°°°°°ŻâŻâŻŻŻŻŻŻŻŻŻŻŻŻ®á®á®®®®®®®®®®­ŕ­ŕ­­­­­­­­¬ß¬ß¬¬¬¬¬¬«Ţ«Ţ««««««ŞÝŞÝŞŞŞŞŞŞ©Ü©Ü©©©©¨Ű¨Ű¨¨¨¨§Ú§Ú§§§§¦Ů¦¦¦¦ĄŘĄŘĄĄ¤×¤×¤¤ŁÖŁÖŁŁ˘Ő˘˘˘˘ˇÔˇˇ Ó    źŇźźžŃžžťĐťťśĎśś›Î››šÍšš™Ě™™—Ę——––•Č••”ǓƓ“’’‘Ä‘‘ŹŹŽÁŽŽŤŤŚŚ‹‹Š˝ŠŠ‰‰‡‡††…¸……„„‚‚€€~~}}}}||{{zzyyxxwwwDvvuuttssrrr?qqppooo¤¤¤¤¤=ŁŁŁŁŁ<˘˘˘˘˘;ˇˇˇ:     9źźź8žžž7ťťť6śśś5›››4ššš3™™™21——–––/••”””-““’’’+‘‘)ŹŹŽŽŤŤŤ&ŚŚ‹‹ŠŠ‰‰‰"‡‡††……„„„‚‚€€~~~~}}||{{zzyyxßxxwwvvuutŰttssrrqqp×ppoonŐmÔmmllkŇkkjjiĐiihĎhhggfÍffeĚeedËddcĘccbÉbbaČaa`Ç``_Ć____^Ĺ^^]Ä]]]]\Ă\\[Â[Â[[ZÁZÁZZZZYŔYYYYXżXXXXXXWľWWWWV˝V˝VVVVVVUĽUUUUUUT»T»TTTTTTSşSşSSSSSSRąRąRRRRRRRRQ¸Q¸Q¸QQQQQQQQP·P·PPPPPPPPPPPPO¶O¶OOOOOOOOOOOONµNµNNNNNNNNNNNNNNM´M´M´MMMMMMMMMMMMMMMMLłLłLłLLLLLLLLLLLLLLLLLLLLK˛K˛K˛KKKKKKKKKKKKKKKKKKKKKKKKJ±J±J±JJJJJJJJJJJJJJJJJJJJJJJJJJJJI°I°I°I°IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHŻHŻHŻHŻHŻHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHG®G®G®G®G®GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGF­F­F­F­F­F­FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE¬E¬E¬E¬E¬E¬E¬EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEşşş‡ş‡ş‡ş‡ş‡ş‡ş‡ş‡ąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą†ą†ą†ą†ą†ą†ą†¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸…¸…¸…¸…¸…·····················································„·„·„·„·„¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ‚µ‚µ‚µ‚´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłł€ł€ł€˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±~±~±~°°°°°°°°°°°°°°°}°}ŻŻŻŻŻŻŻŻŻŻŻŻŻ|Ż|®®®®®®®®®®®{®{­­­­­­­­­­­z­z¬¬¬¬¬¬¬¬¬y«««««««««x«xŞŞŞŞŞŞŞwŞw©©©©©v©v¨¨¨¨¨¨¨u§§§§§t§t¦¦¦¦¦sĄĄĄĄĄrĄr¤¤¤¤¤qŁŁŁŁŁp˘˘˘o˘oˇˇˇn     mźźźlžžžkťťťjśśśś››››šššš™™™fe———d––•••b””“““`’’‘‘‘^ŹŹŹ\ŽŽŤŤŚŚŚY‹‹ŠŠ‰‰U‡‡††……„„‚‚€€~~~~}}||{{zzyyyyxxwwvvu¨uuttssrrq¤qqppo˘oonnmmlźllkkjťjjiśiihhgšggf™ffeeeddddccccbbbba”aa`“``_’____^‘^^]]]]]\Ź\\\\[Ž[[[[ZŤZZZZYŚYYYYX‹X‹XXXXWŠWWWWWWV‰V‰VVVVUUUUUUUUT‡T‡TTTTTTS†S†SSSSSSR…R…RRRRRRRRRRQ„Q„QQQQQQQQPPPPPPPPPPPPPO‚O‚OOOOOOOOOOOONNNNNNNNNNNNNNNNNM€M€M€MMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLK~K~K~KKKKKKKKKKKKKKKKKKKKKKKKJ}J}J}J}JJJJJJJJJJJJJJJJJJJJJJJJJJJJI|I|I|I|IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIH{H{H{H{HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGzGzGzGzGzGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFyFyFyFyFyFyFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFExExExExExExExEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEşşąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸······························································¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬««««««««««ŞŞŞŞŞŞŞŞŞŞ©©©©©©©©¨¨¨¨¨¨¨¨¨¨§§§§§§¦¦¦¦¦¦¦¦ĄĄĄĄĄĄ¤¤¤¤¤¤¤¤ŁŁŁŁŁŁ˘˘˘˘ˇˇˇˇˇˇ      źźźźžžžžťťťťśśśśśś››››šššš™™————––––••””””““’’’’‘‘ŹŹŽŽŽŽŤŤŚŚ‹‹‹‹ŠŠ‰‰‡‡‡‡††……„„‚‚€€~~~~}}||{{zzyyyyxxwwvvvvuuttssrrrrqqppppoonnmmmmllkkkkjjjjiihhhhggggffffeeddddccccbbbbbbaaaa````______^^^^]]]]]]\\\\\\[[[[[[ZZZZZZYYYYYYYYXXXXXXWWWWWWWWWWVVVVVVVVUUUUUUUUUUTTTTTTTTTTSSSSSSSSSSRRRRRRRRRRRRRRQQQQQQQQQQQQQQPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸································································¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬««««««««««ŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©¨¨¨¨¨¨¨¨§§§§§§§§¦¦¦¦¦¦¦¦ĄĄĄĄĄĄ¤¤¤¤¤¤ŁŁŁŁŁŁ˘˘˘˘˘˘ˇˇˇˇˇˇ      źźźźžžžžťťťťťťśśśś››››šššš™™™™————––––••••””““““’’‘‘‘‘ŹŹŹŹŽŽŤŤŤŤŚŚ‹‹ŠŠŠŠ‰‰‡‡††††……„„‚‚‚‚€€~~~~}}||{{zzzzyyxxwwvvvvuuttssssrrqqqqppoonnnnmmllllkkkkjjiiiihhhhggffffeeeeddddccccbbbbbbaaaa````______^^^^]]]]]]\\\\\\[[[[[[ZZZZZZZZYYYYYYXXXXXXXXWWWWWWWWVVVVVVVVVVUUUUUUUUTTTTTTTTTTTTSSSSSSSSSSRRRRRRRRRRRRRRQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸ë¸ë¸ë¸ë¸ë¸ë¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸·ę·ę·ę·ę·ę························································¶é¶é¶é¶é¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µčµčµčµčµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´ç´ç´ç´ç´´´´´´´´´´´´´´´´´´´´´´´´´´łćłćłćłćłłłłłłłłłłłłłłłłłłłłłł˛ĺ˛ĺ˛ĺ˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±ä±ä±ä±±±±±±±±±±±±±±±±°ă°ă°ă°°°°°°°°°°°°°°ŻâŻâŻŻŻŻŻŻŻŻŻŻŻŻ®á®á®®®®®®®®®®®®­ŕ­ŕ­­­­­­­­­­¬ß¬ß¬¬¬¬¬¬¬¬«Ţ«Ţ««««««ŞÝŞÝŞŞŞŞŞŞ©Ü©Ü©©©©©©¨Ű¨Ű¨¨¨¨¨¨§Ú§§§§§§¦Ů¦¦¦¦ĄŘĄŘĄĄĄĄ¤×¤¤¤¤ŁÖŁŁŁŁ˘Ő˘˘˘˘ˇÔˇˇˇˇ Ó    źŇźźžŃžžťĐťťťťśĎśś›Î››šÍšš™Ě™™Ë————––•Č••”Ç””““’Ĺ’’‘‘ĂŹŹŽÁŽŽŤŤŚżŚŚ‹‹ŠŠ‰Ľ‰‰‡‡††††……„„‚‚‚‚€€~~~~}}||{{{HzzyyxxwwwDvvuutttAssrrqqqqppoooĄ>¤¤¤¤¤=ŁŁŁŁŁ<˘˘˘˘˘;ˇˇˇˇˇ:     9źźź8žžžžž7ťťť6śśś5›››4ššš3™™™21———0–––/••””””“““,’’‘‘‘‘ŹŹŹŹŽŽŤŤŤŤŚŚ‹‹‹$ŠŠ‰‰‡‡††…………„„‚‚‚‚€€~~~~}}||{{{{zzyyxxxxwwvvuuuuttssrŮrrqqp×ppoonŐnnmmlÓllkŇkkjjiĐiihĎhhgÎggfÍffeĚeedËddcĘccbÉbbaČaaaa`Ç``_Ć____^Ĺ^^^^]Ä]]\Ă\Ă\\\\[Â[[[[ZÁZZZZYŔYŔYYYYXżXXXXXXWľWWWWWWV˝V˝VVVVVVUĽUĽUUUUUUT»T»TTTTTTSşSşSSSSSSSSRąRąRRRRRRRRRRQ¸Q¸QQQQQQQQQQP·P·P·PPPPPPPPPPPPO¶O¶OOOOOOOOOOOOOONµNµNNNNNNNNNNNNNNNNM´M´M´MMMMMMMMMMMMMMMMMMLłLłLłLLLLLLLLLLLLLLLLLLLLLLK˛K˛K˛K˛KKKKKKKKKKKKKKKKKKKKKKKKKKJ±J±J±JJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJI°I°I°I°IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHŻHŻHŻHŻHŻHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHG®G®G®G®G®GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGF­F­F­F­F­F­FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFE¬E¬E¬E¬E¬E¬E¬EEEEEEąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą†ą†ą†ą†ą†ą†ą†¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸…¸…¸…¸…¸…·····························································„·„·„·„¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ‚µ‚µ‚´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłł€ł€ł€˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±~±~±~°°°°°°°°°°°°°°°}°}°}ŻŻŻŻŻŻŻŻŻŻŻŻŻ|Ż|®®®®®®®®®®®®®{®{­­­­­­­­­­­z­z¬¬¬¬¬¬¬¬¬¬¬y¬y«««««««««x«xŞŞŞŞŞŞŞwŞw©©©©©©©v©v¨¨¨¨¨¨¨u§§§§§§§t§t¦¦¦¦¦s¦sĄĄĄĄĄr¤¤¤¤¤q¤qŁŁŁŁŁp˘˘˘˘˘oˇˇˇˇˇn     mźźźlžžžžžkťťťjśśśi››››šššš™™™™————––––•••b”””a““’’’_‘‘‘^ŹŹŹ\ŽŽŤŤŤZŚŚ‹‹ŠŠŠŠ‰‰U‡‡††………R„„‚‚‚‚€€~±~~}}||{®{{zzyyx«xxwwvvu¨uutts¦ssrrq¤qqppo˘oonnm mmllllkkjťjjiśiihhhhggggffffeeeeddddc–ccb•bba”aaaa`“``_’____^‘^^^^]]]]]\Ź\\\\[Ž[[[[ZŤZZZZZZYŚYYYYX‹X‹XXXXWŠWŠWWWWWWV‰VVVVVVVVUUUUUUUUUT‡TTTTTTTTS†S†SSSSSSSSR…R…RRRRRRRRRRQ„Q„QQQQQQQQQQQQPPPPPPPPPPPPPPO‚O‚OOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNM€M€M€MMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLK~K~K~KKKKKKKKKKKKKKKKKKKKKKKKKKJ}J}J}J}JJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJI|I|I|I|IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIH{H{H{H{HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGzGzGzGzGzGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFyFyFyFyFyFyFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFExExExąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸······································································¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««ŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨§§§§§§§§¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄ¤¤¤¤¤¤ŁŁŁŁŁŁŁŁ˘˘˘˘˘˘ˇˇˇˇˇˇ      źźźźžžžžžžťťťťśśśś››››››šššš™™™™————––––••””””““““’’‘‘‘‘ŹŹŽŽŽŽŤŤŚŚŚŚ‹‹ŠŠŠŠ‰‰‡‡‡‡††……„„„„‚‚‚‚€€~~}}||||{{zzyyyyxxwwvvvvuuttttssrrrrqqppppoonnnnmmllllkkkkjjjjiihhhhggggffffeeeeddddddccccbbbbaaaaaa````______^^^^^^]]]]]]\\\\\\[[[[[[ZZZZZZZZYYYYYYYYXXXXXXXXWWWWWWWWVVVVVVVVVVUUUUUUUUUUTTTTTTTTTTTTSSSSSSSSSSSSRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸········································································¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©¨¨¨¨¨¨¨¨§§§§§§§§§§¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄ¤¤¤¤¤¤ŁŁŁŁŁŁŁŁ˘˘˘˘˘˘ˇˇˇˇˇˇ    źźźźźźžžžžžžťťťťśśśśśś››››šššš™™™™————––––••••””””““’’’’‘‘‘‘ŹŹŹŹŽŽŤŤŤŤŚŚ‹‹‹‹ŠŠ‰‰‰‰‡‡‡‡††……„„„„‚‚€€~~}}||||{{zzyyyyxxwwwwvvuuttttssrrrrqqppppoooonnmmmmllllkkjjjjiiiihhhhggggffffeeeeddddccccccbbbbaaaaaa````______^^^^^^]]]]]]\\\\\\[[[[[[[[ZZZZZZYYYYYYYYXXXXXXXXWWWWWWWWWWVVVVVVVVVVUUUUUUUUUUTTTTTTTTTTTTSSSSSSSSSSSSRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFąąąąąąąąąąąąąąąąąąąąąąąąąąąą¸ë¸ë¸ë¸ë¸ë¸ë¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸·ę·ę·ę·ę·ę······························································¶é¶é¶é¶é¶é¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µčµčµčµčµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´ç´ç´ç´ç´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łćłćłćłćłłłłłłłłłłłłłłłłłłłłłłłłłł˛ĺ˛ĺ˛ĺ˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±ä±ä±ä±±±±±±±±±±±±±±±±±±±±°ă°ă°°°°°°°°°°°°°°°°ŻâŻâŻâŻŻŻŻŻŻŻŻŻŻŻŻ®á®á®á®®®®®®®®®®®®­ŕ­ŕ­­­­­­­­­­¬ß¬ß¬¬¬¬¬¬¬¬¬¬«Ţ«Ţ««««««««ŞÝŞÝŞŞŞŞŞŞŞŞ©Ü©Ü©©©©©©¨Ű¨Ű¨¨¨¨¨¨§Ú§§§§§§¦Ů¦Ů¦¦¦¦ĄŘĄŘĄĄĄĄ¤×¤×¤¤¤¤ŁÖŁŁŁŁ˘Ő˘Ő˘˘˘˘ˇÔˇˇˇˇ Ó  źŇźźźźžŃžžžžťĐťťśĎśśśś›Î››šÍšš™Ě™™Ë—Ę——–É––•Č••”Ç””“Ć““’’‘Ä‘‘ĂŹŹŽÁŽŽŤŤŤŤŚŚ‹‹‹‹ŠŠ‰‰‰‰‡‡†ą††……„„„„‚‚€€~~}}||||{{zzyyyyxxwwwwvvuuuBttsss@rrqqq>ppoooonnn;mmllllkkk8jjj7iii6hhh5ggg4fff3eee2ddd1ccccbbbbb/aaaa`````-_____,^^^^^+]]]]]*\\\\\)[[[[[[[(ZZZZZ'Z'YYYYY&Y&XXXXX%X%WWWWWWW$W$VVVVVVV#V#UUUUUUU"U"TTTTTTTTT!T!SSSSSSSSS S RRRRRRRRRRRRQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFąąąąąąąąąąąąąąąąąąąąąą¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸···································································¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬««««««««««ŞŞŞŞŞŞŞŞŞŞ©©©©©©©©¨¨¨¨¨¨¨¨§§§§§§§ ¦¦¦¦¦¦¦ ĄĄĄĄĄĄĄ ¤¤¤¤¤¤¤ ŁŁŁŁŁ ˘˘˘˘˘˘ˇˇˇˇˇ   źźźźźžžžžžťťťśśśśś›››ššš™™™™————––––••••””“ú““’ů’’‘‘‘‘ŹöŹŹŽŽŽŽŤŤŚóŚŚ‹‹ŠńŠŠ‰‰ď‡‡††††……„„„„‚‚€€~~}}||||{{zzzyyxxxwwvvuuuuttssssrrr qqppp oonnnnmmmlllkkjjjjiiiihhhhggggffffeeeedţddcýccbübbbbaűaa`ú````_ů____^ř^^^^]÷]]]]\ö\\\\[ő[ő[[[[ZôZZZZZZYóYYYYYYXňXXXXXXWńWWWWWWWWVđVVVVVVVVUďUUUUUUUUTîTîTTTTTTTTSíSíSSSSSSSSRěRěRRRRRRRRRRQëQëQëQQQQQQQQQQPęPęPęPPPPPPPPPPPPOéOéOéOOOOOOOOOOOOOONčNčNčNNNNNNNNNNNNNNNNMçMçMçMMMMMMMMMMMMMMMMMMMMLćLćLćLLLLLLLLLLLLLLLLLLLLLLLLKĺKĺKĺKĺKKKKKKKKKKKKKKKKKKKKKKKKKKKKJäJäJäJäJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIăIăIăIăIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHâHâHâHâHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGáGáGáGáGáGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFŕFŕFŕFŕFŕFŕFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFąąąąąRąRąRąRąRąR¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸Q¸Q¸Q¸Q¸Q¸Q·································································P·P·P·P¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶O¶O¶O¶O¶OµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµNµNµNµN´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´M´M´M´MłłłłłłłłłłłłłłłłłłłłłłłłłłłLłLłL˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛K˛K˛K±±±±±±±±±±±±±±±±±±±J±J±J°°°°°°°°°°°°°°°°°I°I°IŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻHŻH®®®®®®®®®®®®®G®G®G­­­­­­­­­­­F­F¬¬¬¬¬¬¬¬¬¬¬E¬E«««««««««««DŞŞŞŞŞŞŞŞŞCŞC©©©©©©©©©B¨¨¨¨¨¨¨¨¨A§§§§§§§@§@¦¦¦¦¦¦¦?ĄĄĄĄĄĄĄ>¤¤¤¤¤=¤=ŁŁŁŁŁ<˘˘˘˘˘;ˇˇˇˇˇ:ˇ:   9źźźźź8žžžžž7ťťťťśśśśś5›››4šššš™™™™™21———0–––/•••.””““““’’’’‘‘‘*ŹŹŹŹŽŽŽ'ŤŤŚŚŚ%‹‹ŠŠŠŠ‰‰‡‡††††……„„„‚‚€€~~}}||||{{zzzzyyxxxxwwvvuÜuuttsÚssrrrrqqp×ppoonŐnnmÔmmllllkkjŃjjiĐiihĎhhgÎggfÍffeĚeeeeddddcĘccbÉbbbbaČaa`Ç````_Ć____^Ĺ^^^^]Ä]]]]\Ă\\\\\\[Â[[[[ZÁZZZZZZYŔYYYYYYXżXXXXXXWľWľWWWWWWV˝V˝VVVVVVUĽUĽUUUUUUUUT»TTTTTTTTTTSşSşSSSSSSSSRąRąRRRRRRRRRRRRQ¸Q¸QQQQQQQQQQQQP·P·PPPPPPPPPPPPPPO¶O¶OOOOOOOOOOOOOOOONµNµNNNNNNNNNNNNNNNNNNM´M´M´MMMMMMMMMMMMMMMMMMMMLłLłLłLLLLLLLLLLLLLLLLLLLLLLLLLLK˛K˛K˛KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJ±J±J±JJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJI°I°I°I°IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHŻHŻHŻHŻHŻHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHG®G®G®G®G®GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGF­F­F­F­F­F­FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFą†ą†¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸…¸…¸…¸…¸…···································································„·„·„·„·„¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ‚µ‚µ‚´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłł€ł€ł€˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±~±~°°°°°°°°°°°°°°°°°°°}°}ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ|Ż|®®®®®®®®®®®®®{®{­­­­­­­­­­­­­z­z¬¬¬¬¬¬¬¬¬¬¬y¬y«««««««««««x«xŞŞŞŞŞŞŞŞŞw©©©©©©©©©v©v¨¨¨¨¨¨¨u¨u§§§§§§§t¦¦¦¦¦¦¦s¦sĄĄĄĄĄrĄr¤¤¤¤¤qŁŁŁŁŁŁŁp˘˘˘˘˘oˇˇˇˇˇn     mźźźźźlžžžžžkťťťťťjśśśi››››šššššg™™™fe———d–––c•••b”””a“““`’’’_‘‘ŹŹŹ\ŽŽŤŤŤŤŚŚ‹‹‹‹ŠŠŠW‰‰U‡‡†††S……„„‚‚€€~~}}|Ż||{{zzzzyyxxxxwwvvvvuut§ttssrĄrrqqqqppo˘oonnnnmmlźllkžkkjťjjiśiih›hhgšggf™ffeeed—ddddc–ccb•bbbba”aa`“````_’____^‘^^^^]]]]]\Ź\Ź\\\\[Ž[[[[ZŤZŤZZZZYŚYŚYYYYX‹X‹XXXXXXWŠWWWWWWWWV‰VVVVVVVVUUUUUUUUUT‡T‡TTTTTTTTTTS†SSSSSSSSSSR…R…RRRRRRRRRRRRQ„Q„QQQQQQQQQQQQPPPPPPPPPPPPPPPPO‚O‚OOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNM€M€M€MMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLK~K~K~KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJ}J}J}J}JJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJI|I|I|I|IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIH{H{H{H{HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGzGzGzGzGzGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFyFyFyFyFyFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸············································································¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁ˘˘˘˘˘˘ˇˇˇˇˇˇ      źźźźźźžžžžžžťťťťťťśśśś››››››šššš™™™™————––––••••””””““““’’’’‘‘‘‘ŹŹŽŽŽŽŤŤŤŤŚŚ‹‹‹‹ŠŠ‰‰‰‰‡‡‡‡††…………„„‚‚€€~~}}}}||{{zzzzyyxxxxwwvvvvuuuuttssssrrqqqqppppoonnnnmmmmllllkkkkjjjjiiiihhhhggggffffeeeeddddddccccbbbbbbaaaa``````______^^^^^^]]]]]]]]\\\\\\[[[[[[[[ZZZZZZZZYYYYYYYYXXXXXXXXWWWWWWWWWWVVVVVVVVVVUUUUUUUUUUUUTTTTTTTTTTTTSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸············································································¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁ˘˘˘˘˘˘ˇˇˇˇˇˇ      źźźźźźžžžžžžťťťťťťśśśś››››››šššš™™™™————––––••••””””““““’’’’‘‘ŹŹŹŹŽŽŽŽŤŤŚŚŚŚ‹‹ŠŠŠŠ‰‰‰‰‡‡‡‡††…………„„‚‚€€~~}}}}||{{{{zzyyyyxxwwwwvvuuuuttssssrrrrqqppppoooonnnnmmllllkkkkjjjjiiiihhhhggggffffffeeeeddddccccccbbbbbbaaaa``````______^^^^^^]]]]]]]]\\\\\\[[[[[[[[ZZZZZZZZYYYYYYYYXXXXXXXXXXWWWWWWWWWWVVVVVVVVVVUUUUUUUUUUUUTTTTTTTTTTTTSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸·ę·ę·ę·ę·ę·ę····································································¶é¶é¶é¶é¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µčµčµčµčµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´ç´ç´ç´ç´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łćłćłćłćłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛ĺ˛ĺ˛ĺ˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±ä±ä±ä±±±±±±±±±±±±±±±±±±±±°ă°ă°ă°°°°°°°°°°°°°°°°°°ŻâŻâŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®á®á®®®®®®®®®®®®®®­ŕ­ŕ­­­­­­­­­­­­¬ß¬ß¬¬¬¬¬¬¬¬¬¬¬¬«Ţ««««««««««ŞÝŞÝŞŞŞŞŞŞŞŞ©Ü©Ü©©©©©©©©¨Ű¨Ű¨¨¨¨¨¨§Ú§Ú§§§§§§¦Ů¦Ů¦¦¦¦¦¦ĄŘĄĄĄĄĄĄ¤×¤¤¤¤¤¤ŁÖŁŁŁŁ˘Ő˘Ő˘˘˘˘ˇÔˇˇˇˇ Ó    źŇźźźźžŃžžžžťĐťťťťśĎśśśś››››šÍšš™Ě™™™™—Ę——–É––•Č••”Ç””“Ć““’Ĺ’’‘Ä‘‘ŹŹŽÁŽŽŤŔŤŤŚŚŚŚ‹‹ŠŠŠŠ‰‰»‡‡†ą††…………„„‚‚€€~~}}}}||{{{{zzyyyyxxwwwwvvuuuutttAssrrrrqqq>ppoooonnnnmmm:lll9kkk8jjj7iii6hhh5ggg4ffffeeeee2ddd1ccccc0bbbbaaaaa.`````-_____,^^^^^+]]]]]]]*\\\\\)[[[[[[[(ZZZZZZZ'YYYYYYY&Y&XXXXXXX%WWWWWWWWW$VVVVVVVVV#V#UUUUUUUUU"U"TTTTTTTTT!T!SSSSSSSSSSS S RRRRRRRRRRRRRRQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFFFFFFFFFFFFFFFFFF¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸···········································································¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««ŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©¨¨¨¨¨¨¨¨¨§§§§§§§§§ ¦¦¦¦¦¦¦ ¦ ĄĄĄĄĄ Ą ¤¤¤¤¤ ¤ ŁŁŁŁŁ ˘˘˘˘˘˘˘ˇˇˇˇˇ     źźźźźžžžžžťťťťťśśśśś›››šššš™™™™˙—ţ————––––••••””””““““’’’’‘‘÷ŹöŹŹŽŽŽŽŤŤŤŤŚŚ‹ň‹‹ŠŠŠŠ‰‰‡‡††††……„ë„„‚‚€€~~}}}}||{{{{zzyyyyxxwwwwvvvuuttttsss rrqqqqppp ooonnmmmmllllkkkkjjjjiiiihhhhhgggffe˙eeeedţddcýccccbübbaűaaaa`ú````_ů____^ř^^^^]÷]÷]]]]\ö\\\\[ő[ő[[[[ZôZôZZZZYóYóYYYYYYXňXXXXXXWńWńWWWWWWVđVđVVVVVVVVUďUUUUUUUUUUTîTTTTTTTTTTSíSíSSSSSSSSSSRěRěRRRRRRRRRRRRQëQëQQQQQQQQQQQQQQPęPęPPPPPPPPPPPPPPOéOéOéOOOOOOOOOOOOOOOONčNčNčNNNNNNNNNNNNNNNNNNMçMçMçMMMMMMMMMMMMMMMMMMMMMMLćLćLćLLLLLLLLLLLLLLLLLLLLLLLLLLLLKĺKĺKĺKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJäJäJäJäJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIăIăIăIăIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHâHâHâHâHâHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGáGáGáGáGáGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGFŕFŕFŕFŕFŕFŕFFFFFF¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸Q¸Q¸Q¸Q¸Q·······································································P·P·P·P·P¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶O¶O¶O¶O¶OµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµNµNµN´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´M´M´M´MłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłLłLłL˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛K˛K˛K±±±±±±±±±±±±±±±±±±±±±±±J±J°°°°°°°°°°°°°°°°°°°°°I°IŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻHŻHŻH®®®®®®®®®®®®®®®G®G­­­­­­­­­­­­­F­F¬¬¬¬¬¬¬¬¬¬¬¬¬E¬E«««««««««««DŞŞŞŞŞŞŞŞŞŞŞCŞC©©©©©©©©©B¨¨¨¨¨¨¨¨¨A¨A§§§§§§§@§@¦¦¦¦¦¦¦?ĄĄĄĄĄĄĄ>¤¤¤¤¤¤¤=ŁŁŁŁŁŁŁ<˘˘˘˘˘˘˘;ˇˇˇˇˇ:     9źźźźź8žžžžž7ťťťťť6śśśśś5›››4ššššš3™™™2—————0–––/•••.”””-“““,’’’+‘‘ŹŹŹŹŽŽŽ'ŤŤŤ&ŚŚ‹‹‹‹ŠŠŠ#‰‰‡‡††††……„„„„‚‚€€~~}}}}||{{{{zzyyyyxxwŢwwvvvvuutŰttssssrrqŘqqppppoooonnmÔmmlÓllkŇkkjŃjjiĐiihĎhhhhggggfÍffeĚeeeeddddcĘccccbbbbaČaaaa`Ç````_Ć____^Ĺ^^^^^^]Ä]]]]\Ă\\\\\\[Â[[[[[[ZÁZZZZZZYŔYYYYYYXżXżXXXXXXWľWWWWWWWWV˝V˝VVVVVVUĽUĽUUUUUUUUT»T»TTTTTTTTTTSşSşSSSSSSSSSSRąRąRRRRRRRRRRRRQ¸Q¸QQQQQQQQQQQQQQP·P·PPPPPPPPPPPPPPPPO¶O¶OOOOOOOOOOOOOOOOOONµNµNNNNNNNNNNNNNNNNNNNNM´M´M´MMMMMMMMMMMMMMMMMMMMMMLłLłLłLLLLLLLLLLLLLLLLLLLLLLLLLLLLK˛K˛K˛KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJ±J±J±JJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJI°I°I°I°IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHŻHŻHŻHŻHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHG®G®G®G®G®GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGF­F­F­¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸…¸…¸…¸…¸…¸…·········································································„·„·„·„¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ‚µ‚µ‚µ‚´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł€ł€ł€ł€˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±~±~±~°°°°°°°°°°°°°°°°°°°}°}°}ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ|Ż|®®®®®®®®®®®®®®®{®{®{­­­­­­­­­­­­­z­z¬¬¬¬¬¬¬¬¬¬¬¬¬y¬y«««««««««««x«xŞŞŞŞŞŞŞŞŞwŞw©©©©©©©©©v©v¨¨¨¨¨¨¨¨¨u§§§§§§§§§t¦¦¦¦¦¦¦s¦sĄĄĄĄĄĄĄr¤¤¤¤¤¤¤qŁŁŁŁŁŁŁp˘˘˘˘˘˘˘oˇˇˇˇˇn     mźźźźźlžžžžžkťťťťťjśśśśśi››››šššššg™™™™e———d–––c••••””””““““’’’’‘‘‘‘]ŹŹŹ\ŽŽŤŤŤŤŚŚŚŚ‹‹‹XŠŠ‰‰‰‰U‡‡††††……„„„„‚‚‚‚€€~~}}}}||{{{{zzy¬yyxxxxwwv©vvuuuutts¦ssrrrrqqpŁppo˘oonˇnnmmmmllllkkkkjjjjiśiih›hhgšggggffffeeed—ddddc–ccb•bbbba”aaaa`“````_’____^‘^^^^^^]]]]]\Ź\\\\\\[Ž[[[[[[ZŤZZZZZZYŚYYYYYYYYX‹XXXXXXWŠWŠWWWWWWWWV‰VVVVVVVVUUUUUUUUUUT‡T‡TTTTTTTTTTS†S†SSSSSSSSSSR…R…RRRRRRRRRRRRQ„Q„QQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPO‚O‚OOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNM€M€M€MMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLK~K~K~K~KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJ}J}J}J}JJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJI|I|I|I|IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIH{H{H{H{H{HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGzGzGzGzGzGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸····················································································¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘ˇˇˇˇˇˇ      źźźźźźžžžžžžťťťťťťśśśśśś››››››šššš™™™™™™————––––••••••””””““““’’’’‘‘‘‘ŹŹŹŹŽŽŽŽŤŤŤŤŚŚŚŚ‹‹ŠŠŠŠ‰‰‰‰‡‡‡‡††††……„„„„‚‚‚‚€€~~}}}}||{{{{zzzzyyxxxxwwwwvvuuuuttttssrrrrqqqqppppoooonnmmmmllllkkkkjjjjjjiiiihhhhggggffffffeeeeddddddccccbbbbbbaaaaaa``````______^^^^^^^^]]]]]]\\\\\\\\[[[[[[[[ZZZZZZZZYYYYYYYYYYXXXXXXXXXXWWWWWWWWWWVVVVVVVVVVVVUUUUUUUUUUUUTTTTTTTTTTTTTTSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸····················································································¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁ˘˘˘˘˘˘ˇˇˇˇˇˇˇˇ      źźźźźźžžžžžžžžťťťťśśśśśś››››››šššš™™™™™™——————––––••••””””““““’’’’‘‘‘‘ŹŹŹŹŽŽŽŽŤŤŤŤŚŚ‹‹‹‹ŠŠŠŠ‰‰‡‡‡‡††…………„„„„‚‚‚‚€€~~}}}}||||{{zzzzyyxxxxwwwwvvuuuuttttssssrrqqqqppppoooonnnnmmmmllllkkkkjjjjiiiihhhhhhggggffffeeeeeeddddccccccbbbbbbaaaaaa``````______^^^^^^^^]]]]]]\\\\\\\\[[[[[[[[ZZZZZZZZZZYYYYYYYYXXXXXXXXXXWWWWWWWWWWWWVVVVVVVVVVUUUUUUUUUUUUUUTTTTTTTTTTTTTTSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸¸·ę·ę·ę·ę·ę··········································································¶é¶é¶é¶é¶é¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µčµčµčµčµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´ç´ç´ç´ç´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łćłćłćłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛ĺ˛ĺ˛ĺ˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±ä±ä±±±±±±±±±±±±±±±±±±±±±±±±°ă°ă°°°°°°°°°°°°°°°°°°°°ŻâŻâŻâŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®á®á®á®®®®®®®®®®®®®®­ŕ­ŕ­­­­­­­­­­­­­­¬ß¬ß¬¬¬¬¬¬¬¬¬¬¬¬«Ţ«Ţ««««««««««ŞÝŞÝŞŞŞŞŞŞŞŞŞŞ©Ü©Ü©©©©©©©©¨Ű¨Ű¨¨¨¨¨¨¨¨§Ú§§§§§§§§¦Ů¦¦¦¦¦¦¦¦ĄŘĄĄĄĄĄĄ¤×¤×¤¤¤¤¤¤ŁÖŁŁŁŁŁŁ˘Ő˘˘˘˘ˇÔˇˇˇˇˇˇ Ó    źŇźźźźźźžžžžžžťĐťťťťśśśś›Î››››šÍšššš™™™™Ë————–É––•Č••”Ç””””““““’’’’‘‘‘‘ŹŹŽÁŽŽŤŔŤŤŚżŚŚ‹‹‹‹ŠŠ‰Ľ‰‰‡‡‡‡††…………„„„„‚‚‚‚€ł€€~~}}}}||||{{zzzzyyyFxxwwwwvvvCuuttttssssrrr?qqq>ppoooonnnnmmmmllllkkkkk8jjj7iii6hhhhggggg4fff3eeeee2ddd1ccccc0bbbbb/aaaaa.`````-_____,^^^^^^^+]]]]]*]*\\\\\)\)[[[[[([(ZZZZZZZ'YYYYYYY&Y&XXXXXXX%X%WWWWWWWWW$VVVVVVVVV#V#UUUUUUUUUUU"TTTTTTTTTTTTT!SSSSSSSSSSSSS S RRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG¸¸¸¸¸¸¸¸¸¸···············································································¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨§§§§§§§ § ¦¦¦¦¦¦¦ ¦ ĄĄĄĄĄĄĄ ¤¤¤¤¤¤¤ ¤ ŁŁŁŁŁ Ł ˘˘˘˘˘ˇˇˇˇˇˇˇ     źźźźźźźžžžžžťťťťťśśśś›››››ššššš™™˙—ţ——–ý––––••••””””“ú““’ů’’‘ř‘‘÷ŹöŹŹŽŽŽŽŤŤŤŤŚŚŚŚ‹‹ŠńŠŠ‰‰‰‰‡‡†í††…………„„ę‚‚‚‚€€€€~~}}}}||||{{zzzzyyyyxxwwwwvvvvuuuttssssrrrrqqqqppp ooonnnmmmlllkkkkjjjjiiiiihhhggggffffe˙eeeeddddcýccccbübbbbaűaaaa`ú````_ů____^ř^^^^^^]÷]]]]]]\ö\\\\\\[ő[[[[[[ZôZZZZZZYóYYYYYYYYXňXXXXXXXXWńWWWWWWWWVđVđVVVVVVVVUďUďUUUUUUUUTîTîTTTTTTTTTTSíSíSSSSSSSSSSSSRěRěRRRRRRRRRRRRQëQëQëQQQQQQQQQQQQQQPęPęPPPPPPPPPPPPPPPPOéOéOéOOOOOOOOOOOOOOOOOONčNčNčNNNNNNNNNNNNNNNNNNNNMçMçMçMMMMMMMMMMMMMMMMMMMMMMMMMMLćLćLćLLLLLLLLLLLLLLLLLLLLLLLLLLLLKĺKĺKĺKĺKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJäJäJäJäJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIăIăIăIăIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHâHâHâHâHâHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGáGáGáGáGáGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG¸Q¸Q···············································································P·P·P·P¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶O¶O¶O¶O¶OµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµNµNµN´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´M´M´M´MłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłLłLłL˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛K˛K˛K±±±±±±±±±±±±±±±±±±±±±±±J±J±J°°°°°°°°°°°°°°°°°°°°°I°I°IŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻHŻH®®®®®®®®®®®®®®®®®G®G­­­­­­­­­­­­­­­F­F¬¬¬¬¬¬¬¬¬¬¬¬¬E¬E«««««««««««««DŞŞŞŞŞŞŞŞŞŞŞCŞC©©©©©©©©©©©B¨¨¨¨¨¨¨¨¨¨¨A§§§§§§§§§@¦¦¦¦¦¦¦¦¦?ĄĄĄĄĄĄĄĄĄ>¤¤¤¤¤¤¤=ŁŁŁŁŁŁŁ<˘˘˘˘˘˘˘;ˇˇˇˇˇˇˇ:     9źźźźźźź8žžžžž7ťťťťť6śśśśś5›››4ššššš3™™™™1————–––––/•••.”””-““““’’’’‘‘‘‘ŹŹŹŹŽŽŽŽŤŤŤ&ŚŚŚ%‹‹ŠŠŠŠ‰‰‰‰!‡‡††††…………„„‚‚‚‚€€€€~~}}}}||||{{zázzyyyyxxwŢwwvvvvuuuuttsÚssrŮrrqqqqppppoooonnnnmmmmllllkkkkjŃjjiĐiiiihhhhgÎggfÍffffeĚeedËddddcĘccccbÉbbbbaČaaaa`Ç````_Ć____^Ĺ^Ĺ^^^^]Ä]]]]]]\Ă\\\\\\[Â[[[[[[ZÁZZZZZZYŔYŔYYYYYYXżXżXXXXXXWľWľWWWWWWWWV˝VVVVVVVVVVUĽUUUUUUUUUUT»T»TTTTTTTTTTSşSşSSSSSSSSSSSSRąRąRRRRRRRRRRRRRRQ¸Q¸QQQQQQQQQQQQQQP·P·PPPPPPPPPPPPPPPPPPO¶O¶OOOOOOOOOOOOOOOOOOOONµNµNNNNNNNNNNNNNNNNNNNNNNM´M´M´MMMMMMMMMMMMMMMMMMMMMMMMMMLłLłLłLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLK˛K˛K˛KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJ±J±J±JJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJI°I°I°I°IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHŻHŻHŻHŻHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHG®G®G®G®G®GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG·········································································„·„·„·„·„¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ‚µ‚µ‚µ‚´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł€ł€ł€˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±~±~°°°°°°°°°°°°°°°°°°°°°°°}°}ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ|Ż|Ż|®®®®®®®®®®®®®®®®®{®{­­­­­­­­­­­­­­­z­z¬¬¬¬¬¬¬¬¬¬¬¬¬y¬y«««««««««««««x«xŞŞŞŞŞŞŞŞŞŞŞw©©©©©©©©©©©v©v¨¨¨¨¨¨¨¨¨u¨u§§§§§§§t§t¦¦¦¦¦¦¦¦¦sĄĄĄĄĄĄĄrĄr¤¤¤¤¤¤¤qŁŁŁŁŁŁŁp˘˘˘˘˘˘˘oˇˇˇˇˇˇˇn     mźźźźźźźlžžžžžkťťťťťjśśśśśi››››šššššg™™™™™f—————d–––c••••””””“““““`’’’_‘‘‘^]ŹŹŹ\ŽŽŽ[ŤŤŚŚŚŚ‹‹‹‹ŠŠŠŠ‰‰‰‰‡‡‡‡††††…………„„‚‚‚‚€€€€~~}}}}||||{{{{zzyyyyxxxxwwv©vvuuuuttttssssrrq¤qqpŁppo˘oonˇnnm mmlźllkžkkkkjjjjiiiih›hhhhggggf™ffffeeeed—ddddc–ccccb•bbbba”aaaa`“````_’______^‘^^^^]]]]]]]\Ź\\\\\\[Ž[[[[[[ZŤZZZZZZZZYŚYYYYYYYYX‹XXXXXXXXWŠWWWWWWWWV‰V‰VVVVVVVVUUUUUUUUUUUUT‡T‡TTTTTTTTTTS†S†SSSSSSSSSSSSR…R…RRRRRRRRRRRRRRQ„Q„QQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPO‚O‚OOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNM€M€M€MMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLK~K~K~KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJ}J}J}J}JJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJI|I|I|I|IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIH{H{H{H{H{HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGzGzGzGzGzGGGGGGGGGGGGGGGGGGGGGGGGGGGG········································································¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇ      źźźźźźźźžžžžžžťťťťťťśśśśśś››››››šššš™™™™™™————––––••••••””””““““’’’’‘‘‘‘ŹŹŹŹŽŽŽŽŤŤŤŤŚŚŚŚ‹‹‹‹ŠŠŠŠ‰‰‰‰‡‡‡‡††††…………„„‚‚‚‚€€€€~~}}}}||||{{{{zzyyyyxxxxwwwwvvuuuuttttssssrrrrqqqqppppoooonnnnmmmmllllkkkkjjjjiiiiiihhhhggggggffffeeeeeeddddddccccccbbbbbbaaaaaa``````________^^^^^^]]]]]]]]\\\\\\\\[[[[[[[[ZZZZZZZZZZYYYYYYYYYYXXXXXXXXXXWWWWWWWWWWWWVVVVVVVVVVVVUUUUUUUUUUUUUUTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGGGGGGGGGGGG······························································¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇ      źźźźźźźźžžžžžžťťťťťťśśśśśś››››››šššššš™™™™————––––––••••””””““““““’’’’‘‘‘‘ŹŹŹŹŽŽŽŽŤŤŤŤŚŚŚŚ‹‹‹‹ŠŠ‰‰‰‰‡‡‡‡††††……„„„„‚‚‚‚€€€€~~~~}}||||{{{{zzyyyyxxxxwwwwvvvvuuttttssssrrrrqqqqppppoooonnnnmmmmllllllkkkkjjjjiiiihhhhhhggggffffffeeeeeeddddddccccbbbbbbbbaaaaaa``````________^^^^^^]]]]]]]]\\\\\\\\[[[[[[[[[[ZZZZZZZZYYYYYYYYYYXXXXXXXXXXXXWWWWWWWWWWWWVVVVVVVVVVVVUUUUUUUUUUUUUUTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGGGGGGGGGGGGGGGGGG······················································¶é¶é¶é¶é¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µčµčµčµčµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´ç´ç´ç´ç´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łćłćłćłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛ĺ˛ĺ˛ĺ˛ĺ˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±ä±ä±ä±±±±±±±±±±±±±±±±±±±±±±±±°ă°ă°ă°°°°°°°°°°°°°°°°°°°°°°ŻâŻâŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®á®á®®®®®®®®®®®®®®®®­ŕ­ŕ­­­­­­­­­­­­­­­­¬ß¬ß¬¬¬¬¬¬¬¬¬¬¬¬«Ţ«Ţ««««««««««««ŞÝŞÝŞŞŞŞŞŞŞŞŞŞ©Ü©Ü©©©©©©©©©©¨Ű¨¨¨¨¨¨¨¨¨¨§Ú§§§§§§§§§§¦Ů¦¦¦¦¦¦¦¦ĄŘĄĄĄĄĄĄĄĄ¤×¤¤¤¤¤¤ŁÖŁÖŁŁŁŁŁŁ˘Ő˘˘˘˘˘˘ˇÔˇˇˇˇˇˇ Ó    źŇźźźźźźžŃžžžžťĐťťťťśĎśśśś›Î››››šÍšššš™Ě™™™™—Ę————––––•Č••••””””““““’Ĺ’’‘Ä‘‘ĂŹÂŹŹŹŹŽŽŽŽŤŤŚżŚŚ‹ľ‹‹Š˝ŠŠ‰‰‰‰‡‡‡‡††…¸……„„„„‚‚‚‚€€€€~~~~}}||||{{{{zzzGyyxxxxwwwwvvvvuuuBtttAsss@rrr?qqq>ppp=ooo¤¤¤¤¤¤¤¤¤=ŁŁŁŁŁŁŁ<˘˘˘˘˘˘˘;˘;ˇˇˇˇˇ:       9źźźźźźź8žžžžž7ťťťťť6śśśśśś››››››ššššš3™™™™™21———0–––––/•••.””””“““““,’’’+‘‘‘*ŹŹŹŹŽŽŽŽŤŤŤŤŚŚŚŚ‹‹‹‹ŠŠŠŠ‰‰‰‰!‡‡‡ ††…………„„„„‚‚‚€€€€~~~~}}||||{{{{zzzzyyyyxxwŢwwvvvvuuuuttttssssrrrrqqqqppppoooonnnnmÔmmlÓllllkkkkjjjjiĐiiiihhhhgÎggggfÍffffeeeedËddddcĘccccbÉbbbbaČaaaaaa`Ç````_Ć______^Ĺ^^^^]Ä]Ä]]]]]]\Ă\\\\\\[Â[[[[[[ZÁZÁZZZZZZYŔYYYYYYYYXżXżXXXXXXXXWľWWWWWWWWWWV˝VVVVVVVVVVUĽUĽUUUUUUUUUUT»T»TTTTTTTTTTTTSşSSSSSSSSSSSSSSRąRąRRRRRRRRRRRRRRQ¸Q¸QQQQQQQQQQQQQQQQP·P·P·PPPPPPPPPPPPPPPPPPO¶O¶OOOOOOOOOOOOOOOOOOOONµNµNµNNNNNNNNNNNNNNNNNNNNNNNNM´M´M´MMMMMMMMMMMMMMMMMMMMMMMMMMMMLłLłLłLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLK˛K˛K˛KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJ±J±J±JJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJI°I°I°I°IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHŻHŻHŻHŻHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH···························„·„·„·„¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ‚µ‚µ‚µ‚´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł€ł€ł€˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±~±~±~°°°°°°°°°°°°°°°°°°°°°°°}°}°}ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ|Ż|®®®®®®®®®®®®®®®®®®®{®{­­­­­­­­­­­­­­­­­z­z¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬y«««««««««««««««x«xŞŞŞŞŞŞŞŞŞŞŞwŞw©©©©©©©©©©©v©v¨¨¨¨¨¨¨¨¨u¨u§§§§§§§§§t§t¦¦¦¦¦¦¦¦¦sĄĄĄĄĄĄĄĄĄr¤¤¤¤¤¤¤¤¤qŁŁŁŁŁŁŁp˘˘˘˘˘˘˘oˇˇˇˇˇˇˇn       mźźźźźźźlžžžžžkťťťťťťśśśśśśśi›››››hšššš™™™™™fe————–––––c••••”””””a““““’’’’‘‘‘‘]ŹŹŹ\ŽŽŽ[ŤŤŤZŚŚŚY‹‹‹XŠŠŠW‰‰‰V‡‡‡‡††††…………„„„„‚‚€€€€~~~~}}|Ż||{{{{zzzzyyyyxxxxwwv©vvu¨uut§tts¦ssrĄrrq¤qqpŁppo˘oonˇnnnnmmmmllllkžkkjťjjjjiiiih›hhhhgšggggffffeeeeed—ddddc–ccccb•bbbba”aaaaaa`“````_’______^‘^^^^^^]]]]]]]\Ź\\\\\\[Ž[[[[[[[[ZŤZZZZZZYŚYŚYYYYYYYYX‹XXXXXXXXWŠWŠWWWWWWWWV‰V‰VVVVVVVVVVUUUUUUUUUUUUUT‡T‡TTTTTTTTTTS†S†SSSSSSSSSSSSSSR…R…RRRRRRRRRRRRRRQ„Q„QQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPO‚O‚OOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNM€M€M€MMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLK~K~K~K~KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJ}J}J}J}JJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJI|I|I|I|IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIH{H{H{H{H{HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH··························¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇ        źźźźźźźźžžžžžžťťťťťťťťśśśśśś››››››šššššš™™™™——————––––••••••””””““““““’’’’‘‘‘‘ŹŹŹŹŽŽŽŽŤŤŤŤŚŚŚŚ‹‹‹‹ŠŠŠŠ‰‰‰‰‡‡‡‡††††…………„„„„‚‚€€€€~~~~}}}}||{{{{zzzzyyyyxxxxwwwwvvvvuuuuttttssssrrrrqqqqppppoooonnnnmmmmllllllkkkkjjjjiiiiiihhhhhhggggffffffeeeeeeddddddccccccbbbbbbaaaaaaaa``````________^^^^^^^^]]]]]]]]\\\\\\\\[[[[[[[[[[ZZZZZZZZZZYYYYYYYYYYXXXXXXXXXXXXWWWWWWWWWWWWVVVVVVVVVVVVUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH················¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇ        źźźźźźźźžžžžžžťťťťťťťťśśśśśś››››››šššššš™™™™™™——————––––––••••””””””““““’’’’‘‘‘‘‘‘ŹŹŹŹŽŽŽŽŤŤŤŤŚŚŚŚ‹‹‹‹ŠŠŠŠ‰‰‰‰‡‡‡‡††††…………„„„„‚‚‚‚€€€€~~~~}}}}||{{{{zzzzyyyyxxxxwwwwvvvvuuuuttttssssrrrrqqqqppppoooonnnnnnmmmmllllkkkkkkjjjjiiiiiihhhhggggggffffffeeeeeeddddddccccccbbbbbbaaaaaa````````________^^^^^^^^]]]]]]]]\\\\\\\\[[[[[[[[[[ZZZZZZZZZZYYYYYYYYYYXXXXXXXXXXXXWWWWWWWWWWWWVVVVVVVVVVVVVVUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH······¶é¶é¶é¶é¶é¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µčµčµčµčµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´ç´ç´ç´ç´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łćłćłćłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛ĺ˛ĺ˛ĺ˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±ä±ä±±±±±±±±±±±±±±±±±±±±±±±±±±±±°ă°ă°ă°°°°°°°°°°°°°°°°°°°°°°ŻâŻâŻâŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®á®á®®®®®®®®®®®®®®®®®®­ŕ­ŕ­ŕ­­­­­­­­­­­­­­­­¬ß¬ß¬¬¬¬¬¬¬¬¬¬¬¬¬¬«Ţ«Ţ««««««««««««ŞÝŞÝŞŞŞŞŞŞŞŞŞŞŞŞ©Ü©©©©©©©©©©©©¨Ű¨¨¨¨¨¨¨¨¨¨§Ú§Ú§§§§§§§§¦Ů¦Ů¦¦¦¦¦¦¦¦ĄŘĄĄĄĄĄĄĄĄ¤×¤¤¤¤¤¤¤¤ŁÖŁŁŁŁŁŁŁŁ˘Ő˘˘˘˘˘˘ˇÔˇˇˇˇˇˇ Ó      źŇźźźźźźžŃžžžžťĐťťťťťťśĎśśśś›Î››››šÍšššš™Ě™™™™Ë————–É––––•Č••••””””“Ć““’Ĺ’’’’‘‘‘‘ĂŹÂŹŹŹŹŽŽŽŽŤŤŤŤŚŚŚŚ‹‹‹‹ŠŠŠŠ‰‰‰‰‡‡‡‡††††…………„„„„‚‚‚‚€€€€~~~~}}}}|||I{{zzzzyyyyxxxxwwwwvvvvuuuuttttssssrrrrqqqqppppp=ooo¤¤¤¤¤¤¤¤¤=ŁŁŁŁŁŁŁ<Ł<˘˘˘˘˘˘˘;ˇˇˇˇˇˇˇ:       9źźźźźźź8žžžžžžťťťťťťť6śśśśś5››››››šššššš™™™™™™1—————0––––•••••.”””””-“““,’’’’‘‘‘‘‘*)ŹŹŹŹŽŽŽŽŤŤŤŤŚŚŚŚŚ%‹‹‹$ŠŠŠ#‰‰‰"!‡‡‡ †††……„„„„‚‚‚‚€€€€~~~~}}}}||||{{{{zzyŕyyxßxxwŢwwvÝvvuÜuutŰttsÚssssrrrrqqqqppppoooonŐnnnnmmmmllllkŇkkkkjjjjiĐiiiihhhhhhggggfÍffffeĚeeeedËddddcĘccccbÉbbbbbbaČaaaa`Ç``````_Ć______^Ĺ^^^^^^]Ä]]]]]]\Ă\\\\\\\\[Â[[[[[[[[ZÁZZZZZZZZYŔYYYYYYYYXżXżXXXXXXXXWľWľWWWWWWWWWWV˝VVVVVVVVVVUĽUĽUUUUUUUUUUUUT»T»TTTTTTTTTTTTSşSşSSSSSSSSSSSSSSRąRąRRRRRRRRRRRRRRRRQ¸Q¸QQQQQQQQQQQQQQQQQQP·P·PPPPPPPPPPPPPPPPPPPPO¶O¶OOOOOOOOOOOOOOOOOOOOOONµNµNµNNNNNNNNNNNNNNNNNNNNNNNNNNM´M´M´MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLłLłLłLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLK˛K˛K˛K˛KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJ±J±J±JJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJI°I°I°I°IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHŻHŻHŻHŻHŻHHHHHHHHHHHHHHHHHHHHHHHHHH¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ‚µ‚µ‚µ‚´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł€ł€ł€˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±~±~±~°°°°°°°°°°°°°°°°°°°°°°°°°}°}ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ|Ż|®®®®®®®®®®®®®®®®®®®®®{®{­­­­­­­­­­­­­­­­­z­z¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬y¬y«««««««««««««««xŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞw©©©©©©©©©©©©©v©v¨¨¨¨¨¨¨¨¨¨¨u§§§§§§§§§§§t¦¦¦¦¦¦¦¦¦¦¦sĄĄĄĄĄĄĄĄĄrĄr¤¤¤¤¤¤¤q¤qŁŁŁŁŁŁŁp˘˘˘˘˘˘˘˘˘oˇˇˇˇˇˇˇn       mźźźźźźźlžžžžžžžkťťťťťjśśśśśś›››››››hšššššg™™™™™f——————–––––c••••”””””a““““’’’’’_‘‘‘‘ŹŹŹŹŹ\ŽŽŽŽŤŤŤŤŚŚŚŚ‹‹‹‹ŠŠŠŠ‰‰‰‰‡‡‡‡††††…………„„„„‚‚‚‚€€€€~~~~}}}}||||{{{{zzzzyyyyxxxxwwwwvvvvuuuuttttssssrrrrqqqqpŁppo˘oooonnnnmmmmlźllllkkkkjťjjjjiiiih›hhhhgšggggf™ffffeeeeed—ddddc–ccccb•bbbbbba”aaaa`“``````_’______^‘^^^^^^]]]]]]]\Ź\Ź\\\\\\[Ž[[[[[[[[ZŤZZZZZZZZYŚYYYYYYYYYYX‹XXXXXXXXXXWŠWWWWWWWWWWV‰V‰VVVVVVVVVVUUUUUUUUUUUUUUT‡TTTTTTTTTTTTTTS†S†SSSSSSSSSSSSSSR…R…RRRRRRRRRRRRRRRRQ„Q„QQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPO‚O‚OOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNM€M€MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLK~K~K~KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJ}J}J}J}JJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJI|I|I|I|IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIH{H{H{H{HHHHHHHHHHHHHHHHHH¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇ        źźźźźźźźžžžžžžžžťťťťťťśśśśśśśś››››››šššššš™™™™™™——————––––••••••””””““““““’’’’‘‘‘‘‘‘ŹŹŹŹŽŽŽŽŽŽŤŤŤŤŚŚŚŚ‹‹‹‹ŠŠŠŠ‰‰‰‰‡‡‡‡††††…………„„„„‚‚‚‚€€€€~~~~}}}}||||{{{{zzzzyyyyxxxxwwwwvvvvuuuuttttssssrrrrqqqqqqppppoooonnnnmmmmmmllllkkkkkkjjjjiiiiiihhhhhhggggggffffffeeeeeeddddddccccccbbbbbbbbaaaaaa````````________^^^^^^^^]]]]]]]]]]\\\\\\\\[[[[[[[[[[ZZZZZZZZZZYYYYYYYYYYYYXXXXXXXXXXXXWWWWWWWWWWWWWWVVVVVVVVVVVVVVUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHHHHHHHHHHHH¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇ        źźźźźźźźžžžžžžžžťťťťťťśśśśśśśś››››››šššššš™™™™™™——————––––––••••””””””““““’’’’’’‘‘‘‘ŹŹŹŹŽŽŽŽŤŤŤŤŚŚŚŚŚŚ‹‹‹‹ŠŠŠŠ‰‰‰‰‡‡‡‡††††…………„„„„‚‚‚‚€€€€~~~~}}}}||||{{{{zzzzyyyyxxxxwwwwvvvvuuuuttttssssssrrrrqqqqppppoooooonnnnmmmmllllllkkkkjjjjjjiiiiiihhhhhhggggffffffeeeeeeddddddddccccccbbbbbbbbaaaaaa````````________^^^^^^^^]]]]]]]]]]\\\\\\\\[[[[[[[[[[ZZZZZZZZZZZZYYYYYYYYYYYYXXXXXXXXXXXXWWWWWWWWWWWWVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIHHHHHHHH¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µčµčµčµčµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´ç´ç´ç´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łćłćłćłćłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛ĺ˛ĺ˛ĺ˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±ä±ä±ä±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°ă°ă°°°°°°°°°°°°°°°°°°°°°°°°°°ŻâŻâŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®á®á®á®®®®®®®®®®®®®®®®®®­ŕ­ŕ­ŕ­­­­­­­­­­­­­­­­¬ß¬ß¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬«Ţ«Ţ««««««««««««««ŞÝŞÝŞŞŞŞŞŞŞŞŞŞŞŞ©Ü©Ü©©©©©©©©©©©©¨Ű¨¨¨¨¨¨¨¨¨¨¨¨§Ú§§§§§§§§§§¦Ů¦Ů¦¦¦¦¦¦¦¦ĄŘĄĄĄĄĄĄĄĄĄĄ¤×¤¤¤¤¤¤¤¤ŁÖŁŁŁŁŁŁŁŁ˘Ő˘˘˘˘˘˘ˇÔˇÔˇˇˇˇˇˇ Ó      źŇźźźźźźžŃžžžžžžťĐťťťťśĎśśśśśś›Î››››šÍšššš™Ě™™™™Ë—Ę————–É––––•Č••••””””“Ć““““’’’’‘Ä‘‘‘‘ŹÂŹŹŹŹŽŽŽŽŤŤŤŤŚŚŚŚ‹ľ‹‹Š˝ŠŠŠŠ‰‰‰‰‡‡‡‡††††…………„„„„‚‚‚‚€€€€~~~~}}}}||||{{{{zzzzyyyyxxxxwwwwvvvvuuuutttttAssssrrrrqqqqppppp=oooonnnnmmmmm:llllkkkkk8jjjjj7iiiihhhhhhggggg4fffff3eeeee2ddddddd1ccccc0bbbbbbaaaaaaa.```````-_______,^^^^^^^+]]]]]]]]]*\\\\\\\)\)[[[[[[[([(ZZZZZZZZZ'YYYYYYYYYYY&XXXXXXXXXXX%WWWWWWWWWWW$W$VVVVVVVVVVV#V#UUUUUUUUUUUUU"U"TTTTTTTTTTTTTTT!SSSSSSSSSSSSSSSSS S RRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§ ¦¦¦¦¦¦¦¦¦¦¦ ĄĄĄĄĄĄĄĄĄ Ą ¤¤¤¤¤¤¤¤¤ ŁŁŁŁŁŁŁŁŁ ˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇ       źźźźźźźžžžžžžžťťťťťśśśśśśś›››››šššššš™™™™™™——————––––•ü••••”ű””””““““’ů’’’’‘‘‘‘÷ŹŹŹŹŽőŽŽŤôŤŤŤŤŚŚŚŚ‹‹‹‹ŠŠŠŠ‰đ‰‰ď‡‡‡‡††††…………„„„„‚‚‚‚€€€€~~~~}}}}||||{{{{zzzzyyyyxxxxwwwwvvvvvuuuttttssssrrrrqqqqq ppppoooonnnnnmmmmlllllkkkkkjjjjiiiiihhhhhgggggffffe˙eeeedţddddddccccccbübbbbaűaaaaaa`ú``````_ů______^ř^^^^^^]÷]]]]]]]]\ö\\\\\\\\[ő[[[[[[[[ZôZZZZZZZZYóYóYYYYYYYYXňXXXXXXXXXXWńWńWWWWWWWWWWVđVVVVVVVVVVVVUďUďUUUUUUUUUUUUTîTîTTTTTTTTTTTTSíSíSSSSSSSSSSSSSSSSRěRěRRRRRRRRRRRRRRRRQëQëQQQQQQQQQQQQQQQQQQPęPęPęPPPPPPPPPPPPPPPPPPPPOéOéOéOOOOOOOOOOOOOOOOOOOOOOOONčNčNNNNNNNNNNNNNNNNNNNNNNNNNNNNMçMçMçMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLćLćLćLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKĺKĺKĺKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJäJäJäJäJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIăIăIăIăIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶O¶O¶O¶OµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµNµNµNµN´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´M´M´MłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłLłLłL˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛K˛K˛K±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±J±J±J°°°°°°°°°°°°°°°°°°°°°°°°°°°I°IŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻHŻH®®®®®®®®®®®®®®®®®®®®®G®G­­­­­­­­­­­­­­­­­­­F­F¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬E¬E«««««««««««««««D«DŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞCŞC©©©©©©©©©©©B©B¨¨¨¨¨¨¨¨¨¨¨¨¨A§§§§§§§§§§§@§@¦¦¦¦¦¦¦¦¦?¦?ĄĄĄĄĄĄĄĄĄ>¤¤¤¤¤¤¤¤¤=¤=ŁŁŁŁŁŁŁ<Ł<˘˘˘˘˘˘˘;ˇˇˇˇˇˇˇˇˇ:       9źźźźźźź8žžžžžžž7ťťťťťťśśśśśśś5››››››ššššššš3™™™™™21—————0––––••••••”””””-““““’’’’’+‘‘‘‘)ŹŹŹŹŽŽŽŽŤŤŤŤŤ&ŚŚŚŚ‹‹‹‹ŠŠŠŠ‰‰‰‰!‡‡‡‡††††…………„„„„‚‚‚‚€€€€~~~~}}}}||||{{{{zzzzyyyyxxxxwŢwwvÝvvvvuuuuttttssssrrrrqŘqqqqppppoooonŐnnnnmmmmlÓllllkkkkkkjjjjiĐiiiihĎhhhhgÎggggfÍffffeĚeeeedËddddcĘccccccbÉbbbbaČaaaaaa`Ç``````_Ć______^Ĺ^^^^^^]Ä]Ä]]]]]]\Ă\\\\\\\\[Â[[[[[[[[ZÁZZZZZZZZZZYŔYYYYYYYYXżXżXXXXXXXXXXWľWWWWWWWWWWV˝V˝VVVVVVVVVVVVUĽUUUUUUUUUUUUUUT»TTTTTTTTTTTTTTSşSşSSSSSSSSSSSSSSSSRąRąRRRRRRRRRRRRRRRRQ¸Q¸QQQQQQQQQQQQQQQQQQQQP·P·PPPPPPPPPPPPPPPPPPPPPPO¶O¶OOOOOOOOOOOOOOOOOOOOOOOONµNµNµNNNNNNNNNNNNNNNNNNNNNNNNNNNNM´M´M´MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLłLłLłLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLK˛K˛K˛KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJ±J±J±JJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJI°I°I°I°IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ‚µ‚µ‚µ‚´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł€ł€ł€˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±~±~°°°°°°°°°°°°°°°°°°°°°°°°°°°}°}°}ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ|Ż|Ż|®®®®®®®®®®®®®®®®®®®®®{®{­­­­­­­­­­­­­­­­­­­z­z¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬y¬y«««««««««««««««««xŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞwŞw©©©©©©©©©©©©©v¨¨¨¨¨¨¨¨¨¨¨¨¨u¨u§§§§§§§§§§§t¦¦¦¦¦¦¦¦¦¦¦sĄĄĄĄĄĄĄĄĄĄĄr¤¤¤¤¤¤¤¤¤qŁŁŁŁŁŁŁŁŁp˘˘˘˘˘˘˘˘˘oˇˇˇˇˇˇˇˇˇn       mźźźźźźźlžžžžžžžkťťťťťťťjśśśśśi›››››››hšššššg™™™™™fe—————d–––––c•••••b””””“““““`’’’’‘‘‘‘‘^ŹŹŹŹŹ\ŽŽŽŽŤŤŤŤŚŚŚŚŚY‹‹‹‹ŠŠŠŠ‰‰‰‰‡‡‡‡‡T†††S…………„„„„‚‚‚‚€€€€~~~~}}}}||||{{{{zzzzyyyyx«xxxxwwwwvvvvuuuutttts¦ssrĄrrrrqqqqppppo˘oooonnnnm mmmmllllkžkkkkjťjjjjiiiiiihhhhhhggggggffffffeeeeeed—ddddc–ccccccbbbbbba”aaaaaa`“``````_’______^‘^^^^^^^^]]]]]]]\Ź\\\\\\\\[Ž[[[[[[[[ZŤZŤZZZZZZZZYŚYYYYYYYYYYX‹XXXXXXXXXXWŠWŠWWWWWWWWWWV‰V‰VVVVVVVVVVUUUUUUUUUUUUUUT‡T‡TTTTTTTTTTTTTTS†S†SSSSSSSSSSSSSSSSR…R…RRRRRRRRRRRRRRRRQ„Q„QQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPO‚O‚OOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNM€M€MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLK~K~K~KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJ}J}J}J}JJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJI|I|I|I|IIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇ        źźźźźźźźžžžžžžžžťťťťťťťťśśśśśś››››››››šššššš™™™™™™——————––––––••••••””””””““““’’’’’’‘‘‘‘ŹŹŹŹŽŽŽŽŽŽŤŤŤŤŚŚŚŚ‹‹‹‹‹‹ŠŠŠŠ‰‰‰‰‡‡‡‡††††………………„„„„‚‚‚‚€€€€~~~~}}}}||||{{{{zzzzyyyyyyxxxxwwwwvvvvuuuuttttttssssrrrrqqqqppppppoooonnnnnnmmmmllllllkkkkkkjjjjiiiiiihhhhhhggggggffffffeeeeeeeeddddddccccccbbbbbbbbaaaaaaaa````````________^^^^^^^^^^]]]]]]]]\\\\\\\\\\[[[[[[[[[[[[ZZZZZZZZZZYYYYYYYYYYYYXXXXXXXXXXXXXXWWWWWWWWWWWWWWVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIII¶¶¶¶¶¶¶¶µµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇ        źźźźźźźźžžžžžžžžťťťťťťťťśśśśśś››››››››šššššš™™™™™™™™——————––––––••••””””””““““““’’’’‘‘‘‘‘‘ŹŹŹŹŽŽŽŽŤŤŤŤŤŤŚŚŚŚ‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‡‡‡‡††††…………„„„„„„‚‚‚‚€€€€~~~~}}}}||||{{{{{{zzzzyyyyxxxxwwwwvvvvuuuuuuttttssssrrrrqqqqqqppppoooooonnnnmmmmmmllllkkkkkkjjjjjjiiiiiihhhhhhggggggffffffeeeeeeddddddddccccccbbbbbbbbaaaaaaaa````````________^^^^^^^^^^]]]]]]]]]]\\\\\\\\\\[[[[[[[[[[ZZZZZZZZZZZZYYYYYYYYYYYYXXXXXXXXXXXXWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIµčµčµčµčµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´ç´ç´ç´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łćłćłćłćłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛ĺ˛ĺ˛ĺ˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±ä±ä±ä±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°ă°ă°ă°°°°°°°°°°°°°°°°°°°°°°°°°°ŻâŻâŻâŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®á®á®®®®®®®®®®®®®®®®®®®®®®­ŕ­ŕ­­­­­­­­­­­­­­­­­­¬ß¬ß¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬«Ţ««««««««««««««««ŞÝŞÝŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©Ü©©©©©©©©©©©©©©¨Ű¨¨¨¨¨¨¨¨¨¨¨¨§Ú§Ú§§§§§§§§§§¦Ů¦¦¦¦¦¦¦¦¦¦ĄŘĄŘĄĄĄĄĄĄĄĄ¤×¤¤¤¤¤¤¤¤¤¤ŁÖŁŁŁŁŁŁŁŁ˘Ő˘˘˘˘˘˘˘˘ˇÔˇˇˇˇˇˇ Ó Ó      źŇźźźźźźžŃžžžžžžťĐťťťťťťśĎśśśśśś››››››šÍšššššš™™™™™™Ë—Ę————–É––––•Č••••””””””““““’Ĺ’’’’‘‘‘‘‘‘ŹÂŹŹŹŹŽŽŽŽŤŤŤŤŚżŚŚŚŚ‹‹‹‹ŠŠŠŠ‰Ľ‰‰‰‰‡‡‡‡††††…………„„„„¶‚‚‚‚€€€€~~~~}}}}||||{{{{{{zzzzyyyyxxxxwwwwvvvvvCuuuuttttssssrrrrr?qqqqppppp=oooonnnnn;mmmmlllll9kkkkk8jjjjj7iiiihhhhhhh5ggggg4fffff3eeeee2ddddddd1ccccc0bbbbbbb/aaaaaaa.```````-_______,^^^^^^^^^+]]]]]]]]]*\\\\\\\\\)[[[[[[[[[(ZZZZZZZZZZZ'YYYYYYYYYYY&XXXXXXXXXXX%X%WWWWWWWWWWW$W$VVVVVVVVVVVVV#UUUUUUUUUUUUUUU"U"TTTTTTTTTTTTTTT!T!SSSSSSSSSSSSSSSSS S RRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIIµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§ § ¦¦¦¦¦¦¦¦¦¦¦ ĄĄĄĄĄĄĄĄĄĄĄ ¤¤¤¤¤¤¤¤¤ ¤ ŁŁŁŁŁŁŁŁŁ ˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇ         źźźźźźźžžžžžžžťťťťťťťśśśśśśś›››››ššššššš™™™™˙—ţ————–ý––––•ü••••”ű””””“ú““““’’’’’’‘‘‘‘÷ŹŹŹŹŽőŽŽŽŽŤŤŤŤŚŚŚŚ‹ň‹‹‹‹ŠŠŠŠ‰‰‰‰ď‡‡‡‡††††…………„„„„‚‚‚‚€€€€~~~~}}}}|||||{{{{zzzzyyyyxxxxwwwwwvvvvuuuuttttsssss rrrrqqqqq ppppooooonnnnmmmmmlllllkkkkjjjjjjiiiiihhhhhhggggggffffffe˙eeeedţddddddccccccbübbbbbbaűaaaaaa`ú``````_ů______^ř^^^^^^^^]÷]]]]]]]]\ö\\\\\\\\[ő[[[[[[[[ZôZZZZZZZZZZYóYYYYYYYYYYXňXXXXXXXXXXXXWńWWWWWWWWWWWWVđVVVVVVVVVVVVUďUďUUUUUUUUUUUUUUTîTTTTTTTTTTTTTTTTSíSíSSSSSSSSSSSSSSSSRěRěRRRRRRRRRRRRRRRRRRQëQëQQQQQQQQQQQQQQQQQQQQPęPęPPPPPPPPPPPPPPPPPPPPPPOéOéOéOOOOOOOOOOOOOOOOOOOOOOOOOONčNčNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMçMçMçMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLćLćLćLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKĺKĺKĺKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJäJäJäJäJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIăIăIăIăIIIIIIIIIIIIIIIIIIIIIIIIIIµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµNµNµNµN´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´M´M´M´MłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłLłLłL˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛K˛K˛K±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±J±J±J°°°°°°°°°°°°°°°°°°°°°°°°°°°I°I°IŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻHŻH®®®®®®®®®®®®®®®®®®®®®®®G®G­­­­­­­­­­­­­­­­­­­­­F­F¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬E¬E«««««««««««««««D«DŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞCŞC©©©©©©©©©©©©©B©B¨¨¨¨¨¨¨¨¨¨¨¨¨A§§§§§§§§§§§§§@¦¦¦¦¦¦¦¦¦¦¦?¦?ĄĄĄĄĄĄĄĄĄ>Ą>¤¤¤¤¤¤¤¤¤=ŁŁŁŁŁŁŁŁŁŁŁ<˘˘˘˘˘˘˘˘˘;ˇˇˇˇˇˇˇ:         9źźźźźźź8žžžžžžž7ťťťťťťť6śśśśśśś5››››››ššššššš3™™™™™2——————––––––••••••””””””“““““,’’’’’+‘‘‘‘)ŹŹŹŹŽŽŽŽŽ'ŤŤŤŤŚŚŚŚ‹‹‹‹‹$ŠŠŠŠ‰‰‰‰!‡‡‡‡††††…………„„„„‚‚‚‚€€€€~~~~}}}}||||||{{{{zzzzyyyyxxxxwŢwwwwvvvvuuuuttttsÚssssrrrrqŘqqqqppppoooooonnnnmÔmmmmllllllkkkkjŃjjjjiĐiiiihĎhhhhgÎggggfÍffffffeeeeeedËddddcĘccccccbÉbbbbbbaČaaaaaa`Ç``````_Ć______^Ĺ^^^^^^^^]Ä]]]]]]]]\Ă\\\\\\\\[Â[[[[[[[[ZÁZÁZZZZZZZZYŔYŔYYYYYYYYXżXżXXXXXXXXXXWľWľWWWWWWWWWWV˝V˝VVVVVVVVVVVVUĽUĽUUUUUUUUUUUUT»T»TTTTTTTTTTTTTTTTSşSSSSSSSSSSSSSSSSSSRąRąRRRRRRRRRRRRRRRRRRQ¸Q¸QQQQQQQQQQQQQQQQQQQQP·P·PPPPPPPPPPPPPPPPPPPPPPPPO¶O¶OOOOOOOOOOOOOOOOOOOOOOOOOONµNµNµNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNM´M´MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLłLłLłLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLK˛K˛K˛K˛KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJ±J±J±JJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJI°I°I°I°IIIIIIIIIIIIIIIIIIµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ‚µ‚µ‚µ‚´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł€ł€ł€˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±~±~±~°°°°°°°°°°°°°°°°°°°°°°°°°°°°°}°}ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ|Ż|®®®®®®®®®®®®®®®®®®®®®®®{®{­­­­­­­­­­­­­­­­­­­­­z­z¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬y¬y«««««««««««««««««xŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞw©©©©©©©©©©©©©©©v¨¨¨¨¨¨¨¨¨¨¨¨¨u¨u§§§§§§§§§§§t§t¦¦¦¦¦¦¦¦¦¦¦sĄĄĄĄĄĄĄĄĄĄĄr¤¤¤¤¤¤¤¤¤¤¤qŁŁŁŁŁŁŁŁŁpŁp˘˘˘˘˘˘˘o˘oˇˇˇˇˇˇˇn         mźźźźźźźlžžžžžžžkťťťťťťťjśśśśśśśi›››››››hšššššg™™™™™™e—————d–––––c•••••b”””””a““““’’’’’’‘‘‘‘‘^ŹŹŹŹŹ\ŽŽŽŽŤŤŤŤŤZŚŚŚŚ‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‡‡‡‡‡T††††…………„„„„‚‚‚‚‚‚€€€€~~~~}}}}|Ż||||{{{{zzzzyyyyxxxxxxwwwwvvvvuuuut§ttttssssrrrrrrqqqqppppo˘oooonnnnnnmmmmlźllllkžkkkkjjjjjjiiiiiihhhhhhggggggf™ffffeeeeeeeddddddc–ccccccb•bbbbbba”aaaaaa`“``````_’______^‘^^^^^^^^]]]]]]]]]\Ź\\\\\\\\[Ž[[[[[[[[[[ZŤZZZZZZZZZZYŚYYYYYYYYYYX‹XXXXXXXXXXXXWŠWWWWWWWWWWWWV‰VVVVVVVVVVVVVVUUUUUUUUUUUUUUUT‡T‡TTTTTTTTTTTTTTS†S†SSSSSSSSSSSSSSSSSSR…R…RRRRRRRRRRRRRRRRRRQ„Q„QQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPO‚O‚O‚OOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNM€M€M€MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLK~K~K~KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJ}J}J}J}JJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJI|I|I|I|IIIIIIIIIIµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇ          źźźźźźźźžžžžžžžžťťťťťťťťśśśśśśśś››››››››šššššš™™™™™™™™——————––––––••••••””””””““““““’’’’’’‘‘‘‘ŹŹŹŹŽŽŽŽŽŽŤŤŤŤŚŚŚŚŚŚ‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‡‡‡‡††††††…………„„„„‚‚‚‚‚‚€€€€~~~~}}}}}}||||{{{{zzzzyyyyxxxxxxwwwwvvvvuuuuuuttttssssrrrrrrqqqqppppppoooonnnnnnmmmmmmllllllkkkkjjjjjjiiiiiihhhhhhggggggggffffffeeeeeeddddddddccccccccbbbbbbbbaaaaaaaa````````________^^^^^^^^^^]]]]]]]]]]\\\\\\\\\\[[[[[[[[[[[[ZZZZZZZZZZZZYYYYYYYYYYYYXXXXXXXXXXXXXXWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIIIIIIIIIµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇ          źźźźźźźźžžžžžžžžťťťťťťťťśśśśśśśś››››››››šššššššš™™™™™™——————––––––––••••••””””““““““’’’’’’‘‘‘‘‘‘ŹŹŹŹŹŹŽŽŽŽŽŽŤŤŤŤŚŚŚŚ‹‹‹‹‹‹ŠŠŠŠ‰‰‰‰‰‰‡‡‡‡††††††…………„„„„‚‚‚‚‚‚€€€€~~~~}}}}}}||||{{{{zzzzyyyyyyxxxxwwwwvvvvvvuuuuttttssssssrrrrqqqqqqppppoooooonnnnnnmmmmllllllkkkkkkjjjjjjiiiiiihhhhhhggggggffffffffeeeeeeddddddddccccccccbbbbbbbbaaaaaaaa````````________^^^^^^^^^^]]]]]]]]]]\\\\\\\\\\\\[[[[[[[[[[ZZZZZZZZZZZZYYYYYYYYYYYYYYXXXXXXXXXXXXXXWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJIIµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´ç´ç´ç´ç´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łćłćłćłćłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛ĺ˛ĺ˛ĺ˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±ä±ä±ä±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°ă°ă°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻâŻâŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®á®á®á®®®®®®®®®®®®®®®®®®®®®®­ŕ­ŕ­­­­­­­­­­­­­­­­­­­­¬ß¬ß¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬«Ţ«Ţ««««««««««««««««ŞÝŞÝŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©Ü©Ü©©©©©©©©©©©©©©¨Ű¨¨¨¨¨¨¨¨¨¨¨¨§Ú§Ú§§§§§§§§§§§§¦Ů¦¦¦¦¦¦¦¦¦¦ĄŘĄŘĄĄĄĄĄĄĄĄĄĄ¤×¤¤¤¤¤¤¤¤ŁÖŁÖŁŁŁŁŁŁŁŁ˘Ő˘˘˘˘˘˘˘˘ˇÔˇˇˇˇˇˇˇˇ Ó        źŇźźźźźźžŃžžžžžžžžťťťťťťťťśśśśśś›Î››››››šÍšššššš™Ě™™™™Ë——————––––––•Č••••”Ç””””““““““’’’’’’‘‘‘‘ĂŹŹŹŹŹŹŽŽŽŽŤŔŤŤŤŤŚŚŚŚ‹‹‹‹‹‹ŠŠŠŠ‰‰‰‰»‡‡‡‡††††…¸…………„„„„‚‚‚‚´€€€€~~~~}}}}}}||||{{{{zzzzyyyyyyxxxxwwwwvvvvvvuuuutttttAssssrrrrr?qqqqppppp=oooonnnnnnmmmmm:lllll9kkkkk8jjjjj7iiiii6hhhhh5ggggg4ffffffeeeeeee2ddddddd1ccccccbbbbbbbbaaaaaaaaa.```````-_______,_,^^^^^^^+]]]]]]]]]*\\\\\\\\\\\)[[[[[[[[[([(ZZZZZZZZZ'Z'YYYYYYYYYYY&XXXXXXXXXXXXX%WWWWWWWWWWWWW$W$VVVVVVVVVVVVV#V#UUUUUUUUUUUUUUU"TTTTTTTTTTTTTTTTT!T!SSSSSSSSSSSSSSSSSSS S RRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJµµµµµµµµµµµµµµµµµµµµµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§ § ¦¦¦¦¦¦¦¦¦¦¦ ĄĄĄĄĄĄĄĄĄĄĄ Ą ¤¤¤¤¤¤¤¤¤ ŁŁŁŁŁŁŁŁŁŁŁ ˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇ         źźźźźźźžžžžžžžžžťťťťťťťśśśśśś›››››››ššššššš™™™™™™—ţ————–ý––––•ü••••”ű””””“ú““““’ů’’’’‘ř‘‘‘‘ŹŹŹŹŽőŽŽŽŽŤŤŤŤŚóŚŚŚŚ‹‹‹‹ŠńŠŠŠŠ‰‰‰‰‡‡‡‡††††………………„„„„‚‚‚‚€€€€~~~~}}}}}}||||{{{{zzzzzyyyyxxxxwwwwwvvvvuuuuttttttssssrrrrrrqqqqq ppppooooonnnnnmmmmllllllkkkkkkjjjjjjiiiiiihhhhhhgggggggffffe˙eeeeeedţddddddccccccbübbbbbbaűaaaaaaaa`ú``````_ů________^ř^^^^^^]÷]]]]]]]]\ö\ö\\\\\\\\[ő[[[[[[[[[[ZôZZZZZZZZZZYóYYYYYYYYYYXňXXXXXXXXXXXXWńWńWWWWWWWWWWWWVđVVVVVVVVVVVVVVUďUUUUUUUUUUUUUUTîTîTTTTTTTTTTTTTTTTSíSíSSSSSSSSSSSSSSSSSSRěRRRRRRRRRRRRRRRRRRRRQëQëQQQQQQQQQQQQQQQQQQQQQQPęPęPPPPPPPPPPPPPPPPPPPPPPPPOéOéOéOOOOOOOOOOOOOOOOOOOOOOOOOONčNčNčNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMçMçMçMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLćLćLćLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKĺKĺKĺKĺKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJäJäJäJäJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJµµµµµµµµµµµµµµµµµµµNµNµNµN´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´M´M´M´MłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłLłLłL˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛K˛K˛K±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±J±J±J°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°I°I°IŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻHŻH®®®®®®®®®®®®®®®®®®®®®®®G®G®G­­­­­­­­­­­­­­­­­­­­­F­F¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬E¬E«««««««««««««««««D«DŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞC©©©©©©©©©©©©©©©B©B¨¨¨¨¨¨¨¨¨¨¨¨¨A¨A§§§§§§§§§§§§§@¦¦¦¦¦¦¦¦¦¦¦¦¦?ĄĄĄĄĄĄĄĄĄĄĄ>¤¤¤¤¤¤¤¤¤¤¤=ŁŁŁŁŁŁŁŁŁŁŁ<˘˘˘˘˘˘˘˘˘;ˇˇˇˇˇˇˇˇˇ:         9źźźźźźź8žžžžžžžžž7ťťťťťťť6śśśśśśś5››››››ššššššš3™™™™™™™21——————––––––••••••””””””““““““’’’’’’‘‘‘‘‘*)ŹŹŹŹŽŽŽŽŽ'ŤŤŤŤŚŚŚŚŚŚ‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰!‡‡‡‡††††……………„„„„‚‚‚‚€€€€~~~~}ä}}}}||||{{{{zzzzzzyyyyxxxxwwwwwwvvvvuuuutŰttttssssrŮrrrrqqqqqqppppoÖoooonnnnnnmmmmlÓllllkŇkkkkjŃjjjjiĐiiiihĎhhhhgÎggggggffffffeĚeeeeeedËddddcĘccccccbÉbbbbbbaČaaaaaaaa`Ç``````_Ć________^Ĺ^^^^^^]Ä]Ä]]]]]]]]\Ă\\\\\\\\[Â[[[[[[[[[[ZÁZZZZZZZZZZYŔYYYYYYYYYYXżXżXXXXXXXXXXXXWľWWWWWWWWWWWWV˝V˝VVVVVVVVVVVVUĽUĽUUUUUUUUUUUUUUT»T»TTTTTTTTTTTTTTTTSşSşSSSSSSSSSSSSSSSSRąRąRRRRRRRRRRRRRRRRRRRRQ¸Q¸QQQQQQQQQQQQQQQQQQQQQQP·P·PPPPPPPPPPPPPPPPPPPPPPPPPPO¶O¶OOOOOOOOOOOOOOOOOOOOOOOOOOOONµNµNµNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNM´M´MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLłLłLłLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLK˛K˛K˛KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJ±J±J±JJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJµµµµµµµµµµµ‚µ‚µ‚µ‚´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł€ł€ł€˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±~±~°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°}°}ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ|Ż|Ż|®®®®®®®®®®®®®®®®®®®®®®®{®{­­­­­­­­­­­­­­­­­­­­­­­z­z¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬y¬y«««««««««««««««««««xŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞwŞw©©©©©©©©©©©©©©©v¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨u§§§§§§§§§§§§§t§t¦¦¦¦¦¦¦¦¦¦¦s¦sĄĄĄĄĄĄĄĄĄĄĄr¤¤¤¤¤¤¤¤¤¤¤qŁŁŁŁŁŁŁŁŁpŁp˘˘˘˘˘˘˘˘˘oˇˇˇˇˇˇˇˇˇn         mźźźźźźźlžžžžžžžžžkťťťťťťťjśśśśśśśi›››››››hšššššš™™™™™™™f———————d–––––c•••••b”””””a“““““`’’’’’_‘‘‘‘ŹŹŹŹŹ\ŽŽŽŽŤŤŤŤŤŤŚŚŚŚŚY‹‹‹‹ŠŠŠŠŠW‰‰‰‰‡‡‡‡‡‡††††…………„„„„„„‚‚‚‚€€€€~~~~~~}}}}||||{{{{z­zzzzyyyyxxxxwŞwwwwvvvvu¨uuuutttts¦ssssrrrrq¤qqqqppppppoooonˇnnnnmmmmmmllllllkkkkkkjjjjjjiiiiiih›hhhhgšggggf™ffffffeeeeeeeddddddc–ccccccb•bbbbbba”aaaaaaaa````````_’________^‘^^^^^^^^]]]]]]]]]\Ź\\\\\\\\[Ž[[[[[[[[[[ZŤZZZZZZZZZZYŚYŚYYYYYYYYYYX‹XXXXXXXXXXXXWŠWŠWWWWWWWWWWWWV‰VVVVVVVVVVVVVVUUUUUUUUUUUUUUUUT‡T‡TTTTTTTTTTTTTTTTS†S†SSSSSSSSSSSSSSSSR…R…RRRRRRRRRRRRRRRRRRRRQ„Q„QQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPO‚O‚O‚OOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNM€M€M€MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLK~K~K~KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJ}J}J}J}JJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJµµµµµµµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇ          źźźźźźźźžžžžžžžžžžťťťťťťťťśśśśśśśś››››››››šššššššš™™™™™™——————––––––••••••””””””““““““’’’’’’‘‘‘‘‘‘ŹŹŹŹŽŽŽŽŽŽŤŤŤŤŤŤŚŚŚŚ‹‹‹‹‹‹ŠŠŠŠ‰‰‰‰‰‰‡‡‡‡‡‡††††…………„„„„„„‚‚‚‚€€€€~~~~~~}}}}||||{{{{{{zzzzyyyyxxxxxxwwwwvvvvvvuuuuttttttssssrrrrrrqqqqppppppoooooonnnnmmmmmmllllllkkkkkkjjjjjjiiiiiiiihhhhhhggggggffffffffeeeeeeddddddddccccccccbbbbbbbbaaaaaaaa``````````__________^^^^^^^^^^]]]]]]]]]]\\\\\\\\\\[[[[[[[[[[[[ZZZZZZZZZZZZZZYYYYYYYYYYYYXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJµµµµ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇ          źźźźźźźźžžžžžžžžžžťťťťťťťťśśśśśśśś››››››››šššššššš™™™™™™——————––––––––••••••””””””““““““’’’’’’‘‘‘‘‘‘ŹŹŹŹŹŹŽŽŽŽŽŽŤŤŤŤŚŚŚŚŚŚ‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‡‡‡‡††††††…………„„„„„„‚‚‚‚€€€€~~~~~~}}}}||||{{{{{{zzzzyyyyxxxxxxwwwwvvvvvvuuuuttttttssssrrrrrrqqqqqqppppoooooonnnnnnmmmmmmllllllkkkkkkjjjjjjiiiiiihhhhhhggggggggffffffeeeeeeeeddddddddccccccccbbbbbbbbaaaaaaaa``````````__________^^^^^^^^^^]]]]]]]]]]\\\\\\\\\\\\[[[[[[[[[[[[ZZZZZZZZZZZZYYYYYYYYYYYYYYXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJ´ç´ç´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łćłćłćłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛ĺ˛ĺ˛ĺ˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±ä±ä±ä±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°ă°ă°ă°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻâŻâŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®á®á®®®®®®®®®®®®®®®®®®®®®®®®­ŕ­ŕ­­­­­­­­­­­­­­­­­­­­­­¬ß¬ß¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬«Ţ«Ţ««««««««««««««««««ŞÝŞÝŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©Ü©©©©©©©©©©©©©©¨Ű¨Ű¨¨¨¨¨¨¨¨¨¨¨¨¨¨§Ú§§§§§§§§§§§§¦Ů¦Ů¦¦¦¦¦¦¦¦¦¦ĄŘĄŘĄĄĄĄĄĄĄĄĄĄ¤×¤¤¤¤¤¤¤¤¤¤ŁÖŁŁŁŁŁŁŁŁŁŁ˘Ő˘˘˘˘˘˘˘˘˘˘ˇÔˇˇˇˇˇˇˇˇ Ó        źŇźźźźźźžŃžžžžžžžžťĐťťťťťťśĎśśśśśś›Î››››››šÍšššššš™Ě™™™™™™—Ę——————––––––•Č••••”Ç””””“Ć““““’Ĺ’’’’‘Ä‘‘‘‘ĂŹŹŹŹŹŹŽŽŽŽŤŔŤŤŤŤŚŚŚŚŚŚ‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‡‡‡‡††††††…………„„„„¶‚‚‚‚€€€€~~~~~~}}}}||||{{{{{{zzzzyyyyyFxxxxwwwwwDvvvvuuuuuBttttsssss@rrrrqqqqqqppppp=oooooĄ>¤¤¤¤¤¤¤¤¤¤¤=ŁŁŁŁŁŁŁŁŁŁŁ<˘˘˘˘˘˘˘˘˘;ˇˇˇˇˇˇˇˇˇˇˇ:         9źźźźźźź8žžžžžžžžž7ťťťťťťť6śśśśśśśś››››››››šššššššš™™™™™™™21—————0––––––•••••••.”””””-“““““,’’’’’+‘‘‘‘‘*ŹŹŹŹŹŹŽŽŽŽŽŽŤŤŤŤŤ&ŚŚŚŚ‹‹‹‹‹‹ŠŠŠŠ‰‰‰‰‰‰!‡‡‡‡††††………………„„„„‚‚‚‚€€€€~~~~~~}}}}||||||{{{{zzzzyyyyyyxxxxwwwwwwvvvvuÜuuuuttttsÚssssrrrrrrqqqqp×ppppoÖoooonnnnnnmmmmmmllllllkkkkkkjjjjjjiĐiiiihĎhhhhhhggggggfÍffffffeĚeeeeeeddddddddccccccccbbbbbbbbaČaaaaaa`Ç````````_Ć________^Ĺ^^^^^^^^]Ä]]]]]]]]\Ă\\\\\\\\\\[Â[[[[[[[[[[ZÁZZZZZZZZZZZZYŔYYYYYYYYYYYYXżXXXXXXXXXXXXWľWWWWWWWWWWWWWWV˝V˝VVVVVVVVVVVVVVUĽUUUUUUUUUUUUUUUUT»T»TTTTTTTTTTTTTTTTSşSşSSSSSSSSSSSSSSSSSSRąRąRRRRRRRRRRRRRRRRRRRRRRQ¸Q¸QQQQQQQQQQQQQQQQQQQQQQP·P·P·PPPPPPPPPPPPPPPPPPPPPPPPPPO¶O¶OOOOOOOOOOOOOOOOOOOOOOOOOOOOOONµNµNµNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNM´M´MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLłLłLłLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLK˛K˛K˛KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJ±J±J±JJJJJJJJJJJJJJ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł€ł€ł€ł€˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±~±~±~°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°}°}°}ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ|Ż|®®®®®®®®®®®®®®®®®®®®®®®®®{®{®{­­­­­­­­­­­­­­­­­­­­­­­z­z¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬y¬y«««««««««««««««««««xŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞwŞw©©©©©©©©©©©©©©©©©v¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨u¨u§§§§§§§§§§§§§t¦¦¦¦¦¦¦¦¦¦¦¦¦s¦sĄĄĄĄĄĄĄĄĄĄĄr¤¤¤¤¤¤¤¤¤¤¤¤¤qŁŁŁŁŁŁŁŁŁŁŁp˘˘˘˘˘˘˘˘˘oˇˇˇˇˇˇˇˇˇˇˇn         mźźźźźźźźžžžžžžžžžkťťťťťťťťśśśśśśśśśi›››››››hšššššššg™™™™™™e——————–––––––c•••••b””””””““““““’’’’’’‘‘‘‘‘‘ŹŹŹŹŹ\ŽŽŽŽŽ[ŤŤŤŤŚŚŚŚŚŚ‹‹‹‹‹XŠŠŠŠ‰‰‰‰‰‰‡‡‡‡‡‡††††………………„„„„‚‚‚‚€€€€€€~~~~~~}}}}||||||{{{{zzzzy¬yyyyxxxxwŞwwwwvvvvvvuuuuttttttssssrĄrrrrqqqqqqppppppoooonˇnnnnm mmmmlźllllkžkkkkjťjjjjjjiiiiiih›hhhhgšggggggf™ffffffeeeeeed—ddddddc–ccccccb•bbbbbbbba”aaaaaa`“````````_’________^‘^^^^^^^^]]]]]]]]]\Ź\Ź\\\\\\\\[Ž[Ž[[[[[[[[ZŤZŤZZZZZZZZZZYŚYYYYYYYYYYYYX‹XXXXXXXXXXXXWŠWŠWWWWWWWWWWWWWWV‰VVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUT‡TTTTTTTTTTTTTTTTTTS†S†SSSSSSSSSSSSSSSSSSR…R…RRRRRRRRRRRRRRRRRRRRRRQ„Q„QQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPO‚O‚O‚OOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNM€M€M€MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLK~K~K~K~KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJ}J}J}J}JJJJJJ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇ          źźźźźźźźźźžžžžžžžžťťťťťťťťťťśśśśśśśś››››››››šššššššš™™™™™™™™————————––––––••••••””””””””““““““’’’’’’‘‘‘‘‘‘ŹŹŹŹŽŽŽŽŽŽŤŤŤŤŤŤŚŚŚŚŚŚ‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‰‰‡‡‡‡‡‡††††………………„„„„‚‚‚‚€€€€€€~~~~~~}}}}||||||{{{{zzzzzzyyyyxxxxxxwwwwvvvvvvuuuuttttttssssssrrrrqqqqqqppppppoooooonnnnnnmmmmmmllllllkkkkkkjjjjjjiiiiiiiihhhhhhggggggggffffffeeeeeeeeddddddddccccccccbbbbbbbbbbaaaaaaaa``````````__________^^^^^^^^^^]]]]]]]]]]]]\\\\\\\\\\\\[[[[[[[[[[[[ZZZZZZZZZZZZYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKJJJJJJ´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇ          źźźźźźźźźźžžžžžžžžťťťťťťťťťťśśśśśśśś››››››››šššššššš™™™™™™™™————————––––––••••••••””””””““““““’’’’’’‘‘‘‘‘‘ŹŹŹŹŹŹŽŽŽŽŽŽŤŤŤŤŤŤŚŚŚŚ‹‹‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‡‡‡‡††††††………………„„„„‚‚‚‚‚‚€€€€€€~~~~~~}}}}||||||{{{{zzzzzzyyyyxxxxxxwwwwvvvvvvuuuuuuttttssssssrrrrrrqqqqqqppppoooooonnnnnnmmmmmmllllllkkkkkkkkjjjjjjiiiiiihhhhhhhhggggggffffffffeeeeeeeeddddddddccccccccbbbbbbbbbbaaaaaaaa``````````__________^^^^^^^^^^]]]]]]]]]]]]\\\\\\\\\\\\[[[[[[[[[[[[ZZZZZZZZZZZZZZYYYYYYYYYYYYYYXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK´´´´´´´´´´´´´´´´´´´´´´´´´´´´łćłćłćłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛ĺ˛ĺ˛ĺ˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±ä±ä±ä±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°ă°ă°ă°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻâŻâŻâŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®á®á®®®®®®®®®®®®®®®®®®®®®®®®®®­ŕ­ŕ­­­­­­­­­­­­­­­­­­­­­­­­¬ß¬ß¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬«Ţ«Ţ««««««««««««««««««ŞÝŞÝŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©Ü©Ü©©©©©©©©©©©©©©¨Ű¨Ű¨¨¨¨¨¨¨¨¨¨¨¨¨¨§Ú§Ú§§§§§§§§§§§§¦Ů¦Ů¦¦¦¦¦¦¦¦¦¦¦¦ĄŘĄĄĄĄĄĄĄĄĄĄ¤×¤×¤¤¤¤¤¤¤¤¤¤ŁÖŁŁŁŁŁŁŁŁŁŁ˘Ő˘˘˘˘˘˘˘˘˘˘ˇÔˇˇˇˇˇˇˇˇ Ó Ó        źŇźźźźźźźźžŃžžžžžžťĐťťťťťťťťśĎśśśśśś›Î››››››šÍšššššš™Ě™™™™™™Ë——————–É––––––••••••”Ç””””“Ć““““““’’’’’’‘‘‘‘‘‘ŹŹŹŹŹŹŽŽŽŽŽŽŤŤŤŤŚżŚŚŚŚ‹‹‹‹‹‹ŠŠŠŠ‰Ľ‰‰‰‰‡‡‡‡††††††…………„·„„„„‚‚‚‚‚‚€€€€€€~~~~~~}}}}||||||{{{{zzzzzzyyyyxxxxxxwwwwwDvvvvuuuuuutttttAssssrrrrrrqqqqqqppppp=ooooo¤¤¤¤¤¤¤¤¤¤¤¤¤=ŁŁŁŁŁŁŁŁŁŁŁ<˘˘˘˘˘˘˘˘˘˘˘;ˇˇˇˇˇˇˇˇˇ:           9źźźźźźźźź8žžžžžžž7ťťťťťťťťť6śśśśśśś5››››››››šššššššš™™™™™™™™1———————0––––––•••••••.”””””-““““““’’’’’’‘‘‘‘‘‘ŹŹŹŹŹŹŽŽŽŽŽŽŤŤŤŤŤŤŚŚŚŚŚ%‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‰‰‡‡‡‡‡‡††††††…………„„„„„„‚‚‚‚‚‚€€€€€€~~~~~~}}}}|ă||||{{{{{{zzzzyyyyyyxxxxwwwwwwvvvvvvuuuuttttttssssssrrrrqŘqqqqp×ppppoÖoooonŐnnnnmÔmmmmlÓllllkŇkkkkkkjjjjjjiĐiiiiiihhhhhhgÎggggggffffffffeeeeeeeeddddddddccccccccbÉbbbbbbaČaaaaaaaa`Ç````````_Ć________^Ĺ^^^^^^^^]Ä]Ä]]]]]]]]\Ă\\\\\\\\\\[Â[[[[[[[[[[[[ZÁZZZZZZZZZZYŔYŔYYYYYYYYYYYYXżXXXXXXXXXXXXWľWľWWWWWWWWWWWWWWV˝VVVVVVVVVVVVVVVVUĽUĽUUUUUUUUUUUUUUUUT»T»TTTTTTTTTTTTTTTTTTSşSSSSSSSSSSSSSSSSSSSSRąRąRRRRRRRRRRRRRRRRRRRRRRQ¸Q¸QQQQQQQQQQQQQQQQQQQQQQQQQQP·P·PPPPPPPPPPPPPPPPPPPPPPPPPPPPO¶O¶OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONµNµNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNM´M´M´MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLłLłLłLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLK˛K˛K˛KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK´´´´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł€ł€ł€˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±~±~±~°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°}°}ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ|Ż|®®®®®®®®®®®®®®®®®®®®®®®®®®®®®{®{­­­­­­­­­­­­­­­­­­­­­­­z­z¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬y¬y«««««««««««««««««««x«xŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞwŞw©©©©©©©©©©©©©©©©©v¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨u¨u§§§§§§§§§§§§§§§t¦¦¦¦¦¦¦¦¦¦¦¦¦s¦sĄĄĄĄĄĄĄĄĄĄĄĄĄr¤¤¤¤¤¤¤¤¤¤¤q¤qŁŁŁŁŁŁŁŁŁŁŁp˘˘˘˘˘˘˘˘˘˘˘oˇˇˇˇˇˇˇˇˇn           mźźźźźźźźźlžžžžžžžžťťťťťťťťťjśśśśśśśś›››››››››hšššššššg™™™™™™™f———————d–––––––c•••••b””””””“““““““`’’’’’_‘‘‘‘‘^]ŹŹŹŹŹ\ŽŽŽŽŽ[ŤŤŤŤŤZŚŚŚŚ‹‹‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‰V‡‡‡‡‡‡†††††S…………„„„„„„‚‚‚‚‚‚€€€€€€~~~~~~}}}}}}||||{{{{{{zzzzyyyyyyxxxxwŞwwwwvvvvvvuuuut§ttttssssssrrrrrrqqqqqqppppppoooooonnnnnnmmmmmmllllllkžkkkkjťjjjjjjiiiiiih›hhhhhhggggggf™ffffffeeeeeeed—ddddddc–ccccccccb•bbbbbba”aaaaaaaa`“````````_’________^‘^^^^^^^^^^]]]]]]]]]\Ź\\\\\\\\\\[Ž[Ž[[[[[[[[[[ZŤZZZZZZZZZZZZYŚYYYYYYYYYYYYX‹XXXXXXXXXXXXXXWŠWWWWWWWWWWWWWWV‰V‰VVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUT‡TTTTTTTTTTTTTTTTTTS†S†SSSSSSSSSSSSSSSSSSSSR…R…RRRRRRRRRRRRRRRRRRRRRRQ„Q„QQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPO‚O‚O‚OOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNM€M€M€MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLK~K~K~KKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK´´´´´´´´łłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇ            źźźźźźźźźźžžžžžžžžžžťťťťťťťťśśśśśśśśśś››››››››šššššššš™™™™™™™™——————––––––––••••••””””””””““““““’’’’’’‘‘‘‘‘‘ŹŹŹŹŹŹŽŽŽŽŽŽŤŤŤŤŤŤŚŚŚŚŚŚ‹‹‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‡‡‡‡‡‡††††………………„„„„„„‚‚‚‚‚‚€€€€€€~~~~~~}}}}}}||||{{{{{{zzzzyyyyyyxxxxxxwwwwvvvvvvuuuuuuttttssssssrrrrrrqqqqqqppppppoooooonnnnnnmmmmmmllllllllkkkkkkjjjjjjiiiiiiiihhhhhhggggggggffffffffeeeeeeeeddddddddccccccccccbbbbbbbbaaaaaaaaaa``````````__________^^^^^^^^^^^^]]]]]]]]]]\\\\\\\\\\\\\\[[[[[[[[[[[[ZZZZZZZZZZZZZZYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇ            źźźźźźźźźźžžžžžžžžžžťťťťťťťťśśśśśśśśśś››››››››šššššššš™™™™™™™™————————––––––••••••••””””””““““““““’’’’’’‘‘‘‘‘‘ŹŹŹŹŹŹŽŽŽŽŽŽŤŤŤŤŤŤŚŚŚŚŚŚ‹‹‹‹‹‹ŠŠŠŠ‰‰‰‰‰‰‡‡‡‡‡‡††††………………„„„„‚‚‚‚‚‚€€€€€€~~~~~~}}}}}}||||{{{{{{zzzzyyyyyyxxxxxxwwwwwwvvvvuuuuuuttttttssssssrrrrrrqqqqppppppoooooonnnnnnnnmmmmmmllllllkkkkkkjjjjjjjjiiiiiihhhhhhhhggggggggffffffffeeeeeeeeddddddddccccccccbbbbbbbbbbaaaaaaaaaa``````````__________^^^^^^^^^^^^]]]]]]]]]]]]\\\\\\\\\\\\[[[[[[[[[[[[ZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛ĺ˛ĺ˛ĺ˛ĺ˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±ä±ä±ä±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°ă°ă°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻâŻâŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®á®á®á®®®®®®®®®®®®®®®®®®®®®®®®®®­ŕ­ŕ­­­­­­­­­­­­­­­­­­­­­­­­¬ß¬ß¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬«Ţ«Ţ««««««««««««««««««««ŞÝŞÝŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©Ü©©©©©©©©©©©©©©©©¨Ű¨Ű¨¨¨¨¨¨¨¨¨¨¨¨¨¨§Ú§Ú§§§§§§§§§§§§§§¦Ů¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄŘĄĄĄĄĄĄĄĄĄĄĄĄ¤×¤¤¤¤¤¤¤¤¤¤¤¤ŁÖŁŁŁŁŁŁŁŁŁŁ˘Ő˘˘˘˘˘˘˘˘˘˘ˇÔˇˇˇˇˇˇˇˇˇˇ Ó          źŇźźźźźźźźžŃžžžžžžžžťĐťťťťťťśĎśśśśśśśś›Î››››››šÍšššššš™Ě™™™™™™Ë—Ę——————–É––––––••••••”Ç””””””““““““’Ĺ’’’’‘Ä‘‘‘‘‘‘ŹŹŹŹŹŹŽŽŽŽŽŽŤŤŤŤŤŤŚŚŚŚŚŚ‹‹‹‹Š˝ŠŠŠŠ‰‰‰‰‰‰‡‡‡‡†ą††††………………„„„„‚‚‚‚‚‚€€€€€€~~~~~~}}}}}}||||{{{{{{zzzzzGyyyyxxxxxxwwwwwwvvvvuuuuuuttttttssssssrrrrrrqqqqq>ppppp=oooooĄ>¤¤¤¤¤¤¤¤¤¤¤=¤=ŁŁŁŁŁŁŁŁŁŁŁ<˘˘˘˘˘˘˘˘˘˘˘;ˇˇˇˇˇˇˇˇˇˇˇ:           9źźźźźźźźź8žžžžžžžžž7ťťťťťťťťť6śśśśśśś5››››››››ššššššššš3™™™™™™™21——————––––––––•••••••.””””””“““““““,’’’’’+‘‘‘‘‘‘ŹŹŹŹŹŹŽŽŽŽŽŽŤŤŤŤŤŤŚŚŚŚŚŚ‹‹‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‰"‡‡‡‡‡‡††††††……………„„„„‚‚‚‚‚‚€€€€€€~ĺ~~~~}}}}}}||||{â{{{{zzzzzzyyyyxßxxxxwwwwwwvvvvvvuuuuuuttttsÚssssrŮrrrrqŘqqqqp×ppppoÖoooonŐnnnnnnmmmmmmllllllkŇkkkkkkjjjjjjiĐiiiiiihhhhhhgÎggggggfÍffffffeĚeeeeeedËddddddddcĘccccccbÉbbbbbbbbaČaaaaaaaa`Ç````````_Ć________^Ĺ^^^^^^^^^^]Ä]]]]]]]]]]\Ă\\\\\\\\\\[Â[[[[[[[[[[[[ZÁZZZZZZZZZZZZYŔYYYYYYYYYYYYYYXżXXXXXXXXXXXXXXWľWWWWWWWWWWWWWWWWV˝VVVVVVVVVVVVVVVVUĽUĽUUUUUUUUUUUUUUUUUUT»TTTTTTTTTTTTTTTTTTTTSşSşSSSSSSSSSSSSSSSSSSSSRąRąRRRRRRRRRRRRRRRRRRRRRRRRQ¸Q¸QQQQQQQQQQQQQQQQQQQQQQQQQQP·P·PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPO¶O¶OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONµNµNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNM´M´M´MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLłLłLłLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLK˛K˛K˛KKKKKKKKKKłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł€ł€ł€˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±~±~°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°}°}°}ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ|Ż|Ż|®®®®®®®®®®®®®®®®®®®®®®®®®®®®®{®{­­­­­­­­­­­­­­­­­­­­­­­­­z­z¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬y¬y«««««««««««««««««««««x«xŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞwŞw©©©©©©©©©©©©©©©©©©©v¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨u§§§§§§§§§§§§§§§t§t¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦sĄĄĄĄĄĄĄĄĄĄĄĄĄr¤¤¤¤¤¤¤¤¤¤¤¤¤qŁŁŁŁŁŁŁŁŁŁŁŁŁp˘˘˘˘˘˘˘˘˘˘˘oˇˇˇˇˇˇˇˇˇˇˇn         m mźźźźźźźźźlžžžžžžžžžkťťťťťťťťťjśśśśśśśi›››››››››hšššššššg™™™™™™™fe———————d–––––––c••••••”””””””a““““““’’’’’’‘‘‘‘‘‘‘^]ŹŹŹŹŹŹŽŽŽŽŽŽŤŤŤŤŤŤŚŚŚŚŚŚ‹‹‹‹‹‹ŠŠŠŠŠW‰‰‰‰‡‡‡‡‡‡††††††…………„„„„„„‚‚‚‚‚O€€€€€€~~~~}}}}}}||||||{{{{zzzzzzyyyyyyxxxxwŞwwwwvvvvvvuuuuuuttttttssssssrrrrrrqqqqqqppppppoooooonnnnnnmmmmmmlźllllllkkkkkkjťjjjjjjiiiiiih›hhhhhhgšggggggf™ffffffeeeeeeed—ddddddddccccccccb•bbbbbbbba”aaaaaaaa`“````````_’________^‘^^^^^^^^^^]]]]]]]]]]]\Ź\\\\\\\\\\[Ž[Ž[[[[[[[[[[ZŤZZZZZZZZZZZZYŚYŚYYYYYYYYYYYYX‹X‹XXXXXXXXXXXXWŠWŠWWWWWWWWWWWWWWV‰V‰VVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUT‡T‡TTTTTTTTTTTTTTTTTTTTS†S†SSSSSSSSSSSSSSSSSSSSR…R…RRRRRRRRRRRRRRRRRRRRRRRRQ„Q„QQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPO‚O‚O‚OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNM€M€M€MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLK~K~K~KKKKłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇ          źźźźźźźźźźźźžžžžžžžžžžťťťťťťťťťťśśśśśśśś››››››››››šššššššš™™™™™™™™————————––––––––••••••••””””””““““““““’’’’’’‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŽŽŽŽŽŽŤŤŤŤŤŤŚŚŚŚŚŚ‹‹‹‹‹‹ŠŠŠŠ‰‰‰‰‰‰‡‡‡‡‡‡††††††…………„„„„„„‚‚‚‚€€€€€€~~~~}}}}}}||||||{{{{zzzzzzyyyyyyxxxxxxwwwwvvvvvvuuuuuuttttttssssssrrrrrrqqqqqqppppppoooooonnnnnnmmmmmmmmllllllkkkkkkkkjjjjjjiiiiiiiihhhhhhhhggggggggffffffffeeeeeeeeddddddddccccccccccbbbbbbbbbbaaaaaaaaaa``````````__________^^^^^^^^^^^^]]]]]]]]]]]]\\\\\\\\\\\\\\[[[[[[[[[[[[ZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLKKKKłłłłłłłłłłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇ          źźźźźźźźźźźźžžžžžžžžžžťťťťťťťťťťśśśśśśśśśś››››››››šššššššš™™™™™™™™™™————————––––––••••••••””””””””““““““’’’’’’‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŽŽŽŽŽŽŤŤŤŤŤŤŚŚŚŚŚŚ‹‹‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‰‰‡‡‡‡‡‡††††………………„„„„„„‚‚‚‚€€€€€€~~~~}}}}}}||||||{{{{zzzzzzyyyyyyxxxxxxwwwwwwvvvvuuuuuuttttttssssssrrrrrrqqqqqqppppppoooooooonnnnnnmmmmmmllllllllkkkkkkjjjjjjjjiiiiiiiihhhhhhggggggggffffffffeeeeeeeeeeddddddddccccccccccbbbbbbbbbbaaaaaaaaaa``````````__________^^^^^^^^^^^^]]]]]]]]]]]]\\\\\\\\\\\\\\[[[[[[[[[[[[[[ZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLłłłłłłłłłłłłłłłłłłłłłłłł˛ĺ˛ĺ˛ĺ˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±ä±ä±ä±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°ă°ă°ă°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻâŻâŻâŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®á®á®®®®®®®®®®®®®®®®®®®®®®®®®®®®­ŕ­ŕ­­­­­­­­­­­­­­­­­­­­­­­­­­¬ß¬ß¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬«Ţ«Ţ««««««««««««««««««««ŞÝŞÝŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©Ü©Ü©©©©©©©©©©©©©©©©¨Ű¨Ű¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§Ú§§§§§§§§§§§§§§§§¦Ů¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄŘĄĄĄĄĄĄĄĄĄĄĄĄ¤×¤×¤¤¤¤¤¤¤¤¤¤¤¤ŁÖŁŁŁŁŁŁŁŁŁŁ˘Ő˘Ő˘˘˘˘˘˘˘˘˘˘ˇÔˇˇˇˇˇˇˇˇˇˇ Ó        źŇźźźźźźźźźźžŃžžžžžžžžťĐťťťťťťťťśĎśśśśśśśś›Î››››››šÍšššššššš™™™™™™™™Ë—Ę——————–É––––––••••••”Ç””””””“Ć““““““’’’’’’‘‘‘‘‘‘ĂŹŹŹŹŹŹŽŽŽŽŽŽŤŤŤŤŤŤŚŚŚŚŚŚ‹‹‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‰‰‡‡‡‡†ą††††………………„„„„„„‚‚‚‚€€€€€€~~~~}}}}}}||||||{{{{{HzzzzyyyyyyxxxxxxwwwwwwvvvvvCuuuuuBtttttAsssss@rrrrr?qqqqq>ppppp=oooooonnnnnnmmmmmmm:llllllkkkkkkk8jjjjjjiiiiiiiihhhhhhh5ggggggg4fffffff3eeeeeeeee2ddddddd1ccccccccc0bbbbbbbbaaaaaaaaaaa.`````````-_________,^^^^^^^^^^^+]]]]]]]]]]]*\\\\\\\\\\\\\)[[[[[[[[[[[[[(ZZZZZZZZZZZZZ'YYYYYYYYYYYYYYY&XXXXXXXXXXXXXXX%X%WWWWWWWWWWWWWWW$W$VVVVVVVVVVVVVVVVV#V#UUUUUUUUUUUUUUUUUUU"U"TTTTTTTTTTTTTTTTTTT!T!SSSSSSSSSSSSSSSSSSSSSSS S RRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLłłłłłłłłłłłłłłłłłłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§ § ¦¦¦¦¦¦¦¦¦¦¦¦¦ ¦ ĄĄĄĄĄĄĄĄĄĄĄĄĄ ¤¤¤¤¤¤¤¤¤¤¤¤¤ ¤ ŁŁŁŁŁŁŁŁŁŁŁ ˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇ         źźźźźźźźźźźžžžžžžžžžťťťťťťťťťśśśśśśśśś››››››››ššššššššš™™™™™™˙—ţ——————–ý––––––•ü••••••””””””“ú““““““’ů’’’’‘ř‘‘‘‘‘‘ŹöŹŹŹŹŽőŽŽŽŽŤôŤŤŤŤŤŤŚŚŚŚŚŚ‹‹‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‰‰‡î‡‡‡‡††††††………………„„„„„„‚‚‚‚€€€€€€~~~~}}}}}}||||||{{{{{{zzzzyyyyyyxxxxxxwwwwwwvvvvvvuuuuuuttttttssssssrrrrrrqqqqqqppppppooooooonnnnnmmmmmmlllllllkkkkkkjjjjjjjiiiiiiihhhhhhhgggggggffffffe˙eeeeeeeeddddddddcýccccccccbbbbbbbbaűaaaaaaaaaa``````````_ů________^ř^^^^^^^^^^]÷]]]]]]]]]]\ö\ö\\\\\\\\\\[ő[[[[[[[[[[[[ZôZZZZZZZZZZZZYóYóYYYYYYYYYYYYXňXňXXXXXXXXXXXXXXWńWWWWWWWWWWWWWWWWVđVđVVVVVVVVVVVVVVVVUďUďUUUUUUUUUUUUUUUUUUTîTTTTTTTTTTTTTTTTTTTTSíSíSSSSSSSSSSSSSSSSSSSSSSRěRěRRRRRRRRRRRRRRRRRRRRRRRRQëQëQQQQQQQQQQQQQQQQQQQQQQQQQQQQPęPęPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOéOéOéOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONčNčNčNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMçMçMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLćLćLćLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLłłłłłłłłłłłLłLłLłL˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛K˛K±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±J±J±J°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°I°I°IŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻHŻH®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®G®G­­­­­­­­­­­­­­­­­­­­­­­­­­­F­F¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬E¬E«««««««««««««««««««««D«DŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞC©©©©©©©©©©©©©©©©©©©B©B¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨A§§§§§§§§§§§§§§§§§@¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦?ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ>¤¤¤¤¤¤¤¤¤¤¤¤¤=ŁŁŁŁŁŁŁŁŁŁŁŁŁ<˘˘˘˘˘˘˘˘˘˘˘;˘;ˇˇˇˇˇˇˇˇˇˇˇ:         9źźźźźźźźźźź8žžžžžžžžž7ťťťťťťťťť6śśśśśśśśś5›››››››››4ššššššš3™™™™™™™2————————––––––––•••••••.””””””““““““““’’’’’’‘‘‘‘‘‘‘*ŹŹŹŹŹŹŽŽŽŽŽŽŤŤŤŤŤŤŤ&ŚŚŚŚŚ%‹‹‹‹‹$ŠŠŠŠŠ#‰‰‰‰‰"‡‡‡‡‡‡††††††………………„„„„„„‚‚‚‚€€€€€€~~~~}}}}}}||||||{{{{{{zzzzyŕyyyyxxxxxxwwwwwwvvvvvvuuuuuuttttttssssssrrrrrrqqqqqqppppppoÖoooooonnnnnnmmmmmmlÓllllllkkkkkkjŃjjjjjjiĐiiiiiihhhhhhhhggggggggffffffffeĚeeeeeedËddddddddcĘccccccbÉbbbbbbbbaČaaaaaaaa`Ç``````````_Ć________^Ĺ^Ĺ^^^^^^^^]Ä]]]]]]]]]]]]\Ă\\\\\\\\\\[Â[[[[[[[[[[[[ZÁZÁZZZZZZZZZZZZYŔYYYYYYYYYYYYYYXżXXXXXXXXXXXXXXWľWľWWWWWWWWWWWWWWWWV˝VVVVVVVVVVVVVVVVVVUĽUUUUUUUUUUUUUUUUUUT»T»TTTTTTTTTTTTTTTTTTTTSşSşSSSSSSSSSSSSSSSSSSSSSSRąRąRRRRRRRRRRRRRRRRRRRRRRRRQ¸Q¸QQQQQQQQQQQQQQQQQQQQQQQQQQQQP·P·PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPO¶O¶OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONµNµNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNM´M´M´MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLłLłLłLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLłłłłł€ł€ł€˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±~±~±~°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°}°}ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ|Ż|®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®{®{­­­­­­­­­­­­­­­­­­­­­­­­­­­z­z¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬y¬y«««««««««««««««««««««x«xŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞwŞw©©©©©©©©©©©©©©©©©v©v¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨u¨u§§§§§§§§§§§§§§§t§t¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦sĄĄĄĄĄĄĄĄĄĄĄĄĄrĄr¤¤¤¤¤¤¤¤¤¤¤¤¤qŁŁŁŁŁŁŁŁŁŁŁŁŁp˘˘˘˘˘˘˘˘˘˘˘oˇˇˇˇˇˇˇˇˇˇˇˇˇn         mźźźźźźźźźźźlžžžžžžžžžkťťťťťťťťťjśśśśśśśśśi›››››››››hšššššššg™™™™™™™™e———————d–––––––c••••••”””””””a“““““““`’’’’’_‘‘‘‘‘‘]ŹŹŹŹŹŹŽŽŽŽŽŽŤŤŤŤŤŤŚŚŚŚŚŚ‹‹‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‰‰‡‡‡‡‡‡††††††………………„„„„„„‚‚‚‚‚‚€€€€€€~~~~}}}}}}||||||{{{{{{zzzzzzyyyyx«xxxxwŞwwwwvvvvvvuuuuuuttttttssssssrĄrrrrq¤qqqqpŁppppppoooooonnnnnnm mmmmmmllllllkžkkkkkkjjjjjjjjiiiiiih›hhhhhhgšggggggf™ffffffffeeeeeeeed—ddddddddccccccccb•bbbbbbbba”aaaaaaaa`“``````````_’__________^‘^^^^^^^^]]]]]]]]]]]]\Ź\\\\\\\\\\[Ž[Ž[[[[[[[[[[[[ZŤZZZZZZZZZZZZYŚYYYYYYYYYYYYYYX‹X‹XXXXXXXXXXXXXXWŠWWWWWWWWWWWWWWWWV‰V‰VVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUT‡T‡TTTTTTTTTTTTTTTTTTTTS†S†SSSSSSSSSSSSSSSSSSSSSSR…R…RRRRRRRRRRRRRRRRRRRRRRRRQ„Q„Q„QQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPO‚O‚O‚OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNM€M€M€MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLłłłł˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇ          źźźźźźźźźźźźžžžžžžžžžžťťťťťťťťťťśśśśśśśśśś››››››››››šššššššš™™™™™™™™™™————————––––––––••••••••””””””““““““““’’’’’’‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŽŽŽŽŽŽŤŤŤŤŤŤŚŚŚŚŚŚ‹‹‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‰‰‡‡‡‡‡‡††††††………………„„„„„„‚‚‚‚‚‚€€€€€€~~~~}}}}}}||||||{{{{{{zzzzzzyyyyyyxxxxxxwwwwvvvvvvuuuuuuttttttssssssssrrrrrrqqqqqqppppppoooooonnnnnnnnmmmmmmllllllllkkkkkkjjjjjjjjiiiiiiiihhhhhhhhggggggggffffffffeeeeeeeeeeddddddddccccccccccbbbbbbbbbbaaaaaaaaaa````````````____________^^^^^^^^^^^^]]]]]]]]]]]]\\\\\\\\\\\\\\[[[[[[[[[[[[[[ZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLLL˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇ            źźźźźźźźźźźźžžžžžžžžžžťťťťťťťťťťťťśśśśśśśś››››››››››šššššššššš™™™™™™™™————————––––––––••••••••””””””””““““““’’’’’’’’‘‘‘‘‘‘ŹŹŹŹŹŹŽŽŽŽŽŽŤŤŤŤŤŤŤŤŚŚŚŚŚŚ‹‹‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‰‰‡‡‡‡‡‡††††††………………„„„„‚‚‚‚‚‚€€€€€€~~~~}}}}}}||||||{{{{{{zzzzzzyyyyyyxxxxxxwwwwwwvvvvvvuuuuuuttttttssssssrrrrrrqqqqqqppppppppoooooonnnnnnmmmmmmmmllllllkkkkkkkkjjjjjjjjiiiiiiiihhhhhhhhggggggggffffffffeeeeeeeeeeddddddddccccccccccbbbbbbbbbbaaaaaaaaaa````````````____________^^^^^^^^^^^^]]]]]]]]]]]]\\\\\\\\\\\\\\[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLLLLLLLL˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±ä±ä±ä±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°ă°ă°ă°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻâŻâŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®á®á®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­ŕ­ŕ­ŕ­­­­­­­­­­­­­­­­­­­­­­­­­­¬ß¬ß¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬«Ţ«Ţ««««««««««««««««««««««ŞÝŞÝŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©Ü©Ü©©©©©©©©©©©©©©©©©©¨Ű¨Ű¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§Ú§Ú§§§§§§§§§§§§§§¦Ů¦Ů¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄŘĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤×¤¤¤¤¤¤¤¤¤¤¤¤ŁÖŁÖŁŁŁŁŁŁŁŁŁŁ˘Ő˘Ő˘˘˘˘˘˘˘˘˘˘ˇÔˇˇˇˇˇˇˇˇˇˇ Ó          źŇźźźźźźźźźźžŃžžžžžžžžťĐťťťťťťťťťťśĎśśśśśśśś››››››››šÍšššššššš™Ě™™™™™™Ë————————––––––––••••••••””””””“Ć““““““’’’’’’’’‘‘‘‘‘‘ŹÂŹŹŹŹŹŹŽŽŽŽŽŽŤŤŤŤŤŤŚżŚŚŚŚ‹ľ‹‹‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‰‰‡‡‡‡‡‡††††††…………„·„„„„‚‚‚‚‚‚€€€€€€~~~~~K}}}}||||||{{{{{{zzzzzzyyyyyyxxxxxxwwwwwwvvvvvvuuuuuuttttttssssssrrrrrrqqqqqqq>ppppppoooooonnnnnnn;mmmmmmlllllll9kkkkkkk8jjjjjjiiiiiiiihhhhhhhhggggggggg4fffffff3eeeeeeeeddddddddd1ccccccccc0bbbbbbbbb/aaaaaaaaa.```````````-___________,^^^^^^^^^^^+]]]]]]]]]]]*\\\\\\\\\\\\\)[[[[[[[[[[[[[(ZZZZZZZZZZZZZZZ'YYYYYYYYYYYYYYY&XXXXXXXXXXXXXXXXX%WWWWWWWWWWWWWWWWW$W$VVVVVVVVVVVVVVVVVVV#UUUUUUUUUUUUUUUUUUUUU"TTTTTTTTTTTTTTTTTTTTTTT!T!SSSSSSSSSSSSSSSSSSSSSSS S RRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLLLLLLLLLLLLLLLLL˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§ ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ ¤¤¤¤¤¤¤¤¤¤¤¤¤ ŁŁŁŁŁŁŁŁŁŁŁŁŁ ˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇ           źźźźźźźźźźźžžžžžžžžžžťťťťťťťťťťťśśśśśśśśś››››››››ššššššššš™™™™™™™™—ţ——————–ý––––––•ü••••••”ű””””””““““““’ů’’’’’’‘ř‘‘‘‘‘‘ŹŹŹŹŹŹŽőŽŽŽŽŽŽŤŤŤŤŤŤŚŚŚŚŚŚ‹‹‹‹‹‹ŠńŠŠŠŠ‰đ‰‰‰‰ď‡î‡‡‡‡†í††††…ě…………„„„„„„‚‚‚‚‚‚€€€€€€~~~~~~}}}}||||||{{{{{{zzzzzzyyyyyyxxxxxxwwwwwwvvvvvvuuuuuuttttttsssssss rrrrr qqqqqqppppppooooooonnnnnnmmmmmmmllllllkkkkkkkkjjjjjjjiiiiiiihhhhhhhggggggggffffffffe˙eeeeeedţddddddddcýccccccccbübbbbbbbbaűaaaaaaaa`ú``````````_ů__________^ř^^^^^^^^^^]÷]]]]]]]]]]\ö\\\\\\\\\\\\[ő[[[[[[[[[[[[ZôZôZZZZZZZZZZZZYóYYYYYYYYYYYYYYXňXňXXXXXXXXXXXXXXWńWńWWWWWWWWWWWWWWWWVđVVVVVVVVVVVVVVVVVVUďUďUUUUUUUUUUUUUUUUUUTîTîTTTTTTTTTTTTTTTTTTTTTTSíSSSSSSSSSSSSSSSSSSSSSSSSRěRěRRRRRRRRRRRRRRRRRRRRRRRRRRQëQëQQQQQQQQQQQQQQQQQQQQQQQQQQQQPęPęPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOéOéOéOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONčNčNčNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMçMçMçMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLćLćLćLLLLLLLLLLLLLL˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛K˛K˛K±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±J±J°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°I°IŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻHŻH®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®G®G­­­­­­­­­­­­­­­­­­­­­­­­­­­­­F­F¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬E¬E«««««««««««««««««««««««D«DŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞCŞC©©©©©©©©©©©©©©©©©©©B©B¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨A¨A§§§§§§§§§§§§§§§@§@¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦?¦?ĄĄĄĄĄĄĄĄĄĄĄĄĄ>Ą>¤¤¤¤¤¤¤¤¤¤¤¤¤=ŁŁŁŁŁŁŁŁŁŁŁŁŁ<˘˘˘˘˘˘˘˘˘˘˘˘˘;ˇˇˇˇˇˇˇˇˇˇˇ:           9źźźźźźźźźźź8žžžžžžžžžžž7ťťťťťťťťť6śśśśśśśśś5›››››››››4ššššššš3™™™™™™™™™21———————0–––––––/•••••••.”””””””-““““““’’’’’’’’‘‘‘‘‘‘‘*ŹŹŹŹŹŹŽŽŽŽŽŽŽ'ŤŤŤŤŤŤŚŚŚŚŚŚ‹‹‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‰‰‡‡‡‡‡‡††††††………………„„„„„„‚‚‚‚‚‚€€€€€€~~~~~~}}}}|ă||||{{{{{{zzzzzzyyyyyyxxxxxxwwwwwwvvvvvvuuuuuutŰttttsÚssssssrrrrrrqqqqqqppppppoÖoooooonnnnnnmÔmmmmmmllllllkŇkkkkkkjŃjjjjjjiĐiiiiiihĎhhhhhhgÎggggggfÍffffffffeeeeeeeedËddddddddcĘccccccccbÉbbbbbbbbaČaaaaaaaa`Ç``````````_Ć__________^Ĺ^^^^^^^^^^]Ä]]]]]]]]]]\Ă\Ă\\\\\\\\\\[Â[Â[[[[[[[[[[[[ZÁZZZZZZZZZZZZYŔYŔYYYYYYYYYYYYYYXżXXXXXXXXXXXXXXXXWľWWWWWWWWWWWWWWWWV˝V˝VVVVVVVVVVVVVVVVVVUĽUUUUUUUUUUUUUUUUUUUUT»T»TTTTTTTTTTTTTTTTTTTTSşSşSSSSSSSSSSSSSSSSSSSSSSSSRąRąRRRRRRRRRRRRRRRRRRRRRRRRRRQ¸Q¸QQQQQQQQQQQQQQQQQQQQQQQQQQQQP·P·P·PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPO¶O¶OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONµNµNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNM´M´M´MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLłLłLłLLLLLLLL˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±~±~±~°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°}°}°}ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ|Ż|Ż|®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®{®{­­­­­­­­­­­­­­­­­­­­­­­­­­­­­z­z¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬y¬y«««««««««««««««««««««««x«xŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞwŞw©©©©©©©©©©©©©©©©©©©v©v¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨u§§§§§§§§§§§§§§§§§t¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦sĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄr¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤qŁŁŁŁŁŁŁŁŁŁŁŁŁp˘˘˘˘˘˘˘˘˘˘˘˘˘oˇˇˇˇˇˇˇˇˇˇˇn           mźźźźźźźźźźźlžžžžžžžžžžžkťťťťťťťťťjśśśśśśśśśi›››››››››hšššššššš™™™™™™™™™f————————––––––––••••••••””””””””“““““““`’’’’’’’_‘‘‘‘‘‘]ŹŹŹŹŹŹŽŽŽŽŽŽŤŤŤŤŤŤŤZŚŚŚŚŚY‹‹‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‰‰‡‡‡‡‡‡††††††………………„„„„„„‚‚‚‚‚‚€€€€€€~~~~~~}}}}}}||||{®{{{{z­zzzzyyyyyyxxxxxxwŞwwwwv©vvvvu¨uuuuuuttttttssssssrrrrrrqqqqqqpŁppppppoooooonˇnnnnnnmmmmmmlźllllllkkkkkkkkjjjjjjjjiiiiiiiihhhhhhhhggggggggf™ffffffeeeeeeeeed—ddddddddccccccccccbbbbbbbbbba”aaaaaaaa`“``````````_’__________^‘^^^^^^^^^^]]]]]]]]]]]]]\Ź\\\\\\\\\\\\[Ž[[[[[[[[[[[[ZŤZZZZZZZZZZZZZZYŚYYYYYYYYYYYYYYX‹X‹XXXXXXXXXXXXXXWŠWŠWWWWWWWWWWWWWWWWV‰V‰VVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUT‡T‡TTTTTTTTTTTTTTTTTTTTS†S†SSSSSSSSSSSSSSSSSSSSSSSSR…R…RRRRRRRRRRRRRRRRRRRRRRRRRRQ„Q„QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPO‚O‚O‚OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNM€M€M€MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLLLLL˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇ            źźźźźźźźźźźźžžžžžžžžžžžžťťťťťťťťťťśśśśśśśśśś››››››››››šššššššššš™™™™™™™™————————––––––––••••••••””””””””““““““’’’’’’’’‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŽŽŽŽŽŽŤŤŤŤŤŤŚŚŚŚŚŚ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‰‰‡‡‡‡‡‡††††††………………„„„„„„‚‚‚‚‚‚€€€€€€~~~~~~}}}}}}||||||{{{{{{zzzzyyyyyyxxxxxxxxwwwwwwvvvvvvuuuuuuttttttssssssrrrrrrqqqqqqqqppppppoooooooonnnnnnmmmmmmmmllllllkkkkkkkkjjjjjjjjiiiiiiiihhhhhhhhggggggggggffffffffeeeeeeeeeeddddddddccccccccccbbbbbbbbbbbbaaaaaaaaaa````````````____________^^^^^^^^^^^^]]]]]]]]]]]]]]\\\\\\\\\\\\\\[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMLL˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇ            źźźźźźźźźźźźžžžžžžžžžžžžťťťťťťťťťťśśśśśśśśśś››››››››››šššššššššš™™™™™™™™————————––––––––••••••••””””””””““““““““’’’’’’’’‘‘‘‘‘‘ŹŹŹŹŹŹŽŽŽŽŽŽŽŽŤŤŤŤŤŤŚŚŚŚŚŚ‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‡‡‡‡‡‡††††††………………„„„„„„‚‚‚‚‚‚€€€€€€~~~~~~}}}}}}||||||{{{{{{zzzzzzyyyyyyxxxxxxwwwwwwvvvvvvuuuuuuttttttssssssssrrrrrrqqqqqqppppppppoooooonnnnnnnnmmmmmmllllllllkkkkkkkkjjjjjjjjiiiiiiiihhhhhhhhggggggggffffffffffeeeeeeeeddddddddddccccccccccbbbbbbbbbbbbaaaaaaaaaa````````````____________^^^^^^^^^^^^]]]]]]]]]]]]]]\\\\\\\\\\\\\\[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±ä±ä±ä±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°ă°ă°ă°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻâŻâŻâŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®á®á®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­ŕ­ŕ­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬ß¬ß¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬«Ţ«Ţ««««««««««««««««««««««««ŞÝŞÝŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©Ü©Ü©©©©©©©©©©©©©©©©©©¨Ű¨Ű¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§Ú§Ú§§§§§§§§§§§§§§§§¦Ů¦Ů¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄŘĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤×¤×¤¤¤¤¤¤¤¤¤¤¤¤ŁÖŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘Ő˘˘˘˘˘˘˘˘˘˘ˇÔˇÔˇˇˇˇˇˇˇˇˇˇ Ó          źŇźźźźźźźźźźžŃžžžžžžžžžžťĐťťťťťťťťśĎśśśśśśśś›Î››››››››šÍšššššššš™Ě™™™™™™™™—Ę——————–É––––––––••••••••””””””””““““““’Ĺ’’’’’’‘Ä‘‘‘‘‘‘ŹÂŹŹŹŹŹŹŽŽŽŽŽŽŤŔŤŤŤŤŤŤŚŚŚŚŚŚ‹‹‹‹‹‹ŠŠŠŠŠŠ‰Ľ‰‰‰‰»‡‡‡‡‡‡††††††………………„„„„„„‚‚‚‚‚‚€€€€€€~~~~~~}}}}}}||||||{{{{{{zzzzzzyyyyyyxxxxxxwwwwwwvvvvvvuuuuuutttttttAssssssrrrrrrqqqqqqq>ppppppoooooonnnnnnnnmmmmmmm:lllllll9kkkkkkjjjjjjjjiiiiiiiihhhhhhhhh5ggggggg4fffffffff3eeeeeee2ddddddddd1ccccccccc0bbbbbbbbbbaaaaaaaaaaa.```````````-___________,^^^^^^^^^^^+]]]]]]]]]]]]]*\\\\\\\\\\\\\)[[[[[[[[[[[[[([(ZZZZZZZZZZZZZ'Z'YYYYYYYYYYYYYYY&XXXXXXXXXXXXXXXXX%X%WWWWWWWWWWWWWWWWW$W$VVVVVVVVVVVVVVVVVVV#UUUUUUUUUUUUUUUUUUUUU"U"TTTTTTTTTTTTTTTTTTTTTTT!T!SSSSSSSSSSSSSSSSSSSSSSSSS S RRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§ ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ ŁŁŁŁŁŁŁŁŁŁŁŁŁ Ł ˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇ           źźźźźźźźźźźžžžžžžžžžžžťťťťťťťťťśśśśśśśśśś››››››››››ššššššššš™™™™™™™™˙————————––––––––•ü••••••”ű””””””“ú““““““’’’’’’’’‘‘‘‘‘‘÷ŹŹŹŹŹŹŽőŽŽŽŽŽŽŤŤŤŤŤŤŚóŚŚŚŚŚŚ‹‹‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‰‰‡î‡‡‡‡†í††††††………………„„„„„„‚‚‚‚‚‚€€€€€€~~~~~~}}}}}}||||||{{{{{{zzzzzzyyyyyyxxxxxxwwwwwwvvvvvvuuuuuuuttttttssssssrrrrrrr qqqqqqppppppooooooonnnnnnnmmmmmmllllllllkkkkkkkjjjjjjjiiiiiiihhhhhhhhgggggggggffffffffeeeeeeeedţddddddddcýccccccccbübbbbbbbbaűaaaaaaaaaa`ú``````````_ů__________^ř^^^^^^^^^^]÷]]]]]]]]]]]]\ö\\\\\\\\\\\\[ő[[[[[[[[[[[[[[ZôZZZZZZZZZZZZZZYóYYYYYYYYYYYYYYXňXňXXXXXXXXXXXXXXXXWńWWWWWWWWWWWWWWWWWWVđVVVVVVVVVVVVVVVVVVUďUďUUUUUUUUUUUUUUUUUUUUTîTîTTTTTTTTTTTTTTTTTTTTTTSíSíSSSSSSSSSSSSSSSSSSSSSSSSRěRěRRRRRRRRRRRRRRRRRRRRRRRRRRQëQëQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPęPęPęPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOéOéOéOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONčNčNčNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMçMçMçMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM˛˛˛˛˛˛˛˛˛˛˛K˛K˛K±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±J±J±J°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°I°I°IŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻHŻH®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®G®G­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­F­F¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬E¬E«««««««««««««««««««««««D«DŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞC©©©©©©©©©©©©©©©©©©©©©B©B¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨A¨A§§§§§§§§§§§§§§§§§@§@¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦?¦?ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ>¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤=ŁŁŁŁŁŁŁŁŁŁŁŁŁ<˘˘˘˘˘˘˘˘˘˘˘˘˘;ˇˇˇˇˇˇˇˇˇˇˇˇˇ:           9źźźźźźźźźźź8žžžžžžžžžžž7ťťťťťťťťť6śśśśśśśśśśś5›››››››››4šššššššš™™™™™™™™™21———————0–––––––/••••••••”””””””-“““““““,’’’’’’’+‘‘‘‘‘‘)ŹŹŹŹŹŹŽŽŽŽŽŽŽ'ŤŤŤŤŤŤŚŚŚŚŚŚŚ%‹‹‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‰‰‡‡‡‡‡‡†††††††………………„„„„„„‚‚‚‚‚‚€€€€€€~~~~~~}}}}}}||||||{{{{{{zzzzzzyyyyyyxxxxxxwwwwwwvvvvvvuÜuuuuuuttttttssssssrrrrrrrrqqqqqqppppppoÖoooooonnnnnnnnmmmmmmlÓllllllkŇkkkkkkjŃjjjjjjiĐiiiiiihĎhhhhhhgÎggggggggffffffffeĚeeeeeeeedËddddddddcĘccccccccbÉbbbbbbbbaČaaaaaaaaaa`Ç``````````_Ć__________^Ĺ^^^^^^^^^^]Ä]]]]]]]]]]]]\Ă\\\\\\\\\\\\[Â[Â[[[[[[[[[[[[ZÁZZZZZZZZZZZZZZYŔYŔYYYYYYYYYYYYYYXżXXXXXXXXXXXXXXXXWľWľWWWWWWWWWWWWWWWWV˝V˝VVVVVVVVVVVVVVVVVVUĽUĽUUUUUUUUUUUUUUUUUUUUT»T»TTTTTTTTTTTTTTTTTTTTTTSşSşSSSSSSSSSSSSSSSSSSSSSSSSRąRąRRRRRRRRRRRRRRRRRRRRRRRRRRQ¸Q¸QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQP·P·PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPO¶O¶OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONµNµNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNM´M´M´MMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM˛˛˛˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±~±~±~°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°}°}ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ|Ż|®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®{®{­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­z­z¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬y¬y«««««««««««««««««««««««x«xŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞwŞw©©©©©©©©©©©©©©©©©©©v©v¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨u§§§§§§§§§§§§§§§§§§§t¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦sĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄrĄr¤¤¤¤¤¤¤¤¤¤¤¤¤q¤qŁŁŁŁŁŁŁŁŁŁŁŁŁp˘˘˘˘˘˘˘˘˘˘˘˘˘oˇˇˇˇˇˇˇˇˇˇˇˇˇn           mźźźźźźźźźźźlžžžžžžžžžžžkťťťťťťťťťťśśśśśśśśśśśi›››››››››hšššššššššg™™™™™™™™e————————––––––––•••••••••b””””””““““““““’’’’’’’’‘‘‘‘‘‘‘^ŹŹŹŹŹŹŹŹŽŽŽŽŽŽŤŤŤŤŤŤŤŤŚŚŚŚŚŚ‹‹‹‹‹‹‹XŠŠŠŠŠŠ‰‰‰‰‰‰‡‡‡‡‡‡††††††…………………R„„„„„Q‚‚‚‚‚‚€€€€€€~~~~~~}}}}}}||||||{{{{{{zzzzzzyyyyyyxxxxxxwwwwwwv©vvvvvvuuuuuuttttttssssssrĄrrrrrrqqqqqqpŁppppppoooooonˇnnnnnnmmmmmmmmllllllllkkkkkkkkjjjjjjjjiiiiiiiihhhhhhhhgšggggggf™ffffffffeeeeeeeeed—ddddddddc–ccccccccb•bbbbbbbba”aaaaaaaaaa`“``````````_’__________^‘^^^^^^^^^^]]]]]]]]]]]]\Ź\\\\\\\\\\\\\\[Ž[[[[[[[[[[[[ZŤZŤZZZZZZZZZZZZZZYŚYYYYYYYYYYYYYYX‹X‹XXXXXXXXXXXXXXXXWŠWWWWWWWWWWWWWWWWWWV‰VVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUT‡TTTTTTTTTTTTTTTTTTTTTTTTS†SSSSSSSSSSSSSSSSSSSSSSSSSSR…R…RRRRRRRRRRRRRRRRRRRRRRRRRRQ„Q„Q„QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPO‚O‚O‚OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNM€M€M€MMMMMMMMMMMMMMMMMMMMMMMMMMMM˛˛˛˛±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇ            źźźźźźźźźźźźžžžžžžžžžžžžťťťťťťťťťťťťśśśśśśśśśś››››››››››šššššššššš™™™™™™™™™™——————————––––––––••••••••””””””””““““““““’’’’’’’’‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŽŽŽŽŽŽŤŤŤŤŤŤŤŤŚŚŚŚŚŚ‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‡‡‡‡‡‡††††††………………„„„„„„‚‚‚‚‚‚€€€€€€~~~~~~}}}}}}||||||{{{{{{zzzzzzyyyyyyxxxxxxwwwwwwwwvvvvvvuuuuuuttttttssssssssrrrrrrqqqqqqqqppppppoooooooonnnnnnmmmmmmmmllllllllkkkkkkkkjjjjjjjjiiiiiiiihhhhhhhhhhggggggggffffffffffeeeeeeeeeeddddddddddccccccccccbbbbbbbbbbaaaaaaaaaaaa````````````____________^^^^^^^^^^^^^^]]]]]]]]]]]]\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMMMMMMMM±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇ            źźźźźźźźźźźźžžžžžžžžžžžžťťťťťťťťťťťťśśśśśśśśśś››››››››››šššššššššš™™™™™™™™™™——————————––––––––••••••••””””””””““““““““’’’’’’’’‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŽŽŽŽŽŽŽŽŤŤŤŤŤŤŚŚŚŚŚŚŚŚ‹‹‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‡‡‡‡‡‡††††††………………„„„„„„‚‚‚‚‚‚‚‚€€€€€€~~~~~~}}}}}}||||||{{{{{{zzzzzzyyyyyyxxxxxxxxwwwwwwvvvvvvuuuuuuttttttttssssssrrrrrrrrqqqqqqppppppppoooooonnnnnnnnmmmmmmmmllllllllkkkkkkkkjjjjjjjjiiiiiiiihhhhhhhhggggggggggffffffffffeeeeeeeeddddddddddccccccccccccbbbbbbbbbbaaaaaaaaaaaa````````````____________^^^^^^^^^^^^^^]]]]]]]]]]]]]]\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNMMMMMMMMMMMMMMMMMMMMMM±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°ă°ă°ă°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻâŻâŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®á®á®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­ŕ­ŕ­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬ß¬ß¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬«Ţ«Ţ««««««««««««««««««««««««ŞÝŞÝŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©Ü©Ü©©©©©©©©©©©©©©©©©©©©¨Ű¨Ű¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§Ú§§§§§§§§§§§§§§§§§§¦Ů¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄŘĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤×¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁÖŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘Ő˘˘˘˘˘˘˘˘˘˘˘˘ˇÔˇˇˇˇˇˇˇˇˇˇˇˇ Ó          źŇźźźźźźźźźźžŃžžžžžžžžžžťĐťťťťťťťťťťśĎśśśśśśśś›Î››››››››šÍšššššššš™Ě™™™™™™™™Ë————————–É––––––––••••••••””””””””““““““““’’’’’’’’‘‘‘‘‘‘ĂŹÂŹŹŹŹŹŹŽŽŽŽŽŽŤŔŤŤŤŤŤŤŚŚŚŚŚŚ‹ľ‹‹‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‰‰»‡‡‡‡‡‡††††††………………„„„„„„‚‚‚‚‚‚‚‚€€€€€€~~~~~~}}}}}}||||||{{{{{{zzzzzzyyyyyyyFxxxxxxwwwwwwvvvvvvuuuuuuuBttttttssssssrrrrrrrrqqqqqqppppppppoooooooĄ>¤¤¤¤¤¤¤¤¤¤¤¤¤=¤=ŁŁŁŁŁŁŁŁŁŁŁŁŁ<˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘;ˇˇˇˇˇˇˇˇˇˇˇˇˇ:           9źźźźźźźźźźź8žžžžžžžžžžž7ťťťťťťťťťťť6śśśśśśśśśś›››››››››››4ššššššššš3™™™™™™™™1—————————0–––––––/••••••••””””””””““““““““’’’’’’’’‘‘‘‘‘‘‘‘)ŹŹŹŹŹŹŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤ&ŚŚŚŚŚŚ‹‹‹‹‹‹ŠŠŠŠŠŠŠ#‰‰‰‰‰‰‡‡‡‡‡‡‡ ††††††………………„„„„„„‚‚‚‚‚‚€€€€€€~~~~~~}}}}}}||||||{{{{{{zázzzzzzyyyyyyxxxxxxwwwwwwvÝvvvvvvuuuuuuttttttsÚssssssrrrrrrqŘqqqqqqppppppoÖoooooonŐnnnnnnmmmmmmmmllllllllkkkkkkkkjjjjjjjjiĐiiiiiihĎhhhhhhhhggggggggfÍffffffffeĚeeeeeeeedËddddddddcĘccccccccbÉbbbbbbbbbbaČaaaaaaaaaa`Ç``````````_Ć__________^Ĺ^^^^^^^^^^^^]Ä]]]]]]]]]]]]\Ă\\\\\\\\\\\\[Â[Â[[[[[[[[[[[[ZÁZÁZZZZZZZZZZZZZZYŔYYYYYYYYYYYYYYYYXżXXXXXXXXXXXXXXXXXXWľWWWWWWWWWWWWWWWWWWV˝V˝VVVVVVVVVVVVVVVVVVUĽUĽUUUUUUUUUUUUUUUUUUUUUUT»TTTTTTTTTTTTTTTTTTTTTTTTSşSşSSSSSSSSSSSSSSSSSSSSSSSSSSRąRąRRRRRRRRRRRRRRRRRRRRRRRRRRRRQ¸Q¸QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQP·P·PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPO¶O¶OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONµNµNµNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNM´M´M´MMMMMM±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±~±~±~°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°}°}°}ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ|Ż|®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®{®{­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­z­z­z¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬y¬y«««««««««««««««««««««««««x«xŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞwŞw©©©©©©©©©©©©©©©©©©©©©v©v¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨u¨u§§§§§§§§§§§§§§§§§t§t¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦sĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄr¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤qŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁp˘˘˘˘˘˘˘˘˘˘˘˘˘o˘oˇˇˇˇˇˇˇˇˇˇˇnˇn           mźźźźźźźźźźźlžžžžžžžžžžžkťťťťťťťťťťťjśśśśśśśśśśśi›››››››››hšššššššššg™™™™™™™™™f—————————d––––––––•••••••••b”””””””a“““““““`’’’’’’’_‘‘‘‘‘‘‘^ŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽ[ŤŤŤŤŤŤŚŚŚŚŚŚŚY‹‹‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‡‡‡‡‡‡†††††††S………………„„„„„„‚‚‚‚‚‚€€€€€€~~~~~~}}}}}}||||||{{{{{{{{zzzzzzyyyyyyxxxxxxwwwwwwwwvvvvvvuuuuuut§ttttttssssssrrrrrrrrqqqqqqpŁppppppoooooooonnnnnnm mmmmmmlźllllllkžkkkkkkjťjjjjjjjjiiiiiiiihhhhhhhhgšggggggggf™ffffffffeeeeeeeeeeddddddddddc–ccccccccb•bbbbbbbbbba”aaaaaaaaaa`“``````````_’__________^‘^^^^^^^^^^^^]]]]]]]]]]]]]\Ź\\\\\\\\\\\\\\[Ž[[[[[[[[[[[[[[ZŤZZZZZZZZZZZZZZYŚYŚYYYYYYYYYYYYYYX‹X‹XXXXXXXXXXXXXXXXWŠWŠWWWWWWWWWWWWWWWWWWV‰VVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUT‡T‡TTTTTTTTTTTTTTTTTTTTTTTTS†S†SSSSSSSSSSSSSSSSSSSSSSSSSSR…R…RRRRRRRRRRRRRRRRRRRRRRRRRRRRQ„Q„QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPO‚O‚O‚OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNM€M€M€±±±±±±±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇ              źźźźźźźźźźźźžžžžžžžžžžžžťťťťťťťťťťťťśśśśśśśśśśśś››››››››››šššššššššš™™™™™™™™™™————————––––––––––••••••••””””””””““““““““’’’’’’’’‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŽŽŽŽŽŽŤŤŤŤŤŤŤŤŚŚŚŚŚŚ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‡‡‡‡‡‡††††††……………………„„„„„„‚‚‚‚‚‚€€€€€€~~~~~~}}}}}}||||||{{{{{{{{zzzzzzyyyyyyxxxxxxwwwwwwwwvvvvvvuuuuuuuuttttttssssssrrrrrrrrqqqqqqqqppppppoooooooonnnnnnnnmmmmmmmmllllllllkkkkkkkkjjjjjjjjiiiiiiiihhhhhhhhhhggggggggggffffffffeeeeeeeeeeddddddddddddccccccccccbbbbbbbbbbbbaaaaaaaaaaaa````````````____________^^^^^^^^^^^^^^]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN±±±±±±±±±±±±±±±±±±±±±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇ              źźźźźźźźźźźźžžžžžžžžžžžžžžťťťťťťťťťťśśśśśśśśśśśś››››››››››šššššššššš™™™™™™™™™™——————————––––––––••••••••””””””””””““““““““’’’’’’’’‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŚŚŚŚŚŚ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‰‰‡‡‡‡‡‡††††††……………………„„„„„„‚‚‚‚‚‚€€€€€€~~~~~~}}}}}}||||||||{{{{{{zzzzzzyyyyyyxxxxxxxxwwwwwwvvvvvvuuuuuuuuttttttssssssssrrrrrrqqqqqqqqppppppppoooooonnnnnnnnmmmmmmmmllllllllkkkkkkkkjjjjjjjjjjiiiiiiiihhhhhhhhhhggggggggffffffffffeeeeeeeeeeddddddddddccccccccccccbbbbbbbbbbbbaaaaaaaaaaaa````````````____________^^^^^^^^^^^^^^]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN±±±±±±±±±±±±±±±±±±±±°ă°ă°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻâŻâŻâŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®á®á®á®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­ŕ­ŕ­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬ß¬ß¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬«Ţ«Ţ««««««««««««««««««««««««««ŞÝŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©Ü©Ü©©©©©©©©©©©©©©©©©©©©¨Ű¨Ű¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§Ú§Ú§§§§§§§§§§§§§§§§§§¦Ů¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄŘĄŘĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤×¤×¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁÖŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘Ő˘˘˘˘˘˘˘˘˘˘˘˘ˇÔˇˇˇˇˇˇˇˇˇˇˇˇ Ó            źŇźźźźźźźźźźźźžžžžžžžžžžžžťĐťťťťťťťťśĎśśśśśśśśśś›Î››››››››šÍšššššššššš™™™™™™™™™™—Ę————————–É––––––––••••••••””””””””“Ć““““““’Ĺ’’’’’’‘Ä‘‘‘‘‘‘ĂŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŤŤŤŤŤŤŚżŚŚŚŚŚŚ‹‹‹‹‹‹Š˝ŠŠŠŠŠŠ‰‰‰‰‰‰‡‡‡‡‡‡††††††………………„·„„„„„„‚‚‚‚‚‚€ł€€€€€€~~~~~~}}}}}}||||||||{{{{{{zzzzzzyyyyyyyFxxxxxxwwwwwwvvvvvvvCuuuuuutttttttAssssssrrrrrrr?qqqqqqppppppppooooooo¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤=¤=ŁŁŁŁŁŁŁŁŁŁŁŁŁ<Ł<˘˘˘˘˘˘˘˘˘˘˘˘˘;ˇˇˇˇˇˇˇˇˇˇˇˇˇ:             9źźźźźźźźźźźźź8žžžžžžžžžžž7ťťťťťťťťťťť6śśśśśśśśś5›››››››››››4ššššššššš3™™™™™™™™™21————————–––––––––/••••••••”””””””””-“““““““,’’’’’’’+‘‘‘‘‘‘‘*)ŹŹŹŹŹŹŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤ&ŚŚŚŚŚŚ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡††††††………………„„„„„„„„‚‚‚‚‚‚€€€€€€€€~~~~~~}}}}}}|ă||||||{{{{{{zzzzzzyyyyyyyyxxxxxxwwwwwwvÝvvvvvvuuuuuutŰttttttssssssrŮrrrrrrqqqqqqqqppppppoÖoooooonŐnnnnnnmÔmmmmmmlÓllllllkŇkkkkkkkkjjjjjjjjiĐiiiiiihĎhhhhhhhhgÎggggggggffffffffffeeeeeeeeeedËddddddddcĘccccccccccbbbbbbbbbbaČaaaaaaaaaa`Ç````````````_Ć__________^Ĺ^^^^^^^^^^^^]Ä]]]]]]]]]]]]]]\Ă\\\\\\\\\\\\\\[Â[[[[[[[[[[[[[[ZÁZZZZZZZZZZZZZZZZYŔYYYYYYYYYYYYYYYYXżXXXXXXXXXXXXXXXXXXWľWľWWWWWWWWWWWWWWWWWWV˝V˝VVVVVVVVVVVVVVVVVVVVUĽUUUUUUUUUUUUUUUUUUUUUUT»T»TTTTTTTTTTTTTTTTTTTTTTTTSşSşSSSSSSSSSSSSSSSSSSSSSSSSSSSSRąRąRRRRRRRRRRRRRRRRRRRRRRRRRRRRQ¸Q¸Q¸QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQP·P·PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPO¶O¶OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONµNµNµNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNN±±±±±~±~°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°}°}ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ|Ż|Ż|®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®{®{®{­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­z­z¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬y¬y«««««««««««««««««««««««««««x«xŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞw©©©©©©©©©©©©©©©©©©©©©©©v©v¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨u§§§§§§§§§§§§§§§§§§§t§t¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦sĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄrĄr¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤qŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁp˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘oˇˇˇˇˇˇˇˇˇˇˇˇˇn             mźźźźźźźźźźźźźlžžžžžžžžžžžkťťťťťťťťťťťjśśśśśśśśśś›››››››››››hšššššššššg™™™™™™™™™fe—————————d––––––––•••••••••b”””””””a““““““““’’’’’’’’‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽ[ŤŤŤŤŤŤŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹XŠŠŠŠŠŠ‰‰‰‰‰‰‰V‡‡‡‡‡‡‡T††††††………………„„„„„„„Q‚‚‚‚‚‚€€€€€€€€~~~~~~}}}}}}}}||||||{{{{{{zzzzzzy¬yyyyyyxxxxxxwwwwwwwwvvvvvvuuuuuuuuttttttssssssssrrrrrrq¤qqqqqqppppppppoooooooonnnnnnnnmmmmmmmmllllllllkkkkkkkkjťjjjjjjjjiiiiiiiih›hhhhhhhhggggggggf™ffffffffeeeeeeeeeeeddddddddddc–ccccccccb•bbbbbbbbbba”aaaaaaaaaa`“````````````_’__________^‘^^^^^^^^^^^^]]]]]]]]]]]]]]\Ź\\\\\\\\\\\\\\[Ž[[[[[[[[[[[[[[ZŤZZZZZZZZZZZZZZZZYŚYYYYYYYYYYYYYYYYX‹X‹XXXXXXXXXXXXXXXXXXWŠWWWWWWWWWWWWWWWWWWWWV‰VVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUT‡T‡TTTTTTTTTTTTTTTTTTTTTTTTS†S†SSSSSSSSSSSSSSSSSSSSSSSSSSSSR…R…RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQ„Q„QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPO‚O‚O‚OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNNNNN±±±±°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇ              źźźźźźźźźźźźźźžžžžžžžžžžžžťťťťťťťťťťťťśśśśśśśśśśśś››››››››››šššššššššš™™™™™™™™™™——————————––––––––––••••••••””””””””““““““““““’’’’’’’’‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŽŽŽŽŽŽŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚ‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‡‡‡‡‡‡††††††††………………„„„„„„‚‚‚‚‚‚€€€€€€€€~~~~~~}}}}}}}}||||||{{{{{{zzzzzzzzyyyyyyxxxxxxwwwwwwwwvvvvvvuuuuuuuuttttttssssssssrrrrrrrrqqqqqqppppppppoooooooonnnnnnnnmmmmmmmmllllllllkkkkkkkkkkjjjjjjjjiiiiiiiiiihhhhhhhhggggggggggffffffffffeeeeeeeeeeddddddddddddccccccccccbbbbbbbbbbbbaaaaaaaaaaaa``````````````____________^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNNNNNN°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇ              źźźźźźźźźźźźźźžžžžžžžžžžžžťťťťťťťťťťťťśśśśśśśśśśśś››››››››››šššššššššššš™™™™™™™™™™————————––––––––––••••••••••””””””””““““““““’’’’’’’’‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŚŚŚŚŚŚ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‡‡‡‡‡‡††††††††………………„„„„„„‚‚‚‚‚‚€€€€€€€€~~~~~~}}}}}}}}||||||{{{{{{zzzzzzzzyyyyyyxxxxxxxxwwwwwwvvvvvvvvuuuuuuttttttttssssssrrrrrrrrqqqqqqqqppppppppoooooooonnnnnnnnmmmmmmmmllllllllkkkkkkkkjjjjjjjjjjiiiiiiiihhhhhhhhhhggggggggggffffffffffeeeeeeeeeeddddddddddddccccccccccbbbbbbbbbbbbaaaaaaaaaaaa``````````````____________^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNNNNNNNN°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻâŻâŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®á®á®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­ŕ­ŕ­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬ß¬ß¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬«Ţ«Ţ««««««««««««««««««««««««««ŞÝŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©Ü©Ü©©©©©©©©©©©©©©©©©©©©©©¨Ű¨Ű¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§Ú§§§§§§§§§§§§§§§§§§¦Ů¦Ů¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄŘĄŘĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤×¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁÖŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘Ő˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇÔˇˇˇˇˇˇˇˇˇˇˇˇ Ó            źŇźźźźźźźźźźźźžŃžžžžžžžžžžťĐťťťťťťťťťťśĎśśśśśśśśśś›Î››››››››šÍšššššššššš™Ě™™™™™™™™Ë—Ę————————––––––––•Č••••••••”Ç””””””“Ć““““““““’’’’’’’’‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŤŤŤŤŤŤŚżŚŚŚŚŚŚ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠ‰Ľ‰‰‰‰‰‰‡ş‡‡‡‡‡‡††††††…¸………………„„„„„„‚‚‚‚‚‚€€€€€€€€~~~~~~}}}}}}}}||||||{{{{{{{Hzzzzzzyyyyyyxxxxxxxxwwwwwwvvvvvvvvuuuuuuttttttttsssssss@rrrrrrqqqqqqqqppppppppoooooooonnnnnnnnmmmmmmmmllllllllkkkkkkkkk8jjjjjjjjiiiiiiiii6hhhhhhhhh5ggggggggg4fffffffff3eeeeeeeee2ddddddddddccccccccccc0bbbbbbbbbbb/aaaaaaaaaaa.`````````````-___________,_,^^^^^^^^^^^^^+]]]]]]]]]]]]]*\\\\\\\\\\\\\\\)[[[[[[[[[[[[[[[[[(ZZZZZZZZZZZZZZZZZ'YYYYYYYYYYYYYYYYY&Y&XXXXXXXXXXXXXXXXXXX%WWWWWWWWWWWWWWWWWWWWW$VVVVVVVVVVVVVVVVVVVVVVV#UUUUUUUUUUUUUUUUUUUUUUUUU"U"TTTTTTTTTTTTTTTTTTTTTTTTT!T!SSSSSSSSSSSSSSSSSSSSSSSSSSSSS S RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNNNNNNNNNNNNNN°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§ ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ Ą ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ ¤ ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ ˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇ             źźźźźźźźźźźźźžžžžžžžžžžžťťťťťťťťťťťśśśśśśśśśśś››››››››››ššššššššššš™™™™™™™™˙—ţ————————–ý––––––––••••••••”ű””””””””““““““““’ů’’’’’’‘ř‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŽőŽŽŽŽŽŽŤôŤŤŤŤŤŤŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡††††††……………………„„„„„„‚‚‚‚‚‚€€€€€€€€~~~~~~}}}}}}}}||||||{{{{{{{{zzzzzzyyyyyyyxxxxxxwwwwwwwvvvvvvuuuuuuuttttttssssssssrrrrrrr qqqqqqq ppppppp ooooooonnnnnnnmmmmmmmlllllllkkkkkkkkjjjjjjjjjiiiiiiiiihhhhhhhhggggggggggffffffffffeeeeeeeeeedţddddddddcýccccccccccbübbbbbbbbbbaűaaaaaaaaaa`ú````````````_ů____________^ř^^^^^^^^^^^^]÷]]]]]]]]]]]]\ö\\\\\\\\\\\\\\[ő[ő[[[[[[[[[[[[[[ZôZZZZZZZZZZZZZZZZYóYYYYYYYYYYYYYYYYYYXňXXXXXXXXXXXXXXXXXXWńWńWWWWWWWWWWWWWWWWWWVđVđVVVVVVVVVVVVVVVVVVVVUďUďUUUUUUUUUUUUUUUUUUUUUUUUTîTTTTTTTTTTTTTTTTTTTTTTTTTTSíSíSSSSSSSSSSSSSSSSSSSSSSSSSSSSRěRěRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQëQëQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPęPęPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOéOéOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONčNčNNNNNNNNNNNN°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°I°I°IŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻHŻH®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®G®G­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­F­F¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬E¬E«««««««««««««««««««««««««««D«DŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞCŞC©©©©©©©©©©©©©©©©©©©©©©©B¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨A¨A§§§§§§§§§§§§§§§§§§§@§@¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦?ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ>¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤=ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ<Ł<˘˘˘˘˘˘˘˘˘˘˘˘˘;ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ:             9źźźźźźźźźźźźź8žžžžžžžžžžž7ťťťťťťťťťťť6śśśśśśśśśśś5›››››››››››4ššššššššš3™™™™™™™™™™——————————–––––––––/••••••••”””””””””-““““““““’’’’’’’’‘‘‘‘‘‘‘‘‘*)ŹŹŹŹŹŹŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹$ŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡††††††……………………„„„„„„‚‚‚‚‚‚€€€€€€€€~~~~~~}}}}}}}}||||||{{{{{{{{zzzzzzyyyyyyyyxxxxxxwwwwwwwwvvvvvvuuuuuuuuttttttsÚssssssrrrrrrrrqqqqqqqqppppppppoooooooonnnnnnnnmmmmmmmmllllllllkŇkkkkkkjŃjjjjjjjjiiiiiiiiiihhhhhhhhgÎggggggggfÍffffffffeĚeeeeeeeeeeddddddddddcĘccccccccccbÉbbbbbbbbbbaČaaaaaaaaaa`Ç````````````_Ć____________^Ĺ^^^^^^^^^^^^]Ä]]]]]]]]]]]]\Ă\Ă\\\\\\\\\\\\\\[Â[[[[[[[[[[[[[[ZÁZZZZZZZZZZZZZZZZYŔYŔYYYYYYYYYYYYYYYYXżXXXXXXXXXXXXXXXXXXXXWľWWWWWWWWWWWWWWWWWWWWV˝VVVVVVVVVVVVVVVVVVVVVVUĽUĽUUUUUUUUUUUUUUUUUUUUUUT»T»TTTTTTTTTTTTTTTTTTTTTTTTTTSşSşSSSSSSSSSSSSSSSSSSSSSSSSSSSSRąRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQ¸Q¸QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQP·P·P·PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPO¶O¶OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONµNµNµNNNNNN°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°}°}ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ|Ż|®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®{®{®{­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­z­z¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬y¬y«««««««««««««««««««««««««««x«xŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞwŞw©©©©©©©©©©©©©©©©©©©©©©©v©v¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨u§§§§§§§§§§§§§§§§§§§§§t¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦s¦sĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄr¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤qŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁp˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘oˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇn             mźźźźźźźźźźźźźlžžžžžžžžžžžkťťťťťťťťťťťjśśśśśśśśśśśi›››››››››››hšššššššššš™™™™™™™™™™™fe—————————d––––––––•••••••••b””””””””“““““““““`’’’’’’’_‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤZŚŚŚŚŚŚŚY‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰V‡‡‡‡‡‡‡T††††††…………………R„„„„„„‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~}°}}}}}}||||||{{{{{{{{zzzzzzyyyyyyyyxxxxxxwwwwwwwwvvvvvvu¨uuuuuuttttttttssssssrĄrrrrrrq¤qqqqqqppppppppoooooooonnnnnnnnm mmmmmmlźllllllllkkkkkkkkjťjjjjjjiśiiiiiiiih›hhhhhhhhgšggggggggf™ffffffffeeeeeeeeed—ddddddddddc–ccccccccccb•bbbbbbbbbba”aaaaaaaaaa`“````````````_’____________^‘^^^^^^^^^^^^]]]]]]]]]]]]]]]\Ź\\\\\\\\\\\\\\[Ž[[[[[[[[[[[[[[ZŤZŤZZZZZZZZZZZZZZZZYŚYYYYYYYYYYYYYYYYX‹X‹XXXXXXXXXXXXXXXXXXWŠWŠWWWWWWWWWWWWWWWWWWV‰V‰VVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUT‡T‡TTTTTTTTTTTTTTTTTTTTTTTTTTS†SSSSSSSSSSSSSSSSSSSSSSSSSSSSR…R…RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQ„Q„QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPO‚O‚O‚OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONNNN°°°°°°°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ              źźźźźźźźźźźźźźžžžžžžžžžžžžťťťťťťťťťťťťśśśśśśśśśśśś››››››››››››šššššššššššš™™™™™™™™™™——————————––––––––––••••••••””””””””””““““““““’’’’’’’’‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŤŤŤŤŤŤŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‡‡‡‡‡‡††††††††………………„„„„„„„„‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}||||||{{{{{{{{zzzzzzyyyyyyyyxxxxxxwwwwwwwwvvvvvvvvuuuuuuttttttttssssssssrrrrrrrrqqqqqqppppppppoooooooonnnnnnnnnnmmmmmmmmllllllllkkkkkkkkkkjjjjjjjjiiiiiiiiiihhhhhhhhhhggggggggggffffffffffeeeeeeeeeeddddddddddddccccccccccccbbbbbbbbbbbbaaaaaaaaaaaa``````````````______________^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOONN°°°°°°°°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ              źźźźźźźźźźźźźźžžžžžžžžžžžžťťťťťťťťťťťťťťśśśśśśśśśśśś››››››››››šššššššššššš™™™™™™™™™™——————————––––––––––••••••••••””””””””““““““““““’’’’’’’’‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‡‡‡‡‡‡††††††††………………„„„„„„„„‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}||||||{{{{{{{{zzzzzzyyyyyyyyxxxxxxxxwwwwwwvvvvvvvvuuuuuuttttttttssssssssrrrrrrrrqqqqqqqqppppppppoooooooonnnnnnnnmmmmmmmmllllllllllkkkkkkkkjjjjjjjjjjiiiiiiiihhhhhhhhhhggggggggggffffffffffffeeeeeeeeeeddddddddddddccccccccccbbbbbbbbbbbbbbaaaaaaaaaaaa``````````````______________^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO°°°°°°°°°°°°°°°°°°°°ŻâŻâŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®á®á®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­ŕ­ŕ­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬ß¬ß¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬«Ţ«Ţ««««««««««««««««««««««««««««ŞÝŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©Ü©©©©©©©©©©©©©©©©©©©©©©©©¨Ű¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§Ú§§§§§§§§§§§§§§§§§§§§¦Ů¦Ů¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄŘĄŘĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤×¤×¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁÖŁÖŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘Ő˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇÔˇˇˇˇˇˇˇˇˇˇˇˇˇˇ Ó            źŇźźźźźźźźźźźźžŃžžžžžžžžžžťĐťťťťťťťťťťťťśĎśśśśśśśśśś›Î››››››››šÍšššššššššš™Ě™™™™™™™™Ë——————————––––––––•Č••••••••”Ç””””””””““““““““’Ĺ’’’’’’‘Ä‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚ‹‹‹‹‹‹Š˝ŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‡ş‡‡‡‡‡‡††††††††………………„„„„„„„„‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}|||||||I{{{{{{zzzzzzzGyyyyyyxxxxxxxxwwwwwwvvvvvvvvuuuuuuuBtttttttAssssssrrrrrrrrqqqqqqqqppppppppoooooooonnnnnnnnmmmmmmmmm:llllllllkkkkkkkkk8jjjjjjjjiiiiiiiii6hhhhhhhhh5ggggggggg4ffffffffffeeeeeeeeeee2ddddddddddccccccccccc0bbbbbbbbbbbbaaaaaaaaaaaaa.`````````````-_____________,^^^^^^^^^^^^^+]]]]]]]]]]]]]]]*\\\\\\\\\\\\\\\)[[[[[[[[[[[[[[[[[(ZZZZZZZZZZZZZZZZZ'Z'YYYYYYYYYYYYYYYYY&Y&XXXXXXXXXXXXXXXXXXX%X%WWWWWWWWWWWWWWWWWWWWW$VVVVVVVVVVVVVVVVVVVVVVV#V#UUUUUUUUUUUUUUUUUUUUUUUUU"TTTTTTTTTTTTTTTTTTTTTTTTTTT!T!SSSSSSSSSSSSSSSSSSSSSSSSSSSSS S RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO°°°°°°°°°°°°°°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§ ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ             źźźźźźźźźźźźźžžžžžžžžžžžťťťťťťťťťťťťťśśśśśśśśśśś››››››››››ššššššššššš™™™™™™™™™™—ţ————————–ý––––––––••••••••••””””””””“ú““““““““’’’’’’’’‘‘‘‘‘‘‘‘÷ŹöŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŤŤŤŤŤŤŚóŚŚŚŚŚŚ‹ň‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡††††††…ě………………„„„„„„„„‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}||||||||{{{{{{zzzzzzzzyyyyyyxxxxxxxxwwwwwwwvvvvvvuuuuuuuuttttttttsssssss rrrrrrr qqqqqqq ppppppp ooooooonnnnnnnmmmmmmmmlllllllllkkkkkkkkjjjjjjjjjiiiiiiiiihhhhhhhhhgggggggggffffffffe˙eeeeeeeeeedţddddddddcýccccccccccbübbbbbbbbbbaűaaaaaaaaaaaa`ú````````````_ů____________^ř^^^^^^^^^^^^]÷]]]]]]]]]]]]]]\ö\\\\\\\\\\\\\\[ő[ő[[[[[[[[[[[[[[ZôZôZZZZZZZZZZZZZZZZYóYYYYYYYYYYYYYYYYYYXňXXXXXXXXXXXXXXXXXXXXWńWWWWWWWWWWWWWWWWWWWWVđVđVVVVVVVVVVVVVVVVVVVVVVUďUUUUUUUUUUUUUUUUUUUUUUUUTîTîTTTTTTTTTTTTTTTTTTTTTTTTTTSíSíSSSSSSSSSSSSSSSSSSSSSSSSSSSSRěRěRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQëQëQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPęPęPęPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOéOéOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO°°°°°°°°°°°I°IŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻHŻHŻH®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®G®G­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­F­F¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬E¬E«««««««««««««««««««««««««««««D«DŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞCŞC©©©©©©©©©©©©©©©©©©©©©©©©©B¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨A§§§§§§§§§§§§§§§§§§§§§@§@¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦?ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ>¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤=ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ<˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘;ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ:             9źźźźźźźźźźźźź8žžžžžžžžžžžžťťťťťťťťťťťťť6śśśśśśśśśśś5›››››››››››4šššššššššš™™™™™™™™™™™21—————————0–––––––––/•••••••••.””””””””“““““““““,’’’’’’’’‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹ(ŽŽŽŽŽŽŽ'ŤŤŤŤŤŤŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰"‡‡‡‡‡‡‡‡††††††……………………„„„„„„„‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}||||||||{{{{{{zzzzzzzzyyyyyyxßxxxxxxwwwwwwwwvvvvvvuÜuuuuuuttttttttssssssssrrrrrrrrqqqqqqqqppppppppoooooooonnnnnnnnmmmmmmmmlÓllllllllkkkkkkkkjŃjjjjjjjjiiiiiiiiiihhhhhhhhhhggggggggggffffffffffeĚeeeeeeeeeeddddddddddcĘccccccccccbÉbbbbbbbbbbaČaaaaaaaaaaaa`Ç````````````_Ć____________^Ĺ^^^^^^^^^^^^]Ä]]]]]]]]]]]]]]\Ă\\\\\\\\\\\\\\\\[Â[[[[[[[[[[[[[[[[ZÁZZZZZZZZZZZZZZZZYŔYYYYYYYYYYYYYYYYYYXżXżXXXXXXXXXXXXXXXXXXWľWľWWWWWWWWWWWWWWWWWWWWV˝VVVVVVVVVVVVVVVVVVVVVVUĽUĽUUUUUUUUUUUUUUUUUUUUUUUUT»T»TTTTTTTTTTTTTTTTTTTTTTTTTTSşSşSSSSSSSSSSSSSSSSSSSSSSSSSSSSRąRąRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQ¸Q¸Q¸QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQP·P·PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPO¶O¶OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO°°°°°}°}°}ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ|Ż|®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®{®{­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­z­z¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬y¬y«««««««««««««««««««««««««««««x«xŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞw©©©©©©©©©©©©©©©©©©©©©©©©©v©v¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨u¨u§§§§§§§§§§§§§§§§§§§§§t¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦s¦sĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄrĄr¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤qŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁpŁp˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘oˇˇˇˇˇˇˇˇˇˇˇˇˇnˇn             mźźźźźźźźźźźźźlžžžžžžžžžžžžžkťťťťťťťťťťťjśśśśśśśśśśśi›››››››››››hšššššššššššg™™™™™™™™™f——————————––––––––––•••••••••b”””””””””a““““““““’’’’’’’’’_‘‘‘‘‘‘‘^ŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠW‰‰‰‰‰‰‡‡‡‡‡‡‡‡††††††……………………„„„„„„‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}||||||||{{{{{{zzzzzzzzyyyyyyyyxxxxxxwwwwwwwwvvvvvvvvuuuuuut§ttttttssssssssrrrrrrrrqqqqqqqqppppppppoooooooonˇnnnnnnm mmmmmmmmllllllllkžkkkkkkkkjjjjjjjjiśiiiiiiiih›hhhhhhhhgšggggggggf™ffffffffffeeeeeeeeed—ddddddddddc–ccccccccccb•bbbbbbbbbba”aaaaaaaaaaaa`“````````````_’____________^‘^^^^^^^^^^^^]]]]]]]]]]]]]]]\Ź\Ź\\\\\\\\\\\\\\[Ž[[[[[[[[[[[[[[[[ZŤZZZZZZZZZZZZZZZZYŚYŚYYYYYYYYYYYYYYYYYYX‹XXXXXXXXXXXXXXXXXXXXWŠWWWWWWWWWWWWWWWWWWWWV‰V‰VVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUT‡T‡TTTTTTTTTTTTTTTTTTTTTTTTTTS†S†SSSSSSSSSSSSSSSSSSSSSSSSSSSSR…R…RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQ„Q„QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPO‚O‚O‚OOOOOOOOOOOOOOOOOOOOOOOOOO°°°°ŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                źźźźźźźźźźźźźźžžžžžžžžžžžžžžťťťťťťťťťťťťśśśśśśśśśśśś››››››››››››šššššššššššš™™™™™™™™™™——————————––––––––––••••••••””””””””””““““““““““’’’’’’’’‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡††††††……………………„„„„„„‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}||||||||{{{{{{zzzzzzzzyyyyyyyyxxxxxxwwwwwwwwvvvvvvvvuuuuuuuuttttttssssssssrrrrrrrrqqqqqqqqppppppppoooooooooonnnnnnnnmmmmmmmmllllllllllkkkkkkkkjjjjjjjjjjiiiiiiiiiihhhhhhhhhhggggggggggffffffffffffeeeeeeeeeeddddddddddddccccccccccccbbbbbbbbbbbbaaaaaaaaaaaaaa``````````````______________^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOOOOOŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                źźźźźźźźźźźźźźžžžžžžžžžžžžžžťťťťťťťťťťťťśśśśśśśśśśśś››››››››››››šššššššššššš™™™™™™™™™™™™——————————––––––––––••••••••••””””””””””““““““““’’’’’’’’’’‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‡‡‡‡‡‡††††††††……………………„„„„„„‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}||||||||{{{{{{{{zzzzzzyyyyyyyyxxxxxxxxwwwwwwvvvvvvvvuuuuuuuuttttttttssssssssrrrrrrrrqqqqqqqqppppppppoooooooonnnnnnnnmmmmmmmmmmllllllllkkkkkkkkkkjjjjjjjjjjiiiiiiiiiihhhhhhhhhhggggggggggffffffffffeeeeeeeeeeeeddddddddddddccccccccccccbbbbbbbbbbbbaaaaaaaaaaaaaa``````````````______________^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOOOOOOOŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®á®á®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­ŕ­ŕ­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬ß¬ß¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬«Ţ«Ţ««««««««««««««««««««««««««««««ŞÝŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©Ü©Ü©©©©©©©©©©©©©©©©©©©©©©©©¨Ű¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§Ú§Ú§§§§§§§§§§§§§§§§§§§§¦Ů¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄŘĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤×¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁÖŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘Ő˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇÔˇˇˇˇˇˇˇˇˇˇˇˇˇˇ Ó              źŇźźźźźźźźźźźźžŃžžžžžžžžžžžžťĐťťťťťťťťťťśĎśśśśśśśśśśśś››››››››››››šššššššššš™Ě™™™™™™™™™™Ë—Ę————————–É––––––––•Č••••••••”Ç””””””””“Ć““““““““’’’’’’’’‘Ä‘‘‘‘‘‘ĂŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‡ş‡‡‡‡‡‡††††††††……………………„„„„„„‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}||||||||{{{{{{{{zzzzzzyyyyyyyyxxxxxxxxwwwwwwvvvvvvvvuuuuuuuuttttttttssssssssrrrrrrrrqqqqqqqqppppppppoooooooonnnnnnnnn;mmmmmmmmlllllllll9kkkkkkkkjjjjjjjjjjiiiiiiiiiihhhhhhhhhhggggggggggg4fffffffff3eeeeeeeeeee2ddddddddddd1ccccccccccc0bbbbbbbbbbb/aaaaaaaaaaaaa.`````````````-_____________,^^^^^^^^^^^^^^^+]]]]]]]]]]]]]]]*\\\\\\\\\\\\\\\)[[[[[[[[[[[[[[[[[([(ZZZZZZZZZZZZZZZZZ'YYYYYYYYYYYYYYYYYYY&Y&XXXXXXXXXXXXXXXXXXX%X%WWWWWWWWWWWWWWWWWWWWW$W$VVVVVVVVVVVVVVVVVVVVVVV#V#UUUUUUUUUUUUUUUUUUUUUUUUU"U"TTTTTTTTTTTTTTTTTTTTTTTTTTTTT!SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS S RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOOOOOOOOOOOOOOOŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§ § ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ ¦ ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ Ą ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ               źźźźźźźźźźźźźžžžžžžžžžžžžžťťťťťťťťťťťśśśśśśśśśśśśś›››››››››››šššššššššš™™™™™™™™™™˙—ţ——————————––––––––––••••••••••””””””””“ú““““““““’ů’’’’’’’’‘‘‘‘‘‘‘‘ŹöŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰ď‡‡‡‡‡‡‡‡††††††††………………„ë„„„„„„‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}||||||||{{{{{{{{zzzzzzyyyyyyyyxxxxxxxxwwwwwwwvvvvvvvuuuuuuttttttttssssssssrrrrrrrrqqqqqqqqppppppppp ooooooonnnnnnnnmmmmmmmmmllllllllkkkkkkkkkjjjjjjjjjiiiiiiiiihhhhhhhhhggggggggggffffffffffe˙eeeeeeeeeeddddddddddddccccccccccccbbbbbbbbbbbbaűaaaaaaaaaaaa`ú````````````_ů____________^ř^^^^^^^^^^^^^^]÷]]]]]]]]]]]]]]\ö\\\\\\\\\\\\\\[ő[ő[[[[[[[[[[[[[[[[ZôZZZZZZZZZZZZZZZZYóYóYYYYYYYYYYYYYYYYYYXňXXXXXXXXXXXXXXXXXXXXWńWWWWWWWWWWWWWWWWWWWWWWVđVđVVVVVVVVVVVVVVVVVVVVVVUďUďUUUUUUUUUUUUUUUUUUUUUUUUTîTîTTTTTTTTTTTTTTTTTTTTTTTTTTSíSíSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRěRěRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQëQëQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPęPęPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOéOéOOOOOOOOOOOOŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻHŻH®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®G®G­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­F­F¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬E¬E«««««««««««««««««««««««««««««D«DŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞCŞC©©©©©©©©©©©©©©©©©©©©©©©©©B¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨A¨A§§§§§§§§§§§§§§§§§§§§§@¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦?ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ>¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤=ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ<˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘;ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ:               9źźźźźźźźźźźźź8žžžžžžžžžžžžž7ťťťťťťťťťťťťśśśśśśśśśśśśś5›››››››››››4ššššššššššš3™™™™™™™™™2———————————0–––––––––/•••••••••.””””””””““““““““““’’’’’’’’’+‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹ(ŽŽŽŽŽŽŽ'ŤŤŤŤŤŤŤ&ŚŚŚŚŚŚŚ%‹‹‹‹‹‹‹$ŠŠŠŠŠŠŠ#‰‰‰‰‰‰‡‡‡‡‡‡‡‡††††††††………………„„„„„„„„‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}|ă||||||{{{{{{{{zzzzzzyŕyyyyyyxxxxxxxxwwwwwwwwvvvvvvvvuuuuuutŰttttttsÚssssssrŮrrrrrrqŘqqqqqqp×ppppppppoooooooonnnnnnnnmÔmmmmmmmmllllllllkŇkkkkkkkkjŃjjjjjjjjiiiiiiiiiihhhhhhhhhhgÎggggggggfÍffffffffffeeeeeeeeeedËddddddddddcĘccccccccccbÉbbbbbbbbbbbbaČaaaaaaaaaaaa`Ç````````````_Ć____________^Ĺ^^^^^^^^^^^^^^]Ä]]]]]]]]]]]]]]\Ă\\\\\\\\\\\\\\\\[Â[[[[[[[[[[[[[[[[ZÁZZZZZZZZZZZZZZZZZZYŔYYYYYYYYYYYYYYYYYYXżXżXXXXXXXXXXXXXXXXXXWľWľWWWWWWWWWWWWWWWWWWWWWWV˝VVVVVVVVVVVVVVVVVVVVVVVVUĽUUUUUUUUUUUUUUUUUUUUUUUUUUT»T»TTTTTTTTTTTTTTTTTTTTTTTTTTSşSşSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRąRąRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQ¸Q¸QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQP·P·PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPO¶O¶OOOOOOOOŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ|Ż|®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®{®{­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­z­z¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬y¬y«««««««««««««««««««««««««««««x«xŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞwŞw©©©©©©©©©©©©©©©©©©©©©©©©©v©v¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨u§§§§§§§§§§§§§§§§§§§§§t§t¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦s¦sĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄr¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤q¤qŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁpŁp˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘oˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇn               mźźźźźźźźźźźźźlžžžžžžžžžžžžžkťťťťťťťťťťťťťjśśśśśśśśśśśi›››››››››››hšššššššššššg™™™™™™™™™™e—————————d––––––––––•••••••••b”””””””””a“““““““““`’’’’’’’’‘‘‘‘‘‘‘‘‘^]ŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡†††††††S………………„„„„„„„„‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}}}||||||{{{{{{{{zzzzzzzzyyyyyyx«xxxxxxwwwwwwwwvvvvvvvvuuuuuuuuttttttttssssssssrrrrrrrrqqqqqqqqppppppppoooooooonˇnnnnnnnnmmmmmmmmlźllllllllkkkkkkkkkkjjjjjjjjiśiiiiiiiih›hhhhhhhhhhggggggggggf™ffffffffeeeeeeeeeeed—ddddddddddc–ccccccccccb•bbbbbbbbbbbba”aaaaaaaaaaaa``````````````_’____________^‘^^^^^^^^^^^^^^]]]]]]]]]]]]]]]\Ź\\\\\\\\\\\\\\\\[Ž[[[[[[[[[[[[[[[[ZŤZZZZZZZZZZZZZZZZZZYŚYYYYYYYYYYYYYYYYYYYYX‹XXXXXXXXXXXXXXXXXXXXWŠWWWWWWWWWWWWWWWWWWWWWWV‰V‰VVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUT‡TTTTTTTTTTTTTTTTTTTTTTTTTTTTS†S†SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSR…R…RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQ„Q„QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPO‚O‚O‚OOŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                źźźźźźźźźźźźźźžžžžžžžžžžžžžžťťťťťťťťťťťťťťśśśśśśśśśśśś››››››››››››šššššššššššš™™™™™™™™™™™™——————————––––––––––––••••••••””””””””””““““““““““’’’’’’’’’’‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡††††††……………………„„„„„„„„‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}}}||||||{{{{{{{{zzzzzzzzyyyyyyyyxxxxxxwwwwwwwwvvvvvvvvuuuuuuuuttttttttssssssssrrrrrrrrqqqqqqqqppppppppoooooooooonnnnnnnnmmmmmmmmmmllllllllkkkkkkkkkkjjjjjjjjjjiiiiiiiiiihhhhhhhhhhggggggggggggffffffffffeeeeeeeeeeeeddddddddddddccccccccccccbbbbbbbbbbbbbbaaaaaaaaaaaa````````````````______________^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPOOŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                źźźźźźźźźźźźźźžžžžžžžžžžžžžžťťťťťťťťťťťťťťśśśśśśśśśśśś››››››››››››šššššššššššš™™™™™™™™™™™™————————————––––––––––••••••••••””””””””””““““““““““’’’’’’’’‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‡‡‡‡‡‡††††††††……………………„„„„„„„„‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}}}||||||{{{{{{{{zzzzzzzzyyyyyyyyxxxxxxxxwwwwwwvvvvvvvvuuuuuuuuttttttttssssssssrrrrrrrrqqqqqqqqqqppppppppoooooooonnnnnnnnnnmmmmmmmmllllllllllkkkkkkkkkkjjjjjjjjjjiiiiiiiiiihhhhhhhhhhggggggggggffffffffffffeeeeeeeeeeeeddddddddddddccccccccccccbbbbbbbbbbbbbbaaaaaaaaaaaa````````````````______________^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻŻ®á®á®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­ŕ­ŕ­ŕ­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬ß¬ß¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬«Ţ«Ţ««««««««««««««««««««««««««««««ŞÝŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©Ü©©©©©©©©©©©©©©©©©©©©©©©©©©¨Ű¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§Ú§§§§§§§§§§§§§§§§§§§§§§¦Ů¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄŘĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤×¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁÖŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘Ő˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇÔˇˇˇˇˇˇˇˇˇˇˇˇˇˇ Ó              źŇźźźźźźźźźźźźžŃžžžžžžžžžžžžťĐťťťťťťťťťťťťśĎśśśśśśśśśś›Î››››››››››››šššššššššš™Ě™™™™™™™™™™Ë——————————–É––––––––•Č••••••••”Ç””””””””“Ć““““““““’Ĺ’’’’’’’’‘‘‘‘‘‘‘‘ĂŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŤŔŤŤŤŤŤŤŚżŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰»‡ş‡‡‡‡‡‡††††††††……………………„„„„„„„„‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}}}|||||||I{{{{{{zzzzzzzzyyyyyyyyxxxxxxxxwwwwwwwDvvvvvvvCuuuuuutttttttttAsssssss@rrrrrrr?qqqqqqqqppppppppooooooooo¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤=¤=ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ<˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘;˘;ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ:               9źźźźźźźźźźźźź8žžžžžžžžžžžžž7ťťťťťťťťťťťťť6śśśśśśśśśśśśś5›››››››››››4ššššššššššš3™™™™™™™™™™™21——————————–––––––––––/•••••••••.”””””””””-““““““““’’’’’’’’’’‘‘‘‘‘‘‘‘‘*ŹŹŹŹŹŹŹŹŹ(ŽŽŽŽŽŽŽ'ŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡††††††††……………………„„„„„„„‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}}}||||||||{{{{{{zázzzzzzyyyyyyyyxxxxxxxxwwwwwwwwvvvvvvvvuuuuuuuuttttttttssssssssrrrrrrrrqqqqqqqqp×ppppppppoooooooonŐnnnnnnnnmmmmmmmmlÓllllllllkkkkkkkkkkjjjjjjjjjjiiiiiiiiiihĎhhhhhhhhgÎggggggggggffffffffffeĚeeeeeeeeeedËddddddddddcĘccccccccccccbÉbbbbbbbbbbaČaaaaaaaaaaaa`Ç``````````````_Ć____________^Ĺ^^^^^^^^^^^^^^]Ä]]]]]]]]]]]]]]]]\Ă\\\\\\\\\\\\\\\\[Â[[[[[[[[[[[[[[[[ZÁZÁZZZZZZZZZZZZZZZZYŔYŔYYYYYYYYYYYYYYYYYYXżXżXXXXXXXXXXXXXXXXXXXXWľWWWWWWWWWWWWWWWWWWWWWWV˝V˝VVVVVVVVVVVVVVVVVVVVVVVVUĽUĽUUUUUUUUUUUUUUUUUUUUUUUUUUT»TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSşSşSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRąRąRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQ¸Q¸QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQP·P·PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPŻŻŻŻŻŻŻ|Ż|®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®{®{­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­z­z­z¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬y¬y«««««««««««««««««««««««««««««««x«xŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞw©©©©©©©©©©©©©©©©©©©©©©©©©©©v¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨u¨u§§§§§§§§§§§§§§§§§§§§§t§t¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦sĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄrĄr¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤qŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁp˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘oˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇn               mźźźźźźźźźźźźźlžžžžžžžžžžžžžkťťťťťťťťťťťťťjśśśśśśśśśśśśśi›››››››››››hšššššššššššg™™™™™™™™™™™f———————————d–––––––––c••••••••••””””””””””“““““““““`’’’’’’’’’_‘‘‘‘‘‘‘‘]ŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤZŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡††††††††…………………R„„„„„„‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}}}||||||||{{{{{{{{zzzzzzyyyyyyyyxxxxxxxxwwwwwwwwvvvvvvvvuuuuuuuuttttttttssssssssrĄrrrrrrq¤qqqqqqqqppppppppoooooooooonnnnnnnnm mmmmmmmmllllllllkžkkkkkkkkjťjjjjjjjjiśiiiiiiiiiihhhhhhhhhhgšggggggggf™ffffffffffeeeeeeeeeeed—ddddddddddc–ccccccccccccbbbbbbbbbbbba”aaaaaaaaaaaa`“``````````````_’____________^‘^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]\Ź\\\\\\\\\\\\\\\\[Ž[[[[[[[[[[[[[[[[[[ZŤZZZZZZZZZZZZZZZZZZYŚYYYYYYYYYYYYYYYYYYYYX‹XXXXXXXXXXXXXXXXXXXXWŠWŠWWWWWWWWWWWWWWWWWWWWWWV‰V‰VVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUT‡T‡TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTS†SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSR…R…RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQ„Q„QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPPPPŻŻŻŻŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                źźźźźźźźźźźźźźžžžžžžžžžžžžžžťťťťťťťťťťťťťťśśśśśśśśśśśśśś››››››››››››šššššššššššš™™™™™™™™™™™™——————————––––––––––••••••••••••””””””””””““““““““’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡††††††††………………„„„„„„„„‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}}}||||||||{{{{{{{{zzzzzzyyyyyyyyxxxxxxxxwwwwwwwwvvvvvvvvuuuuuuuuttttttttssssssssssrrrrrrrrqqqqqqqqppppppppoooooooooonnnnnnnnnnmmmmmmmmllllllllllkkkkkkkkkkjjjjjjjjjjiiiiiiiiiihhhhhhhhhhhhggggggggggffffffffffffeeeeeeeeeeeeddddddddddddccccccccccccbbbbbbbbbbbbbbaaaaaaaaaaaaaa````````````````______________^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPPPPPPŻŻ®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                źźźźźźźźźźźźźźžžžžžžžžžžžžžžťťťťťťťťťťťťťťśśśśśśśśśśśśśś››››››››››››šššššššššššš™™™™™™™™™™™™————————————––––––––––••••••••••””””””””””““““““““““’’’’’’’’’’‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡††††††……………………„„„„„„„„‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}}}||||||||{{{{{{{{zzzzzzzzyyyyyyyyxxxxxxwwwwwwwwvvvvvvvvuuuuuuuuuuttttttttssssssssrrrrrrrrqqqqqqqqppppppppppoooooooonnnnnnnnnnmmmmmmmmmmllllllllllkkkkkkkkjjjjjjjjjjjjiiiiiiiiiihhhhhhhhhhggggggggggggffffffffffffeeeeeeeeeeeeddddddddddddccccccccccccbbbbbbbbbbbbbbaaaaaaaaaaaaaa````````````````______________^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPPPPPPPPPPPPPPPPP®á®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­ŕ­ŕ­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬ß¬ß¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬«Ţ«Ţ««««««««««««««««««««««««««««««ŞÝŞÝŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©Ü©Ü©©©©©©©©©©©©©©©©©©©©©©©©©©¨Ű¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§Ú§§§§§§§§§§§§§§§§§§§§§§¦Ů¦Ů¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄŘĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤×¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁÖŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘Ő˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇÔˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ Ó              źŇźźźźźźźźźźźźžŃžžžžžžžžžžžžžžťťťťťťťťťťťťťťśśśśśśśśśśśś›Î››››››››››››šššššššššššš™™™™™™™™™™™™—Ę——————————–É––––––––•Č••••••••”Ç””””””””””““““““““’Ĺ’’’’’’’’‘Ä‘‘‘‘‘‘‘‘ŹÂŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŤŔŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰Ľ‰‰‰‰‰‰»‡ş‡‡‡‡‡‡†ą††††††……………………„„„„„„„„‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}}}||||||||{{{{{{{{zzzzzzzzyyyyyyyyxxxxxxxEwwwwwwwDvvvvvvvCuuuuuuuuttttttttssssssssrrrrrrrrqqqqqqqqq>ppppppppooooooooo¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤=ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ<˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘;ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ:ˇ:               9źźźźźźźźźźźźź8žžžžžžžžžžžžžžž7ťťťťťťťťťťťťť6śśśśśśśśśśśś›››››››››››››4ššššššššššš3™™™™™™™™™™™21——————————–––––––––––/•••••••••.”””””””””-“““““““““,’’’’’’’’’+‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹ(ŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚ%‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡††††††††……………………„„„„„„„„‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}}}||||||||{{{{{{{{zzzzzzzzyyyyyyyyxxxxxxxxwwwwwwwwvvvvvvvvuuuuuuuuttttttttsÚssssssssrrrrrrrrqqqqqqqqp×ppppppppoooooooonŐnnnnnnnnmmmmmmmmmmllllllllllkkkkkkkkkkjjjjjjjjjjiiiiiiiiiihĎhhhhhhhhhhggggggggggfÍffffffffffeĚeeeeeeeeeedËddddddddddcĘccccccccccccbÉbbbbbbbbbbbbaČaaaaaaaaaaaa`Ç``````````````_Ć____________^Ĺ^Ĺ^^^^^^^^^^^^^^]Ä]]]]]]]]]]]]]]]]\Ă\\\\\\\\\\\\\\\\[Â[[[[[[[[[[[[[[[[[[ZÁZZZZZZZZZZZZZZZZZZYŔYYYYYYYYYYYYYYYYYYYYXżXżXXXXXXXXXXXXXXXXXXXXWľWľWWWWWWWWWWWWWWWWWWWWWWV˝V˝VVVVVVVVVVVVVVVVVVVVVVVVUĽUĽUUUUUUUUUUUUUUUUUUUUUUUUUUT»T»TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSşSşSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRąRąRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQ¸Q¸QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQP·P·PPPPPPPPPP®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®{®{­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­z­z¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬y«««««««««««««««««««««««««««««««««x«xŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞwŞw©©©©©©©©©©©©©©©©©©©©©©©©©©©v¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨u¨u§§§§§§§§§§§§§§§§§§§§§§§t¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦sĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄr¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤qŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁp˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘oˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇn                 mźźźźźźźźźźźźźlžžžžžžžžžžžžžžžkťťťťťťťťťťťťťjśśśśśśśśśśśśśi›››››››››››hšššššššššššg™™™™™™™™™™™fe———————————d–––––––––c••••••••••””””””””””““““““““““’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘]ŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽ[ŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹XŠŠŠŠŠŠŠW‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡††††††††……………………„„„„„„„„‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}}}||||||||{{{{{{{{zzzzzzzzyyyyyyyyxxxxxxxxwwwwwwwwvvvvvvvvuuuuuuuut§ttttttttssssssssrrrrrrrrq¤qqqqqqqqppppppppo˘oooooooonnnnnnnnm mmmmmmmmlźllllllllkžkkkkkkkkjťjjjjjjjjiśiiiiiiiiiihhhhhhhhhhgšggggggggggf™ffffffffffeeeeeeeeeeed—ddddddddddc–ccccccccccccb•bbbbbbbbbbbba”aaaaaaaaaaaa`“``````````````_’______________^‘^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]\Ź\\\\\\\\\\\\\\\\[Ž[[[[[[[[[[[[[[[[[[ZŤZZZZZZZZZZZZZZZZZZYŚYŚYYYYYYYYYYYYYYYYYYYYX‹XXXXXXXXXXXXXXXXXXXXXXWŠWWWWWWWWWWWWWWWWWWWWWWWWV‰VVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUT‡T‡TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTS†S†SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSR…R…RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQ„Q„QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPPPPP®®®®®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                  źźźźźźźźźźźźźźžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťśśśśśśśśśśśśśś››››››››››››šššššššššššš™™™™™™™™™™™™————————————––––––––––••••••••••••””””””””””““““““““““’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡††††††††……………………„„„„„„„„‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}}}||||||||{{{{{{{{zzzzzzzzyyyyyyyyxxxxxxxxwwwwwwwwvvvvvvvvuuuuuuuuuuttttttttssssssssrrrrrrrrrrqqqqqqqqppppppppppoooooooonnnnnnnnnnmmmmmmmmmmllllllllllkkkkkkkkkkjjjjjjjjjjiiiiiiiiiihhhhhhhhhhhhggggggggggggffffffffffffeeeeeeeeeeeeddddddddddddccccccccccccccbbbbbbbbbbbbbbaaaaaaaaaaaaaa````````````````________________^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQPPPP®®®®®®®®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                  źźźźźźźźźźźźźźžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťśśśśśśśśśśśśśś››››››››››››šššššššššššššš™™™™™™™™™™™™——————————––––––––––––••••••••••””””””””””““““““““““’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡††††††††……………………„„„„„„„„‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}}}||||||||{{{{{{{{zzzzzzzzyyyyyyyyxxxxxxxxwwwwwwwwwwvvvvvvvvuuuuuuuuttttttttssssssssssrrrrrrrrqqqqqqqqppppppppppoooooooooonnnnnnnnmmmmmmmmmmllllllllllkkkkkkkkkkjjjjjjjjjjjjiiiiiiiiiihhhhhhhhhhhhggggggggggffffffffffffeeeeeeeeeeeeddddddddddddddccccccccccccccbbbbbbbbbbbbbbaaaaaaaaaaaaaa````````````````________________^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ®®®®®®®®®®®®®®®®®®®®®®­ŕ­ŕ­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬ß¬ß¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬«Ţ«Ţ««««««««««««««««««««««««««««««ŞÝŞÝŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©Ü©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨Ű¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§Ú§Ú§§§§§§§§§§§§§§§§§§§§§§¦Ů¦Ů¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄŘĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤×¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁÖŁÖŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘Ő˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇÔˇˇˇˇˇˇˇˇˇˇˇˇˇˇ Ó                źŇźźźźźźźźźźźźžŃžžžžžžžžžžžžžžťĐťťťťťťťťťťťťśĎśśśśśśśśśśśś›Î››››››››››››šššššššššššš™Ě™™™™™™™™™™Ë—Ę——————————––––––––––•Č••••••••••””””””””””““““““““““’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘ŹÂŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚ‹ľ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‡ş‡‡‡‡‡‡†ą††††††††……………………„„„„„„„„‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}}}||||||||{{{{{{{{zzzzzzzzyyyyyyyyyFxxxxxxxEwwwwwwwwvvvvvvvvuuuuuuuuttttttttssssssssssrrrrrrrrqqqqqqqqq>ppppppppoooooooooonnnnnnnnn;mmmmmmmmm:lllllllll9kkkkkkkkk8jjjjjjjjjjiiiiiiiiiii6hhhhhhhhhhggggggggggg4fffffffffff3eeeeeeeeeee2ddddddddddddd1ccccccccccccbbbbbbbbbbbbbbb/aaaaaaaaaaaaa.```````````````-_______________,^^^^^^^^^^^^^^^+]]]]]]]]]]]]]]]]]*\\\\\\\\\\\\\\\\\)\)[[[[[[[[[[[[[[[[[([(ZZZZZZZZZZZZZZZZZZZ'Z'YYYYYYYYYYYYYYYYYYYYY&XXXXXXXXXXXXXXXXXXXXXXX%WWWWWWWWWWWWWWWWWWWWWWWWW$W$VVVVVVVVVVVVVVVVVVVVVVVVV#V#UUUUUUUUUUUUUUUUUUUUUUUUUUUUU"TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT!SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS S RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ®®®®®®®®®®®®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬«««««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§ ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                 źźźźźźźźźźźźźžžžžžžžžžžžžžžžťťťťťťťťťťťťťśśśśśśśśśśśśś›››››››››››››ššššššššššš™™™™™™™™™™˙—ţ——————————–ý––––––––––••••••••••”ű””””””””“ú““““““““’ů’’’’’’’’‘ř‘‘‘‘‘‘‘‘÷ŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŤôŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹ŠńŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡††††††††…ě………………„ë„„„„„„ę‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~}}}}}}}|||||||{{{{{{{zzzzzzzyyyyyyyyxxxxxxxxwwwwwwwwvvvvvvvvuuuuuuuuttttttttt ssssssssrrrrrrrrr qqqqqqqqppppppppp oooooooonnnnnnnnnnmmmmmmmmmmllllllllllkkkkkkkkkkjjjjjjjjjjjiiiiiiiiiihhhhhhhhhhhgggggggggggffffffffffe˙eeeeeeeeeedţddddddddddddcýccccccccccbübbbbbbbbbbbbbbaaaaaaaaaaaaaa`ú``````````````_ů______________^ř^^^^^^^^^^^^^^]÷]]]]]]]]]]]]]]]]\ö\\\\\\\\\\\\\\\\\\[ő[[[[[[[[[[[[[[[[[[ZôZZZZZZZZZZZZZZZZZZZZYóYYYYYYYYYYYYYYYYYYYYXňXXXXXXXXXXXXXXXXXXXXXXWńWńWWWWWWWWWWWWWWWWWWWWWWWWVđVVVVVVVVVVVVVVVVVVVVVVVVVVUďUďUUUUUUUUUUUUUUUUUUUUUUUUUUTîTîTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSíSíSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRěRěRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQëQëQëQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ®®®®®®®®®®®®®G®G­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­F­F¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬E¬E«««««««««««««««««««««««««««««««««D«DŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞCŞC©©©©©©©©©©©©©©©©©©©©©©©©©©©B©B¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨A¨A§§§§§§§§§§§§§§§§§§§§§§§@§@¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦?¦?ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ>Ą>¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤=ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ<˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘;ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ:                 9źźźźźźźźźźźźź8žžžžžžžžžžžžžžž7ťťťťťťťťťťťťť6śśśśśśśśśśśśś5›››››››››››››4ššššššššššš3™™™™™™™™™™™™———————————0–––––––––––/•••••••••.””””””””””““““““““““’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹ(ŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤ&ŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠ#‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡††††††††……………………„„„„„„„„‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}}}||||||||{{{{{{{{zzzzzzzzyyyyyyyyxxxxxxxxwwwwwwwwvvvvvvvvuuuuuuuutŰttttttttssssssssrrrrrrrrrrqqqqqqqqp×ppppppppoooooooonŐnnnnnnnnmÔmmmmmmmmlÓllllllllkŇkkkkkkkkjŃjjjjjjjjjjiiiiiiiiiihĎhhhhhhhhhhgÎggggggggggffffffffffffeeeeeeeeeeeedËddddddddddddccccccccccccbÉbbbbbbbbbbbbaČaaaaaaaaaaaaaa`Ç``````````````_Ć______________^Ĺ^^^^^^^^^^^^^^]Ä]]]]]]]]]]]]]]]]\Ă\\\\\\\\\\\\\\\\\\[Â[[[[[[[[[[[[[[[[[[ZÁZZZZZZZZZZZZZZZZZZZZYŔYYYYYYYYYYYYYYYYYYYYXżXżXXXXXXXXXXXXXXXXXXXXXXWľWWWWWWWWWWWWWWWWWWWWWWWWV˝V˝VVVVVVVVVVVVVVVVVVVVVVVVVVUĽUUUUUUUUUUUUUUUUUUUUUUUUUUUUT»T»TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSşSşSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRąRąRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQ¸Q¸QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ®®®®®®®®®{®{­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­z­z¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬y¬y«««««««««««««««««««««««««««««««««x«xŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞw©©©©©©©©©©©©©©©©©©©©©©©©©©©©©v¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨u§§§§§§§§§§§§§§§§§§§§§§§§§t¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦sĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄr¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤qŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁp˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘oˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇn               m mźźźźźźźźźźźźźźžžžžžžžžžžžžžžžkťťťťťťťťťťťťťjśśśśśśśśśśśśśi›››››››››››››hšššššššššššš™™™™™™™™™™™™™fe——————————–––––––––––c••••••••••”””””””””””a“““““““““`’’’’’’’’’_‘‘‘‘‘‘‘‘‘^]ŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽ[ŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚY‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰V‡‡‡‡‡‡‡‡††††††††……………………„„„„„„„„‚‚‚‚‚‚‚‚€€€€€€€€€€~~~~~~~~}}}}}}}}||||||||{{{{{{{{zzzzzzzzyyyyyyyyxxxxxxxxwwwwwwwwvvvvvvvvu¨uuuuuuuuttttttttssssssssrĄrrrrrrrrqqqqqqqqqqppppppppo˘oooooooonnnnnnnnnnmmmmmmmmmmllllllllllkkkkkkkkkkjťjjjjjjjjiśiiiiiiiiiihhhhhhhhhhhhggggggggggf™ffffffffffeeeeeeeeeeeeed—ddddddddddc–ccccccccccccb•bbbbbbbbbbbba”aaaaaaaaaaaaaa`“``````````````_’______________^‘^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]\Ź\Ź\\\\\\\\\\\\\\\\[Ž[[[[[[[[[[[[[[[[[[ZŤZŤZZZZZZZZZZZZZZZZZZYŚYŚYYYYYYYYYYYYYYYYYYYYX‹XXXXXXXXXXXXXXXXXXXXXXWŠWŠWWWWWWWWWWWWWWWWWWWWWWWWV‰VVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUT‡T‡TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTS†S†SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSR…R…RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQ„Q„QQQQQQQQQQQQQQQQQQQQQQQQQQQQ®®®®®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                źźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžťťťťťťťťťťťťťťśśśśśśśśśśśśśś››››››››››››››šššššššššššššš™™™™™™™™™™™™————————————––––––––––••••••••••••””””””””””““““““““““’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡††††††††……………………„„„„„„„„‚‚‚‚‚‚‚‚€€€€€€€€€€~~~~~~~~}}}}}}}}||||||||{{{{{{{{zzzzzzzzyyyyyyyyxxxxxxxxwwwwwwwwvvvvvvvvvvuuuuuuuuttttttttssssssssssrrrrrrrrqqqqqqqqqqppppppppppoooooooonnnnnnnnnnmmmmmmmmmmllllllllllkkkkkkkkkkkkjjjjjjjjjjiiiiiiiiiihhhhhhhhhhhhggggggggggggffffffffffffeeeeeeeeeeeeeeddddddddddddccccccccccccccbbbbbbbbbbbbbbaaaaaaaaaaaaaaaa````````````````________________^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQQQQQ®®®®­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                źźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťśśśśśśśśśśśśśś››››››››››››šššššššššššššš™™™™™™™™™™™™————————————––––––––––––••••••••••””””””””””””““““““““““’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡††††††††……………………„„„„„„„„‚‚‚‚‚‚‚‚€€€€€€€€€€~~~~~~~~}}}}}}}}||||||||{{{{{{{{zzzzzzzzyyyyyyyyxxxxxxxxwwwwwwwwwwvvvvvvvvuuuuuuuuttttttttttssssssssrrrrrrrrrrqqqqqqqqppppppppppoooooooooonnnnnnnnnnmmmmmmmmmmllllllllllkkkkkkkkkkjjjjjjjjjjjjiiiiiiiiiihhhhhhhhhhhhggggggggggggffffffffffffeeeeeeeeeeeeddddddddddddddccccccccccccccbbbbbbbbbbbbbbaaaaaaaaaaaaaaaa````````````````________________^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQQQQQ­ŕ­ŕ­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬ß¬ß¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬«Ţ«Ţ««««««««««««««««««««««««««««««««ŞÝŞÝŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©Ü©Ü©©©©©©©©©©©©©©©©©©©©©©©©©©¨Ű¨Ű¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§Ú§§§§§§§§§§§§§§§§§§§§§§§§¦Ů¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄŘĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤×¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁÖŁÖŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘Ő˘Ő˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇÔˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ Ó              źŇźźźźźźźźźźźźźźźźžŃžžžžžžžžžžžžťĐťťťťťťťťťťťťťťśĎśśśśśśśśśśśś›Î››››››››››››šššššššššššš™Ě™™™™™™™™™™Ë——————————–É––––––––––•Č••••••••••””””””””””“Ć““““““““’Ĺ’’’’’’’’‘Ä‘‘‘‘‘‘‘‘ĂŹÂŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŚżŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹Š˝ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‡ş‡‡‡‡‡‡‡‡††††††††……………………„„„„„„„„‚‚‚‚‚‚‚‚€€€€€€€€˛~~~~~~~~}}}}}}}}||||||||{{{{{{{{zzzzzzzzyyyyyyyyxxxxxxxxxEwwwwwwwwvvvvvvvvuuuuuuuuuBttttttttssssssssrrrrrrrrrrqqqqqqqqq>ppppppppoooooooooonnnnnnnnnnmmmmmmmmmmllllllllllkkkkkkkkkkk8jjjjjjjjjjiiiiiiiiiii6hhhhhhhhhhh5ggggggggggg4fffffffffff3eeeeeeeeeee2ddddddddddddd1ccccccccccccc0bbbbbbbbbbbbb/aaaaaaaaaaaaaaa.```````````````-_______________,^^^^^^^^^^^^^^^^^+]]]]]]]]]]]]]]]]]*\\\\\\\\\\\\\\\\\\\)[[[[[[[[[[[[[[[[[[[(ZZZZZZZZZZZZZZZZZZZZZ'YYYYYYYYYYYYYYYYYYYYYYY&XXXXXXXXXXXXXXXXXXXXXXX%X%WWWWWWWWWWWWWWWWWWWWWWWWW$VVVVVVVVVVVVVVVVVVVVVVVVVVVVV#UUUUUUUUUUUUUUUUUUUUUUUUUUUUU"U"TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT!T!SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS S RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQQQQQQQQQQQQQ­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬«««««««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§ § ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ Ą ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ               źźźźźźźźźźźźźźźźźžžžžžžžžžžžžžťťťťťťťťťťťťťťťśśśśśśśśśśśśś›››››››››››››ššššššššššš™™™™™™™™™™™™—ţ——————————––––––––––––••••••••••”ű””””””””””““““““““““’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŤôŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰đ‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡†í††††††††……………………„„„„„„„„‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}}}||||||||{{{{{{{{zzzzzzzzyyyyyyyyyxxxxxxxxwwwwwwwwvvvvvvvvuuuuuuuuuuttttttttsssssssss rrrrrrrrqqqqqqqqqqppppppppp ooooooooonnnnnnnnnmmmmmmmmmlllllllllkkkkkkkkkkjjjjjjjjjjjiiiiiiiiiihhhhhhhhhhhhggggggggggggffffffffffffeeeeeeeeeeeedţddddddddddddcýccccccccccccbübbbbbbbbbbbbaűaaaaaaaaaaaaaa`ú``````````````_ů______________^ř^^^^^^^^^^^^^^^^]÷]]]]]]]]]]]]]]]]\ö\\\\\\\\\\\\\\\\\\[ő[[[[[[[[[[[[[[[[[[ZôZZZZZZZZZZZZZZZZZZZZYóYóYYYYYYYYYYYYYYYYYYYYXňXXXXXXXXXXXXXXXXXXXXXXXXWńWWWWWWWWWWWWWWWWWWWWWWWWVđVđVVVVVVVVVVVVVVVVVVVVVVVVVVUďUďUUUUUUUUUUUUUUUUUUUUUUUUUUUUTîTîTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSíSíSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRěRěRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQëQëQQQQQQQQQQQQQQQQ­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­F­F¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬E¬E«««««««««««««««««««««««««««««««««««D«DŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞC©©©©©©©©©©©©©©©©©©©©©©©©©©©©©B©B¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨A§§§§§§§§§§§§§§§§§§§§§§§§§@¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦?¦?ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ>¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤=ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ<˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘;ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ:               9źźźźźźźźźźźźźźźźź8žžžžžžžžžžžžž7ťťťťťťťťťťťťťťť6śśśśśśśśśśśśś5›››››››››››››4šššššššššššš™™™™™™™™™™™™™21———————————0–––––––––––/••••••••••”””””””””””-“““““““““,’’’’’’’’’+‘‘‘‘‘‘‘‘‘*)ŹŹŹŹŹŹŹŹŹ(ŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚ%‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡†††††††††……………………„„„„„„„„‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}}}||||||||{{{{{{{{zzzzzzzzyyyyyyyyyyxxxxxxxxwwwwwwwwvvvvvvvvuÜuuuuuuuuttttttttsÚssssssssrrrrrrrrqŘqqqqqqqqppppppppppoooooooooonnnnnnnnnnmmmmmmmmmmllllllllllkkkkkkkkkkjŃjjjjjjjjjjiiiiiiiiiihĎhhhhhhhhhhgÎggggggggggfÍffffffffffeĚeeeeeeeeeeeedËddddddddddddccccccccccccccbbbbbbbbbbbbbbaČaaaaaaaaaaaaaa````````````````_Ć______________^Ĺ^^^^^^^^^^^^^^^^]Ä]]]]]]]]]]]]]]]]\Ă\\\\\\\\\\\\\\\\\\[Â[[[[[[[[[[[[[[[[[[ZÁZÁZZZZZZZZZZZZZZZZZZZZYŔYYYYYYYYYYYYYYYYYYYYXżXżXXXXXXXXXXXXXXXXXXXXXXWľWľWWWWWWWWWWWWWWWWWWWWWWWWV˝V˝VVVVVVVVVVVVVVVVVVVVVVVVVVUĽUĽUUUUUUUUUUUUUUUUUUUUUUUUUUUUT»T»TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSşSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRąRąRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQ¸Q¸QQQQQQQQQQQQ­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­z­z¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬y¬y«««««««««««««««««««««««««««««««««««x«xŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞwŞw©©©©©©©©©©©©©©©©©©©©©©©©©©©©©v¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨u¨u§§§§§§§§§§§§§§§§§§§§§§§t§t¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦sĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄr¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤qŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁp˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘oˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇn               mźźźźźźźźźźźźźźźźźlžžžžžžžžžžžžžžťťťťťťťťťťťťťťťjśśśśśśśśśśśśśi›››››››››››››hšššššššššššššg™™™™™™™™™™™f————————————–––––––––––c•••••••••••b”””””””””a““““““““““’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤZŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹XŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰V‡‡‡‡‡‡‡‡††††††††…………………………„„„„„„„„‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}}}||||||||{{{{{{{{zzzzzzzzy¬yyyyyyyyxxxxxxxxwwwwwwwwv©vvvvvvvvuuuuuuuuttttttttttssssssssrrrrrrrrrrqqqqqqqqpŁppppppppo˘oooooooonˇnnnnnnnnm mmmmmmmmlźllllllllkžkkkkkkkkkkjjjjjjjjjjiśiiiiiiiiiih›hhhhhhhhhhgšggggggggggf™ffffffffffeeeeeeeeeeeeeddddddddddddc–ccccccccccccb•bbbbbbbbbbbbbba”aaaaaaaaaaaa`“````````````````_’______________^‘^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]\Ź\\\\\\\\\\\\\\\\\\[Ž[[[[[[[[[[[[[[[[[[[[ZŤZZZZZZZZZZZZZZZZZZZZYŚYYYYYYYYYYYYYYYYYYYYYYX‹XXXXXXXXXXXXXXXXXXXXXXXXWŠWWWWWWWWWWWWWWWWWWWWWWWWWWV‰VVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUT‡T‡TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTS†S†SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSR…R…RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQ„Q„QQQQQQQQ­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                źźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťśśśśśśśśśśśśśś››››››››››››››šššššššššššššš™™™™™™™™™™™™————————————––––––––––••••••••••••””””””””””““““““““““““’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡††††††††…………………………„„„„„„„„‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}}}||||||||{{{{{{{{zzzzzzzzzzyyyyyyyyxxxxxxxxwwwwwwwwwwvvvvvvvvuuuuuuuuttttttttttssssssssrrrrrrrrrrqqqqqqqqqqppppppppppoooooooooonnnnnnnnnnmmmmmmmmmmllllllllllkkkkkkkkkkjjjjjjjjjjjjiiiiiiiiiiiihhhhhhhhhhhhggggggggggggffffffffffffeeeeeeeeeeeeddddddddddddddccccccccccccccbbbbbbbbbbbbbbbbaaaaaaaaaaaaaa``````````````````________________^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQQQQQ­­­­­­­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                źźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťśśśśśśśśśśśśśś››››››››››››››šššššššššššššš™™™™™™™™™™™™™™————————————––––––––––––••••••••••””””””””””””““““““““““’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡††††††††……………………„„„„„„„„„„‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}}}||||||||{{{{{{{{{{zzzzzzzzyyyyyyyyxxxxxxxxwwwwwwwwwwvvvvvvvvuuuuuuuuuuttttttttssssssssssrrrrrrrrqqqqqqqqqqppppppppppoooooooooonnnnnnnnnnmmmmmmmmmmllllllllllkkkkkkkkkkkkjjjjjjjjjjiiiiiiiiiiiihhhhhhhhhhhhggggggggggggffffffffffffffeeeeeeeeeeeeddddddddddddddccccccccccccccbbbbbbbbbbbbbbbbaaaaaaaaaaaaaa````````````````__________________^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRQQQQ­­­­­­­­­­­­­­­­­­­­­­­­¬ß¬ß¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬«Ţ««««««««««««««««««««««««««««««««««ŞÝŞÝŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©Ü©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨Ű¨Ű¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§Ú§Ú§§§§§§§§§§§§§§§§§§§§§§§§¦Ů¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄŘĄŘĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤×¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁÖŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘Ő˘Ő˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇÔˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ Ó              źŇźźźźźźźźźźźźźźźźžŃžžžžžžžžžžžžžžťĐťťťťťťťťťťťťśĎśśśśśśśśśśśśśś››››››››››››››šššššššššššš™Ě™™™™™™™™™™™™Ë—Ę——————————–É––––––––––•Č••••••••••””””””””””“Ć““““““““““’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘ĂŹÂŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚ‹ľ‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰Ľ‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡††††††††……………………„„„„„„„„„„‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}}}||||||||{{{{{{{{{{zzzzzzzzyyyyyyyyxxxxxxxxxEwwwwwwwwvvvvvvvvuuuuuuuuuuttttttttssssssssssrrrrrrrrr?qqqqqqqqq>ppppppppoooooooooonnnnnnnnnnmmmmmmmmmmm:lllllllll9kkkkkkkkkkjjjjjjjjjjj7iiiiiiiiiii6hhhhhhhhhhh5ggggggggggg4ffffffffffffeeeeeeeeeeeee2ddddddddddddd1ccccccccccccc0bbbbbbbbbbbbbbb/aaaaaaaaaaaaa.```````````````-_________________,^^^^^^^^^^^^^^^^^+]]]]]]]]]]]]]]]]]]]*\\\\\\\\\\\\\\\\\\\)[[[[[[[[[[[[[[[[[[[([(ZZZZZZZZZZZZZZZZZZZZZ'YYYYYYYYYYYYYYYYYYYYYYY&XXXXXXXXXXXXXXXXXXXXXXXXX%WWWWWWWWWWWWWWWWWWWWWWWWWWW$VVVVVVVVVVVVVVVVVVVVVVVVVVVVV#V#UUUUUUUUUUUUUUUUUUUUUUUUUUUUU"U"TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT!T!SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS S RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR­­­­­­­­­­­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬«««««««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§ § ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ               źźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžťťťťťťťťťťťťťśśśśśśśśśśśśśśś›››››››››››››šššššššššššš™™™™™™™™™™™™˙—ţ————————————––––––––––•ü••••••••••”ű””””””””””““““““““““’ů’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŚóŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡†í††††††††……………………„„„„„„„„ę‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}}}||||||||{{{{{{{{{{zzzzzzzzyyyyyyyyxxxxxxxxxxwwwwwwwwvvvvvvvvvuuuuuuuuttttttttt ssssssssrrrrrrrrrrqqqqqqqqqqppppppppp ooooooooonnnnnnnnnmmmmmmmmmmlllllllllllkkkkkkkkkjjjjjjjjjjjiiiiiiiiiihhhhhhhhhhhhhgggggggggggffffffffffe˙eeeeeeeeeeeedţddddddddddddcýccccccccccccbübbbbbbbbbbbbbbaaaaaaaaaaaaaa`ú``````````````_ů________________^ř^^^^^^^^^^^^^^^^]÷]]]]]]]]]]]]]]]]]]\ö\\\\\\\\\\\\\\\\\\[ő[[[[[[[[[[[[[[[[[[[[ZôZZZZZZZZZZZZZZZZZZZZYóYYYYYYYYYYYYYYYYYYYYYYXňXňXXXXXXXXXXXXXXXXXXXXXXWńWńWWWWWWWWWWWWWWWWWWWWWWWWVđVđVVVVVVVVVVVVVVVVVVVVVVVVVVVVUďUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTîTîTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSíSíSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRěRěRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR­­­­­­­­­­­­­­­­­F­F¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬E¬E«««««««««««««««««««««««««««««««««««D«DŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞCŞC©©©©©©©©©©©©©©©©©©©©©©©©©©©©©B©B¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨A§§§§§§§§§§§§§§§§§§§§§§§§§§§@¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦?¦?ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ>Ą>¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤=ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ<˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘;ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ:                 9źźźźźźźźźźźźźźźźź8žžžžžžžžžžžžžžž7ťťťťťťťťťťťťťťśśśśśśśśśśśśśśś5›››››››››››››4ššššššššššššš3™™™™™™™™™™™2—————————————0––––––––––••••••••••••”””””””””””-““““““““““’’’’’’’’’’’+‘‘‘‘‘‘‘‘‘*)ŹŹŹŹŹŹŹŹŹ(ŽŽŽŽŽŽŽŽŽ'ŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹$ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰"‡‡‡‡‡‡‡‡††††††††††……………………„„„„„„„„‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}}}||||||||{â{{{{{{{{zzzzzzzzyyyyyyyyxßxxxxxxxxwwwwwwwwvvvvvvvvvvuuuuuuuuttttttttttssssssssrŮrrrrrrrrqqqqqqqqqqppppppppppoooooooooonnnnnnnnnnmmmmmmmmmmlÓllllllllllkkkkkkkkkkjŃjjjjjjjjjjiiiiiiiiiihĎhhhhhhhhhhhhggggggggggggffffffffffffeĚeeeeeeeeeeeedËddddddddddddcĘccccccccccccbÉbbbbbbbbbbbbaČaaaaaaaaaaaaaa`Ç``````````````_Ć________________^Ĺ^^^^^^^^^^^^^^^^]Ä]]]]]]]]]]]]]]]]]]\Ă\\\\\\\\\\\\\\\\\\[Â[[[[[[[[[[[[[[[[[[[[ZÁZZZZZZZZZZZZZZZZZZZZYŔYŔYYYYYYYYYYYYYYYYYYYYYYXżXXXXXXXXXXXXXXXXXXXXXXXXWľWWWWWWWWWWWWWWWWWWWWWWWWWWV˝VVVVVVVVVVVVVVVVVVVVVVVVVVVVUĽUĽUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUT»T»TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSşSşSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRąRąRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR­­­­­­­­­­­­­z­z¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬y¬y«««««««««««««««««««««««««««««««««««x«xŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞwŞw©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©v¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨u¨u§§§§§§§§§§§§§§§§§§§§§§§§§t§t¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦sĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄr¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤q¤qŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁp˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘oˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇn                 mźźźźźźźźźźźźźźźźźlžžžžžžžžžžžžžžžkťťťťťťťťťťťťťťťjśśśśśśśśśśśśśi›››››››››››››hšššššššššššššg™™™™™™™™™™™™e———————————d–––––––––––c•••••••••••b””””””””””“““““““““““`’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚY‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡††††††††††……………………„„„„„„„„‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}}}||||||||||{{{{{{{{zzzzzzzzyyyyyyyyyyxxxxxxxxwwwwwwwwv©vvvvvvvvuuuuuuuut§ttttttttssssssssssrrrrrrrrq¤qqqqqqqqpŁppppppppo˘oooooooonˇnnnnnnnnm mmmmmmmmmmllllllllllkžkkkkkkkkkkjjjjjjjjjjiśiiiiiiiiiih›hhhhhhhhhhgšggggggggggf™ffffffffffffeeeeeeeeeeeeeddddddddddddddccccccccccccccb•bbbbbbbbbbbba”aaaaaaaaaaaaaa`“``````````````_’________________^‘^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]\Ź\\\\\\\\\\\\\\\\\\[Ž[[[[[[[[[[[[[[[[[[[[ZŤZZZZZZZZZZZZZZZZZZZZZZYŚYYYYYYYYYYYYYYYYYYYYYYX‹XXXXXXXXXXXXXXXXXXXXXXXXWŠWŠWWWWWWWWWWWWWWWWWWWWWWWWV‰V‰VVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUT‡TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTS†S†SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSR…R…RRRRRRRRRRRRRRRRRRRRRRRRRRRRRR­­­­­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                  źźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťśśśśśśśśśśśśśś››››››››››››››šššššššššššššš™™™™™™™™™™™™™™————————————––––––––––––••••••••••••””””””””””””““““““““““’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡††††††††††……………………„„„„„„„„‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}}}||||||||||{{{{{{{{zzzzzzzzyyyyyyyyyyxxxxxxxxwwwwwwwwwwvvvvvvvvuuuuuuuuuuttttttttssssssssssrrrrrrrrrrqqqqqqqqqqppppppppppoooooooooonnnnnnnnnnmmmmmmmmmmllllllllllllkkkkkkkkkkjjjjjjjjjjjjiiiiiiiiiiiihhhhhhhhhhhhggggggggggggffffffffffffffeeeeeeeeeeeeddddddddddddddccccccccccccccccbbbbbbbbbbbbbbaaaaaaaaaaaaaaaa````````````````__________________^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR­­­­­­­­¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                  źźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťśśśśśśśśśśśśśś››››››››››››››šššššššššššššš™™™™™™™™™™™™™™——————————————––––––––––––••••••••••””””””””””””““““““““““““’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡††††††††…………………………„„„„„„„„‚‚‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}}}||||||||||{{{{{{{{zzzzzzzzyyyyyyyyyyxxxxxxxxwwwwwwwwwwvvvvvvvvuuuuuuuuuuttttttttttssssssssrrrrrrrrrrqqqqqqqqqqppppppppppoooooooooonnnnnnnnnnmmmmmmmmmmmmllllllllllkkkkkkkkkkkkjjjjjjjjjjiiiiiiiiiiiihhhhhhhhhhhhggggggggggggggffffffffffffeeeeeeeeeeeeeeddddddddddddddccccccccccccccbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaa````````````````__________________^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRRRRRRRRRRRRRRRRRR­­­­¬ß¬ß¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬«Ţ«Ţ««««««««««««««««««««««««««««««««««ŞÝŞÝŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©Ü©Ü©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨Ű¨Ű¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§Ú§Ú§§§§§§§§§§§§§§§§§§§§§§§§¦Ů¦Ů¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄŘĄŘĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤×¤×¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁÖŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘Ő˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇÔˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ Ó                źŇźźźźźźźźźźźźźźźźžŃžžžžžžžžžžžžžžťĐťťťťťťťťťťťťťťśĎśśśśśśśśśśśś›Î››››››››››››››šššššššššššššš™™™™™™™™™™™™Ë————————————–É––––––––––•Č••••••••••””””””””””””““““““““““’Ĺ’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹Š˝ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡††††††††…………………………„„„„„„„„‚‚‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}}}||||||||||{{{{{{{{zzzzzzzzzGyyyyyyyyxxxxxxxxxEwwwwwwwwvvvvvvvvvCuuuuuuuuttttttttttsssssssss@rrrrrrrrr?qqqqqqqqppppppppppooooooooooo¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤=¤=ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ<˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘;˘;ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ:                 9źźźźźźźźźźźźźźźźź8žžžžžžžžžžžžžžž7ťťťťťťťťťťťťťťť6śśśśśśśśśśśśśśś5›››››››››››››4ššššššššššššš3™™™™™™™™™™™™™21————————————––––––––––––••••••••••••””””””””””””“““““““““““,’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘)ŹŹŹŹŹŹŹŹŹ(ŽŽŽŽŽŽŽŽŽ'ŤŤŤŤŤŤŤŤŤ&ŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡ ††††††††……………………„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}}}}}||||||||{{{{{{{{zzzzzzzzzzyyyyyyyyxxxxxxxxxxwwwwwwwwvÝvvvvvvvvuuuuuuuuuuttttttttsÚssssssssrrrrrrrrrrqqqqqqqqqqppppppppppoooooooooonnnnnnnnnnmÔmmmmmmmmmmllllllllllkŇkkkkkkkkkkjjjjjjjjjjjjiiiiiiiiiiiihhhhhhhhhhhhggggggggggggfÍffffffffffffeĚeeeeeeeeeeeeddddddddddddddcĘccccccccccccbÉbbbbbbbbbbbbbbaČaaaaaaaaaaaaaa`Ç``````````````_Ć________________^Ĺ^^^^^^^^^^^^^^^^^^]Ä]]]]]]]]]]]]]]]]]]\Ă\\\\\\\\\\\\\\\\\\[Â[[[[[[[[[[[[[[[[[[[[ZÁZZZZZZZZZZZZZZZZZZZZZZYŔYYYYYYYYYYYYYYYYYYYYYYYYXżXXXXXXXXXXXXXXXXXXXXXXXXWľWľWWWWWWWWWWWWWWWWWWWWWWWWWWV˝VVVVVVVVVVVVVVVVVVVVVVVVVVVVUĽUĽUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUT»TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSşSşSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRąRąRRRRRRRRRRRRRR¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬y¬y«««««««««««««««««««««««««««««««««««««x«xŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞwŞw©©©©©©©©©©©©©©©©©©©©©©©©©©©©©v©v¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨u§§§§§§§§§§§§§§§§§§§§§§§§§§§t¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦s¦sĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄr¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤qŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁp˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘oˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇn                 mźźźźźźźźźźźźźźźźźlžžžžžžžžžžžžžžžkťťťťťťťťťťťťťťťjśśśśśśśśśśśśśśśi›››››››››››››hšššššššššššššg™™™™™™™™™™™™™f—————————————d–––––––––––c•••••••••••b”””””””””””a““““““““““’’’’’’’’’’’_‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠW‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡††††††††††……………………„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}}}}}||||||||{{{{{{{{z­zzzzzzzzyyyyyyyyx«xxxxxxxxwwwwwwwwwwvvvvvvvvuuuuuuuuuuttttttttttssssssssrĄrrrrrrrrq¤qqqqqqqqpŁppppppppo˘oooooooonˇnnnnnnnnnnmmmmmmmmmmlźllllllllllkkkkkkkkkkjťjjjjjjjjjjiśiiiiiiiiiih›hhhhhhhhhhgšggggggggggggf™ffffffffffffeeeeeeeeeeeed—ddddddddddddddccccccccccccccb•bbbbbbbbbbbbbba”aaaaaaaaaaaaaa`“``````````````_’________________^‘^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]\Ź\\\\\\\\\\\\\\\\\\[Ž[[[[[[[[[[[[[[[[[[[[ZŤZŤZZZZZZZZZZZZZZZZZZZZYŚYŚYYYYYYYYYYYYYYYYYYYYYYX‹XXXXXXXXXXXXXXXXXXXXXXXXXXWŠWWWWWWWWWWWWWWWWWWWWWWWWWWV‰V‰VVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUT‡T‡TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTS†S†SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSR…R…RRRRRRRRRR¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                  źźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśś››››››››››››››šššššššššššššš™™™™™™™™™™™™™™————————————––––––––––––••••••••••••””””””””””””““““““““““““’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡††††††††††……………………„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}}}}}||||||||{{{{{{{{{{zzzzzzzzyyyyyyyyyyxxxxxxxxwwwwwwwwwwvvvvvvvvuuuuuuuuuuttttttttttssssssssssrrrrrrrrrrqqqqqqqqqqppppppppppoooooooooonnnnnnnnnnmmmmmmmmmmmmllllllllllkkkkkkkkkkkkjjjjjjjjjjjjiiiiiiiiiiiihhhhhhhhhhhhggggggggggggggffffffffffffeeeeeeeeeeeeeeddddddddddddddccccccccccccccccbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaa````````````````__________________^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRRRRRR¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                  źźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśś››››››››››››››››šššššššššššššš™™™™™™™™™™™™————————————––––––––––––––••••••••••••””””””””””““““““““““““’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡††††††††††……………………„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}}}}}||||||||{{{{{{{{{{zzzzzzzzyyyyyyyyyyxxxxxxxxwwwwwwwwwwvvvvvvvvvvuuuuuuuuttttttttttssssssssssrrrrrrrrrrqqqqqqqqqqppppppppppoooooooooonnnnnnnnnnnnmmmmmmmmmmllllllllllllkkkkkkkkkkkkjjjjjjjjjjiiiiiiiiiiiiiihhhhhhhhhhhhggggggggggggffffffffffffffeeeeeeeeeeeeeeddddddddddddddccccccccccccccccbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaa````````````````__________________^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSRRRRRR¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬«Ţ«Ţ««««««««««««««««««««««««««««««««««««ŞÝŞÝŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©Ü©Ü©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨Ű¨Ű¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§Ú§§§§§§§§§§§§§§§§§§§§§§§§§§¦Ů¦Ů¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄŘĄŘĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤×¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁÖŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘Ő˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇÔˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ Ó                źŇźźźźźźźźźźźźźźźźžŃžžžžžžžžžžžžžžťĐťťťťťťťťťťťťťťťťśśśśśśśśśśśśśś›Î››››››››››››››šÍšššššššššššš™Ě™™™™™™™™™™™™—Ę————————————––––––––––––•Č••••••••••”Ç””””””””””““““““““““““’’’’’’’’’’‘Ä‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŽÁŽŽŽŽŽŽŽŽŤŔŤŤŤŤŤŤŤŤŚżŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡††††††††…¸……………………„„„„„„„„¶‚‚‚‚‚‚‚‚´€€€€€€€€~~~~~~~~}}}}}}}}}}||||||||{{{{{{{{{{zzzzzzzzyyyyyyyyyyxxxxxxxxxEwwwwwwwwvvvvvvvvvvuuuuuuuuuBttttttttssssssssssrrrrrrrrrrqqqqqqqqqqppppppppppp=oooooooooĄ>¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤=ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ<˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘;ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ:                 9źźźźźźźźźźźźźźźźź8žžžžžžžžžžžžžžžžž7ťťťťťťťťťťťťťťť6śśśśśśśśśśśśśś›››››››››››››››4ššššššššššššš3™™™™™™™™™™™™™21———————————0––––––––––––••••••••••••””””””””””””““““““““““““’’’’’’’’’’’+‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŹ(ŽŽŽŽŽŽŽŽŽ'ŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹$ŠŠŠŠŠŠŠŠŠ#‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡ ††††††††…………………………„„„„„„„„‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}}}}}||||||||{{{{{{{{{{zzzzzzzzyŕyyyyyyyyxxxxxxxxxxwwwwwwwwvÝvvvvvvvvuuuuuuuuuuttttttttttssssssssssrrrrrrrrrrqqqqqqqqqqppppppppppoooooooooonŐnnnnnnnnnnmmmmmmmmmmlÓllllllllllkkkkkkkkkkkkjjjjjjjjjjjjiiiiiiiiiiiihhhhhhhhhhhhgÎggggggggggggffffffffffffeĚeeeeeeeeeeeeeeddddddddddddddcĘccccccccccccbÉbbbbbbbbbbbbbbaČaaaaaaaaaaaaaaaa`Ç``````````````_Ć__________________^Ĺ^^^^^^^^^^^^^^^^]Ä]]]]]]]]]]]]]]]]]]\Ă\Ă\\\\\\\\\\\\\\\\\\[Â[[[[[[[[[[[[[[[[[[[[[[ZÁZZZZZZZZZZZZZZZZZZZZZZYŔYYYYYYYYYYYYYYYYYYYYYYYYXżXXXXXXXXXXXXXXXXXXXXXXXXXXWľWWWWWWWWWWWWWWWWWWWWWWWWWWWWV˝VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUĽUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUT»T»TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSşSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬y¬y«««««««««««««««««««««««««««««««««««««x«xŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞwŞw©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©v©v¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨u¨u§§§§§§§§§§§§§§§§§§§§§§§§§§§t¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦s¦sĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄr¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤qŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁp˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘oˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇn                 mźźźźźźźźźźźźźźźźźlžžžžžžžžžžžžžžžžžkťťťťťťťťťťťťťťťjśśśśśśśśśśśśśśśi›››››››››››››hšššššššššššššš™™™™™™™™™™™™™™e————————————–––––––––––––c•••••••••••b”””””””””””a“““““““““““`’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘^ŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŤZŚŚŚŚŚŚŚŚŚY‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡††††††††††…………………………„„„„„„„„‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}}}}}||||||||{®{{{{{{{{zzzzzzzzzzyyyyyyyyxxxxxxxxxxwwwwwwwwwwvvvvvvvvu¨uuuuuuuuttttttttttssssssssssrrrrrrrrrrqqqqqqqqqqpŁppppppppo˘oooooooooonnnnnnnnnnm mmmmmmmmmmllllllllllkžkkkkkkkkkkjťjjjjjjjjjjiśiiiiiiiiiih›hhhhhhhhhhhhggggggggggggf™ffffffffffffeeeeeeeeeeeeed—ddddddddddddddccccccccccccccb•bbbbbbbbbbbbbba”aaaaaaaaaaaaaaaa`“``````````````_’__________________^‘^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]\Ź\\\\\\\\\\\\\\\\\\[Ž[Ž[[[[[[[[[[[[[[[[[[[[ZŤZZZZZZZZZZZZZZZZZZZZZZYŚYYYYYYYYYYYYYYYYYYYYYYYYX‹XXXXXXXXXXXXXXXXXXXXXXXXXXWŠWŠWWWWWWWWWWWWWWWWWWWWWWWWWWV‰V‰VVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUT‡T‡TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTS†S†SSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                  źźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśś››››››››››››››šššššššššššššššš™™™™™™™™™™™™™™——————————————––––––––––––••••••••••••””””””””””””““““““““““““’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡††††††††††…………………………„„„„„„„„‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~}}}}}}}}}}||||||||||{{{{{{{{zzzzzzzzzzyyyyyyyyxxxxxxxxxxwwwwwwwwwwvvvvvvvvvvuuuuuuuuttttttttttssssssssssrrrrrrrrrrqqqqqqqqqqqqppppppppppoooooooooonnnnnnnnnnnnmmmmmmmmmmllllllllllllkkkkkkkkkkkkjjjjjjjjjjjjiiiiiiiiiiiihhhhhhhhhhhhggggggggggggggffffffffffffffeeeeeeeeeeeeeeddddddddddddddccccccccccccccccbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaa````````````````____________________^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS¬¬¬¬¬¬¬¬¬¬¬¬««««««««««««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                  źźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśś››››››››››››››››šššššššššššššš™™™™™™™™™™™™™™————————————––––––––––––––••••••••••••””””””””””””““““““““““““’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡††††††††††……………………„„„„„„„„„„‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~~~}}}}}}}}||||||||||{{{{{{{{zzzzzzzzzzyyyyyyyyyyxxxxxxxxwwwwwwwwwwvvvvvvvvvvuuuuuuuuuuttttttttttssssssssssrrrrrrrrrrqqqqqqqqqqppppppppppoooooooooooonnnnnnnnnnmmmmmmmmmmmmllllllllllkkkkkkkkkkkkjjjjjjjjjjjjiiiiiiiiiiiiiihhhhhhhhhhhhggggggggggggggffffffffffffffeeeeeeeeeeeeeeddddddddddddddccccccccccccccccbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaa````````````````____________________^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS¬¬¬¬¬¬¬¬«Ţ«Ţ««««««««««««««««««««««««««««««««««««ŞÝŞÝŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©Ü©Ü©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨Ű¨Ű¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§Ú§Ú§§§§§§§§§§§§§§§§§§§§§§§§§§¦Ů¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄŘĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤×¤×¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁÖŁÖŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘Ő˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇÔˇÔˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ Ó Ó                źŇźźźźźźźźźźźźźźźźžŃžžžžžžžžžžžžžžžžťĐťťťťťťťťťťťťťťśĎśśśśśśśśśśśśśś›Î››››››››››››››šÍšššššššššššš™Ě™™™™™™™™™™™™Ë—Ę————————————––––––––––––•Č••••••••••”Ç””””””””””“Ć““““““““““’Ĺ’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘ĂŹŹŹŹŹŹŹŹŹŹŽÁŽŽŽŽŽŽŽŽŤŔŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰»‡‡‡‡‡‡‡‡‡‡††††††††††……………………„„„„„„„„„„‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~~~}}}}}}}}||||||||||{{{{{{{{zzzzzzzzzzyyyyyyyyyyxxxxxxxxwwwwwwwwwwvvvvvvvvvvuuuuuuuuuuttttttttttssssssssssrrrrrrrrrrqqqqqqqqqqppppppppppp=oooooooooonnnnnnnnnnmmmmmmmmmmmmlllllllllll9kkkkkkkkkkk8jjjjjjjjjjj7iiiiiiiiiiiihhhhhhhhhhhhh5ggggggggggggffffffffffffffeeeeeeeeeeeeeee2ddddddddddddd1ccccccccccccccc0bbbbbbbbbbbbbbb/aaaaaaaaaaaaaaaaa.```````````````-___________________,^^^^^^^^^^^^^^^^^+^+]]]]]]]]]]]]]]]]]]]*\\\\\\\\\\\\\\\\\\\\\)[[[[[[[[[[[[[[[[[[[[[([(ZZZZZZZZZZZZZZZZZZZZZZZ'YYYYYYYYYYYYYYYYYYYYYYYYY&XXXXXXXXXXXXXXXXXXXXXXXXXXX%X%WWWWWWWWWWWWWWWWWWWWWWWWWWW$W$VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV#UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU"TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT!T!SSSSSSSSSSSSSSSSSSSSSSSSSS¬¬¬¬¬¬««««««««««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§ § ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ ¦ ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                   źźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťśśśśśśśśśśśśśśś›››››››››››››››ššššššššššššš™™™™™™™™™™™™˙—ţ————————————–ý––––––––––•ü••••••••••••””””””””””””““““““““““““’’’’’’’’’’‘ř‘‘‘‘‘‘‘‘‘‘ŹöŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŚóŚŚŚŚŚŚŚŚ‹ň‹‹‹‹‹‹‹‹ŠńŠŠŠŠŠŠŠŠ‰đ‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡††††††††…ě……………………„„„„„„„„„„‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~~~}}}}}}}}||||||||||{{{{{{{{{zzzzzzzzyyyyyyyyyyxxxxxxxxxwwwwwwwwvvvvvvvvvvuuuuuuuuuuttttttttttssssssssssrrrrrrrrrrqqqqqqqqqqq ppppppppppoooooooooonnnnnnnnnnnmmmmmmmmmmmllllllllllkkkkkkkkkkkkjjjjjjjjjjjjjiiiiiiiiiiihhhhhhhhhhhhgggggggggggggffffffffffffe˙eeeeeeeeeeeeeeddddddddddddddcýccccccccccccccbübbbbbbbbbbbbbbaűaaaaaaaaaaaaaaaa````````````````_ů__________________^ř^^^^^^^^^^^^^^^^^^]÷]]]]]]]]]]]]]]]]]]\ö\\\\\\\\\\\\\\\\\\\\[ő[[[[[[[[[[[[[[[[[[[[[[ZôZZZZZZZZZZZZZZZZZZZZZZYóYYYYYYYYYYYYYYYYYYYYYYYYXňXňXXXXXXXXXXXXXXXXXXXXXXXXXXWńWWWWWWWWWWWWWWWWWWWWWWWWWWWWVđVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUďUďUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTîTîTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSíSíSSSSSSSSSSSSSSSSSSSSSS¬E¬E«««««««««««««««««««««««««««««««««««««««DŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞC©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©B©B¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨A¨A§§§§§§§§§§§§§§§§§§§§§§§§§§§@¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦?ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ>¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤=ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ<Ł<˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘;ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ:                   9źźźźźźźźźźźźźźźźź8žžžžžžžžžžžžžžžžž7ťťťťťťťťťťťťťťť6śśśśśśśśśśśśśśś5›››››››››››››››4šššššššššššššš™™™™™™™™™™™™™™—————————————0––––––––––––•••••••••••••.”””””””””””-“““““““““““,’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘*ŹŹŹŹŹŹŹŹŹŹŹ(ŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡††††††††…………………………„„„„„„„„„„‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~~~}}}}}}}}||||||||||{{{{{{{{{{zzzzzzzzyyyyyyyyyyxxxxxxxxxxwwwwwwwwvÝvvvvvvvvuÜuuuuuuuutŰttttttttsÚssssssssrŮrrrrrrrrqŘqqqqqqqqqqppppppppppoooooooooonŐnnnnnnnnnnmmmmmmmmmmmmllllllllllkŇkkkkkkkkkkjŃjjjjjjjjjjjjiiiiiiiiiiiihĎhhhhhhhhhhgÎggggggggggggfÍffffffffffffeĚeeeeeeeeeeeedËddddddddddddddcĘccccccccccccccbÉbbbbbbbbbbbbbbaČaaaaaaaaaaaaaa`Ç````````````````_Ć__________________^Ĺ^^^^^^^^^^^^^^^^^^]Ä]]]]]]]]]]]]]]]]]]\Ă\\\\\\\\\\\\\\\\\\\\[Â[[[[[[[[[[[[[[[[[[[[[[ZÁZZZZZZZZZZZZZZZZZZZZZZYŔYŔYYYYYYYYYYYYYYYYYYYYYYYYXżXXXXXXXXXXXXXXXXXXXXXXXXXXWľWľWWWWWWWWWWWWWWWWWWWWWWWWWWV˝V˝VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUĽUĽUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUT»T»TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSşSşSSSSSSSSSSSSSSSSSS«««««««««««««««««««««««««««««««««««««««x«xŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞwŞw©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©v©v¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨u¨u§§§§§§§§§§§§§§§§§§§§§§§§§§§t§t¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦s¦sĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄrĄr¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤qŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁp˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘oˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇn                   mźźźźźźźźźźźźźźźźźlžžžžžžžžžžžžžžžžžkťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśś›››››››››››››››hšššššššššššššššg™™™™™™™™™™™™™fe————————————–––––––––––––c•••••••••••b””””””””””””“““““““““““`’’’’’’’’’’’_‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŽ[ŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡T††††††††…………………………„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~~~}}}}}}}}||||||||||{{{{{{{{{{zzzzzzzzyyyyyyyyyyxxxxxxxxxxwwwwwwwwwwvvvvvvvvvvuuuuuuuuuuttttttttttssssssssssrrrrrrrrrrqqqqqqqqqqppppppppppo˘oooooooooonnnnnnnnnnm mmmmmmmmmmlźllllllllllkžkkkkkkkkkkjťjjjjjjjjjjiśiiiiiiiiiiiihhhhhhhhhhhhgšggggggggggggf™ffffffffffffeeeeeeeeeeeeed—ddddddddddddddc–ccccccccccccccbbbbbbbbbbbbbbbba”aaaaaaaaaaaaaa`“````````````````_’__________________^‘^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]\Ź\\\\\\\\\\\\\\\\\\\\[Ž[Ž[[[[[[[[[[[[[[[[[[[[ZŤZZZZZZZZZZZZZZZZZZZZZZZZYŚYYYYYYYYYYYYYYYYYYYYYYYYX‹X‹XXXXXXXXXXXXXXXXXXXXXXXXXXWŠWWWWWWWWWWWWWWWWWWWWWWWWWWWWV‰V‰VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUT‡T‡TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTS†S†SSSSSSSSSSSSSS««««««««««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                    źźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśś››››››››››››››šššššššššššššššš™™™™™™™™™™™™™™——————————————––––––––––––••••••••••••””””””””””””””““““““““““’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡††††††††††…………………………„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~~~}}}}}}}}||||||||||{{{{{{{{{{zzzzzzzzyyyyyyyyyyxxxxxxxxxxwwwwwwwwwwvvvvvvvvvvuuuuuuuuuuttttttttttssssssssssrrrrrrrrrrqqqqqqqqqqppppppppppppoooooooooonnnnnnnnnnnnmmmmmmmmmmmmllllllllllllkkkkkkkkkkkkjjjjjjjjjjjjiiiiiiiiiiiihhhhhhhhhhhhhhggggggggggggggffffffffffffffeeeeeeeeeeeeeeddddddddddddddddccccccccccccccbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaa``````````````````____________________^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSSSS««««««««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                    źźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśś››››››››››››››››šššššššššššššš™™™™™™™™™™™™™™——————————————––––––––––––––••••••••••••””””””””””””““““““““““““’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡††††††††††…………………………„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~~~}}}}}}}}||||||||||{{{{{{{{{{zzzzzzzzzzyyyyyyyyxxxxxxxxxxwwwwwwwwwwvvvvvvvvvvuuuuuuuuuuttttttttttssssssssssrrrrrrrrrrqqqqqqqqqqqqppppppppppoooooooooooonnnnnnnnnnmmmmmmmmmmmmllllllllllllkkkkkkkkkkkkjjjjjjjjjjjjiiiiiiiiiiiiiihhhhhhhhhhhhggggggggggggggffffffffffffffeeeeeeeeeeeeeeeeddddddddddddddccccccccccccccccbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaa``````````````````____________________^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSSSSSSSSSSSS««««««««««««««««««««««««««««««ŞÝŞÝŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©Ü©Ü©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨Ű¨Ű¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§Ú§Ú§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦Ů¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄŘĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤×¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁÖŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘Ő˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇÔˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ Ó                  źŇźźźźźźźźźźźźźźźźžŃžžžžžžžžžžžžžžžžťĐťťťťťťťťťťťťťťťťśĎśśśśśśśśśśśśśś›Î››››››››››››››šÍšššššššššššš™Ě™™™™™™™™™™™™™™————————————–É––––––––––––•Č••••••••••”Ç””””””””””“Ć““““““““““’Ĺ’’’’’’’’’’‘Ä‘‘‘‘‘‘‘‘‘‘ĂŹŹŹŹŹŹŹŹŹŹŽÁŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡††††††††††…………………………„„„„„„„„¶‚‚‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~~~}}}}}}}}||||||||||{{{{{{{{{{zzzzzzzzzzyyyyyyyyyFxxxxxxxxwwwwwwwwwwvvvvvvvvvvuuuuuuuuuuttttttttttsssssssssss@rrrrrrrrr?qqqqqqqqqqppppppppppp=oooooooooonnnnnnnnnnn;mmmmmmmmmmm:lllllllllll9kkkkkkkkkkk8jjjjjjjjjjj7iiiiiiiiiiiihhhhhhhhhhhhh5ggggggggggggg4fffffffffffff3eeeeeeeeeeeeeee2ddddddddddddd1ccccccccccccccc0bbbbbbbbbbbbbbbbb/aaaaaaaaaaaaaaa.`````````````````-___________________,^^^^^^^^^^^^^^^^^^^+]]]]]]]]]]]]]]]]]]]]]*\\\\\\\\\\\\\\\\\\\\\)[[[[[[[[[[[[[[[[[[[[[[[(ZZZZZZZZZZZZZZZZZZZZZZZ'Z'YYYYYYYYYYYYYYYYYYYYYYYYY&XXXXXXXXXXXXXXXXXXXXXXXXXXXXX%WWWWWWWWWWWWWWWWWWWWWWWWWWWWW$W$VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV#V#UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU"U"TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT!T!SSSSSSSS««««««««««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§§ § ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ ¦ ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                   źźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśś›››››››››››››››šššššššššššššš™™™™™™™™™™™™™™˙—ţ————————————––––––––––––•ü••••••••••••””””””””””””““““““““““““’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŹöŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŤôŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡††††††††††…………………………„„„„„„„„‚‚‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~~~}}}}}}}}}||||||||{{{{{{{{{{zzzzzzzzzzyyyyyyyyyyxxxxxxxxxwwwwwwwwwvvvvvvvvvuuuuuuuuuttttttttt ssssssssssrrrrrrrrrrqqqqqqqqqqq ppppppppppooooooooooonnnnnnnnnnmmmmmmmmmmmmllllllllllllkkkkkkkkkkkkjjjjjjjjjjjjjiiiiiiiiiiihhhhhhhhhhhhhgggggggggggggffffffffffffe˙eeeeeeeeeeeeeeddddddddddddddcýccccccccccccccbübbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaa`ú````````````````_ů__________________^ř^^^^^^^^^^^^^^^^^^]÷]]]]]]]]]]]]]]]]]]]]\ö\\\\\\\\\\\\\\\\\\\\[ő[[[[[[[[[[[[[[[[[[[[[[ZôZZZZZZZZZZZZZZZZZZZZZZZZYóYYYYYYYYYYYYYYYYYYYYYYYYXňXňXXXXXXXXXXXXXXXXXXXXXXXXXXWńWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVđVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUďUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTîTîTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSíSíSSSS«««««««««««««««««««««««««DŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞCŞC©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©B¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨A¨A§§§§§§§§§§§§§§§§§§§§§§§§§§§§§@¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦?ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ>Ą>¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤=ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ<Ł<˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘;ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ:                   9źźźźźźźźźźźźźźźźź8žžžžžžžžžžžžžžžžž7ťťťťťťťťťťťťťťťťť6śśśśśśśśśśśśśśś5›››››››››››››››4ššššššššššššššš3™™™™™™™™™™™™™21—————————————0––––––––––––•••••••••••••.”””””””””””-“““““““““““,’’’’’’’’’’’+‘‘‘‘‘‘‘‘‘‘‘*ŹŹŹŹŹŹŹŹŹŹŹ(ŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŤ&ŚŚŚŚŚŚŚŚŚ%‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡††††††††††………………………„„„„„„„„‚‚‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~~~}}}}}}}}}}||||||||{{{{{{{{{{zzzzzzzzzzyyyyyyyyyyxxxxxxxxxxwwwwwwwwwwvvvvvvvvvvuuuuuuuuuuttttttttttssssssssssrrrrrrrrrrqŘqqqqqqqqqqppppppppppoooooooooooonnnnnnnnnnmÔmmmmmmmmmmlÓllllllllllkŇkkkkkkkkkkjŃjjjjjjjjjjjjiiiiiiiiiiiihĎhhhhhhhhhhhhggggggggggggggffffffffffffffeĚeeeeeeeeeeeedËddddddddddddddcĘccccccccccccccbÉbbbbbbbbbbbbbbaČaaaaaaaaaaaaaaaa`Ç````````````````_Ć__________________^Ĺ^^^^^^^^^^^^^^^^^^]Ä]]]]]]]]]]]]]]]]]]]]\Ă\\\\\\\\\\\\\\\\\\\\[Â[[[[[[[[[[[[[[[[[[[[[[ZÁZÁZZZZZZZZZZZZZZZZZZZZZZYŔYYYYYYYYYYYYYYYYYYYYYYYYYYXżXXXXXXXXXXXXXXXXXXXXXXXXXXWľWľWWWWWWWWWWWWWWWWWWWWWWWWWWWWV˝V˝VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUĽUĽUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUT»T»TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTSşSş«««««««««««««««««««««x«xŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞwŞw©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©v©v¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨u§§§§§§§§§§§§§§§§§§§§§§§§§§§§§t§t¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦sĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄr¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤q¤qŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁp˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘o˘oˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇn                   mźźźźźźźźźźźźźźźźźlžžžžžžžžžžžžžžžžžkťťťťťťťťťťťťťťťťťjśśśśśśśśśśśśśśśi›››››››››››››››hšššššššššššššššg™™™™™™™™™™™™™f—————————————d–––––––––––––c••••••••••••””””””””””””““““““““““““’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘]ŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŽ[ŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹‹XŠŠŠŠŠŠŠŠŠW‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡T†††††††††S……………………„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~~~}}}}}}}}}}||||||||{®{{{{{{{{zzzzzzzzzzyyyyyyyyyyxxxxxxxxxxwwwwwwwwwwvvvvvvvvvvuuuuuuuuuuttttttttttssssssssssrĄrrrrrrrrrrqqqqqqqqqqppppppppppo˘oooooooooonnnnnnnnnnnnmmmmmmmmmmmmllllllllllllkkkkkkkkkkkkjjjjjjjjjjjjiśiiiiiiiiiiiihhhhhhhhhhhhgšggggggggggggf™ffffffffffffffeeeeeeeeeeeeeed—ddddddddddddddc–ccccccccccccccb•bbbbbbbbbbbbbba”aaaaaaaaaaaaaaaa`“````````````````_’__________________^‘^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]\Ź\\\\\\\\\\\\\\\\\\\\[Ž[Ž[[[[[[[[[[[[[[[[[[[[[[ZŤZZZZZZZZZZZZZZZZZZZZZZYŚYŚYYYYYYYYYYYYYYYYYYYYYYYYX‹X‹XXXXXXXXXXXXXXXXXXXXXXXXXXWŠWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWV‰VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUT‡TTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT««««««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                    źźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśś››››››››››››››››šššššššššššššššš™™™™™™™™™™™™™™————————————––––––––––––––••••••••••••••””””””””””””““““““““““““’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡††††††††††…………………………„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~~~}}}}}}}}}}||||||||||{{{{{{{{zzzzzzzzzzyyyyyyyyyyxxxxxxxxxxwwwwwwwwwwvvvvvvvvvvuuuuuuuuuuttttttttttssssssssssssrrrrrrrrrrqqqqqqqqqqppppppppppppoooooooooonnnnnnnnnnnnmmmmmmmmmmmmllllllllllllkkkkkkkkkkkkjjjjjjjjjjjjjjiiiiiiiiiiiihhhhhhhhhhhhhhggggggggggggggffffffffffffffeeeeeeeeeeeeeeeeddddddddddddddddccccccccccccccccbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaa``````````````````____________________^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT««««««««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                    źźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśśśś››››››››››››››››šššššššššššššš™™™™™™™™™™™™™™™™——————————————––––––––––––••••••••••••••””””””””””””““““““““““““’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡††††††††††…………………………„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚€€€€€€€€~~~~~~~~~~}}}}}}}}}}||||||||||{{{{{{{{zzzzzzzzzzyyyyyyyyyyxxxxxxxxxxwwwwwwwwwwvvvvvvvvvvuuuuuuuuuuttttttttttttssssssssssrrrrrrrrrrqqqqqqqqqqqqppppppppppoooooooooooonnnnnnnnnnnnmmmmmmmmmmmmllllllllllllkkkkkkkkkkkkjjjjjjjjjjjjiiiiiiiiiiiiiihhhhhhhhhhhhhhggggggggggggggffffffffffffffeeeeeeeeeeeeeeeeddddddddddddddccccccccccccccccccbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaa``````````````````____________________^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTTT««««««««««««ŞÝŞÝŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©Ü©Ü©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨Ű¨Ű¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§Ú§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦Ů¦Ů¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄŘĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤×¤×¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁÖŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘Ő˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇÔˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ Ó                  źŇźźźźźźźźźźźźźźźźžŃžžžžžžžžžžžžžžžžžžťĐťťťťťťťťťťťťťťśĎśśśśśśśśśśśśśśśś›Î››››››››››››››šÍšššššššššššššš™™™™™™™™™™™™™™Ë—Ę————————————–É––––––––––––••••••••••••”Ç””””””””””””““““““““““““’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŽÁŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŚżŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡††††††††††…………………………„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚€ł€€€€€€€€~~~~~~~~~~}}}}}}}}}}||||||||||{{{{{{{{{HzzzzzzzzyyyyyyyyyyxxxxxxxxxxwwwwwwwwwwvvvvvvvvvvvCuuuuuuuuuBttttttttttssssssssssrrrrrrrrrrr?qqqqqqqqqqppppppppppp=oooooooooonnnnnnnnnnnnmmmmmmmmmmmmllllllllllllkkkkkkkkkkkkjjjjjjjjjjjjj7iiiiiiiiiiiii6hhhhhhhhhhhhggggggggggggggfffffffffffffff3eeeeeeeeeeeeeeddddddddddddddd1ccccccccccccccccbbbbbbbbbbbbbbbbb/aaaaaaaaaaaaaaaaa.`````````````````-___________________,^^^^^^^^^^^^^^^^^^^+^+]]]]]]]]]]]]]]]]]]]*\\\\\\\\\\\\\\\\\\\\\\\)[[[[[[[[[[[[[[[[[[[[[[[(ZZZZZZZZZZZZZZZZZZZZZZZZZ'YYYYYYYYYYYYYYYYYYYYYYYYYYY&XXXXXXXXXXXXXXXXXXXXXXXXXXXXX%WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW$W$VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV#V#UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU"U"TTTTTTTTTTTTTTTTTTTTTTTTTTTTTT««««««««««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§§ ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ ¦ ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                   źźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśśś›››››››››››››››ššššššššššššššš™™™™™™™™™™™™˙—ţ————————————–ý––––––––––––•ü••••••••••••””””””””””””“ú““““““““““’ů’’’’’’’’’’‘ř‘‘‘‘‘‘‘‘‘‘÷ŹöŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŤôŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚ‹ň‹‹‹‹‹‹‹‹ŠńŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡††††††††††…………………………„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€~~~~~~~~~~}}}}}}}}}}||||||||||{{{{{{{{{{zzzzzzzzzyyyyyyyyyxxxxxxxxxwwwwwwwwwvvvvvvvvvvuuuuuuuuuuttttttttttsssssssssss rrrrrrrrrrqqqqqqqqqqppppppppppppooooooooooonnnnnnnnnnnmmmmmmmmmmmlllllllllllkkkkkkkkkkkjjjjjjjjjjjjjiiiiiiiiiiiihhhhhhhhhhhhhgggggggggggggffffffffffffffe˙eeeeeeeeeeeedţddddddddddddddcýccccccccccccccbübbbbbbbbbbbbbbbbaűaaaaaaaaaaaaaaaa`ú````````````````_ů__________________^ř^^^^^^^^^^^^^^^^^^^^]÷]]]]]]]]]]]]]]]]]]\ö\ö\\\\\\\\\\\\\\\\\\\\[ő[[[[[[[[[[[[[[[[[[[[[[ZôZôZZZZZZZZZZZZZZZZZZZZZZYóYóYYYYYYYYYYYYYYYYYYYYYYYYXňXňXXXXXXXXXXXXXXXXXXXXXXXXXXWńWńWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVđVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUďUďUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTîTîTTTTTTTTTTTTTTTTTTTTTTTTTT«««««««DŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞCŞC©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©B¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨A§§§§§§§§§§§§§§§§§§§§§§§§§§§§§@§@¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦?ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ>¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤=ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ<˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘;ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ:                   9źźźźźźźźźźźźźźźźźźź8žžžžžžžžžžžžžžžžž7ťťťťťťťťťťťťťťťťť6śśśśśśśśśśśśśśś5›››››››››››››››4ššššššššššššššš3™™™™™™™™™™™™™™——————————————––––––––––––––•••••••••••••.””””””””””””““““““““““““’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŹ(ŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŤ&ŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠ#‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡††††††††††…………………………„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€~~~~~~~~~~}}}}}}}}}}||||||||||{{{{{{{{{{zzzzzzzzzzyyyyyyyyyyxxxxxxxxxxwwwwwwwwwwvvvvvvvvvvuuuuuuuuuuttttttttttsÚssssssssssrrrrrrrrrrqqqqqqqqqqp×ppppppppppoooooooooooonnnnnnnnnnnnmmmmmmmmmmmmllllllllllllkkkkkkkkkkkkjŃjjjjjjjjjjjjiiiiiiiiiiiihĎhhhhhhhhhhhhgÎggggggggggggfÍffffffffffffffeeeeeeeeeeeeeedËddddddddddddddcĘccccccccccccccbÉbbbbbbbbbbbbbbbbaČaaaaaaaaaaaaaaaa`Ç````````````````_Ć__________________^Ĺ^^^^^^^^^^^^^^^^^^^^]Ä]]]]]]]]]]]]]]]]]]]]\Ă\\\\\\\\\\\\\\\\\\\\[Â[Â[[[[[[[[[[[[[[[[[[[[[[ZÁZZZZZZZZZZZZZZZZZZZZZZZZYŔYYYYYYYYYYYYYYYYYYYYYYYYYYXżXXXXXXXXXXXXXXXXXXXXXXXXXXXXWľWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWV˝V˝VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUĽUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUT»TTTTTTTTTTTTTTTTTTTTTTTT«««x«xŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞw©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©v©v¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨u¨u§§§§§§§§§§§§§§§§§§§§§§§§§§§§§t¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦sĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄrĄr¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤q¤qŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁpŁp˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘oˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇn                   mźźźźźźźźźźźźźźźźźźźlžžžžžžžžžžžžžžžžžkťťťťťťťťťťťťťťťťťjśśśśśśśśśśśśśśśi›››››››››››››››hšššššššššššššššg™™™™™™™™™™™™™™™fe—————————————d–––––––––––––c••••••••••••”””””””””””””a“““““““““““`’’’’’’’’’’’_‘‘‘‘‘‘‘‘‘‘‘^]ŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰V‡‡‡‡‡‡‡‡‡‡††††††††††…………………………„„„„„„„„„„‚‚‚‚‚‚‚‚‚O€€€€€€€€€€~~~~~~~~~~}}}}}}}}}}||||||||||{{{{{{{{{{zzzzzzzzzzyyyyyyyyyyxxxxxxxxxxwwwwwwwwwwvvvvvvvvvvuuuuuuuuuut§ttttttttttssssssssssrrrrrrrrrrq¤qqqqqqqqqqppppppppppo˘oooooooooonˇnnnnnnnnnnm mmmmmmmmmmlźllllllllllkžkkkkkkkkkkkkjjjjjjjjjjjjiśiiiiiiiiiiiihhhhhhhhhhhhhhggggggggggggggf™ffffffffffffeeeeeeeeeeeeeeed—ddddddddddddddc–ccccccccccccccb•bbbbbbbbbbbbbbbba”aaaaaaaaaaaaaaaa`“````````````````_’__________________^‘^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]\Ź\\\\\\\\\\\\\\\\\\\\\\[Ž[[[[[[[[[[[[[[[[[[[[[[ZŤZZZZZZZZZZZZZZZZZZZZZZZZYŚYYYYYYYYYYYYYYYYYYYYYYYYYYX‹X‹XXXXXXXXXXXXXXXXXXXXXXXXXXWŠWŠWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWV‰VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUT‡T‡TTTTTTTTTTTTTTTTTTTT««ŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                    źźźźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśś››››››››››››››››šššššššššššššššš™™™™™™™™™™™™™™™™——————————————––––––––––––––••••••••••••••””””””””””””““““““““““““’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡††††††††††…………………………„„„„„„„„„„‚‚‚‚‚‚‚‚€€€€€€€€€€~~~~~~~~~~}}}}}}}}}}||||||||||{{{{{{{{{{zzzzzzzzzzyyyyyyyyyyxxxxxxxxxxwwwwwwwwwwvvvvvvvvvvuuuuuuuuuuuuttttttttttssssssssssrrrrrrrrrrrrqqqqqqqqqqppppppppppppoooooooooooonnnnnnnnnnnnmmmmmmmmmmmmllllllllllllkkkkkkkkkkkkjjjjjjjjjjjjjjiiiiiiiiiiiihhhhhhhhhhhhhhggggggggggggggggffffffffffffffeeeeeeeeeeeeeeeeddddddddddddddddccccccccccccccccbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaa``````````````````____________________^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTTTTTŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                    źźźźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśś››››››››››››››››››šššššššššššššššš™™™™™™™™™™™™™™————————————————––––––––––––••••••••••••••””””””””””””””““““““““““““’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡††††††††††………………………………„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€~~~~~~~~~~}}}}}}}}}}||||||||||{{{{{{{{{{zzzzzzzzzzyyyyyyyyyyxxxxxxxxxxwwwwwwwwwwvvvvvvvvvvvvuuuuuuuuuuttttttttttssssssssssssrrrrrrrrrrqqqqqqqqqqqqppppppppppoooooooooooonnnnnnnnnnnnmmmmmmmmmmmmllllllllllllkkkkkkkkkkkkkkjjjjjjjjjjjjiiiiiiiiiiiiiihhhhhhhhhhhhhhggggggggggggggffffffffffffffffeeeeeeeeeeeeeeddddddddddddddddddccccccccccccccccbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaa``````````````````____________________^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTTTTTTTTTTTTTTTŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©Ü©Ü©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨Ű¨Ű¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§Ú§Ú§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦Ů¦Ů¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄŘĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤×¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁÖŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘Ő˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇÔˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ Ó Ó                  źŇźźźźźźźźźźźźźźźźźźžŃžžžžžžžžžžžžžžžžťĐťťťťťťťťťťťťťťťťśĎśśśśśśśśśśśśśśśś››››››››››››››››šÍšššššššššššššš™Ě™™™™™™™™™™™™Ë——————————————–É––––––––––––••••••••••••”Ç””””””””””””“Ć““““““““““’Ĺ’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŹÂŹŹŹŹŹŹŹŹŹŹŽÁŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŚżŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹Š˝ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡††††††††††…………………………„·„„„„„„„„¶‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€~~~~~~~~~~}}}}}}}}}}||||||||||{{{{{{{{{{zzzzzzzzzzyyyyyyyyyyxxxxxxxxxxwwwwwwwwwwwDvvvvvvvvvvuuuuuuuuuuttttttttttssssssssssssrrrrrrrrrrqqqqqqqqqqqqppppppppppp=ooooooooooo¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤=¤=ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ<˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘;ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ:                     9źźźźźźźźźźźźźźźźźźź8žžžžžžžžžžžžžžžžž7ťťťťťťťťťťťťťťťťť6śśśśśśśśśśśśśśśśś5›››››››››››››››4ššššššššššššššš3™™™™™™™™™™™™™™™21——————————————––––––––––––––•••••••••••••.””””””””””””“““““““““““““,’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŹ(ŽŽŽŽŽŽŽŽŽŽŽ'ŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚŚ%‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰"‡‡‡‡‡‡‡‡‡‡††††††††††…………………………„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€~~~~~~~~~~}}}}}}}}}}||||||||||{{{{{{{{{{zzzzzzzzzzyyyyyyyyyyxßxxxxxxxxxxwwwwwwwwwwvvvvvvvvvvuuuuuuuuuutŰttttttttttssssssssssrrrrrrrrrrrrqqqqqqqqqqp×ppppppppppoÖoooooooooonnnnnnnnnnnnmmmmmmmmmmmmlÓllllllllllkŇkkkkkkkkkkkkjjjjjjjjjjjjjjiiiiiiiiiiiihĎhhhhhhhhhhhhgÎggggggggggggggffffffffffffffeĚeeeeeeeeeeeeeedËddddddddddddddcĘccccccccccccccccbbbbbbbbbbbbbbbbaČaaaaaaaaaaaaaaaaaa`Ç````````````````_Ć__________________^Ĺ^^^^^^^^^^^^^^^^^^^^]Ä]]]]]]]]]]]]]]]]]]]]\Ă\Ă\\\\\\\\\\\\\\\\\\\\[Â[Â[[[[[[[[[[[[[[[[[[[[[[ZÁZZZZZZZZZZZZZZZZZZZZZZZZZZYŔYYYYYYYYYYYYYYYYYYYYYYYYYYXżXXXXXXXXXXXXXXXXXXXXXXXXXXXXWľWľWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWV˝V˝VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUĽUĽUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUT»TTTTTTŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞwŞw©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©v©v¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨u§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§t¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦sĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄr¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤qŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁpŁp˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘oˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇn                     mźźźźźźźźźźźźźźźźźźźlžžžžžžžžžžžžžžžžžkťťťťťťťťťťťťťťťťťjśśśśśśśśśśśśśśśśśi›››››››››››››››hšššššššššššššššg™™™™™™™™™™™™™™™f———————————————d–––––––––––––c••••••••••••”””””””””””””a““““““““““““’’’’’’’’’’’’’_‘‘‘‘‘‘‘‘‘‘‘^]ŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŤZŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹‹XŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰U‡‡‡‡‡‡‡‡‡‡††††††††††…………………………„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€~~~~~~~~~~}}}}}}}}}}||||||||||{{{{{{{{{{z­zzzzzzzzy¬yyyyyyyyyyxxxxxxxxxxwwwwwwwwwwvvvvvvvvvvu¨uuuuuuuuuuttttttttttssssssssssrĄrrrrrrrrrrqqqqqqqqqqqqppppppppppppoooooooooonˇnnnnnnnnnnm mmmmmmmmmmmmllllllllllllkkkkkkkkkkkkjťjjjjjjjjjjjjiśiiiiiiiiiiiih›hhhhhhhhhhhhgšggggggggggggf™ffffffffffffffeeeeeeeeeeeeeeed—ddddddddddddddc–ccccccccccccccb•bbbbbbbbbbbbbbbba”aaaaaaaaaaaaaaaaaa`“````````````````_’__________________^‘^‘^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]\Ź\\\\\\\\\\\\\\\\\\\\\\[Ž[[[[[[[[[[[[[[[[[[[[[[ZŤZŤZZZZZZZZZZZZZZZZZZZZZZZZYŚYYYYYYYYYYYYYYYYYYYYYYYYYYX‹X‹XXXXXXXXXXXXXXXXXXXXXXXXXXXXWŠWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWV‰VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUT‡T‡TTŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                      źźźźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśśśś››››››››››››››››šššššššššššššššš™™™™™™™™™™™™™™™™——————————————––––––––––––––••••••••••••••””””””””””””““““““““““““““’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡‡‡††††††††††…………………………„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€~~~~~~~~~~}}}}}}}}}}||||||||||{{{{{{{{{{{{zzzzzzzzzzyyyyyyyyyyxxxxxxxxxxwwwwwwwwwwvvvvvvvvvvvvuuuuuuuuuuttttttttttssssssssssssrrrrrrrrrrqqqqqqqqqqqqppppppppppppoooooooooooonnnnnnnnnnnnmmmmmmmmmmmmllllllllllllkkkkkkkkkkkkkkjjjjjjjjjjjjjjiiiiiiiiiiiiiihhhhhhhhhhhhhhggggggggggggggffffffffffffffffeeeeeeeeeeeeeeeeddddddddddddddddccccccccccccccccbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaa``````````````````______________________^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUTTŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                      źźźźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśśśś››››››››››››››››››šššššššššššššššš™™™™™™™™™™™™™™™™——————————————––––––––––––––••••••••••••••””””””””””””””““““““““““““’’’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡‡‡††††††††††…………………………„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€~~~~~~~~~~}}}}}}}}}}||||||||||||{{{{{{{{{{zzzzzzzzzzyyyyyyyyyyxxxxxxxxxxwwwwwwwwwwvvvvvvvvvvvvuuuuuuuuuuttttttttttttssssssssssrrrrrrrrrrrrqqqqqqqqqqppppppppppppoooooooooooonnnnnnnnnnnnmmmmmmmmmmmmmmllllllllllllkkkkkkkkkkkkkkjjjjjjjjjjjjiiiiiiiiiiiiiihhhhhhhhhhhhhhggggggggggggggggffffffffffffffeeeeeeeeeeeeeeeeddddddddddddddddddccccccccccccccccbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaa````````````````````______________________^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞŞ©Ü©Ü©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨Ű¨Ű¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§Ú§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦Ů¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄŘĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤×¤×¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁÖŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘Ő˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇÔˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ Ó                    źŇźźźźźźźźźźźźźźźźźźžŃžžžžžžžžžžžžžžžžťĐťťťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśś›Î››››››››››››››››šÍšššššššššššššš™Ě™™™™™™™™™™™™™™Ë—Ę——————————————––––––––––––––••••••••••••••””””””””””””“Ć““““““““““““’’’’’’’’’’’’‘Ä‘‘‘‘‘‘‘‘‘‘ĂŹÂŹŹŹŹŹŹŹŹŹŹŽÁŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚ‹ľ‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰Ľ‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡†ą††††††††††…………………………„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€~~~~~~~~~~}}}}}}}}}}}J||||||||||{{{{{{{{{{zzzzzzzzzzyyyyyyyyyyxxxxxxxxxxwwwwwwwwwwwDvvvvvvvvvvuuuuuuuuuuttttttttttttssssssssssrrrrrrrrrrrrqqqqqqqqqqq>ppppppppppp=ooooooooooo¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤=ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ<˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘;ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ:                     9źźźźźźźźźźźźźźźźźźź8žžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťťťť6śśśśśśśśśśśśśśśśś5›››››››››››››››4ššššššššššššššš3™™™™™™™™™™™™™™™21—————————————0––––––––––––––•••••••••••••.”””””””””””””-““““““““““““’’’’’’’’’’’’’+‘‘‘‘‘‘‘‘‘‘‘*)ŹŹŹŹŹŹŹŹŹŹŹ(ŽŽŽŽŽŽŽŽŽŽŽ'ŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹‹$ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰"‡‡‡‡‡‡‡‡‡‡††††††††††……………………………„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€~~~~~~~~~~}ä}}}}}}}}}}||||||||||{{{{{{{{{{zzzzzzzzzzyyyyyyyyyyxxxxxxxxxxxxwwwwwwwwwwvvvvvvvvvvuuuuuuuuuuuuttttttttttssssssssssssrrrrrrrrrrqŘqqqqqqqqqqp×ppppppppppoÖoooooooooonŐnnnnnnnnnnmÔmmmmmmmmmmmmllllllllllllkŇkkkkkkkkkkkkjjjjjjjjjjjjiĐiiiiiiiiiiiihĎhhhhhhhhhhhhhhggggggggggggggfÍffffffffffffffeĚeeeeeeeeeeeeeedËddddddddddddddcĘccccccccccccccccbÉbbbbbbbbbbbbbbbbaČaaaaaaaaaaaaaaaa`Ç``````````````````_Ć____________________^Ĺ^^^^^^^^^^^^^^^^^^^^]Ä]]]]]]]]]]]]]]]]]]]]\Ă\\\\\\\\\\\\\\\\\\\\\\[Â[Â[[[[[[[[[[[[[[[[[[[[[[ZÁZÁZZZZZZZZZZZZZZZZZZZZZZZZYŔYŔYYYYYYYYYYYYYYYYYYYYYYYYYYXżXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWľWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWV˝VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUĽUĽUUUUUUUUUUUUUUUUUUUUUUUUUUUUŞŞŞŞŞŞŞŞŞw©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©v©v¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨u§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§t§t¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦sĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄrĄr¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤qŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁp˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘oˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇn                     mźźźźźźźźźźźźźźźźźźźlžžžžžžžžžžžžžžžžžžžkťťťťťťťťťťťťťťťťťjśśśśśśśśśśśśśśśśśi›››››››››››››››hšššššššššššššššš™™™™™™™™™™™™™™™™e——————————————–––––––––––––––c••••••••••••””””””””””””””“““““““““““““`’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚŚY‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰U‡‡‡‡‡‡‡‡‡‡††††††††††…………………………„„„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€~~~~~~~~~~~~}}}}}}}}}}||||||||||{{{{{{{{{{zzzzzzzzzzyyyyyyyyyyx«xxxxxxxxxxwwwwwwwwwwvvvvvvvvvvu¨uuuuuuuuuutttttttttts¦ssssssssssrrrrrrrrrrrrqqqqqqqqqqqqppppppppppppoooooooooooonnnnnnnnnnnnmmmmmmmmmmmmlźllllllllllllkkkkkkkkkkkkjťjjjjjjjjjjjjiśiiiiiiiiiiiih›hhhhhhhhhhhhgšggggggggggggggf™ffffffffffffffeeeeeeeeeeeeeeeeddddddddddddddddc–ccccccccccccccccb•bbbbbbbbbbbbbbbba”aaaaaaaaaaaaaaaa`“``````````````````_’____________________^‘^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]\Ź\\\\\\\\\\\\\\\\\\\\\\\\[Ž[[[[[[[[[[[[[[[[[[[[[[[[ZŤZZZZZZZZZZZZZZZZZZZZZZZZZZYŚYYYYYYYYYYYYYYYYYYYYYYYYYYX‹X‹XXXXXXXXXXXXXXXXXXXXXXXXXXXXWŠWŠWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWV‰V‰VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUUUŞŞŞŞŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                      źźźźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśśśś››››››››››››››››šššššššššššššššššš™™™™™™™™™™™™™™™™————————————————––––––––––––––••••••••••••••””””””””””””””““““““““““““’’’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡‡‡††††††††††…………………………„„„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€~~~~~~~~~~~~}}}}}}}}}}||||||||||{{{{{{{{{{zzzzzzzzzzyyyyyyyyyyyyxxxxxxxxxxwwwwwwwwwwvvvvvvvvvvvvuuuuuuuuuuttttttttttttssssssssssrrrrrrrrrrrrqqqqqqqqqqqqppppppppppppoooooooooooonnnnnnnnnnnnmmmmmmmmmmmmmmllllllllllllkkkkkkkkkkkkkkjjjjjjjjjjjjjjiiiiiiiiiiiiiihhhhhhhhhhhhhhggggggggggggggggffffffffffffffeeeeeeeeeeeeeeeeddddddddddddddddddccccccccccccccccccbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaa````````````````````______________________^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUUUUŞŞŞŞ©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                      źźźźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśśśś››››››››››››››››››šššššššššššššššš™™™™™™™™™™™™™™™™——————————————––––––––––––––••••••••••••••””””””””””””””““““““““““““““’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡‡‡††††††††††…………………………„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€~~~~~~~~~~~~}}}}}}}}}}||||||||||{{{{{{{{{{zzzzzzzzzzyyyyyyyyyyyyxxxxxxxxxxwwwwwwwwwwwwvvvvvvvvvvuuuuuuuuuuttttttttttttssssssssssssrrrrrrrrrrrrqqqqqqqqqqppppppppppppoooooooooooonnnnnnnnnnnnnnmmmmmmmmmmmmllllllllllllllkkkkkkkkkkkkjjjjjjjjjjjjjjiiiiiiiiiiiiiihhhhhhhhhhhhhhhhggggggggggggggffffffffffffffffeeeeeeeeeeeeeeeeddddddddddddddddddccccccccccccccccbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaa````````````````````______________________^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUUUUUUUUUUUUUU©Ü©Ü©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨Ű¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§Ú§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦Ů¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄŘĄŘĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤×¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁÖŁÖŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘Ő˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇÔˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ Ó                    źŇźźźźźźźźźźźźźźźźźźžŃžžžžžžžžžžžžžžžžžžťĐťťťťťťťťťťťťťťťťśĎśśśśśśśśśśśśśśśś›Î››››››››››››››››šÍšššššššššššššš™Ě™™™™™™™™™™™™™™Ë—Ę————————————–É––––––––––––––••••••••••••••””””””””””””””““““““““““““’Ĺ’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŹÂŹŹŹŹŹŹŹŹŹŹŽÁŽŽŽŽŽŽŽŽŽŽŤŔŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹Š˝ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡†ą††††††††††…………………………„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€~~~~~~~~~~~~}}}}}}}}}}||||||||||{{{{{{{{{{zzzzzzzzzzzGyyyyyyyyyyxxxxxxxxxxwwwwwwwwwwwwvvvvvvvvvvuuuuuuuuuuuBttttttttttssssssssssssrrrrrrrrrrrrqqqqqqqqqqq>ppppppppppp=ooooooooooo¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤=ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ<˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘;ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ:                     9źźźźźźźźźźźźźźźźźźź8žžžžžžžžžžžžžžžžžžž7ťťťťťťťťťťťťťťťťť6śśśśśśśśśśśśśśśśśś›››››››››››››››››4šššššššššššššššš™™™™™™™™™™™™™™™™———————————————0–––––––––––––/••••••••••••••”””””””””””””-“““““““““““““,’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘‘*)ŹŹŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŤ&ŚŚŚŚŚŚŚŚŚŚŚ%‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡†††††††††††…………………………„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€~~~~~~~~~~~~}}}}}}}}}}||||||||||{{{{{{{{{{zázzzzzzzzzzyyyyyyyyyyxxxxxxxxxxxxwwwwwwwwwwvvvvvvvvvvuÜuuuuuuuuuuttttttttttttssssssssssrŮrrrrrrrrrrqŘqqqqqqqqqqp×ppppppppppoÖoooooooooonŐnnnnnnnnnnnnmmmmmmmmmmmmlÓllllllllllllkkkkkkkkkkkkjŃjjjjjjjjjjjjiĐiiiiiiiiiiiiiihhhhhhhhhhhhhhgÎggggggggggggggffffffffffffffffeeeeeeeeeeeeeeeedËddddddddddddddcĘccccccccccccccccbÉbbbbbbbbbbbbbbbbaČaaaaaaaaaaaaaaaaaa`Ç``````````````````_Ć____________________^Ĺ^^^^^^^^^^^^^^^^^^^^]Ä]]]]]]]]]]]]]]]]]]]]]]\Ă\\\\\\\\\\\\\\\\\\\\\\[Â[Â[[[[[[[[[[[[[[[[[[[[[[[[ZÁZZZZZZZZZZZZZZZZZZZZZZZZZZYŔYYYYYYYYYYYYYYYYYYYYYYYYYYYYXżXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWľWľWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWV˝VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUĽUUUUUUUUUUUU©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©v©v¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨u¨u§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§t§t¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦sĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄr¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤qŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁp˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘oˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇn                     mźźźźźźźźźźźźźźźźźźźlžžžžžžžžžžžžžžžžžžžkťťťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśśśśśi›››››››››››››››hšššššššššššššššššg™™™™™™™™™™™™™™™fe—————————————d––––––––––––––•••••••••••••••b””””””””””””““““““““““““““’’’’’’’’’’’’’_‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŹŹŹ\ŽŽŽŽŽŽŽŽŽŽŽ[ŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠW‰‰‰‰‰‰‰‰‰‰U‡‡‡‡‡‡‡‡‡‡††††††††††………………………………„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€~~~~~~~~~~~~}}}}}}}}}}||||||||||{{{{{{{{{{{{zzzzzzzzzzyyyyyyyyyyxxxxxxxxxxxxwwwwwwwwwwvvvvvvvvvvvvuuuuuuuuuut§ttttttttttssssssssssssrrrrrrrrrrrrqqqqqqqqqqqqppppppppppppoooooooooooonnnnnnnnnnnnm mmmmmmmmmmmmllllllllllllkžkkkkkkkkkkkkjťjjjjjjjjjjjjiśiiiiiiiiiiiih›hhhhhhhhhhhhhhggggggggggggggf™ffffffffffffffeeeeeeeeeeeeeeeeeddddddddddddddddc–ccccccccccccccccb•bbbbbbbbbbbbbbbba”aaaaaaaaaaaaaaaaaa`“``````````````````_’____________________^‘^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]\Ź\\\\\\\\\\\\\\\\\\\\\\\\[Ž[[[[[[[[[[[[[[[[[[[[[[[[ZŤZZZZZZZZZZZZZZZZZZZZZZZZZZYŚYŚYYYYYYYYYYYYYYYYYYYYYYYYYYX‹X‹XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWŠWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWV‰V‰VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUUUU©©©©©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                      źźźźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśśśś››››››››››››››››šššššššššššššššššš™™™™™™™™™™™™™™™™——————————————––––––––––––––––••••••••••••••””””””””””””””““““““““““““““’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡‡‡††††††††††………………………………„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€~~~~~~~~~~~~}}}}}}}}}}||||||||||{{{{{{{{{{{{zzzzzzzzzzyyyyyyyyyyxxxxxxxxxxxxwwwwwwwwwwvvvvvvvvvvvvuuuuuuuuuuuuttttttttttssssssssssssrrrrrrrrrrrrqqqqqqqqqqqqppppppppppppoooooooooooonnnnnnnnnnnnnnmmmmmmmmmmmmllllllllllllllkkkkkkkkkkkkkkjjjjjjjjjjjjjjiiiiiiiiiiiiiihhhhhhhhhhhhhhggggggggggggggggffffffffffffffffeeeeeeeeeeeeeeeeddddddddddddddddddccccccccccccccccccbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaa````````````````````______________________^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUUUUUU©©©©©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                        źźźźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśśśś››››››››››››››››››šššššššššššššššš™™™™™™™™™™™™™™™™————————————————––––––––––––––••••••••••••••””””””””””””””““““““““““““““’’’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡‡‡††††††††††………………………………„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€~~~~~~~~~~}}}}}}}}}}||||||||||{{{{{{{{{{{{zzzzzzzzzzyyyyyyyyyyyyxxxxxxxxxxwwwwwwwwwwwwvvvvvvvvvvuuuuuuuuuuuuttttttttttttssssssssssrrrrrrrrrrrrqqqqqqqqqqqqppppppppppppoooooooooooooonnnnnnnnnnnnmmmmmmmmmmmmmmllllllllllllkkkkkkkkkkkkkkjjjjjjjjjjjjjjiiiiiiiiiiiiiihhhhhhhhhhhhhhhhggggggggggggggggffffffffffffffffeeeeeeeeeeeeeeeeddddddddddddddddddccccccccccccccccccbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaa````````````````````______________________^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUUUU©©©©©©©©©©©©©©©©©©©©©©©©¨Ű¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§Ú§Ú§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦Ů¦Ů¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄŘĄŘĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤×¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁÖŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘Ő˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇÔˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ Ó                      źŇźźźźźźźźźźźźźźźźźźžŃžžžžžžžžžžžžžžžžžžťĐťťťťťťťťťťťťťťťťťťśĎśśśśśśśśśśśśśśśś›Î››››››››››››››››šÍšššššššššššššš™Ě™™™™™™™™™™™™™™™™——————————————–É––––––––––––––••••••••••••••””””””””””””””““““““““““““““’’’’’’’’’’’’‘Ä‘‘‘‘‘‘‘‘‘‘‘‘ŹÂŹŹŹŹŹŹŹŹŹŹŽÁŽŽŽŽŽŽŽŽŽŽŤŔŤŤŤŤŤŤŤŤŤŤŚżŚŚŚŚŚŚŚŚŚŚ‹ľ‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰»‡‡‡‡‡‡‡‡‡‡‡‡††††††††††…………………………„·„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€~~~~~~~~~~}}}}}}}}}}|||||||||||I{{{{{{{{{{zzzzzzzzzzyyyyyyyyyyyyxxxxxxxxxxwwwwwwwwwwwwvvvvvvvvvvuuuuuuuuuuuuttttttttttttsssssssssss@rrrrrrrrrrr?qqqqqqqqqqq>ppppppppppp=oooooooooooonnnnnnnnnnnnn;mmmmmmmmmmmmlllllllllllll9kkkkkkkkkkkkk8jjjjjjjjjjjjj7iiiiiiiiiiiii6hhhhhhhhhhhhhhh5ggggggggggggggfffffffffffffffff3eeeeeeeeeeeeeee2ddddddddddddddddd1ccccccccccccccccc0bbbbbbbbbbbbbbbbb/aaaaaaaaaaaaaaaaaaa.```````````````````-_____________________,^^^^^^^^^^^^^^^^^^^^^+]]]]]]]]]]]]]]]]]]]]]]]*]*\\\\\\\\\\\\\\\\\\\\\\\)[[[[[[[[[[[[[[[[[[[[[[[[[[[(ZZZZZZZZZZZZZZZZZZZZZZZZZZZ'YYYYYYYYYYYYYYYYYYYYYYYYYYYYY&Y&XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX%WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW$W$VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV#UU©©©©©©©©©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§ ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                       źźźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśśś›››››››››››››››››šššššššššššššššš™™™™™™™™™™™™™™™™˙—ţ——————————————––––––––––––––•ü••••••••••••”ű””””””””””””“ú““““““““““““’ů’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘÷ŹŹŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡†í††††††††††…………………………„„„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚耀€€€€€€€€~~~~~~~~~~}}}}}}}}}}||||||||||||{{{{{{{{{{zzzzzzzzzzyyyyyyyyyyyyxxxxxxxxxxwwwwwwwwwwwwvvvvvvvvvvvuuuuuuuuuuttttttttttttssssssssssssrrrrrrrrrrrrqqqqqqqqqqqqppppppppppppooooooooooooonnnnnnnnnnnnmmmmmmmmmmmmmllllllllllllkkkkkkkkkkkkkkjjjjjjjjjjjjjjiiiiiiiiiiiiiiihhhhhhhhhhhhhhgggggggggggggggffffffffffffffffeeeeeeeeeeeeeeeedţddddddddddddddddccccccccccccccccccbbbbbbbbbbbbbbbbbbaűaaaaaaaaaaaaaaaaaa`ú``````````````````_ů____________________^ř^^^^^^^^^^^^^^^^^^^^]÷]÷]]]]]]]]]]]]]]]]]]]]]]\ö\\\\\\\\\\\\\\\\\\\\\\[ő[ő[[[[[[[[[[[[[[[[[[[[[[[[ZôZZZZZZZZZZZZZZZZZZZZZZZZZZYóYóYYYYYYYYYYYYYYYYYYYYYYYYYYYYXňXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWńWńWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVđVđVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVUď©©©©©©©©©©©©©©©©©©©B¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨A¨A§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§@§@¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦?ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ>¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤=ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ<˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘;˘;ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ:                     9 9źźźźźźźźźźźźźźźźźźź8žžžžžžžžžžžžžžžžžžž7ťťťťťťťťťťťťťťťťťťť6śśśśśśśśśśśśśśśśś5›››››››››››››››››4ššššššššššššššššš3™™™™™™™™™™™™™™™21———————————————0–––––––––––––/••••••••••••••””””””””””””””““““““““““““““’’’’’’’’’’’’’+‘‘‘‘‘‘‘‘‘‘‘‘)ŹŹŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠ#‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡††††††††††††…………………………„„„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€~~~~~~~~~~}}}}}}}}}}||||||||||||{{{{{{{{{{zzzzzzzzzzyŕyyyyyyyyyyxxxxxxxxxxwŢwwwwwwwwwwvvvvvvvvvvvvuuuuuuuuuutŰttttttttttssssssssssssrrrrrrrrrrrrqqqqqqqqqqqqppppppppppppoÖoooooooooooonnnnnnnnnnnnmmmmmmmmmmmmmmllllllllllllkŇkkkkkkkkkkkkjŃjjjjjjjjjjjjiĐiiiiiiiiiiiiiihhhhhhhhhhhhhhgÎggggggggggggggfÍffffffffffffffeĚeeeeeeeeeeeeeeeeddddddddddddddddcĘccccccccccccccccbÉbbbbbbbbbbbbbbbbbbaČaaaaaaaaaaaaaaaaaa`Ç``````````````````_Ć____________________^Ĺ^^^^^^^^^^^^^^^^^^^^^^]Ä]]]]]]]]]]]]]]]]]]]]]]\Ă\\\\\\\\\\\\\\\\\\\\\\\\[Â[[[[[[[[[[[[[[[[[[[[[[[[ZÁZZZZZZZZZZZZZZZZZZZZZZZZZZZZYŔYYYYYYYYYYYYYYYYYYYYYYYYYYYYXżXżXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWľWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWV˝VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV©©©©©©©©©©©©©©©v©v¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨u§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§t¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦s¦sĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄrĄr¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤q¤qŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁp˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘oˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇn                     mźźźźźźźźźźźźźźźźźźźźźlžžžžžžžžžžžžžžžžžžžkťťťťťťťťťťťťťťťťťťťjśśśśśśśśśśśśśśśśśś››››››››››››››››››šššššššššššššššššg™™™™™™™™™™™™™™™f———————————————d––––––––––––––•••••••••••••••b”””””””””””””a“““““““““““““`’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘‘^ŹŹŹŹŹŹŹŹŹŹŹŹŹ\ŽŽŽŽŽŽŽŽŽŽŽ[ŤŤŤŤŤŤŤŤŤŤŤZŚŚŚŚŚŚŚŚŚŚŚY‹‹‹‹‹‹‹‹‹‹‹XŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰‰U‡‡‡‡‡‡‡‡‡‡††††††††††††…………………………„„„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€~~~~~~~~~~}}}}}}}}}}||||||||||||{{{{{{{{{{zzzzzzzzzzzzyyyyyyyyyyxxxxxxxxxxxxwwwwwwwwwwvvvvvvvvvvvvuuuuuuuuuuuutttttttttts¦ssssssssssrĄrrrrrrrrrrq¤qqqqqqqqqqpŁppppppppppppoooooooooooonnnnnnnnnnnnm mmmmmmmmmmmmlźllllllllllllkkkkkkkkkkkkkkjjjjjjjjjjjjjjiśiiiiiiiiiiiih›hhhhhhhhhhhhhhgšggggggggggggggf™ffffffffffffffeeeeeeeeeeeeeeed—ddddddddddddddddc–ccccccccccccccccb•bbbbbbbbbbbbbbbbbba”aaaaaaaaaaaaaaaaaa`“``````````````````_’____________________^‘^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]\Ź\\\\\\\\\\\\\\\\\\\\\\\\[Ž[[[[[[[[[[[[[[[[[[[[[[[[ZŤZŤZZZZZZZZZZZZZZZZZZZZZZZZZZYŚYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYX‹XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWŠWŠWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWV‰V‰VVVVVVVVVVVVVVVVVVVVVVVVVVVVVV©©©©©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                      źźźźźźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśśśśśś››››››››››››››››››šššššššššššššššš™™™™™™™™™™™™™™™™——————————————––––––––––––––––••••••••••••••””””””””””””””““““““““““““““’’’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡‡‡††††††††††††…………………………„„„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€~~~~~~~~~~}}}}}}}}}}||||||||||||{{{{{{{{{{zzzzzzzzzzzzyyyyyyyyyyxxxxxxxxxxxxwwwwwwwwwwvvvvvvvvvvvvuuuuuuuuuuuuttttttttttttssssssssssssrrrrrrrrrrrrqqqqqqqqqqqqppppppppppppoooooooooooonnnnnnnnnnnnnnmmmmmmmmmmmmmmllllllllllllkkkkkkkkkkkkkkjjjjjjjjjjjjjjjjiiiiiiiiiiiiiihhhhhhhhhhhhhhhhggggggggggggggggffffffffffffffffeeeeeeeeeeeeeeeeddddddddddddddddddccccccccccccccccccbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaa````````````````````______________________^^^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV©©©©©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                      źźźźźźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśśśśśś››››››››››››››››››šššššššššššššššš™™™™™™™™™™™™™™™™™™————————————————––––––––––––––••••••••••••••••””””””””””””””““““““““““““““’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡‡‡††††††††††………………………………„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€~~~~~~~~~~}}}}}}}}}}||||||||||||{{{{{{{{{{zzzzzzzzzzzzyyyyyyyyyyxxxxxxxxxxxxwwwwwwwwwwwwvvvvvvvvvvuuuuuuuuuuuuttttttttttttssssssssssssrrrrrrrrrrrrqqqqqqqqqqqqppppppppppppoooooooooooooonnnnnnnnnnnnmmmmmmmmmmmmmmllllllllllllllkkkkkkkkkkkkkkjjjjjjjjjjjjjjiiiiiiiiiiiiiiiihhhhhhhhhhhhhhggggggggggggggggffffffffffffffffeeeeeeeeeeeeeeeeeeddddddddddddddddddccccccccccccccccccbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaa````````````````````______________________^^^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVVVVVVVVVVVVVVVV©©©©©©©©¨Ű¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§Ú§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦Ů¦Ů¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄŘĄŘĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤×¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁÖŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘Ő˘Ő˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇÔˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ Ó                    źŇźźźźźźźźźźźźźźźźźźźźžŃžžžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťťťśĎśśśśśśśśśśśśśśśśśś›Î››››››››››››››››šÍšššššššššššššššš™™™™™™™™™™™™™™™™Ë—Ę——————————————–É––––––––––––––••••••••••••••”Ç””””””””””””“Ć““““““““““““’Ĺ’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŹŹŽÁŽŽŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰Ľ‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡‡‡††††††††††………………………………„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€~~~~~~~~~~}}}}}}}}}}}J||||||||||{{{{{{{{{{{HzzzzzzzzzzyyyyyyyyyyyFxxxxxxxxxxwwwwwwwwwwwwvvvvvvvvvvvCuuuuuuuuuuttttttttttttssssssssssssrrrrrrrrrrrrqqqqqqqqqqqqq>ppppppppppp=oooooooooooonnnnnnnnnnnnn;mmmmmmmmmmmmm:llllllllllllkkkkkkkkkkkkkkk8jjjjjjjjjjjjj7iiiiiiiiiiiiiihhhhhhhhhhhhhhh5ggggggggggggggg4fffffffffffffff3eeeeeeeeeeeeeeeee2ddddddddddddddddd1ccccccccccccccccc0bbbbbbbbbbbbbbbbbbb/aaaaaaaaaaaaaaaaaaa.```````````````````-_____________________,^^^^^^^^^^^^^^^^^^^^^^^+]]]]]]]]]]]]]]]]]]]]]]]*\\\\\\\\\\\\\\\\\\\\\\\\\)[[[[[[[[[[[[[[[[[[[[[[[[[[[(ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ'YYYYYYYYYYYYYYYYYYYYYYYYYYYYY&Y&XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX%X%WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW$W$VVVVVVVVVVVVVVVVVVVVVVVV©©©©©©¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§ ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ Ą ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                     źźźźźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśśśśś›››››››››››››››››ššššššššššššššššš™™™™™™™™™™™™™™˙—ţ——————————————–ý––––––––––––––•ü••••••••••••”ű””””””””””””””““““““““““““““’’’’’’’’’’’’‘ř‘‘‘‘‘‘‘‘‘‘‘‘÷ŹŹŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŽŽŤôŤŤŤŤŤŤŤŤŤŤŚóŚŚŚŚŚŚŚŚŚŚ‹ň‹‹‹‹‹‹‹‹‹‹ŠńŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡†í††††††††††………………………………„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€~~~~~~~~~~}}}}}}}}}}}}||||||||||{{{{{{{{{{{{zzzzzzzzzzyyyyyyyyyyyyxxxxxxxxxxwwwwwwwwwwwwvvvvvvvvvvvvuuuuuuuuuuuttttttttttt sssssssssss rrrrrrrrrrr qqqqqqqqqqqqppppppppppppooooooooooooonnnnnnnnnnnnmmmmmmmmmmmmmmlllllllllllllkkkkkkkkkkkkkkjjjjjjjjjjjjjjiiiiiiiiiiiiiiihhhhhhhhhhhhhhhgggggggggggggggffffffffffffffe˙eeeeeeeeeeeeeeeedţddddddddddddddddccccccccccccccccccbübbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaa`ú``````````````````_ů____________________^ř^^^^^^^^^^^^^^^^^^^^^^]÷]]]]]]]]]]]]]]]]]]]]]]\ö\\\\\\\\\\\\\\\\\\\\\\\\[ő[ő[[[[[[[[[[[[[[[[[[[[[[[[ZôZôZZZZZZZZZZZZZZZZZZZZZZZZZZYóYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXňXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWńWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVđVVVVVVVVVVVVVVVVVVVVVV©B©B¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨A§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§@§@¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦?ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ>¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤=¤=ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ<˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘;ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ:                     9źźźźźźźźźźźźźźźźźźźźź8žžžžžžžžžžžžžžžžžžžžž7ťťťťťťťťťťťťťťťťťťť6śśśśśśśśśśśśśśśśś5›››››››››››››››››4ššššššššššššššššš3™™™™™™™™™™™™™™™™————————————————–––––––––––––––/••••••••••••••”””””””””””””””-“““““““““““““,’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘‘‘)ŹŹŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡††††††††††††………………………………„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€~~~~~~~~~~}}}}}}}}}}}}||||||||||{{{{{{{{{{{{zzzzzzzzzzyyyyyyyyyyyyxxxxxxxxxxwŢwwwwwwwwwwvvvvvvvvvvvvuuuuuuuuuuuuttttttttttttssssssssssssrrrrrrrrrrrrqqqqqqqqqqqqppppppppppppoÖoooooooooooonnnnnnnnnnnnmÔmmmmmmmmmmmmlÓllllllllllllkŇkkkkkkkkkkkkjŃjjjjjjjjjjjjiĐiiiiiiiiiiiiiihĎhhhhhhhhhhhhhhggggggggggggggggffffffffffffffffeĚeeeeeeeeeeeeeeeeddddddddddddddddcĘccccccccccccccccccbÉbbbbbbbbbbbbbbbbaČaaaaaaaaaaaaaaaaaaaa`Ç``````````````````_Ć____________________^Ĺ^^^^^^^^^^^^^^^^^^^^^^]Ä]]]]]]]]]]]]]]]]]]]]]]\Ă\Ă\\\\\\\\\\\\\\\\\\\\\\\\[Â[[[[[[[[[[[[[[[[[[[[[[[[[[ZÁZZZZZZZZZZZZZZZZZZZZZZZZZZYŔYŔYYYYYYYYYYYYYYYYYYYYYYYYYYYYXżXżXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWľWľWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWV˝V˝VVVVVVVVVVVVVVVVVV¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨u¨u§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§t¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦s¦sĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄr¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤qŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁp˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘oˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇn                     mźźźźźźźźźźźźźźźźźźźźźlžžžžžžžžžžžžžžžžžžžžžkťťťťťťťťťťťťťťťťťťťjśśśśśśśśśśśśśśśśśi››››››››››››››››››šššššššššššššššššg™™™™™™™™™™™™™™™™™fe———————————————d––––––––––––––•••••••••••••••b”””””””””””””a““““““““““““““’’’’’’’’’’’’’_‘‘‘‘‘‘‘‘‘‘‘‘‘^ŹŹŹŹŹŹŹŹŹŹŹŹŹ\ŽŽŽŽŽŽŽŽŽŽŽ[ŤŤŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰‰U‡‡‡‡‡‡‡‡‡‡††††††††††††……………………………R„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€~~~~~~~~~~}}}}}}}}}}}}||||||||||{{{{{{{{{{{{zzzzzzzzzzyyyyyyyyyyyyxxxxxxxxxxxxwwwwwwwwwwv©vvvvvvvvvvuuuuuuuuuuuuttttttttttttssssssssssssrrrrrrrrrrrrqqqqqqqqqqqqpŁppppppppppppoooooooooooonˇnnnnnnnnnnnnmmmmmmmmmmmmmmllllllllllllllkkkkkkkkkkkkkkjjjjjjjjjjjjjjiśiiiiiiiiiiiiiihhhhhhhhhhhhhhgšggggggggggggggf™ffffffffffffffffeeeeeeeeeeeeeeeed—ddddddddddddddddc–ccccccccccccccccccbbbbbbbbbbbbbbbbbba”aaaaaaaaaaaaaaaaaaaa````````````````````_’____________________^‘^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]]\Ź\\\\\\\\\\\\\\\\\\\\\\\\[Ž[[[[[[[[[[[[[[[[[[[[[[[[[[ZŤZZZZZZZZZZZZZZZZZZZZZZZZZZZZYŚYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYX‹XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWŠWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWV‰V‰VVVVVVVVVVVVVV¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                      źźźźźźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśśśś››››››››››››››››››››šššššššššššššššš™™™™™™™™™™™™™™™™™™————————————————––––––––––––––––••••••••••••••””””””””””””””““““““““““““““““’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡‡‡††††††††††††…………………………„„„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€~~~~~~~~~~}}}}}}}}}}}}||||||||||{{{{{{{{{{{{zzzzzzzzzzyyyyyyyyyyyyxxxxxxxxxxxxwwwwwwwwwwwwvvvvvvvvvvuuuuuuuuuuuuttttttttttttssssssssssssrrrrrrrrrrrrqqqqqqqqqqqqqqppppppppppppoooooooooooooonnnnnnnnnnnnmmmmmmmmmmmmmmllllllllllllllkkkkkkkkkkkkkkjjjjjjjjjjjjjjjjiiiiiiiiiiiiiihhhhhhhhhhhhhhhhggggggggggggggggffffffffffffffffeeeeeeeeeeeeeeeeeeddddddddddddddddddccccccccccccccccccbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaa``````````````````````______________________^^^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVVVV¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                      źźźźźźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśśśśśś››››››››››››››››››šššššššššššššššššš™™™™™™™™™™™™™™™™————————————————––––––––––––––––••••••••••••••••””””””””””””””““““““““““““““’’’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡‡‡††††††††††††…………………………„„„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€~~~~~~~~~~}}}}}}}}}}}}||||||||||{{{{{{{{{{{{zzzzzzzzzzzzyyyyyyyyyyxxxxxxxxxxxxwwwwwwwwwwwwvvvvvvvvvvvvuuuuuuuuuuuuttttttttttttssssssssssssrrrrrrrrrrrrqqqqqqqqqqqqppppppppppppppoooooooooooonnnnnnnnnnnnnnmmmmmmmmmmmmmmllllllllllllllkkkkkkkkkkkkkkjjjjjjjjjjjjjjiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhggggggggggggggggffffffffffffffffeeeeeeeeeeeeeeeeeeddddddddddddddddddccccccccccccccccccbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaa``````````````````````______________________^^^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVVVVVVVVV¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§Ú§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦Ů¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄŘĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤×¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁÖŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘Ő˘Ő˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇÔˇÔˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ Ó                    źŇźźźźźźźźźźźźźźźźźźźźžŃžžžžžžžžžžžžžžžžžžžžťĐťťťťťťťťťťťťťťťťťťśĎśśśśśśśśśśśśśśśśśś›Î››››››››››››››››šÍšššššššššššššššš™Ě™™™™™™™™™™™™™™Ë————————————————––––––––––––––––••••••••••••••”Ç””””””””””””“Ć““““““““““““““’’’’’’’’’’’’‘Ä‘‘‘‘‘‘‘‘‘‘‘‘ĂŹŹŹŹŹŹŹŹŹŹŹŹŽÁŽŽŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹‹‹Š˝ŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡‡‡††††††††††…¸…………………………„„„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€~~~~~~~~~~}}}}}}}}}}}}||||||||||{{{{{{{{{{{{zzzzzzzzzzzzyyyyyyyyyyxxxxxxxxxxxxwwwwwwwwwwwwvvvvvvvvvvvvuuuuuuuuuuuuttttttttttttssssssssssssrrrrrrrrrrrrqqqqqqqqqqqqppppppppppppppoooooooooooonnnnnnnnnnnnnnmmmmmmmmmmmmmmllllllllllllllkkkkkkkkkkkkkkjjjjjjjjjjjjjjj7iiiiiiiiiiiiiihhhhhhhhhhhhhhhhggggggggggggggggfffffffffffffffff3eeeeeeeeeeeeeeeee2ddddddddddddddddd1ccccccccccccccccc0bbbbbbbbbbbbbbbbbbb/aaaaaaaaaaaaaaaaaaa.`````````````````````-_____________________,_,^^^^^^^^^^^^^^^^^^^^^+]]]]]]]]]]]]]]]]]]]]]]]]]*\\\\\\\\\\\\\\\\\\\\\\\\\)[[[[[[[[[[[[[[[[[[[[[[[[[[[([(ZZZZZZZZZZZZZZZZZZZZZZZZZZZ'Z'YYYYYYYYYYYYYYYYYYYYYYYYYYYYY&Y&XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX%WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW$W$VVVVVVVV¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§ § ¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ ¦ ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ ¤ ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                     źźźźźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśśśśś›››››››››››››››››ššššššššššššššššš™™™™™™™™™™™™™™™™—ţ——————————————–ý––––––––––––––•ü••••••••••••”ű””””””””””””””““““““““““““““’ů’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘‘‘ŹöŹŹŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŽŽŤôŤŤŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡‡‡††††††††††………………………………„„„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€~~~~~~~~~~}}}}}}}}}}}}|||||||||||{{{{{{{{{{zzzzzzzzzzzzyyyyyyyyyyyxxxxxxxxxxwwwwwwwwwwwwvvvvvvvvvvvvuuuuuuuuuuuuttttttttttttssssssssssssrrrrrrrrrrrrr qqqqqqqqqqq ppppppppppppooooooooooooonnnnnnnnnnnnnmmmmmmmmmmmmmlllllllllllllkkkkkkkkkkkkkjjjjjjjjjjjjjjiiiiiiiiiiiiiiihhhhhhhhhhhhhhhgggggggggggggggffffffffffffffffe˙eeeeeeeeeeeeeeeeddddddddddddddddddcýccccccccccccccccbübbbbbbbbbbbbbbbbbbaűaaaaaaaaaaaaaaaaaa`ú````````````````````_ů______________________^ř^^^^^^^^^^^^^^^^^^^^]÷]÷]]]]]]]]]]]]]]]]]]]]]]\ö\\\\\\\\\\\\\\\\\\\\\\\\[ő[ő[[[[[[[[[[[[[[[[[[[[[[[[[[ZôZZZZZZZZZZZZZZZZZZZZZZZZZZZZYóYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXňXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWńWńWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVđVVVVVV¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨A§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§@¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦?ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ>Ą>¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤=ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ<˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘;ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ:                     9źźźźźźźźźźźźźźźźźźźźź8žžžžžžžžžžžžžžžžžžžžž7ťťťťťťťťťťťťťťťťťťť6śśśśśśśśśśśśśśśśśśś5›››››››››››››››››4ššššššššššššššššš3™™™™™™™™™™™™™™™™™21———————————————0–––––––––––––––/••••••••••••••”””””””””””””””-“““““““““““““,’’’’’’’’’’’’’+‘‘‘‘‘‘‘‘‘‘‘‘‘*ŹŹŹŹŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŤŤŤ&ŚŚŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡‡ ††††††††††………………………………„„„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€~~~~~~~~~~}}}}}}}}}}}}||||||||||||{{{{{{{{{{zzzzzzzzzzzzyyyyyyyyyyyyxxxxxxxxxxwŢwwwwwwwwwwvvvvvvvvvvvvuuuuuuuuuuuuttttttttttttssssssssssssrŮrrrrrrrrrrrrqqqqqqqqqqqqppppppppppppoÖoooooooooooonnnnnnnnnnnnnnmmmmmmmmmmmmmmllllllllllllllkkkkkkkkkkkkkkjjjjjjjjjjjjjjiĐiiiiiiiiiiiiiihĎhhhhhhhhhhhhhhgÎggggggggggggggfÍffffffffffffffffeeeeeeeeeeeeeeeedËddddddddddddddddddccccccccccccccccccbÉbbbbbbbbbbbbbbbbbbaČaaaaaaaaaaaaaaaaaa`Ç````````````````````_Ć______________________^Ĺ^^^^^^^^^^^^^^^^^^^^^^]Ä]]]]]]]]]]]]]]]]]]]]]]\Ă\\\\\\\\\\\\\\\\\\\\\\\\\\[Â[[[[[[[[[[[[[[[[[[[[[[[[[[ZÁZZZZZZZZZZZZZZZZZZZZZZZZZZZZYŔYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXżXżXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWľWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWV˝V˝VV¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨u¨u§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§t§t¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦s¦sĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄr¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤qŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁp˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘oˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇnˇn                     mźźźźźźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžžžžkťťťťťťťťťťťťťťťťťťťjśśśśśśśśśśśśśśśśśśśi››››››››››››››››››šššššššššššššššššš™™™™™™™™™™™™™™™™™f————————————————––––––––––––––––•••••••••••••••b””””””””””””””““““““““““““““’’’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŹŹŹ\ŽŽŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚŚŚŚY‹‹‹‹‹‹‹‹‹‹‹XŠŠŠŠŠŠŠŠŠŠŠW‰‰‰‰‰‰‰‰‰‰‰VU‡‡‡‡‡‡‡‡‡‡††††††††††††………………………………„„„„„„„„„„„Q‚‚‚‚‚‚‚‚‚‚‚‚N€€€€€€€€€€~~~~~~~~~~}}}}}}}}}}}}||||||||||||{{{{{{{{{{zzzzzzzzzzzzyyyyyyyyyyyyxxxxxxxxxxxxwwwwwwwwwwv©vvvvvvvvvvu¨uuuuuuuuuut§tttttttttts¦ssssssssssssrrrrrrrrrrrrqqqqqqqqqqqqpŁppppppppppppoooooooooooonˇnnnnnnnnnnnnm mmmmmmmmmmmmlźllllllllllllkžkkkkkkkkkkkkjťjjjjjjjjjjjjjjiśiiiiiiiiiiiiiihhhhhhhhhhhhhhhhggggggggggggggggf™ffffffffffffffeeeeeeeeeeeeeeeeed—ddddddddddddddddc–ccccccccccccccccccb•bbbbbbbbbbbbbbbbbba”aaaaaaaaaaaaaaaaaa`“````````````````````_’______________________^‘^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]\Ź\Ź\\\\\\\\\\\\\\\\\\\\\\\\[Ž[[[[[[[[[[[[[[[[[[[[[[[[[[ZŤZZZZZZZZZZZZZZZZZZZZZZZZZZZZYŚYŚYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYX‹XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWŠWŠWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWV‰¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                        źźźźźźźźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśśśśśś››››››››››››››››››››šššššššššššššššššš™™™™™™™™™™™™™™™™————————————————––––––––––––––––••••••••••••••””””””””””””””””““““““““““““““’’’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡‡‡††††††††††††………………………………„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€€€~~~~~~~~~~}}}}}}}}}}}}||||||||||||{{{{{{{{{{zzzzzzzzzzzzyyyyyyyyyyyyxxxxxxxxxxxxwwwwwwwwwwwwvvvvvvvvvvvvuuuuuuuuuuuuttttttttttttssssssssssssrrrrrrrrrrrrqqqqqqqqqqqqqqppppppppppppoooooooooooooonnnnnnnnnnnnnnmmmmmmmmmmmmmmllllllllllllllkkkkkkkkkkkkkkjjjjjjjjjjjjjjjjiiiiiiiiiiiiiihhhhhhhhhhhhhhhhggggggggggggggggggffffffffffffffffeeeeeeeeeeeeeeeeeeddddddddddddddddddccccccccccccccccccccbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaa``````````````````````________________________^^^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                        źźźźźźźźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśśśś››››››››››››››››››››šššššššššššššššššš™™™™™™™™™™™™™™™™™™————————————————––––––––––––––––••••••••••••••••””””””””””””””““““““““““““““’’’’’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡‡‡††††††††††††………………………………„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€€€~~~~~~~~~~}}}}}}}}}}}}||||||||||||{{{{{{{{{{{{zzzzzzzzzzyyyyyyyyyyyyxxxxxxxxxxxxwwwwwwwwwwwwvvvvvvvvvvvvuuuuuuuuuuuuttttttttttttssssssssssssrrrrrrrrrrrrrrqqqqqqqqqqqqppppppppppppppoooooooooooonnnnnnnnnnnnnnmmmmmmmmmmmmmmllllllllllllllkkkkkkkkkkkkkkkkjjjjjjjjjjjjjjiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhggggggggggggggggffffffffffffffffffeeeeeeeeeeeeeeeeeeddddddddddddddddddccccccccccccccccccccbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaa``````````````````````________________________^^^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW¨¨¨¨¨¨¨¨¨¨¨¨¨¨§Ú§Ú§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦Ů¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄŘĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤×¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁÖŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘Ő˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇÔˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ Ó                      źŇźźźźźźźźźźźźźźźźźźźźźźžŃžžžžžžžžžžžžžžžžžžťĐťťťťťťťťťťťťťťťťťťťťśĎśśśśśśśśśśśśśśśśśś››››››››››››››››››šÍšššššššššššššššš™Ě™™™™™™™™™™™™™™™™Ë—Ę————————————————––––––––––––––•Č••••••••••••••”Ç””””””””””””””““““““““““““““’’’’’’’’’’’’’’‘Ä‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŹŹŽÁŽŽŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŤŤŚżŚŚŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡‡‡††††††††††††…………………………„·„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€€€~~~~~~~~~~}}}}}}}}}}}}||||||||||||{{{{{{{{{{{{zzzzzzzzzzyyyyyyyyyyyyxxxxxxxxxxxxwwwwwwwwwwwwvvvvvvvvvvvvuuuuuuuuuuuuttttttttttttsssssssssssss@rrrrrrrrrrrrqqqqqqqqqqqqppppppppppppppooooooooooooo¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤=ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ<˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘;ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ:                       9źźźźźźźźźźźźźźźźźźźźźźź8žžžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťťťťťť6śśśśśśśśśśśśśśśśśśś5›››››››››››››››››4šššššššššššššššššš™™™™™™™™™™™™™™™™™21———————————————0–––––––––––––––/•••••••••••••••.””””””””””””””““““““““““““““’’’’’’’’’’’’’’’+‘‘‘‘‘‘‘‘‘‘‘‘‘*ŹŹŹŹŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŽŽŽ'ŤŤŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹‹‹‹$ŠŠŠŠŠŠŠŠŠŠŠ#‰‰‰‰‰‰‰‰‰‰‰"‡‡‡‡‡‡‡‡‡‡‡ †††††††††††…………………………„„„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€€€~~~~~~~~~~~~}}}}}}}}}}||||||||||||{{{{{{{{{{{{zzzzzzzzzzzzyyyyyyyyyyxßxxxxxxxxxxwŢwwwwwwwwwwvÝvvvvvvvvvvuÜuuuuuuuuuutŰttttttttttttssssssssssssrrrrrrrrrrrrqŘqqqqqqqqqqqqppppppppppppoÖoooooooooooonŐnnnnnnnnnnnnmmmmmmmmmmmmmmlÓllllllllllllkŇkkkkkkkkkkkkkkjjjjjjjjjjjjjjiĐiiiiiiiiiiiiiihĎhhhhhhhhhhhhhhgÎggggggggggggggggffffffffffffffffeĚeeeeeeeeeeeeeeeedËddddddddddddddddddccccccccccccccccccbÉbbbbbbbbbbbbbbbbbbaČaaaaaaaaaaaaaaaaaaaa`Ç````````````````````_Ć______________________^Ĺ^^^^^^^^^^^^^^^^^^^^^^]Ä]]]]]]]]]]]]]]]]]]]]]]]]\Ă\\\\\\\\\\\\\\\\\\\\\\\\\\[Â[[[[[[[[[[[[[[[[[[[[[[[[[[ZÁZÁZZZZZZZZZZZZZZZZZZZZZZZZZZZZYŔYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXżXżXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWľWľWWWWWWWWWWWWWWWWWWWWWWWW¨¨¨¨¨¨¨u§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§t§t¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦sĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄr¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤qŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁp˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘o˘oˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇn                       mźźźźźźźźźźźźźźźźźźźźźźźlžžžžžžžžžžžžžžžžžžžžžkťťťťťťťťťťťťťťťťťťťjśśśśśśśśśśśśśśśśśśśi››››››››››››››››››šššššššššššššššššššg™™™™™™™™™™™™™™™™e————————————————––––––––––––––––•••••••••••••••b”””””””””””””””a“““““““““““““`’’’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘‘‘]ŹŹŹŹŹŹŹŹŹŹŹŹŹ\ŽŽŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŤŤŤZŚŚŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰‰U‡‡‡‡‡‡‡‡‡‡††††††††††††………………………………„„„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€€€~~~~~~~~~~~~}}}}}}}}}}||||||||||||{{{{{{{{{{{{zzzzzzzzzzzzyyyyyyyyyyyyxxxxxxxxxxxxwwwwwwwwwwwwvvvvvvvvvvvvuuuuuuuuuuuuttttttttttttssssssssssssrĄrrrrrrrrrrrrqqqqqqqqqqqqpŁppppppppppppoooooooooooooonnnnnnnnnnnnm mmmmmmmmmmmmmmllllllllllllllkkkkkkkkkkkkkkjťjjjjjjjjjjjjjjiśiiiiiiiiiiiiiihhhhhhhhhhhhhhhhgšggggggggggggggf™ffffffffffffffffeeeeeeeeeeeeeeeeed—ddddddddddddddddc–ccccccccccccccccccb•bbbbbbbbbbbbbbbbbba”aaaaaaaaaaaaaaaaaaaa`“````````````````````_’______________________^‘^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]]\Ź\\\\\\\\\\\\\\\\\\\\\\\\\\[Ž[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZŤZZZZZZZZZZZZZZZZZZZZZZZZZZZZYŚYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYX‹XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWŠWWWWWWWWWWWWWWWWWWWWWW¨¨¨¨¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                        źźźźźźźźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśśśśśś››››››››››››››››››››šššššššššššššššššš™™™™™™™™™™™™™™™™™™——————————————————––––––––––––––––••••••••••••••””””””””””””””””““““““““““““““’’’’’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡‡‡††††††††††††………………………………„„„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€€€~~~~~~~~~~~~}}}}}}}}}}||||||||||||{{{{{{{{{{{{zzzzzzzzzzzzyyyyyyyyyyyyxxxxxxxxxxxxwwwwwwwwwwwwvvvvvvvvvvvvuuuuuuuuuuuuttttttttttttssssssssssssssrrrrrrrrrrrrqqqqqqqqqqqqqqppppppppppppoooooooooooooonnnnnnnnnnnnnnmmmmmmmmmmmmmmllllllllllllllkkkkkkkkkkkkkkkkjjjjjjjjjjjjjjjjiiiiiiiiiiiiiihhhhhhhhhhhhhhhhhhggggggggggggggggffffffffffffffffffeeeeeeeeeeeeeeeeeeddddddddddddddddddccccccccccccccccccccbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaaaa``````````````````````________________________^^^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWWWWWW¨¨§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                        źźźźźźźźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśśśśśś››››››››››››››››››››šššššššššššššššššš™™™™™™™™™™™™™™™™™™————————————————––––––––––––––––••••••••••••••••””””””””””””””““““““““““““““““’’’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡‡‡††††††††††††………………………………„„„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€€€~~~~~~~~~~~~}}}}}}}}}}||||||||||||{{{{{{{{{{{{zzzzzzzzzzzzyyyyyyyyyyyyxxxxxxxxxxxxwwwwwwwwwwwwvvvvvvvvvvvvuuuuuuuuuuuuttttttttttttttssssssssssssrrrrrrrrrrrrqqqqqqqqqqqqqqppppppppppppppoooooooooooonnnnnnnnnnnnnnmmmmmmmmmmmmmmmmllllllllllllllkkkkkkkkkkkkkkjjjjjjjjjjjjjjjjiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhggggggggggggggggggffffffffffffffffffeeeeeeeeeeeeeeeeeeddddddddddddddddddccccccccccccccccccccbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaaaa``````````````````````________________________^^^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWWWWWWWWWWWWW§Ú§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦Ů¦Ů¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄŘĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤×¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁÖŁÖŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘Ő˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇÔˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ Ó                      źŇźźźźźźźźźźźźźźźźźźźźźźžŃžžžžžžžžžžžžžžžžžžžžťĐťťťťťťťťťťťťťťťťťťśĎśśśśśśśśśśśśśśśśśś›Î››››››››››››››››››šÍšššššššššššššššš™Ě™™™™™™™™™™™™™™™™Ë—Ę——————————————–É––––––––––––––•Č••••••••••••••”Ç””””””””””””””““““““““““““““’Ĺ’’’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŹŹŽÁŽŽŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹‹‹Š˝ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡‡‡††††††††††††………………………………„„„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€€€~~~~~~~~~~~~}}}}}}}}}}}J||||||||||{{{{{{{{{{{{zzzzzzzzzzzzyyyyyyyyyyyyxxxxxxxxxxxxwwwwwwwwwwwwvvvvvvvvvvvvuuuuuuuuuuuuuBttttttttttttssssssssssssrrrrrrrrrrrrr?qqqqqqqqqqqqppppppppppppppoooooooooooooĄ>¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤=ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ<˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘;ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ:                       9źźźźźźźźźźźźźźźźźźźźźźź8žžžžžžžžžžžžžžžžžžžžž7ťťťťťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśśśśśśś5›››››››››››››››››4ššššššššššššššššššš3™™™™™™™™™™™™™™™™™2—————————————————0–––––––––––––––/•••••••••••••••.””””””””””””””“““““““““““““““,’’’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŽŽŽ'ŤŤŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚŚŚŚ%‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰‰‰"!‡‡‡‡‡‡‡‡‡‡‡‡††††††††††††………………………………„„„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€€€~~~~~~~~~~~~}}}}}}}}}}}}||||||||||{â{{{{{{{{{{zázzzzzzzzzzyyyyyyyyyyyyxxxxxxxxxxxxwŢwwwwwwwwwwvÝvvvvvvvvvvvvuuuuuuuuuuuuttttttttttttssssssssssssssrrrrrrrrrrrrqqqqqqqqqqqqqqppppppppppppoÖoooooooooooonŐnnnnnnnnnnnnmÔmmmmmmmmmmmmmmllllllllllllllkkkkkkkkkkkkkkkkjjjjjjjjjjjjjjiĐiiiiiiiiiiiiiihĎhhhhhhhhhhhhhhhhggggggggggggggggfÍffffffffffffffffeĚeeeeeeeeeeeeeeeedËddddddddddddddddddccccccccccccccccccccbbbbbbbbbbbbbbbbbbbbaČaaaaaaaaaaaaaaaaaaaa`Ç````````````````````_Ć______________________^Ĺ^^^^^^^^^^^^^^^^^^^^^^^^]Ä]]]]]]]]]]]]]]]]]]]]]]]]\Ă\\\\\\\\\\\\\\\\\\\\\\\\\\[Â[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZÁZZZZZZZZZZZZZZZZZZZZZZZZZZZZYŔYŔYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXżXżXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWľWWWWWWWWWW§§§§§§§§§§§§§§§§§§§§§§§§§§§§§t¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦sĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄr¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤qŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁp˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘oˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇn                       mźźźźźźźźźźźźźźźźźźźźźźźlžžžžžžžžžžžžžžžžžžžžžkťťťťťťťťťťťťťťťťťťťťťjśśśśśśśśśśśśśśśśśśśi››››››››››››››››››šššššššššššššššššššg™™™™™™™™™™™™™™™™™fe———————————————d––––––––––––––––••••••••••••••••”””””””””””””””a““““““““““““““’’’’’’’’’’’’’’’_‘‘‘‘‘‘‘‘‘‘‘‘‘^]ŹŹŹŹŹŹŹŹŹŹŹŹŹ\ŽŽŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡‡‡‡T†††††††††††S……………………………R„„„„„„„„„„„Q‚‚‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€€€~~~~~~~~~~~~}}}}}}}}}}}}||||||||||||{{{{{{{{{{{{zzzzzzzzzzy¬yyyyyyyyyyx«xxxxxxxxxxxxwwwwwwwwwwwwvvvvvvvvvvvvuuuuuuuuuuuutttttttttttts¦ssssssssssssrrrrrrrrrrrrq¤qqqqqqqqqqqqppppppppppppppoooooooooooooonnnnnnnnnnnnnnmmmmmmmmmmmmmmlźllllllllllllkžkkkkkkkkkkkkkkjťjjjjjjjjjjjjjjiiiiiiiiiiiiiiiih›hhhhhhhhhhhhhhgšggggggggggggggggf™ffffffffffffffffeeeeeeeeeeeeeeeeeed—ddddddddddddddddc–ccccccccccccccccccb•bbbbbbbbbbbbbbbbbbbba”aaaaaaaaaaaaaaaaaaaa`“````````````````````_’______________________^‘^^^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]]\Ź\\\\\\\\\\\\\\\\\\\\\\\\\\[Ž[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZŤZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZYŚYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYX‹XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWŠWŠWWWWWW§§§§§§§§§§§§§§§§§§§§§§§§§§§§¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦¦ĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄĄ¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤¤ŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁŁ˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘˘ˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇˇ                        źźźźźźźźźźźźźźźźźźźźźźźźžžžžžžžžžžžžžžžžžžžžžžťťťťťťťťťťťťťťťťťťťťťťśśśśśśśśśśśśśśśśśśśś››››››››››››››››››››šššššššššššššššššš™™™™™™™™™™™™™™™™™™————————————————––––––––––––––––––••••••••••••••••””””””””””””””““““““““““““““““’’’’’’’’’’’’’’‘‘‘‘‘‘‘‘‘‘‘‘‘‘ŹŹŹŹŹŹŹŹŹŹŹŹŹŹŽŽŽŽŽŽŽŽŽŽŽŽŽŽŤŤŤŤŤŤŤŤŤŤŤŤŤŤŚŚŚŚŚŚŚŚŚŚŚŚ‹‹‹‹‹‹‹‹‹‹‹‹‹‹ŠŠŠŠŠŠŠŠŠŠŠŠ‰‰‰‰‰‰‰‰‰‰‰‰‡‡‡‡‡‡‡‡‡‡‡‡††††††††††††………………………………„„„„„„„„„„„„‚‚‚‚‚‚‚‚‚‚‚‚€€€€€€€€€€€€~~~~~~~~~~~~}}}}}}}}}}}}||||||||||||{{{{{{{{{{{{zzzzzzzzzzzzyyyyyyyyyyyyxxxxxxxxxxxxwwwwwwwwwwwwvvvvvvvvvvvvuuuuuuuuuuuuttttttttttttttssssssssssssrrrrrrrrrrrrrrqqqqqqqqqqqqppppppppppppppoooooooooooooonnnnnnnnnnnnnnmmmmmmmmmmmmmmmmllllllllllllllkkkkkkkkkkkkkkkkjjjjjjjjjjjjjjiiiiiiiiiiiiiiiiiihhhhhhhhhhhhhhhhggggggggggggggggggffffffffffffffffeeeeeeeeeeeeeeeeeeeeddddddddddddddddddccccccccccccccccccccbbbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaaaaaaa``````````````````````________________________^^^^^^^^^^^^^^^^^^^^^^^^^^]]]]]]]]]]]]]]]]]]]]]]]]]]\\\\\\\\\\\\\\\\\\\\\\\\\\\\[[[[[[[[[[[[[[[[[[[[[[[[[[[[[[ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXWWWWWWmlt-7.22.0/src/tests/common.pri000664 000000 000000 00000001124 14531534050 016311 0ustar00rootroot000000 000000 QT += testlib QT -= gui CONFIG += console CONFIG -= app_bundle CONFIG += testcase CONFIG += c++11 QMAKE_CXXFLAGS += -std=c++11 TEMPLATE = app DEFINES += SRCDIR=\\\"$$PWD/\\\" win32 { INCLUDEPATH += $$PWD/.. LIBS += -L$$PWD/../framework -L$$PWD/../mlt++ -lmlt++ -lmlt isEmpty(PREFIX) { message("Install PREFIX not set; using C:\\Projects\\Shotcut. You can change this with 'qmake PREFIX=...'") PREFIX = C:\\Projects\\Shotcut } target.path = $$PREFIX INSTALLS += target } else { CONFIG += link_pkgconfig PKGCONFIG += mlt++-7 } mlt-7.22.0/src/tests/setenv000664 000000 000000 00000000307 14531534050 015536 0ustar00rootroot000000 000000 export MLT_REPOSITORY=`pwd`/../modules export LD_LIBRARY_PATH=`pwd`/../framework:\ `pwd`/../modules/bluefish:\ `pwd`/../../../bluefish/lib:\ `pwd`/../../../mpeg_sdk_demo/bin:\ `pwd`/../../../dv_sdk mlt-7.22.0/src/tests/test_animation/000775 000000 000000 00000000000 14531534050 017325 5ustar00rootroot000000 000000 mlt-7.22.0/src/tests/test_animation/test_animation.cpp000664 000000 000000 00000107026 14531534050 023055 0ustar00rootroot000000 000000 /* * Copyright (C) 2015-2023 Dan Dennedy * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with consumer library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include using namespace Mlt; class TestAnimation : public QObject { Q_OBJECT private Q_SLOTS: void DefaultConstructorIsInvalid() { Animation a; QVERIFY(!a.is_valid()); } void ConstructFromProperties() { Properties p; p.anim_set("foo", "bar", 10); Animation *a = p.get_anim("foo"); QVERIFY(a); QVERIFY(a->is_valid()); delete a; a = p.get_anim("bar"); QVERIFY(a); QVERIFY(!a->is_valid()); } void ConstructFromCType() { Properties p; p.anim_set("foo", "bar", 10); Animation a1(p.get_animation("foo")); QVERIFY(a1.is_valid()); Animation a2(a1.get_animation()); QVERIFY(a2.is_valid()); QVERIFY(a1.get_animation() == a2.get_animation()); } void CopyConstructor() { Properties p; p.anim_set("foo", "bar", 10); Animation a1(p.get_animation("foo")); QVERIFY(a1.is_valid()); Animation a2(a1); QVERIFY(a2.is_valid()); QVERIFY(a1.get_animation() == a2.get_animation()); } void Assignment() { Properties p; p.anim_set("foo", "bar", 10); Animation a1 = p.get_animation("foo"); QVERIFY(a1.is_valid()); Animation a2; a2 = a1; QVERIFY(a2.is_valid()); QVERIFY(a1.get_animation() == a2.get_animation()); } void LengthIsCorrect() { Properties p; p.anim_set("foo", "bar", 10); Animation a = p.get_animation("foo"); QVERIFY(a.is_valid()); QCOMPARE(a.length(), 10); } void IsAKeyFrame() { Properties p; p.anim_set("foo", "bar", 10); Animation a = p.get_animation("foo"); QVERIFY(a.is_valid()); QVERIFY(!a.is_key(5)); QVERIFY(a.is_key(10)); } void KeyFrameTypeIsDiscrete() { Properties p; p.anim_set("foo", "bar", 10); Animation a = p.get_animation("foo"); QVERIFY(a.is_valid()); QCOMPARE(a.keyframe_type(0), mlt_keyframe_discrete); QCOMPARE(a.keyframe_type(10), mlt_keyframe_discrete); QCOMPARE(a.keyframe_type(11), mlt_keyframe_discrete); } void KeyFrameTypeIsLinear() { Properties p; p.anim_set("foo", 1, 10); Animation a = p.get_animation("foo"); QVERIFY(a.is_valid()); QCOMPARE(a.keyframe_type(0), mlt_keyframe_linear); QCOMPARE(a.keyframe_type(10), mlt_keyframe_linear); QCOMPARE(a.keyframe_type(11), mlt_keyframe_linear); } void KeyFrameTypeIsSmooth() { Properties p; int pos = 10; p.anim_set("foo", 1, pos, pos, mlt_keyframe_smooth); Animation a = p.get_animation("foo"); QVERIFY(a.is_valid()); QCOMPARE(a.keyframe_type(0), mlt_keyframe_smooth); QCOMPARE(a.keyframe_type(10), mlt_keyframe_smooth); QCOMPARE(a.keyframe_type(11), mlt_keyframe_smooth); } void GetItem() { Properties p; int pos = 10; p.anim_set("foo", 1, pos, pos, mlt_keyframe_smooth); Animation a = p.get_animation("foo"); QVERIFY(a.is_valid()); bool is_key = true; mlt_keyframe_type type = mlt_keyframe_linear; int error = a.get_item(10, is_key, type); QVERIFY(!error); QVERIFY(is_key); QCOMPARE(type, mlt_keyframe_smooth); error = a.get_item(1, is_key, type); QVERIFY(!error); QVERIFY(!is_key); QCOMPARE(type, mlt_keyframe_smooth); } void AnimationFromStringProperty() { Properties p; p.set("foo", "50=100; 60=60; 100=0"); Animation a = p.get_animation("foo"); QVERIFY(!a.is_valid()); // Cause the string to be interpreted as animated value. p.anim_get("foo", 0); a = p.get_animation("foo"); QVERIFY(a.is_valid()); QCOMPARE(a.length(), 100); } void SetLength() { Properties p; p.set("foo", "50=100; 60=60; 100=0"); // Cause the string to be interpreted as animated value. p.anim_get("foo", 0); Animation a = p.get_animation("foo"); QVERIFY(a.is_valid()); QCOMPARE(a.length(), 100); a.set_length(200); QCOMPARE(a.length(), 200); a.set_length(60); QCOMPARE(a.length(), 60); QCOMPARE(a.serialize_cut(), "50=100;60=60"); } void RemoveMiddleKeyframe() { Properties p; p.set("foo", "50=100; 60=60; 100=0"); // Cause the string to be interpreted as animated value. p.anim_get_int("foo", 0); Animation a = p.get_animation("foo"); QVERIFY(a.is_valid()); int error = a.remove(60); QVERIFY(!error); QCOMPARE(a.serialize_cut(), "50=100;100=0"); QCOMPARE(a.length(), 100); QCOMPARE(p.anim_get_int("foo", 50), 100); QCOMPARE(p.anim_get_int("foo", 75), 50); QCOMPARE(p.anim_get_int("foo", 100), 0); } void RemoveFirstKeyframe() { Properties p; p.set("foo", "50=100; 60=60; 100=0"); // Cause the string to be interpreted as animated value. p.anim_get_int("foo", 0); Animation a = p.get_animation("foo"); QVERIFY(a.is_valid()); int error = a.remove(0); QVERIFY(error); error = a.remove(50); QVERIFY(!error); QCOMPARE(a.serialize_cut(), "60=60;100=0"); QCOMPARE(a.length(), 100); QCOMPARE(p.anim_get_int("foo", 50), 60); QCOMPARE(p.anim_get_int("foo", 80), 30); QCOMPARE(p.anim_get_int("foo", 100), 0); } void RemoveLastKeyframe() { Properties p; p.set("foo", "50=100; 60=60; 100=0"); // Cause the string to be interpreted as animated value. p.anim_get_int("foo", 0); Animation a = p.get_animation("foo"); QVERIFY(a.is_valid()); int error = a.remove(101); QVERIFY(error); error = a.remove(100); QVERIFY(!error); QCOMPARE(a.serialize_cut(), "50=100;60=60"); QCOMPARE(a.length(), 60); QCOMPARE(p.anim_get_int("foo", 50), 100); QCOMPARE(p.anim_get_int("foo", 55), 80); QCOMPARE(p.anim_get_int("foo", 60), 60); } void EmptyAnimationIsInvalid() { Properties p; p.set("foo", ""); // Cause the string to be interpreted as animated value. p.anim_get_int("foo", 0); Animation a = p.get_animation("foo"); QVERIFY(!a.is_valid()); } void NonEmptyAnimationKeyCount() { Properties p; p.set("foo", "50=100; 60=60; 100=0"); // Cause the string to be interpreted as animated value. p.anim_get_int("foo", 0); Animation a = p.get_animation("foo"); QVERIFY(a.is_valid()); QCOMPARE(a.key_count(), 3); } void RemoveKeyframeCount() { Properties p; p.set("foo", "50=100; 60=60; 100=0"); // Cause the string to be interpreted as animated value. p.anim_get_int("foo", 0); Animation a = p.get_animation("foo"); QVERIFY(a.is_valid()); a.remove(50); QCOMPARE(a.key_count(), 2); } void GetKeyFrame() { Properties p; p.set("foo", "50=100; 60=60; 100=0"); // Cause the string to be interpreted as animated value. p.anim_get_int("foo", 0); Animation a = p.get_animation("foo"); QVERIFY(a.is_valid()); int frame = -1; mlt_keyframe_type type = mlt_keyframe_smooth; int error = a.key_get(0, frame, type); QVERIFY(!error); QCOMPARE(frame, 50); QCOMPARE(type, mlt_keyframe_linear); QCOMPARE(a.key_count(), 3); QCOMPARE(a.key_get_frame(0), 50); QCOMPARE(a.key_get_frame(1), 60); QCOMPARE(a.key_get_frame(2), 100); QCOMPARE(a.key_get_frame(3), -1); QCOMPARE(a.keyframe_type(0), mlt_keyframe_linear); QCOMPARE(a.keyframe_type(1), mlt_keyframe_linear); QCOMPARE(a.keyframe_type(2), mlt_keyframe_linear); } void SerializesInTimeFormat() { Profile profile; Properties p; p.set("_profile", profile.get_profile(), 0); p.set("foo", "50=100; 60=60; 100=0"); // Cause the string to be interpreted as animated value. p.anim_get("foo", 0); Animation a = p.get_animation("foo"); QVERIFY(a.is_valid()); QCOMPARE(a.serialize_cut(mlt_time_clock), "00:00:02.000=100;00:00:02.400=60;00:00:04.000=0"); QCOMPARE(a.serialize_cut(mlt_time_smpte_ndf), "00:00:02:00=100;00:00:02:10=60;00:00:04:00=0"); } void GetPropertyInTimeFormat() { Profile profile; Properties p; p.set("_profile", profile.get_profile(), 0); p.set("foo", "50=100; 60=60; 100=0"); // Cause the string to be interpreted as animated value. p.anim_get_int("foo", 0); Animation a = p.get_animation("foo"); QVERIFY(a.is_valid()); for (int i = 0; i < p.count(); i++) { if (!qstrcmp(p.get_name(i), "foo")) { QCOMPARE(p.get(i, mlt_time_clock), "00:00:02.000=100;00:00:02.400=60;00:00:04.000=0"); QCOMPARE(p.get(i, mlt_time_smpte_ndf), "00:00:02:00=100;00:00:02:10=60;00:00:04:00=0"); break; } } } void AnimationClears() { Properties p; p.set("foo", "50=100; 60=60; 100=0"); // Cause the string to be interpreted as animated value. p.anim_get_int("foo", 0); Animation a = p.get_animation("foo"); QVERIFY(a.is_valid()); p.clear("foo"); QCOMPARE(p.get_animation("foo"), mlt_animation(0)); } void CanBeEscapedWithQuotes() { Properties p; p.set("foo", "\"50=100; 60=60; 100=0\""); // Quotes are retained when using the non-anim getter. QCOMPARE(p.get("foo"), "\"50=100; 60=60; 100=0\""); // Quotes are removed when using the anim getter. QCOMPARE(p.anim_get("foo", 0), "50=100; 60=60; 100=0"); // Anim strings may contain delimiters and equal signs if quoted. p.set("foo", "50=100; 60=\"60; 100=0\";\"hello=world\""); QCOMPARE(p.anim_get("foo", 0), "hello=world"); QCOMPARE(p.anim_get("foo", 50), "100"); QCOMPARE(p.anim_get("foo", 60), "60; 100=0"); } void ShiftFramesPositive() { Properties p; p.set("foo", "50=100; 60=60; 100=0"); // Cause the string to be interpreted as animated value. p.anim_get_int("foo", 0); Animation a = p.get_animation("foo"); QVERIFY(a.is_valid()); a.shift_frames(60); QCOMPARE(a.key_get_frame(0), 110); QCOMPARE(a.key_get_frame(1), 120); QCOMPARE(a.key_get_frame(2), 160); QCOMPARE(a.key_get_frame(3), -1); } void ShiftFramesNegative() { Properties p; p.set("foo", "50=100; 60=60; 100=0"); // Cause the string to be interpreted as animated value. p.anim_get_int("foo", 0); Animation a = p.get_animation("foo"); QVERIFY(a.is_valid()); a.shift_frames(-60); QCOMPARE(a.key_get_frame(0), -10); QCOMPARE(a.key_get_frame(1), 0); QCOMPARE(a.key_get_frame(2), 40); QCOMPARE(a.key_get_frame(3), -1); } void NextKey() { Properties p; p.set("foo", "50=100; 60=60; 100=0"); // Cause the string to be interpreted as animated value. p.anim_get_int("foo", 0); Animation a = p.get_animation("foo"); QVERIFY(a.is_valid()); int key; bool ret; ret = a.next_key(0, key); QCOMPARE(ret, false); QCOMPARE(key, 50); ret = a.next_key(59, key); QCOMPARE(ret, false); QCOMPARE(key, 60); ret = a.next_key(60, key); QCOMPARE(ret, false); QCOMPARE(key, 60); ret = a.next_key(61, key); QCOMPARE(ret, false); QCOMPARE(key, 100); ret = a.next_key(100, key); QCOMPARE(ret, false); QCOMPARE(key, 100); key = 7; // random value ret = a.next_key(101, key); QCOMPARE(ret, true); // error - No next key QCOMPARE(key, 7); // Not modified on error } void PreviousKey() { Properties p; p.set("foo", "50=100; 60=60; 100=0"); // Cause the string to be interpreted as animated value. p.anim_get_int("foo", 0); Animation a = p.get_animation("foo"); QVERIFY(a.is_valid()); int key; bool ret; key = 7; // random value ret = a.previous_key(0, key); QCOMPARE(ret, true); // error - no previous key QCOMPARE(key, 7); // Not modified on error ret = a.previous_key(59, key); QCOMPARE(ret, false); QCOMPARE(key, 50); ret = a.previous_key(60, key); QCOMPARE(ret, false); QCOMPARE(key, 60); ret = a.previous_key(61, key); QCOMPARE(ret, false); QCOMPARE(key, 60); ret = a.previous_key(100, key); QCOMPARE(ret, false); QCOMPARE(key, 100); ret = a.previous_key(101, key); QCOMPARE(ret, false); QCOMPARE(key, 100); } void LinearInterpolationOneKey() { Properties p; p.set("foo", "50=10"); // Cause the string to be interpreted as animated value. p.anim_get_int("foo", 0); Animation a = p.get_animation("foo"); QVERIFY(a.is_valid()); // Values from 0 to 10 should all be 10 for (int i = 0; i <= 50; i++) { double current = p.anim_get_double("foo", i); QCOMPARE(current, 10); } } void SmoothInterpolationOneKey() { Properties p; p.set("foo", "50~=10"); // Cause the string to be interpreted as animated value. p.anim_get_int("foo", 0); Animation a = p.get_animation("foo"); QVERIFY(a.is_valid()); // Values from 0 to 10 should all be 10 for (int i = 0; i <= 50; i++) { double current = p.anim_get_double("foo", i); QCOMPARE(current, 10); } } void LinearInterpolationTwoKey() { Properties p; double prev; p.set("foo", "10=50; 20=100"); // Cause the string to be interpreted as animated value. p.anim_get_int("foo", 0); Animation a = p.get_animation("foo"); QVERIFY(a.is_valid()); // Values from 0 to 10 should all be 50 for (int i = 0; i <= 10; i++) { double current = p.anim_get_double("foo", i); QCOMPARE(current, 50); } // Values from 10 to 20 should step by 5 prev = 50 - 5; for (int i = 10; i <= 20; i++) { double current = p.anim_get_double("foo", i); QCOMPARE(current, prev + 5); prev = current; } // Values after 20 should all be 100 for (int i = 20; i <= 30; i++) { double current = p.anim_get_double("foo", i); QCOMPARE(current, 100); } } void SmoothInterpolationTwoKey() { Properties p; double prev; p.set("foo", "10~=50; 20~=100"); // Cause the string to be interpreted as animated value. p.anim_get_int("foo", 0); Animation a = p.get_animation("foo"); QVERIFY(a.is_valid()); // Values from 0 to 10 should all be 50 for (int i = 0; i <= 10; i++) { double current = p.anim_get_double("foo", i); QCOMPARE(current, 50); } // Values from 10 to 20 should increase but not exceed 100 prev = 49; for (int i = 10; i <= 20; i++) { double current = p.anim_get_double("foo", i); QVERIFY(current > prev); QVERIFY(current <= 100); prev = current; } // Values after 20 should all be 100 for (int i = 20; i <= 30; i++) { double current = p.anim_get_double("foo", i); QCOMPARE(current, 100); } } void LinearInterpolationThreeKey() { Properties p; double prev; p.set("foo", "10=50; 20=100; 30=50"); // Cause the string to be interpreted as animated value. p.anim_get_int("foo", 0); Animation a = p.get_animation("foo"); QVERIFY(a.is_valid()); // Values from 0 to 10 should all be 50 for (int i = 0; i <= 10; i++) { double current = p.anim_get_double("foo", i); QCOMPARE(current, 50); } // Values from 10 to 20 should step up by 5 prev = 50 - 5; for (int i = 10; i <= 20; i++) { double current = p.anim_get_double("foo", i); QCOMPARE(current, prev + 5); prev = current; } // Values from 20 to 30 should step down by 5 prev = 100 + 5; for (int i = 20; i <= 30; i++) { double current = p.anim_get_double("foo", i); QCOMPARE(current, prev - 5); prev = current; } // Values after 30 should all be 50 for (int i = 30; i <= 40; i++) { double current = p.anim_get_double("foo", i); QCOMPARE(current, 50); } } void SmoothInterpolationThreeKey() { Properties p; double prev; p.set("foo", "10~=50; 20~=100; 30~=50"); // Cause the string to be interpreted as animated value. p.anim_get_int("foo", 0); Animation a = p.get_animation("foo"); QVERIFY(a.is_valid()); // Values from 0 to 10 should all be 50 for (int i = 0; i <= 10; i++) { double current = p.anim_get_double("foo", i); QCOMPARE(current, 50); } // Values from 10 to 20 should increase but not exceed 100 prev = 49; for (int i = 10; i <= 20; i++) { double current = p.anim_get_double("foo", i); QVERIFY(current > prev); QVERIFY(current <= 100); prev = current; } // Test two arbitrary intermediate points QCOMPARE(p.anim_get_double("foo", 13), 64.475); QCOMPARE(p.anim_get_double("foo", 18), 95.6); // Values from 20 to 30 should decrease but not exceed 50 prev = 101; for (int i = 20; i <= 30; i++) { double current = p.anim_get_double("foo", i); QVERIFY(current < prev); QVERIFY(current >= 50); prev = current; } // Test two arbitrary intermediate points QCOMPARE(p.anim_get_double("foo", 23), 90.775); QCOMPARE(p.anim_get_double("foo", 28), 58.4); // Values after 30 should all be 50 for (int i = 30; i <= 40; i++) { double current = p.anim_get_double("foo", i); QCOMPARE(current, 50); } } void LinearDoesNotReverse() { Properties p; double prev; // This sequence of keyframes has abrupt changes. For some interpolation algorithms, this // can result in values changing direction along the interpolation path (cusp or // overshoot). // The purpose of this test is to ensure that values do not reverse direction (exept as // expected at keyframes if the user specified a direction change). p.set("foo", "50=0; 60=100; 100=110; 150=200; 200=110; 240=100; 260=50; 300=200; 301=10; 350=11"); // Cause the string to be interpreted as animated value. p.anim_get_int("foo", 0); Animation a = p.get_animation("foo"); QVERIFY(a.is_valid()); // Values from 0 to 50 should all be 0 for (int i = 0; i <= 50; i++) { double current = p.anim_get_double("foo", i); QCOMPARE(current, 0); } // Values from 50 to 150 should only go up prev = 0; for (int i = 50; i <= 150; i++) { double current = p.anim_get_double("foo", i); QVERIFY(current >= prev); prev = current; } // Values from 150 to 260 should only go down prev = 201; for (int i = 150; i <= 260; i++) { double current = p.anim_get_double("foo", i); QVERIFY(current < prev); prev = current; } // Values from 260 to 300 should only go up prev = 49; for (int i = 260; i <= 300; i++) { double current = p.anim_get_double("foo", i); QVERIFY(current > prev); QVERIFY(current < 200.01); prev = current; } // Values above 301 to 350 should go up from 10 to 11 prev = 9.99; for (int i = 301; i <= 350; i++) { double current = p.anim_get_double("foo", i); QVERIFY(current > prev); QVERIFY(current < 11.01); prev = current; } // Values above 350 should be 11 for (int i = 350; i <= 360; i++) { double current = p.anim_get_double("foo", i); QCOMPARE(current, 11); } } void SmoothLooseCanReverse() { Properties p; p.set("foo", "50~=0; 60~=100; 100~=110; 150~=200; 200~=110; 240~=100; 260~=50; 300~=200; 301~=10; " "350~=11"); // Cause the string to be interpreted as animated value. p.anim_get_int("foo", 0); Animation a = p.get_animation("foo"); QVERIFY(a.is_valid()); // Values from 0 to 50 should all be 0 for (int i = 0; i <= 50; i++) { double current = p.anim_get_double("foo", i); QCOMPARE(current, 0); } // Values from 50 to 150 will cusp for (int i = 50; i <= 150; i++) { double current = p.anim_get_double("foo", i); QVERIFY(current >= 0); QVERIFY(current < 200.01); } // Values from 150 to 260 will cusp for (int i = 150; i <= 260; i++) { double current = p.anim_get_double("foo", i); QVERIFY(current < 200.01); QVERIFY(current > 47); } // Values from 260 to 300 will overshoot for (int i = 260; i <= 300; i++) { double current = p.anim_get_double("foo", i); QVERIFY(current > 49.99); QVERIFY(current < 205.01); } // Values above 301 to 350 will overshoot below 10 for (int i = 301; i <= 350; i++) { double current = p.anim_get_double("foo", i); QVERIFY(current > -3.8); QVERIFY(current < 11.01); } // Values above 350 should be 11 for (int i = 350; i <= 360; i++) { double current = p.anim_get_double("foo", i); QCOMPARE(current, 11); } } void SmoothNaturalDoesNotReverse() { Properties p; double prev; // This sequence of keyframes has abrupt changes. For some interpolation algorithms, this // can result in values changing direction along the interpolation path (cusp or // overshoot). // The purpose of this test is to ensure that values do not reverse direction (exept as // expected at keyframes if the user specified a direction change). p.set("foo", "50$=0; 60$=100; 100$=110; 150$=200; 200$=110; 240$=100; 260$=50; 300$=200; 301$=10; " "350$=11"); // Cause the string to be interpreted as animated value. p.anim_get_int("foo", 0); Animation a = p.get_animation("foo"); QVERIFY(a.is_valid()); // Values from 0 to 50 should all be 0 for (int i = 0; i <= 50; i++) { double current = p.anim_get_double("foo", i); QCOMPARE(current, 0); } // Values from 50 to 150 should only go up prev = -0.01; for (int i = 50; i <= 150; i++) { double current = p.anim_get_double("foo", i); QVERIFY(current > prev); QVERIFY(current < 200.01); prev = current; } // Values from 150 to 260 should only go down prev = 200.01; for (int i = 150; i <= 260; i++) { double current = p.anim_get_double("foo", i); QVERIFY(current < prev); prev = current; } // Values from 260 to 300 should only go up prev = 49.99; for (int i = 260; i <= 300; i++) { double current = p.anim_get_double("foo", i); QVERIFY(current > prev); QVERIFY(current < 200.01); prev = current; } // Values above 301 to 350 should go up from 10 to 11 prev = 9.99; for (int i = 301; i <= 350; i++) { double current = p.anim_get_double("foo", i); QVERIFY(current > prev); QVERIFY(current < 11.01); prev = current; } // Values above 350 should be 11 for (int i = 350; i <= 360; i++) { double current = p.anim_get_double("foo", i); QCOMPARE(current, 11); } } void SmoothTightDoesNotReverse() { Properties p; double prev; // This sequence of keyframes has abrupt changes. For some interpolation algorithms, this // can result in values changing direction along the interpolation path (cusp or // overshoot). // The purpose of this test is to ensure that values do not reverse direction (exept as // expected at keyframes if the user specified a direction change). p.set("foo", "50-=0; 60-=100; 100-=110; 150-=200; 200-=110; 240-=100; 260-=50; 300-=200; 301-=10; " "350-=11"); // Cause the string to be interpreted as animated value. p.anim_get_int("foo", 0); Animation a = p.get_animation("foo"); QVERIFY(a.is_valid()); // Values from 0 to 50 should all be 0 for (int i = 0; i <= 50; i++) { double current = p.anim_get_double("foo", i); QCOMPARE(current, 0); } // Values from 50 to 150 should only go up prev = -0.01; for (int i = 50; i <= 150; i++) { double current = p.anim_get_double("foo", i); QVERIFY(current > prev); QVERIFY(current < 200.01); prev = current; } // Values from 150 to 260 should only go down prev = 200.01; for (int i = 150; i <= 260; i++) { double current = p.anim_get_double("foo", i); QVERIFY(current < prev); prev = current; } // Values from 260 to 300 should only go up prev = 49.99; for (int i = 260; i <= 300; i++) { double current = p.anim_get_double("foo", i); QVERIFY(current > prev); QVERIFY(current < 200.01); prev = current; } // Values above 301 to 350 should go up from 10 to 11 prev = 9.99; for (int i = 301; i <= 350; i++) { double current = p.anim_get_double("foo", i); QVERIFY(current > prev); QVERIFY(current < 11.01); prev = current; } // Values above 350 should be 11 for (int i = 350; i <= 360; i++) { double current = p.anim_get_double("foo", i); QCOMPARE(current, 11); } } void EaseIn() { Properties p; p.set("sinu", "0a=0; 100a=100;"); p.set("quad", "0d=0; 100d=100;"); p.set("cube", "0g=0; 100g=100;"); p.set("quar", "0j=0; 100j=100;"); p.set("quin", "0m=0; 100m=100;"); p.set("expo", "0p=0; 100p=100;"); p.set("circ", "0s=0; 100s=100;"); p.set("back", "0v=0; 100v=100;"); p.set("elas", "0y=0; 100y=100;"); p.set("boun", "0B=0; 100B=100;"); p.anim_get_int("sinu", 0); p.anim_get_int("quad", 0); p.anim_get_int("cube", 0); p.anim_get_int("quar", 0); p.anim_get_int("quin", 0); p.anim_get_int("expo", 0); p.anim_get_int("circ", 0); p.anim_get_int("back", 0); p.anim_get_int("elas", 0); p.anim_get_int("boun", 0); // fprintf(stderr, "sinu\tquad\tcube\tquar\tquin\texpo\tcirc\tback\telas\tboun\n"); for (int i = 0; i <= 100; i++) { double sinu = p.anim_get_double("sinu", i); double quad = p.anim_get_double("quad", i); double cube = p.anim_get_double("cube", i); double quar = p.anim_get_double("quar", i); double quin = p.anim_get_double("quin", i); double expo = p.anim_get_double("expo", i); double circ = p.anim_get_double("circ", i); double back = p.anim_get_double("back", i); double elas = p.anim_get_double("elas", i); double boun = p.anim_get_double("boun", i); // fprintf(stderr,"%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\n", sinu, quad, cube, quar, quin, expo, circ, back, elas, boun); QVERIFY(sinu >= 0.0); QVERIFY(sinu <= 100.0); QVERIFY(quad >= 0.0); QVERIFY(quad <= 100.0); QVERIFY(quad <= sinu); QVERIFY(cube >= 0.0); QVERIFY(cube <= 100.0); QVERIFY(cube <= quad); QVERIFY(quar >= 0.0); QVERIFY(quar <= 100.0); QVERIFY(quar <= cube); QVERIFY(quin >= 0.0); QVERIFY(quin <= 100.0); QVERIFY(quin <= quar); QVERIFY(expo >= 0.0); QVERIFY(expo <= 100.0); QVERIFY(circ >= 0.0); QVERIFY(circ <= 100.0); QVERIFY(back >= -37.88); QVERIFY(back <= 100.0); QVERIFY(elas >= -36.39); QVERIFY(elas <= 100.0); QVERIFY(boun >= -0.00000001); QVERIFY(boun <= 100.0); } } void EaseOut() { Properties p; p.set("sinu", "0b=0; 100b=100;"); p.set("quad", "0e=0; 100e=100;"); p.set("cube", "0h=0; 100h=100;"); p.set("quar", "0k=0; 100k=100;"); p.set("quin", "0n=0; 100n=100;"); p.set("expo", "0q=0; 100q=100;"); p.set("circ", "0t=0; 100t=100;"); p.set("back", "0w=0; 100w=100;"); p.set("elas", "0z=0; 100z=100;"); p.set("boun", "0C=0; 100C=100;"); p.anim_get_int("sinu", 0); p.anim_get_int("quad", 0); p.anim_get_int("cube", 0); p.anim_get_int("quar", 0); p.anim_get_int("quin", 0); p.anim_get_int("expo", 0); p.anim_get_int("circ", 0); p.anim_get_int("back", 0); p.anim_get_int("elas", 0); p.anim_get_int("boun", 0); // fprintf(stderr, "sinu\tquad\tcube\tquar\tquin\texpo\tcirc\tback\telas\tboun\n"); for (int i = 0; i <= 100; i++) { double sinu = p.anim_get_double("sinu", i); double quad = p.anim_get_double("quad", i); double cube = p.anim_get_double("cube", i); double quar = p.anim_get_double("quar", i); double quin = p.anim_get_double("quin", i); double expo = p.anim_get_double("expo", i); double circ = p.anim_get_double("circ", i); double back = p.anim_get_double("back", i); double elas = p.anim_get_double("elas", i); double boun = p.anim_get_double("boun", i); // fprintf(stderr,"%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\n", sinu, quad, cube, quar, quin, expo, circ, back, elas, boun); QVERIFY(sinu >= 0.0); QVERIFY(sinu <= 100.0); QVERIFY(quad >= 0.0); QVERIFY(quad <= 100.0); QVERIFY(quad >= sinu); QVERIFY(cube >= 0.0); QVERIFY(cube <= 100.0); QVERIFY(cube >= quad); QVERIFY(quar >= 0.0); QVERIFY(quar <= 100.0); QVERIFY(quar >= cube); QVERIFY(quin >= 0.0); QVERIFY(quin <= 100.0); QVERIFY(quin >= quar); QVERIFY(expo >= 0.0); QVERIFY(expo <= 100.0); QVERIFY(circ >= 0.0); QVERIFY(circ <= 100.0); QVERIFY(back >= 0.0); QVERIFY(back <= 137.9); QVERIFY(elas >= 0.0); QVERIFY(elas <= 136.4); QVERIFY(boun >= 0.0); QVERIFY(boun <= 100.1); } } void EaseInOut() { Properties p; p.set("sinu", "0c=0; 100c=100;"); p.set("quad", "0f=0; 100f=100;"); p.set("cube", "0i=0; 100i=100;"); p.set("quar", "0l=0; 100l=100;"); p.set("quin", "0o=0; 100o=100;"); p.set("expo", "0r=0; 100r=100;"); p.set("circ", "0u=0; 100u=100;"); p.set("back", "0x=0; 100x=100;"); p.set("elas", "0A=0; 100A=100;"); p.set("boun", "0D=0; 100D=100;"); p.anim_get_int("sinu", 0); p.anim_get_int("quad", 0); p.anim_get_int("cube", 0); p.anim_get_int("quar", 0); p.anim_get_int("quin", 0); p.anim_get_int("expo", 0); p.anim_get_int("circ", 0); p.anim_get_int("back", 0); p.anim_get_int("elas", 0); p.anim_get_int("boun", 0); // fprintf(stderr, "sinu\tquad\tcube\tquar\tquin\texpo\tcirc\tback\telas\tboun\n"); for (int i = 0; i <= 100; i++) { double sinu = p.anim_get_double("sinu", i); double quad = p.anim_get_double("quad", i); double cube = p.anim_get_double("cube", i); double quar = p.anim_get_double("quar", i); double quin = p.anim_get_double("quin", i); double expo = p.anim_get_double("expo", i); double circ = p.anim_get_double("circ", i); double back = p.anim_get_double("back", i); double elas = p.anim_get_double("elas", i); double boun = p.anim_get_double("boun", i); // fprintf(stderr,"%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\t%f\n", sinu, quad, cube, quar, quin, expo, circ, back, elas, boun); if (i < 50) { QVERIFY(quad <= sinu); QVERIFY(cube <= quad); QVERIFY(quar <= cube); QVERIFY(quin <= quar); } else { QVERIFY(quad >= sinu); QVERIFY(cube >= quad); QVERIFY(quar >= cube); QVERIFY(quin >= quar); } QVERIFY(sinu >= 0.0); QVERIFY(sinu <= 100.0); QVERIFY(quad >= 0.0); QVERIFY(quad <= 100.0); QVERIFY(cube >= 0.0); QVERIFY(cube <= 100.0); QVERIFY(quar >= 0.0); QVERIFY(quar <= 100.0); QVERIFY(quin >= 0.0); QVERIFY(quin <= 100.0); QVERIFY(expo >= 0.0); QVERIFY(expo <= 100.0); QVERIFY(circ >= 0.0); QVERIFY(circ <= 100.0); QVERIFY(back >= -19.0); QVERIFY(back <= 119.0); QVERIFY(elas >= -18.2); QVERIFY(elas <= 118.2); QVERIFY(boun >= -0.01); QVERIFY(boun <= 100.1); } } }; QTEST_APPLESS_MAIN(TestAnimation) #include "test_animation.moc" mlt-7.22.0/src/tests/test_animation/test_animation.pro000664 000000 000000 00000000115 14531534050 023062 0ustar00rootroot000000 000000 include(../common.pri) TARGET = test_animation SOURCES += test_animation.cpp mlt-7.22.0/src/tests/test_audio/000775 000000 000000 00000000000 14531534050 016447 5ustar00rootroot000000 000000 mlt-7.22.0/src/tests/test_audio/test_audio.cpp000664 000000 000000 00000002677 14531534050 021327 0ustar00rootroot000000 000000 /* * Copyright (C) 2020 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with consumer library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include using namespace Mlt; class TestAudio : public QObject { Q_OBJECT private Q_SLOTS: void DefaultConstructor() { Audio a; QVERIFY(a.samples() == 0); } void ConstructFromAudio() { mlt_audio audio = mlt_audio_new(); audio->samples = 500; Audio a(audio); QVERIFY(a.samples() == 500); } void GetSetData() { Audio a; void *data = malloc(500); a.set_data(data); QVERIFY(a.data() == data); free(data); a.set_data(nullptr); } }; QTEST_APPLESS_MAIN(TestAudio) #include "test_audio.moc" mlt-7.22.0/src/tests/test_audio/test_audio.pro000664 000000 000000 00000000105 14531534050 021325 0ustar00rootroot000000 000000 include(../common.pri) TARGET = test_audio SOURCES += test_audio.cpp mlt-7.22.0/src/tests/test_events/000775 000000 000000 00000000000 14531534050 016652 5ustar00rootroot000000 000000 mlt-7.22.0/src/tests/test_events/test_events.cpp000664 000000 000000 00000005421 14531534050 021723 0ustar00rootroot000000 000000 /* * Copyright (C) 2019-2021 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with consumer library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include using namespace Mlt; class TestEvents : public QObject { Q_OBJECT private: mlt_properties m_properties; public: TestEvents() : m_properties(nullptr) { Factory::init(); } private: void checkOwner(mlt_properties owner) { QVERIFY(owner == m_properties); } static void onPropertyChanged(mlt_properties owner, TestEvents *self, mlt_event_data data) { QVERIFY(self != nullptr); QCOMPARE(Mlt::EventData(data).to_string(), "foo"); self->checkOwner(owner); } private Q_SLOTS: void ListenToPropertyChanged() { Profile profile; Producer producer(profile, "noise"); QVERIFY(producer.is_valid()); Event *event = producer.listen("property-changed", this, (mlt_listener) onPropertyChanged); QVERIFY(event != nullptr); QVERIFY(event->is_valid()); m_properties = producer.get_properties(); producer.set("foo", 1); delete event; } void BlockEvent() { Profile profile; Producer producer(profile, "noise"); m_properties = nullptr; Event *event = producer.listen("property-changed", nullptr, (mlt_listener) onPropertyChanged); QVERIFY(event != nullptr); QVERIFY(event->is_valid()); event->block(); producer.set("bar", 1); delete event; } void UnblockEvent() { Profile profile; Producer producer(profile, "noise"); m_properties = producer.get_properties(); Event *event = producer.listen("property-changed", this, (mlt_listener) onPropertyChanged); QVERIFY(event != nullptr); QVERIFY(event->is_valid()); event->block(); producer.set("bar", 1); event->unblock(); producer.set("foo", 1); delete event; } }; QTEST_APPLESS_MAIN(TestEvents) #include "test_events.moc" mlt-7.22.0/src/tests/test_events/test_events.pro000664 000000 000000 00000000107 14531534050 021735 0ustar00rootroot000000 000000 include(../common.pri) TARGET = test_events SOURCES += test_events.cpp mlt-7.22.0/src/tests/test_filter/000775 000000 000000 00000000000 14531534050 016633 5ustar00rootroot000000 000000 mlt-7.22.0/src/tests/test_filter/test_filter.cpp000664 000000 000000 00000004240 14531534050 021663 0ustar00rootroot000000 000000 /* * Copyright (C) 2015 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with consumer library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include using namespace Mlt; class TestFilter : public QObject { Q_OBJECT mlt_locale_t locale; public: TestFilter() { #if defined(__linux__) || defined(__APPLE__) locale = newlocale(LC_NUMERIC_MASK, "POSIX", NULL); #endif Factory::init(); } ~TestFilter() { #if defined(__linux__) || defined(__APPLE__) freelocale(locale); #endif } private Q_SLOTS: void ProcessModifiesFrame() { Profile profile("dv_ntsc"); Producer producer(profile, "noise", NULL); Filter filter(profile, "resize"); int width = 0; int height = 0; mlt_image_format format = mlt_image_rgb; // Get a frame from the producer Frame *frame = producer.get_frame(); // Get the default image size: width should match profile frame->get_image(format, width, height, 0); QCOMPARE(width, 720); // Without applying the filter, the width request is not honored. width = 360; frame->get_image(format, width, height, 0); QCOMPARE(width, 720); // Apply the filter and the requested width will be provided width = 360; filter.process(*frame); frame->get_image(format, width, height, 0); QCOMPARE(width, 360); delete frame; } }; QTEST_APPLESS_MAIN(TestFilter) #include "test_filter.moc" mlt-7.22.0/src/tests/test_filter/test_filter.pro000664 000000 000000 00000000110 14531534050 021671 0ustar00rootroot000000 000000 include(../common.pri) TARGET = test_filter SOURCES += test_filter.cpp mlt-7.22.0/src/tests/test_frame/000775 000000 000000 00000000000 14531534050 016440 5ustar00rootroot000000 000000 mlt-7.22.0/src/tests/test_frame/test_frame.cpp000664 000000 000000 00000005653 14531534050 021306 0ustar00rootroot000000 000000 /* * Copyright (C) 2015 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with consumer library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include using namespace Mlt; class TestFrame : public QObject { Q_OBJECT public: TestFrame() {} private Q_SLOTS: void FrameConstructorAddsReference() { mlt_frame frame = mlt_frame_init(NULL); QCOMPARE(mlt_properties_ref_count(MLT_FRAME_PROPERTIES(frame)), 1); Frame f(frame); QCOMPARE(f.ref_count(), 2); mlt_frame_close(frame); } void CopyConstructorAddsReference() { mlt_frame frame = mlt_frame_init(NULL); QCOMPARE(mlt_properties_ref_count(MLT_FRAME_PROPERTIES(frame)), 1); Frame f1(frame); QCOMPARE(f1.ref_count(), 2); Frame f2(f1); QCOMPARE(f1.ref_count(), 3); mlt_frame_close(frame); } void ConstCopyConstructorAddsReference() { mlt_frame frame = mlt_frame_init(NULL); QCOMPARE(mlt_properties_ref_count(MLT_FRAME_PROPERTIES(frame)), 1); Frame f1(frame); QCOMPARE(f1.ref_count(), 2); const Frame &cf1 = f1; // Force const to avoid non-const constructor. Frame f2(cf1); QCOMPARE(f1.ref_count(), 3); QCOMPARE(f2.ref_count(), 3); mlt_frame_close(frame); } void DefaultConstructorIsNotValid() { Frame f1; QCOMPARE(f1.is_valid(), false); QCOMPARE(f1.ref_count(), 0); } void OperatorEqualsAddsReference() { mlt_frame frame = mlt_frame_init(NULL); QCOMPARE(mlt_properties_ref_count(MLT_FRAME_PROPERTIES(frame)), 1); Frame f1(frame); QCOMPARE(f1.ref_count(), 2); Frame f2; f2 = f1; QCOMPARE(f1.ref_count(), 3); QCOMPARE(f2.ref_count(), 3); mlt_frame_close(frame); } void DestructionRemovesReference() { mlt_frame frame = mlt_frame_init(NULL); QCOMPARE(mlt_properties_ref_count(MLT_FRAME_PROPERTIES(frame)), 1); Frame f1(frame); QCOMPARE(f1.ref_count(), 2); Frame *f2 = new Frame(f1); QCOMPARE(f2->ref_count(), 3); delete f2; QCOMPARE(f1.ref_count(), 2); mlt_frame_close(frame); } }; QTEST_APPLESS_MAIN(TestFrame) #include "test_frame.moc" mlt-7.22.0/src/tests/test_frame/test_frame.pro000664 000000 000000 00000000106 14531534050 021310 0ustar00rootroot000000 000000 include(../common.pri) TARGET = test_frame SOURCES += test_frame.cpp mlt-7.22.0/src/tests/test_image/000775 000000 000000 00000000000 14531534050 016430 5ustar00rootroot000000 000000 mlt-7.22.0/src/tests/test_image/test_image.cpp000664 000000 000000 00000007262 14531534050 021264 0ustar00rootroot000000 000000 /* * Copyright (C) 2021 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with consumer library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include using namespace Mlt; class TestImage : public QObject { Q_OBJECT public: TestImage() { Factory::init(); } private Q_SLOTS: void DefaultConstructor() { Image i; QVERIFY(i.width() == 0); } void ConstructFromImage() { mlt_image image = mlt_image_new(); image->width = 500; Image i(image); QCOMPARE(i.width(), 500); } void ConstructAndAlloc() { Image i(1920, 1080, mlt_image_rgb); QVERIFY(i.plane(0) != nullptr); } void PlaneAndStrideRgb() { Image i(1920, 1080, mlt_image_rgb); QVERIFY(i.plane(0) != nullptr); QCOMPARE(i.stride(0), 1920 * 3); QVERIFY(i.plane(1) == nullptr); QCOMPARE(i.stride(1), 0); QVERIFY(i.plane(2) == nullptr); QCOMPARE(i.stride(2), 0); QVERIFY(i.plane(3) == nullptr); QCOMPARE(i.stride(3), 0); } void PlaneAndStrideRgba() { Image i(1920, 1080, mlt_image_rgba); QVERIFY(i.plane(0) != nullptr); QCOMPARE(i.stride(0), 1920 * 4); QVERIFY(i.plane(1) == nullptr); QCOMPARE(i.stride(1), 0); QVERIFY(i.plane(2) == nullptr); QCOMPARE(i.stride(2), 0); QVERIFY(i.plane(3) == nullptr); QCOMPARE(i.stride(3), 0); } void PlaneAndStrideYuv422() { Image i(1920, 1080, mlt_image_yuv422); QVERIFY(i.plane(0) != nullptr); QCOMPARE(i.stride(0), 1920 * 2); QVERIFY(i.plane(1) == nullptr); QCOMPARE(i.stride(1), 0); QVERIFY(i.plane(2) == nullptr); QCOMPARE(i.stride(2), 0); QVERIFY(i.plane(3) == nullptr); QCOMPARE(i.stride(3), 0); } void PlaneAndStrideYuv420p() { Image i(1920, 1080, mlt_image_yuv420p); QVERIFY(i.plane(0) != nullptr); QCOMPARE(i.stride(0), 1920); QVERIFY(i.plane(1) != nullptr); QCOMPARE(i.stride(1), 1920 / 2); QVERIFY(i.plane(2) != nullptr); QCOMPARE(i.stride(2), 1920 / 2); QVERIFY(i.plane(3) == nullptr); QCOMPARE(i.stride(3), 0); } void PlaneAndStrideYuv422p16() { Image i(1920, 1080, mlt_image_yuv420p); QVERIFY(i.plane(0) != nullptr); QCOMPARE(i.stride(0), 1920); QVERIFY(i.plane(1) != nullptr); QCOMPARE(i.stride(1), 1920 / 2); QVERIFY(i.plane(2) != nullptr); QCOMPARE(i.stride(2), 1920 / 2); QVERIFY(i.plane(3) == nullptr); QCOMPARE(i.stride(3), 0); } void GetSetColorspace() { Image i(1920, 1080, mlt_image_rgb); i.set_colorspace(mlt_colorspace_bt709); QCOMPARE(i.colorspace(), mlt_colorspace_bt709); } void InitAlpha() { Image i(1920, 1080, mlt_image_rgb); i.init_alpha(); QVERIFY(i.plane(3) != nullptr); } }; QTEST_APPLESS_MAIN(TestImage) #include "test_image.moc" mlt-7.22.0/src/tests/test_image/test_image.pro000664 000000 000000 00000000105 14531534050 021267 0ustar00rootroot000000 000000 include(../common.pri) TARGET = test_image SOURCES += test_image.cpp mlt-7.22.0/src/tests/test_playlist/000775 000000 000000 00000000000 14531534050 017207 5ustar00rootroot000000 000000 mlt-7.22.0/src/tests/test_playlist/test_playlist.cpp000664 000000 000000 00000017020 14531534050 022613 0ustar00rootroot000000 000000 /* * Copyright (C) 2019 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with consumer library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include using namespace Mlt; class TestPlaylist : public QObject { Q_OBJECT Profile profile; public: TestPlaylist() : profile("dv_pal") { Factory::init(); } private Q_SLOTS: void Append() { Playlist pl(profile); QVERIFY(pl.is_valid()); Producer p(profile, "noise"); QVERIFY(p.is_valid()); QCOMPARE(pl.count(), 0); int result = pl.append(p); QCOMPARE(result, 0); // Success QCOMPARE(pl.count(), 1); } void Remove() { Playlist pl(profile); QVERIFY(pl.is_valid()); Producer p(profile, "noise"); QVERIFY(p.is_valid()); QCOMPARE(pl.count(), 0); pl.append(p); QCOMPARE(pl.count(), 1); int result = pl.remove(0); QCOMPARE(result, 0); // Success QCOMPARE(pl.count(), 0); } void RemoveErrorOnInvalidIndex() { Playlist pl(profile); QVERIFY(pl.is_valid()); Producer p(profile, "noise"); QVERIFY(p.is_valid()); QCOMPARE(pl.count(), 0); pl.append(p); QCOMPARE(pl.count(), 1); int result = pl.remove(10); // Invalid index QCOMPARE(result, 1); // Fail QCOMPARE(pl.count(), 1); } void Move() { Playlist pl(profile); QVERIFY(pl.is_valid()); // Initial order: 1, 2, 3 Producer p1(profile, "noise"); p1.set("id", 1); QVERIFY(p1.is_valid()); pl.append(p1); Producer p2(profile, "noise"); p2.set("id", 2); QVERIFY(p2.is_valid()); pl.append(p2); Producer p3(profile, "noise"); p3.set("id", 3); QVERIFY(p3.is_valid()); pl.append(p3); QCOMPARE(pl.count(), 3); // Move first to last int result = pl.move(0, 2); QCOMPARE(result, 0); // Success QCOMPARE(pl.count(), 3); // New order: 2, 3, 1 Producer *pp1 = pl.get_clip(0); Producer *pp2 = pl.get_clip(1); Producer *pp3 = pl.get_clip(2); QCOMPARE(pp1->parent().get_int("id"), 2); QCOMPARE(pp2->parent().get_int("id"), 3); QCOMPARE(pp3->parent().get_int("id"), 1); delete pp1; delete pp2; delete pp3; } void MoveCorrectInvalidIndex() { Playlist pl(profile); QVERIFY(pl.is_valid()); // Initial order: 1, 2, 3 Producer p1(profile, "noise"); p1.set("id", 1); QVERIFY(p1.is_valid()); pl.append(p1); Producer p2(profile, "noise"); p2.set("id", 2); QVERIFY(p2.is_valid()); pl.append(p2); Producer p3(profile, "noise"); p3.set("id", 3); QVERIFY(p3.is_valid()); pl.append(p3); QCOMPARE(pl.count(), 3); // Move first to an invalid index int result = pl.move(0, 10); // 10 is invalid // This is still successful because move() corrects the index to be the last entry. QCOMPARE(result, 0); // Success QCOMPARE(pl.count(), 3); // The first will be moved to the last after move() corrects the index. Producer *pp1 = pl.get_clip(0); Producer *pp2 = pl.get_clip(1); Producer *pp3 = pl.get_clip(2); QCOMPARE(pp1->parent().get_int("id"), 2); QCOMPARE(pp2->parent().get_int("id"), 3); QCOMPARE(pp3->parent().get_int("id"), 1); delete pp1; delete pp2; delete pp3; } void Reorder() { Playlist pl(profile); QVERIFY(pl.is_valid()); // Initial order: 1, 2, 3 Producer p1(profile, "noise"); p1.set("id", 1); QVERIFY(p1.is_valid()); pl.append(p1); Producer p2(profile, "noise"); p2.set("id", 2); QVERIFY(p2.is_valid()); pl.append(p2); Producer p3(profile, "noise"); p3.set("id", 3); QVERIFY(p3.is_valid()); pl.append(p3); QCOMPARE(pl.count(), 3); // Reverse the order of the clips. int new_order[] = {2, 1, 0}; int result = pl.reorder(new_order); QCOMPARE(result, 0); // Success QCOMPARE(pl.count(), 3); // The order will be reversed. Producer *pp1 = pl.get_clip(0); Producer *pp2 = pl.get_clip(1); Producer *pp3 = pl.get_clip(2); QCOMPARE(pp1->parent().get_int("id"), 3); QCOMPARE(pp2->parent().get_int("id"), 2); QCOMPARE(pp3->parent().get_int("id"), 1); delete pp1; delete pp2; delete pp3; } void ReorderErrorOnDuplicate() { Playlist pl(profile); QVERIFY(pl.is_valid()); // Initial order: 1, 2, 3 Producer p1(profile, "noise"); p1.set("id", 1); QVERIFY(p1.is_valid()); pl.append(p1); Producer p2(profile, "noise"); p2.set("id", 2); QVERIFY(p2.is_valid()); pl.append(p2); Producer p3(profile, "noise"); p3.set("id", 3); QVERIFY(p3.is_valid()); pl.append(p3); QCOMPARE(pl.count(), 3); // Provide an order with duplicate indices. int new_order[] = {2, 2, 2}; int result = pl.reorder(new_order); QCOMPARE(result, 1); // Fail QCOMPARE(pl.count(), 3); // The order will unchanged. Producer *pp1 = pl.get_clip(0); Producer *pp2 = pl.get_clip(1); Producer *pp3 = pl.get_clip(2); QCOMPARE(pp1->parent().get_int("id"), 1); QCOMPARE(pp2->parent().get_int("id"), 2); QCOMPARE(pp3->parent().get_int("id"), 3); delete pp1; delete pp2; delete pp3; } void ReorderErrorOnInvalidIndex() { Playlist pl(profile); QVERIFY(pl.is_valid()); // Initial order: 1, 2, 3 Producer p1(profile, "noise"); p1.set("id", 1); QVERIFY(p1.is_valid()); pl.append(p1); Producer p2(profile, "noise"); p2.set("id", 2); QVERIFY(p2.is_valid()); pl.append(p2); Producer p3(profile, "noise"); p3.set("id", 3); QVERIFY(p3.is_valid()); pl.append(p3); QCOMPARE(pl.count(), 3); // Provide an order with an invalid index. int new_order[] = {0, 1, 10}; int result = pl.reorder(new_order); QCOMPARE(result, 1); // Fail QCOMPARE(pl.count(), 3); // The order will unchanged. Producer *pp1 = pl.get_clip(0); Producer *pp2 = pl.get_clip(1); Producer *pp3 = pl.get_clip(2); QCOMPARE(pp1->parent().get_int("id"), 1); QCOMPARE(pp2->parent().get_int("id"), 2); QCOMPARE(pp3->parent().get_int("id"), 3); delete pp1; delete pp2; delete pp3; } }; QTEST_APPLESS_MAIN(TestPlaylist) #include "test_playlist.moc" mlt-7.22.0/src/tests/test_playlist/test_playlist.pro000664 000000 000000 00000000113 14531534050 022624 0ustar00rootroot000000 000000 include(../common.pri) TARGET = test_playlist SOURCES += test_playlist.cpp mlt-7.22.0/src/tests/test_producer/000775 000000 000000 00000000000 14531534050 017171 5ustar00rootroot000000 000000 mlt-7.22.0/src/tests/test_producer/test_producer.cpp000664 000000 000000 00000003421 14531534050 022557 0ustar00rootroot000000 000000 /* * Copyright (C) 2021 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with consumer library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include using namespace Mlt; #include #include #include using namespace Mlt; class TestProducer : public QObject { Q_OBJECT public: TestProducer() { Factory::init(); } private Q_SLOTS: void DefaultConstructorIsInvalid() { Producer p; QVERIFY(!p.is_valid()); } void CutIdentifiesAsProducer() { Profile profile; Producer producer(profile, "noise"); QCOMPARE(producer.type(), mlt_service_producer_type); // Taken as a service, the identity is correct Mlt::Service *cutService = producer.cut(0, 1); QCOMPARE(cutService->type(), mlt_service_producer_type); // Promoted to a producer and the identity is correct Mlt::Producer cutProducer(*cutService); QCOMPARE(cutProducer.type(), mlt_service_producer_type); delete cutService; } }; QTEST_APPLESS_MAIN(TestProducer) #include "test_producer.moc" mlt-7.22.0/src/tests/test_producer/test_producer.pro000664 000000 000000 00000000116 14531534050 022573 0ustar00rootroot000000 000000 include (../common.pri) TARGET = test_producer SOURCES = test_producer.cpp mlt-7.22.0/src/tests/test_properties/000775 000000 000000 00000000000 14531534050 017542 5ustar00rootroot000000 000000 mlt-7.22.0/src/tests/test_properties/test_properties.cpp000664 000000 000000 00000141541 14531534050 023507 0ustar00rootroot000000 000000 /* * Copyright (C) 2013-2018 Dan Dennedy * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with consumer library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include using namespace Mlt; extern "C" { #define __APPLE__ #include #include } #include static const bool kRunLongTests = true; class TestProperties : public QObject { Q_OBJECT mlt_locale_t locale; public: TestProperties() { #if !defined(_WIN32) && (defined(__GLIBC__) || defined(__APPLE__)) locale = newlocale(LC_NUMERIC_MASK, "POSIX", NULL); #else locale = 0; #endif Factory::init(); } ~TestProperties() { #if !defined(_WIN32) && (defined(__GLIBC__) || defined(__APPLE__)) freelocale(locale); #endif } private Q_SLOTS: void InstantiationIsAReference() { Properties p; QCOMPARE(p.ref_count(), 1); } void CopyAddsReference() { Properties p; Properties q = p; QCOMPARE(p.ref_count(), 2); } void DestructionRemovesReference() { Properties p; Properties *q = new Properties(p); QCOMPARE(p.ref_count(), 2); delete q; QCOMPARE(p.ref_count(), 1); } void SetAndGetString() { Properties p; p.set("key", "value"); QVERIFY(p.get("key")); QVERIFY(QString(p.get("key")) != QString("")); QCOMPARE(p.get("key"), "value"); } void SetAndGetInt() { Properties p; int i = 1; p.set("key", i); QCOMPARE(p.get_int("key"), i); } void SetAndGetDouble() { Properties p; double d = 1.0; p.set("key", d); QCOMPARE(p.get_double("key"), d); } void SetAndGetInt64() { Properties p; int64_t i = 1LL << 32; p.set("key", i); QCOMPARE(p.get_int64("key"), i); } void SetAndGetData() { Properties p; const char *value = "value"; char *const s = strdup(value); p.set("key", s, strlen(s), free); int size = 0; QCOMPARE((char *) p.get_data("key", size), value); QCOMPARE(size, int(strlen(value))); } void IntFromString() { Properties p; const char *s = "-1"; int i = -1; p.set("key", i); QCOMPARE(p.get("key"), s); p.set("key", s); QCOMPARE(p.get_int("key"), i); } void Int64FromString() { Properties p; const char *s = "-1"; int64_t i = -1; p.set("key", i); QCOMPARE(p.get("key"), s); p.set("key", s); QCOMPARE(p.get_int64("key"), i); } void DoubleFromString() { Properties p; const char *s = "-1.23456"; double d = -1.23456; p.set("key", d); QCOMPARE(p.get("key"), s); p.set("key", s); QCOMPARE(p.get_double("key"), d); } void SetNullRemovesProperty() { Properties p; const char *s = NULL; p.set("key", "value"); p.set("key", s); QCOMPARE(p.get("key"), s); } void SetAndGetHexColor() { Properties p; const char *hexColorString = "0xaabbccdd"; int hexColorInt = 0xaabbccdd; p.set("key", hexColorString); QCOMPARE(p.get_int("key"), hexColorInt); } void SetAndGetCssColor() { Properties p; const char *cssColorString = "#aabbcc"; int cssColorInt = 0xaabbccff; p.set("key", cssColorString); QCOMPARE(p.get_int("key"), cssColorInt); const char *cssColorAlphaString = "#00aabbcc"; int cssColorAlphaInt = 0xaabbcc00; p.set("key", cssColorAlphaString); QCOMPARE(p.get_int("key"), cssColorAlphaInt); } void SetAndGetTimeCode() { Profile profile; Properties p; p.set("_profile", profile.get_profile(), 0); const char *timeString = "11:22:33:04"; p.set("key", timeString); QCOMPARE(p.get_int("key"), 1023829); p.set("key", 1023829); QCOMPARE(p.get_time("key", mlt_time_smpte_df), timeString); } void SetAndGetTimeCodeNtscFps() { Profile profile("atsc_720p_2997"); Properties p; p.set("_profile", profile.get_profile(), 0); const char *timeString = "00:00:17;09"; // Looking just add seconds: // 510 f / 29.97 fps = 17.017017017 s // 29.97 fps * 17 s = 509.49 f // round(509.49) = 509 f, which it not equal to 510, the original amount. // However, ceiling(509.49) = 510 f // Now, adding on the frames part: // 509.49 f + 9 f = 518.49 f // ceiling(518.49) = 519 f const int frames = 519; p.set("key", timeString); QCOMPARE(p.get_int("key"), frames); p.set("key", frames); QCOMPARE(p.get_time("key", mlt_time_smpte_df), timeString); } void SetAndGetTimeCode5994Fps() { Profile profile("atsc_720p_5994"); Properties p; p.set("_profile", profile.get_profile(), 0); const char *timeString = "00:01:00;04"; int frames = 3600; p.set("key", timeString); QCOMPARE(p.get_int("key"), frames); p.set("key", frames); QCOMPARE(p.get_time("key", mlt_time_smpte_df), timeString); timeString = "00:10:00;01"; frames = 36001 - 9 * 4; p.set("key", timeString); QCOMPARE(p.get_int("key"), frames); p.set("key", frames); QCOMPARE(p.get_time("key", mlt_time_smpte_df), timeString); } void SetAndGetTimeCodeNonIntFps() { Profile profile("atsc_720p_2398"); Properties p; p.set("_profile", profile.get_profile(), 0); const char *timeString = "11:22:33:04"; // 11 * 3600 + 22 * 60 + 33 = 40953 s // floor(23.98 fps * 40953 s) = 981890 f // 981890 f + 4 f = 981894 f int frames = 981894; p.set("key", timeString); QCOMPARE(p.get_int("key"), frames); p.set("key", frames); // floor(981894 f / (24000/1001 * 3600)) = 11 h // 981894 f - floor(11 * 3600 * 24000/1001) = 32444 f // floor(32444 f / (24000/1001 * 60)) = 22 m // 981894 f - floor((11 * 3600 + 22 * 60) * 24000/1001) = 796 f // floor(796 f / (24000/1001)) = 33 s // 981894 f - ceil((11 * 3600 + 22 * 60 + 33) * 24000/1001) = 4f QCOMPARE(p.get_time("key", mlt_time_smpte_df), timeString); QCOMPARE(p.get_time("key", mlt_time_clock), "11:22:33.200"); // Case that is known to have floating point error frames = 2877; p.set("key", frames); QCOMPARE(p.get_time("key", mlt_time_smpte_df), "00:02:00:00"); QCOMPARE(p.get_time("key", mlt_time_clock), "00:02:00.000"); if (kRunLongTests) for (int i = 0; i < 9999999; ++i) { p.set("key", i); // QWARN(p.get_time("key", mlt_time_smpte_df)); p.set("test", p.get_time("key", mlt_time_smpte_df)); QCOMPARE(p.get_int("test"), i); } } void SetAndGetTimeCodeNonDropFrame() { Profile profile("dv_ntsc"); Properties p; p.set("_profile", profile.get_profile(), 0); const char *timeString = "11:22:33:04"; const int frames = 1228594; p.set("key", frames); QCOMPARE(p.get_time("key", mlt_time_smpte_ndf), timeString); } void SetAndGetTimeClock() { Profile profile; Properties p; p.set("_profile", profile.get_profile(), 0); const char *timeString = "11:22:33.400"; p.set("key", timeString); QCOMPARE(p.get_int("key"), 1023835); p.set("key", 1023835); QCOMPARE(p.get_time("key", mlt_time_clock), timeString); } void SetAndGetTimeClockNtscFps() { Profile profile("dv_ntsc"); Properties p; p.set("_profile", profile.get_profile(), 0); const char *timeString = "11:22:33.400"; const int frames = 1227374; p.set("key", timeString); QCOMPARE(p.get_int("key"), frames); p.set("key", frames); QCOMPARE(p.get_time("key", mlt_time_clock), timeString); if (kRunLongTests) for (int i = 0; i < 9999999; ++i) { p.set("key", i); // QWARN(p.get_time("key", mlt_time_clock)); p.set("test", p.get_time("key", mlt_time_clock)); QCOMPARE(p.get_int("test"), i); } } void SetAndGetTimeClockNonIntFps() { Profile profile("atsc_720p_2398"); Properties p; p.set("_profile", profile.get_profile(), 0); if (kRunLongTests) for (int i = 0; i < 9999999; ++i) { p.set("key", i); // QWARN(p.get_time("key", mlt_time_clock)); p.set("test", p.get_time("key", mlt_time_clock)); QCOMPARE(p.get_int("test"), i); } } void SetSimpleMathExpression() { Properties p; p.set("key", "@16.0/9.0 *2 +3 -1"); QCOMPARE(p.get_int("key"), 5); QCOMPARE(p.get_double("key"), 16.0 / 9.0 * 2 + 3 - 1); } void SetMathExpressionWithProperty() { Properties p; p.set("width", 100); p.set("key", "@16.0/9.0 *width"); QCOMPARE(p.get_int("key"), 177); } void PassOneProperty() { Properties p[2]; const char *s = "value"; p[0].set("key", s); QCOMPARE(p[1].get("key"), (void *) 0); p[1].pass_property(p[0], "key"); QCOMPARE(p[1].get("key"), s); } void PassMultipleByPrefix() { Properties p[2]; const char *s = "value"; p[0].set("key.one", s); p[0].set("key.two", s); QCOMPARE(p[1].get("key.one"), (void *) 0); QCOMPARE(p[1].get("key.two"), (void *) 0); p[1].pass_values(p[0], "key."); QCOMPARE(p[1].get("one"), s); QCOMPARE(p[1].get("two"), s); } void PassMultipleByList() { Properties p[2]; const char *s = "value"; p[0].set("key.one", s); p[0].set("key.two", s); QCOMPARE(p[1].get("key.one"), (void *) 0); QCOMPARE(p[1].get("key.two"), (void *) 0); p[1].pass_list(p[0], "key.one key.two"); QCOMPARE(p[1].get("key.one"), s); QCOMPARE(p[1].get("key.two"), s); } void MirrorProperties() { Properties p[2]; p[0].mirror(p[1]); p[0].set("key", "value"); QCOMPARE(p[1].get("key"), "value"); } void InheritProperties() { Properties p[2]; p[0].set("key", "value"); QVERIFY(p[1].get("key") == 0); p[1].inherit(p[0]); QCOMPARE(p[1].get("key"), "value"); } void ParseString() { Properties p; QCOMPARE(p.get("key"), (void *) 0); p.parse("key=value"); QCOMPARE(p.get("key"), "value"); p.parse("key=\"new value\""); QCOMPARE(p.get("key"), "new value"); } void RenameProperty() { Properties p; p.set("key", "value"); QVERIFY(p.get("new key") == 0); p.rename("key", "new key"); QCOMPARE(p.get("new key"), "value"); } void SequenceDetected() { Properties p; p.set("1", 1); p.set("2", 2); p.set("3", 3); QVERIFY(p.is_sequence()); p.set("four", 4); QVERIFY(!p.is_sequence()); } void SerializesToYamlTiny() { Properties p[3]; p[0].set("key1", "value1"); p[0].set("key:2", "value[2]"); p[1].set("1", "value3"); p[1].set("2", "value:4"); p[2].set("1", "value5"); p[2].set("2", "\"value6\""); p[0].set("seq1", p[1].get_properties(), 0); p[0].set("seq'2", p[2].get_properties(), 0); char *serializedYaml = p[0].serialise_yaml(); QCOMPARE(serializedYaml, "---\n" "key1: value1\n" "\"key:2\": \"value[2]\"\n" "seq1:\n" " - value3\n" " - \"value:4\"\n" "\"seq'2\":\n" " - value5\n" " - '\"value6\"'\n" "...\n"); free(serializedYaml); } void ParsesYamlTiny() { QTemporaryFile tempFile; if (tempFile.open()) { tempFile.write("---\n" "key1: value1\n" "\"key:2\":\"value[2]\"\n" "seq1:\n" " - value3\n" " - \"value:4\"\n" "\"seq'2\":\n" " - value5\n" " - \"value:6\"\n" "...\n"); tempFile.close(); } QScopedPointer p( Properties::parse_yaml(tempFile.fileName().toUtf8().constData())); QVERIFY(!p.isNull()); QVERIFY(p->is_valid()); QCOMPARE(p->get("key1"), "value1"); QCOMPARE(p->get("key:2"), "value[2]"); } void RadixRespondsToLocale() { Properties p; p.set_lcnumeric("en_US.UTF-8"); p.set("key", "0.125"); QCOMPARE(p.get_double("key"), double(1) / double(8)); #if !defined(_WIN32) p.set_lcnumeric("de_DE.UTF-8"); p.set("key", "0,125"); QCOMPARE(p.get_double("key"), double(1) / double(8)); #endif } void AnimationInsert() { double fps = 25.0; mlt_animation a = mlt_animation_new(); struct mlt_animation_item_s item; item.is_key = 1; item.keyframe_type = mlt_keyframe_discrete; item.property = mlt_property_init(); item.frame = 0; mlt_property_set_string(item.property, "0"); mlt_animation_insert(a, &item); item.frame = 1; mlt_property_set_string(item.property, "1"); mlt_animation_insert(a, &item); item.frame = 2; mlt_property_set_string(item.property, "2"); mlt_animation_insert(a, &item); QCOMPARE(mlt_animation_get_length(a), 2); char *a_serialized = mlt_animation_serialize(a); mlt_animation_parse(a, a_serialized, 0, fps, locale); QCOMPARE(a_serialized, "0|=0;1|=1;2|=2"); if (a_serialized) free(a_serialized); mlt_property_close(item.property); mlt_animation_close(a); } void DoubleAnimation() { double fps = 25.0; mlt_animation a = mlt_animation_new(); struct mlt_animation_item_s item; mlt_animation_parse(a, "50=1; 60=60; 100=0", 100, fps, locale); mlt_animation_remove(a, 60); char *a_serialized = mlt_animation_serialize(a); QCOMPARE(a_serialized, "50=1;100=0"); if (a_serialized) free(a_serialized); item.property = mlt_property_init(); mlt_animation_get_item(a, &item, 10); QCOMPARE(mlt_property_get_double(item.property, fps, locale), 1.0); QCOMPARE(item.is_key, 0); mlt_animation_get_item(a, &item, 50); QCOMPARE(mlt_property_get_double(item.property, fps, locale), 1.0); QCOMPARE(item.is_key, 1); mlt_animation_get_item(a, &item, 75); QCOMPARE(mlt_property_get_double(item.property, fps, locale), 0.5); QCOMPARE(item.is_key, 0); mlt_animation_get_item(a, &item, 100); QCOMPARE(mlt_property_get_double(item.property, fps, locale), 0.0); QCOMPARE(item.is_key, 1); mlt_animation_get_item(a, &item, 110); QCOMPARE(mlt_property_get_double(item.property, fps, locale), 0.0); QCOMPARE(item.is_key, 0); mlt_property_close(item.property); mlt_animation_close(a); } void IntAnimation() { double fps = 25.0; mlt_animation a = mlt_animation_new(); struct mlt_animation_item_s item; mlt_animation_parse(a, "50=100; 60=60; 100=0", 100, fps, locale); mlt_animation_remove(a, 60); char *a_serialized = mlt_animation_serialize(a); QCOMPARE(a_serialized, "50=100;100=0"); if (a_serialized) free(a_serialized); item.property = mlt_property_init(); mlt_animation_get_item(a, &item, 10); QCOMPARE(mlt_property_get_int(item.property, fps, locale), 100); QCOMPARE(item.is_key, 0); mlt_animation_get_item(a, &item, 50); QCOMPARE(mlt_property_get_int(item.property, fps, locale), 100); QCOMPARE(item.is_key, 1); mlt_animation_get_item(a, &item, 75); QCOMPARE(mlt_property_get_int(item.property, fps, locale), 50); QCOMPARE(item.is_key, 0); mlt_animation_get_item(a, &item, 100); QCOMPARE(mlt_property_get_int(item.property, fps, locale), 0); QCOMPARE(item.is_key, 1); mlt_animation_get_item(a, &item, 110); QCOMPARE(mlt_property_get_int(item.property, fps, locale), 0); QCOMPARE(item.is_key, 0); mlt_property_close(item.property); mlt_animation_close(a); } void AnimationWithTimeValueKeyframes() { double fps = 25.0; mlt_animation a = mlt_animation_new(); struct mlt_animation_item_s item; mlt_animation_parse(a, ":2.0=1; :4.0=0", 100, fps, locale); char *a_serialized = mlt_animation_serialize(a); // Time serializes to frame units :-\. QCOMPARE(a_serialized, "50=1;100=0"); if (a_serialized) free(a_serialized); item.property = mlt_property_init(); mlt_animation_get_item(a, &item, 10); QCOMPARE(mlt_property_get_double(item.property, fps, locale), 1.0); QCOMPARE(item.is_key, 0); mlt_animation_get_item(a, &item, 50); QCOMPARE(mlt_property_get_double(item.property, fps, locale), 1.0); QCOMPARE(item.is_key, 1); mlt_animation_get_item(a, &item, 75); QCOMPARE(mlt_property_get_double(item.property, fps, locale), 0.5); QCOMPARE(item.is_key, 0); mlt_animation_get_item(a, &item, 100); QCOMPARE(mlt_property_get_double(item.property, fps, locale), 0.0); QCOMPARE(item.is_key, 1); mlt_animation_get_item(a, &item, 110); QCOMPARE(mlt_property_get_double(item.property, fps, locale), 0.0); QCOMPARE(item.is_key, 0); mlt_property_close(item.property); mlt_animation_close(a); } void DiscreteIntAnimation() { double fps = 25.0; mlt_animation a = mlt_animation_new(); struct mlt_animation_item_s item; mlt_animation_parse(a, "50|=100; 60|=60; 100|=0", 100, fps, locale); char *a_serialized = mlt_animation_serialize(a); QCOMPARE(a_serialized, "50|=100;60|=60;100|=0"); if (a_serialized) free(a_serialized); item.property = mlt_property_init(); mlt_animation_get_item(a, &item, 10); QCOMPARE(mlt_property_get_int(item.property, fps, locale), 100); QCOMPARE(item.is_key, 0); mlt_animation_get_item(a, &item, 50); QCOMPARE(mlt_property_get_int(item.property, fps, locale), 100); QCOMPARE(item.is_key, 1); mlt_animation_get_item(a, &item, 55); QCOMPARE(mlt_property_get_int(item.property, fps, locale), 100); QCOMPARE(item.is_key, 0); mlt_animation_get_item(a, &item, 60); QCOMPARE(mlt_property_get_int(item.property, fps, locale), 60); QCOMPARE(item.is_key, 1); mlt_animation_get_item(a, &item, 75); QCOMPARE(mlt_property_get_int(item.property, fps, locale), 60); QCOMPARE(item.is_key, 0); mlt_animation_get_item(a, &item, 100); QCOMPARE(mlt_property_get_int(item.property, fps, locale), 0); QCOMPARE(item.is_key, 1); mlt_animation_get_item(a, &item, 110); QCOMPARE(mlt_property_get_int(item.property, fps, locale), 0); QCOMPARE(item.is_key, 0); mlt_property_close(item.property); mlt_animation_close(a); } void StringAnimation() { double fps = 25.0; mlt_animation a = mlt_animation_new(); struct mlt_animation_item_s item; mlt_animation_parse(a, "50=hello world; 60=\"good night\"; 100=bar", 100, fps, locale); char *a_serialized = mlt_animation_serialize(a); QCOMPARE(a_serialized, "50=hello world;60=good night;100=bar"); if (a_serialized) free(a_serialized); mlt_animation_parse(a, "50=hello world; 60=\"good; night\"; 100=bar", 100, fps, locale); a_serialized = mlt_animation_serialize(a); QCOMPARE(a_serialized, "50=hello world;60=\"good; night\";100=bar"); if (a_serialized) free(a_serialized); mlt_animation_parse(a, "50=hello world; 60=\"\"good night\"\"; 100=bar", 100, fps, locale); a_serialized = mlt_animation_serialize(a); QCOMPARE(a_serialized, "50=hello world;60=\"good night\";100=bar"); if (a_serialized) free(a_serialized); item.property = mlt_property_init(); mlt_animation_get_item(a, &item, 10); QCOMPARE(mlt_property_get_string(item.property), "hello world"); QCOMPARE(item.is_key, 0); mlt_animation_get_item(a, &item, 50); QCOMPARE(mlt_property_get_string(item.property), "hello world"); QCOMPARE(item.is_key, 1); mlt_animation_get_item(a, &item, 75); QCOMPARE(mlt_property_get_string(item.property), "\"good night\""); QCOMPARE(item.is_key, 0); mlt_animation_get_item(a, &item, 100); QCOMPARE(mlt_property_get_string(item.property), "bar"); QCOMPARE(item.is_key, 1); mlt_animation_get_item(a, &item, 110); QCOMPARE(mlt_property_get_string(item.property), "bar"); QCOMPARE(item.is_key, 0); mlt_property_close(item.property); mlt_animation_close(a); } void test_property_anim_get_double() { double fps = 25.0; int len = 0; mlt_property p = mlt_property_init(); mlt_property_set_string(p, "10=100; 20=200"); QCOMPARE(mlt_property_get_double(p, fps, locale), 10.0); QCOMPARE(mlt_property_anim_get_double(p, fps, locale, 0, len), 100.0); QCOMPARE(mlt_property_anim_get_double(p, fps, locale, 15, len), 150.0); QCOMPARE(mlt_property_anim_get_double(p, fps, locale, 20, len), 200.0); mlt_property_set_string(p, "1.5"); QCOMPARE(mlt_property_get_double(p, fps, locale), 1.5); QCOMPARE(mlt_property_anim_get_double(p, fps, locale, 10, 100), 1.5); mlt_property_close(p); } void test_property_anim_get_int() { double fps = 25.0; int len = 100; mlt_property p = mlt_property_init(); mlt_property_set_string(p, "10=100; 20=200"); QCOMPARE(mlt_property_get_int(p, fps, locale), 10); QCOMPARE(mlt_property_anim_get_int(p, fps, locale, 0, len), 100); QCOMPARE(mlt_property_anim_get_int(p, fps, locale, 15, len), 150); QCOMPARE(mlt_property_anim_get_int(p, fps, locale, 20, len), 200); mlt_property_set_string(p, "1.5"); QCOMPARE(mlt_property_get_int(p, fps, locale), 1); QCOMPARE(mlt_property_anim_get_int(p, fps, locale, 10, 100), 1); mlt_property_close(p); } void test_property_anim_get_color() { double fps = 25.0; int len = 100; mlt_property p = mlt_property_init(); mlt_property_set_string(p, "10=#ffff00ff;20=green"); QCOMPARE(mlt_property_get_int(p, fps, locale), 10); mlt_color color = mlt_property_anim_get_color(p, fps, locale, 0, len); QCOMPARE(color.r, 255); QCOMPARE(color.g, 0); QCOMPARE(color.b, 255); QCOMPARE(color.a, 255); color = mlt_property_anim_get_color(p, fps, locale, 15, len); QCOMPARE(color.r, 127); QCOMPARE(color.g, 127); QCOMPARE(color.b, 127); QCOMPARE(color.a, 255); color = mlt_property_anim_get_color(p, fps, locale, 20, len); QCOMPARE(color.r, 0); QCOMPARE(color.g, 255); QCOMPARE(color.b, 0); QCOMPARE(color.a, 255); mlt_property_close(p); } void SmoothIntAnimation() { double fps = 25.0; mlt_animation a = mlt_animation_new(); struct mlt_animation_item_s item; mlt_animation_parse(a, "0=80;10~=80; 20~=30; 30~=40; 40~=28; 50=90; 60=0; 70=60; 80=20", 100, fps, locale); item.property = mlt_property_init(); char *a_serialized = mlt_animation_serialize(a); QCOMPARE(a_serialized, "0=80;10~=80;20~=30;30~=40;40~=28;50=90;60=0;70=60;80=20"); if (a_serialized) free(a_serialized); mlt_animation_get_item(a, &item, 10); QCOMPARE(mlt_property_get_int(item.property, fps, locale), 80); QCOMPARE(item.is_key, 1); mlt_animation_get_item(a, &item, 50); QCOMPARE(mlt_property_get_int(item.property, fps, locale), 90); QCOMPARE(item.is_key, 1); mlt_animation_get_item(a, &item, 55); QCOMPARE(mlt_property_get_int(item.property, fps, locale), 45); QCOMPARE(item.is_key, 0); mlt_animation_get_item(a, &item, 60); QCOMPARE(mlt_property_get_int(item.property, fps, locale), 0); QCOMPARE(item.is_key, 1); mlt_animation_get_item(a, &item, 75); QCOMPARE(mlt_property_get_int(item.property, fps, locale), 40); QCOMPARE(item.is_key, 0); mlt_animation_get_item(a, &item, 100); QCOMPARE(mlt_property_get_int(item.property, fps, locale), 20); QCOMPARE(item.is_key, 0); mlt_animation_get_item(a, &item, 110); QCOMPARE(mlt_property_get_int(item.property, fps, locale), 20); QCOMPARE(item.is_key, 0); mlt_property_close(item.property); mlt_animation_close(a); } void test_property_anim_set_double() { double fps = 25.0; int len = 100; mlt_property p = mlt_property_init(); mlt_property_set_string(p, "10=100; 20=200"); mlt_property_anim_set_double(p, 1.5, fps, locale, 30, len, mlt_keyframe_linear); QCOMPARE(mlt_property_get_double(p, fps, locale), 10.0); QCOMPARE(mlt_property_anim_get_double(p, fps, locale, 0, len), 100.0); QCOMPARE(mlt_property_anim_get_double(p, fps, locale, 15, len), 150.0); QCOMPARE(mlt_property_anim_get_double(p, fps, locale, 20, len), 200.0); QCOMPARE(mlt_property_anim_get_double(p, fps, locale, 25, len), 100.75); QCOMPARE(mlt_property_anim_get_double(p, fps, locale, 30, len), 1.5); mlt_property_close(p); } void test_property_anim_set_int() { double fps = 25.0; int len = 0; mlt_property p = mlt_property_init(); mlt_property_set_string(p, "10=100; 20=200"); mlt_property_anim_set_int(p, 300, fps, locale, 30, len, mlt_keyframe_linear); QCOMPARE(mlt_property_get_int(p, fps, locale), 10); QCOMPARE(mlt_property_anim_get_int(p, fps, locale, 0, len), 100); QCOMPARE(mlt_property_anim_get_int(p, fps, locale, 15, len), 150); QCOMPARE(mlt_property_anim_get_int(p, fps, locale, 20, len), 200); QCOMPARE(mlt_property_anim_get_int(p, fps, locale, 25, len), 250); QCOMPARE(mlt_property_anim_get_int(p, fps, locale, 30, len), 300); mlt_property_close(p); } void PercentAsRatio() { Properties p; p.set("foo", "12.3%"); QCOMPARE(p.get_double("foo"), 0.123); p.set("foo", "456 %"); QCOMPARE(p.get_double("foo"), 456.0); } void PropertiesAnimInt() { Properties p; p.set_lcnumeric("POSIX"); // Construct animation from scratch p.anim_set("foo", 0, 0); p.anim_set("foo", 100, 50, -1, mlt_keyframe_smooth); QCOMPARE(p.anim_get_int("foo", 0), 0); QCOMPARE(p.anim_get_int("foo", 25), 50); QCOMPARE(p.anim_get_int("foo", 50), 100); QCOMPARE(p.get("foo"), "0=0;50~=100"); // Animation from string value p.set("foo", "10=100;20=200"); QCOMPARE(p.anim_get_int("foo", 0), 100); QCOMPARE(p.anim_get_int("foo", 15), 150); QCOMPARE(p.anim_get_int("foo", 20), 200); // Animation from string using time clock values // Need to set a profile so fps can be used to convert time to frames. Profile profile("dv_pal"); p.set("_profile", profile.get_profile(), 0); p.set("foo", ":0.0=100; :2.0=200"); QCOMPARE(p.anim_get_int("foo", 0), 100); QCOMPARE(p.anim_get_int("foo", 25), 150); QCOMPARE(p.anim_get_int("foo", 50), 200); } void PropertiesAnimDouble() { Properties p; p.set_lcnumeric("POSIX"); // Construct animation from scratch p.anim_set("foo", 0.0, 0); p.anim_set("foo", 100.0, 50, -1, mlt_keyframe_smooth); QCOMPARE(p.anim_get_double("foo", 0), 0.0); QCOMPARE(p.anim_get_double("foo", 25), 50.0); QCOMPARE(p.anim_get_double("foo", 50), 100.0); QCOMPARE(p.get("foo"), "0=0;50~=100"); // Animation from string value p.set("foo", "10=100.2;20=200.8"); QCOMPARE(p.anim_get_double("foo", 0), 100.2); QCOMPARE(p.anim_get_double("foo", 15), 150.5); QCOMPARE(p.anim_get_double("foo", 20), 200.8); // Animation from string using time clock values // Need to set a profile so fps can be used to convert time to frames. Profile profile("dv_pal"); p.set("_profile", profile.get_profile(), 0); p.set("foo", ":0.0=100; :2.0=200"); QCOMPARE(p.anim_get_double("foo", 0), 100.0); QCOMPARE(p.anim_get_double("foo", 25), 150.0); QCOMPARE(p.anim_get_double("foo", 50), 200.0); // Test a non-animation string. p.set("bar", "0.5"); QCOMPARE(p.anim_get_double("bar", 25), 0.5); } void PropertiesStringAnimation() { Properties p; p.anim_set("key", "foo", 10); p.anim_set("key", "bar", 30); QCOMPARE(p.get("key"), "10|=foo;30|=bar"); p.set("key", "0=; 10=foo bar; 30=hello world"); QCOMPARE(p.anim_get("key", 1), ""); QCOMPARE(p.anim_get("key", 15), "foo bar"); QCOMPARE(p.anim_get("key", 45), "hello world"); } void PropertyRefreshOnAnimationChange() { // Create an animation property from string and see that it works. // Get the animation and modify the first position. // Ensure that change affects other get() functions { Properties p; p.set("foo", "10=100; 20=200"); QCOMPARE(p.get_double("foo"), 10.0); // Call anim_get_double() to create the animation QCOMPARE(p.anim_get_double("foo", 15, 20), 150.0); Mlt::Animation animation = p.get_animation("foo"); animation.key_set_frame(0, 15); QCOMPARE(p.get_double("foo"), 15.0); } { Properties p; p.set("foo", "10=100;20=200"); QCOMPARE(p.anim_get_double("foo", 15, 20), 150.0); Mlt::Animation animation = p.get_animation("foo"); animation.key_set_frame(0, 15); QCOMPARE(p.anim_get_double("foo", 15, 0), 100.0); } { Properties p; p.set("foo", "10=100; 20=200"); QCOMPARE(p.get_int("foo"), 10); // Call anim_get_int() to create the animation QCOMPARE(p.anim_get_int("foo", 15, 20), 150); Mlt::Animation animation = p.get_animation("foo"); animation.key_set_frame(0, 15); QCOMPARE(p.get_int("foo"), 15); } { Properties p; p.set("foo", "10=100; 20=200"); QCOMPARE(p.anim_get_int("foo", 15, 20), 150); Mlt::Animation animation = p.get_animation("foo"); animation.key_set_frame(0, 15); QCOMPARE(p.anim_get_int("foo", 15, 0), 100); } { Properties p; p.set("foo", "10=100;20=200"); // Call anim_get_int() to create the animation QCOMPARE(p.anim_get_int("foo", 15, 20), 150); Mlt::Animation animation = p.get_animation("foo"); QCOMPARE(p.get("foo"), "10=100;20=200"); animation.key_set_frame(0, 15); QCOMPARE(p.get("foo"), "15=100;20=200"); } } void test_mlt_rect() { mlt_property p = mlt_property_init(); mlt_rect r = {1, 2, 3, 4, 5}; mlt_property_set_rect(p, r); QCOMPARE(mlt_property_get_string(p), "1 2 3 4 5"); r.o = DBL_MIN; mlt_property_set_rect(p, r); QCOMPARE(mlt_property_get_string(p), "1 2 3 4"); r.w = DBL_MIN; r.h = DBL_MIN; mlt_property_set_rect(p, r); QCOMPARE(mlt_property_get_string(p), "1 2"); mlt_property_set_string(p, "1.1/2.2:3.3x4.4:5.5"); r = mlt_property_get_rect(p, locale); QCOMPARE(r.x, 1.1); QCOMPARE(r.y, 2.2); QCOMPARE(r.w, 3.3); QCOMPARE(r.h, 4.4); QCOMPARE(r.o, 5.5); mlt_property_set_string(p, "1.1 2.2"); r = mlt_property_get_rect(p, locale); QCOMPARE(r.x, 1.1); QCOMPARE(r.y, 2.2); QCOMPARE(r.w, DBL_MIN); mlt_property_set_int64(p, UINT_MAX); r = mlt_property_get_rect(p, locale); QCOMPARE(r.x, double(UINT_MAX)); mlt_property_close(p); } void SetAndGetRect() { Properties p; mlt_rect r; r.x = 1.1; r.y = 2.2; r.w = 3.3; r.h = 4.4; r.o = 5.5; p.set("key", r); mlt_rect q = p.get_rect("key"); QCOMPARE(q.x, 1.1); QCOMPARE(q.y, 2.2); QCOMPARE(q.w, 3.3); QCOMPARE(q.h, 4.4); QCOMPARE(q.o, 5.5); p.set("key", 10, 20, 30, 40); q = p.get_rect("key"); QCOMPARE(q.x, 10.0); QCOMPARE(q.y, 20.0); QCOMPARE(q.w, 30.0); QCOMPARE(q.h, 40.0); } void RectFromString() { Properties p; p.set_lcnumeric("POSIX"); const char *s = "1.1 2.2 3.3 4.4 5.5"; mlt_rect r = {1.1, 2.2, 3.3, 4.4, 5.5}; p.set("key", r); QCOMPARE(p.get("key"), s); p.set("key", s); r = p.get_rect("key"); QCOMPARE(r.x, 1.1); QCOMPARE(r.y, 2.2); QCOMPARE(r.w, 3.3); QCOMPARE(r.h, 4.4); QCOMPARE(r.o, 5.5); } void RectAnimation() { mlt_rect r1 = {0, 0, 200, 200, 0}; mlt_rect r2 = {100, 100, 400, 400, 1.0}; Properties p; p.set_lcnumeric("POSIX"); // Construct animation from scratch p.anim_set("key", r1, 0); p.anim_set("key", r2, 50); QCOMPARE(p.anim_get_rect("key", 0).x, 0.0); QCOMPARE(p.anim_get_rect("key", 25).x, 50.0); QCOMPARE(p.anim_get_rect("key", 25).y, 50.0); QCOMPARE(p.anim_get_rect("key", 25).w, 300.0); QCOMPARE(p.anim_get_rect("key", 25).h, 300.0); QCOMPARE(p.anim_get_rect("key", 25).o, 0.5); QCOMPARE(p.anim_get_rect("key", 50).x, 100.0); QCOMPARE(p.get("key"), "0=0 0 200 200 0;50=100 100 400 400 1"); // Animation from string value QCOMPARE(p.anim_get_rect("key", 0).x, 0.0); QCOMPARE(p.anim_get_rect("key", 0).y, 0.0); QCOMPARE(p.anim_get_rect("key", 0).w, 200.0); QCOMPARE(p.anim_get_rect("key", 0).h, 200.0); QCOMPARE(p.anim_get_rect("key", 0).o, 0.0); QCOMPARE(p.anim_get_rect("key", 50).x, 100.0); QCOMPARE(p.anim_get_rect("key", 50).y, 100.0); QCOMPARE(p.anim_get_rect("key", 50).w, 400.0); QCOMPARE(p.anim_get_rect("key", 50).h, 400.0); QCOMPARE(p.anim_get_rect("key", 50).o, 1.0); QCOMPARE(p.anim_get_rect("key", 15).x, 30.0); QCOMPARE(p.anim_get_rect("key", 15).y, 30.0); QCOMPARE(p.anim_get_rect("key", 15).w, 260.0); QCOMPARE(p.anim_get_rect("key", 15).h, 260.0); QCOMPARE(p.anim_get_rect("key", 15).o, 0.3); // Smooth animation p.set("key", "0~=0/0:200x200:0; 50=100/100:400x400:1"); QCOMPARE(p.anim_get_rect("key", 0).x, 0.0); QCOMPARE(p.anim_get_rect("key", 0).y, 0.0); QCOMPARE(p.anim_get_rect("key", 0).w, 200.0); QCOMPARE(p.anim_get_rect("key", 0).h, 200.0); QCOMPARE(p.anim_get_rect("key", 0).o, 0.0); QCOMPARE(p.anim_get_rect("key", 50).x, 100.0); QCOMPARE(p.anim_get_rect("key", 50).y, 100.0); QCOMPARE(p.anim_get_rect("key", 50).w, 400.0); QCOMPARE(p.anim_get_rect("key", 50).h, 400.0); QCOMPARE(p.anim_get_rect("key", 50).o, 1.0); QCOMPARE(p.anim_get_rect("key", 15).x, 25.8); QCOMPARE(p.anim_get_rect("key", 15).y, 25.8); QCOMPARE(p.anim_get_rect("key", 15).w, 251.6); QCOMPARE(p.anim_get_rect("key", 15).h, 251.6); QCOMPARE(p.anim_get_rect("key", 15).o, 0.258); // Using percentages p.set("key", "0=0 0; 50=100% 200%"); QCOMPARE(p.anim_get_rect("key", 25).x, 0.5); QCOMPARE(p.anim_get_rect("key", 25).y, 1.0); } void ColorFromInt() { Properties p; p.set_lcnumeric("POSIX"); p.set("key", (int) 0xaabbccdd); mlt_color color = p.get_color("key"); QCOMPARE(color.r, quint8(0xaa)); QCOMPARE(color.g, quint8(0xbb)); QCOMPARE(color.b, quint8(0xcc)); QCOMPARE(color.a, quint8(0xdd)); p.set("key", color); QCOMPARE(p.get_int("key"), int(0xaabbccdd)); } void ColorFromString() { Properties p; p.set_lcnumeric("POSIX"); p.set("key", "red"); mlt_color color = p.get_color("key"); QCOMPARE(color.r, quint8(0xff)); QCOMPARE(color.g, quint8(0x00)); QCOMPARE(color.b, quint8(0x00)); QCOMPARE(color.a, quint8(0xff)); //pattern #AARRGGBB p.set("key", "#deadd00d"); color = p.get_color("key"); QCOMPARE(color.r, quint8(0xad)); QCOMPARE(color.g, quint8(0xd0)); QCOMPARE(color.b, quint8(0x0d)); QCOMPARE(color.a, quint8(0xde)); //pattern #0xRRGGBBAA p.set("key", "0xadd00dde"); color = p.get_color("key"); QCOMPARE(color.r, quint8(0xad)); QCOMPARE(color.g, quint8(0xd0)); QCOMPARE(color.b, quint8(0x0d)); QCOMPARE(color.a, quint8(0xde)); } void StringFromColor() { mlt_color color; color.r = quint8(0xad); color.g = quint8(0xd0); color.b = quint8(0x0d); color.a = quint8(0xde); mlt_property p = mlt_property_init(); mlt_property_set_color(p, color); QCOMPARE(mlt_property_get_string(p), "#deadd00d"); mlt_property_close(p); Properties pies; pies.set("key", color); QCOMPARE(pies.get("key"), "#deadd00d"); } void ColorAnimationCpp() { mlt_color c1 = {0xff, 0xef, 0xab, 0x00}; mlt_color c2 = {0x00, 0xff, 0xab, 0xdf}; Properties p; p.set_lcnumeric("POSIX"); // Construct animation from scratch p.anim_set("key", c1, 0); p.anim_set("key", c2, 50); QCOMPARE(p.anim_get_color("key", 0).r, 255); QCOMPARE(p.anim_get_color("key", 25).g, 247); QCOMPARE(p.anim_get_color("key", 25).b, 171); QCOMPARE(p.anim_get_color("key", 25).a, 111); QCOMPARE(p.get("key"), "0=#00ffefab;50=#df00ffab"); // Animation from string value QCOMPARE(p.anim_get_color("key", 0).r, 255); QCOMPARE(p.anim_get_color("key", 0).g, 239); QCOMPARE(p.anim_get_color("key", 0).b, 171); QCOMPARE(p.anim_get_color("key", 0).a, 0); QCOMPARE(p.anim_get_color("key", 50).r, 0); QCOMPARE(p.anim_get_color("key", 50).g, 255); QCOMPARE(p.anim_get_color("key", 50).b, 171); QCOMPARE(p.anim_get_color("key", 50).a, 223); QCOMPARE(p.anim_get_color("key", 15).r, 178); QCOMPARE(p.anim_get_color("key", 15).g, 243); QCOMPARE(p.anim_get_color("key", 15).b, 171); QCOMPARE(p.anim_get_color("key", 15).a, 66); } void SmoothColorAnimationCpp() { Properties p; p.set_lcnumeric("POSIX"); // Smooth animation p.set("key", "0~=#00ffefab; 50~=#df00ffab; 100~=#df00ffab; 150~=#df00ffab"); QCOMPARE(p.anim_get_color("key", 0).r, 255); QCOMPARE(p.anim_get_color("key", 0).g, 239); QCOMPARE(p.anim_get_color("key", 0).b, 171); QCOMPARE(p.anim_get_color("key", 0).a, 0); QCOMPARE(p.anim_get_color("key", 25).r, 127); QCOMPARE(p.anim_get_color("key", 25).g, 247); QCOMPARE(p.anim_get_color("key", 25).b, 171); QCOMPARE(p.anim_get_color("key", 25).a, 111); QCOMPARE(p.anim_get_color("key", 50).r, 0); QCOMPARE(p.anim_get_color("key", 50).g, 255); QCOMPARE(p.anim_get_color("key", 50).b, 171); QCOMPARE(p.anim_get_color("key", 50).a, 223); QCOMPARE(p.anim_get_color("key", 60).a, 237); QCOMPARE(p.anim_get_color("key", 70).a, 239); QCOMPARE(p.anim_get_color("key", 75).r, 0); QCOMPARE(p.anim_get_color("key", 75).g, 255); QCOMPARE(p.anim_get_color("key", 75).b, 171); QCOMPARE(p.anim_get_color("key", 75).a, 236); QCOMPARE(p.anim_get_color("key", 100).r, 0); QCOMPARE(p.anim_get_color("key", 100).g, 255); QCOMPARE(p.anim_get_color("key", 100).b, 171); QCOMPARE(p.anim_get_color("key", 100).a, 223); } void ColorAnimationCssString() { double fps = 25.0; mlt_animation a = mlt_animation_new(); struct mlt_animation_item_s item; mlt_animation_parse(a, "50=0xff00ffff; 60=0xaa00ffff; 100=0x00ff00ff", 100, fps, locale); mlt_animation_remove(a, 60); char *a_serialized = mlt_animation_serialize(a); QCOMPARE(a_serialized, "50=0xff00ffff;100=0x00ff00ff"); if (a_serialized) free(a_serialized); item.property = mlt_property_init(); mlt_animation_get_item(a, &item, 10); mlt_color color = mlt_property_get_color(item.property, fps, locale); QCOMPARE(color.r, 255); QCOMPARE(color.g, 0); QCOMPARE(color.b, 255); QCOMPARE(color.a, 255); QCOMPARE(item.is_key, 0); mlt_animation_get_item(a, &item, 50); color = mlt_property_get_color(item.property, fps, locale); QCOMPARE(color.r, 255); QCOMPARE(color.g, 0); QCOMPARE(color.b, 255); QCOMPARE(color.a, 255); QCOMPARE(item.is_key, 1); mlt_animation_get_item(a, &item, 75); color = mlt_property_get_color(item.property, fps, locale); QCOMPARE(color.r, 127); QCOMPARE(color.g, 127); QCOMPARE(color.b, 127); QCOMPARE(color.a, 255); QCOMPARE(item.is_key, 0); mlt_animation_get_item(a, &item, 100); color = mlt_property_get_color(item.property, fps, locale); QCOMPARE(color.r, 0); QCOMPARE(color.g, 255); QCOMPARE(color.b, 0); QCOMPARE(color.a, 255); QCOMPARE(item.is_key, 1); mlt_animation_get_item(a, &item, 110); color = mlt_property_get_color(item.property, fps, locale); QCOMPARE(color.r, 0); QCOMPARE(color.g, 255); QCOMPARE(color.b, 0); QCOMPARE(color.a, 255); QCOMPARE(item.is_key, 0); mlt_property_close(item.property); mlt_animation_close(a); } void ColorAnimationHexString() { double fps = 25.0; mlt_animation a = mlt_animation_new(); struct mlt_animation_item_s item; mlt_animation_parse(a, "50=#ffff00ff; 60=#ffaa00ff; 100=#ff00ff00", 100, fps, locale); mlt_animation_remove(a, 60); char *a_serialized = mlt_animation_serialize(a); QCOMPARE(a_serialized, "50=#ffff00ff;100=#ff00ff00"); if (a_serialized) free(a_serialized); item.property = mlt_property_init(); mlt_animation_get_item(a, &item, 10); mlt_color color = mlt_property_get_color(item.property, fps, locale); QCOMPARE(color.r, 255); QCOMPARE(color.g, 0); QCOMPARE(color.b, 255); QCOMPARE(color.a, 255); QCOMPARE(item.is_key, 0); mlt_animation_get_item(a, &item, 50); color = mlt_property_get_color(item.property, fps, locale); QCOMPARE(color.r, 255); QCOMPARE(color.g, 0); QCOMPARE(color.b, 255); QCOMPARE(color.a, 255); QCOMPARE(item.is_key, 1); mlt_animation_get_item(a, &item, 75); color = mlt_property_get_color(item.property, fps, locale); QCOMPARE(color.r, 127); QCOMPARE(color.g, 127); QCOMPARE(color.b, 127); QCOMPARE(color.a, 255); QCOMPARE(item.is_key, 0); mlt_animation_get_item(a, &item, 100); color = mlt_property_get_color(item.property, fps, locale); QCOMPARE(color.r, 0); QCOMPARE(color.g, 255); QCOMPARE(color.b, 0); QCOMPARE(color.a, 255); QCOMPARE(item.is_key, 1); mlt_animation_get_item(a, &item, 110); color = mlt_property_get_color(item.property, fps, locale); QCOMPARE(color.r, 0); QCOMPARE(color.g, 255); QCOMPARE(color.b, 0); QCOMPARE(color.a, 255); QCOMPARE(item.is_key, 0); mlt_property_close(item.property); mlt_animation_close(a); } void SetIntAndGetAnim() { Properties p; p.set_lcnumeric("POSIX"); p.set("key", 123); QCOMPARE(p.anim_get_int("key", 10, 50), 123); p.set("key", "123"); QCOMPARE(p.anim_get_int("key", 10, 50), 123); } void SetDoubleAndGetAnim() { Properties p; p.set_lcnumeric("POSIX"); p.set("key", 123.0); QCOMPARE(p.anim_get_double("key", 10, 50), 123.0); p.set("key", "123"); QCOMPARE(p.anim_get_double("key", 10, 50), 123.0); } void AnimNegativeTimevalue() { Properties p; Profile profile("dv_pal"); p.set("_profile", profile.get_profile(), 0); p.set_lcnumeric("POSIX"); p.set("key", "0=100; -1=200"); QCOMPARE(p.anim_get_int("key", 75, 100), 175); p.set("key", "0=100; -1:=200"); QCOMPARE(p.anim_get_int("key", 75, 125), 175); } void NestedProperties() { Properties parent; Properties child1; child1.set("c1A", "A"); child1.set("c1B", "B"); parent.set("c1", child1); QCOMPARE(child1.ref_count(), 2); Properties child2; child2.set("c2C", "C"); child2.set("c2D", "D"); parent.set("c2", child2); QCOMPARE(child2.ref_count(), 2); QCOMPARE(parent.count(), 2); Properties *pChild1 = parent.get_props("c1"); QCOMPARE(pChild1->get("c1B"), "B"); delete pChild1; Properties *pChild2 = parent.get_props_at(1); QCOMPARE(pChild1->get("c2D"), "D"); delete pChild2; } void PropertyClears() { Properties p; p.set("key", 1); QCOMPARE(p.get_int("key"), 1); QCOMPARE(p.get("key"), "1"); p.clear("key"); QCOMPARE(p.get("key"), (char *) 0); QCOMPARE(p.get_data("key"), (void *) 0); QCOMPARE(p.get_animation("key"), mlt_animation(0)); QCOMPARE(p.get_int("key"), 0); } void PropertyExists() { Properties p; // Never set should return false QCOMPARE(p.property_exists("key"), false); // Set should return true p.set("key", 1); QCOMPARE(p.property_exists("key"), true); // Cleared should return false p.clear("key"); QCOMPARE(p.property_exists("key"), false); } void AnimationExists() { Properties p; // Never set should return false QCOMPARE(p.property_exists("key"), false); // Get an animation but don't set anything - should return false Mlt::Animation animation = p.get_animation("key"); QCOMPARE(p.property_exists("key"), false); // Set animation should return true p.anim_set("key", 1, 0); QCOMPARE(p.property_exists("key"), true); // Cleared should return false p.clear("key"); QCOMPARE(p.property_exists("key"), false); } void SetString() { Properties p; p.set_string("foo", "123.4"); QCOMPARE(p.get("foo"), "123.4"); QCOMPARE(p.get_int("foo"), 123); QCOMPARE(p.get_double("foo"), 123.4); } }; QTEST_APPLESS_MAIN(TestProperties) #include "test_properties.moc" mlt-7.22.0/src/tests/test_properties/test_properties.pro000664 000000 000000 00000000122 14531534050 023512 0ustar00rootroot000000 000000 include (../common.pri) TARGET = test_properties SOURCES = test_properties.cpp mlt-7.22.0/src/tests/test_repository/000775 000000 000000 00000000000 14531534050 017565 5ustar00rootroot000000 000000 mlt-7.22.0/src/tests/test_repository/test_repository.cpp000664 000000 000000 00000003123 14531534050 023546 0ustar00rootroot000000 000000 /* * Copyright (C) 2013 Dan Dennedy * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with consumer library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include using namespace Mlt; class TestRepository : public QObject { Q_OBJECT public: TestRepository() {} private Q_SLOTS: void ThereAreProducers() { Repository *r = Factory::init(); Properties *producers = r->producers(); QVERIFY(producers->is_valid()); if (producers->is_valid()) QVERIFY(producers->count() > 0); delete producers; } void ThereAreConsumers() { Repository *r = Factory::init(); Properties *consumers = r->consumers(); QVERIFY(consumers->is_valid()); if (consumers->is_valid()) QVERIFY(consumers->count() > 0); delete consumers; } }; QTEST_APPLESS_MAIN(TestRepository) #include "test_repository.moc" mlt-7.22.0/src/tests/test_repository/test_repository.pro000664 000000 000000 00000000120 14531534050 023556 0ustar00rootroot000000 000000 include(../common.pri) TARGET = test_repository SOURCES += test_repository.cpp mlt-7.22.0/src/tests/test_service/000775 000000 000000 00000000000 14531534050 017006 5ustar00rootroot000000 000000 mlt-7.22.0/src/tests/test_service/test_service.cpp000664 000000 000000 00000005557 14531534050 022225 0ustar00rootroot000000 000000 /* * Copyright (C) 2019 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with consumer library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include using namespace Mlt; class TestService : public QObject { Q_OBJECT public: TestService() { repo = Factory::init(); } ~TestService() { Factory::close(); } private Q_SLOTS: void CanIdentifyServicesFromFactory() { Profile profile; Producer producer(profile, "color"); QCOMPARE(mlt_service_identify(producer.get_service()), mlt_service_producer_type); Filter filter(profile, "resize"); QCOMPARE(mlt_service_identify(filter.get_service()), mlt_service_filter_type); Transition transition(profile, "mix"); QCOMPARE(mlt_service_identify(transition.get_service()), mlt_service_transition_type); Consumer consumer(profile, "null"); QCOMPARE(mlt_service_identify(consumer.get_service()), mlt_service_consumer_type); } void CanIdentifyServicesFromAPI() { mlt_profile profile = mlt_profile_init(NULL); mlt_playlist playlist = mlt_playlist_new(profile); QCOMPARE(mlt_service_identify(MLT_PLAYLIST_SERVICE(playlist)), mlt_service_playlist_type); mlt_tractor tractor = mlt_tractor_new(); QCOMPARE(mlt_service_identify(MLT_TRACTOR_SERVICE(tractor)), mlt_service_tractor_type); QCOMPARE(mlt_service_identify(MLT_MULTITRACK_SERVICE(mlt_tractor_multitrack(tractor))), mlt_service_multitrack_type); mlt_producer producer = mlt_producer_new(profile); QCOMPARE(mlt_service_identify(MLT_PRODUCER_SERVICE(producer)), mlt_service_producer_type); mlt_filter filter = mlt_filter_new(); QCOMPARE(mlt_service_identify(MLT_FILTER_SERVICE(filter)), mlt_service_filter_type); mlt_transition transition = mlt_transition_new(); QCOMPARE(mlt_service_identify(MLT_TRANSITION_SERVICE(transition)), mlt_service_transition_type); mlt_consumer consumer = mlt_consumer_new(profile); QCOMPARE(mlt_service_identify(MLT_CONSUMER_SERVICE(consumer)), mlt_service_consumer_type); } private: Repository *repo; }; QTEST_APPLESS_MAIN(TestService) #include "test_service.moc" mlt-7.22.0/src/tests/test_service/test_service.pro000664 000000 000000 00000000120 14531534050 022220 0ustar00rootroot000000 000000 include(../common.pri) TARGET = test_service SOURCES += \ test_service.cpp mlt-7.22.0/src/tests/test_tractor/000775 000000 000000 00000000000 14531534050 017024 5ustar00rootroot000000 000000 mlt-7.22.0/src/tests/test_tractor/test_tractor.cpp000664 000000 000000 00000027347 14531534050 022262 0ustar00rootroot000000 000000 /* * Copyright (C) 2015 Dan Dennedy * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with consumer library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include using namespace Mlt; class TestTractor : public QObject { Q_OBJECT Profile profile; public: TestTractor() : profile("dv_pal") { Factory::init(); } private Q_SLOTS: void CreateSingleTrack() { Tractor t(profile); QVERIFY(t.is_valid()); Producer p(profile, "noise"); QVERIFY(p.is_valid()); QCOMPARE(t.count(), 0); t.set_track(p, t.count()); QCOMPARE(t.count(), 1); } void FailSameProducerNewTrack() { Tractor t(profile); QVERIFY(t.is_valid()); Producer p(profile, "noise"); QVERIFY(p.is_valid()); QCOMPARE(t.count(), 0); t.set_track(p, t.count()); QCOMPARE(t.count(), 1); int result = t.set_track(p, t.count()); QCOMPARE(result, 3); QCOMPARE(t.count(), 1); } void CreateMultipleTracks() { Tractor t(profile); QVERIFY(t.is_valid()); Producer p1(profile, "noise"); QVERIFY(p1.is_valid()); QCOMPARE(t.count(), 0); t.set_track(p1, t.count()); QCOMPARE(t.count(), 1); Producer p2(profile, "noise"); QVERIFY(p2.is_valid()); t.set_track(p2, t.count()); QCOMPARE(t.count(), 2); } void PlantTransitionWorks() { Tractor t(profile); QVERIFY(t.is_valid()); Producer p1(profile, "noise"); QVERIFY(p1.is_valid()); QCOMPARE(t.count(), 0); t.set_track(p1, t.count()); QCOMPARE(t.count(), 1); Producer p2(profile, "noise"); QVERIFY(p2.is_valid()); t.set_track(p2, t.count()); QCOMPARE(t.count(), 2); Transition trans(profile, "mix"); QVERIFY(trans.is_valid()); t.plant_transition(trans, 0, 1); QCOMPARE(trans.get_int("a_track"), 0); QCOMPARE(trans.get_int("b_track"), 1); } void InsertTrackBelowAdjustsTransition() { Tractor t(profile); QVERIFY(t.is_valid()); Producer p1(profile, "noise"); QVERIFY(p1.is_valid()); QCOMPARE(t.count(), 0); t.set_track(p1, t.count()); QCOMPARE(t.count(), 1); Producer p2(profile, "noise"); QVERIFY(p2.is_valid()); t.set_track(p2, t.count()); QCOMPARE(t.count(), 2); Transition trans(profile, "mix"); QVERIFY(trans.is_valid()); t.plant_transition(trans, 0, 1); QCOMPARE(trans.get_int("a_track"), 0); QCOMPARE(trans.get_int("b_track"), 1); Producer p3(profile, "noise"); QVERIFY(p3.is_valid()); t.insert_track(p3, 0); QCOMPARE(t.count(), 3); QCOMPARE(trans.get_int("a_track"), 1); QCOMPARE(trans.get_int("b_track"), 2); } void InsertTrackMiddleAdjustsTransition() { Tractor t(profile); QVERIFY(t.is_valid()); Producer p1(profile, "noise"); QVERIFY(p1.is_valid()); QCOMPARE(t.count(), 0); t.set_track(p1, t.count()); QCOMPARE(t.count(), 1); Producer p2(profile, "noise"); QVERIFY(p2.is_valid()); t.set_track(p2, t.count()); QCOMPARE(t.count(), 2); Transition trans(profile, "mix"); QVERIFY(trans.is_valid()); t.plant_transition(trans, 0, 1); QCOMPARE(trans.get_int("a_track"), 0); QCOMPARE(trans.get_int("b_track"), 1); Producer p3(profile, "noise"); QVERIFY(p3.is_valid()); t.insert_track(p3, 1); QCOMPARE(t.count(), 3); QCOMPARE(trans.get_int("a_track"), 0); QCOMPARE(trans.get_int("b_track"), 2); } void InsertTrackAboveDoesNotAffectTransition() { Tractor t(profile); QVERIFY(t.is_valid()); Producer p1(profile, "noise"); QVERIFY(p1.is_valid()); QCOMPARE(t.count(), 0); t.set_track(p1, t.count()); QCOMPARE(t.count(), 1); Producer p2(profile, "noise"); QVERIFY(p2.is_valid()); t.set_track(p2, t.count()); QCOMPARE(t.count(), 2); Transition trans(profile, "mix"); QVERIFY(trans.is_valid()); t.plant_transition(trans, 0, 1); QCOMPARE(trans.get_int("a_track"), 0); QCOMPARE(trans.get_int("b_track"), 1); Producer p3(profile, "noise"); QVERIFY(p3.is_valid()); t.insert_track(p3, 2); QCOMPARE(t.count(), 3); QCOMPARE(trans.get_int("a_track"), 0); QCOMPARE(trans.get_int("b_track"), 1); } void RemoveTrackBelowAdjustsTransition() { Tractor t(profile); QVERIFY(t.is_valid()); Producer p1(profile, "noise"); QVERIFY(p1.is_valid()); QCOMPARE(t.count(), 0); t.set_track(p1, t.count()); QCOMPARE(t.count(), 1); Producer p2(profile, "noise"); QVERIFY(p2.is_valid()); t.set_track(p2, t.count()); QCOMPARE(t.count(), 2); Producer p3(profile, "noise"); QVERIFY(p3.is_valid()); t.set_track(p3, t.count()); Transition trans(profile, "mix"); QVERIFY(trans.is_valid()); t.plant_transition(trans, 0, 1); QCOMPARE(trans.get_int("a_track"), 0); QCOMPARE(trans.get_int("b_track"), 1); t.remove_track(0); QCOMPARE(t.count(), 2); QCOMPARE(trans.get_int("a_track"), 0); QCOMPARE(trans.get_int("b_track"), 0); // This transition is a candidate for removal. } void RemoveMiddleTrackAdjustsTransition() { Tractor t(profile); QVERIFY(t.is_valid()); Producer p1(profile, "noise"); QVERIFY(p1.is_valid()); QCOMPARE(t.count(), 0); t.set_track(p1, t.count()); QCOMPARE(t.count(), 1); Producer p2(profile, "noise"); QVERIFY(p2.is_valid()); t.set_track(p2, t.count()); QCOMPARE(t.count(), 2); Producer p3(profile, "noise"); QVERIFY(p3.is_valid()); t.set_track(p3, t.count()); Transition trans(profile, "mix"); QVERIFY(trans.is_valid()); t.plant_transition(trans, 0, 2); QCOMPARE(trans.get_int("a_track"), 0); QCOMPARE(trans.get_int("b_track"), 2); t.remove_track(1); QCOMPARE(t.count(), 2); QCOMPARE(trans.get_int("a_track"), 0); QCOMPARE(trans.get_int("b_track"), 1); } void RemoveTrackAboveAdjustsTransition() { Tractor t(profile); QVERIFY(t.is_valid()); Producer p1(profile, "noise"); QVERIFY(p1.is_valid()); QCOMPARE(t.count(), 0); t.set_track(p1, t.count()); QCOMPARE(t.count(), 1); Producer p2(profile, "noise"); QVERIFY(p2.is_valid()); t.set_track(p2, t.count()); QCOMPARE(t.count(), 2); Producer p3(profile, "noise"); QVERIFY(p3.is_valid()); t.set_track(p3, t.count()); Transition trans(profile, "mix"); QVERIFY(trans.is_valid()); t.plant_transition(trans, 1, 2); QCOMPARE(trans.get_int("a_track"), 1); QCOMPARE(trans.get_int("b_track"), 2); t.remove_track(2); QCOMPARE(t.count(), 2); QCOMPARE(trans.get_int("a_track"), 1); QCOMPARE(trans.get_int("b_track"), 1); // This transition is a candidate for removal. } void PlantFilterWorks() { Tractor t(profile); QVERIFY(t.is_valid()); Producer p1(profile, "noise"); QVERIFY(p1.is_valid()); QCOMPARE(t.count(), 0); t.set_track(p1, t.count()); QCOMPARE(t.count(), 1); Filter filter(profile, "crop"); QVERIFY(filter.is_valid()); t.plant_filter(filter, 0); QCOMPARE(filter.get_track(), 0); } void InsertTrackBelowAdjustsFilter() { Tractor t(profile); QVERIFY(t.is_valid()); Producer p1(profile, "noise"); QVERIFY(p1.is_valid()); QCOMPARE(t.count(), 0); t.set_track(p1, t.count()); QCOMPARE(t.count(), 1); Filter filter(profile, "crop"); QVERIFY(filter.is_valid()); t.plant_filter(filter, 0); QCOMPARE(filter.get_track(), 0); Producer p2(profile, "noise"); QVERIFY(p2.is_valid()); t.insert_track(p2, 0); QCOMPARE(t.count(), 2); QCOMPARE(filter.get_track(), 1); } void InsertTrackAboveDoesNotAffectFilter() { Tractor t(profile); QVERIFY(t.is_valid()); Producer p1(profile, "noise"); QVERIFY(p1.is_valid()); QCOMPARE(t.count(), 0); t.set_track(p1, t.count()); QCOMPARE(t.count(), 1); Filter filter(profile, "crop"); QVERIFY(filter.is_valid()); t.plant_filter(filter, 0); QCOMPARE(filter.get_track(), 0); Producer p2(profile, "noise"); QVERIFY(p2.is_valid()); t.insert_track(p2, 1); QCOMPARE(t.count(), 2); QCOMPARE(filter.get_track(), 0); } void RemoveTrackBelowAdjustsFilter() { Tractor t(profile); QVERIFY(t.is_valid()); Producer p1(profile, "noise"); QVERIFY(p1.is_valid()); QCOMPARE(t.count(), 0); t.set_track(p1, t.count()); QCOMPARE(t.count(), 1); Producer p2(profile, "noise"); QVERIFY(p2.is_valid()); t.set_track(p2, t.count()); QCOMPARE(t.count(), 2); Filter filter(profile, "crop"); QVERIFY(filter.is_valid()); t.plant_filter(filter, 1); QCOMPARE(filter.get_track(), 1); t.remove_track(0); QCOMPARE(t.count(), 1); QCOMPARE(filter.get_track(), 0); } void RemoveFilteredTrackAdjustsFilter() { Tractor t(profile); QVERIFY(t.is_valid()); Producer p1(profile, "noise"); QVERIFY(p1.is_valid()); QCOMPARE(t.count(), 0); t.set_track(p1, t.count()); QCOMPARE(t.count(), 1); Producer p2(profile, "noise"); QVERIFY(p2.is_valid()); t.set_track(p2, t.count()); QCOMPARE(t.count(), 2); Filter filter(profile, "crop"); QVERIFY(filter.is_valid()); t.plant_filter(filter, 1); QCOMPARE(filter.get_track(), 1); t.remove_track(1); QCOMPARE(t.count(), 1); QCOMPARE(filter.get_track(), 0); // This filter is a candidate for removal. } void RemoveTrackAboveDoesNotAffectFilter() { Tractor t(profile); QVERIFY(t.is_valid()); Producer p1(profile, "noise"); QVERIFY(p1.is_valid()); QCOMPARE(t.count(), 0); t.set_track(p1, t.count()); QCOMPARE(t.count(), 1); Producer p2(profile, "noise"); QVERIFY(p2.is_valid()); t.set_track(p2, t.count()); QCOMPARE(t.count(), 2); Filter filter(profile, "crop"); QVERIFY(filter.is_valid()); t.plant_filter(filter, 0); QCOMPARE(filter.get_track(), 0); t.remove_track(1); QCOMPARE(t.count(), 1); QCOMPARE(filter.get_track(), 0); } }; QTEST_APPLESS_MAIN(TestTractor) #include "test_tractor.moc" mlt-7.22.0/src/tests/test_tractor/test_tractor.pro000664 000000 000000 00000000111 14531534050 022254 0ustar00rootroot000000 000000 include(../common.pri) TARGET = test_tractor SOURCES += test_tractor.cpp mlt-7.22.0/src/tests/test_xml/000775 000000 000000 00000000000 14531534050 016146 5ustar00rootroot000000 000000 mlt-7.22.0/src/tests/test_xml/test_xml.cpp000664 000000 000000 00000004076 14531534050 020520 0ustar00rootroot000000 000000 /* * Copyright (C) 2021 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with consumer library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include using namespace Mlt; #include #include #include using namespace Mlt; class TestXml : public QObject { Q_OBJECT public: TestXml() { Factory::init(); } private Q_SLOTS: void NestedPropertiesRoundTrip() { // Create a producer Profile profile; Producer producer(profile, "noise"); // Nest properties two deep Properties child1; producer.set("child1", child1); Properties child2; child2.set("test_param", "C2"); child1.set("child2", child2); // Convert to XML Consumer c(profile, "xml", "string"); c.connect(producer); c.start(); Producer retProducer(profile, "xml-string", c.get("string")); QCOMPARE(retProducer.get("mlt_service"), "noise"); Properties *pchild1 = retProducer.get_props("child1"); QVERIFY(pchild1 != nullptr); QVERIFY(pchild1->is_valid()); Properties *pchild2 = pchild1->get_props("child2"); QVERIFY(pchild2 != nullptr); QVERIFY(pchild2->is_valid()); QCOMPARE(pchild2->get("test_param"), "C2"); delete pchild1; delete pchild2; } }; QTEST_APPLESS_MAIN(TestXml) #include "test_xml.moc" mlt-7.22.0/src/tests/test_xml/test_xml.pro000664 000000 000000 00000000104 14531534050 020522 0ustar00rootroot000000 000000 include (../common.pri) TARGET = test_xml SOURCES = test_xml.cpp mlt-7.22.0/src/tests/tests.pro000664 000000 000000 00000000414 14531534050 016172 0ustar00rootroot000000 000000 TEMPLATE = subdirs SUBDIRS = test_audio \ test_filter \ test_events \ test_frame \ test_image \ test_playlist \ test_producer \ test_properties \ test_repository \ test_animation \ test_tractor \ test_service \ test_xml mlt-7.22.0/src/win32/000775 000000 000000 00000000000 14531534050 014107 5ustar00rootroot000000 000000 mlt-7.22.0/src/win32/fnmatch.c000664 000000 000000 00000013430 14531534050 015674 0ustar00rootroot000000 000000 /* * Copyright (c) 1989, 1993, 1994 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * Guido van Rossum. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. 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. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University 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 REGENTS 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 REGENTS 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. * * From FreeBSD fnmatch.c 1.11 * $Id$ */ #if defined(LIBC_SCCS) && !defined(lint) static char sccsid[] = "@(#)fnmatch.c 8.2 (Berkeley) 4/16/94"; #endif /* LIBC_SCCS and not lint */ /* * Function fnmatch() as specified in POSIX 1003.2-1992, section B.6. * Compares a filename or pathname to a pattern. */ #include #include #include #include "fnmatch.h" #define EOS '\0' static const char *rangematch(const char *, char, int); int fnmatch(const char *pattern, const char *string, int flags) { const char *stringstart; char c, test; for (stringstart = string;;) switch (c = *pattern++) { case EOS: if ((flags & FNM_LEADING_DIR) && *string == '/') return (0); return (*string == EOS ? 0 : FNM_NOMATCH); case '?': if (*string == EOS) return (FNM_NOMATCH); if (*string == '/' && (flags & FNM_PATHNAME)) return (FNM_NOMATCH); if (*string == '.' && (flags & FNM_PERIOD) && (string == stringstart || ((flags & FNM_PATHNAME) && *(string - 1) == '/'))) return (FNM_NOMATCH); ++string; break; case '*': c = *pattern; /* Collapse multiple stars. */ while (c == '*') c = *++pattern; if (*string == '.' && (flags & FNM_PERIOD) && (string == stringstart || ((flags & FNM_PATHNAME) && *(string - 1) == '/'))) return (FNM_NOMATCH); /* Optimize for pattern with * at end or before /. */ if (c == EOS) if (flags & FNM_PATHNAME) return ((flags & FNM_LEADING_DIR) || strchr(string, '/') == NULL ? 0 : FNM_NOMATCH); else return (0); else if (c == '/' && flags & FNM_PATHNAME) { if ((string = strchr(string, '/')) == NULL) return (FNM_NOMATCH); break; } /* General case, use recursion. */ while ((test = *string) != EOS) { if (!fnmatch(pattern, string, flags & ~FNM_PERIOD)) return (0); if (test == '/' && flags & FNM_PATHNAME) break; ++string; } return (FNM_NOMATCH); case '[': if (*string == EOS) return (FNM_NOMATCH); if (*string == '/' && flags & FNM_PATHNAME) return (FNM_NOMATCH); if ((pattern = rangematch(pattern, *string, flags)) == NULL) return (FNM_NOMATCH); ++string; break; case '\\': if (!(flags & FNM_NOESCAPE)) { if ((c = *pattern++) == EOS) { c = '\\'; --pattern; } } /* FALLTHROUGH */ default: if (c == *string) ; else if ((flags & FNM_CASEFOLD) && (tolower((unsigned char)c) == tolower((unsigned char)*string))) ; else if ((flags & FNM_PREFIX_DIRS) && *string == EOS && ((c == '/' && string != stringstart) || (string == stringstart+1 && *stringstart == '/')) ) return (0); else return (FNM_NOMATCH); string++; break; } /* NOTREACHED */ } static const char * rangematch(const char *pattern, char test, int flags) { int negate, ok; char c, c2; /* * A bracket expression starting with an unquoted circumflex * character produces unspecified results (IEEE 1003.2-1992, * 3.13.2). This implementation treats it like '!', for * consistency with the regular expression syntax. * J.T. Conklin (conklin@ngai.kaleida.com) */ if ( (negate = (*pattern == '!' || *pattern == '^')) ) ++pattern; if (flags & FNM_CASEFOLD) test = tolower((unsigned char)test); for (ok = 0; (c = *pattern++) != ']';) { if (c == '\\' && !(flags & FNM_NOESCAPE)) c = *pattern++; if (c == EOS) return (NULL); if (flags & FNM_CASEFOLD) c = tolower((unsigned char)c); if (*pattern == '-' && (c2 = *(pattern+1)) != EOS && c2 != ']') { pattern += 2; if (c2 == '\\' && !(flags & FNM_NOESCAPE)) c2 = *pattern++; if (c2 == EOS) return (NULL); if (flags & FNM_CASEFOLD) c2 = tolower((unsigned char)c2); if ((unsigned char)c <= (unsigned char)test && (unsigned char)test <= (unsigned char)c2) ok = 1; } else if (c == test) ok = 1; } return (ok == negate ? NULL : pattern); } mlt-7.22.0/src/win32/fnmatch.h000664 000000 000000 00000004657 14531534050 015714 0ustar00rootroot000000 000000 /*- * Copyright (c) 1992, 1993 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. 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. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University 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 REGENTS 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 REGENTS 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. * * @(#)fnmatch.h 8.1 (Berkeley) 6/2/93 * * From FreeBSD fnmatch.h 1.7 * $Id$ */ #ifndef _FNMATCH_H_ #define _FNMATCH_H_ #define FNM_NOMATCH 1 /* Match failed. */ #define FNM_NOESCAPE 0x01 /* Disable backslash escaping. */ #define FNM_PATHNAME 0x02 /* Slash must be matched by slash. */ #define FNM_PERIOD 0x04 /* Period must be matched by period. */ #define FNM_LEADING_DIR 0x08 /* Ignore / after Imatch. */ #define FNM_CASEFOLD 0x10 /* Case insensitive search. */ #define FNM_PREFIX_DIRS 0x20 /* Directory prefixes of pattern match too. */ int fnmatch(const char *pattern, const char *string, int flags); #endif /* !_FNMATCH_H_ */ mlt-7.22.0/src/win32/strptime.c000664 000000 000000 00000032642 14531534050 016131 0ustar00rootroot000000 000000 /* $NetBSD: strptime.c,v 1.36 2012/03/13 21:13:48 christos Exp $ */ /*- * Copyright (c) 1997, 1998, 2005, 2008 The NetBSD Foundation, Inc. * All rights reserved. * * This code was contributed to The NetBSD Foundation by Klaus Klein. * Heavily optimised by David Laight * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. 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. * * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. 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 FOUNDATION 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. */ /* #include #if defined(LIBC_SCCS) && !defined(lint) __RCSID("$NetBSD: strptime.c,v 1.36 2012/03/13 21:13:48 christos Exp $"); #endif #include "namespace.h" #include */ #include #include #include #include #include /* #include #include "private.h" #ifdef __weak_alias __weak_alias(strptime,_strptime) #endif */ typedef unsigned char u_char; typedef unsigned int uint; typedef unsigned __int64 uint64_t; #define _ctloc(x) (_CurrentTimeLocale->x) /* * We do not implement alternate representations. However, we always * check whether a given modifier is allowed for a certain conversion. */ #define ALT_E 0x01 #define ALT_O 0x02 #define LEGAL_ALT(x) { if (alt_format & ~(x)) return NULL; } static int TM_YEAR_BASE = 1900; static char gmt[] = { "GMT" }; static char utc[] = { "UTC" }; /* RFC-822/RFC-2822 */ static const char * const nast[5] = { "EST", "CST", "MST", "PST", "\0\0\0" }; static const char * const nadt[5] = { "EDT", "CDT", "MDT", "PDT", "\0\0\0" }; static const char * const am_pm[2] = { "am", "pm" }; static const char * const day[7] = { "sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday" }; static const char * const abday[7] = { "sun", "mon", "tue", "wed", "thu", "fri", "sat" }; static const char * const mon[12] = { "january", "february", "march", "april", "may", "june", "july", "august", "september", "october", "november", "december" }; static const char * const abmon[12] = { "jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec" }; static const u_char *conv_num(const unsigned char *, int *, uint, uint); static const u_char *find_string(const u_char *, int *, const char * const *, const char * const *, int); char * strptime(const char *buf, const char *fmt, struct tm *tm) { unsigned char c; const unsigned char *bp, *ep; int alt_format, i, split_year = 0, neg = 0, offs; const char *new_fmt; bp = (const u_char *)buf; while (bp != NULL && (c = *fmt++) != '\0') { /* Clear `alternate' modifier prior to new conversion. */ alt_format = 0; i = 0; /* Eat up white-space. */ if (isspace(c)) { while (isspace(*bp)) bp++; continue; } if (c != '%') goto literal; again: switch (c = *fmt++) { case '%': /* "%%" is converted to "%". */ literal: if (c != *bp++) return NULL; LEGAL_ALT(0); continue; /* * "Alternative" modifiers. Just set the appropriate flag * and start over again. */ case 'E': /* "%E?" alternative conversion modifier. */ LEGAL_ALT(0); alt_format |= ALT_E; goto again; case 'O': /* "%O?" alternative conversion modifier. */ LEGAL_ALT(0); alt_format |= ALT_O; goto again; /* * "Complex" conversion rules, implemented through recursion. */ /* we do not need 'c' case 'c': Date and time, using the locale's format. new_fmt = _ctloc(d_t_fmt); goto recurse; */ case 'D': /* The date as "%m/%d/%y". */ new_fmt = "%m/%d/%y"; LEGAL_ALT(0); goto recurse; case 'F': /* The date as "%Y-%m-%d". */ new_fmt = "%Y-%m-%d"; LEGAL_ALT(0); goto recurse; case 'R': /* The time as "%H:%M". */ new_fmt = "%H:%M"; LEGAL_ALT(0); goto recurse; case 'r': /* The time in 12-hour clock representation. */ new_fmt = "%I:%M:S %p";//_ctloc(t_fmt_ampm); LEGAL_ALT(0); goto recurse; case 'T': /* The time as "%H:%M:%S". */ new_fmt = "%H:%M:%S"; LEGAL_ALT(0); goto recurse; /* we don't use 'X' case 'X': The time, using the locale's format. new_fmt =_ctloc(t_fmt); goto recurse; */ /* we do not need 'x' case 'x': The date, using the locale's format. new_fmt =_ctloc(d_fmt);*/ recurse: bp = (const u_char *)strptime((const char *)bp, new_fmt, tm); LEGAL_ALT(ALT_E); continue; /* * "Elementary" conversion rules. */ case 'A': /* The day of week, using the locale's form. */ case 'a': bp = find_string(bp, &tm->tm_wday, day, abday, 7); LEGAL_ALT(0); continue; case 'B': /* The month, using the locale's form. */ case 'b': case 'h': bp = find_string(bp, &tm->tm_mon, mon, abmon, 12); LEGAL_ALT(0); continue; case 'C': /* The century number. */ i = 20; bp = conv_num(bp, &i, 0, 99); i = i * 100 - TM_YEAR_BASE; if (split_year) i += tm->tm_year % 100; split_year = 1; tm->tm_year = i; LEGAL_ALT(ALT_E); continue; case 'd': /* The day of month. */ case 'e': bp = conv_num(bp, &tm->tm_mday, 1, 31); LEGAL_ALT(ALT_O); continue; case 'k': /* The hour (24-hour clock representation). */ LEGAL_ALT(0); /* FALLTHROUGH */ case 'H': bp = conv_num(bp, &tm->tm_hour, 0, 23); LEGAL_ALT(ALT_O); continue; case 'l': /* The hour (12-hour clock representation). */ LEGAL_ALT(0); /* FALLTHROUGH */ case 'I': bp = conv_num(bp, &tm->tm_hour, 1, 12); if (tm->tm_hour == 12) tm->tm_hour = 0; LEGAL_ALT(ALT_O); continue; case 'j': /* The day of year. */ i = 1; bp = conv_num(bp, &i, 1, 366); tm->tm_yday = i - 1; LEGAL_ALT(0); continue; case 'M': /* The minute. */ bp = conv_num(bp, &tm->tm_min, 0, 59); LEGAL_ALT(ALT_O); continue; case 'm': /* The month. */ i = 1; bp = conv_num(bp, &i, 1, 12); tm->tm_mon = i - 1; LEGAL_ALT(ALT_O); continue; case 'p': /* The locale's equivalent of AM/PM. */ bp = find_string(bp, &i, am_pm, NULL, 2); if (tm->tm_hour > 11) return NULL; tm->tm_hour += i * 12; LEGAL_ALT(0); continue; case 'S': /* The seconds. */ bp = conv_num(bp, &tm->tm_sec, 0, 61); LEGAL_ALT(ALT_O); continue; #ifndef TIME_MAX #define TIME_MAX INT64_MAX #endif case 's': /* seconds since the epoch */ { time_t sse = 0; uint64_t rulim = TIME_MAX; if (*bp < '0' || *bp > '9') { bp = NULL; continue; } do { sse *= 10; sse += *bp++ - '0'; rulim /= 10; } while ((sse * 10 <= TIME_MAX) && rulim && *bp >= '0' && *bp <= '9'); if (sse < 0 || (uint64_t)sse > TIME_MAX) { bp = NULL; continue; } tm = localtime(&sse); if (tm == NULL) bp = NULL; } continue; case 'U': /* The week of year, beginning on sunday. */ case 'W': /* The week of year, beginning on monday. */ /* * XXX This is bogus, as we can not assume any valid * information present in the tm structure at this * point to calculate a real value, so just check the * range for now. */ bp = conv_num(bp, &i, 0, 53); LEGAL_ALT(ALT_O); continue; case 'w': /* The day of week, beginning on sunday. */ bp = conv_num(bp, &tm->tm_wday, 0, 6); LEGAL_ALT(ALT_O); continue; case 'u': /* The day of week, monday = 1. */ bp = conv_num(bp, &i, 1, 7); tm->tm_wday = i % 7; LEGAL_ALT(ALT_O); continue; case 'g': /* The year corresponding to the ISO week * number but without the century. */ bp = conv_num(bp, &i, 0, 99); continue; case 'G': /* The year corresponding to the ISO week * number with century. */ do bp++; while (isdigit(*bp)); continue; case 'V': /* The ISO 8601:1988 week number as decimal */ bp = conv_num(bp, &i, 0, 53); continue; case 'Y': /* The year. */ i = TM_YEAR_BASE; /* just for data sanity... */ bp = conv_num(bp, &i, 0, 9999); tm->tm_year = i - TM_YEAR_BASE; LEGAL_ALT(ALT_E); continue; case 'y': /* The year within 100 years of the epoch. */ /* LEGAL_ALT(ALT_E | ALT_O); */ bp = conv_num(bp, &i, 0, 99); if (split_year) /* preserve century */ i += (tm->tm_year / 100) * 100; else { split_year = 1; if (i <= 68) i = i + 2000 - TM_YEAR_BASE; else i = i + 1900 - TM_YEAR_BASE; } tm->tm_year = i; continue; case 'Z': _tzset(); if (strncasecmp((const char *)bp, gmt, 3) == 0 || strncasecmp((const char *)bp, utc, 3) == 0) { tm->tm_isdst = 0; #ifdef TM_GMTOFF tm->TM_GMTOFF = 0; #endif #ifdef TM_ZONE tm->TM_ZONE = gmt; #endif bp += 3; } else { ep = find_string(bp, &i, (const char * const *)tzname, NULL, 2); if (ep != NULL) { tm->tm_isdst = i; #ifdef TM_GMTOFF tm->TM_GMTOFF = -(timezone); #endif #ifdef TM_ZONE tm->TM_ZONE = tzname[i]; #endif } bp = ep; } continue; case 'z': /* * We recognize all ISO 8601 formats: * Z = Zulu time/UTC * [+-]hhmm * [+-]hh:mm * [+-]hh * We recognize all RFC-822/RFC-2822 formats: * UT|GMT * North American : UTC offsets * E[DS]T = Eastern : -4 | -5 * C[DS]T = Central : -5 | -6 * M[DS]T = Mountain: -6 | -7 * P[DS]T = Pacific : -7 | -8 * Military * [A-IL-M] = -1 ... -9 (J not used) * [N-Y] = +1 ... +12 */ while (isspace(*bp)) bp++; switch (*bp++) { case 'G': if (*bp++ != 'M') return NULL; /*FALLTHROUGH*/ case 'U': if (*bp++ != 'T') return NULL; /*FALLTHROUGH*/ case 'Z': tm->tm_isdst = 0; #ifdef TM_GMTOFF tm->TM_GMTOFF = 0; #endif #ifdef TM_ZONE tm->TM_ZONE = utc; #endif continue; case '+': neg = 0; break; case '-': neg = 1; break; default: --bp; ep = find_string(bp, &i, nast, NULL, 4); if (ep != NULL) { #ifdef TM_GMTOFF tm->TM_GMTOFF = -5 - i; #endif #ifdef TM_ZONE tm->TM_ZONE = __UNCONST(nast[i]); #endif bp = ep; continue; } ep = find_string(bp, &i, nadt, NULL, 4); if (ep != NULL) { tm->tm_isdst = 1; #ifdef TM_GMTOFF tm->TM_GMTOFF = -4 - i; #endif #ifdef TM_ZONE tm->TM_ZONE = __UNCONST(nadt[i]); #endif bp = ep; continue; } if ((*bp >= 'A' && *bp <= 'I') || (*bp >= 'L' && *bp <= 'Y')) { #ifdef TM_GMTOFF /* Argh! No 'J'! */ if (*bp >= 'A' && *bp <= 'I') tm->TM_GMTOFF = ('A' - 1) - (int)*bp; else if (*bp >= 'L' && *bp <= 'M') tm->TM_GMTOFF = 'A' - (int)*bp; else if (*bp >= 'N' && *bp <= 'Y') tm->TM_GMTOFF = (int)*bp - 'M'; #endif #ifdef TM_ZONE tm->TM_ZONE = NULL; /* XXX */ #endif bp++; continue; } return NULL; } offs = 0; for (i = 0; i < 4; ) { if (isdigit(*bp)) { offs = offs * 10 + (*bp++ - '0'); i++; continue; } if (i == 2 && *bp == ':') { bp++; continue; } break; } switch (i) { case 2: offs *= 100; break; case 4: i = offs % 100; if (i >= 60) return NULL; /* Convert minutes into decimal */ offs = (offs / 100) * 100 + (i * 50) / 30; break; default: return NULL; } if (neg) offs = -offs; tm->tm_isdst = 0; /* XXX */ #ifdef TM_GMTOFF tm->TM_GMTOFF = offs; #endif #ifdef TM_ZONE tm->TM_ZONE = NULL; /* XXX */ #endif continue; /* * Miscellaneous conversions. */ case 'n': /* Any kind of white-space. */ case 't': while (isspace(*bp)) bp++; LEGAL_ALT(0); continue; default: /* Unknown/unsupported conversion. */ return NULL; } } return (char *)(bp); } static const u_char * conv_num(const unsigned char *buf, int *dest, uint llim, uint ulim) { uint result = 0; unsigned char ch; /* The limit also determines the number of valid digits. */ uint rulim = ulim; ch = *buf; if (ch < '0' || ch > '9') return NULL; do { result *= 10; result += ch - '0'; rulim /= 10; ch = *++buf; } while ((result * 10 <= ulim) && rulim && ch >= '0' && ch <= '9'); if (result < llim || result > ulim) return NULL; *dest = result; return buf; } static const u_char * find_string(const u_char *bp, int *tgt, const char * const *n1, const char * const *n2, int c) { int i; size_t len; /* check full name - then abbreviated ones */ for (; n1 != NULL; n1 = n2, n2 = NULL) { for (i = 0; i < c; i++, n1++) { len = strlen(*n1); if (strncasecmp(*n1, (const char *)bp, len) == 0) { *tgt = i; return bp + len; } } } /* Nothing matched */ return NULL; } mlt-7.22.0/src/win32/win32.c000664 000000 000000 00000016571 14531534050 015227 0ustar00rootroot000000 000000 /** * \file win32.c * \brief Miscellaneous utility functions for Windows. * * Copyright (C) 2003-2016 Meltytech, LLC * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include #include #include "../framework/mlt_properties.h" #include "../framework/mlt_log.h" int usleep(unsigned int useconds) { HANDLE timer; LARGE_INTEGER due; due.QuadPart = -(10 * (__int64)useconds); timer = CreateWaitableTimer(NULL, TRUE, NULL); SetWaitableTimer(timer, &due, 0, NULL, NULL, 0); WaitForSingleObject(timer, INFINITE); CloseHandle(timer); return 0; } int nanosleep( const struct timespec * rqtp, struct timespec * rmtp ) { if (rqtp->tv_nsec > 999999999) { /* The time interval specified 1,000,000 or more microseconds. */ errno = EINVAL; return -1; } return usleep( rqtp->tv_sec * 1000000 + rqtp->tv_nsec / 1000 ); } int setenv(const char *name, const char *value, int overwrite) { int result = 1; if (overwrite == 0 && getenv (name)) { result = 0; } else { result = SetEnvironmentVariable (name,value); } return result; } static int iconv_from_utf8( mlt_properties properties, const char *prop_name, const char *prop_name_out, const char* encoding ) { char *text = mlt_properties_get( properties, prop_name ); int result = 0; if ( text ) { iconv_t cd = iconv_open( encoding, "UTF-8" ); if ( cd != (iconv_t) -1 ) { size_t inbuf_n = strlen( text ); size_t outbuf_n = inbuf_n * 6; char *outbuf = mlt_pool_alloc( outbuf_n ); char *outbuf_p = outbuf; memset( outbuf, 0, outbuf_n ); if ( text != NULL && strcmp( text, "" ) && iconv( cd, &text, &inbuf_n, &outbuf_p, &outbuf_n ) != -1 ) mlt_properties_set( properties, prop_name_out, outbuf ); else mlt_properties_set( properties, prop_name_out, "" ); mlt_pool_release( outbuf ); result = iconv_close( cd ); } else { result = -1; } } return result; } static int iconv_to_utf8( mlt_properties properties, const char *prop_name, const char *prop_name_out, const char* encoding ) { char *text = mlt_properties_get( properties, prop_name ); int result = 0; if ( text ) { iconv_t cd = iconv_open( "UTF-8", encoding ); if ( cd != (iconv_t) -1 ) { size_t inbuf_n = strlen( text ); size_t outbuf_n = inbuf_n * 6; char *outbuf = mlt_pool_alloc( outbuf_n ); char *outbuf_p = outbuf; memset( outbuf, 0, outbuf_n ); if ( text != NULL && strcmp( text, "" ) && iconv( cd, &text, &inbuf_n, &outbuf_p, &outbuf_n ) != -1 ) mlt_properties_set( properties, prop_name_out, outbuf ); else mlt_properties_set( properties, prop_name_out, "" ); mlt_pool_release( outbuf ); result = iconv_close( cd ); } else { result = -1; } } return result; } int mlt_properties_from_utf8( mlt_properties properties, const char *prop_name, const char *prop_name_out ) { int result = -1; UINT codepage = GetACP(); if ( codepage > 0 ) { // numeric code page char codepage_str[10]; snprintf( codepage_str, sizeof(codepage_str), "CP%u", codepage ); codepage_str[sizeof(codepage_str) - 1] = '\0'; result = iconv_from_utf8( properties, prop_name, prop_name_out, codepage_str ); } if ( result < 0 ) { result = mlt_properties_set( properties, prop_name_out, mlt_properties_get( properties, prop_name ) ); mlt_log_warning( NULL, "iconv failed to convert \"%s\" from UTF-8 to code page %u: %s\n", prop_name, codepage, mlt_properties_get( properties, prop_name ) ); } return result; } int mlt_properties_to_utf8( mlt_properties properties, const char *prop_name, const char *prop_name_out ) { int result = -1; UINT codepage = GetACP(); if ( codepage > 0 ) { // numeric code page char codepage_str[10]; snprintf( codepage_str, sizeof(codepage_str), "CP%u", codepage ); codepage_str[sizeof(codepage_str) - 1] = '\0'; result = iconv_to_utf8( properties, prop_name, prop_name_out, codepage_str ); } if ( result < 0 ) { result = mlt_properties_set( properties, prop_name_out, mlt_properties_get( properties, prop_name ) ); mlt_log_warning( NULL, "iconv failed to convert \"%s\" from code page %u to UTF-8\n", prop_name, codepage ); } return result; } /* Adapted from g_win32_getlocale() - free() the result */ char* getlocale() { LCID lcid; LANGID langid; char *ev; int primary, sub; char iso639[10]; char iso3166[10]; const char *script = ""; char result[33]; /* Let the user override the system settings through environment * variables, as on POSIX systems. */ if (((ev = getenv ("LC_ALL")) != NULL && ev[0] != '\0') || ((ev = getenv ("LC_MESSAGES")) != NULL && ev[0] != '\0') || ((ev = getenv ("LANG")) != NULL && ev[0] != '\0')) return strdup (ev); lcid = GetThreadLocale (); if (!GetLocaleInfo (lcid, LOCALE_SISO639LANGNAME, iso639, sizeof (iso639)) || !GetLocaleInfo (lcid, LOCALE_SISO3166CTRYNAME, iso3166, sizeof (iso3166))) return strdup ("C"); /* Strip off the sorting rules, keep only the language part. */ langid = LANGIDFROMLCID (lcid); /* Split into language and territory part. */ primary = PRIMARYLANGID (langid); sub = SUBLANGID (langid); /* Handle special cases */ switch (primary) { case LANG_AZERI: switch (sub) { case SUBLANG_AZERI_LATIN: script = "@Latn"; break; case SUBLANG_AZERI_CYRILLIC: script = "@Cyrl"; break; } break; case LANG_SERBIAN: /* LANG_CROATIAN == LANG_SERBIAN */ switch (sub) { case SUBLANG_SERBIAN_LATIN: case 0x06: /* Serbian (Latin) - Bosnia and Herzegovina */ script = "@Latn"; break; } break; case LANG_UZBEK: switch (sub) { case SUBLANG_UZBEK_LATIN: script = "@Latn"; break; case SUBLANG_UZBEK_CYRILLIC: script = "@Cyrl"; break; } break; } snprintf (result, sizeof(result), "%s_%s%s", iso639, iso3166, script); result[sizeof(result) - 1] = '\0'; return strdup (result); } FILE* win32_fopen(const char *filename_utf8, const char *mode_utf8) { // Convert UTF-8 to wide chars. int n = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, filename_utf8, -1, NULL, 0); if (n > 0) { wchar_t *filename_w = (wchar_t *) calloc(n, sizeof(wchar_t)); if (filename_w) { int m = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, mode_utf8, -1, NULL, 0); if (m > 0) { wchar_t *mode_w = (wchar_t *) calloc(m, sizeof(wchar_t)); if (mode_w) { MultiByteToWideChar(CP_UTF8, 0, filename_utf8, -1, filename_w, n); MultiByteToWideChar(CP_UTF8, 0, mode_utf8, -1, mode_w, n); FILE *fh = _wfopen(filename_w, mode_w); free(filename_w); if (fh) return fh; } } } } // Try with regular old fopen. return fopen(filename_utf8, mode_utf8); } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/glaxnimate/import_state.hpp000664 001750 001750 00000051461 14477652011 032267 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include #include "math/bezier/bezier.hpp" #include "glaxnimate_format.hpp" #include "model/assets/assets.hpp" namespace glaxnimate::io::glaxnimate::detail { class ImportState { private: struct UnresolvedPath { struct Step { model::Object* object = nullptr; model::BaseProperty* prop = nullptr; }; struct Item { QString propname; int index = -1; model::Object* step(model::Object* prev) const { auto prop = prev->get_property(propname); if ( !prop || prop->traits().type != model::PropertyTraits::Object ) return nullptr; if ( prop->traits().flags & model::PropertyTraits::List ) { if ( index == -1 ) return nullptr; auto val_list = prop->value().toList(); if ( val_list.size() <= index ) return nullptr; return val_list[index].value(); } return prop->value().value(); } }; UnresolvedPath(model::Object* base_object = nullptr) : base_object(base_object) {} UnresolvedPath sub(model::BaseProperty* prop) const { auto copy = *this; copy.items.push_back({prop->name()}); return copy; } UnresolvedPath sub(int index) const { auto copy = *this; copy.items.back().index = index; return copy; } model::BaseProperty* prop() const { if ( items.empty() || !base_object ) return nullptr; model::Object* object = base_object; for ( int i = 0, e = items.size() - 1; i < e; i++ ) { object = items[i].step(object); if ( !object ) return nullptr; } return object->get_property(items.back().propname); } model::Object* base_object = nullptr; std::vector items; }; public: ImportState(GlaxnimateFormat* fmt, model::Document* document, int document_version = GlaxnimateFormat::format_version) : fmt(fmt), document(document), document_version(document_version) {} ~ImportState() {} void resolve() { for ( const auto& p : unresolved_references ) { model::BaseProperty* prop = p.first.prop(); model::DocumentNode* node = document->find_by_uuid(p.second); if ( !node ) { error(GlaxnimateFormat::tr("Property %1 of %2 refers to unexisting object %3") .arg(prop->name()) .arg(prop->object()->object_name()) .arg(p.second.toString()) ); } else { if ( !prop->set_value(QVariant::fromValue(node)) ) error(GlaxnimateFormat::tr("Could not load %1 for %2: uuid refers to an unacceptable object") .arg(prop->name()) .arg(prop->object()->object_name()) ); } } for ( model::Object* obj : unwanted ) { if ( obj ) { error(GlaxnimateFormat::tr("Object %1 is invalid").arg(obj->object_name())); delete obj; } } } void load_object ( model::Object* target, QJsonObject object ) { version_fixup(object); do_load_object(target, object, target); } void load_metadata(const QJsonObject& object) { document->metadata() = object["metadata"].toObject().toVariantMap(); auto info = object["info"]; document->info().author = info["author"].toString(); document->info().description = info["description"].toString(); for ( const auto& kw: info["keywords"].toArray() ) document->info().keywords.push_back(kw.toString()); } void load_document(QJsonObject top_level) { auto assets = top_level[document_version < 3 ? "defs" : "assets"].toObject(); if ( document_version < 8 ) { QJsonObject precomps; QJsonArray values; if ( assets.contains("precompositions") ) { precomps = assets["precompositions"].toObject(); values = precomps["values"].toArray(); } else { precomps["__type__"] = "CompositionList"; } if ( top_level["animation"].isObject() ) { QJsonObject main = top_level["animation"].toObject(); top_level.remove("animation"); values.push_front(main); } precomps["values"] = values; assets["precompositions"] = precomps; } load_metadata(top_level); load_object(document->assets(), assets); resolve(); } private: void error(const QString& msg) { if ( fmt ) emit fmt->warning(msg); } QJsonObject fixed_asset_list(const QString& type, const QJsonValue& values) { QJsonObject fixed; fixed["__type__"] = type; fixed["values"] = values; fixed["uuid"] = QUuid::createUuid().toString(); return fixed; } void version_fixup(QJsonObject& object) { if ( document_version == 1 ) { QString type = object["__type__"].toString(); static const auto fix_ac = [](QJsonObject& object){ QJsonObject ac; ac["__type__"] = "AnimationContainer"; ac["first_frame"] = object["first_frame"]; ac["last_frame"] = object["last_frame"]; object.remove("first_frame"); object.remove("last_frame"); }; if ( type == "MainComposition" ) { fix_ac(object); object["shapes"] = object["layers"]; object.remove("layers"); } else if ( type == "ShapeLayer" ) { fix_ac(object); object["__type__"] = "Layer"; } else if ( type == "EmptyLayer" ) { fix_ac(object); object["__type__"] = "Layer"; object["shapes"] = QJsonArray(); } } if ( document_version < 3 && object["__type__"].toString() == "Defs" ) { static const std::vector> types = { {"colors", "NamedColorList"}, {"gradient_colors", "GradientColorsList"}, {"gradients", "GradientList"}, {"images", "BitmapList"}, {"precompositions", "PrecompositionList"}, }; for ( const auto & pair : types ) { if ( object.contains(pair.first) ) { object[pair.first] = fixed_asset_list(pair.second, object[pair.first]); } } object["uuid"] = QUuid::createUuid().toString(); object["__type__"] = "Assets"; } if ( document_version < 4 && object["__type__"].toString() == "Assets" ) { object["fonts"] = fixed_asset_list("FontList", QJsonArray()); } if ( document_version < 5 ) { if ( object["__type__"].toString() == "Trim" ) { // values were swapped if ( object["multiple"].toString() == "Individually" ) object["multiple"] = "Simultaneously"; else object["multiple"] = "Individually"; } } if ( document_version < 6 ) { if ( object["__type__"].toString() == "MaskSettings" ) object["mask"] = int(object["mask"].toBool()); } if ( document_version < 8 ) { if ( object["__type__"].toString() == "MainComposition" ) { object["__type__"] = "Composition"; } else if ( object["__type__"].toString() == "Precomposition" ) { object["__type__"] = "Composition"; if ( !document->assets()->compositions->values.empty() ) { auto main = document->assets()->compositions->values[0]; if ( !object.contains("fps") ) object["fps"] = main->fps.get(); if ( !object.contains("width") ) object["width"] = main->width.get(); if ( !object.contains("height") ) object["height"] = main->height.get(); } } else if ( object["__type__"].toString() == "PrecompositionList" ) { object["__type__"] = "CompositionList"; } else if ( object["__type__"].toString() == "Assets" ) { QJsonObject comps = object["precompositions"].toObject(); object.remove("precompositions"); object["compositions"] = comps; } } } void do_load_object ( model::Object* target, QJsonObject object, const UnresolvedPath& path ) { QString type = object["__type__"].toString(); if ( type != target->type_name() ) error(GlaxnimateFormat::tr("Wrong object type: expected '%1' but got '%2'").arg(target->type_name()).arg(type)); for ( model::BaseProperty* prop : target->properties() ) { if ( object.contains(prop->name()) && !load_prop(prop, object[prop->name()], path.sub(prop)) ) { error(GlaxnimateFormat::tr("Could not load %1 for %2") .arg(prop->name()) .arg(prop->object()->object_name()) ); } } for ( auto it = object.begin(); it != object.end(); ++it ) { if ( !target->has(it.key()) && it.key() != "__type__" ) { if ( !target->set(it.key(), it->toVariant()) ) error(GlaxnimateFormat::tr("Could not set property %1").arg(it.key())); } } } bool load_prop ( model::BaseProperty* target, const QJsonValue& val, const UnresolvedPath& path ) { if ( target->traits().flags & model::PropertyTraits::List ) { if ( !val.isArray() ) return false; QVariantList list; for ( QJsonValue item : val.toArray() ) list.push_back(load_prop_value(target, item, false, {})); if ( target->traits().type == model::PropertyTraits::Object ) { int index = 0; for ( const QVariant& item : list ) { auto ptr = item.value(); model::ObjectListPropertyBase* prop = static_cast(target); if ( !ptr ) { error( GlaxnimateFormat::tr("Item %1 for %2 in %3 isn't an object") .arg(index) .arg(target->name()) .arg(target->object()->object_name()) ); } else { auto inserted = prop->insert_clone(ptr); if ( !inserted ) { error( GlaxnimateFormat::tr("Item %1 for %2 in %3 is not acceptable") .arg(index) .arg(target->name()) .arg(target->object()->object_name()) ); } else { do_load_object(inserted, deferred_loads[ptr], path.sub(index)); } deferred_loads.remove(ptr); } index++; } return true; } else { return target->set_value(list); } } else if ( target->traits().flags & model::PropertyTraits::Animated ) { QJsonObject jso = val.toObject(); if ( jso.contains("value") ) { return target->set_value(load_prop_value(target, jso["value"], true, path)); } else { model::AnimatableBase* anim = static_cast(target); bool position = anim->traits().type == model::PropertyTraits::Point; for ( auto v : jso["keyframes"].toArray() ) { QJsonObject kfobj = v.toObject(); if ( !kfobj.contains("time") ) { error(GlaxnimateFormat::tr("Keyframe must specify a time")); continue; } if ( !kfobj.contains("value") ) { error(GlaxnimateFormat::tr("Keyframe must specify a value")); continue; } model::KeyframeBase* kf = anim->set_keyframe( kfobj["time"].toDouble(), load_prop_value(target, kfobj["value"], false, {}) ); if ( !kf ) { error(GlaxnimateFormat::tr("Could not add keyframe")); continue; } QPointF before, after; if ( load_2d(kfobj["before"], "x", "y", before) && load_2d(kfobj["after"], "x", "y", after) ) { kf->set_transition({before, after}); } else { kf->set_transition({{0, 0}, {1, 1}, true}); } if ( position ) { auto pkf = static_cast*>(kf); QPointF tan_in = pkf->get(); QPointF tan_out = pkf->get(); bool load_ti = load_2d(kfobj["tan_in"], "x", "y", tan_in); bool load_to = load_2d(kfobj["tan_out"], "x", "y", tan_out); if ( load_ti || load_to ) { auto type = math::bezier::PointType(kfobj["point_type"].toInt()); pkf->set_point({pkf->get(), tan_in, tan_out, type}); } } } return true; } } if ( target->traits().type == model::PropertyTraits::ObjectReference ) { QUuid uuid(val.toString()); if ( !uuid.isNull() ) unresolved_references.emplace_back(path, uuid); return true; } else if ( target->traits().type == model::PropertyTraits::Uuid ) { QUuid uuid(val.toString()); if ( uuid.isNull() ) return false; return target->set_value(uuid); } QVariant loaded_val = load_prop_value(target, val, true, path); if ( !target->set_value(loaded_val) ) { if ( target->traits().type == model::PropertyTraits::Object ) unwanted.push_back(loaded_val.value()); return false; } return true; } QColor load_color(const QJsonValue& val) { QString name = val.toString(); // We want #rrggbbaa, qt does #aarrggbb if ( name.startsWith("#") && name.size() == 9 ) { int alpha = name.right(2).toInt(nullptr, 16); QColor col(name.left(7)); col.setAlpha(alpha); return col; } return QColor(name); } QVariant load_prop_value ( model::BaseProperty* target, const QJsonValue& val, bool load_objects, const UnresolvedPath& path ) { switch ( target->traits().type ) { case model::PropertyTraits::Object: { if ( !val.isObject() ) return {}; QJsonObject jobj = val.toObject(); version_fixup(jobj); model::Object* object = create_object(jobj["__type__"].toString()); if ( !object ) return {}; if ( load_objects ) do_load_object(object, jobj, path); else deferred_loads.insert(object, jobj); return QVariant::fromValue(object); } case model::PropertyTraits::ObjectReference: case model::PropertyTraits::Uuid: // handled above return {}; case model::PropertyTraits::Color: return load_color(val); case model::PropertyTraits::Point: { QPointF p; if ( load_2d(val, "x", "y", p) ) return p; return {}; } case model::PropertyTraits::Size: { QSizeF p; if ( load_2d(val, "width", "height", p) ) return p; return {}; } case model::PropertyTraits::Scale: { QVector2D p; if ( load_2d(val, "x", "y", p) ) return p; return {}; } case model::PropertyTraits::Bezier: { if ( !val.isObject() ) return {}; math::bezier::Bezier bezier; QJsonObject obj = val.toObject(); bezier.set_closed(obj["closed"].toBool()); for ( auto jspv : obj["points"].toArray() ) { if ( !jspv.isObject() ) continue; QJsonObject jsp = jspv.toObject(); math::bezier::Point p{{}, {}, {}}; load_2d(jsp["pos"], "x", "y", p.pos); load_2d(jsp["tan_in"], "x", "y", p.tan_in); load_2d(jsp["tan_out"], "x", "y", p.tan_out); p.type = math::bezier::PointType(jsp["type"].toInt()); bezier.push_back(p); } return QVariant::fromValue(bezier); } case model::PropertyTraits::Gradient: { if ( !val.isArray() ) return {}; QGradientStops stops; for ( auto jstopv : val.toArray() ) { if ( !jstopv.isObject() ) continue; auto jstop = jstopv.toObject(); stops.push_back({jstop["offset"].toDouble(), load_color(jstop["color"])}); } return QVariant::fromValue(stops); } case model::PropertyTraits::Data: return QByteArray::fromBase64(val.toString().toLatin1()); default: return val.toVariant(); } } template bool load_2d(const QJsonValue& val, const QString& x, const QString& y, Type& ret) { QJsonObject obj = val.toObject(); if ( obj.empty() ) return false; ret = Type(obj[x].toDouble(), obj[y].toDouble()); return true; } model::Object* create_object(const QString& type) { if ( auto obj = model::Factory::instance().build(type, document) ) { temporaries.emplace_back(obj); return obj; } error(GlaxnimateFormat::tr("Unknown object of type '%1'").arg(type)); temporaries.emplace_back(new model::Object(document)); return temporaries.back().get(); } GlaxnimateFormat* fmt; model::Document* document = nullptr; QMap references; std::vector> unresolved_references; QMap deferred_loads; std::vector unwanted; std::vector> temporaries; int document_version; }; } // namespace glaxnimate::io::glaxnimate::detail mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/lottie/validation.hpp000664 001750 001750 00000001762 14477652011 031055 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include "lottie_format.hpp" #include "model/visitor.hpp" namespace glaxnimate::io::lottie { class ValidationVisitor : public model::Visitor { public: explicit ValidationVisitor(LottieFormat* fmt) : fmt(fmt) {} protected: void show_error(model::DocumentNode * node, const QString& message, app::log::Severity severity) { fmt->message(LottieFormat::tr("%1: %2").arg(node->object_name()).arg(message), severity); } void on_visit(model::Document * document, model::Composition* main) override; LottieFormat* fmt; QSize fixed_size; std::vector allowed_fps; int max_frames = 0; }; /** * \brief Triggers warnings on \p format if \p document isn't suitable for Discord stickers */ void validate_discord(model::Document* document, model::Composition* main, LottieFormat* format); } // namespace glaxnimate::io::lottie mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/transform.cpp000664 001750 001750 00000003771 14477652011 030124 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "transform.hpp" #include "math/math.hpp" namespace { QTransform make_transform( const QPointF& anchor_point, const QPointF& position, double rotation, QVector2D scale, const std::optional& pos_derivative ) { QTransform trans; trans.translate(position.x(), position.y()); trans.rotate(rotation); trans.scale(scale.x(), scale.y()); trans.translate(-anchor_point.x(), -anchor_point.y()); if ( pos_derivative ) trans.rotate(glaxnimate::math::rad2deg(glaxnimate::math::atan2(pos_derivative->y(), pos_derivative->x()))); return trans; } } // namespace GLAXNIMATE_OBJECT_IMPL(glaxnimate::model::Transform) QTransform glaxnimate::model::Transform::transform_matrix(FrameTime f, bool auto_orient) const { std::optional pos_derivative; if ( auto_orient ) pos_derivative = position.derivative_at(f); return make_transform( anchor_point.get_at(f), position.get_at(f), rotation.get_at(f), scale.get_at(f), pos_derivative ); } void glaxnimate::model::Transform::set_transform_matrix(const QTransform& t) { qreal a = t.m11(); qreal b = t.m12(); qreal c = t.m21(); qreal d = t.m22(); qreal tx = t.m31(); qreal ty = t.m32(); position.set(QPointF(tx, ty)); qreal delta = a * d - b * c; qreal sx = 1; qreal sy = 1; if ( a != 0 || b != 0 ) { qreal r = math::hypot(a, b); rotation.set(-math::rad2deg(-math::sign(b) * math::acos(a/r))); sx = r; sy = delta / r; } else { qreal r = math::hypot(c, d); rotation.set(-math::rad2deg(math::pi / 2 + math::sign(d) * math::acos(c / r))); sx = delta / r; sy = r; } scale.set(QVector2D(sx, sy)); } void glaxnimate::model::Transform::copy(glaxnimate::model::Transform* other) { other->clone_into(this); } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/shapes/layer.cpp000664 001750 001750 00000012023 14477652011 030476 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "layer.hpp" #include #include "model/assets/composition.hpp" #include "model/document.hpp" GLAXNIMATE_OBJECT_IMPL(glaxnimate::model::Layer) void glaxnimate::model::Layer::ChildLayerIterator::find_first() { while ( index < comp->size() && (*comp)[index]->docnode_group_parent() != parent ) ++index; } glaxnimate::model::VisualNode* glaxnimate::model::Layer::ChildLayerIterator::operator*() const { return (*comp)[index]; } glaxnimate::model::VisualNode* glaxnimate::model::Layer::ChildLayerIterator::operator->() const { return (*comp)[index]; } glaxnimate::model::VisualNode * glaxnimate::model::Layer::docnode_group_parent() const { return parent.get(); } glaxnimate::model::VisualNode * glaxnimate::model::Layer::docnode_group_child(int index) const { ChildLayerIterator iter(owner(), this, 0); std::advance(iter, index); return *iter; } int glaxnimate::model::Layer::docnode_group_child_count() const { if ( !owner() ) return 0; int sz = 0; for ( const auto& sib : *owner() ) if ( sib->docnode_group_parent() == this ) sz++; return sz; } std::vector glaxnimate::model::Layer::valid_parents() const { std::vector refs; refs.push_back(nullptr); if ( is_top_level() ) { for ( const auto& sh : *owner() ) { if ( auto lay = qobject_cast(sh.get()) ) if ( !is_ancestor_of(lay) ) refs.push_back(lay); } } return refs; } bool glaxnimate::model::Layer::is_valid_parent(glaxnimate::model::DocumentNode* node) const { if ( node == nullptr ) return true; if ( is_top_level() ) { if ( Layer* layer = qobject_cast(node) ) return !is_ancestor_of(layer); } return false; } bool glaxnimate::model::Layer::is_ancestor_of ( const glaxnimate::model::Layer* other ) const { while ( other ) { if ( other == this ) return true; other = other->parent.get(); } return false; } void glaxnimate::model::Layer::set_time(glaxnimate::model::FrameTime t) { Object::set_time(relative_time(t)); } bool glaxnimate::model::Layer::is_top_level() const { return qobject_cast(docnode_parent()); } void glaxnimate::model::Layer::paint(QPainter* painter, FrameTime time, PaintMode mode, glaxnimate::model::Modifier* modifier) const { if ( !visible.get() || (mode == Render && !render.get()) ) return; time = relative_time(time); if ( !animation->time_visible(time) ) return; if ( mask->has_mask() ) { auto n_shapes = shapes.size(); if ( n_shapes <= 1 ) return; painter->save(); auto transform = group_transform_matrix(time); painter->setTransform(transform, true); if ( shapes[0]->visible.get() ) { QPainterPath clip = shapes[0]->to_clip(time); clip.setFillRule(Qt::WindingFill); if ( mask->inverted.get() ) { QPainterPath outer_clip; outer_clip.addPolygon( transform.inverted().map(owner_composition()->rect()) ); clip = outer_clip.subtracted(clip); } painter->setClipPath(clip, Qt::IntersectClip); } on_paint(painter, time, mode, modifier); for ( int i = 1; i < n_shapes; i++ ) docnode_visual_child(i)->paint(painter, time, mode); painter->restore(); } else { VisualNode::paint(painter, time, mode); } } QPainterPath glaxnimate::model::Layer::to_clip(glaxnimate::model::FrameTime time) const { time = relative_time(time); if ( !animation->time_visible(time) || !render.get() ) return {}; return Group::to_clip(time); } QPainterPath glaxnimate::model::Layer::to_painter_path_impl(glaxnimate::model::FrameTime time) const { time = relative_time(time); if ( !animation->time_visible(time) || !render.get() ) return {}; return Group::to_painter_path_impl(time); } QIcon glaxnimate::model::Layer::tree_icon() const { return mask->has_mask() ? QIcon::fromTheme("path-clip-edit") : QIcon::fromTheme("folder"); } QIcon glaxnimate::model::Layer::static_tree_icon() { return QIcon::fromTheme("folder"); } std::unique_ptr glaxnimate::model::Layer::to_path() const { auto clone = std::make_unique(document()); for ( BaseProperty* prop : properties() ) { if ( prop != &shapes ) clone->get_property(prop->name())->assign_from(prop); } for ( const auto& shape : shapes ) { clone->shapes.insert(shape->to_path()); if ( shape->is_instance() ) break; } return clone; } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/property/option_list_property.cpp000664 001750 001750 00000000243 14477652011 034273 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "option_list_property.hpp" mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/math/bezier/bezier_length.hpp000664 001750 001750 00000002627 14477652011 032047 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include "bezier.hpp" namespace glaxnimate::math::bezier { class LengthData { public: struct SplitInfo { int index = 0; qreal ratio = 0; qreal length = 0; const LengthData* child = nullptr; SplitInfo descend() const { return child->at_ratio(ratio); } }; explicit LengthData(const Solver& segment, int steps); explicit LengthData(const Bezier& bez, int steps); explicit LengthData(const MultiBezier& mbez, int steps); SplitInfo at_ratio(qreal ratio) const; SplitInfo at_length(qreal length) const; /** * \brief Returns the length such that * `at_length(length).ratio == ratio` */ qreal from_ratio(qreal ratio) const; qreal length() const noexcept; /** * \returns The length at which the child at \p index starts */ qreal child_start(int index) const; /** * \returns The length at which the child at \p index ends */ qreal child_end(int index) const; private: LengthData(qreal t, qreal length, qreal cumulative_length); qreal t_ = 0; qreal length_ = 0; qreal cumulative_length_ = 0; std::vector children_; bool leaf_ = false; }; } // namespace glaxnimate::math::bezier mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/svg/parse_error.hpp000664 001750 001750 00000001133 14477652011 030535 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include namespace glaxnimate::io::svg { class SvgParseError : public std::exception { public: QString formatted(const QString& filename) const { return QString("%1:%2:%3: XML Parse Error: %4") .arg(filename) .arg(line) .arg(column) .arg(message) ; } QString message; int line = -1; int column = -1; }; } // namespace glaxnimate::io::svg mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/lottie/tgs_format.cpp000664 001750 001750 00000007315 14477652011 031063 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "tgs_format.hpp" #include #include "cbor_write_json.hpp" #include "utils/gzip.hpp" #include "model/shapes/polystar.hpp" #include "model/shapes/image.hpp" #include "model/shapes/stroke.hpp" #include "model/shapes/repeater.hpp" #include "model/shapes/inflate_deflate.hpp" #include "model/shapes/offset_path.hpp" #include "model/shapes/zig_zag.hpp" #include "validation.hpp" using namespace glaxnimate; using namespace glaxnimate::io::lottie; namespace { class TgsVisitor : public ValidationVisitor { public: explicit TgsVisitor(LottieFormat* fmt) : ValidationVisitor(fmt) { allowed_fps.push_back(30); allowed_fps.push_back(60); fixed_size = QSize(512, 512); max_frames = 180; } private: using ValidationVisitor::on_visit; void on_visit(model::DocumentNode * node) override { if ( qobject_cast(node) ) { show_error(node, TgsFormat::tr("Star Shapes are not officially supported"), app::log::Info); } else if ( qobject_cast(node) || qobject_cast(node) ) { show_error(node, TgsFormat::tr("Images are not supported"), app::log::Error); } else if ( auto st = qobject_cast(node) ) { if ( qobject_cast(st->use.get()) ) show_error(node, TgsFormat::tr("Gradient strokes are not officially supported"), app::log::Info); } else if ( auto layer = qobject_cast(node) ) { if ( layer->mask->has_mask() ) show_error(node, TgsFormat::tr("Masks are not supported"), app::log::Error); } else if ( qobject_cast(node) ) { show_error(node, TgsFormat::tr("Repeaters are not officially supported"), app::log::Info); } else if ( qobject_cast(node) ) { show_error(node, TgsFormat::tr("Inflate/Deflate is not supported"), app::log::Warning); } else if ( qobject_cast(node) ) { show_error(node, TgsFormat::tr("Offset Path is not supported"), app::log::Warning); } else if ( qobject_cast(node) ) { show_error(node, TgsFormat::tr("ZigZag is not supported"), app::log::Warning); } } }; } // namespace bool glaxnimate::io::lottie::TgsFormat::on_open(QIODevice& file, const QString&, model::Document* document, const QVariantMap&) { QByteArray json; if ( !utils::gzip::decompress(file, json, [this](const QString& s){ error(s); }) ) return false; return load_json(json, document); } bool glaxnimate::io::lottie::TgsFormat::on_save(QIODevice& file, const QString&, model::Composition* comp, const QVariantMap&) { validate(comp->document(), comp); QCborMap json = LottieFormat::to_json(comp, true, true); json[QLatin1String("tgs")] = 1; QByteArray data = cbor_write_json(json, true); quint32 compressed_size = 0; if ( !utils::gzip::compress(data, file, [this](const QString& s){ error(s); }, 9, &compressed_size) ) return false; qreal size_k = compressed_size / 1024.0; if ( size_k > 64 ) error(tr("File too large: %1k, should be under 64k").arg(size_k)); return true; } void glaxnimate::io::lottie::TgsFormat::validate(model::Document* document, model::Composition* comp) { TgsVisitor(this).visit(document, comp); } glaxnimate::io::Autoreg glaxnimate::io::lottie::TgsFormat::autoreg = {}; mlt-7.22.0/src/modules/glaxnimate/glaxnimate/external/QtAppSetup/src/app/log/log.hpp000664 001750 001750 00000003134 14477652011 032071 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include "app/log/logger.hpp" namespace app::log { class LogStream { public: LogStream(const QString& source, const QString& detail = "", Severity severity = Warning) : source(source), detail(detail), severity(severity) {} ~LogStream() { if ( !message.isEmpty() ) Logger::instance().log({severity, source, detail, message, QDateTime::currentDateTime()}); } template LogStream& operator<<(T&& item) { if ( !message.isEmpty() ) str << ' '; str << std::forward(item); return *this; } private: QString source; QString detail; Severity severity; QString message; QTextStream str{&message}; }; class Log { public: Log(const QString& source, const QString& detail="") : source(source), detail(detail) {} const Log& log(const QString& message, Severity severity = Warning) const { Logger::instance().log({severity, source, detail, message, QDateTime::currentDateTime()}); return *this; } void operator()(const QString& message, Severity severity = Warning) { log(message, severity); } void set_detail(const QString& detail) { this->detail = detail; } LogStream stream(Severity severity = Warning) const { return LogStream(source, detail, severity); } private: QString source; QString detail; }; } // namespace app::log mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/aep/aep_parser.hpp000664 001750 001750 00000100562 14477652011 030307 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include #include "aep_riff.hpp" #include "ae_project.hpp" #include "cos.hpp" #include "gradient_xml.hpp" #include "aep_format.hpp" namespace glaxnimate::io::aep { class AepError : public std::runtime_error { public: AepError(QString message) : runtime_error(message.toStdString()), message(std::move(message)) {} QString message; }; class AepParser { private: using Chunk = const RiffChunk*; using ChunkRange = RiffChunk::FindRange; struct PropertyContext { Composition* comp = nullptr; Layer* layer = nullptr; model::FrameTime time_to_frames(model::FrameTime time) const { return time / comp->time_scale + layer->start_time; } }; public: AepParser(ImportExport* io) : io(io) {} Project parse(const RiffChunk& root) { if ( root.subheader != "Egg!" ) throw AepError(AepFormat::tr("Not an AEP file")); Project project; Chunk fold = nullptr, efdg = nullptr; root.find_multiple({&fold, &efdg}, {"Fold", "EfdG"}); if ( load_unecessary && efdg ) parse_effect_definitions(efdg->find_all("EfDf"), project); parse_folder(fold, project.folder, project); for ( auto& comp : project.compositions ) parse_composition(comp_chunks[comp->id], *comp); return project; } private: void parse_folder(Chunk chunk, Folder& folder, Project& project) { FolderItem* current_item = nullptr; for ( const auto& child : chunk->children ) { if ( *child == "fiac" ) { if ( current_item && child->data().read_uint8() ) project.current_item = current_item; } else if ( *child == "Item" ) { Chunk item = nullptr; Chunk name_chunk = nullptr; child->find_multiple({&item, &name_chunk}, {"idta", "Utf8"}); current_item = nullptr; if ( !item ) continue; auto name = to_string(name_chunk); auto data = item->data(); auto type = data.read_uint16(); data.skip(14); auto id = data.read_uint32(); data.skip(38); auto color = LabelColors(data.read_uint8()); switch ( type ) { case 1: // Folder { auto child_item = folder.add(); child_item->id = id; child_item->name = name; current_item = child_item; if ( auto contents = child->child("Sfdr") ) parse_folder(contents, *child_item, project); break; } case 4: // Composition { auto comp = folder.add(); comp->id = id; comp->name = name; current_item = comp; project.compositions.push_back(comp); project.assets[id] = comp; comp_chunks[id] = child.get(); break; } case 7: // Asset current_item = parse_asset(id, child->child("Pin "), folder, project); break; default: warning(QObject::tr("Unknown Item type %s").arg(type)); } if ( current_item ) current_item->label_color = color; } } } void parse_composition(Chunk chunk, Composition& comp) { auto cdta = chunk->child("cdta"); if ( !cdta ) { warning(AepFormat::tr("Missing composition data")); return; } /// \todo label color? auto data = cdta->data(); comp.resolution_x = data.read_uint16(); comp.resolution_y = data.read_uint16(); data.skip(1); // Time stuff comp.time_scale = data.read_uint16(); data.skip(14); comp.playhead_time = comp.time_to_frames(data.read_uint16()); data.skip(6); comp.in_time = comp.time_to_frames(data.read_uint16()); data.skip(6); auto out_time = data.read_uint16(); data.skip(6); comp.duration = comp.time_to_frames(data.read_uint16()); if ( out_time == 0xffff ) comp.out_time = comp.duration; else comp.out_time = comp.time_to_frames(out_time); data.skip(5); // Background comp.color.setRed(data.read_uint8()); comp.color.setGreen(data.read_uint8()); comp.color.setBlue(data.read_uint8()); // Flags data.skip(84); Flags attr(data.read_uint8()); comp.shy = attr.get(0, 0); comp.motion_blur = attr.get(0, 3); comp.frame_blending = attr.get(0, 4); comp.preserve_framerate = attr.get(0, 5); comp.preserve_resolution = attr.get(0, 7); // Lottie comp.width = data.read_uint16(); comp.height = data.read_uint16(); comp.pixel_ratio_width = data.read_uint32(); comp.pixel_ratio_height = data.read_uint32(); data.skip(4); comp.framerate = data.read_uint16(); // Misc data.skip(16); comp.shutter_angle = data.read_uint16(); comp.shutter_phase = data.read_sint32(); data.skip(16); comp.samples_limit = data.read_uint32(); comp.samples_per_frame = data.read_uint32(); for ( const auto& child : chunk->children ) { if ( *child == "Layr" ) comp.layers.push_back(parse_layer(child.get(), comp)); else if ( load_unecessary && *child == "SecL" ) comp.markers = parse_layer(child.get(), comp); else if ( load_unecessary && (*child == "CLay" || *child == "DLay" || *child == "SLay") ) comp.views.push_back(parse_layer(child.get(), comp)); } } QString to_string(Chunk chunk) { if ( !chunk ) return ""; auto data = chunk->data().read(); if ( data == placeholder ) return ""; if ( chunk->header == "Utf8" ) return QString::fromUtf8(data); warning(AepFormat::tr("Unknown encoding for %1").arg(chunk->header.to_string())); return ""; } void warning(const QString& msg) const { io->warning(msg); } FolderItem* parse_asset(Id id, Chunk chunk, Folder& folder, Project& project) { Chunk sspc, utf8, als2, opti; sspc = utf8 = als2 = opti = nullptr; chunk->find_multiple({&sspc, &utf8, &als2, &opti}, {"sspc", "Utf8", "Als2", "opti"}); if ( !sspc || !opti ) { warning(AepFormat::tr("Missing asset data")); return nullptr; } auto name = to_string(utf8); auto asset_reader = sspc->data(); asset_reader.skip(32); auto width = asset_reader.read_uint16(); asset_reader.skip(2); auto height = asset_reader.read_uint16(); Asset* asset; auto data = opti->data(); if ( data.read(4) == "Soli" ) { data.skip(6); auto solid = folder.add(); solid->color.setAlphaF(data.read_float32()); solid->color.setRedF(data.read_float32()); solid->color.setGreenF(data.read_float32()); solid->color.setBlueF(data.read_float32()); solid->name = data.read_utf8_nul(256); asset = solid; } else { auto doc = QJsonDocument::fromJson(als2->child("alas")->data().read()); QString path = doc.object()["fullpath"].toString(); // Handle weird windows paths if ( path.contains('\\') && QDir::separator() == '/' ) { path = path.replace('\\', '/'); if ( path.size() > 1 && path[1] == ':' ) path = '/' + path; } auto file = folder.add(); file->path = QFileInfo(path); file->name = name.isEmpty() ? file->path.fileName() : name; asset = file; } asset->width = width; asset->height = height; asset->id = id; project.assets[id] = asset; return asset; } std::unique_ptr parse_layer(Chunk chunk, Composition& comp) { auto layer = std::make_unique(); Chunk ldta, utf8, tdgp; ldta = utf8 = tdgp = nullptr; chunk->find_multiple({&ldta, &utf8, &tdgp}, {"ldta", "Utf8", "tdgp"}); if ( !ldta ) { warning(AepFormat::tr("Missing layer data")); return {}; } PropertyContext context{&comp, layer.get()}; layer->name = to_string(utf8); auto data = ldta->data(); layer->id = data.read_uint32(); layer->quality = LayerQuality(data.read_uint16()); data.skip(4); layer->time_stretch = data.read_uint16(); data.skip(1); layer->start_time = comp.time_to_frames(data.read_sint16()); data.skip(6); layer->in_time = context.time_to_frames(data.read_uint16()); data.skip(6); layer->out_time = context.time_to_frames(data.read_uint16()); data.skip(6); Flags flags = data.read_uint<3>(); layer->is_guide = flags.get(2, 1); layer->bicubic_sampling = flags.get(2, 6); layer->auto_orient = flags.get(1, 0); layer->is_adjustment = flags.get(1, 1); layer->threedimensional = flags.get(1, 2); layer->solo = flags.get(1, 3); layer->is_null = flags.get(1, 7); layer->visible = flags.get(0, 0); layer->effects_enabled = flags.get(0, 2); layer->motion_blur = flags.get(0, 3); layer->locked = flags.get(0, 5); layer->shy = flags.get(0, 6); layer->continuously_rasterize = flags.get(0, 7); layer->asset_id = data.read_uint32(); data.skip(17); layer->label_color = LabelColors(data.read_uint8()); data.skip(2); data.skip(32); // Name, we get it from Utf8 instead data.skip(11); layer->matte_mode = TrackMatteType(data.read_uint8()); data.skip(2); layer->time_stretch /= data.read_uint16(); data.skip(19); layer->type = LayerType(data.read_uint8()); layer->parent_id = data.read_uint32(); data.skip(24); layer->matte_id = data.read_uint32(); parse_property_group(tdgp, layer->properties, context); return layer; } void parse_property_group(Chunk chunk, PropertyGroup& group, const PropertyContext& context) { QString match_name; for ( auto it = chunk->children.begin(); it != chunk->children.end(); ++it ) { auto child = it->get(); if ( *child == "tdmn" ) { match_name = child->data().read_utf8_nul(); } else if ( *child == "tdsb" ) { Flags flags = child->data().read_uint32(); group.visible = flags.get(0, 0); } else if ( *child == "tdsn" ) { group.name = to_string(child->child("Utf8")); } else if ( *child == "mkif" ) { auto mask = std::make_unique(); auto data = child->data(); mask->inverted = data.read_uint8(); mask->locked = data.read_uint8(); data.skip(4); mask->mode = MaskMode(data.read_uint16()); ++it; if ( it == chunk->children.end() ) { warning(AepFormat::tr("Missing mask properties")); return; } if ( **it != "tdgp" ) { warning(AepFormat::tr("Missing mask properties")); continue; } parse_property_group(it->get(), mask->properties, context); group.properties.push_back({match_name, std::move(mask)}); match_name.clear(); } else if ( !match_name.isEmpty() ) { auto prop = parse_property(child, context); if ( prop ) group.properties.push_back({match_name, std::move(prop)}); match_name.clear(); } } } std::unique_ptr parse_property_group(Chunk chunk, const PropertyContext& context) { auto group = std::make_unique(); parse_property_group(chunk, *group, context); return group; } std::unique_ptr parse_property(Chunk chunk, const PropertyContext& context) { if ( *chunk == "tdgp" ) return parse_property_group(chunk, context); else if ( *chunk == "tdbs" ) return parse_animated_property(chunk, context, {}); else if ( *chunk == "om-s" ) return parse_animated_with_values(chunk, context, "omks", "shap", &AepParser::parse_bezier); else if ( *chunk == "GCst" ) return parse_animated_with_values(chunk, context, "GCky", "Utf8", &AepParser::parse_gradient); else if ( *chunk == "btds" ) return parse_animated_text(chunk, context); else if ( *chunk == "sspc" ) return parse_effect_instance(chunk, context); else if ( *chunk == "otst" ) return load_unecessary ? parse_animated_with_values(chunk, context, "otky", "otda", &AepParser::parse_orientation) : nullptr; else if ( *chunk == "mrst" ) return load_unecessary ? parse_animated_with_values(chunk, context, "mrky", "Nmrd", &AepParser::parse_marker) : nullptr; // I've seen these in files but I'm not sure how to parse them else if ( *chunk == "OvG2" || *chunk == "blsi" || *chunk == "blsv" ) return {}; warning(AepFormat::tr("Unknown property type: %1").arg(chunk->name().to_string())); return {}; } std::unique_ptr parse_animated_property( Chunk chunk, const PropertyContext& context, std::vector&& values ) { auto prop = std::make_unique(); parse_animated_property(prop.get(), chunk, context, std::move(values)); return prop; } void parse_animated_property( Property* prop, Chunk chunk, const PropertyContext& context, std::vector values ) { Chunk tdsb, header, value, keyframes, expression, tdpi, tdps, tdli; tdsb = header = value = keyframes = expression = tdpi = tdps = tdli = nullptr; chunk->find_multiple( {&tdsb, &header, &value, &keyframes, &expression, &tdpi, &tdps, &tdli}, {"tdsb", "tdb4", "cdat", "list", "Utf8", "tdpi", "tdps", "tdli"} ); if ( tdsb ) { Flags flags(tdsb->data().read_uint32()); prop->split = flags.get(1, 3); } auto data = header->data(); data.skip(2); prop->components = data.read_uint16(); bool position = Flags(data.read_uint16()).get(0, 3); data.skip(10+8*5); Flags type = data.read_uint32(); bool no_value = type.get(2, 0); bool color = type.get(0, 0); bool integer = type.get(0, 2); data.skip(8); if ( position ) prop->type = PropertyType::Position; else if ( color ) prop->type = PropertyType::Color; else if ( no_value ) prop->type = PropertyType::NoValue; else if ( integer ) prop->type = PropertyType::Integer; else prop->type = PropertyType::MultiDimensional; prop->animated = data.read_uint8() == 1; data.skip(6); prop->is_component = data.read_uint8() == 1; if ( integer && tdpi ) { prop->type = PropertyType::LayerSelection; LayerSelection val; val.layer_id = tdpi->data().read_uint32(); if ( tdps ) val.layer_source = LayerSource(tdps->data().read_sint32()); prop->value = val; } else if ( integer && tdli ) { prop->type = PropertyType::MaskIndex; prop->value = tdli->data().read_uint32(); } else if ( keyframes ) { auto raw_keys = list_values(keyframes); for ( std::size_t i = 0; i < raw_keys.size(); i++ ) { prop->keyframes.push_back(load_keyframe(i, raw_keys[i], *prop, context, values)); } } else if ( value ) { auto vdat = value->data(); auto raw_value = vdat.read_array(&BinaryReader::read_float64, prop->components); prop->value = property_value(0, raw_value, values, prop->type); } if ( expression ) prop->expression = to_string(expression); } PropertyValue property_value( int index, const std::vector& raw_value, std::vector& values, PropertyType type ) { switch ( type ) { case PropertyType::NoValue: if ( index < int(values.size()) ) return std::move(values[index]); return nullptr; case PropertyType::Color: if ( raw_value.size() < 4 ) return QColor(); return QColor(raw_value[1], raw_value[2], raw_value[3], raw_value[0]); default: return vector_value(raw_value); } } PropertyValue vector_value(const std::vector& raw_value) { switch ( raw_value.size() ) { case 0: return nullptr; case 1: return raw_value[0]; case 2: return QPointF(raw_value[0], raw_value[1]); case 3: default: return QVector3D(raw_value[0], raw_value[1], raw_value[2]); } } Keyframe load_keyframe(int index, BinaryReader& reader, Property& prop, const PropertyContext& context, std::vector& values) { reader.prepare(); Keyframe kf; reader.skip(1); kf.time = context.time_to_frames(reader.read_uint16()); reader.skip(2); kf.transition_type = KeyframeTransitionType(reader.read_uint8()); kf.label_color = LabelColors(reader.read_uint8()); Flags flags = reader.read_uint8(); kf.roving = flags.get(0, 5); if ( flags.get(0, 3) ) kf.bezier_mode = KeyframeBezierMode::Continuous; else if ( flags.get(0, 4) ) kf.bezier_mode = KeyframeBezierMode::Auto; else kf.bezier_mode = KeyframeBezierMode::Normal; if ( prop.type == PropertyType::NoValue ) { reader.skip(16); kf.in_speed.push_back(reader.read_float64()); kf.in_influence.push_back(reader.read_float64()); kf.out_speed.push_back(reader.read_float64()); kf.out_influence.push_back(reader.read_float64()); kf.value = std::move(values[index]); } else if ( prop.type == PropertyType::MultiDimensional || prop.type == PropertyType::Integer ) { kf.value = vector_value(reader.read_array(&BinaryReader::read_float64, prop.components)); kf.in_speed = reader.read_array(&BinaryReader::read_float64, prop.components); kf.in_influence = reader.read_array(&BinaryReader::read_float64, prop.components); kf.out_speed = reader.read_array(&BinaryReader::read_float64, prop.components); kf.out_influence = reader.read_array(&BinaryReader::read_float64, prop.components); } else if ( prop.type == PropertyType::Position ) { reader.skip(16); kf.in_speed.push_back(reader.read_float64()); kf.in_influence.push_back(reader.read_float64()); kf.out_speed.push_back(reader.read_float64()); kf.out_influence.push_back(reader.read_float64()); kf.value = vector_value(reader.read_array(&BinaryReader::read_float64, prop.components)); auto it = reader.read_array(&BinaryReader::read_float64, prop.components); auto ot = reader.read_array(&BinaryReader::read_float64, prop.components); if ( prop.components >= 2 ) { kf.in_tangent = {it[0], it[1]}; kf.out_tangent = {ot[0], ot[1]}; } } else if ( prop.type == PropertyType::Color ) { reader.skip(16); kf.in_speed.push_back(reader.read_float64()); kf.in_influence.push_back(reader.read_float64()); kf.out_speed.push_back(reader.read_float64()); kf.out_influence.push_back(reader.read_float64()); auto value = reader.read_array(&BinaryReader::read_float64, prop.components); kf.value = QColor(value[1], value[2], value[3], value[0]); } return kf; } template std::unique_ptr parse_animated_with_values( Chunk chunk, const PropertyContext& context, const char* container, const char* value_name, T (AepParser::*parse)(Chunk chunk) ) { Chunk value_container, tdbs; value_container = tdbs = nullptr; chunk->find_multiple({&value_container, &tdbs}, {container, "tdbs"}); std::vector values; for ( const RiffChunk& value_chunk : value_container->find_all(value_name) ) values.emplace_back((this->*parse)(&value_chunk)); return parse_animated_property(tdbs, context, std::move(values)); } std::vector list_values(Chunk list) { Chunk head, vals; head = vals = nullptr; list->find_multiple({&head, &vals}, {"lhd3", "ldat"}); if ( !head || !vals ) { warning(AepFormat::tr("Missing list data")); return {}; } auto data = head->data(); data.skip(10); std::uint32_t count = data.read_uint16(); data.skip(6); std::uint32_t size = data.read_uint16(); std::uint32_t total_size = count * size; if ( vals->reader.size() < total_size ) { warning(AepFormat::tr("Not enough data in list")); return {}; } std::vector values; values.reserve(count); for ( std::uint32_t i = 0; i < count; i++ ) values.push_back(vals->reader.sub_reader(size, i * size)); vals->reader.prepare(); return values; } BezierData parse_bezier(Chunk chunk) { BezierData data; auto bounds = chunk->child("shph")->data(); bounds.skip(3); data.closed = !Flags(bounds.read_uint8()).get(0, 3); data.minimum.setX(bounds.read_float32()); data.minimum.setY(bounds.read_float32()); data.maximum.setX(bounds.read_float32()); data.maximum.setY(bounds.read_float32()); for ( auto& pt : list_values(chunk->child("list")) ) { float x = pt.read_float32(); float y = pt.read_float32(); data.points.push_back({x, y}); } return data; } Gradient parse_gradient(Chunk chunk) { return parse_gradient_xml(to_string(chunk)); } QVector3D parse_orientation(Chunk chunk) { auto data = chunk->data(); QVector3D v; v.setX(data.read_float64()); v.setY(data.read_float64()); v.setZ(data.read_float64()); return v; } Marker parse_marker(Chunk chunk) { Marker marker; marker.name = to_string(chunk->child("Utf8")); auto data = chunk->child("NmHd")->data(); data.skip(4); marker.is_protected = data.read_uint8() & 2; data.skip(4); marker.duration = data.read_uint32(); data.skip(4); marker.label_color = LabelColors(data.read_uint8()); return marker; } QColor cos_color(const CosValue& cos) { const auto& arr = *cos.get(); if ( arr.size() < 4 ) throw CosError("Not enough components for color"); return QColor::fromRgbF( arr[1].get(), arr[2].get(), arr[3].get(), arr[0].get() ); } TextDocument parse_text_document(const CosValue& cos) { TextDocument doc; doc.text = get_as(cos, 0, 0); for ( const auto& cs : *get_as(cos, 0, 5, 0) ) { LineStyle style; style.character_count = get_as(cs, 1); const auto& data = get(cs, 0, 0, 5); style.text_justify = TextJustify(get_as(data, 0)); doc.line_styles.emplace_back(std::move(style)); } for ( const auto& cs : *get_as(cos, 0, 6, 0) ) { CharacterStyle style; style.character_count = get_as(cs, 1); const auto& data = get(cs, 0, 0, 6); style.font_index = get_as(data, 0); style.size = get_as(data, 1); style.faux_bold = get_as(data, 2); style.faux_italic = get_as(data, 3); style.text_transform = TextTransform(get_as(data, 12)); style.vertical_align = TextVerticalAlign(get_as(data, 13)); style.fill_color = cos_color(get(data, 53, 0, 1)); style.stroke_color = cos_color(get(data, 54, 0, 1)); style.stroke_enabled = get_as(data, 57); style.stroke_over_fill = get_as(data, 58); style.stroke_width = get_as(data, 63); doc.character_styles.emplace_back(std::move(style)); } return doc; } std::unique_ptr parse_animated_text(Chunk chunk, const PropertyContext& context) { Chunk text_data, tdbs; text_data = tdbs = nullptr; chunk->find_multiple({&text_data, &tdbs}, {"btdk", "tdbs"}); try { auto val = CosParser(text_data->data().read()).parse(); if ( val.type() != CosValue::Index::Object ) throw CosError("Expected Object"); auto property = std::make_unique(); for ( const auto& font : *get_as(val, 0, 1, 0) ) property->fonts.push_back({get_as(font, 0, 0, 0)}); std::vector values; for ( const auto& doc : *get_as(val, 1, 1) ) values.push_back(parse_text_document(doc)); parse_animated_property(&property->documents, tdbs, context, std::move(values)); return property; } catch ( const CosError& err ) { warning(AepFormat::tr("Invalid text document: %1").arg(err.message)); return {}; } } void parse_effect_definitions(const ChunkRange& range, Project& project) { for ( const auto& chunk : range ) { Chunk tdmn, sspc; tdmn = sspc = nullptr; chunk.find_multiple({&tdmn, &sspc}, {"tdmn", "sspc"}); if ( !tdmn || !sspc ) continue; auto mn = tdmn->data().read_utf8_nul(); EffectDefinition& effect = project.effects[mn]; effect.match_name = mn; Chunk fnam, part; fnam = part = nullptr; chunk.find_multiple({&fnam, &part}, {"fnam", "parT"}); if ( fnam ) effect.name = to_string(fnam->child("Utf8")); QString param_mn; for ( const auto& param_chunk : part->children ) { if ( *param_chunk == "tdmn" ) { param_mn = param_chunk->data().read_utf8_nul(); } else { auto& param = effect.parameter_map[param_mn]; param.match_name = param_mn; effect.parameters.push_back(¶m); parse_effect_parameter(param, param_chunk->data()); } } } } void parse_effect_parameter(EffectParameter& param, BinaryReader data) { data.skip(15); param.type = EffectParameterType(data.read_uint8()); param.name = data.read_utf8_nul(32); data.skip(8); switch ( param.type ) { case EffectParameterType::Layer: param.last_value = LayerSelection(); param.default_value = LayerSelection(); break; case EffectParameterType::Scalar: case EffectParameterType::Angle: param.last_value = data.read_sint32() / 0x10000; param.default_value = 0; break; case EffectParameterType::Boolean: param.last_value = data.read_uint32(); param.default_value = data.read_uint8(); break; case EffectParameterType::Color: { auto a = data.read_uint8(); auto r = data.read_uint8(); auto g = data.read_uint8(); auto b = data.read_uint8(); param.last_value = QColor(r, g, b, a); data.skip(1); a = 255; r = data.read_uint8(); g = data.read_uint8(); b = data.read_uint8(); param.default_value = QColor(r, g, b, a); break; } case EffectParameterType::Vector2D: { qreal x = data.read_sint32(); qreal y = data.read_sint32(); param.last_value = QPointF(x / 0x80, y / 0x80); param.default_value = QPointF(); break; } case EffectParameterType::Enum: param.last_value = data.read_uint32(); data.skip(2); // Number of enum values param.default_value = data.read_uint16(); break; case EffectParameterType::Slider: param.last_value = data.read_float64(); param.default_value = 0; break; case EffectParameterType::Vector3D: { auto x3 = data.read_float64() * 512; auto y3 = data.read_float64() * 512; auto z3 = data.read_float64() * 512; param.last_value = QVector3D(x3, y3, z3); param.default_value = QVector3D(0, 0, 0); break; } default: param.last_value = 0; param.default_value = 0; break; } } std::unique_ptr parse_effect_instance(Chunk chunk, const PropertyContext& context) { if ( !load_unecessary ) return {}; auto effect = std::make_unique(); Chunk fnam, tdgp; fnam = tdgp = nullptr; chunk->find_multiple({&fnam, &tdgp}, {"fnam", "tdgp"}); if ( fnam ) effect->name = to_string(fnam->child("Utf8")); parse_property_group(tdgp, effect->parameters, context); return effect; } static constexpr const char* const placeholder = "-_0_/-"; std::unordered_map comp_chunks; // For loading into the object model stuff Glaxnimate doesn't support // I'm adding the code to load them just in case someone wants to do // something else with them and so that if Glaxnimate ever supports given // features, it's easier to add const bool load_unecessary = false; ImportExport* io; }; } // namespace glaxnimate::io::aep mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/assets/asset_base.hpp000664 001750 001750 00000001237 14477652011 031524 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include "utils/pseudo_mutex.hpp" namespace glaxnimate::model { class ReferencePropertyBase; class DocumentNode; class AssetBase { public: using User = ReferencePropertyBase; virtual ~AssetBase() {} /** * \brief Removes the asset if it isn't needed * \param clean_lists when \b true, remove even if the asset is in a useful list * \return Whether it has been removed */ virtual bool remove_if_unused(bool clean_lists) = 0; }; } // namespace glaxnimate::model mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/rive/rive_format.hpp000664 001750 001750 00000002114 14477652011 030675 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include "io/base.hpp" #include "io/io_registry.hpp" namespace glaxnimate::io::rive { class RiveFormat : public ImportExport { Q_OBJECT public: static constexpr const int format_version = 7; QString slug() const override { return "rive"; } QString name() const override { return tr("Rive Animation"); } QStringList extensions() const override { return {"riv"}; } bool can_save() const override { return true; } bool can_open() const override { return true; } static RiveFormat* instance() { return autoreg.registered; } QJsonDocument to_json(const QByteArray& binary_data); protected: bool on_save(QIODevice& file, const QString&, model::Composition* comp, const QVariantMap&) override; bool on_open(QIODevice& file, const QString&, model::Document* document, const QVariantMap&) override; private: static Autoreg autoreg; }; } // namespace glaxnimate::io::rive mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/animation/animatable_path.cpp000664 001750 001750 00000010634 14477652011 033175 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "animatable_path.hpp" #include "command/undo_macro_guard.hpp" #include "command/animation_commands.hpp" using namespace glaxnimate; void glaxnimate::model::detail::AnimatedPropertyBezier::set_closed(bool closed) { value_.set_closed(closed); for ( auto& keyframe : keyframes_ ) { auto v = keyframe->get(); v.set_closed(closed); keyframe->set(v); } value_changed(); emitter(object(), value_); } void glaxnimate::model::detail::AnimatedPropertyBezier::split_segment(int index, qreal factor) { command::UndoMacroGuard guard(tr("Split Segment"), object()->document()); QVariant before = QVariant::fromValue(value_); auto bez = value_; bool set = true; for ( const auto& kf : keyframes_ ) { auto bez = kf->get(); bez.split_segment(index, factor); if ( !mismatched_ && kf->time() == time() ) set = false; object()->push_command(new command::SetKeyframe(this, kf->time(), QVariant::fromValue(bez), true)); } if ( set ) { bez.split_segment(index, factor); QVariant after = QVariant::fromValue(bez); object()->push_command(new command::SetMultipleAnimated("", {this}, {before}, {after}, true)); } } void glaxnimate::model::detail::AnimatedPropertyBezier::remove_point(int index) { remove_points({index}); } void glaxnimate::model::detail::AnimatedPropertyBezier::remove_points(const std::set& indices) { command::UndoMacroGuard guard(tr("Remove Nodes"), object()->document()); QVariant before = QVariant::fromValue(value_); auto bez = value_; bool set = true; for ( const auto& kf : keyframes_ ) { auto bez = kf->get().removed_points(indices); if ( !mismatched_ && kf->time() == time() ) set = false; object()->push_command(new command::SetKeyframe(this, kf->time(), QVariant::fromValue(bez), true)); } if ( set ) { bez = bez.removed_points(indices); object()->push_command(new command::SetMultipleAnimated(this, QVariant::fromValue(bez), true)); } } static QVariant extend_impl(math::bezier::Bezier subject, const math::bezier::Bezier& target, bool at_end) { if ( target.closed() ) { subject.set_closed(true); if ( !subject.empty() ) { if ( at_end ) subject[0].type = math::bezier::Corner; else subject.back().type = math::bezier::Corner; if ( !target.empty() ) { subject[0].tan_in = target[0].tan_in; subject.back().tan_out = target.back().tan_out; } } } if ( subject.size() < target.size() ) { if ( at_end ) { if ( !subject.empty() ) { subject.back().type = math::bezier::Corner; subject.back().tan_out = target.back().tan_out; } subject.points().insert( subject.points().end(), target.points().begin() + subject.size(), target.points().end() ); } else { if ( !subject.empty() ) { subject[0].type = math::bezier::Corner; subject[0].tan_in = target[0].tan_in; } subject.points().insert( subject.points().begin(), target.points().begin(), target.points().begin() + target.size() - subject.size() ); } } return QVariant::fromValue(subject); } void glaxnimate::model::detail::AnimatedPropertyBezier::extend(const math::bezier::Bezier& target, bool at_end) { command::UndoMacroGuard guard(tr("Extend Shape"), object()->document()); auto bez = value_; bool set = true; for ( auto& kf : keyframes_ ) { if ( !mismatched_ && kf->time() == time() ) set = false; object()->push_command( new command::SetKeyframe(this, kf->time(), extend_impl(kf->get(), target, at_end), true) ); } if ( set ) { QVariant before = QVariant::fromValue(bez); QVariant after = extend_impl(bez, target, at_end); object()->push_command(new command::SetMultipleAnimated("", {this}, {before}, {after}, true)); } } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/shapes/inflate_deflate.hpp000664 001750 001750 00000001321 14477652011 032474 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include "path_modifier.hpp" namespace glaxnimate::model { class InflateDeflate : public StaticOverrides { GLAXNIMATE_OBJECT(InflateDeflate) GLAXNIMATE_ANIMATABLE(float, amount, 0, {}, -1, 1, false, PropertyTraits::Percent) public: using Ctor::Ctor; static QIcon static_tree_icon(); static QString static_type_name_human(); math::bezier::MultiBezier process(FrameTime t, const math::bezier::MultiBezier& mbez) const override; protected: bool process_collected() const override; }; } // namespace glaxnimate::model mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/assets/embedded_font.cpp000664 001750 001750 00000003045 14477652011 032164 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "embedded_font.hpp" #include "model/document.hpp" #include "command/object_list_commands.hpp" #include "model/assets/assets.hpp" GLAXNIMATE_OBJECT_IMPL(glaxnimate::model::EmbeddedFont) glaxnimate::model::EmbeddedFont::EmbeddedFont(model::Document* document) : Asset(document) { } glaxnimate::model::EmbeddedFont::EmbeddedFont(model::Document* document, CustomFont custom_font) : Asset(document), custom_font_(std::move(custom_font)) { this->data.set(this->custom_font_.data()); this->source_url.set(this->custom_font_.source_url()); this->css_url.set(this->custom_font_.css_url()); } QIcon glaxnimate::model::EmbeddedFont::instance_icon() const { return QIcon::fromTheme("font"); } QString glaxnimate::model::EmbeddedFont::object_name() const { return custom_font_.family() + " " + custom_font_.style_name(); } QString glaxnimate::model::EmbeddedFont::type_name_human() const { return tr("Font"); } bool glaxnimate::model::EmbeddedFont::remove_if_unused(bool clean_lists) { /// \todo Needs a way to keep track users... if ( clean_lists && users().empty() ) { document()->push_command(new command::RemoveObject( this, &document()->assets()->fonts->values )); return true; } return false; } void glaxnimate::model::EmbeddedFont::on_data_changed() { custom_font_ = CustomFontDatabase::instance().add_font("", data.get()); } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/document.hpp000664 001750 001750 00000006245 14477652011 027733 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include #include "io/options.hpp" #include "model/comp_graph.hpp" #include "model/document_node.hpp" namespace glaxnimate::model { class Assets; struct PendingAsset; class Composition; class Document : public QObject { Q_OBJECT Q_PROPERTY(QString filename READ filename) Q_PROPERTY(double current_time READ current_time WRITE set_current_time NOTIFY current_time_changed) Q_PROPERTY(bool record_to_keyframe READ record_to_keyframe WRITE set_record_to_keyframe NOTIFY record_to_keyframe_changed) Q_PROPERTY(Object* assets READ assets_obj) Q_PROPERTY(QVariantMap metadata READ metadata WRITE set_metadata) public: struct DocumentInfo { QString author; QString description; QStringList keywords; bool empty() const { return author.isEmpty() && description.isEmpty() && keywords.empty(); } }; explicit Document(const QString& filename = {}); ~Document(); QString filename() const; QUuid uuid() const; QVariantMap& metadata(); void set_metadata(const QVariantMap& meta); DocumentInfo& info(); Composition* current_comp(); void set_current_comp(Composition* comp); QUndoStack& undo_stack(); const io::Options& io_options() const; void set_io_options(const io::Options& opt); Q_INVOKABLE glaxnimate::model::DocumentNode* find_by_uuid(const QUuid& n) const; Q_INVOKABLE glaxnimate::model::DocumentNode* find_by_name(const QString& name) const; Q_INVOKABLE QVariantList find_by_type_name(const QString& type_name) const; Q_INVOKABLE bool undo(); Q_INVOKABLE bool redo(); void push_command(QUndoCommand* cmd); FrameTime current_time() const; void set_current_time(FrameTime t); /** * \brief Whether animated values should add keyframes when their value changes */ bool record_to_keyframe() const; void set_record_to_keyframe(bool r); Q_INVOKABLE QString get_best_name(glaxnimate::model::DocumentNode* node, const QString& suggestion={}) const; Q_INVOKABLE void set_best_name(glaxnimate::model::DocumentNode* node, const QString& suggestion={}) const; model::Assets* assets() const; model::CompGraph& comp_graph(); void stretch_time(qreal multiplier); int add_pending_asset(const QString& name, const QUrl& url); int add_pending_asset(const QString& name, const QByteArray& data); int add_pending_asset(const model::PendingAsset& asset); std::vector pending_assets(); void mark_asset_loaded(int pending_id); void clear_pending_assets(); signals: void filename_changed(const QString& n); void current_time_changed(FrameTime t); void record_to_keyframe_changed(bool r); void graphics_invalidated(); private: Object* assets_obj() const; void decrease_node_name(const QString& old_name); void increase_node_name(const QString& new_name); private: class Private; friend DocumentNode; std::unique_ptr d; }; } // namespace glaxnimate::model mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/animation/frame_time.hpp000664 001750 001750 00000000354 14477652011 032177 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once namespace glaxnimate::model { using FrameTime = double; } // namespace glaxnimate::model mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/shapes/text.cpp000664 001750 001750 00000040521 14477652011 030352 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "text.hpp" #include #include #include #include "group.hpp" #include "path.hpp" #include "command/undo_macro_guard.hpp" #include "model/assets/assets.hpp" #include "model/custom_font.hpp" #include "math/bezier/bezier_length.hpp" GLAXNIMATE_OBJECT_IMPL(glaxnimate::model::Font) GLAXNIMATE_OBJECT_IMPL(glaxnimate::model::TextShape) class glaxnimate::model::Font::Private { public: QStringList styles; QFont query; QRawFont raw; QRawFont raw_scaled; QFontMetricsF metrics; QFontDatabase database; Private() : raw(QRawFont::fromFont(query)), metrics(query) { #ifdef Q_OS_ANDROID query.setPointSizeF(32); #endif // query.setKerning(false); upscaled_raw(); } void update_data() { // disable kerning because QRawFont doesn't handle kerning properly // query.setKerning(false); raw = QRawFont::fromFont(query); // Dynamic fonts might have weird names if ( !raw.familyName().startsWith(query.family()) ) { QString family = query.family(); QFont new_query = query; new_query.setFamily(family + ' ' + query.styleName()); auto new_raw = QRawFont::fromFont(new_query); if ( new_raw.familyName().startsWith(family) ) { query = new_query; raw = new_raw; } } metrics = QFontMetricsF(query); upscaled_raw(); } static const QStringList& default_styles() { static QStringList styles; if ( styles.empty() ) { auto meta = QMetaEnum::fromType(); for ( int i = 0; i < meta.keyCount(); i++ ) { QString key = meta.key(i); for ( const char* style : {"", " Italic", " Oblique"} ) { styles.push_back(key + style); } } } return styles; } void refresh_styles(Font* parent) { if ( !raw.familyName().startsWith(query.family()) ) { styles = default_styles(); } else { styles = database.styles(parent->family.get()); if ( !parent->valid_style(parent->style.get()) && !styles.empty() ) parent->style.set(styles[0]); } } // QRawFont::pathForGlyph doesn't work well, so we work around it void upscaled_raw() { QFont font = query; #ifndef Q_OS_ANDROID font.setPointSizeF(qMin(4000., font.pointSizeF() * 1000)); #endif raw_scaled = QRawFont::fromFont(font); } QPainterPath path_for_glyph(quint32 glyph, bool fix_paint) { QPainterPath path = raw_scaled.pathForGlyph(glyph); if ( fix_paint ) path = path.simplified(); if ( raw_scaled.pixelSize() == 0 ) return path; QPainterPath dest; qreal mult = raw.pixelSize() / raw_scaled.pixelSize(); std::array data; int data_i = 0; for ( int i = 0; i < path.elementCount(); i++ ) { auto element = path.elementAt(i); QPointF p = QPointF(element) * mult; switch ( element.type ) { case QPainterPath::MoveToElement: dest.moveTo(p); break; case QPainterPath::LineToElement: dest.lineTo(p); break; case QPainterPath::CurveToElement: data_i = 0; data[0] = p; break; case QPainterPath::CurveToDataElement: ++data_i; data[data_i] = p; if ( data_i == 2 ) { dest.cubicTo(data[0], data[1], data[2]); data_i = -1; } break; } } return dest; } }; glaxnimate::model::Font::Font(glaxnimate::model::Document* doc) : Object(doc), d(std::make_unique()) { family.set(d->raw.familyName()); style.set(d->raw.styleName()); size.set(d->query.pointSize()); d->refresh_styles(this); on_transfer(doc); } glaxnimate::model::Font::~Font() = default; void glaxnimate::model::Font::refresh_data ( bool update_styles ) { d->query = CustomFontDatabase::instance().font(family.get(), style.get(), size.get()); d->update_data(); if ( update_styles ) d->refresh_styles(this); emit font_changed(); } void glaxnimate::model::Font::on_font_changed() { refresh_data(false); } void glaxnimate::model::Font::on_transfer ( model::Document* doc ) { if ( document() ) disconnect(document()->assets()->fonts.get(), nullptr, this, nullptr); if ( doc ) { connect(doc->assets()->fonts.get(), &FontList::font_added, this, [this]{ refresh_data(true); document()->graphics_invalidated(); }); } } void glaxnimate::model::Font::on_family_changed() { refresh_data(true); } bool glaxnimate::model::Font::valid_style(const QString& style) { return d->styles.contains(style); } const QFont & glaxnimate::model::Font::query() const { return d->query; } const QRawFont & glaxnimate::model::Font::raw_font() const { return d->raw; } QStringList glaxnimate::model::Font::styles() const { return d->styles; } const QFontMetricsF & glaxnimate::model::Font::metrics() const { return d->metrics; } QString glaxnimate::model::Font::type_name_human() const { return tr("Font"); } QPainterPath glaxnimate::model::Font::path_for_glyph(quint32 glyph, glaxnimate::model::Font::CharDataCache& cache, bool fix_paint) const { auto it = cache.find(glyph); if ( it != cache.end() ) return it->second; QPainterPath path = d->path_for_glyph(glyph, fix_paint); cache.emplace(glyph, path); return path; } void glaxnimate::model::Font::from_qfont(const QFont& f) { command::UndoMacroGuard g(tr("Change Font"), document()); QFontInfo finfo(f); family.set_undoable(finfo.family()); style.set_undoable(finfo.styleName()); size.set_undoable(f.pointSizeF()); } glaxnimate::model::Font::ParagraphData glaxnimate::model::Font::layout(const QString& text) const { glaxnimate::model::Font::ParagraphData para_data; auto lines = text.split('\n'); QTextLayout layout(text, d->query, nullptr); QTextOption option; option.setUseDesignMetrics(true); layout.setTextOption(option); layout.beginLayout(); for ( const auto& line_size : lines ) { QTextLine line = layout.createLine(); if ( !line.isValid() ) break; line.setNumColumns(line_size.size()); line.setLeadingIncluded(true); } layout.endLayout(); qreal line_y = 0; qreal yoff = -d->metrics.ascent(); for ( int ln = 0; ln < layout.lineCount(); ln++ ) { QTextLine line = layout.lineAt(ln); auto& line_data = para_data.emplace_back(); line_data.baseline = QPointF(0, line_y); line_data.bounds = line.rect(); line_data.text = lines[ln]; QPointF baseline(0, line_y + yoff); for ( const auto& run : line.glyphRuns() ) { auto glyphs = run.glyphIndexes(); line_data.glyphs.reserve(line_data.glyphs.size() + glyphs.size()); auto positions = run.positions(); for ( int i = 0; i < glyphs.size(); i++ ) { line_data.glyphs.push_back({ glyphs[i], positions[i] + baseline }); } } line_data.advance = QPointF(line.cursorToX(lines[ln].size()), 0); line_y += line_spacing(); } // QRawFont way: for some reason it ignores KernedAdvances /* qreal line_y = 0; for ( const auto& line : text.split('\n') ) { auto glyphs = d->raw.glyphIndexesForString(line); auto advances = d->raw.advancesForGlyphIndexes(glyphs, QRawFont::UseDesignMetrics|QRawFont::KernedAdvances); auto& line_data = para_data.emplace_back(); line_data.glyphs.reserve(glyphs.size()); line_data.text = line; line_data.baseline = line_data.advance = QPointF(0, line_y); for ( int i = 0; i < glyphs.size(); i++ ) { line_data.glyphs.push_back({ glyphs[i], line_data.advance, }); line_data.advance += advances[i]; } line_y += line_spacing(); } */ return para_data; } qreal glaxnimate::model::Font::line_spacing() const { // for some reason QTextLayout ignores leading() return line_spacing_unscaled() * line_height.get(); } qreal glaxnimate::model::Font::line_spacing_unscaled() const { // for some reason QTextLayout ignores leading() return d->metrics.ascent() + d->metrics.descent(); } QStringList glaxnimate::model::Font::families() const { return d->database.families(); } QList glaxnimate::model::Font::standard_sizes() const { auto list = QFontDatabase::standardSizes(); int actual = d->query.pointSize(); auto it = std::upper_bound(list.begin(), list.end(), actual); if ( it == list.begin() || *(it-1) != actual ) list.insert(it, actual); return list; } glaxnimate::model::TextShape::TextShape(glaxnimate::model::Document* document) : ShapeElement(document) { connect(font.get(), &Font::font_changed, this, &TextShape::on_font_changed); } void glaxnimate::model::TextShape::on_text_changed() { #if QT_VERSION >= QT_VERSION_CHECK(5, 13, 0) shape_cache.clear(); #else shape_cache = QPainterPath(); #endif propagate_bounding_rect_changed(); } void glaxnimate::model::TextShape::on_font_changed() { cache.clear(); on_text_changed(); } const QPainterPath & glaxnimate::model::TextShape::untranslated_path(FrameTime t) const { if ( shape_cache.isEmpty() ) { if ( path.get() ) { QString txt = text.get(); txt.replace('\n', ' '); auto bezier = path->shapes(t); const int length_steps = 5; math::bezier::LengthData length_data(bezier, length_steps); for ( const auto& line : font->layout(txt) ) { for ( const auto& glyph : line.glyphs ) { qreal x = path_offset.get_at(t) + glyph.position.x(); if ( x > length_data.length() || x < 0 ) continue; auto glyph_shape = font->path_for_glyph(glyph.glyph, cache, true); auto glyph_rect = glyph_shape.boundingRect(); auto start1 = length_data.at_length(x); auto start2 = start1.descend(); auto start_p = bezier.beziers()[start1.index].split_segment_point(start2.index, start2.ratio); auto end1 = length_data.at_length(x + glyph_rect.width()); auto end2 = end1.descend(); auto end_p = bezier.beziers()[end1.index].split_segment_point(end2.index, end2.ratio); QTransform mat; mat.translate(start_p.pos.x(), start_p.pos.y()); mat.rotate(qRadiansToDegrees(math::atan2(end_p.pos.y() - start_p.pos.y(), end_p.pos.x() - start_p.pos.x()))); shape_cache += mat.map(glyph_shape); } } } else { for ( const auto& line : font->layout(text.get()) ) for ( const auto& glyph : line.glyphs ) shape_cache += font->path_for_glyph(glyph.glyph, cache, true).translated(glyph.position); } } return shape_cache; } void glaxnimate::model::TextShape::add_shapes(glaxnimate::model::FrameTime t, math::bezier::MultiBezier& bez, const QTransform& transform) const { if ( !transform.isIdentity() ) { auto mb = math::bezier::MultiBezier::from_painter_path(shape_data(t)); mb.transform(transform); bez.append(mb); } else { bez.append(shape_data(t)); } } QPainterPath glaxnimate::model::TextShape::to_painter_path_impl(glaxnimate::model::FrameTime) const { return {}; } QPainterPath glaxnimate::model::TextShape::shape_data(FrameTime t) const { // Ignore position if we have a path, it can still be moved from the group if ( path.get() ) return untranslated_path(t); QPointF pos = position.get_at(t); return untranslated_path(t).translated(pos); } QIcon glaxnimate::model::TextShape::tree_icon() const { return QIcon::fromTheme("font"); } QRectF glaxnimate::model::TextShape::local_bounding_rect(glaxnimate::model::FrameTime t) const { return shape_data(t).boundingRect(); } QString glaxnimate::model::TextShape::type_name_human() const { return tr("Text"); } std::unique_ptr glaxnimate::model::TextShape::to_path() const { auto group = std::make_unique(document()); group->name.set(name.get()); group->group_color.set(group_color.get()); group->visible.set(visible.get()); Font::CharDataCache local_cache; for ( const auto& line : font->layout(text.get()) ) { auto line_group = std::make_unique(document()); line_group->name.set(line.text); for ( const auto& glyph : line.glyphs ) { QPainterPath p = font->path_for_glyph(glyph.glyph, local_cache, false).translated(glyph.position); math::bezier::MultiBezier bez; bez.append(p); if ( bez.beziers().size() == 1 ) { auto path = std::make_unique(document()); path->shape.set(bez.beziers()[0]); line_group->shapes.insert(std::move(path), 0); } else if ( bez.beziers().size() > 1 ) { auto glyph_group = std::make_unique(document()); for ( const auto& sub : bez.beziers() ) { auto path = std::make_unique(document()); path->shape.set(sub); glyph_group->shapes.insert(std::move(path), 0); } line_group->shapes.insert(std::move(glyph_group), 0); } } group->shapes.insert(std::move(line_group), 0); } group->set_time(time()); if ( position.animated() ) { for ( const auto& kf : position ) { group->transform->position.set_keyframe(kf.time(), kf.get())->set_transition(kf.transition()); // group->transform->anchor_point.set_keyframe(kf.time(), kf.get())->set_transition(kf.transition()); } } group->transform->position.set(position.get()); // group->transform->anchor_point.set(position.get()); return group; } QPointF glaxnimate::model::TextShape::offset_to_next_character() const { auto layout = font->layout(text.get()); if ( layout.empty() ) return {}; return layout.back().advance; } std::vector glaxnimate::model::TextShape::valid_paths() const { std::vector shapes; shapes.push_back(nullptr); for ( const auto& sib : *owner() ) if ( sib.get() != this ) shapes.push_back(sib.get()); return shapes; } bool glaxnimate::model::TextShape::is_valid_path(glaxnimate::model::DocumentNode* node) const { if ( node == nullptr ) return true; if ( node == this ) return false; if ( auto shape = node->cast() ) return shape->owner_composition() == owner_composition(); return false; } void glaxnimate::model::TextShape::path_changed(glaxnimate::model::ShapeElement* new_path, glaxnimate::model::ShapeElement* old_path) { on_text_changed(); if ( old_path ) disconnect(old_path, nullptr, this, nullptr); if ( new_path ) { connect(new_path, &Object::visual_property_changed, this, &TextShape::on_text_changed); connect(new_path, &VisualNode::bounding_rect_changed, this, &TextShape::on_text_changed); } } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/comp_graph.cpp000664 001750 001750 00000010753 14477652011 030226 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "comp_graph.hpp" #include #include #include #include "model/document.hpp" #include "model/assets/assets.hpp" #include "model/shapes/precomp_layer.hpp" void glaxnimate::model::CompGraph::add_composition(glaxnimate::model::Composition* comp) { std::vector& comp_layers = layers[comp]; std::deque nodes(comp->docnode_children().begin(), comp->docnode_children().end()); while ( !nodes.empty() ) { auto front = nodes.front(); nodes.pop_front(); if ( auto layer = front->cast() ) comp_layers.push_back(layer); else nodes.insert(nodes.end(), front->docnode_children().begin(), front->docnode_children().end()); } } void glaxnimate::model::CompGraph::remove_composition(glaxnimate::model::Composition* comp) { layers.erase(comp); } bool glaxnimate::model::CompGraph::is_ancestor_of(glaxnimate::model::Composition* ancestor, glaxnimate::model::Composition* descendant) const { std::unordered_set checked; std::unordered_set not_checked; not_checked.insert(ancestor); while ( !not_checked.empty() ) { std::unordered_set next; for ( glaxnimate::model::Composition* comp : not_checked ) { if ( comp == descendant ) return true; auto it = layers.find(comp); if ( it == layers.end() ) continue; for ( auto layer : layers.at(comp) ) { auto laycomp = layer->composition.get(); if ( laycomp && !checked.count(laycomp) ) next.insert(laycomp); } checked.insert(comp); } not_checked = std::move(next); } return false; } std::vector glaxnimate::model::CompGraph::children(glaxnimate::model::Composition* comp) const { std::unordered_set vals; for ( auto layer : layers.at(comp) ) { if ( auto laycomp = layer->composition.get() ) vals.insert(laycomp); } return std::vector(vals.begin(), vals.end()); } static bool recursive_is_ancestor_of( glaxnimate::model::Composition* ancestor, glaxnimate::model::Composition* descendant, std::unordered_map& cache, const std::unordered_map>& layers ) { if ( ancestor == descendant ) return cache[ancestor] = true; auto it = cache.find(ancestor); if ( it != cache.end() ) return it->second; int is_ancestor = 0; for ( auto layer : layers.at(ancestor) ) { if ( auto laycomp = layer->composition.get() ) is_ancestor += recursive_is_ancestor_of(laycomp, descendant, cache, layers); } return cache[ancestor] = is_ancestor; } std::vector glaxnimate::model::CompGraph::possible_descendants(glaxnimate::model::Composition* ancestor, glaxnimate::model::Document* document) const { std::unordered_map cache; std::vector valid; for ( const auto& precomp : document->assets()->compositions->values ) { if ( !recursive_is_ancestor_of(precomp.get(), ancestor, cache, layers) ) valid.push_back(precomp.get()); } return valid; } void glaxnimate::model::CompGraph::add_connection(glaxnimate::model::Composition* comp, glaxnimate::model::PreCompLayer* layer) { auto it = layers.find(comp); if ( it != layers.end() ) it->second.push_back(layer); } void glaxnimate::model::CompGraph::remove_connection(glaxnimate::model::Composition* comp, glaxnimate::model::PreCompLayer* layer) { auto it_map = layers.find(comp); if ( it_map != layers.end() ) { auto it_v = std::find(it_map->second.begin(), it_map->second.end(), layer); if ( it_v != it_map->second.end() ) { if ( it_v != it_map->second.end() - 1 ) std::swap(*it_v, it_map->second.back()); it_map->second.pop_back(); } } } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/glaxnimate/glaxnimate_mime.hpp000664 001750 001750 00000001734 14477652011 032713 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include #include "io/mime/mime_serializer.hpp" #include "glaxnimate_format.hpp" namespace glaxnimate::io::glaxnimate { class GlaxnimateMime : public io::mime::MimeSerializer { public: QString slug() const override { return "glaxnimate"; } QString name() const override { return GlaxnimateFormat::tr("Glaxnimate Animation"); } QStringList mime_types() const override; QByteArray serialize(const std::vector& objects) const override; io::mime::DeserializedData deserialize(const QByteArray& data) const override; bool can_deserialize() const override { return true; } static QJsonDocument serialize_json(const std::vector& objects); private: static Autoreg autoreg; }; } // namespace glaxnimate::io::glaxnimate mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/rive/type_def.hpp000664 001750 001750 00000002000 14477652011 030151 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include #include #include #include "type_ids.hpp" #include "io/binary_types.hpp" namespace glaxnimate::io::rive { using Identifier = VarUint; enum class PropertyType { VarUint = 0, // LEB128 Uint Bool = 1, // Byte String = 2, // length(LEB128 Uint) + Utf8 Bytes = 3, // length(LEB128 Uint) + Data Float = 4, // Float32 Color = 5, // Uint32 }; using PropertyTable = std::unordered_map; struct Property { QString name; Identifier id = 0; PropertyType type = PropertyType::VarUint; }; struct ObjectDefinition { QString name; TypeId type_id = TypeId::NoType; TypeId extends = TypeId::NoType; std::vector properties; }; extern std::unordered_map defined_objects; } // namespace glaxnimate::io::rive mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/svg/svg_parser_private.hpp000664 001750 001750 00000025666 14477652011 032140 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include "utils/regexp.hpp" #include "utils/sort_gradient.hpp" #include "model/shapes/group.hpp" #include "model/shapes/layer.hpp" #include "model/shapes/precomp_layer.hpp" #include "model/shapes/rect.hpp" #include "model/shapes/ellipse.hpp" #include "model/shapes/path.hpp" #include "model/shapes/polystar.hpp" #include "model/shapes/fill.hpp" #include "model/shapes/stroke.hpp" #include "model/shapes/image.hpp" #include "model/shapes/text.hpp" #include "model/document.hpp" #include "model/assets/named_color.hpp" #include "math/math.hpp" #include "app/utils/string_view.hpp" #include "path_parser.hpp" #include "animate_parser.hpp" #include "parse_error.hpp" #include "font_weight.hpp" #include "css_parser.hpp" #include "detail.hpp" namespace glaxnimate::io::svg::detail { class SvgParserPrivate { public: using ShapeCollection = std::vector>; struct ParseFuncArgs { const QDomElement& element; model::ShapeListProperty* shape_parent; const Style& parent_style; bool in_group; }; SvgParserPrivate( model::Document* document, const std::function& on_warning, ImportExport* io, QSize forced_size, model::FrameTime default_time ) : document(document), on_warning(on_warning), io(io), forced_size(forced_size), default_time(default_time == 0 ? 180 : default_time) { animate_parser.on_warning = on_warning; } void load(QIODevice* device) { SvgParseError err; if ( !dom.setContent(device, true, &err.message, &err.line, &err.column) ) throw err; } virtual ~SvgParserPrivate() = default; void parse(model::Document* document = nullptr) { if ( document ) this->document = document; if ( this->document->assets()->compositions->values.empty() ) main = this->document->assets()->compositions->values.insert(std::make_unique(this->document)); else main = this->document->assets()->compositions->values[0]; animate_parser.fps = main->fps.get(); size = main->size(); auto root = dom.documentElement(); if ( forced_size.isValid() ) { size = forced_size; } else { size = get_size(root); } to_process = 0; on_parse_prepare(root); if ( io ) io->progress_max_changed(to_process); on_parse(root); write_document_data(); } protected: QString attr(const QDomElement& e, const QString& ns, const QString& name, const QString& defval = {}) { if ( ns.isEmpty() ) return e.attribute(name, defval); return e.attributeNS(xmlns.at(ns), name, defval); } qreal len_attr(const QDomElement& e, const QString& name, qreal defval = 0) { if ( e.hasAttribute(name) ) return parse_unit(e.attribute(name)); return defval; } qreal parse_unit(const QString& svg_value) { QRegularExpressionMatch match = unit_re.match(svg_value); if ( match.hasMatch() ) { qreal value = match.captured(1).toDouble(); qreal mult = unit_multiplier(match.captured(2)); if ( mult != 0 ) return value * mult; } warning(QString("Unknown length value %1").arg(svg_value)); return 0; } qreal unit_multiplier(const QString& unit) { static const constexpr qreal cmin = 2.54; if ( unit == "px" || unit == "" || unit == "dp" || unit == "dip" || unit == "sp" ) return 1; else if ( unit == "vw" ) return size.width() * 0.01; else if ( unit == "vh" ) return size.height() * 0.01; else if ( unit == "vmin" ) return std::min(size.width(), size.height()) * 0.01; else if ( unit == "vmax" ) return std::max(size.width(), size.height()) * 0.01; else if ( unit == "in" ) return dpi; else if ( unit == "pc" ) return dpi / 6; else if ( unit == "pt" ) return dpi / 72; else if ( unit == "cm" ) return dpi / cmin; else if ( unit == "mm" ) return dpi / cmin / 10; else if ( unit == "Q" ) return dpi / cmin / 40; return 0; } qreal unit_convert(qreal val, const QString& from, const QString& to) { return val * unit_multiplier(from) / unit_multiplier(to); } void warning(const QString& msg) { if ( on_warning ) on_warning(msg); } model::Layer* add_layer(model::ShapeListProperty* parent) { model::Layer* lay = new model::Layer(document); parent->insert(std::unique_ptr(lay)); layers.push_back(lay); return lay; } QStringList split_attr(const QDomElement& e, const QString& name) { return e.attribute(name).split(AnimateParser::separator, #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) Qt::SkipEmptyParts #else QString::SkipEmptyParts #endif ); } void parse_children(const ParseFuncArgs& args) { for ( const auto& domnode : ItemCountRange(args.element.childNodes()) ) { if ( domnode.isElement() ) { auto child = domnode.toElement(); parse_shape({child, args.shape_parent, args.parent_style, args.in_group}); } } } template T* push(ShapeCollection& sc) { T* t = new T(document); sc.emplace_back(t); return t; } void populate_ids(const QDomElement& elem) { if ( elem.hasAttribute("id") ) map_ids[elem.attribute("id")] = elem; for ( const auto& domnode : ItemCountRange(elem.childNodes()) ) { if ( domnode.isElement() ) populate_ids(domnode.toElement()); } } QDomElement element_by_id(const QString& id) { // dom.elementById() doesn't work ;_; if ( map_ids.empty() ) populate_ids(dom.documentElement()); auto it = map_ids.find(id); if ( it == map_ids.end() ) return {}; return it->second; } void mark_progress() { processed++; if ( io && processed % 10 == 0 ) io->progress(processed); } QDomElement query_element(const std::vector& path, const QDomElement& parent, std::size_t index = 0) { if ( index >= path.size() ) return parent; auto head = path[index]; for ( const auto& domnode : ItemCountRange(parent.childNodes()) ) { if ( domnode.isElement() ) { auto child = domnode.toElement(); if ( child.tagName() == head ) return query_element(path, child, index+1); } } return {}; } QString query(const std::vector& path, const QDomElement& parent, std::size_t index = 0) { return query_element(path, parent, index).text(); } std::vector double_args(const QString& str) { auto args_s = ::utils::split_ref(str, AnimateParser::separator, #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) Qt::SkipEmptyParts #else QString::SkipEmptyParts #endif ); std::vector args; args.reserve(args_s.size()); std::transform(args_s.begin(), args_s.end(), std::back_inserter(args), [](const ::utils::StringView& s){ return s.toDouble(); }); return args; } // parse attributes like opacity where it's a value in [0-1] or a percentage static double percent_1(const QString& s) { if ( s.contains('%') ) return ::utils::mid_ref(s, 0, s.size()-1).toDouble() / 100; return s.toDouble(); } static model::Stroke::Cap line_cap(const QString& linecap) { if ( linecap == "round" ) return model::Stroke::RoundCap; else if ( linecap == "butt" ) return model::Stroke::ButtCap; else if ( linecap == "square" ) return model::Stroke::SquareCap; return model::Stroke::ButtCap; } static model::Stroke::Join line_join(const QString& linecap) { if ( linecap == "round" ) return model::Stroke::RoundJoin; else if ( linecap == "bevel" ) return model::Stroke::BevelJoin; else if ( linecap == "miter" ) return model::Stroke::MiterJoin; return model::Stroke::MiterJoin; } void path_animation( const std::vector& paths, const AnimatedProperties& anim, const QString& attr ) { if ( !paths.empty() ) { for ( const auto& kf : anim.single(attr) ) { for ( int i = 0; i < math::min(kf.values.bezier().size(), paths.size()); i++ ) paths[i]->shape.set_keyframe(kf.time, kf.values.bezier()[i])->set_transition(kf.transition); } } } private: void write_document_data() { main->width.set(size.width()); main->height.set(size.height()); if ( !animate_parser.kf_range_initialized ) { animate_parser.min_kf = 0; animate_parser.max_kf = default_time; } else { animate_parser.max_kf = qRound(animate_parser.max_kf); } main->animation->first_frame.set(animate_parser.min_kf); main->animation->last_frame.set(animate_parser.max_kf); for ( auto lay : layers ) { lay->animation->last_frame.set(animate_parser.min_kf); lay->animation->last_frame.set(animate_parser.max_kf); } document->undo_stack().clear(); } protected: virtual void on_parse_prepare(const QDomElement& root) = 0; virtual QSizeF get_size(const QDomElement& root) = 0; virtual void parse_shape(const ParseFuncArgs& args) = 0; virtual void on_parse(const QDomElement& root) = 0; QDomDocument dom; qreal dpi = 96; QSizeF size; model::Document* document; AnimateParser animate_parser; std::function on_warning; std::unordered_map map_ids; std::unordered_map brush_styles; std::unordered_map gradients; std::vector layers; int to_process = 0; int processed = 0; ImportExport* io = nullptr; QSize forced_size; model::FrameTime default_time; model::Composition* main = nullptr; static const QRegularExpression unit_re; }; } // namespace glaxnimate::io::svg::detail src/modules/glaxnimate/glaxnimate/external/QtAppSetup/src/app/widgets/keyboard_settings_widget.ui000664 001750 001750 00000003741 14477652011 037033 0ustar00ddennedyddennedy000000 000000 mlt-7.22.0 KeyboardSettingsWidget 0 0 400 300 Filter Clear Filter Clear Filter toolButton clicked() KeyboardSettingsWidget clear_filter() 364 20 515 80 filter textChanged(QString) KeyboardSettingsWidget filter(QString) 216 27 429 177 clear_filter() filter(QString) mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/property/property.hpp000664 001750 001750 00000024505 14477652011 031664 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include #include #include #include #include #include #include #include #include #include #include "model/animation/frame_time.hpp" namespace glaxnimate::math::bezier { class Bezier; } namespace glaxnimate::model { class Object; class Document; struct PropertyTraits { enum Type { Unknown, Object, ObjectReference, Bool, Int, Float, Point, Color, Size, Scale, String, Enum, Uuid, Bezier, Data, Gradient, }; enum Flags { NoFlags = 0x00, List = 0x01, ///< list/array of values ReadOnly = 0x02, ///< not modifiable by GUI Animated = 0x04, ///< animated Visual = 0x08, ///< has visible effects OptionList = 0x10, ///< has a set of valid values Percent = 0x20, ///< for Float, show as percentage on the GUI Hidden = 0x40, ///< for Visual, not shown prominently }; Type type = Unknown; int flags = NoFlags; bool is_object() const { return type == Object || type == ObjectReference; } template static constexpr Type get_type() noexcept; template static PropertyTraits from_scalar(int flags=NoFlags) { return { get_type(), flags }; } }; namespace detail { template struct GetType; template static constexpr bool is_object_v = std::is_base_of_v || std::is_same_v; // template // struct GetType>> // { // static constexpr const PropertyTraits::Type value = PropertyTraits::ObjectReference; // }; template struct GetType, std::enable_if_t>> { static constexpr const PropertyTraits::Type value = PropertyTraits::Object; }; template<> struct GetType { static constexpr const PropertyTraits::Type value = PropertyTraits::Bool; }; template<> struct GetType { static constexpr const PropertyTraits::Type value = PropertyTraits::Float; }; template<> struct GetType { static constexpr const PropertyTraits::Type value = PropertyTraits::Scale; }; template<> struct GetType { static constexpr const PropertyTraits::Type value = PropertyTraits::Color; }; template<> struct GetType { static constexpr const PropertyTraits::Type value = PropertyTraits::Size; }; template<> struct GetType { static constexpr const PropertyTraits::Type value = PropertyTraits::String; }; template<> struct GetType { static constexpr const PropertyTraits::Type value = PropertyTraits::Uuid; }; template<> struct GetType { static constexpr const PropertyTraits::Type value = PropertyTraits::Point; }; template<> struct GetType { static constexpr const PropertyTraits::Type value = PropertyTraits::Bezier; }; template<> struct GetType { static constexpr const PropertyTraits::Type value = PropertyTraits::Data; }; template<> struct GetType { static constexpr const PropertyTraits::Type value = PropertyTraits::Gradient; }; template struct GetType>> { static constexpr const PropertyTraits::Type value = PropertyTraits::Int; }; template struct GetType>> { static constexpr const PropertyTraits::Type value = PropertyTraits::Enum; }; } // namespace detail template inline constexpr PropertyTraits::Type PropertyTraits::get_type() noexcept { return detail::GetType::value; } #define GLAXNIMATE_PROPERTY_IMPL(type, name) \ public: \ type get_##name() const { return name.get(); } \ bool set_##name(const type& v) { \ return name.set_undoable(QVariant::fromValue(v)); \ } \ private: \ Q_PROPERTY(type name READ get_##name WRITE set_##name) \ Q_CLASSINFO(#name, "property " #type) \ // macro end #define GLAXNIMATE_PROPERTY(type, name, ...) \ public: \ Property name{this, #name, __VA_ARGS__}; \ GLAXNIMATE_PROPERTY_IMPL(type, name) \ // macro end #define GLAXNIMATE_PROPERTY_RO(type, name, default_value) \ public: \ Property name{this, #name, default_value, {}, {}, PropertyTraits::ReadOnly}; \ type get_##name() const { return name.get(); } \ private: \ Q_PROPERTY(type name READ get_##name) \ Q_CLASSINFO(#name, "property " #type) \ // macro end class BaseProperty { Q_GADGET public: BaseProperty(Object* object, const QString& name, PropertyTraits traits); virtual ~BaseProperty() = default; virtual QVariant value() const = 0; virtual bool set_value(const QVariant& val) = 0; virtual bool set_undoable(const QVariant& val, bool commit = true); virtual void set_time(FrameTime t) = 0; virtual void transfer(Document*) {}; virtual bool valid_value(const QVariant& v) const = 0; virtual bool assign_from(const BaseProperty* prop) { return set_value(prop->value()); } /** * \brief Stretches animations by the given amount */ virtual void stretch_time(qreal multiplier) { Q_UNUSED(multiplier); } const QString& name() const { return name_; } PropertyTraits traits() const { return traits_; } Object* object() const { return object_; } protected: void value_changed(); private: Object* object_; QString name_; PropertyTraits traits_; }; namespace detail { template inline T defval() { return T(); } template<> inline void defval() {} template auto invoke_impl(const FuncT& fun, std::index_sequence, const std::tuple& args) { return fun(std::get(args)...); } template auto invoke(const FuncT& fun, const Args&... t) { return invoke_impl(fun, std::make_index_sequence(), std::make_tuple(t...)); } } // namespace detail template class PropertyCallback { private: class HolderBase { public: virtual ~HolderBase() = default; virtual Return invoke(Object* obj, const ArgType&... v) const = 0; }; template class Holder : public HolderBase { public: using FuncP = std::function; Holder(FuncP func) : func(std::move(func)) {} Return invoke(Object* obj, const ArgType&... v) const override { return detail::invoke(func, static_cast(obj), v...); } FuncP func; }; std::unique_ptr holder; public: PropertyCallback() = default; PropertyCallback(std::nullptr_t) {} template PropertyCallback(Return (ObjT::*func)(Arg...)) : holder(std::make_unique>(func)) {} template PropertyCallback(Return (ObjT::*func)(Arg...) const) : holder(std::make_unique>(func)) {} explicit operator bool() const { return bool(holder); } Return operator() (Object* obj, const ArgType&... v) const { if ( holder ) return holder->invoke(obj, v...); return detail::defval(); } }; namespace detail { template std::optional variant_cast(const QVariant& val) { if ( !val.canConvert() ) return {}; QVariant converted = val; #if QT_VERSION_MAJOR < 6 if ( !converted.convert(qMetaTypeId()) ) #else if ( !converted.convert(QMetaType::fromType()) ) #endif return {}; return converted.value(); } template class PropertyTemplate : public Base { public: using value_type = Type; using reference = const Type&; PropertyTemplate(Object* obj, const QString& name, Type default_value = Type(), PropertyCallback emitter = {}, PropertyCallback validator = {}, int flags = PropertyTraits::NoFlags ) : Base(obj, name, PropertyTraits::from_scalar(flags)), value_(std::move(default_value)), emitter(std::move(emitter)), validator(std::move(validator)) {} bool valid_value(const QVariant& val) const override { if ( auto v = detail::variant_cast(val) ) return !validator || validator(this->object(), *v); return false; } bool set(Type value) { if ( validator && !validator(this->object(), value) ) return false; std::swap(value_, value); this->value_changed(); if ( emitter ) emitter(this->object(), value_, value); return true; } reference get() const { return value_; } QVariant value() const override { return QVariant::fromValue(value_); } bool set_value(const QVariant& val) override { if ( auto v = detail::variant_cast(val) ) return set(*v); return false; } void set_time(FrameTime) override {} private: Type value_; PropertyCallback emitter; PropertyCallback validator; }; } // namespace detail template class Property : public detail::PropertyTemplate { public: using detail::PropertyTemplate::PropertyTemplate; }; } // namespace glaxnimate::model mlt-7.22.0/src/modules/glaxnimate/glaxnimate/external/QtAppSetup/src/app/application.hpp000664 001750 001750 00000004202 14477652011 033027 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include #include #include "app/settings/settings.hpp" #include "app/translation_service.hpp" namespace app { class Application : public QApplication { Q_OBJECT public: using QApplication::QApplication; virtual QSettings qsettings() const; void initialize(); void finalize() { app::settings::Settings::instance().save(); } static Application* instance() { return static_cast(QCoreApplication::instance()); } /** * \brief A path to write user preferences into * \param name Name of the data subdirectory */ QString writable_data_path(const QString& name) const; /** * \brief Path to get the file from * \param name Name of the data files */ virtual QString data_file(const QString& name) const; /** * \brief Get all available directories to search data from * \param name Name of the data directory */ QStringList data_paths(const QString& name) const; /** * \brief Get all directories to search data from * * This function may include directories that don't exist but that will be * checked if they existed * * \param name Name of the data directory */ QStringList data_paths_unchecked(const QString& name) const; /** * \brief Get all possible directories to search data from */ QList data_roots() const; bool notify(QObject *receiver, QEvent *e) override; signals: void icon_theme_changed(const QString& theme_name); protected: /** * \brief Called after construction, before anything else * \note set application name and stuff in here */ virtual void on_initialize() {} /** * \brief Called after on_initialize */ virtual void on_initialize_translations(); /** * \brief Called after on_initialize() and after translations are loaded */ virtual void on_initialize_settings() {} }; } // namespace app mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/video/video_format.cpp000664 001750 001750 00000062055 14477652011 031204 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "video_format.hpp" #include #include #include #include extern "C" { #include #include #include #include } #include "app/qstring_exception.hpp" #include "app/log/log.hpp" #include "model/assets/composition.hpp" namespace glaxnimate::av { template QString to_str(Callback callback, Args... args) { char buf[max_size] = {0}; return callback(buf, args...); } QString err2str(int errnum) { return to_str(&av_make_error_string, AV_ERROR_MAX_STRING_SIZE, errnum); } class Error: public app::QStringException<>{ using Ctor::Ctor; }; template class CGuard { public: CGuard(Callback callback, Object object) : callback(callback), object(object) {} ~CGuard() { callback(object); } private: Callback callback; Object object; }; // a wrapper around a single output AVStream struct OutputStream { OutputStream( AVFormatContext *oc, AVCodecID codec_id ) { format_context = oc; // find the encoder codec = (AVCodec*)avcodec_find_encoder(codec_id); if ( !codec ) throw av::Error(QObject::tr("Could not find encoder for '%1'").arg(avcodec_get_name(codec_id))); stream = avformat_new_stream(oc, nullptr); if (!stream) throw av::Error(QObject::tr("Could not allocate stream")); stream->id = oc->nb_streams-1; codec_context = avcodec_alloc_context3(codec); if ( !codec_context ) throw av::Error(QObject::tr("Could not alloc an encoding context")); // Some formats want stream headers to be separate. if (oc->oformat->flags & AVFMT_GLOBALHEADER) codec_context->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; } ~OutputStream() { avcodec_free_context(&codec_context); av_frame_free(&frame); av_frame_free(&tmp_frame); sws_freeContext(sws_context); } int read_packets() { int ret = 0; while ( ret >= 0 ) { AVPacket pkt; memset(&pkt, 0, sizeof(AVPacket)); ret = avcodec_receive_packet(codec_context, &pkt); if ( ret == AVERROR(EAGAIN) || ret == AVERROR_EOF ) break; else if (ret < 0) throw av::Error(QObject::tr("Error encoding a frame: %1").arg(av::err2str(ret))); // rescale output packet timestamp values from codec to stream timebase av_packet_rescale_ts(&pkt, codec_context->time_base, stream->time_base); pkt.stream_index = stream->index; // Write the compressed frame to the media file. ret = av_interleaved_write_frame(format_context, &pkt); av_packet_unref(&pkt); if (ret < 0) throw av::Error(QObject::tr("Error while writing output packet: %1").arg(av::err2str(ret))); } return ret; } int write_frame(AVFrame *frame) { // send the frame to the encoder int ret = avcodec_send_frame(codec_context, frame); if ( ret < 0 ) throw av::Error(QObject::tr("Error sending a frame to the encoder: %1").arg(av::err2str(ret))); return read_packets(); } int flush_frames() { return write_frame(nullptr); } AVStream *stream = nullptr; AVCodecContext *codec_context = nullptr; // pts of the next frame that will be generated int64_t next_pts = 0; AVFrame *frame = nullptr; AVFrame *tmp_frame = nullptr; SwsContext *sws_context = nullptr; AVFormatContext *format_context = nullptr; AVCodec *codec = nullptr; }; class DictWrapper { public: class Item { public: Item(AVDictionary** av_dict, QByteArray key) : av_dict(av_dict), key(std::move(key)), value(nullptr) {} operator const char*() const { return get(); } const char* get() const { if ( value == nullptr ) { if ( auto entry = av_dict_get(*av_dict, key.data(), nullptr, 0) ) value = entry->value; } return value; } void set(const char* text) { int ret = av_dict_set(av_dict, key.data(), text, 0); if ( ret >= 0 ) value = nullptr; else throw Error(QObject::tr("Could not set dict key `%1`: %2").arg(QString(key)).arg(err2str(ret))); } void set(const QString& s) { set(s.toUtf8().data()); } void set(int64_t v) { int ret = av_dict_set_int(av_dict, key.data(), v, 0); if ( ret >= 0 ) value = nullptr; else throw Error(QObject::tr("Could not set dict key `%1`: %2").arg(QString(key)).arg(err2str(ret))); } Item& operator=(const char* text) { set(text); return *this; } Item& operator=(const QString& text) { set(text); return *this; } Item& operator=(int64_t v) { set(v); return *this; } private: AVDictionary** av_dict; QByteArray key; mutable const char* value; }; DictWrapper(AVDictionary** av_dict) : av_dict(av_dict) {} Item operator[](const QString& key) { return Item(av_dict, key.toUtf8()); } int size() const { return av_dict_count(*av_dict); } void erase(const QString& key) { int ret = av_dict_set(av_dict, key.toUtf8().data(), nullptr, 0); if ( ret < 0 ) throw Error(QObject::tr("Could not erase dict key `%1`: %2").arg(key).arg(err2str(ret))); } private: AVDictionary** av_dict; }; class Dict : public DictWrapper { public: Dict() : DictWrapper(&local_dict) {} Dict(Dict&& other) : Dict() { std::swap(local_dict, other.local_dict); } Dict(const Dict& other) : Dict() { int ret = av_dict_copy(&local_dict, other.local_dict, 0); if ( ret < 0 ) throw Error(QObject::tr("Could not copy dict: %1").arg(err2str(ret))); } Dict& operator=(Dict&& other) { std::swap(local_dict, other.local_dict); return *this; } Dict& operator=(const Dict& other) { int ret = av_dict_copy(&local_dict, other.local_dict, 0); if ( ret < 0 ) throw Error(QObject::tr("Could not copy dict `%1`: %2").arg(err2str(ret))); return *this; } ~Dict() { av_dict_free(&local_dict); } AVDictionary** dict() { return &local_dict; } private: AVDictionary* local_dict = nullptr; }; class Video { public: static AVFrame *alloc_picture(AVPixelFormat pix_fmt, int width, int height) { AVFrame *picture; int ret; picture = av_frame_alloc(); if (!picture) return nullptr; picture->format = pix_fmt; picture->width = width; picture->height = height; // allocate the buffers for the frame data ret = av_frame_get_buffer(picture, 0); if (ret < 0) throw av::Error(QObject::tr("Could not allocate frame data.")); return picture; } static std::pair image_format(QImage::Format format) { switch ( format ) { case QImage::Format_Invalid: default: return {AV_PIX_FMT_NONE, QImage::Format_Invalid}; case QImage::Format_Mono: case QImage::Format_MonoLSB: return {AV_PIX_FMT_MONOBLACK, format}; case QImage::Format_Indexed8: return {AV_PIX_FMT_ARGB, QImage::Format_RGB32}; case QImage::Format_RGB32: return {AV_PIX_FMT_0RGB, format}; case QImage::Format_ARGB32: return {AV_PIX_FMT_ARGB, format}; case QImage::Format_ARGB32_Premultiplied: return {AV_PIX_FMT_ARGB, format}; case QImage::Format_RGB16: return {AV_PIX_FMT_RGB565LE, format}; case QImage::Format_RGB555: return {AV_PIX_FMT_RGB555LE, format}; case QImage::Format_RGB888: return {AV_PIX_FMT_RGB24, format}; case QImage::Format_RGBX8888: return {AV_PIX_FMT_RGB0, format}; case QImage::Format_RGBA8888: case QImage::Format_RGBA8888_Premultiplied: return {AV_PIX_FMT_RGBA, format}; case QImage::Format_Alpha8: case QImage::Format_Grayscale8: return {AV_PIX_FMT_GRAY8, format}; case QImage::Format_RGBA64_Premultiplied: case QImage::Format_ARGB8555_Premultiplied: case QImage::Format_ARGB8565_Premultiplied: case QImage::Format_ARGB6666_Premultiplied: case QImage::Format_ARGB4444_Premultiplied: case QImage::Format_A2RGB30_Premultiplied: case QImage::Format_A2BGR30_Premultiplied: return {AV_PIX_FMT_ARGB, QImage::Format_ARGB32}; case QImage::Format_RGB30: case QImage::Format_RGB444: case QImage::Format_RGB666: case QImage::Format_BGR30: case QImage::Format_RGBX64: case QImage::Format_RGBA64: return {AV_PIX_FMT_RGB24, QImage::Format_RGB888}; } } static AVPixelFormat best_pixel_format(const AVPixelFormat* pix_fmts) { static const std::array preferred = { // RGBA and similar (no conversion) AV_PIX_FMT_ARGB, AV_PIX_FMT_RGBA, AV_PIX_FMT_ABGR, AV_PIX_FMT_BGRA, // YUV + Alpha AV_PIX_FMT_YUVA444P, AV_PIX_FMT_YUVA420P, // RGB AV_PIX_FMT_RGB24, AV_PIX_FMT_BGR24, }; if ( !pix_fmts ) return AV_PIX_FMT_NONE; std::set supported; for ( auto it = pix_fmts; it && *it != AV_PIX_FMT_NONE; ++it ) supported.insert(*it); for ( auto pref : preferred ) if ( supported.count(pref) ) return pref; return *pix_fmts; } Video(AVFormatContext *oc, Dict options, AVCodecID codec_id, int64_t bit_rate, int width, int height, int fps) : ost(oc, codec_id) { if ( ost.codec->type != AVMEDIA_TYPE_VIDEO ) throw Error(QObject::tr("No video codec")); ost.codec_context->codec_id = codec_id; ost.codec_context->bit_rate = bit_rate; // Resolution must be a multiple of two ost.codec_context->width = width; if ( ost.codec_context->width % 2 ) ost.codec_context->width -= 1; ost.codec_context->height = height; if ( ost.codec_context->height % 2 ) ost.codec_context->height -= 1; // timebase: This is the fundamental unit of time (in seconds) in terms // of which frame timestamps are represented. For fixed-fps content, // timebase should be 1/framerate and timestamp increments should be // identical to 1. ost.stream->time_base = AVRational{ 1, fps }; ost.codec_context->time_base = ost.stream->time_base; // emit one intra frame every twelve frames at most ost.codec_context->gop_size = 12; // get_format() for some reason returns an invalid value ost.codec_context->pix_fmt = best_pixel_format(ost.codec->pix_fmts); if ( ost.codec_context->pix_fmt == AV_PIX_FMT_NONE ) throw av::Error(QObject::tr("Could not determine pixel format")); // // just for testing, we also add B-frames // if ( ost.codec_context->codec_id == AV_CODEC_ID_MPEG2VIDEO ) // ost.codec_context->max_b_frames = 2; int ret; // open the codec ret = avcodec_open2(ost.codec_context, ost.codec, options.dict()); if (ret < 0) throw av::Error(QObject::tr("Could not open video codec: %1").arg(av::err2str(ret))); // allocate and init a re-usable frame ost.frame = alloc_picture(ost.codec_context->pix_fmt, ost.codec_context->width, ost.codec_context->height); if (!ost.frame) throw av::Error(QObject::tr("Could not allocate video frame")); ost.tmp_frame = nullptr; /* copy the stream parameters to the muxer */ ret = avcodec_parameters_from_context(ost.stream->codecpar, ost.codec_context); if (ret < 0) throw av::Error(QObject::tr("Could not copy the stream parameters")); } static void fill_image(AVFrame *pict, const QImage& image) { for ( int y = 0; y < image.height(); y++) { auto line = image.constScanLine(y); for ( int x = 0; x < image.bytesPerLine(); x++ ) { pict->data[0][y * pict->linesize[0] + x] = line[x]; } } } AVFrame *get_video_frame(QImage image) { // when we pass a frame to the encoder, it may keep a reference to it // internally; make sure we do not overwrite it here if ( av_frame_make_writable(ost.frame) < 0 ) throw av::Error(QObject::tr("Error while creating video frame")); auto format = image_format(image.format()); if ( format.first == AV_PIX_FMT_NONE ) { image = QImage(ost.codec_context->width, ost.codec_context->height, QImage::Format_RGB888); format.first = AV_PIX_FMT_RGB24; } else if ( format.second != image.format() ) { image = image.convertToFormat(format.second); } if ( ost.codec_context->pix_fmt != format.first || image.width() != ost.codec_context->width || image.height() != ost.codec_context->height ) { if (!ost.sws_context) { ost.sws_context = sws_getContext( image.width(), image.height(), format.first, ost.codec_context->width, ost.codec_context->height, ost.codec_context->pix_fmt, SWS_BICUBIC, nullptr, nullptr, nullptr ); if (!ost.sws_context) throw av::Error(QObject::tr("Could not initialize the conversion context")); } if ( !ost.tmp_frame ) { ost.tmp_frame = alloc_picture(format.first, image.width(), image.height()); if (!ost.tmp_frame) throw av::Error(QObject::tr("Could not allocate temporary picture")); } fill_image(ost.tmp_frame, image); sws_scale(ost.sws_context, (const uint8_t * const *) ost.tmp_frame->data, ost.tmp_frame->linesize, 0, ost.codec_context->height, ost.frame->data, ost.frame->linesize); } else { fill_image(ost.frame, image); } ost.frame->pts = ost.next_pts++; return ost.frame; } void write_video_frame(const QImage& image) { ost.write_frame(get_video_frame(image)); } void flush() { ost.flush_frames(); } private: OutputStream ost; }; class Logger { private: struct LogData { std::mutex mutex; io::video::VideoFormat* format = nullptr; int log_level; static LogData& instance() { static LogData instance; return instance; } static void static_callback(void *, int level, const char *fmt, va_list vl) { instance().callback(level, fmt, vl); } void setup(io::video::VideoFormat* format, int log_level) { auto guard = std::lock_guard(mutex); this->format = format; this->log_level = log_level; av_log_set_callback(&LogData::static_callback); } void teardown() { auto guard = std::lock_guard(mutex); av_log_set_callback(&av_log_default_callback); format = nullptr; } void callback(int level, const char *fmt, va_list vl) { auto guard = std::lock_guard(mutex); if ( level > log_level ) return; char buffer[1024]; std::vsprintf(buffer, fmt, vl); QString msg(buffer); if ( msg.endsWith('\n') ) msg.remove(msg.size()-1, 1); if ( level > AV_LOG_WARNING ) { format->information(msg); } else if ( level == AV_LOG_WARNING ) { app::log::Log("libav").log(msg); format->warning(msg); } else { app::log::Log("libav").log(msg, app::log::Error); format->error(msg); } } }; public: Logger(io::video::VideoFormat* format) : Logger(format, av_log_get_level()) {} Logger(io::video::VideoFormat* format, int log_level) { level = av_log_get_level(); av_log_set_level(log_level); LogData::instance().setup(format, log_level); } ~Logger() { LogData::instance().teardown(); av_log_set_level(level); } private: int level; }; class DeviceIo { public: DeviceIo(QIODevice* device, int block_size = 4*1024) { buffer = (unsigned char*)av_malloc(block_size); context_ = avio_alloc_context( buffer, block_size, 1, device, &DeviceIo::read_packet, &DeviceIo::write_packet, &DeviceIo::seek ); } ~DeviceIo() { avio_context_free(&context_); av_free(buffer); } AVIOContext* context() const noexcept { return context_; } private: static int read_packet(void *opaque, uint8_t *buf, int buf_size) { QIODevice* device = (QIODevice*)opaque; return device->read((char*)buf, buf_size); } static int write_packet(void *opaque, uint8_t *buf, int buf_size) { QIODevice* device = (QIODevice*)opaque; return device->write((char*)buf, buf_size); } static int64_t seek(void *opaque, int64_t offset, int whence) { QIODevice* device = (QIODevice*)opaque; switch ( whence ) { case SEEK_SET: return device->seek(offset); case SEEK_CUR: return device->seek(offset + device->pos()); case SEEK_END: device->readAll(); return device->seek(offset + device->pos()); } return 0; } AVIOContext* context_; unsigned char * buffer; }; } // namespace glaxnimate::av glaxnimate::io::Autoreg glaxnimate::io::video::VideoFormat::autoreg; static QStringList out_ext; static bool format_skip(const AVOutputFormat* format) { static std::set blacklisted = { "webp", "gif", "ico" }; return blacklisted.count(format->name) || format->video_codec == AV_CODEC_ID_NONE || format->flags & (AVFMT_NOFILE|AVFMT_NEEDNUMBER) ; } static void get_formats() { out_ext.push_back("mp4"); void* opaque = nullptr; while ( auto format = av_muxer_iterate(&opaque) ) { if ( format_skip(format) ) continue; out_ext += QString(format->extensions).split(',', #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) Qt::SkipEmptyParts #else QString::SkipEmptyParts #endif ); } } QStringList glaxnimate::io::video::VideoFormat::extensions() const { if ( out_ext.empty() ) get_formats(); return out_ext; } bool glaxnimate::io::video::VideoFormat::on_save(QIODevice& dev, const QString& name, model::Composition* comp, const QVariantMap& settings) { try { av::Logger logger(this, settings["verbose"].toBool() ? AV_LOG_INFO : AV_LOG_WARNING); auto filename = name.toUtf8(); // allocate the output media context AVFormatContext *oc; avformat_alloc_output_context2(&oc, nullptr, nullptr, filename.data()); if ( !oc ) { warning(tr("Could not deduce output format from file extension: using MPEG.")); avformat_alloc_output_context2(&oc, nullptr, "mpeg", filename.data()); if ( !oc ) { error(tr("Could not find output format")); return false; } } // see https://libav.org/documentation/doxygen/master/group__metadata__api.html av::DictWrapper metadata(&oc->metadata); auto document = comp->document(); metadata["title"] = comp->name.get(); if ( !document->info().author.isEmpty() ) metadata["artist"] = document->info().author; if ( !document->info().description.isEmpty() ) metadata["comment"] = document->info().description; for ( auto it = document->metadata().begin(); it != document->metadata().end(); ++it ) metadata[it.key()] = it->toString(); av::CGuard guard(&avformat_free_context, oc); // Add the audio and video streams using the given (or default) // format codecs and initialize the codecs. AVCodecID codec_id = oc->oformat->video_codec; if ( codec_id == AV_CODEC_ID_NONE ) { error(tr("No video codec")); return false; } // Options av::Dict opt; opt["crf"] = 23; if ( codec_id == AV_CODEC_ID_H264 ) { opt["profile"] = "high"; opt["preset"] = "veryslow"; opt["tune"] = "animation"; } for ( auto it = settings.begin(); it != settings.end(); ++it ) { if ( it.key().startsWith("ffmpeg:") ) { auto value = it->toString(); opt[it.key().mid(7)] = value; } } // Now that all the parameters are set, we can open the audio and // video codecs and allocate the necessary encode buffers. int width = settings["width"].toInt(); if ( width == 0 ) width = comp->width.get(); int height = settings["height"].toInt(); if ( height == 0 ) height = comp->height.get(); int fps = qRound(comp->fps.get()); av::Video video(oc, opt, codec_id, 7000000, width, height, fps); // log format info av_dump_format(oc, 0, filename, 1); // open the output file, if needed av::DeviceIo io(&dev); oc->pb = io.context(); // Write the stream header, if any int ret = avformat_write_header(oc, opt.dict()); if ( ret < 0 ) { error(tr("Error occurred when opening output file: %1").arg(av::err2str(ret))); return false; } auto first_frame = comp->animation->first_frame.get(); auto last_frame = comp->animation->last_frame.get(); QColor background = settings["background"].value(); emit progress_max_changed(last_frame - first_frame); for ( auto i = first_frame; i < last_frame; i++ ) { video.write_video_frame(comp->render_image(i, {width, height}, background)); emit progress(i - first_frame); } video.flush(); // Write the trailer, if any. The trailer must be written before you // close the CodecContexts open when you wrote the header; otherwise // av_write_trailer() may try to use memory that was freed on // av_codec_close(). av_write_trailer(oc); return true; } catch ( const av::Error& e ) { error(e.message()); return false; } } std::unique_ptr glaxnimate::io::video::VideoFormat::save_settings(model::Composition* comp) const { return std::make_unique(app::settings::SettingList{ // slug label description default min max app::settings::Setting{"background", tr("Background"), tr("Background color"), QColor(0, 0, 0, 0)}, app::settings::Setting{"width", tr("Width"), tr("If not 0, it will overwrite the size"), comp->width.get(), 0, 99999}, app::settings::Setting{"height", tr("Height"), tr("If not 0, it will overwrite the size"), comp->height.get(), 0, 99999}, app::settings::Setting{"verbose", tr("Verbose"), tr("Show verbose information on the conversion"), false}, }); } QString glaxnimate::io::video::VideoFormat::library_version() { return QStringList{ LIBAVUTIL_IDENT, LIBAVFORMAT_IDENT, LIBAVCODEC_IDENT, LIBSWSCALE_IDENT }.join(", "); } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/math/bezier/000775 001750 001750 00000000000 14477652011 026506 5ustar00ddennedyddennedy000000 000000 mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/mime/mime_serializer.hpp000664 001750 001750 00000003364 14477652011 031532 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include #include #include #include #include "app/log/log_line.hpp" namespace glaxnimate::model { class Document; class Composition; class DocumentNode; } // namespace glaxnimate::model namespace glaxnimate::io::mime { struct DeserializedData { DeserializedData(); DeserializedData(const DeserializedData&) = delete; DeserializedData(DeserializedData&&); DeserializedData& operator=(const DeserializedData&) = delete; DeserializedData& operator=(DeserializedData&&); ~DeserializedData(); std::unique_ptr document; model::Composition* main = nullptr; bool empty() const; void initialize_data(); }; class MimeSerializer { public: virtual ~MimeSerializer() = default; virtual QString slug() const = 0; virtual QString name() const = 0; virtual QStringList mime_types() const = 0; virtual QByteArray serialize(const std::vector& objects) const = 0; virtual io::mime::DeserializedData deserialize(const QByteArray& data) const; virtual bool can_deserialize() const = 0; virtual void to_mime_data(QMimeData& out, const std::vector& objects) const { QByteArray data = serialize(objects); for ( const QString& mime : mime_types() ) out.setData(mime, data); } io::mime::DeserializedData from_mime_data(const QMimeData& data) const; protected: void message(const QString& message, app::log::Severity severity = app::log::Warning) const; }; } // namespace glaxnimate::io::mime mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/math/bezier/solver.hpp000664 001750 001750 00000032777 14477652011 030551 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include "math/vector.hpp" #include "math/polynomial.hpp" #include namespace glaxnimate::math::bezier { template class CubicBezierSolver { public: using vector_type = Vec; using scalar = scalar_type; using argument_type = scalar; using bounding_box_type = std::pair; constexpr CubicBezierSolver(const std::array& points) noexcept : points_(points) { rebuild_coeff(); } constexpr CubicBezierSolver(Vec p0, Vec p1, Vec p2, Vec p3) noexcept : CubicBezierSolver{{p0, p1, p2, p3}} {} /** * \brief Finds the point along the bezier curve * \param t between 0 and 1 */ constexpr Vec solve(argument_type t) const noexcept { return ((a_ * t + b_ ) * t + c_ ) * t + d_; } constexpr scalar solve_component(argument_type t, int component) const noexcept { scalar a = detail::get(a_, component); scalar b = detail::get(b_, component); scalar c = detail::get(c_, component); scalar d = detail::get(d_, component); return ((a * t + b ) * t + c ) * t + d; } constexpr scalar derivative(argument_type factor, int component) const noexcept { scalar a = detail::get(a_, component); scalar b = detail::get(b_, component); scalar c = detail::get(c_, component); return (3 * a * factor + 2 * b) * factor + c; } /** * \brief Finds the tangent of the point on the bezier * \param factor between 0 and 1 * \return Angle in radians */ scalar tangent_angle(argument_type factor) const { return std::atan2(derivative(factor, 1), derivative(factor, 0)); } /** * \brief Finds the normal of the point on the bezier * \param factor between 0 and 1 * \return Angle in radians */ scalar normal_angle(argument_type factor) const { return std::atan2(derivative(factor, 0), derivative(factor, 1)); } constexpr const std::array& points() const noexcept { return points_; } // constexpr std::array& points() noexcept // { // return points_; // } template constexpr void set(const Vec& v) noexcept { points_[i] = v; rebuild_coeff(); } /** * \brief Splits a bezier * \param factor value between 0 and 1 determining the split position * \return Two vectors for the two resulting cubic beziers */ std::pair, std::array> split(argument_type factor) const { // linear if ( points_[0] == points_[1] && points_[2] == points_[3] ) { Vec mid = lerp(points_[0], points_[3], factor); return { {points_[0], points_[0], mid, mid}, {mid, mid, points_[3], points_[3]}, }; } Vec p01 = lerp(points_[0], points_[1], factor); Vec p12 = lerp(points_[1], points_[2], factor); Vec p23 = lerp(points_[2], points_[3], factor); Vec p012 = lerp(p01, p12, factor); Vec p123 = lerp(p12, p23, factor); Vec p0123 = lerp(p012, p123, factor); return { {points_[0], p01, p012, p0123}, {p0123, p123, p23, points_[3]} }; } bounding_box_type bounds() const { std::vector solutions; for ( int i = 0; i < detail::VecSize::value; i++ ) { bounds_solve(3 * detail::get(a_, i), 2 * detail::get(b_, i), detail::get(c_, i), solutions); } std::vector boundary_points; //Add Begin and end point not the control points! boundary_points.push_back(points_[0]); boundary_points.push_back(points_[3]); for ( scalar e : solutions ) boundary_points.push_back(solve(e)); Vec min; Vec max; for ( int i = 0; i < detail::VecSize::value; i++ ) { scalar cmin = std::numeric_limits::max(); scalar cmax = std::numeric_limits::lowest(); for ( const Vec& p : boundary_points ) { if ( detail::get(p, i) < cmin ) cmin = detail::get(p, i); if ( detail::get(p, i) > cmax ) cmax = detail::get(p, i); } detail::get(max, i) = cmax; detail::get(min, i) = cmin; } return {min, max}; } /** * \brief Returns the \p t for the min/max points for the given component * * \note The returned value is sorted so that \p first <= \p second */ std::pair extrema(int component) const { std::vector solutions; bounds_solve(3 * detail::get(a_, component), 2 * detail::get(b_, component), detail::get(c_, component), solutions); // No solution: end points have the extrema if ( solutions.size() == 0 ) return {0, 1}; // One solution: one of the end points has an extreme if ( solutions.size() == 1 ) { auto val = solve_component(solutions[0], component); // The end point is a minimum if ( detail::get(points_[0], component) < val ) { // Last point is min if ( detail::get(points_[3], component) < detail::get(points_[0], component) ) return {solutions[0], 1}; // First point is min return {0, solutions[0]}; } // Last point is min else if ( detail::get(points_[3], component) < val ) { return {solutions[0], 1}; } // end point is a maximum else { // First point is max if ( detail::get(points_[0], component) > val ) return {0, solutions[0]}; // Last point is max return {solutions[0], 1}; } } if ( solutions[0] > solutions[1] ) return {solutions[1], solutions[0]}; return {solutions[0], solutions[1]}; } /** * \brief Return inflection points for a 2D bezier */ std::vector inflection_points() const { auto denom = detail::get(a_, 1) * detail::get(b_, 0) - detail::get(a_, 0) * detail::get(b_, 1); if ( qFuzzyIsNull(denom) ) return {}; auto t_cusp = -0.5 * (detail::get(a_, 1) * detail::get(c_, 0) - detail::get(a_, 0) * detail::get(c_, 1)) / denom; auto square = t_cusp * t_cusp - 1./3. * (detail::get(b_, 1) * detail::get(c_, 0) - detail::get(b_, 0) * detail::get(c_, 1)) / denom; if ( square < 0 ) return {}; auto root = std::sqrt(square); if ( qFuzzyIsNull(root) ) { if ( is_valid_inflection(t_cusp) ) return {t_cusp}; return {}; } std::vector roots; roots.reserve(2); if ( is_valid_inflection(t_cusp - root) ) roots.push_back(t_cusp - root); if ( is_valid_inflection(t_cusp + root) ) roots.push_back(t_cusp + root); return roots; } std::vector> intersections( const CubicBezierSolver& other, int max_count = 1, scalar tolerance = 2, int max_recursion = 10 ) const { std::vector> intersections; intersects_impl(IntersectData(*this), IntersectData(other), max_count, tolerance, intersections, 0, max_recursion); return intersections; } /** * \brief Returns the t corresponding to the given value for a component. * \returns `t` or -1 in case of no root has been found */ scalar t_at_value(scalar value, int comp = 0) const { auto roots = math::cubic_roots( detail::get(a_, comp), detail::get(b_, comp), detail::get(c_, comp), detail::get(d_, comp) - value ); for ( auto root : roots ) { if ( valid_solution(root) ) return root; } return -1; } private: static constexpr bool is_valid_inflection(scalar root) noexcept { return root > 0 && root < 1; } // You get these terms by expanding the Bezier definition and re-arranging them as a polynomial in t // B(t) = a t^3 + b t^2 + c t + d static constexpr scalar a(const scalar& k0, const scalar& k1, const scalar& k2, const scalar& k3) noexcept { return -k0 + k1*3 + k2 * -3 + k3; } static constexpr scalar b(const scalar& k0, const scalar& k1, const scalar& k2) noexcept { return k0 * 3 + k1 * -6 + k2 * 3; } static constexpr scalar c(const scalar& k0, const scalar& k1) noexcept { return k0 * -3 + k1 * 3; } static constexpr scalar d(const scalar& k0) noexcept { return k0; } static void bounds_solve(scalar a, scalar b, scalar c, std::vector& solutions) { scalar d = b * b - 4. * a * c; // no real solution if ( d < 0 ) return; if ( qFuzzyIsNull(a) ) { // linear case add_bounds_solution(-c / b, solutions); } else { add_bounds_solution((-b + std::sqrt(d)) / (2 * a), solutions); if ( d != 0 ) add_bounds_solution((-b - std::sqrt(d)) / (2 * a), solutions); } } static bool valid_solution(scalar& root) { if ( root >= 0 && root <= 1 ) return true; if ( qFuzzyIsNull(root) ) { root = 0; return true; } if ( qFuzzyCompare(root, 1) ) { root = 1; return true; } return false; } static void add_bounds_solution(scalar solution, std::vector& solutions) { if ( valid_solution(solution) ) solutions.push_back(solution); } struct IntersectData { IntersectData( const CubicBezierSolver& solver, const bounding_box_type& box, argument_type t1, argument_type t2 ) : bez(solver), width(box.second.x() - box.first.x()), height(box.second.y() - box.first.y()), center((box.first + box.second) / 2), t1(t1), t2(t2), t((t1 + t2) / 2) {} IntersectData(const CubicBezierSolver& solver) : IntersectData(solver, solver.bounds(), 0, 1) {} std::pair split() const { auto split = bez.split(0.5); return { IntersectData(split.first), IntersectData(split.second) }; } bool intersects(const IntersectData& other) const { return std::abs(center.x() - other.center.x()) * 2 < width + other.width && std::abs(center.y() - other.center.y()) * 2 < height + other.height; } CubicBezierSolver bez; scalar width; scalar height; Vec center; argument_type t1, t2, t; }; static void intersects_impl( const IntersectData& d1, const IntersectData& d2, std::size_t max_count, scalar tolerance, std::vector>& intersections, int depth, int max_recursion ) { if ( !d1.intersects(d2) ) return; if ( depth >= max_recursion || (d1.width <= tolerance && d1.height <= tolerance && d2.width <= tolerance && d2.height <= tolerance) ) { intersections.emplace_back(d1.t, d2.t); return; } auto d1s = d1.split(); auto d2s = d2.split(); std::array, 4> splits = {{ {d1s.first, d2s.first }, {d1s.first, d2s.second}, {d1s.second, d2s.first }, {d1s.second, d2s.second} }}; for ( const auto& p : splits ) { intersects_impl(p.first, p.second, max_count, tolerance, intersections, depth + 1, max_recursion); if ( intersections.size() >= max_count ) return; } } void rebuild_coeff() { for ( int component = 0; component < detail::VecSize::value; component++ ) { detail::get(a_, component) = a( detail::get(points_[0], component), detail::get(points_[1], component), detail::get(points_[2], component), detail::get(points_[3], component) ); detail::get(b_, component) = b( detail::get(points_[0], component), detail::get(points_[1], component), detail::get(points_[2], component) ); detail::get(c_, component) = c( detail::get(points_[0], component), detail::get(points_[1], component) ); detail::get(d_, component) = d( detail::get(points_[0], component) ); } } std::array points_; // Polynomial coefficients (a t^3 + b t^2 + c t + d = 0) Vec a_, b_, c_, d_; }; } // namespace glaxnimate::math::bezier mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/utils/sort_gradient.hpp000664 001750 001750 00000000767 14477652011 031024 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include namespace glaxnimate::utils { inline bool gradient_stop_comparator(const QGradientStop& a, const QGradientStop& b) noexcept { return a.first <= b.first; } inline void sort_gradient(QGradientStops& stops) { std::sort(stops.begin(), stops.end(), &gradient_stop_comparator); } } // namespace glaxnimate::utils mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/aep/aep_loader.hpp000664 001750 001750 00000002736 14477652011 030265 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include "ae_project.hpp" #include "model/document.hpp" #include "aep_format.hpp" #include "model/assets/assets.hpp" namespace glaxnimate::io::aep { class AepLoader { public: AepLoader(model::Document* document, const Project& project, QDir asset_path, ImportExport* io) : document(document), project(project), asset_path(asset_path), io(io) {} void load_project(); struct CompData; private: struct ColorInfo { model::NamedColor* asset; const Solid* solid; }; void load_comp(const Composition& comp); void load_asset(const FolderItem* item); void load_layer(const Layer& layer, CompData& data); void asset_layer(model::Layer* layer, const Layer& ae_layer, CompData& data); void shape_layer(model::Layer* layer, const Layer& ae_layer, CompData& data); void text_layer(model::Layer* layer, const Layer& ae_layer, CompData& data); void warning(const QString& msg); void info(const QString& msg); model::Composition* get_comp(Id id); model::Document* document; const Project& project; QDir asset_path; ImportExport* io; std::unordered_map colors; std::unordered_map comps; std::unordered_map images; std::unordered_map asset_size; }; } // namespace glaxnimate::io::aep mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/svg/svg_format.hpp000664 001750 001750 00000001772 14477652011 030372 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include "io/base.hpp" #include "io/io_registry.hpp" namespace glaxnimate::io::svg { class SvgFormat : public ImportExport { Q_OBJECT public: QString slug() const override { return "svg"; } QString name() const override { return tr("SVG"); } QStringList extensions() const override { return {"svg", "svgz"}; } bool can_save() const override { return true; } bool can_open() const override { return true; } std::unique_ptr save_settings(model::Composition*) const override; protected: bool on_open(QIODevice& file, const QString&, model::Document* document, const QVariantMap&) override; bool on_save(QIODevice & file, const QString & filename, model::Composition* comp, const QVariantMap & setting_values) override; private: static Autoreg autoreg; }; } // namespace glaxnimate::io::svg mlt-7.22.0/src/modules/glaxnimate/glaxnimate/external/QtAppSetup/src/app/log/log_model.cpp000664 001750 001750 00000005353 14477652011 033251 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "log_model.hpp" namespace { enum Columns { Time, Source, SourceDetail, Message, Count }; } // namespace app::log::LogModel::LogModel() { connect(&Logger::instance(), &Logger::logged, this, &LogModel::on_line); } int app::log::LogModel::rowCount(const QModelIndex &) const { return lines.size(); } int app::log::LogModel::columnCount(const QModelIndex &) const { return Count; } QVariant app::log::LogModel::headerData(int section, Qt::Orientation orientation, int role) const { if ( orientation == Qt::Horizontal ) { if ( role == Qt::DisplayRole ) { switch ( section ) { case Time: return tr("Time"); case Source: return tr("Source"); case SourceDetail: return tr("Details"); case Message: return tr("Message"); } } } else { if ( role == Qt::DecorationRole ) { switch ( lines[section].severity ) { case Info: return QIcon::fromTheme("emblem-information"); case Warning: return QIcon::fromTheme("emblem-warning"); case Error: return QIcon::fromTheme("emblem-error"); } } else if ( role == Qt::ToolTipRole ) { return Logger::severity_name(lines[section].severity); } } return {}; } QVariant app::log::LogModel::data(const QModelIndex & index, int role) const { if ( !index.isValid() ) return {}; const LogLine& line = lines[index.row()]; if ( role == Qt::DisplayRole ) { switch ( index.column() ) { case Time: return line.time.toString(Qt::ISODate); case Source: return line.source; case SourceDetail: return line.source_detail; case Message: return line.message; } } else if ( role == Qt::ToolTipRole ) { switch ( index.column() ) { case Time: return line.time.toString(); case SourceDetail: return line.source_detail; } } return {}; } void app::log::LogModel::on_line(const LogLine& line) { beginInsertRows(QModelIndex(), lines.size(), lines.size()); lines.push_back(line); endInsertRows(); } void app::log::LogModel::populate(const std::vector& lines) { beginResetModel(); this->lines = lines; endResetModel(); } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/plugin/service.hpp000664 001750 001750 00000002027 14477652011 027745 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include #include #include #include #include "app/settings/setting.hpp" namespace glaxnimate::plugin { class PluginScript { Q_GADGET public: QString module; QString function; app::settings::SettingList settings; bool valid() const { return !module.isEmpty() && !function.isEmpty(); } }; enum class ServiceType { Action, IoFormat, }; class Plugin; class PluginService : public QObject { public: virtual ~PluginService() = default; virtual ServiceType type() const = 0; virtual QString name() const = 0; virtual void enable() = 0; virtual void disable() = 0; virtual QIcon service_icon() const = 0; Plugin* plugin() const { return owner; } void set_plugin(Plugin* plugin) { owner = plugin; } private: Plugin* owner = nullptr; }; } // namespace glaxnimate::plugin mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/command/structure_commands.hpp000664 001750 001750 00000002063 14477652011 032346 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include #include "model/document_node.hpp" #include "model/shapes/shape.hpp" namespace glaxnimate::command { class DeferredCommandBase : public QUndoCommand { public: ~DeferredCommandBase(); void undo() override; void redo() override; bool has_action() const; protected: DeferredCommandBase(const QString& name, std::unique_ptr d = {}) : QUndoCommand(name), d(std::move(d)) {} std::unique_ptr d; }; class ReorderCommand : public DeferredCommandBase { public: enum SpecialPosition { MoveUp = -1, MoveDown = -2, MoveTop = -3, MoveBottom = -4, }; static bool resolve_position(model::ShapeElement* node, int& position); ReorderCommand(model::ShapeElement* node, int new_position); private: static QString name(model::DocumentNode* node); }; } // namespace glaxnimate::command mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/rive/rive_html_format.cpp000664 001750 001750 00000002314 14477652011 031716 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "rive_html_format.hpp" #include "rive_exporter.hpp" #include "io/lottie/lottie_html_format.hpp" bool glaxnimate::io::rive::RiveHtmlFormat::on_save( QIODevice& file, const QString&, model::Composition* comp, const QVariantMap& ) { file.write(lottie::LottieHtmlFormat::html_head(this, comp, "" )); QBuffer buffer; buffer.open(QIODevice::WriteOnly); RiveExporter exp(&buffer, this); exp.write_document(comp->document()); file.write(QString(R"( )"); return true; } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/document_node.hpp000664 001750 001750 00000026576 14477652011 030751 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include #include #include #include "model/animation/animatable.hpp" #include "model/object.hpp" namespace glaxnimate::model { class Document; class ReferencePropertyBase; /** * \brief Base class for elements of the document tree, that need to show in the tree view etc. */ class DocumentNode : public Object { Q_OBJECT public: /** * @brief Unique identifier for the node */ GLAXNIMATE_PROPERTY_RO(QUuid, uuid, {}) /** * @brief Name of the node, used to display it in the UI */ GLAXNIMATE_PROPERTY(QString, name, "", &DocumentNode::on_name_changed) protected: template class ChildRange { public: using get_func_t = Base* (Base::*) (int) const; using count_func_t = int (Base::*) () const; class ChildIterator { public: using value_type = Base*; using reference = value_type*; using pointer = value_type*; using difference_type = int; using iterator_category = std::forward_iterator_tag; ChildIterator& operator++() noexcept { ++index; return *this; } Base* operator->() const { return (parent->*get_func)(index); } Base* operator*() const { return (parent->*get_func)(index); } bool operator==(const ChildIterator& oth) const noexcept { return parent == oth.parent && index == oth.index; } bool operator!=(const ChildIterator& oth) const noexcept { return !(*this == oth ); } private: ChildIterator(const Base* parent, int index, get_func_t get_func) noexcept : parent(parent), index(index), get_func(get_func) {} const Base* parent; int index; get_func_t get_func; friend ChildRange; }; ChildIterator begin() const noexcept { return ChildIterator{parent, 0, get_func}; } ChildIterator end() const noexcept { return ChildIterator{parent, size(), get_func}; } int size() const { return (parent->*count_func)(); } private: ChildRange(const Base* parent, get_func_t get_func, count_func_t count_func) noexcept : parent(parent), get_func(get_func), count_func(count_func) {} const Base* parent; get_func_t get_func; count_func_t count_func; friend Base; }; public: using User = ReferencePropertyBase; explicit DocumentNode(model::Document* document); ~DocumentNode(); virtual QIcon tree_icon() const = 0; virtual DocumentNode* docnode_parent() const; virtual int docnode_child_count() const = 0; virtual DocumentNode* docnode_child(int index) const = 0; virtual int docnode_child_index(DocumentNode* dn) const = 0; virtual QIcon instance_icon() const = 0; ChildRange docnode_children() const noexcept { return ChildRange{this, &DocumentNode::docnode_child, &DocumentNode::docnode_child_count}; } template T* docnode_find_by_uuid(const QUuid& uuid) { if ( this->uuid.get() == uuid && qobject_cast(this) ) return this; for ( DocumentNode* child : docnode_children() ) if ( auto found = child->docnode_find_by_uuid(uuid) ) return found; return nullptr; } template T* docnode_find_by_name(const QString& name) { if ( this->name.get() == name && qobject_cast(this) ) return this; for ( DocumentNode* child : docnode_children() ) if ( auto found = child->docnode_find_by_name(name) ) return found; return nullptr; } template std::vector docnode_find_by_type_name(const QString& type_name) { std::vector matches; docnode_find_impl(type_name, matches); return matches; } template std::vector docnode_find_by_type() { std::vector matches; docnode_find_impl({}, matches); return matches; } bool docnode_is_instance(const QString& type_name) const; Q_INVOKABLE glaxnimate::model::DocumentNode* find_by_name(const QString& name) { return docnode_find_by_name(name); } Q_INVOKABLE glaxnimate::model::DocumentNode* find_by_uuid(const QUuid& uuid) { return docnode_find_by_uuid(uuid); } Q_INVOKABLE QVariantList find_by_type_name(const QString& type_name) { auto ob = docnode_find_by_type_name(type_name); QVariantList ret; ret.reserve(ob.size()); for ( auto o : ob ) ret.push_back(QVariant::fromValue(o)); return ret; } /** * \brief Updates the name of this node and all of its children * using the "best name" document functions */ void recursive_rename(); /** * \brief Recursively updates uuid */ void refresh_uuid(); QString object_name() const override; /** * \brief List of properties referencing this node */ const std::unordered_set& users() const; /** * \brief Mark \p user as referencing this object */ void add_user(User* user); /** * \brief Remove a user */ void remove_user(User* user); /** * \brief Signals all users that the item has been reinstated */ void attach(); /** * \brief Signals all users that the item has been removed */ void detach(); /** * \brief Whether this node is a descendant of \p other */ bool is_descendant_of(const model::DocumentNode* other) const; protected: virtual void on_parent_changed(model::DocumentNode* old_parent, model::DocumentNode* new_parent) { Q_UNUSED(old_parent); Q_UNUSED(new_parent); } virtual void on_graphics_changed() {} private: template void docnode_find_impl(const QString& type_name, std::vector& matches) { if ( type_name.isEmpty() || docnode_is_instance(type_name) ) { if ( auto obj = qobject_cast(this) ) matches.push_back(obj); } for ( DocumentNode* child : docnode_children() ) child->docnode_find_impl(type_name, matches); } void removed_from_list(); void added_to_list(DocumentNode* new_parent); void on_name_changed(const QString& name, const QString& old_name); signals: void docnode_child_add_begin(int row); void docnode_child_add_end(DocumentNode* node, int row); void docnode_child_remove_begin(int row); void docnode_child_remove_end(DocumentNode* node, int row); void docnode_child_move_begin(int from, int to); void docnode_child_move_end(DocumentNode* node, int from, int to); void name_changed(const QString&); signals: void users_changed(); protected: class Private; DocumentNode(model::Document* document, std::unique_ptr d); std::unique_ptr d; friend ObjectListPropertyBase; }; class Modifier; /** * \brief A document node that has a physical size and shape in the document */ class VisualNode : public DocumentNode { Q_OBJECT /** * @brief Color of the node the tree UI to highlight grouped items * * Generally parent/child relationshitps define groups but layers can * be grouped with each other even if they are children of a composition */ GLAXNIMATE_PROPERTY(QColor, group_color, QColor(0, 0, 0, 0), &VisualNode::on_group_color_changed) /** * \brief Visible setting for this node */ GLAXNIMATE_PROPERTY(bool, visible, true, &VisualNode::on_visible_changed, {}, PropertyTraits::Visual|PropertyTraits::Hidden) /** * \brief Locked setting for this node */ GLAXNIMATE_PROPERTY(bool, locked, false, &VisualNode::docnode_locked_changed) Q_PROPERTY(bool visible_recursive READ docnode_visible_recursive) Q_PROPERTY(bool locked_recursive READ docnode_locked_recursive) Q_PROPERTY(bool selectable READ docnode_selectable) public: enum PaintMode { Canvas, ///< Paint everything Render ///< Recursive, but hide objects marked with render == false }; explicit VisualNode(model::Document* document); QColor docnode_group_color() const; virtual VisualNode* docnode_group_parent() const; virtual int docnode_group_child_count() const; virtual VisualNode* docnode_group_child(int index) const; VisualNode* docnode_fuzzy_parent() const; VisualNode* docnode_visual_child(int index) const; VisualNode* docnode_visual_parent() const; ChildRange docnode_visual_children() const noexcept { return ChildRange{this, &VisualNode::docnode_visual_child, &VisualNode::docnode_child_count}; } ChildRange docnode_group_children() const noexcept { return ChildRange{this, &VisualNode::docnode_group_child, &VisualNode::docnode_group_child_count}; } /** * \brief Bounding rect in local coordinates (current frame) */ virtual QRectF local_bounding_rect(FrameTime t) const = 0; /** * \brief \b true iff this node and all of its ancestors are visible and unlocked */ bool docnode_selectable() const; /** * \brief \b true iff this node and all of its ancestors are visible */ bool docnode_visible_recursive() const; /** * \brief \b true iff this node or any of its ancestors is locked */ bool docnode_locked_recursive() const; /** * \brief Transform matrix mapping points from document coordinates to local coordinates */ QTransform transform_matrix(FrameTime t) const; QTransform group_transform_matrix(FrameTime t) const; /** * \brief Transform matrix mapping points from parent coordinates to local coordinates */ virtual QTransform local_transform_matrix(FrameTime) const { return QTransform(); } virtual void paint(QPainter* painter, FrameTime time, PaintMode mode, model::Modifier* modifier = nullptr) const; QIcon instance_icon() const override; signals: void docnode_visible_changed(bool); void docnode_locked_changed(bool); void docnode_visible_recursive_changed(bool); void docnode_group_color_changed(const QColor&); /** * \note Do not emit directly, call propagate_bounding_rect_changed instead */ void bounding_rect_changed(); void transform_matrix_changed(const QTransform& t); void group_transform_matrix_changed(const QTransform& t); void local_transform_matrix_changed(const QTransform& t); private: void propagate_visible(bool visible); void on_group_color_changed(const QColor& color); protected: void docnode_on_update_group(bool force = false); bool docnode_valid_color() const; void propagate_transform_matrix_changed(const QTransform& t_global, const QTransform& t_group); void propagate_bounding_rect_changed(); virtual void on_paint(QPainter*, FrameTime, PaintMode, model::Modifier*) const {} private: void on_visible_changed(bool visible); class Private; Private* dd() const; }; } // namespace glaxnimate::model Q_DECLARE_METATYPE(std::vector) src/modules/glaxnimate/glaxnimate/external/QtAppSetup/src/app/widgets/widget_palette_editor.cpp000664 001750 001750 00000015025 14477652011 036462 0ustar00ddennedyddennedy000000 000000 mlt-7.22.0/* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "widget_palette_editor.hpp" #include "ui_widget_palette_editor.h" #include #include #include #include #ifndef WITHOUT_QT_COLOR_WIDGETS #include #endif #include "app/settings/palette_settings.hpp" class WidgetPaletteEditor::Private { public: enum Roles { ColorRole = Qt::UserRole, ColorGroup }; void refresh_custom() { ui.palette_view->blockSignals(true); ui.palette_view->clearContents(); int i = 0; for ( const auto& p : app::settings::PaletteSettings::roles() ) { ui.palette_view->setItem(i, 0, color_item(edited, p.second, QPalette::Active)); ui.palette_view->setItem(i, 1, color_item(edited, p.second, QPalette::Disabled)); ++i; } ui.palette_view->blockSignals(false); } QTableWidgetItem* color_item(const QPalette& palette, QPalette::ColorRole role, QPalette::ColorGroup group) { auto item = new QTableWidgetItem(); QColor color = palette.color(group, role); item->setData(Qt::DisplayRole, color); item->setData(Qt::EditRole, color); item->setData(ColorRole, role); item->setData(ColorGroup, group); return item; } void setup_view() { ui.palette_view->blockSignals(true); ui.palette_view->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); #ifndef WITHOUT_QT_COLOR_WIDGETS ui.palette_view->setItemDelegateForColumn(0, &delegate); ui.palette_view->setItemDelegateForColumn(1, &delegate); #endif int i = 0; for ( const auto& p : app::settings::PaletteSettings::roles() ) { ui.palette_view->setRowCount(i+1); ui.palette_view->setVerticalHeaderItem(i, new QTableWidgetItem(p.first)); ui.palette_view->setItem(i, 0, color_item(settings->default_palette, p.second, QPalette::Active)); ui.palette_view->setItem(i, 1, color_item(settings->default_palette, p.second, QPalette::Disabled)); ++i; } ui.palette_view->blockSignals(false); } void add_palette(QString name_hint) { if ( name_hint.isEmpty() ) name_hint = tr("Custom"); QString name = name_hint; for ( int i = 1; settings->palettes.contains(name); i++ ) name = tr("%1 %2").arg(name_hint).arg(i); settings->palettes[name] = edited; ui.combo_saved->addItem(name); ui.combo_saved->setCurrentText(name); } bool use_default() { return ui.combo_saved->currentIndex() == 0; } bool use_built_in() { return ui.combo_saved->currentData().toBool(); } void apply_style(QStyle* style) { ui.preview_widget->setStyle(style); for ( auto wid : ui.preview_widget->findChildren() ) wid->setStyle(style); } app::settings::PaletteSettings* settings; Ui::WidgetPaletteEditor ui; #ifndef WITHOUT_QT_COLOR_WIDGETS color_widgets::ColorDelegate delegate; #endif QPalette edited; QStyle* style = nullptr; }; WidgetPaletteEditor::WidgetPaletteEditor ( app::settings::PaletteSettings* settings, QWidget* parent ) : QWidget ( parent ), d ( std::make_unique() ) { d->settings = settings; d->ui.setupUi ( this ); d->setup_view(); d->edited = settings->default_palette; d->ui.combo_saved->setItemData(0, true); for ( auto name : settings->palettes.keys() ) { d->ui.combo_saved->addItem(name, settings->palettes[name].built_in); } if ( settings->palettes.find(settings->selected) != settings->palettes.end() ) d->ui.combo_saved->setCurrentText(settings->selected); for ( const auto& style : QStyleFactory::keys() ) d->ui.combo_style->addItem(style); if ( !d->settings->style.isEmpty() ) d->ui.combo_style->setCurrentText(d->settings->style); connect(d->ui.combo_style, &QComboBox::currentTextChanged, this, [this](const QString& name){ auto old_style = d->style; d->style = QStyleFactory::create(name); d->apply_style(d->style); if ( old_style ) delete old_style; }); } WidgetPaletteEditor::~WidgetPaletteEditor() { if ( d->style ) delete d->style; } void WidgetPaletteEditor::changeEvent ( QEvent* e ) { QWidget::changeEvent ( e ); if ( e->type() == QEvent::LanguageChange ) { d->ui.retranslateUi ( this ); } } void WidgetPaletteEditor::update_color ( int row, int column ) { auto item = d->ui.palette_view->item(row, column); if ( !item ) return; auto group = item->data(Private::ColorGroup).value(); auto role = item->data(Private::ColorRole).value(); auto color = item->data(Qt::DisplayRole).value(); d->edited.setColor(group, role, color); if ( group == QPalette::Active ) d->edited.setColor(QPalette::Inactive, role, color); d->ui.preview_widget->setPalette(d->edited); if ( d->use_built_in() ) d->add_palette({}); } void WidgetPaletteEditor::select_palette(const QString& name) { if ( d->use_default() ) d->edited = d->settings->default_palette; else d->edited = d->settings->palettes[name]; d->refresh_custom(); d->ui.preview_widget->setPalette(d->edited); } void WidgetPaletteEditor::add_palette() { bool ok = false; QString default_name = d->ui.combo_saved->currentText(); if ( d->use_default() ) default_name = tr("Custom"); QString name_hint = QInputDialog::getText( this, tr("Add Theme"), tr("Name"), QLineEdit::Normal, default_name.isEmpty() ? tr("Custom") : default_name, &ok ); if ( !ok ) return; d->add_palette(name_hint); } void WidgetPaletteEditor::apply_palette() { if ( d->use_default() ) { d->settings->set_selected(""); } else { QString name = d->ui.combo_saved->currentText(); d->settings->palettes[name] = d->edited; d->settings->set_selected(name); } d->settings->set_style(d->ui.combo_style->currentText()); } void WidgetPaletteEditor::remove_palette() { if ( !d->use_built_in() ) { d->settings->palettes.remove(d->ui.combo_saved->currentText()); d->ui.combo_saved->removeItem(d->ui.combo_saved->currentIndex()); } } src/modules/glaxnimate/glaxnimate/external/QtAppSetup/src/app/scripting/python/python_engine.hpp000664 001750 001750 00000002300 14477652011 036633 0ustar00ddennedyddennedy000000 000000 mlt-7.22.0/* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include "app/scripting/script_engine.hpp" namespace app::scripting::python { class PythonContext : public ScriptExecutionContext { public: PythonContext(const ScriptEngine* engine); ~PythonContext(); void expose(const QString& name, const QVariant& obj) override; QString eval_to_string(const QString& code) override; bool run_from_module ( const QDir& path, const QString& module, const QString& function, const QVariantList& args ) override; const ScriptEngine* engine() const override; void app_module(const QString& name) override; private: class Private; std::unique_ptr d; }; class PythonEngine : public ScriptEngine { public: static void add_module_search_paths(const QStringList& paths); QString slug() const override { return "python"; } QString label() const override { return "Python"; } ScriptContext create_context() const override; private: static Autoregister autoreg; }; } // namespace app::scripting::python mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/avd/avd_renderer.hpp000664 001750 001750 00000001246 14477652011 030632 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include #include #include namespace glaxnimate::model { class Composition; } // namespace glaxnimate::model namespace glaxnimate::io::avd { class AvdRenderer { public: AvdRenderer(const std::function& on_warning); ~AvdRenderer(); void render(model::Composition* comp); QDomElement graphics(); QDomDocument single_file(); private: class Private; std::unique_ptr d; }; } // namespace glaxnimate::io::avd mlt-7.22.0/src/modules/glaxnimate/glaxnimate/external/QtAppSetup/src/app/debug/model.hpp000664 001750 001750 00000012510 14477652011 032713 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include namespace app::debug { namespace detail { inline void print_model_column(const QAbstractItemModel* model, int i, bool flags, const std::vector& roles, const QModelIndex& index, int indent) { auto logger = qDebug(); logger.noquote(); auto colindex = model->index(index.row(), i, index.parent()); for ( int role : roles ) { logger << QString(4*indent, ' ') << " *" << model->data(colindex, role); if ( flags ) logger << model->flags(colindex); } } struct DebugSlot { QString prefix; QString signal; template decltype(std::declval() << std::declval()) print(QDebug& stream, T&& t, bool) { return stream << std::forward(t); } template void print(QDebug&, T&&, ...){} void print_all(QDebug&) {} template void print_all(QDebug& stream, T&& t, Args&&... args) { print(stream, std::forward(t), true); print_all(stream, std::forward(args)...); } template void operator()(Args&&... args) { auto stream = qDebug(); if ( !prefix.isEmpty() ) stream << prefix; stream << signal; print_all(stream, std::forward(args)...); } }; } // namespace detail inline void print_model_row(const QAbstractItemModel* model, const QModelIndex& index, const std::vector& columns = {}, bool flags = false, const std::vector& roles = {Qt::DisplayRole}, int indent = 0) { int rows = model->rowCount(index); int cols = model->columnCount(index); qDebug().noquote() << QString(4*indent, ' ') << index << "rows" << rows << "cols" << cols << "ptr" << index.internalId(); if ( columns.empty() ) { for ( int i = 0; i < cols; i++ ) detail::print_model_column(model, i, flags, roles, index, indent); } else { for ( int i : columns ) detail::print_model_column(model, i, flags, roles, index, indent); } } inline void print_model(const QAbstractItemModel* model, const std::vector& columns = {}, bool flags = false, const std::vector& roles = {Qt::DisplayRole}, const QModelIndex& index = {}, int indent = 0) { print_model_row(model, index, columns, flags, roles, indent); for ( int i = 0; i < model->rowCount(index); i++ ) { QModelIndex ci = model->index(i, 0, index); if ( !ci.isValid() ) qDebug().noquote() << QString(4*(indent+1), ' ') << "invalid"; else print_model(model, columns, flags, roles, ci, indent+1); } } inline void connect_debug(QAbstractItemModel* model, const QString& prefix) { QObject::connect(model, &QAbstractItemModel::columnsAboutToBeInserted, model, detail::DebugSlot{prefix, "columnsAboutToBeInserted"}); QObject::connect(model, &QAbstractItemModel::columnsAboutToBeMoved, model, detail::DebugSlot{prefix, "columnsAboutToBeMoved"}); QObject::connect(model, &QAbstractItemModel::columnsAboutToBeRemoved, model, detail::DebugSlot{prefix, "columnsAboutToBeRemoved"}); QObject::connect(model, &QAbstractItemModel::columnsInserted, model, detail::DebugSlot{prefix, "columnsInserted"}); QObject::connect(model, &QAbstractItemModel::columnsMoved, model, detail::DebugSlot{prefix, "columnsMoved"}); QObject::connect(model, &QAbstractItemModel::columnsRemoved, model, detail::DebugSlot{prefix, "columnsRemoved"}); QObject::connect(model, &QAbstractItemModel::dataChanged, model, detail::DebugSlot{prefix, "dataChanged"}); QObject::connect(model, &QAbstractItemModel::headerDataChanged, model, detail::DebugSlot{prefix, "headerDataChanged"}); QObject::connect(model, &QAbstractItemModel::layoutAboutToBeChanged, model, detail::DebugSlot{prefix, "layoutAboutToBeChanged"}); QObject::connect(model, &QAbstractItemModel::layoutChanged, model, detail::DebugSlot{prefix, "layoutChanged"}); QObject::connect(model, &QAbstractItemModel::modelAboutToBeReset, model, detail::DebugSlot{prefix, "modelAboutToBeReset"}); QObject::connect(model, &QAbstractItemModel::modelReset, model, detail::DebugSlot{prefix, "modelReset"}); QObject::connect(model, &QAbstractItemModel::rowsAboutToBeInserted, model, detail::DebugSlot{prefix, "rowsAboutToBeInserted"}); QObject::connect(model, &QAbstractItemModel::rowsAboutToBeMoved, model, detail::DebugSlot{prefix, "rowsAboutToBeMoved"}); QObject::connect(model, &QAbstractItemModel::rowsAboutToBeRemoved, model, detail::DebugSlot{prefix, "rowsAboutToBeRemoved"}); QObject::connect(model, &QAbstractItemModel::rowsInserted, model, detail::DebugSlot{prefix, "rowsInserted"}); QObject::connect(model, &QAbstractItemModel::rowsMoved, model, detail::DebugSlot{prefix, "rowsMoved"}); QObject::connect(model, &QAbstractItemModel::rowsRemoved, model, detail::DebugSlot{prefix, "rowsRemoved"}); } } // namespace app::debug mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/shapes/polystar.cpp000664 001750 001750 00000004564 14477652011 031252 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "polystar.hpp" #include "math/math.hpp" GLAXNIMATE_OBJECT_IMPL(glaxnimate::model::PolyStar) glaxnimate::math::bezier::Bezier glaxnimate::model::PolyStar::to_bezier(model::FrameTime t) const { return draw( type.get(), position.get_at(t), inner_radius.get_at(t), outer_radius.get_at(t), math::deg2rad(angle.get_at(t)), points.get_at(t), inner_roundness.get_at(t), outer_roundness.get_at(t), reversed.get() ); } glaxnimate::math::bezier::Bezier glaxnimate::model::PolyStar::draw( model::PolyStar::StarType type, const QPointF& pos, float radius_inner, float radius_outer, float angle_radians, int p, float round_inner, float round_outer, bool reverse ) { math::bezier::Bezier bezier; bezier.close(); qreal direction = reverse ? -1 : 1; qreal halfd = math::pi / p * direction; qreal tangent_len_outer = round_outer * (math::tau * radius_outer) / (p * 4) * direction; qreal tangent_len_inner = round_inner * (math::tau * radius_inner) / (p * 4) * direction; for ( int i = 0; i < p; i++ ) { qreal main_angle = -math::pi / 2 + angle_radians + i * halfd * 2; qreal dx = radius_outer * math::cos(main_angle); qreal dy = radius_outer * math::sin(main_angle); QPointF tangents_outer; if ( radius_outer != 0 ) tangents_outer = { dy / radius_outer, -dx / radius_outer, }; bezier.add_point( QPointF(pos.x() + dx, pos.y() + dy), tangent_len_outer * tangents_outer, -tangent_len_outer * tangents_outer ); if ( type == Star ) { dx = radius_inner * math::cos(main_angle+halfd); dy = radius_inner * math::sin(main_angle+halfd); QPointF tangents_inner; if ( radius_inner != 0 ) tangents_inner = { dy / radius_inner, -dx / radius_inner, }; bezier.add_point( QPointF(pos.x() + dx, pos.y() + dy), tangent_len_inner * tangents_inner, -tangent_len_inner * tangents_inner ); } } return bezier; } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/animation/join_animatables.hpp000664 001750 001750 00000026051 14477652011 033370 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include #include "model/animation/animatable.hpp" namespace glaxnimate::model { /** * \brief Utility to join keyframes from multiple animatables */ class JoinAnimatables { private: using MidTransition = model::AnimatableBase::MidTransition; public: struct Keyframe { FrameTime time; std::vector values; std::vector transitions; Keyframe(FrameTime time, std::size_t prop_count) : time(time) { values.reserve(prop_count); transitions.reserve(prop_count); } static KeyframeTransition mix_transitions(const std::vector& transitions) { int count = 0; QPointF in; QPointF out; for ( const auto& transition : transitions ) { if ( !transition.hold() ) { in += transition.before(); out += transition.after(); count++; } } if ( count == 0 ) return {{0, 0}, {1, 1}, true}; return {in / count, out / count}; } KeyframeTransition transition() const { return mix_transitions(transitions); } }; enum Flags { Normal = 0x00, NoKeyframes = 0x01, NoValues = 0x02, }; using iterator = typename std::vector::const_iterator; JoinAnimatables(std::vector properties, int flags = Normal) : properties_(std::move(properties)) { if ( !(flags & NoKeyframes) ) load_keyframes(flags); } /** * \brief Whether the result has more than one keyframe */ bool animated() const { return keyframes_.size() > 1; } /** * \brief Keyframe range begin */ auto begin() const { return keyframes_.begin(); } /** * \brief Keyframe range end */ auto end() const { return keyframes_.end(); } /** * \brief Current value as a vector of variants */ std::vector current_value() const { std::vector values; values.reserve(properties_.size()); for ( auto prop : properties_ ) values.push_back(prop->value()); return values; } /** * \brief Value at time as a vector of variants */ std::vector value_at(FrameTime time) const { std::vector values; values.reserve(properties_.size()); for ( auto prop : properties_ ) values.push_back(prop->value(time)); return values; } const std::vector& properties() const { return properties_; } const std::vector& keyframes() const { return keyframes_; } /** * \brief Current value combined by a callback * \pre each property can be converted to the corresponding \p AnimatedProperty. */ template auto combine_current_value(const Func& func) { return invoke_combine_get(func, std::index_sequence_for()); } /** * \brief Value at a given time combined by a callback * \pre each property can be converted to the corresponding \p AnimatedProperty. */ template auto combine_value_at(FrameTime time, const Func& func) { return invoke_combine_get_at(time, func, std::index_sequence_for()); } /** * \brief Fills \p target with the combined values * \pre Each property can be converted to the corresponding \p AnimatedProperty. * \pre \p target values can be initialized by what \p func returns */ template void apply_to(Target* target, const Func& func, const model::AnimatedProperty*...) { target->clear_keyframes(); target->set(combine_current_value(func)); for ( const auto& keyframe : keyframes_ ) { auto real_kf = target->set_keyframe(keyframe.time, combine_value_at(keyframe.time, func)); real_kf->set_transition(keyframe.transition()); } } private: std::vector properties_; std::vector keyframes_; void load_keyframes(int flags) { std::set time_set; for ( auto prop : properties_ ) for ( int i = 0, e = prop->keyframe_count(); i < e; i++ ) time_set.insert(prop->keyframe(i)->time()); std::vector time_vector(time_set.begin(), time_set.end()); time_set.clear(); std::vector> mids; mids.reserve(time_vector.size()); for ( FrameTime t : time_vector ) { mids.push_back({}); mids.back().reserve(properties_.size()); for ( auto prop : properties_ ) mids.back().push_back(prop->mid_transition(t)); } keyframes_.reserve(time_vector.size()); for ( std::size_t i = 0; i < time_vector.size(); i++ ) { keyframes_.emplace_back(time_vector[i], properties_.size()); for ( std::size_t j = 0; j < properties_.size(); j++ ) { if ( !(flags & NoValues) ) keyframes_.back().values.push_back(mids[i][j].value); keyframes_.back().transitions.push_back(mids[i][j].to_next); if ( mids[i][j].type == MidTransition::Middle && i > 0 && mids[i-1][j].type != MidTransition::Middle ) { keyframes_[i-1].transitions[j] = mids[i][j].from_previous; } } } } template auto invoke_combine_get(const Func& func, std::integer_sequence) { return func(static_cast*>(properties_[Indices])->get()...); } template auto invoke_combine_get_at(FrameTime t, const Func& func, std::integer_sequence) { return func(static_cast*>(properties_[Indices])->get_at(t)...); } }; /** * \brief JoinAnimatables implementing AnimatableBase */ class JoinedAnimatable : public AnimatableBase, public JoinAnimatables { public: using ConversionFunction = std::function& args)>; class Keyframe : public KeyframeBase { public: Keyframe(JoinedAnimatable* parent, const JoinAnimatables::Keyframe* subkf) : KeyframeBase(subkf->time), parent(parent), subkf(subkf) { set_transition(subkf->transition()); } Keyframe(JoinedAnimatable* parent, model::FrameTime time) : KeyframeBase(time), parent(parent), subkf(nullptr) {} std::vector get() const { if ( subkf ) return subkf->values; else return parent->value_at(time()); } QVariant value() const override { if ( subkf ) return parent->converter(subkf->values); else return parent->converter(parent->value_at(time())); } // read only bool set_value(const QVariant&) override { return false; } protected: std::unique_ptr do_clone() const override { return std::make_unique(parent, subkf); } class Splitter : public KeyframeSplitter { public: Splitter(const Keyframe* a, const Keyframe* b) : a(a), b(b) {} void step(const QPointF&) override {} std::unique_ptr left(const QPointF& p) const override { return std::make_unique( a->parent, math::lerp(a->time(), b->time(), p.x()) ); } std::unique_ptr right(const QPointF& p) const override { return std::make_unique( a->parent, math::lerp(a->time(), b->time(), p.x()) ); } std::unique_ptr last() const override { return b->clone(); } const Keyframe* a; const Keyframe* b; }; std::unique_ptr splitter(const KeyframeBase* other) const override { return std::make_unique(this, static_cast(other)); } private: JoinedAnimatable* parent; const JoinAnimatables::Keyframe* subkf = nullptr; }; JoinedAnimatable(std::vector properties, ConversionFunction converter, int flags = Normal) : AnimatableBase(nullptr, "", {}), JoinAnimatables(std::move(properties), flags), converter(std::move(converter)) { wrapped_keyframes.reserve(keyframes().size()); for ( auto& kf : keyframes() ) wrapped_keyframes.push_back(std::make_unique(this, &kf)); } int keyframe_count() const override { return wrapped_keyframes.size(); } using JoinAnimatables::animated; const KeyframeBase* keyframe(int i) const override { return wrapped_keyframes[i].get(); } KeyframeBase* keyframe(int i) override { return wrapped_keyframes[i].get(); } QVariant value(FrameTime time) const override { return converter(value_at(time)); } QVariant value() const override { return converter(current_value()); } // read only bool set_value(const QVariant&) override { return false; } bool valid_value(const QVariant&) const override { return false; } bool set_undoable(const QVariant&, bool) override { return false; } int move_keyframe(int, FrameTime) override { return -1; } bool value_mismatch() const override { return false; } KeyframeBase* set_keyframe(FrameTime , const QVariant& , SetKeyframeInfo*, bool ) override { return nullptr; } void remove_keyframe(int) override {}; void clear_keyframes() override {}; bool remove_keyframe_at_time(FrameTime) override { return false; } protected: void on_set_time(FrameTime) override {} // Shouldn't be needed QVariant do_mid_transition_value(const KeyframeBase*, const KeyframeBase*, qreal) const override { return {}; } private: ConversionFunction converter; std::vector> wrapped_keyframes; }; } // namespace glaxnimate::model mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/simple_visitor.hpp000664 001750 001750 00000001416 14477652011 031160 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include "visitor.hpp" namespace glaxnimate::model { template class SimpleVisitor : public Visitor { public: SimpleVisitor(Callback callback) : callback(std::move(callback)) {} private: void on_visit(model::DocumentNode* node) override { if ( auto obj = node->cast() ) callback(obj); } Callback callback; }; template void simple_visit(model::DocumentNode* node, bool skip_locked, Callback callback) { SimpleVisitor(std::move(callback)).visit(node, skip_locked); } } // namespace glaxnimate::model mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/math/bezier/point.hpp000664 001750 001750 00000005143 14477652011 030353 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include #include "math/vector.hpp" #include "math/math.hpp" namespace glaxnimate::math::bezier { enum PointType { Corner, Smooth, Symmetrical, }; struct Point { QPointF pos; QPointF tan_in; QPointF tan_out; PointType type; Point( const QPointF& pos, const QPointF& tan_in, const QPointF& tan_out, PointType type = Corner ) : pos(pos), tan_in(tan_in), tan_out(tan_out), type(type) {} Point(const QPointF& pos = {0, 0}) : Point(pos, pos, pos, Corner) {} static Point from_relative( const QPointF& pos, const QPointF& tan_in_rel = {0, 0}, const QPointF& tan_out_rel = {0, 0}, PointType type = Corner ) { return {pos, pos+tan_in_rel, pos+tan_out_rel, type}; } QPointF relative_tan_in() const { return tan_in - pos; } QPointF relative_tan_out() const { return tan_out - pos; } PolarVector polar_tan_in() const { return relative_tan_in(); } PolarVector polar_tan_out() const { return relative_tan_out(); } void drag_tan_in(const QPointF& p) { tan_in = p; tan_out = drag_tangent(tan_in, tan_out, pos, type); } void drag_tan_out(const QPointF& p) { tan_out = p; tan_in = drag_tangent(tan_out, tan_in, pos, type); } void adjust_handles_from_type(); void set_point_type(PointType t) { type = t; adjust_handles_from_type(); } static QPointF drag_tangent(const QPointF& dragged, const QPointF& other, const QPointF& pos, PointType type) { if ( type == math::bezier::PointType::Symmetrical ) { return 2*pos - dragged; } else if ( type == math::bezier::PointType::Smooth ) { return math::PolarVector{ math::length(other - pos), pi + math::angle(dragged - pos) }.to_cartesian() + pos; } return other; } void translate(const QPointF& delta) { pos += delta; tan_in += delta; tan_out += delta; } void translate_to(const QPointF& new_pos) { translate(new_pos - pos); } void transform(const QTransform& t); QPointF position() const { return pos; } }; } // namespace glaxnimate::math::bezier Q_DECLARE_METATYPE(glaxnimate::math::bezier::Point) mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/000775 001750 001750 00000000000 14506450142 024267 5ustar00ddennedyddennedy000000 000000 mlt-7.22.0/src/modules/glaxnimate/glaxnimate/external/QtAppSetup/src/app/log/logger.cpp000664 001750 001750 00000000225 14477652011 032560 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "logger.hpp" mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/aep/aep_riff.hpp000664 001750 001750 00000001661 14477652011 027741 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include "riff.hpp" namespace glaxnimate::io::aep { class AepRiff : public RiffReader { public: static bool is_fake_list(const ChunkId& header) { return header == "tdsn" || header == "fnam" || header == "pdnm"; } protected: void on_chunk(RiffChunk & chunk) override { if ( is_fake_list(chunk.header) ) { chunk.children = read_chunks(chunk.reader); } else if ( chunk.header == "LIST" ) { chunk.subheader = chunk.reader.read(4); if ( chunk.subheader != "btdk" ) chunk.children = read_chunks(chunk.reader); else chunk.reader.defer(); } else { chunk.reader.defer(); } } }; } // namespace glaxnimate::io::aep mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/svg/detail.cpp000664 001750 001750 00000004021 14477652011 027446 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "detail.hpp" const std::map glaxnimate::io::svg::detail::xmlns = { {"osb", "http://www.openswatchbook.org/uri/2009/osb"}, {"dc", "http://purl.org/dc/elements/1.1/"}, {"cc", "http://creativecommons.org/ns#"}, {"rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#"}, {"svg", "http://www.w3.org/2000/svg"}, {"sodipodi", "http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"}, {"inkscape", "http://www.inkscape.org/namespaces/inkscape"}, {"xlink", "http://www.w3.org/1999/xlink"}, {"android", "http://schemas.android.com/apk/res/android"}, {"aapt", "http://schemas.android.com/aapt"}, }; const std::unordered_set glaxnimate::io::svg::detail::css_atrrs = { "fill", "alignment-baseline", "baseline-shift", "clip-path", "clip-rule", "color", "color-interpolation", "color-interpolation-filters", "color-rendering", "cursor", "direction", "display", "dominant-baseline", "fill-opacity", "fill-rule", "filter", "flood-color", "flood-opacity", "font-family", "font-size", "font-size-adjust", "font-stretch", "font-style", "font-variant", "font-weight", "glyph-orientation-horizontal", "glyph-orientation-vertical", "image-rendering", "letter-spacing", "lighting-color", "marker-end", "marker-mid", "marker-start", "mask", "opacity", "overflow", "paint-order", "pointer-events", "shape-rendering", "stop-color", "stop-opacity", "stroke", "stroke-dasharray", "stroke-dashoffset", "stroke-linecap", "stroke-linejoin", "stroke-miterlimit", "stroke-opacity", "stroke-width", "text-anchor", "text-decoration", "text-overflow", "text-rendering", "unicode-bidi", "vector-effect", "visibility", "white-space", "word-spacing", "writing-mode" }; mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/base.cpp000664 001750 001750 00000005473 14477652011 026333 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "base.hpp" #include "model/assets/assets.hpp" QString glaxnimate::io::ImportExport::name_filter() const { QString ext_str; for ( const QString& ext : extensions() ) { ext_str += "*." + ext + " "; } if ( ext_str.isEmpty() ) return {}; ext_str.resize(ext_str.size() - 1); //: Open/Save file dialog file filter eg: "Text files (.txt)" return tr("%1 (%2)").arg(name()).arg(ext_str); } QByteArray glaxnimate::io::ImportExport::save(model::Composition* comp, const QVariantMap& setting_values, const QString& filename) { QByteArray data; QBuffer file(&data); file.open(QIODevice::WriteOnly); QVariantMap clean_setting_values = setting_values; if ( auto settings = save_settings(comp) ) { for ( const auto& setting : *settings ) clean_setting_values[setting.slug] = setting.get_variant(clean_setting_values); } if ( !save(file, filename, comp, clean_setting_values) ) return {}; return data; } bool glaxnimate::io::ImportExport::save ( QIODevice& file, const QString& filename, model::Document* document, const QVariantMap& setting_values ) { if ( document->assets()->compositions->values.empty() ) return false; return save(file, filename, document->assets()->compositions->values[0], setting_values); } bool glaxnimate::io::ImportExport::open(QIODevice& file, const QString& filename, model::Document* document, const QVariantMap& setting_values) { if ( !file.isOpen() && auto_open() ) if ( !file.open(QIODevice::ReadOnly) ) return false; bool ok = on_open(file, filename, document, setting_values); emit completed(ok); return ok; } bool glaxnimate::io::ImportExport::save(QIODevice& file, const QString& filename, model::Composition* comp, const QVariantMap& setting_values) { if ( !file.isOpen() && auto_open() ) if ( !file.open(QIODevice::WriteOnly) ) return false; bool ok = on_save(file, filename, comp, setting_values); emit completed(ok); return ok; } bool glaxnimate::io::ImportExport::load(model::Document* document, const QByteArray& data, const QVariantMap& setting_values, const QString& filename) { if ( !document ) return false; QBuffer file(const_cast(&data)); file.open(QIODevice::ReadOnly); QVariantMap clean_setting_values = setting_values; if ( auto settings = open_settings() ) { for ( const auto& setting : *settings ) clean_setting_values[setting.slug] = setting.get_variant(clean_setting_values); } return open(file, filename, document, clean_setting_values); } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/assets/named_color.hpp000664 001750 001750 00000001276 14477652011 031700 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include "brush_style.hpp" #include "model/animation/animatable.hpp" namespace glaxnimate::model { class NamedColor : public BrushStyle { GLAXNIMATE_OBJECT(NamedColor) GLAXNIMATE_ANIMATABLE(QColor, color, QColor(0, 0, 0), &NamedColor::invalidate_icon) public: using BrushStyle::BrushStyle; QString type_name_human() const override; QBrush brush_style(FrameTime t) const override; bool remove_if_unused(bool clean_lists) override; protected: void fill_icon(QPixmap& icon) const override; }; } // namespace glaxnimate::model mlt-7.22.0/src/modules/glaxnimate/glaxnimate/external/QtAppSetup/src/app/widgets/settings_dialog.hpp000664 001750 001750 00000001021 14477652011 035345 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #ifndef SETTINGSDIALOG_H #define SETTINGSDIALOG_H #include #include namespace app { class SettingsDialog : public QDialog { Q_OBJECT public: SettingsDialog(QWidget* parent=nullptr); ~SettingsDialog(); protected: void changeEvent(QEvent *e) override; private: class Private; std::unique_ptr d; }; } // namespace app #endif // SETTINGSDIALOG_H mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/shapes/round_corners.cpp000664 001750 001750 00000004724 14477652011 032255 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "round_corners.hpp" GLAXNIMATE_OBJECT_IMPL(glaxnimate::model::RoundCorners) QIcon glaxnimate::model::RoundCorners::static_tree_icon() { return QIcon::fromTheme("transform-affect-rounded-corners"); } QString glaxnimate::model::RoundCorners::static_type_name_human() { return tr("Round Corners"); } bool glaxnimate::model::RoundCorners::process_collected() const { return false; } static std::pair get_vert_tan(const glaxnimate::math::bezier::Bezier& bezier, const QPointF& current_vertex, int closest_index, qreal round_distance) { // value from https://spencermortensen.com/articles/bezier-circle/ static const qreal tangent_length = 0.5519; if ( closest_index < 0 ) closest_index += bezier.size(); auto closest_vertex = bezier[closest_index].pos; auto distance = glaxnimate::math::length(current_vertex - closest_vertex); auto new_pos_perc = distance != 0 ? glaxnimate::math::min(distance/2, round_distance) / distance : 0; auto vertex = current_vertex + (closest_vertex - current_vertex) * new_pos_perc; auto tangent = - (vertex - current_vertex) * tangent_length; return {vertex, tangent}; } static glaxnimate::math::bezier::Bezier round_corners(const glaxnimate::math::bezier::Bezier& original, qreal round_distance) { glaxnimate::math::bezier::Bezier result; result.set_closed(original.closed()); for ( int i = 0; i < original.size(); i++ ) { if ( !original.closed() && (i == 0 || i == original.size() - 1) ) { result.push_back(original[i]); } else { QPointF vert1, vert2, out_t, in_t; std::tie(vert1, out_t) = get_vert_tan(original, original[i].pos, i - 1, round_distance); result.add_point(vert1, {0, 0}, out_t); std::tie(vert2, in_t) = get_vert_tan(original, original[i].pos, i + 1, round_distance); result.add_point(vert2, in_t, {0, 0}); } } return result; } glaxnimate::math::bezier::MultiBezier glaxnimate::model::RoundCorners::process(glaxnimate::model::FrameTime t, const math::bezier::MultiBezier& mbez) const { qreal round_distance = radius.get_at(t); math::bezier::MultiBezier result; for ( const auto& inbez : mbez.beziers() ) result.beziers().push_back(round_corners(inbez, round_distance)); return result; } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/utils/000775 001750 001750 00000000000 14477652011 025435 5ustar00ddennedyddennedy000000 000000 mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/rive/000775 001750 001750 00000000000 14477652011 025651 5ustar00ddennedyddennedy000000 000000 mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/raster/000775 001750 001750 00000000000 14477652011 026204 5ustar00ddennedyddennedy000000 000000 mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/avd/000775 001750 001750 00000000000 14477652011 025456 5ustar00ddennedyddennedy000000 000000 mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/plugin/snippet.hpp000664 001750 001750 00000005132 14477652011 027767 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include "plugin.hpp" #include "app/application.hpp" namespace glaxnimate::plugin { class Snippet { public: explicit Snippet(const QString& name = {}) { set_name(name); } const QString& set_name(const QString& name) { QString clean_name; for ( const auto& ch : name ) { if ( ch.isLetterOrNumber() || ch == ' ' || ch == '_' || ch == '-' || ch == '.' || ch == '\'' || ch == '"' ) clean_name.push_back(ch); } if ( !clean_name.isEmpty() ) { if ( name_.isEmpty() ) { name_ = clean_name; } else if ( name_ != clean_name && QFileInfo(filename()).exists() ) { if ( QFile::rename(filename(), snippet_filename(clean_name)) ) name_ = clean_name; } } return name_; } const QString& name() const { return name_; } static QString snippet_path() { return app::Application::instance()->writable_data_path("snippets"); } static QString snippet_filename(const QString& basename) { return snippet_path() + "/" + basename + ".py"; } QString filename() const { return snippet_filename(name_); } QString get_source() const { QFile file(filename()); if ( !file.open(QFile::ReadOnly|QIODevice::Text) ) return {}; return QString::fromUtf8(file.readAll()); } bool ensure_file_exists() const { QFileInfo finfo(filename()); if ( finfo.exists() ) return true; QDir parent(snippet_path()); if ( !parent.exists() ) { parent.cdUp(); if ( !parent.mkpath("snippets") ) return false; } QFile file(filename()); if ( !file.open(QFile::WriteOnly|QIODevice::Text) ) return false; file.write(QApplication::tr("# Glaxnimate snippet").toUtf8()); file.write("\n"); file.write(QApplication::tr("# You have access to the `window` and `document` global variables and the `glaxnimate` module").toUtf8()); file.write("\n"); file.write(QApplication::tr("# For documentation see https://glaxnimate.mattbas.org/contributing/scripting/").toUtf8()); file.write("\n"); file.write("\n"); return true; } private: QString name_; }; } // namespace glaxnimate::plugin mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/assets/composition.hpp000664 001750 001750 00000004645 14477652011 031764 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include "asset.hpp" #include "model/property/object_list_property.hpp" #include "model/shapes/layer.hpp" namespace glaxnimate::model { class Composition : public VisualNode, public AssetBase { GLAXNIMATE_OBJECT(Composition) GLAXNIMATE_PROPERTY_LIST(model::ShapeElement, shapes) GLAXNIMATE_SUBOBJECT(AnimationContainer, animation) // type name default notify validate GLAXNIMATE_PROPERTY(float, fps, 60, &Composition::fps_changed, &Composition::validate_fps) GLAXNIMATE_PROPERTY(int, width, 512, &Composition::width_changed, &Composition::validate_nonzero, PropertyTraits::Visual) GLAXNIMATE_PROPERTY(int, height, 512, &Composition::height_changed, &Composition::validate_nonzero, PropertyTraits::Visual) Q_PROPERTY(QSize size READ size) Q_PROPERTY(QRectF rect READ rect) public: using VisualNode::VisualNode; utils::Range top_level() const { return { Layer::ChildLayerIterator(&shapes, nullptr, 0), Layer::ChildLayerIterator(&shapes, nullptr, shapes.size()) }; } DocumentNode* docnode_child(int index) const override { return shapes[index]; } int docnode_child_count() const override { return shapes.size(); } int docnode_child_index(DocumentNode* dn) const override; QRectF local_bounding_rect(FrameTime t) const override; QIcon tree_icon() const override; QString type_name_human() const override; bool remove_if_unused(bool clean_lists) override; DocumentNode* docnode_parent() const override; QSize size() const { return {width.get(), height.get()}; } QRectF rect() const { return QRectF(QPointF(0, 0), size()); } Q_INVOKABLE QImage render_image(float time, QSize size = {}, const QColor& background = {}) const; Q_INVOKABLE QImage render_image() const; signals: void fps_changed(float fps); void width_changed(int); void height_changed(int); private: bool validate_nonzero(int size) const { return size > 0; } bool validate_out_point(int p) const { return p > 0; } bool validate_fps(float v) const { return v > 0; } }; } // namespace glaxnimate::model src/modules/glaxnimate/glaxnimate/external/QtAppSetup/src/app/settings/palette_settings.hpp000664 001750 001750 00000004476 14477652011 035700 0ustar00ddennedyddennedy000000 000000 mlt-7.22.0/* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include "custom_settings_group.hpp" #include #include namespace app::settings { class PaletteSettings : public CustomSettingsGroupBase { public: class Palette : public QPalette { public: Palette() = default; Palette(const QPalette& oth, bool built_in = false) : QPalette(oth), built_in(built_in) {} Palette(QPalette&& oth, bool built_in = false) : QPalette(std::move(oth)), built_in(built_in) {} Palette(const Palette& oth) : QPalette(oth), built_in(oth.built_in) {} Palette(Palette&& oth) : QPalette(std::move(oth)), built_in(oth.built_in) {} Palette& operator=(const Palette& oth) { QPalette::operator=(oth); built_in = oth.built_in; return *this; } Palette& operator=(Palette&& oth) { QPalette::operator=(std::move(oth)); built_in = oth.built_in; return *this; } Palette& operator=(const QPalette& oth) { QPalette::operator=(oth); return *this; } Palette& operator=(QPalette&& oth) { QPalette::operator=(std::move(oth)); return *this; } bool built_in = false; }; PaletteSettings(); QString slug() const override; QString label() const override; QIcon icon() const override; void load ( QSettings & settings ) override; void save ( QSettings & settings ) override; QWidget * make_widget ( QWidget * parent ) override; void load_palette(const QSettings& settings, bool mark_built_in = false); void write_palette(QSettings& settings, const QString& name, const QPalette& palette); const QPalette& palette() const; static const std::vector>& roles(); void set_selected(const QString& name); void apply_palette(const QPalette& palette); void set_style(const QString& name); static QString color_to_string(const QColor& c); static QColor string_to_color(const QString& s); QMap palettes; QString selected; Palette default_palette; QString style; }; } // namespace app::settings mlt-7.22.0/src/modules/glaxnimate/glaxnimate/external/000775 001750 001750 00000000000 14506447326 024404 5ustar00ddennedyddennedy000000 000000 mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/raster/spritesheet_format.hpp000664 001750 001750 00000001756 14477652011 032635 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include "io/base.hpp" #include "io/io_registry.hpp" namespace glaxnimate::io::raster { class SpritesheetFormat : public ImportExport { Q_OBJECT public: QString slug() const override { return "spritesheet"; } QString name() const override { return tr("Sprite Sheet"); } QStringList extensions() const override; std::unique_ptr save_settings(model::Composition* comp) const override; bool can_save() const override { return true; } bool can_open() const override { return false; } int priority() const override { return -2; } protected: bool on_save(QIODevice & file, const QString & filename, model::Composition* comp, const QVariantMap & setting_values) override; private: static Autoreg autoreg; }; } // namespace glaxnimate::io::raster mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/assets/asset.cpp000664 001750 001750 00000000224 14477652011 030520 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "asset.hpp" mlt-7.22.0/src/modules/glaxnimate/glaxnimate/external/QtAppSetup/src/app/translation_service.hpp000664 001750 001750 00000003322 14477652011 034604 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include class QTranslator; namespace app { class TranslationService { public: static TranslationService& instance() { static TranslationService instance; return instance; } void change_lang_code ( QString code ); /** * \brief Initialize the resource system */ void initialize(QString default_lang_code="en"); /** * \brief Determine human readable language name from ISO 639-1 code * * Depending on Qt version the returned string is either in English or * in the language itself. * * If lang_code is not rectognised, a null string is returned */ QString language_name ( QString lang_code ); /** * \brief Register a translation * * \param name Human-readable language name * \param code ISO 639-1 language code * \param file Path to the translation file, if empty no file gets loaded */ void register_translation ( QString name, QString code, QString file ); QString current_language_name(); QString current_language_code(); QTranslator* translator(); /** * \brief Map of language names to codes */ const QMap& available_languages(); private: TranslationService() = default; TranslationService(const TranslationService&) = delete; ~TranslationService() = default; QMap lang_names; ///< map lang_name -> lang_code QMap translators; ///< map lang_code -> translator QString current_language; }; } // namespace app mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/math/bezier/segment.hpp000664 001750 001750 00000000461 14477652011 030662 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include namespace glaxnimate::math::bezier { using BezierSegment = std::array; } // namespace glaxnimate::math::bezier mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/application_info_generated.in.hpp000664 001750 001750 00000000624 14477652011 032751 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #define PROJECT_VERSION "${PROJECT_VERSION}" #define PROJECT_SLUG "${PROJECT_SLUG}" #define PROJECT_NAME "${PROJECT_NAME}" #define URL_DOCS "${URL_DOCS}" #define URL_ISSUES "${URL_ISSUES}" #define URL_DONATE "${URL_DONATE}" #define PROJECT_DESCRIPTION "${PROJECT_DESCRIPTION}" mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/binary_types.hpp000664 001750 001750 00000000422 14477652011 030123 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include namespace glaxnimate::io { using VarUint = std::uint64_t; using Float32 = float; } // namespace glaxnimate::io mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/utils/trace.hpp000664 001750 001750 00000002302 14477652011 027241 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include #include #include "math/bezier/bezier.hpp" namespace glaxnimate::utils::trace { class TraceOptions { public: TraceOptions(); ~TraceOptions(); qreal smoothness() const; void set_smoothness(qreal smoothness); int min_area() const; void set_min_area(int min_area); private: friend class Tracer; class Private; std::unique_ptr d; }; class Tracer : public QObject { Q_OBJECT public: Tracer(const QImage& image, const TraceOptions& options); ~Tracer(); bool trace(math::bezier::MultiBezier& output); static QString potrace_version(); void set_progress_range(double min, double max); void set_target_alpha(int threshold, bool invert); void set_target_color(const QColor& color, qint32 tolerance); void set_target_index(uchar index); signals: void progress(double value); private: class Private; std::unique_ptr d; }; std::map> trace_pixels(QImage image); } // namespace glaxnimate::utils::trace mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/aep/aep_format.hpp000664 001750 001750 00000002710 14477652011 030277 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include "io/base.hpp" #include "io/io_registry.hpp" namespace glaxnimate::io::aep { struct RiffChunk; class AepFormat : public ImportExport { Q_OBJECT public: QString slug() const override { return "aep"; } QString name() const override { return tr("Adobe After Effects Project"); } QStringList extensions() const override { return {"aep"}; } bool can_save() const override { return false; } bool can_open() const override { return true; } protected: bool on_open(QIODevice& file, const QString&, model::Document* document, const QVariantMap& options) override; bool riff_to_document(const RiffChunk& chunk, model::Document* document, const QString& filename); private: static Autoreg autoreg; }; class AepxFormat : public AepFormat { Q_OBJECT public: QString slug() const override { return "aepx"; } QString name() const override { return tr("Adobe After Effects Project XML"); } QStringList extensions() const override { return {"aepx"}; } bool can_save() const override { return false; } bool can_open() const override { return true; } protected: bool on_open(QIODevice& file, const QString&, model::Document* document, const QVariantMap& options) override; private: static Autoreg autoreg; }; } // namespace glaxnimate::io::aep mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/shapes/rect.hpp000664 001750 001750 00000001641 14477652011 030330 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include "shape.hpp" namespace glaxnimate::model { class Rect : public Shape { GLAXNIMATE_OBJECT(Rect) GLAXNIMATE_ANIMATABLE(QPointF, position, QPointF()) GLAXNIMATE_ANIMATABLE(QSizeF, size, QSizeF()) GLAXNIMATE_ANIMATABLE(float, rounded, 0, {}, 0) public: using Shape::Shape; QIcon tree_icon() const override { return QIcon::fromTheme("draw-rectangle"); } QString type_name_human() const override { return tr("Rectangle"); } math::bezier::Bezier to_bezier(FrameTime t) const override; QRectF local_bounding_rect(FrameTime t) const override { QSizeF sz = size.get_at(t); return QRectF(position.get_at(t) - QPointF(sz.width()/2, sz.height()/2), sz); } }; } // namespace glaxnimate::model mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/raster/raster_format.hpp000664 001750 001750 00000001634 14477652011 031571 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include "io/base.hpp" #include "io/io_registry.hpp" #include "model/shapes/image.hpp" #include "model/assets/assets.hpp" namespace glaxnimate::io::raster { class RasterFormat : public ImportExport { Q_OBJECT public: QString slug() const override { return "raster"; } QString name() const override { return tr("Raster Image"); } QStringList extensions() const override; bool can_save() const override { return false; } bool can_open() const override { return true; } int priority() const override { return -1; } protected: bool on_open(QIODevice& dev, const QString&, model::Document* document, const QVariantMap&) override; private: static Autoreg autoreg; }; } // namespace glaxnimate::io::raster mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/math/ellipse_solver.cpp000664 001750 001750 00000010341 14477652011 030760 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "ellipse_solver.hpp" #include "bezier/bezier.hpp" #include "vector.hpp" #include "math.hpp" using namespace glaxnimate; math::EllipseSolver::EllipseSolver(const QPointF& center, const QPointF& radii, qreal xrot) : center(center), radii(radii), xrot(xrot) {} QPointF math::EllipseSolver::point(qreal t) const { return QPointF( center.x() + radii.x() * qCos(xrot) * qCos(t) - radii.y() * qSin(xrot) * qSin(t), center.y() + radii.x() * qSin(xrot) * qCos(t) + radii.y() * qCos(xrot) * qSin(t) ); } QPointF math::EllipseSolver::derivative(qreal t) const { return QPointF( - radii.x() * qCos(xrot) * qSin(t) - radii.y() * qSin(xrot) * qCos(t), - radii.x() * qSin(xrot) * qSin(t) + radii.y() * qCos(xrot) * qCos(t) ); } math::bezier::Bezier math::EllipseSolver::to_bezier(qreal anglestart, qreal angle_delta) { bezier::Bezier points; qreal angle1 = anglestart; qreal angle_left = qAbs(angle_delta); qreal step = math::pi / 2; qreal sign = anglestart+angle_delta < angle1 ? -1 : 1; // We need to fix the first handle qreal firststep = qMin(angle_left, step) * sign; qreal alpha = _alpha(firststep); QPointF q1 = derivative(angle1) * alpha; points.push_back(bezier::Point::from_relative(point(angle1), QPointF(0, 0), q1, math::bezier::Symmetrical)); // Then we iterate until the angle has been completed qreal tolerance = step / 2; do { qreal lstep = qMin(angle_left, step); qreal step_sign = lstep * sign; qreal angle2 = angle1 + step_sign; angle_left -= abs(lstep); alpha = _alpha(step_sign); QPointF p2 = point(angle2); QPointF q2 = derivative(angle2) * alpha; points.push_back(bezier::Point::from_relative(p2, -q2, q2, math::bezier::Symmetrical)); angle1 = angle2; } while ( angle_left > tolerance ); if ( points.size() > 1 && qFuzzyCompare(angle_delta, math::tau) ) { points.close(); points[0].tan_in = points.back().tan_in; points.points().pop_back(); } return points; } math::bezier::Bezier math::EllipseSolver::from_svg_arc( QPointF start, qreal rx, qreal ry, qreal xrot, bool large, bool sweep, QPointF dest ) { rx = qAbs(rx); ry = qAbs(ry); qreal x1 = start.x(); qreal y1 = start.y(); qreal x2 = dest.x(); qreal y2 = dest.y(); qreal phi = pi * xrot / 180; QPointF p1 = _matrix_mul(phi, (start-dest)/2, -1); qreal x1p = p1.x(); qreal y1p = p1.y(); qreal cr = (x1p * x1p) / (rx * rx) + (y1p * y1p) / (ry * ry); if ( cr > 1 ) { qreal s = qSqrt(cr); rx *= s; ry *= s; } qreal dq = rx * rx * y1p * y1p + ry * ry * x1p * x1p; qreal pq = (rx * rx * ry * ry - dq) / dq; qreal cpm = qSqrt(qMax(0., pq)); if ( large == sweep ) cpm = -cpm; QPointF cp(cpm * rx * y1p / ry, -cpm * ry * x1p / rx); QPointF c = _matrix_mul(phi, cp) + QPointF((x1+x2)/2, (y1+y2)/2); qreal theta1 = _angle(QPointF(1, 0), QPointF((x1p - cp.x()) / rx, (y1p - cp.y()) / ry)); qreal deltatheta = std::fmod(_angle( QPointF((x1p - cp.x()) / rx, (y1p - cp.y()) / ry), QPointF((-x1p - cp.x()) / rx, (-y1p - cp.y()) / ry) ), 2*pi); if ( !sweep && deltatheta > 0 ) deltatheta -= 2*pi; else if ( sweep && deltatheta < 0 ) deltatheta += 2*pi; return EllipseSolver(c, QPointF(rx, ry), phi).to_bezier(theta1, deltatheta); } qreal math::EllipseSolver::_alpha(qreal step) { return qSin(step) * (qSqrt(4+3*qPow(qTan(step/2), 2)) - 1) / 3; } QPointF math::EllipseSolver::_matrix_mul(qreal phi, const QPointF p, qreal sin_mul) { qreal c = qCos(phi); qreal s = qSin(phi) * sin_mul; qreal xr = c * p.x() - s * p.y(); qreal yr = s * p.x() + c * p.y(); return QPointF(xr, yr); } qreal math::EllipseSolver::_angle(const QPointF& u, const QPointF& v) { qreal arg = qAcos(qMax(-1., qMin(1., QPointF::dotProduct(u, v) / (length(u) * length(v))))); if ( u.x() * v.y() - u.y() * v.x() < 0 ) return -arg; return arg; } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/factory.cpp000664 001750 001750 00000000476 14477652011 027557 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "factory.hpp" glaxnimate::model::Object* glaxnimate::model::Factory::static_build(const QString& name, glaxnimate::model::Document* doc) { return instance().build(name, doc); } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/000775 001750 001750 00000000000 14477652011 025375 5ustar00ddennedyddennedy000000 000000 mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/shapes/styler.hpp000664 001750 001750 00000002272 14477652011 030716 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include "shape.hpp" #include "model/assets/assets.hpp" #include "model/property/reference_property.hpp" namespace glaxnimate::model { /** * \brief Base class for elements that add a style */ class Styler : public ShapeOperator { Q_OBJECT GLAXNIMATE_ANIMATABLE(QColor, color, QColor()) GLAXNIMATE_ANIMATABLE(float, opacity, 1, {}, 0, 1, false, PropertyTraits::Percent) GLAXNIMATE_PROPERTY_REFERENCE(BrushStyle, use, &Styler::valid_uses, &Styler::is_valid_use, &Styler::on_use_changed) public: using ShapeOperator::ShapeOperator; void add_shapes(FrameTime, math::bezier::MultiBezier&, const QTransform&) const override {} protected: QBrush brush(FrameTime t) const; private: std::vector valid_uses() const; bool is_valid_use(DocumentNode* node) const; void on_use_changed(BrushStyle* new_use, BrushStyle* old_use); void on_update_style(); signals: void use_changed(BrushStyle* new_use); void use_changed_from(BrushStyle* old_use, BrushStyle* new_use); }; } // namespace glaxnimate::model mlt-7.22.0/src/modules/glaxnimate/glaxnimate/external/QtAppSetup/src/app/log/log_line.hpp000664 001750 001750 00000001002 14477652011 033070 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include #include namespace app::log { enum Severity { Info, Warning, Error, }; struct LogLine { Severity severity; QString source; QString source_detail; QString message; QDateTime time; }; } // namespace app::log Q_DECLARE_METATYPE(app::log::LogLine) Q_DECLARE_METATYPE(app::log::Severity) mlt-7.22.0/src/modules/glaxnimate/glaxnimate/external/QtAppSetup/src/app/settings/widget_builder.hpp000664 001750 001750 00000017114 14477652011 035363 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include #include #include #include #include #include #include #ifndef WITHOUT_QT_COLOR_WIDGETS #include "QtColorWidgets/ColorSelector" #endif #include "app/settings/setting.hpp" #include #include "app/settings/settings_group.hpp" namespace app::settings { class WidgetBuilder { public: void add_widgets(const SettingList& settings_list, QWidget* parent, QFormLayout* layout, QVariantMap& target, const QString& name_infix = {}) const { for ( const Setting& opt : settings_list ) { if ( opt.type == Setting::Internal ) continue; target[opt.slug] = opt.get_variant(target); QWidget* wid = make_setting_widget(opt, target); if ( !wid ) continue; QLabel* label = new QLabel(opt.label, parent); label->setToolTip(opt.description); wid->setParent(parent); wid->setToolTip(opt.description); wid->setWhatsThis(opt.description); wid->setObjectName(object_name("widget", name_infix, opt.slug)); label->setObjectName(object_name("label", name_infix, opt.slug)); layout->addRow(label, wid); } } void translate_widgets(const SettingList& settings_list, QWidget* parent, const QString& name_infix = {}) { for ( const Setting& opt : settings_list ) { if ( opt.type == Setting::Internal ) continue; if ( QWidget* wid = parent->findChild(object_name("widget", name_infix, opt.slug)) ) { wid->setToolTip(opt.description); wid->setWhatsThis(opt.description); } if ( QLabel* label = parent->findChild(object_name("label", name_infix, opt.slug)) ) { label->setToolTip(opt.description); label->setText(opt.label); } } } bool show_dialog(const SettingList& settings_list, QVariantMap& target, const QString& title, QWidget* parent = nullptr) { QDialog dialog(parent); dialog.setWindowTitle(title); QFormLayout layout; dialog.setLayout(&layout); add_widgets(settings_list, &dialog, &layout, target); QDialogButtonBox box(QDialogButtonBox::Ok|QDialogButtonBox::Cancel); layout.setWidget(layout.rowCount(), QFormLayout::SpanningRole, &box); QObject::connect(&box, &QDialogButtonBox::accepted, &dialog, &QDialog::accept); QObject::connect(&box, &QDialogButtonBox::rejected, &dialog, &QDialog::reject); if ( dialog.exec() == QDialog::Rejected ) return false; return true; } private: QString object_name(const QString& labwid, const QString& name_infix, const QString& slug) const { return QString("__settings_%1__%2%3").arg(labwid).arg(name_infix).arg(slug); } QWidget* make_setting_widget(const Setting& opt, QVariantMap& target) const { if ( !opt.choices.isEmpty() ) { auto wid = new QComboBox(); int index = 0; QVariant val = opt.get_variant(target); for ( const QString& key : opt.choices.keys() ) { QVariant choice = opt.choices[key]; wid->addItem(key, choice); if ( choice == val ) wid->setCurrentIndex(index); index++; } QObject::connect(wid, &QComboBox::currentTextChanged, [wid, slug=opt.slug, &target, side_effects=opt.side_effects](){ target[slug] = wid->currentData(); if ( side_effects ) side_effects(wid->currentData()); }); return wid; } else if ( opt.type == Setting::Info ) { return new QLabel(opt.description); } else if ( opt.type == Setting::Bool ) { auto wid = new QCheckBox(); wid->setChecked(opt.get(target)); QObject::connect(wid, &QCheckBox::toggled, SettingSetter{opt.slug, &target, opt.side_effects}); return wid; } else if ( opt.type == Setting::Int ) { auto wid = new QSpinBox(); if ( opt.min == opt.max && opt.max == -1 ) { wid->setMinimum(std::numeric_limits::min()); wid->setMaximum(std::numeric_limits::max()); } else { wid->setMinimum(opt.min); wid->setMaximum(opt.max); } wid->setValue(opt.get(target)); QObject::connect(wid, (void(QSpinBox::*)(int))&QSpinBox::valueChanged, SettingSetter{opt.slug, &target, opt.side_effects}); return wid; } else if ( opt.type == Setting::Float ) { auto wid = new QDoubleSpinBox(); if ( opt.min == opt.max && opt.max == -1 ) { wid->setMinimum(std::numeric_limits::min()); wid->setMaximum(std::numeric_limits::max()); } else { wid->setMinimum(opt.min); wid->setMaximum(opt.max); } wid->setValue(opt.get(target)); QObject::connect(wid, (void(QDoubleSpinBox::*)(double))&QDoubleSpinBox::valueChanged, SettingSetter{opt.slug, &target, opt.side_effects}); return wid; } else if ( opt.type == Setting::String ) { auto wid = new QLineEdit(); wid->setText(opt.get(target)); QObject::connect(wid, &QLineEdit::textChanged, SettingSetter{opt.slug, &target, opt.side_effects}); return wid; } #ifndef WITHOUT_QT_COLOR_WIDGETS else if ( opt.type == Setting::Color ) { auto wid = new color_widgets::ColorSelector(); wid->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); wid->setColor(opt.get(target)); QObject::connect(wid, &color_widgets::ColorSelector::colorChanged, SettingSetter{opt.slug, &target, opt.side_effects}); return wid; } #endif return nullptr; } template struct SettingSetter { QString slug; QVariantMap* options; std::function side_effects; void operator()(T v) { if ( side_effects ) side_effects(v); (*options)[slug] = QVariant(v); } }; }; class SettingsGroupWidget : public QWidget { public: SettingsGroupWidget(SettingsGroup* group, QWidget* parent = nullptr) : QWidget(parent), group(group) { QFormLayout* lay = new QFormLayout(this); this->setLayout(lay); bob.add_widgets(group->settings(), this, lay, group->values(), group->slug() + "__"); } void changeEvent(QEvent *e) { QWidget::changeEvent(e); if ( e->type() == QEvent::LanguageChange) { bob.translate_widgets(group->settings(), this, group->slug() + "__"); } } private: app::settings::WidgetBuilder bob; SettingsGroup* group; }; } // namespace app::settings mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/property/reference_property.hpp000664 001750 001750 00000010717 14477652011 033702 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include "model/document_node.hpp" #include "model/property/property.hpp" #define GLAXNIMATE_PROPERTY_REFERENCE(type, name, ...) \ public: \ ReferenceProperty name{this, #name, __VA_ARGS__}; \ type* get_##name() const { return name.get(); } \ bool set_##name(type* v) \ { \ return name.set_undoable(QVariant::fromValue(v)); \ } \ private: \ Q_PROPERTY(type* name READ get_##name WRITE set_##name) \ Q_CLASSINFO(#name, "property ref " #type) \ // macro end namespace glaxnimate::model { class ReferencePropertyBase : public BaseProperty { Q_GADGET public: ReferencePropertyBase( Object* obj, const QString& name, PropertyCallback> valid_options, PropertyCallback is_valid_option, PropertyTraits::Flags flags = PropertyTraits::Visual) : BaseProperty(obj, name, PropertyTraits{PropertyTraits::ObjectReference, flags}), valid_options_(std::move(valid_options)), is_valid_option_(std::move(is_valid_option)) { } bool valid_value(const QVariant & v) const override { return is_valid_option_(object(), v.value()); } std::vector valid_options() const { return valid_options_(object()); } bool is_valid_option(DocumentNode* ptr) const { return is_valid_option_(object(), ptr); } void set_time(FrameTime) override {} void transfer(Document*) override; virtual bool set_ref(model::DocumentNode* t) = 0; virtual model::DocumentNode* get_ref() const = 0; private: PropertyCallback> valid_options_; PropertyCallback is_valid_option_; protected: // static void remove_user(ReferencePropertyBase*, void*) {} // static void add_user(ReferencePropertyBase*, void*) {} static void remove_user(ReferencePropertyBase* prop, model::DocumentNode* obj) { obj->remove_user(prop); } static void add_user(ReferencePropertyBase* prop, model::DocumentNode* obj) { obj->add_user(prop); } }; template class ReferenceProperty : public ReferencePropertyBase { public: using value_type = Type*; ReferenceProperty( Object* obj, const QString& name, PropertyCallback> valid_options, PropertyCallback is_valid_option, PropertyCallback on_changed = {}, PropertyTraits::Flags flags = PropertyTraits::Visual) : ReferencePropertyBase(obj, name, std::move(valid_options), std::move(is_valid_option), flags), on_changed(std::move(on_changed)) { } bool set(Type* value) { if ( !is_valid_option(value) ) return false; set_force(value); return true; } void set_force(Type* value) { auto old = value_; value_ = value; value_changed(); if ( old ) remove_user(this, old); if ( value ) add_user(this, value); on_changed(object(), value_, old); } Type* get() const { return value_; } QVariant value() const override { if ( !value_ ) return {}; return QVariant::fromValue(value_); } bool set_value(const QVariant& val) override { if ( val.isNull() ) return set(nullptr); if ( auto v = detail::variant_cast(val) ) return set(*v); return true; } Type* operator->() const { return value_; } bool set_ref(model::DocumentNode* t) override { if ( !t ) { set_force(nullptr); return true; } if ( auto p = qobject_cast(t) ) return set(p); return false; } model::DocumentNode* get_ref() const override { return value_; } private: Type* value_ = nullptr; PropertyCallback on_changed; }; } // namespace glaxnimate::model mlt-7.22.0/src/modules/glaxnimate/glaxnimate/external/QtAppSetup/src/app/settings/000775 001750 001750 00000000000 14477652011 031655 5ustar00ddennedyddennedy000000 000000 mlt-7.22.0/src/modules/glaxnimate/glaxnimate/external/QtAppSetup/src/app/scripting/js/js_engine.cpp000664 001750 001750 00000000412 14477652011 035075 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "js_engine.hpp" app::scripting::ScriptEngine::Autoregister app::scripting::js::JsEngine::autoreg; mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/binary_stream.hpp000664 001750 001750 00000002066 14477652011 030260 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include #include "io/binary_types.hpp" namespace glaxnimate::io { class BinaryInputStream { public: explicit BinaryInputStream(QIODevice* file); explicit BinaryInputStream(QByteArray data); quint32 read_uint32_le(); Float32 read_float32_le(); VarUint read_uint_leb128(); bool eof() const; bool has_error() const; QByteArray read(qint64 max_size); quint8 next(); private: void on_overflow(); private: QByteArray data; const char* data_start; const char* data_end; bool error = false; }; class BinaryOutputStream { public: explicit BinaryOutputStream(QIODevice* file); void write_uint32_le(quint32 v); void write_float32_le(Float32 v); void write_uint_leb128(VarUint v); void write_byte(quint8 v); void write(const QByteArray& data); private: QIODevice* file = nullptr; }; } // namespace glaxnimate::io modules/glaxnimate/glaxnimate/external/QtAppSetup/src/app/scripting/python/register_machinery.cpp000664 001750 001750 00000052566 14477652011 037666 0ustar00ddennedyddennedy000000 000000 mlt-7.22.0/src/* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "register_machinery.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "app/log/log.hpp" namespace app::scripting::python { template const char* type_name() { #if QT_VERSION_MAJOR >= 6 return QMetaType(qMetaTypeId()).name(); #else return QMetaType::typeName(qMetaTypeId()); #endif } template struct meta_2_cpp_s; template struct cpp_2_meta_s; #define TYPE_NAME(Type) //template<> const char* type_name() { return #Type; } #define SETUP_TYPE(MetaInt, Type) \ TYPE_NAME(Type) \ template<> struct meta_2_cpp_s { using type = Type; }; \ template<> struct cpp_2_meta_s { static constexpr const int value = MetaInt; }; template using meta_2_cpp = typename meta_2_cpp_s::type; template constexpr const int cpp_2_meta = cpp_2_meta_s::value; SETUP_TYPE(QMetaType::Int, int) SETUP_TYPE(QMetaType::Bool, bool) SETUP_TYPE(QMetaType::Double, double) SETUP_TYPE(QMetaType::Float, float) SETUP_TYPE(QMetaType::UInt, unsigned int) SETUP_TYPE(QMetaType::Long, long) SETUP_TYPE(QMetaType::LongLong, long long) SETUP_TYPE(QMetaType::Short, short) SETUP_TYPE(QMetaType::ULong, unsigned long) SETUP_TYPE(QMetaType::ULongLong, unsigned long long) SETUP_TYPE(QMetaType::UShort, unsigned short) SETUP_TYPE(QMetaType::QString, QString) SETUP_TYPE(QMetaType::QColor, QColor) SETUP_TYPE(QMetaType::QUuid, QUuid) SETUP_TYPE(QMetaType::QObjectStar, QObject*) SETUP_TYPE(QMetaType::QVariantList, QVariantList) SETUP_TYPE(QMetaType::QVariant, QVariant) SETUP_TYPE(QMetaType::QStringList, QStringList) SETUP_TYPE(QMetaType::QVariantMap, QVariantMap) SETUP_TYPE(QMetaType::QVariantHash, QVariantHash) SETUP_TYPE(QMetaType::QPointF, QPointF) SETUP_TYPE(QMetaType::QSizeF, QSizeF) SETUP_TYPE(QMetaType::QSize, QSize) SETUP_TYPE(QMetaType::QVector2D, QVector2D) SETUP_TYPE(QMetaType::QRectF, QRectF) SETUP_TYPE(QMetaType::QByteArray, QByteArray) SETUP_TYPE(QMetaType::QDateTime, QDateTime) SETUP_TYPE(QMetaType::QDate, QDate) SETUP_TYPE(QMetaType::QTime, QTime) SETUP_TYPE(QMetaType::QImage, QImage) // If you add stuff here, remember to add it to supported_types too TYPE_NAME(std::vector) using supported_types = std::integer_sequence; template QVariant qvariant_from_cpp(const T& t) { return QVariant::fromValue(t); } template T qvariant_to_cpp(const QVariant& v) { return v.value(); } template<> QVariant qvariant_from_cpp(const std::string& t) { return QString::fromStdString(t); } template<> std::string qvariant_to_cpp(const QVariant& v) { return v.toString().toStdString(); } template<> QVariant qvariant_from_cpp(const QVariant& t) { return t; } template<> QVariant qvariant_to_cpp(const QVariant& v) { return v; } template<> void qvariant_to_cpp(const QVariant&) {} template<> QVariant qvariant_from_cpp>(const std::vector& t) { QVariantList list; for ( QObject* obj : t ) list.push_back(QVariant::fromValue(obj)); return list; } template<> std::vector qvariant_to_cpp>(const QVariant& v) { std::vector objects; for ( const QVariant& vi : v.toList() ) objects.push_back(vi.value()); return objects; } template class Func, class RetT, class... FuncArgs> bool type_dispatch_impl_step(int meta_type, RetT& ret, FuncArgs&&... args) { if ( meta_type != i ) return false; ret = Func>::do_the_thing(std::forward(args)...); return true; } template class Func, class RetT, class... FuncArgs, int... i> bool type_dispatch_impl(int meta_type, RetT& ret, std::integer_sequence, FuncArgs&&... args) { return (type_dispatch_impl_step(meta_type, ret, std::forward(args)...)||...); } template class Func, class RetT, class... FuncArgs> RetT type_dispatch(int meta_type, FuncArgs&&... args) { if ( meta_type >= QMetaType::User ) { if ( QMetaType(meta_type).flags() & QMetaType::IsEnumeration ) return Func::do_the_thing(std::forward(args)...); return Func::do_the_thing(std::forward(args)...); } RetT ret; type_dispatch_impl(meta_type, ret, supported_types(), std::forward(args)...); return ret; } template class Func, class RetT, class... FuncArgs> static RetT type_dispatch_maybe_void(int meta_type, FuncArgs&&... args) { if ( meta_type == QMetaType::Void ) return Func::do_the_thing(std::forward(args)...); return type_dispatch(meta_type, std::forward(args)...); } std::string fix_type(QByteArray ba) { if ( ba.endsWith('*') || ba.endsWith('&') ) ba.remove(ba.size()-1, 1); if ( ba.startsWith("const ") ) ba.remove(0, 6); if ( ba == "QString" ) return "str"; else if ( ba == "QVariantList" ) return "list"; if ( ba == "QStringList" ) return "List[str]"; else if ( ba == "double" ) return "float"; else if ( ba == "void" ) return "None"; else if ( ba == "QImage" ) return "PIL.Image.Image"; return ba.toStdString(); } template struct RegisterProperty { static PyPropertyInfo do_the_thing(const QMetaProperty& prop) { PyPropertyInfo py; py.name = prop.name(); std::string sig = "Type: " + fix_type(prop.typeName()); py.get = py::cpp_function( [prop](const QObject* o) { return qvariant_to_cpp(prop.read(o)); }, py::return_value_policy::automatic_reference, sig.c_str() ); if ( prop.isWritable() ) py.set = py::cpp_function([prop](QObject* o, const CppType& v) { prop.write(o, qvariant_from_cpp(v)); }); return py; } }; PyPropertyInfo register_property(const QMetaProperty& prop, const QMetaObject& meta) { if ( !prop.isScriptable() ) return {}; PyPropertyInfo pyprop = type_dispatch(prop.userType(), prop); if ( !pyprop.name ) log::LogStream("Python", "", log::Error) << "Invalid property" << meta.className() << "::" << prop.name() << "of type" << prop.userType() << prop.typeName(); return pyprop; } class ArgumentBuffer { public: ArgumentBuffer(const QMetaMethod& method) : method(method) {} ArgumentBuffer(const ArgumentBuffer&) = delete; ArgumentBuffer& operator=(const ArgumentBuffer&) = delete; ~ArgumentBuffer() { for ( int i = 0; i < destructors_used; i++) { destructors[i]->destruct(); delete destructors[i]; } } template const char* object_type_name(const CppType&) { return type_name(); } std::string object_type_name(QObject* value) { std::string s = value->metaObject()->className(); std::string target = method.parameterTypes()[arguments].toStdString(); if ( !target.empty() && target.back() == '*' ) { target.pop_back(); if ( s != target ) { for ( auto mo = value->metaObject()->superClass(); mo; mo = mo->superClass() ) { std::string moname = mo->className(); if ( moname == target ) return target + "*"; } } } return s + "*"; } template CppType* allocate(const CppType& value) { if ( avail() < int(sizeof(CppType)) ) throw py::type_error("Cannot allocate argument"); CppType* addr = new (next_mem()) CppType; buffer_used += sizeof(CppType); names[arguments] = object_type_name(value); generic_args[arguments] = { names[arguments].c_str(), addr }; ensure_destruction(addr); arguments += 1; *addr = value; return addr; } template void allocate_return_type(const char* name) { if ( avail() < int(sizeof(CppType)) ) throw py::type_error("Cannot allocate return value"); CppType* addr = new (next_mem()) CppType; buffer_used += sizeof(CppType); ret = { name, addr }; ensure_destruction(addr); ret_addr = addr; } template CppType return_value() { return *static_cast(ret_addr); } const QGenericArgument& arg(int i) const { return generic_args[i]; } const QGenericReturnArgument& return_arg() const { return ret; } private: class Destructor { public: Destructor() = default; Destructor(const Destructor&) = delete; Destructor& operator=(const Destructor&) = delete; virtual ~Destructor() = default; virtual void destruct() const = 0; }; template struct DestructorImpl : public Destructor { DestructorImpl(CppType* addr) : addr(addr) {} void destruct() const override { addr->~CppType(); } CppType* addr; }; int arguments = 0; int buffer_used = 0; std::array buffer; std::array destructors; std::array generic_args; std::array names; QGenericReturnArgument ret; void* ret_addr = nullptr; QMetaMethod method; int destructors_used = 0; int avail() { return buffer.size() - buffer_used; } void* next_mem() { return buffer.data() + buffer_used; } template std::enable_if_t> ensure_destruction(CppType*) {} template std::enable_if_t> ensure_destruction(CppType* addr) { destructors[destructors_used] = new DestructorImpl(addr); destructors_used++; } }; template<> void ArgumentBuffer::allocate_return_type(const char*){} template<> void ArgumentBuffer::return_value(){} template struct ConvertArgument { static bool do_the_thing(const py::handle& val, ArgumentBuffer& buf) { buf.allocate(val.cast()); return true; } }; bool convert_argument(int meta_type, const py::handle& value, ArgumentBuffer& buf) { return type_dispatch(meta_type, value, buf); } template struct RegisterMethod { static PyMethodInfo do_the_thing(const QMetaMethod& meth, py::handle& handle) { PyMethodInfo py; py.name = meth.name(); std::string signature = "Signature:\n"; signature += meth.name().toStdString(); signature += "(self"; auto names = meth.parameterNames(); auto types = meth.parameterTypes(); for ( int i = 0; i < meth.parameterCount(); i++ ) { signature += ", "; signature += names[i].toStdString(); signature += ": "; signature += fix_type(types[i]); if ( meth.parameterType(i) == QMetaType::UnknownType ) { auto cls = py::str(handle.attr("__name__")); log::LogStream("Python", "", log::Error) << "Invalid parameter" << QString::fromStdString(cls) << "::" << meth.name() << i << names[i] << "of type" << meth.parameterType(i) << types[i]; return {}; } } signature += ") -> "; signature += fix_type(meth.typeName()); py.method = py::cpp_function( [meth](QObject* o, py::args args) -> ReturnType { int len = py::len(args); if ( len > 9 ) throw pybind11::value_error("Invalid argument count"); ArgumentBuffer argbuf(meth); argbuf.allocate_return_type(meth.typeName()); for ( int i = 0; i < len; i++ ) { if ( !convert_argument(meth.parameterType(i), args[i], argbuf) ) throw pybind11::value_error("Invalid argument"); } // Calling by name from QMetaObject ensures that default arguments work correctly bool ok = QMetaObject::invokeMethod( o, meth.name(), Qt::DirectConnection, argbuf.return_arg(), argbuf.arg(0), argbuf.arg(1), argbuf.arg(2), argbuf.arg(3), argbuf.arg(4), argbuf.arg(5), argbuf.arg(6), argbuf.arg(7), argbuf.arg(8), argbuf.arg(9) ); if ( !ok ) throw pybind11::value_error("Invalid method invocation"); return argbuf.return_value(); }, py::name(py.name), py::is_method(handle), py::sibling(py::getattr(handle, py.name, py::none())), py::return_value_policy::automatic_reference, signature.c_str() ); return py; } }; PyMethodInfo register_method(const QMetaMethod& meth, py::handle& handle, const QMetaObject& cls) { if ( meth.access() != QMetaMethod::Public ) return {}; if ( meth.methodType() != QMetaMethod::Method && meth.methodType() != QMetaMethod::Slot ) return {}; if ( meth.parameterCount() > 9 ) { log::LogStream("Python", "", log::Error) << "Too many arguments for method " << cls.className() << "::" << meth.name() << ": " << meth.parameterCount(); return {}; } PyMethodInfo pymeth = type_dispatch_maybe_void(meth.returnType(), meth, handle); if ( !pymeth.name ) log::LogStream("Python", "", log::Error) << "Invalid method" << cls.className() << "::" << meth.name() << "return type" << meth.returnType() << meth.typeName(); return pymeth; } template bool qvariant_type_caster_load_impl(QVariant& into, const pybind11::handle& src) { auto caster = pybind11::detail::make_caster>(); if ( caster.load(src, false) ) { into = QVariant::fromValue(pybind11::detail::cast_op>(caster)); return true; } return false; } template<> bool qvariant_type_caster_load_impl(QVariant&, const pybind11::handle&) { return false; } template bool qvariant_type_caster_load(QVariant& into, const pybind11::handle& src, std::integer_sequence) { return (qvariant_type_caster_load_impl(into, src)||...); } template bool qvariant_type_caster_cast_impl( pybind11::handle& into, const QVariant& src, pybind11::return_value_policy policy, const pybind11::handle& parent) { if ( src.userType() == i ) { into = pybind11::detail::make_caster>::cast(src.value>(), policy, parent); return true; } return false; } template<> bool qvariant_type_caster_cast_impl( pybind11::handle&, const QVariant&, pybind11::return_value_policy, const pybind11::handle&) { return false; } template bool qvariant_type_caster_cast( pybind11::handle& into, const QVariant& src, pybind11::return_value_policy policy, const pybind11::handle& parent, std::integer_sequence ) { return (qvariant_type_caster_cast_impl(into, src, policy, parent)||...); } } // namespace app::scripting::python using namespace app::scripting::python; std::map::CustomConverter> pybind11::detail::type_caster::custom_converters; bool pybind11::detail::type_caster::load(handle src, bool) { if ( src.ptr() == Py_None ) { value = QVariant(); return true; } if ( qvariant_type_caster_load(value, src, supported_types()) ) return true; for ( const auto& p : custom_converters ) if ( p.second.load(src, value) ) return true; return false; } pybind11::handle pybind11::detail::type_caster::cast(QVariant src, return_value_policy policy, handle parent) { if ( src.isNull() ) return pybind11::none(); policy = py::return_value_policy::automatic_reference; int meta_type = src.userType(); if ( meta_type >= QMetaType::User ) { if ( QMetaType(meta_type).flags() & QMetaType::IsEnumeration ) return pybind11::detail::make_caster::cast(src.value(), policy, parent); else if ( meta_type == qMetaTypeId() ) return pybind11::detail::make_caster::cast(src.value(), policy, parent); auto it = custom_converters.find(meta_type); if ( it != custom_converters.end() ) return it->second.cast(src, policy, parent); return pybind11::detail::make_caster::cast(src.value(), policy, parent); } pybind11::handle ret; qvariant_type_caster_cast(ret, src, policy, parent, supported_types()); return ret; } bool pybind11::detail::type_caster::load(handle src, bool ic) { if ( isinstance(src, pybind11::module_::import("uuid").attr("UUID")) ) src = py::str(src); type_caster stdc; if ( stdc.load(src, ic) ) { value = QUuid::fromString((const QString &)stdc); return true; } return false; } pybind11::handle pybind11::detail::type_caster::cast(QUuid src, return_value_policy policy, handle parent) { auto str = type_caster::cast(src.toString(), policy, parent); return pybind11::module_::import("uuid").attr("UUID")(str).release(); } bool pybind11::detail::type_caster::load(handle src, bool) { if ( !isinstance(src, pybind11::module_::import("PIL.Image").attr("Image")) ) return false; py::object obj = py::reinterpret_borrow(src); std::string mode = obj.attr("mode").cast(); QImage::Format format; if ( mode == "RGBA" ) { format = QImage::Format_RGBA8888; } else if ( mode == "RGB" ) { format = QImage::Format_RGB888; } else if ( mode == "RGBa" ) { format = QImage::Format_RGBA8888_Premultiplied; } else if ( mode == "RGBX" ) { format = QImage::Format_RGBX8888; } else { format = QImage::Format_RGBA8888; obj = obj.attr("convert")("RGBA"); } std::string data = obj.attr("tobytes")().cast(); int width = obj.attr("width").cast(); int height = obj.attr("height").cast(); value = QImage(width, height, format); if ( data.size() != std::size_t(value.sizeInBytes()) ) return false; std::memcpy(value.bits(), data.data(), data.size()); return true; } pybind11::handle pybind11::detail::type_caster::cast(QImage src, return_value_policy, handle) { auto mod = pybind11::module_::import("PIL.Image"); auto frombytes = mod.attr("frombytes"); const char* mode; switch ( src.format() ) { case QImage::Format_Invalid: return mod.attr("Image")().release(); case QImage::Format_RGB888: mode = "RGB"; break; case QImage::Format_RGBA8888: mode = "RGBA"; break; case QImage::Format_RGBA8888_Premultiplied: mode = "RGBa"; break; case QImage::Format_RGBX8888: mode = "RGBX"; break; default: mode = "RGBA"; src = src.convertToFormat(QImage::Format_RGBA8888); break; } py::tuple size(2); size[0] = py::int_(src.width()); size[1] = py::int_(src.height()); auto image = frombytes( mode, size, py::bytes((const char*)src.bits(), src.sizeInBytes()) ); return image.release(); } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/math/geom.hpp000664 001750 001750 00000002025 14477652011 026665 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include #include "vector.hpp" namespace glaxnimate::math { /** * \brief Finds the closest point to a line * \param line_a Point to determine the line * \param line_b Second point to determine the line * \param p Point to find the closest of * \returns The point on the line that is closest to \p p */ QPointF line_closest_point(const QPointF& line_a, const QPointF& line_b, const QPointF& p); /** * \brief Gets the center of the circle passing through 3 points */ QPointF circle_center(const QPointF& p1, const QPointF& p2, const QPointF& p3); /** * \brief Intersection point between two lines, each defined by two points * \note the intersection might lay outside the segments provided */ std::optional line_intersection(const QPointF& start1, const QPointF& end1, const QPointF& start2, const QPointF& end2); } // namespace glaxnimate::math mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/math/bezier/bezier.hpp000664 001750 001750 00000020546 14477652011 030506 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include #include #include "math/bezier/solver.hpp" #include "math/bezier/point.hpp" #include "math/bezier/segment.hpp" namespace glaxnimate::math::bezier { using Solver = math::bezier::CubicBezierSolver; class Bezier { public: using value_type = Point; Bezier() = default; explicit Bezier(const Point& initial_point) : points_(1, initial_point) {} explicit Bezier(const QPointF& initial_point) : points_(1, initial_point) {} const std::vector& points() const { return points_; } std::vector& points() { return points_; } int size() const { return points_.size(); } int closed_size() const { return points_.size() + (closed_ ? 1 : 0); } bool empty() const { return points_.empty(); } auto begin() { return points_.begin(); } auto begin() const { return points_.begin(); } auto cbegin() const { return points_.begin(); } auto end() { return points_.end(); } auto end() const { return points_.end(); } auto cend() const { return points_.end(); } void push_back(const Point& p) { points_.push_back(p); } void clear() { points_.clear(); closed_ = false; } const Point& back() const { return points_.back(); } Point& back() { return points_.back(); } const Point& operator[](int index) const { return points_[index % points_.size()]; } Point& operator[](int index) { return points_[index % points_.size()]; } bool closed() const { return closed_; } void set_closed(bool closed) { closed_ = closed; } /** * \brief Inserts a point at the given index * \param index Index to insert the point at * \param p Point to add * \returns \c this, for easy chaining */ Bezier& insert_point(int index, const Point& p) { points_.insert(points_.begin() + qBound(0, index, size()), p); return *this; } /** * \brief Appends a point to the curve (relative tangents) * \see insert_point() */ Bezier& add_point(const QPointF& p, const QPointF& in_t = {0, 0}, const QPointF& out_t = {0, 0}) { points_.push_back(Point::from_relative(p, in_t, out_t)); return *this; } /** * \brief Appends a point with symmetrical (relative) tangents * \see insert_point() */ Bezier& add_smooth_point(const QPointF& p, const QPointF& in_t) { points_.push_back(Point::from_relative(p, in_t, -in_t, Smooth)); return *this; } /** * \brief Closes the bezier curve * \returns \c this, for easy chaining */ Bezier& close() { closed_ = true; return *this; } /** * \brief Line from the last point to \p p * \returns \c this, for easy chaining */ Bezier& line_to(const QPointF& p) { if ( !empty() ) points_.back().tan_out = points_.back().pos; points_.push_back(p); return *this; } /** * \brief Quadratic bezier from the last point to \p dest * \param handle Quadratic bezier handle * \param dest Destination point * \returns \c this, for easy chaining */ Bezier& quadratic_to(const QPointF& handle, const QPointF& dest) { if ( !empty() ) points_.back().tan_out = points_.back().pos + 2.0/3.0 * (handle - points_.back().pos); push_back(dest); points_.back().tan_in = points_.back().pos + 2.0/3.0 * (handle - points_.back().pos); return *this; } /** * \brief Cubic bezier from the last point to \p dest * \param handle1 First cubic bezier handle * \param handle2 Second cubic bezier handle * \param dest Destination point * \returns \c this, for easy chaining */ Bezier& cubic_to(const QPointF& handle1, const QPointF& handle2, const QPointF& dest) { if ( !empty() ) points_.back().tan_out = handle1; push_back(dest); points_.back().tan_in = handle2; return *this; } /** * \brief Reverses the orders of the points */ void reverse(); QRectF bounding_box() const; /** * \brief Split a segmet * \param index index of the point at the beginning of the segment to split * \param factor Value between [0,1] to determine the split point * \post size() increased by one and points[index+1] is the new point */ void split_segment(int index, qreal factor); /** * \brief The point you'd get by calling split_segment(index, factor) */ Point split_segment_point(int index, qreal factor) const; void remove_point(int index) { if ( index >= 0 && index < size() ) points_.erase(points_.begin() + index); } void add_to_painter_path(QPainterPath& out) const; math::bezier::Bezier lerp(const math::bezier::Bezier& other, qreal factor) const; void set_point(int index, const math::bezier::Point& p) { if ( index >= 0 && index < size() ) points_[index] = p; } BezierSegment segment(int index) const; void set_segment(int index, const BezierSegment& s); BezierSegment inverted_segment(int index) const; int segment_count() const; Bezier transformed(const QTransform& t) const; void transform(const QTransform& t); /** * \brief Returns a new bezier with the given points removed */ math::bezier::Bezier removed_points(const std::set& indices) const; /** * \brief For closed beziers, ensure the last segment is present */ void add_close_point(); private: /** * \brief Solver for the point \p p to the point \p p + 1 */ math::bezier::CubicBezierSolver solver_for_point(int p) const { return segment(p); } std::vector points_; bool closed_ = false; }; class MultiBezier { public: const std::vector& beziers() const { return beziers_; } std::vector& beziers() { return beziers_; } Bezier& back() { return beziers_.back(); } const Bezier& back() const { return beziers_.back(); } MultiBezier& move_to(const QPointF& p) { beziers_.push_back(Bezier(p)); at_end = false; return *this; } MultiBezier& line_to(const QPointF& p) { handle_end(); beziers_.back().line_to(p); return *this; } MultiBezier& quadratic_to(const QPointF& handle, const QPointF& dest) { handle_end(); beziers_.back().quadratic_to(handle, dest); return *this; } MultiBezier& cubic_to(const QPointF& handle1, const QPointF& handle2, const QPointF& dest) { handle_end(); beziers_.back().cubic_to(handle1, handle2, dest); return *this; } MultiBezier& close() { if ( !beziers_.empty() ) beziers_.back().close(); at_end = true; return *this; } QRectF bounding_box() const; QPainterPath painter_path() const { QPainterPath p; for ( const Bezier& bez : beziers_ ) bez.add_to_painter_path(p); return p; } void append(const MultiBezier& other) { beziers_.insert(beziers_.end(), other.beziers_.begin(), other.beziers_.end()); } void append(const QPainterPath& path); void transform(const QTransform& t); static MultiBezier from_painter_path(const QPainterPath& path); int size() const { return beziers_.size(); } bool empty() const { return beziers_.empty(); } const Bezier& operator[](int index) const { return beziers_[index]; } Bezier& operator[](int index) { return beziers_[index]; } private: void handle_end() { if ( at_end ) { beziers_.push_back(Bezier()); if ( beziers_.size() > 1 ) beziers_.back().add_point(beziers_[beziers_.size()-2].points().back().pos); at_end = false; } } std::vector beziers_; bool at_end = true; }; } // namespace glaxnimate::math namespace glaxnimate::math { inline bezier::Bezier lerp(const math::bezier::Bezier& a, const math::bezier::Bezier& b, qreal factor) { return a.lerp(b, factor); } } // namespace glaxnimate::math Q_DECLARE_METATYPE(glaxnimate::math::bezier::Bezier) mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/utils/trace_wrapper.cpp000664 001750 001750 00000017067 14477652011 031012 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "trace_wrapper.hpp" #include "utils/quantize.hpp" #include "model/document.hpp" #include "model/shapes/stroke.hpp" #include "model/shapes/fill.hpp" #include "model/shapes/path.hpp" #include "model/shapes/rect.hpp" #include "command/object_list_commands.hpp" class glaxnimate::utils::trace::TraceWrapper::Private { public: QImage source_image; utils::trace::TraceOptions options; std::vector eem_colors; model::Image* image = nullptr; model::Document* document; model::Composition* comp; QString name; void set_image(const QImage& image) { if ( image.format() != QImage::Format_RGBA8888 ) source_image = image.convertToFormat(QImage::Format_RGBA8888); else source_image = image; } void result_to_shapes(model::ShapeListProperty& prop, const TraceResult& result, qreal stroke_width) { auto fill = std::make_unique(document); fill->color.set(result.color); prop.insert(std::move(fill)); if ( stroke_width > 0 ) { auto stroke = std::make_unique(document); stroke->color.set(result.color); stroke->width.set(stroke_width); prop.insert(std::move(stroke)); } for ( const auto& bez : result.bezier.beziers() ) { auto path = std::make_unique(document); path->shape.set(bez); prop.insert(std::move(path)); } for ( const auto& rect : result.rects ) { auto shape = std::make_unique(document); shape->position.set(rect.center()); shape->size.set(rect.size()); prop.insert(std::move(shape)); } } }; glaxnimate::utils::trace::TraceWrapper::TraceWrapper(model::Image* image) : TraceWrapper(image->owner_composition(), image->image->pixmap().toImage(), image->object_name()) { d->image = image; } glaxnimate::utils::trace::TraceWrapper::TraceWrapper(model::Composition* comp, const QImage& image, const QString& name) : d(std::make_unique()) { d->comp = comp; d->document = comp->document(); d->name = name; d->set_image(image); } glaxnimate::utils::trace::TraceWrapper::~TraceWrapper() = default; QSize glaxnimate::utils::trace::TraceWrapper::size() const { return d->source_image.size(); } glaxnimate::utils::trace::TraceOptions & glaxnimate::utils::trace::TraceWrapper::options() { return d->options; } void glaxnimate::utils::trace::TraceWrapper::trace_mono( const QColor& color, bool inverted, int alpha_threshold, std::vector& result) { result.emplace_back(); emit progress_max_changed(100); result.back().color = color; utils::trace::Tracer tracer(d->source_image, d->options); tracer.set_target_alpha(alpha_threshold, inverted); connect(&tracer, &utils::trace::Tracer::progress, this, &TraceWrapper::progress_changed); tracer.set_progress_range(0, 100); tracer.trace(result.back().bezier); } void glaxnimate::utils::trace::TraceWrapper::trace_exact( const std::vector& colors, int tolerance, std::vector& result ) { result.reserve(result.size() + colors.size()); emit progress_max_changed(100 * colors.size()); int progress_index = 0; for ( QColor color : colors ) { result.emplace_back(); result.back().color = color; utils::trace::Tracer tracer(d->source_image, d->options); tracer.set_target_color(color, tolerance * tolerance); tracer.set_progress_range(100 * progress_index, 100 * (progress_index+1)); tracer.trace(result.back().bezier); ++progress_index; } } void glaxnimate::utils::trace::TraceWrapper::trace_closest( const std::vector& colors, std::vector& result) { emit progress_max_changed(100 * colors.size()); QImage converted = utils::quantize::quantize(d->source_image, colors); utils::trace::Tracer tracer(converted, d->options); result.reserve(result.size() + colors.size()); for ( int i = 0; i < int(colors.size()); i++ ) { tracer.set_target_index(i); tracer.set_progress_range(100 * i, 100 * (i+1)); result.emplace_back(); result.back().color = colors[i]; tracer.trace(result.back().bezier); } } void glaxnimate::utils::trace::TraceWrapper::trace_pixel(std::vector& result) { auto pixdata = utils::trace::trace_pixels(d->source_image); result.reserve(pixdata.size()); for ( const auto& p : pixdata ) result.push_back({p.first, {}, p.second}); } glaxnimate::model::Group* glaxnimate::utils::trace::TraceWrapper::apply( std::vector& trace, qreal stroke_width ) { auto layer = std::make_unique(d->document); auto created = layer.get(); layer->name.set(tr("Traced %1").arg(d->name)); if ( trace.size() == 1 ) { d->result_to_shapes(layer->shapes, trace[0], stroke_width); } else { for ( const auto& result : trace ) { auto group = std::make_unique(d->document); group->name.set(result.color.name()); group->group_color.set(result.color); d->result_to_shapes(group->shapes, result, stroke_width); layer->shapes.insert(std::move(group)); } } if ( d->image ) { layer->transform->copy(d->image->transform.get()); d->document->push_command(new command::AddObject( d->image->owner(), std::move(layer), d->image->position()+1 )); } else { d->document->push_command(new command::AddObject( &d->comp->shapes, std::move(layer) )); } // created->recursive_rename(); return created; } const QImage & glaxnimate::utils::trace::TraceWrapper::image() const { return d->source_image; } const std::vector& glaxnimate::utils::trace::TraceWrapper::eem_colors() const { if ( d->eem_colors.empty() ) d->eem_colors = utils::quantize::edge_exclusion_modes(d->source_image, 256); return d->eem_colors; } glaxnimate::utils::trace::TraceWrapper::Preset glaxnimate::utils::trace::TraceWrapper::preset_suggestion() const { int w = d->source_image.width(); int h = d->source_image.height(); if ( w > 1024 || h > 1024 ) return Preset::ComplexPreset; auto color_count = utils::quantize::color_frequencies(d->source_image).size(); if ( w < 128 && h < 128 && color_count < 128 ) return Preset::PixelPreset; color_count = eem_colors().size(); if ( w < 1024 && h < 1024 && color_count < 32 ) return Preset::FlatPreset; else return Preset::ComplexPreset; } void glaxnimate::utils::trace::TraceWrapper::trace_preset( Preset preset, int complex_posterization, std::vector &colors, std::vector& result ) { d->options.set_min_area(16); d->options.set_smoothness(0.75); switch ( preset ) { case utils::trace::TraceWrapper::ComplexPreset: colors = utils::quantize::octree(d->source_image, complex_posterization); trace_closest(colors, result); break; case utils::trace::TraceWrapper::FlatPreset: colors = eem_colors(); trace_closest(colors, result); break; case utils::trace::TraceWrapper::PixelPreset: trace_pixel(result); break; } } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/animation/animatable.cpp000664 001750 001750 00000036457 14477652011 032174 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "animatable.hpp" #include "command/animation_commands.hpp" #include "model/object.hpp" #include "math/bezier/segment.hpp" std::vector> glaxnimate::model::KeyframeBase::split(const KeyframeBase* other, std::vector splits) const { std::vector> kfs; if ( transition().hold() ) { kfs.push_back(clone()); kfs.push_back(other->clone()); return kfs; } auto splitter = this->splitter(other); kfs.reserve(splits.size()+2); qreal prev_split = 0; const KeyframeBase* to_split = this; std::unique_ptr split_right; QPointF old_p; for ( qreal split : splits ) { // Skip zeros if ( qFuzzyIsNull(split) ) continue; qreal split_ratio = (split - prev_split) / (1 - prev_split); prev_split = split; auto transitions = to_split->transition().split_t(split_ratio); // split_ratio is t // p.x() is time lerp // p.y() is value lerp QPointF p = transition().bezier().solve(split); splitter->step(p); auto split_left = splitter->left(old_p); split_left->set_transition(transitions.first); old_p = p; split_right = splitter->right(p); split_right->set_transition(transitions.second); to_split = split_right.get(); kfs.push_back(std::move(split_left)); } kfs.push_back(std::move(split_right)); kfs.push_back(splitter->last()); kfs.back()->set_transition(other->transition()); return kfs; } class glaxnimate::model::Keyframe::PointKeyframeSplitter : public KeyframeSplitter { public: const Keyframe* self; const Keyframe* other; math::bezier::CubicBezierSolver solver; math::bezier::LengthData len; QPointF tan_in; math::bezier::Point point_before; math::bezier::Point point_mid; qreal prev_split = 0; bool linear; PointKeyframeSplitter(const Keyframe* self, const Keyframe* other) : self(self), other(other), solver(self->bezier_solver(*other)), len(solver, 20), tan_in(self->point_.tan_in), linear(self->is_linear()) { } void step(const QPointF& p) override { if ( linear ) return; // TODO: would need the ability to access other keyframes to properly handle values outside [0, 1] qreal split = math::bound(0., p.y(), 1.); auto beziers = solver.split((split - prev_split) / (1 - prev_split)); prev_split = split; solver = beziers.second; point_before = math::bezier::Point (beziers.first[0], tan_in, beziers.first[1]); point_mid = math::bezier::Point (beziers.first[3], beziers.first[2], beziers.second[1]); tan_in = beziers.second[2]; } std::unique_ptr left(const QPointF& p) const override { if ( linear ) { return std::make_unique( math::lerp(self->time(), other->time(), p.x()), math::lerp(self->get(), other->get(), p.y()) ); } return std::make_unique(math::lerp(self->time(), other->time(), p.x()), point_before); } std::unique_ptr right(const QPointF& p) const override { if ( linear ) { return std::make_unique( math::lerp(self->time(), other->time(), p.x()), math::lerp(self->get(), other->get(), p.y()) ); } return std::make_unique(math::lerp(self->time(), other->time(), p.x()), point_mid); } std::unique_ptr last() const override { if ( linear ) return other->clone(); math::bezier::Point point_after = other->point(); point_after.tan_in = tan_in; return std::make_unique(other->time(), point_after); } }; std::unique_ptr glaxnimate::model::Keyframe::splitter(const KeyframeBase* other) const { return std::make_unique(this, static_cast*>(other)); } bool glaxnimate::model::AnimatableBase::assign_from(const model::BaseProperty* prop) { if ( prop->traits().flags != traits().flags || prop->traits().type != traits().type ) return false; const AnimatableBase* other = static_cast(prop); clear_keyframes(); if ( !other->animated() ) return set_value(other->value()); for ( int i = 0, e = other->keyframe_count(); i < e; i++ ) { const KeyframeBase* kf_other = other->keyframe(i); KeyframeBase* kf = set_keyframe(kf_other->time(), kf_other->value()); if ( kf ) kf->set_transition(kf_other->transition()); } return true; } bool glaxnimate::model::AnimatableBase::set_undoable(const QVariant& val, bool commit) { if ( !valid_value(val) ) return false; object()->push_command(new command::SetMultipleAnimated( tr("Update %1").arg(name()), {this}, {value()}, {val}, commit )); return true; } glaxnimate::model::AnimatableBase::MidTransition glaxnimate::model::AnimatableBase::mid_transition(model::FrameTime time) const { int keyframe_index = this->keyframe_index(time); const KeyframeBase* kf_before = this->keyframe(keyframe_index); if ( !kf_before ) return {MidTransition::Invalid, value(), {}, {}}; auto before_time = kf_before->time(); if ( before_time >= time ) return {MidTransition::SingleKeyframe, kf_before->value(), {}, kf_before->transition(),}; const KeyframeBase* kf_after = this->keyframe(keyframe_index + 1); if ( !kf_after ) return {MidTransition::SingleKeyframe, kf_before->value(), kf_before->transition(), {},}; auto after_time = kf_after->time(); if ( after_time <= time ) return { MidTransition::SingleKeyframe, kf_after->value(), kf_before->transition(), kf_after->transition(), }; qreal x = math::unlerp(before_time, after_time, time); return do_mid_transition(kf_before, kf_after, x, keyframe_index); } glaxnimate::model::AnimatableBase::MidTransition glaxnimate::model::AnimatableBase::do_mid_transition( const model::KeyframeBase* kf_before, const model::KeyframeBase* kf_after, qreal x, int index ) const { const auto& beftrans = kf_before->transition(); if ( beftrans.hold() || (beftrans.before() == QPointF(0, 0) && beftrans.after() == QPointF(1,1)) ) return {MidTransition::Middle, kf_before->value(), beftrans, beftrans}; qreal t = beftrans.bezier_parameter(x); if ( t <= 0 ) { KeyframeTransition from_previous = {{}, {1, 1}}; if ( index > 0 ) from_previous = keyframe(index-1)->transition(); return {MidTransition::SingleKeyframe, kf_before->value(), from_previous, beftrans}; } else if ( t >= 1 ) { return {MidTransition::SingleKeyframe, kf_before->value(), beftrans, kf_after->transition(),}; } model::AnimatableBase::MidTransition mt; mt.type = MidTransition::Middle; mt.value = do_mid_transition_value(kf_before, kf_after, x); std::tie(mt.from_previous, mt.to_next) = beftrans.split(x); return mt; } void glaxnimate::model::AnimatableBase::clear_keyframes_undoable(QVariant value) { if ( !value.isValid() || value.isNull() ) value = this->value(); object()->push_command(new command::RemoveAllKeyframes(this, std::move(value))); } void glaxnimate::model::AnimatableBase::add_smooth_keyframe_undoable(FrameTime time, const QVariant& val) { object()->push_command( new command::SetKeyframe(this, time, val.isNull() ? value() : val, true) ); } void glaxnimate::model::detail::AnimatedPropertyPosition::split_segment(int index, qreal factor) { if ( keyframes_.size() < 2 ) return; auto before = bezier(); auto after = before; after.split_segment(index, factor); auto parent = std::make_unique(tr("Split Segment")); FrameTime time = 0; QVariant value; if ( index <= 0 && factor <= 0 ) { time = keyframes_[0]->time(); value = keyframes_[0]->value(); } else if ( index >= int(keyframes_.size()) - 1 && factor >= 1 ) { time = keyframes_.back()->time(); value = keyframes_.back()->value(); } else { auto kf_before = keyframes_[index].get(); auto kf_after = keyframes_[index + 1].get(); value = kf_before->lerp(*kf_after, factor); // Reverse length.at_ratio() to get the correct time at which the transition is equal to `value` math::bezier::Solver segment(kf_before->get(), kf_before->point().tan_out, kf_after->point().tan_in, kf_after->get()); math::bezier::LengthData length(segment, 20); qreal time_factor = qFuzzyIsNull(length.length()) ? 0 : length.from_ratio(factor) / length.length(); time = qRound(math::lerp(kf_before->time(), kf_after->time(), time_factor)); } parent->add_command( std::make_unique(this, time, value, true, true), 0, 0 ); parent->add_command( std::make_unique(this, before, after, true), 1, 1 ); object()->push_command(parent.release()); } bool glaxnimate::model::detail::AnimatedPropertyPosition::set_bezier(math::bezier::Bezier bezier) { bezier.add_close_point(); // TODO if sizes don't match, re-arrange keyframes based on // how far keyframes are in the bezier // eg: a point at 50% of the length will result in a keyframe // at time (keyframes[0].time + keyframes[-1].time) / 2 if ( bezier.size() != int(keyframes_.size()) ) return false; for ( int i = 0; i < bezier.size(); i++ ) { keyframes_[i]->set_point(bezier[i]); on_keyframe_updated(keyframes_[i]->time(), i-1, i+1); } value_ = get_at_impl(time()).second; emitter(this->object(), value_); emit bezier_set(bezier); return true; } void glaxnimate::model::detail::AnimatedPropertyPosition::remove_points(const std::set& indices) { auto parent = std::make_unique(tr("Remove Nodes")); auto before = bezier(); auto after = before.removed_points(indices); int order = 0; for ( int index : indices ) { parent->add_command(std::make_unique(this, index), -order, order); ++order; } object()->push_command(parent.release()); } glaxnimate::math::bezier::Bezier glaxnimate::model::detail::AnimatedPropertyPosition::bezier() const { math::bezier::Bezier bez; for ( const auto& kf : keyframes_ ) bez.push_back(kf->point()); return bez; } glaxnimate::model::detail::AnimatedPropertyPosition::keyframe_type* glaxnimate::model::detail::AnimatedPropertyPosition::set_keyframe( FrameTime time, const QVariant& val, SetKeyframeInfo* info, bool force_insert ) { if ( val.userType() == QMetaType::QPointF ) return detail::AnimatedProperty::set_keyframe(time, val.value(), info, force_insert); if ( auto v = detail::variant_cast(val) ) { auto kf = detail::AnimatedProperty::set_keyframe(time, v->pos, info, force_insert); kf->set_point(*v); emit bezier_set(bezier()); return kf; } // We accept a bezier here so it can be used with SetMultipleAnimated if ( auto v = detail::variant_cast(val) ) { set_bezier(*v); return nullptr; } return nullptr; } glaxnimate::model::detail::AnimatedPropertyPosition::keyframe_type* glaxnimate::model::detail::AnimatedPropertyPosition::set_keyframe( FrameTime time, reference value, SetKeyframeInfo* info, bool force_insert ) { return detail::AnimatedProperty::set_keyframe(time, value, info, force_insert); } bool glaxnimate::model::detail::AnimatedPropertyPosition::set_value(const QVariant& val) { if ( auto v = detail::variant_cast(val) ) return detail::AnimatedProperty::set(*v); if ( auto v = detail::variant_cast(val) ) return set_bezier(*v); return false; } bool glaxnimate::model::detail::AnimatedPropertyPosition::valid_value(const QVariant& val) const { if ( detail::variant_cast(val) || detail::variant_cast(val) ) return true; return false; } void glaxnimate::model::detail::AnimatedPropertyPosition::add_smooth_keyframe_undoable(FrameTime time, const QVariant& val) { auto parent = std::make_unique(tr("Add Keyframe")); auto value = val.isNull() ? QVariant(value_) : val; parent->add_command(std::make_unique(this, time, value, true), 0, 0); int count = keyframes_.size(); if ( value.userType() == QMetaType::QPointF && count >= 2 && keyframes_[0]->time() < time && keyframes_.back()->time() > time ) { int index = keyframe_index(time); auto first = keyframe(index); const keyframe_type* second = keyframe(index+1); if ( !first->is_linear() || second->is_linear() ) { double scaled_time = (time - first->time()) / (second->time() - first->time()); auto factor = first->transition().lerp_factor(scaled_time); auto solver = first->bezier_solver(*second); math::bezier::LengthData len(solver, 20); auto t = len.at_ratio(factor).ratio; auto split = solver.split(t); auto before = bezier(); auto after = before; after[index].tan_out = split.first[1]; after[index+1].tan_in = split.second[2]; math::bezier::Point p(split.first[3], split.first[2], split.second[1]); p.translate_to(value.value()); after.insert_point(index+1, p); parent->add_command(std::make_unique(this, before, after, true), 1, 1); } } object()->document()->push_command(parent.release()); } std::optional glaxnimate::model::detail::AnimatedPropertyPosition::derivative_at(glaxnimate::model::FrameTime time) const { int count = keyframe_count(); if ( count < 2 ) return {}; int index_before = keyframe_index(time); const keyframe_type* kf_before = keyframe(index_before); const keyframe_type* kf_after = nullptr; qreal factor = 1; if ( index_before == count - 1 ) { kf_after = kf_before; kf_before = keyframe(index_before - 1); } else { kf_after = keyframe(index_before + 1); factor = math::unlerp(kf_before->time(), kf_after->time(), time); } const math::bezier::Point& point_before = kf_before->point(); const math::bezier::Point& point_after = kf_after->point(); math::bezier::CubicBezierSolver solver(point_before.pos, point_before.tan_out, point_after.tan_in, point_after.pos); return QPointF( solver.derivative(factor, 0), solver.derivative(factor, 1) ); } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/stretchable_time.hpp000664 001750 001750 00000001507 14477652011 031427 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include "model/object.hpp" #include "model/property/property.hpp" namespace glaxnimate::model { class StretchableTime : public Object { GLAXNIMATE_OBJECT(StretchableTime) GLAXNIMATE_PROPERTY(float, start_time, 0, &StretchableTime::timing_changed, {}, PropertyTraits::Visual) GLAXNIMATE_PROPERTY(float, stretch, 1, &StretchableTime::timing_changed, {}, PropertyTraits::Visual|PropertyTraits::Percent) public: using Object::Object; float time_to_local(float global) const; float time_from_local(float local) const; QString type_name_human() const override; private: bool validate_stretch(float stretch); signals: void timing_changed(); }; } // model mlt-7.22.0/src/modules/glaxnimate/glaxnimate/external/QtAppSetup/src/app/scripting/script_engine.cpp000664 001750 001750 00000000234 14477652011 035353 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "script_engine.hpp" mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/utils/gzip.cpp000664 001750 001750 00000017510 14477652011 027116 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "gzip.hpp" #include #include #include #include #include using namespace glaxnimate; namespace { class Gzipper { public: static const int chunk_size = 0x4000; using Buffer = std::array; struct BufferView { const char* data; std::size_t size; }; explicit Gzipper(const utils::gzip::ErrorFunc& on_error) : on_error(on_error) { zip_stream.zalloc = Z_NULL; zip_stream.zfree = Z_NULL; zip_stream.opaque = Z_NULL; } void add_data(const QByteArray& data) { add_data(data.data(), data.size()); } void add_data(const char* data, std::size_t size) { zip_stream.next_in = (Bytef*) data; zip_stream.avail_in = size; zip_stream.avail_out = 0; } bool finished() const { return zip_stream.avail_out != 0; } bool inflate_init() { process_fn = &inflate; end_fn = &inflateEnd; op = "inflate"; return zlib_check("inflateInit2", inflateInit2(&zip_stream, 16|MAX_WBITS)); } BufferView process() { zip_stream.avail_out = chunk_size; zip_stream.next_out = buffer.data(); zlib_check(op, process_fn(&zip_stream, Z_FINISH)); return {(const char*)buffer.data(), chunk_size - zip_stream.avail_out}; } bool end() { return zlib_check(op, end_fn(&zip_stream), "End"); } bool deflate_init(int level) { process_fn = &deflate; end_fn = &deflateEnd; op = "deflate"; return zlib_check("deflateInit2", deflateInit2(&zip_stream, level, Z_DEFLATED, 15 | 16, 8, Z_DEFAULT_STRATEGY)); } void log_error(const QString& msg) { if ( on_error ) on_error(msg); } private: bool zlib_check(const char* func, int result, const char* extra = "") { if ( result >= 0 || result == Z_BUF_ERROR ) return true; log_error(QApplication::tr("ZLib %1%2 returned %3").arg(func).arg(extra).arg(result)); return false; } z_stream zip_stream; utils::gzip::ErrorFunc on_error; Buffer buffer; int (*process_fn)(z_streamp, int); int (*end_fn)(z_streamp); const char* op; }; } // namespace bool utils::gzip::compress(const QByteArray& data, QIODevice& output, const utils::gzip::ErrorFunc& on_error, int level, quint32* compressed_size) { Gzipper gz(on_error); if ( !gz.deflate_init(level) ) return false; gz.add_data(data); quint32 total_size = 0; while ( !gz.finished() ) { auto bv = gz.process(); output.write(bv.data, bv.size); total_size += bv.size; } if ( compressed_size ) *compressed_size = total_size; return gz.end(); } bool utils::gzip::decompress(QIODevice& input, QByteArray& output, const utils::gzip::ErrorFunc& on_error) { Gzipper gz(on_error); if ( !gz.inflate_init() ) return false; while ( true ) { QByteArray data = input.read(Gzipper::chunk_size); if ( data.isEmpty() ) break; gz.add_data(data); while ( !gz.finished() ) { auto bv = gz.process(); output.append(bv.data, bv.size); } } return gz.end(); } bool utils::gzip::decompress(const QByteArray& input, QByteArray& output, const utils::gzip::ErrorFunc& on_error) { Gzipper gz(on_error); if ( !gz.inflate_init() ) return false; gz.add_data(input); while ( !gz.finished() ) { auto bv = gz.process(); output.append(bv.data, bv.size); } return gz.end(); } bool utils::gzip::is_compressed(QIODevice& input) { return input.peek(2) == "\x1f\x8b"; } bool utils::gzip::is_compressed(const QByteArray& input) { return input.size() >= 2 && input[0] == '\x1f' && input[1] == '\x8b'; } class utils::gzip::GzipStream::Private { public: Private(QIODevice* target, const ErrorFunc& ef) : zipper(ef), target(target) {} Gzipper zipper; QIODevice* target; QIODevice::OpenMode mode = QIODevice::NotOpen; qint64 total_size = 0; QByteArray buffer; QFile f{"/tmp/foo.txt"}; void _memcpy(char* dest, const char* src, std::size_t size) { std::memcpy(dest, src, size); if ( !f.isOpen() ) f.open(WriteOnly); f.write(src, size); // f.write("\n=====\n"); f.flush(); } }; utils::gzip::GzipStream::GzipStream(QIODevice* target, const utils::gzip::ErrorFunc& on_error) : d(std::make_unique(target, on_error)) {} utils::gzip::GzipStream::~GzipStream() { if ( d->mode != NotOpen ) d->zipper.end(); } bool utils::gzip::GzipStream::open(QIODevice::OpenMode mode) { if ( d->mode != NotOpen ) { QString error = "Gzip stream already open"; setErrorString(error); return false; } if ( mode == ReadOnly ) { d->zipper.inflate_init(); d->mode = ReadOnly; setOpenMode(d->mode); return true; } if ( mode == WriteOnly ) { d->zipper.deflate_init(9); d->mode = WriteOnly; setOpenMode(d->mode); return true; } setErrorString("Unsupported open mode for Gzip stream"); return false; } bool utils::gzip::GzipStream::atEnd() const { return d->target->atEnd() && d->buffer.isEmpty(); } qint64 utils::gzip::GzipStream::writeData(const char* data, qint64 len) { if ( d->mode != WriteOnly ) { setErrorString("Gzip stream not open for writing"); return -1; } d->zipper.add_data(data, len); while ( !d->zipper.finished() ) { auto bv = d->zipper.process(); d->target->write(bv.data, bv.size); d->total_size += bv.size; } return len; } qint64 utils::gzip::GzipStream::readData(char* data, qint64 maxlen) { if ( d->mode != ReadOnly ) { setErrorString("Gzip stream not open for reading"); return -1; } if ( maxlen <= 0 ) return 0; qint64 read = 0; if ( !d->buffer.isEmpty() ) { if ( d->buffer.size() < maxlen ) { d->_memcpy(data, d->buffer.data(), d->buffer.size()); maxlen -= d->buffer.size(); data += d->buffer.size(); read += d->buffer.size(); d->buffer.clear(); } else { d->_memcpy(data, d->buffer.data(), maxlen); d->buffer = d->buffer.mid(maxlen); return maxlen; } } while ( read < maxlen ) { QByteArray buf = d->target->read(Gzipper::chunk_size); if ( buf.isEmpty() ) break; d->zipper.add_data(buf); while ( !d->zipper.finished() ) { auto bv = d->zipper.process(); if ( qint64(read + bv.size) >= maxlen ) { auto delta = maxlen - read; d->_memcpy(data + read, bv.data, delta); d->buffer = QByteArray(bv.data + delta, bv.size - delta); read = maxlen; while ( !d->zipper.finished() ) { bv = d->zipper.process(); d->buffer += QByteArray(bv.data, bv.size); } break; } else { d->_memcpy(data + read, bv.data, bv.size); read += bv.size; } } } d->total_size += read; return read; } qint64 utils::gzip::GzipStream::ouput_size() const { return d->total_size; } #ifdef zlib_version #undef zlib_version #endif QString utils::gzip::zlib_version() { return zlibVersion(); } src/modules/glaxnimate/glaxnimate/external/QtAppSetup/src/app/settings/keyboard_shortcuts.cpp000664 001750 001750 00000007076 14477652011 036232 0ustar00ddennedyddennedy000000 000000 mlt-7.22.0/* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "keyboard_shortcuts.hpp" #include "app/widgets/keyboard_settings_widget.hpp" void app::settings::ShortcutSettings::load(QSettings & settings) { for ( const auto& key : settings.childKeys() ) { auto& action = actions[key]; action.overwritten = true; action.shortcut = QKeySequence(settings.value(key).toString(), QKeySequence::PortableText); } } void app::settings::ShortcutSettings::save(QSettings& settings) { for ( const auto& p : actions ) { if ( p.second.overwritten ) settings.setValue(p.first, p.second.shortcut.toString(QKeySequence::PortableText)); else settings.remove(p.first); } } void app::settings::ShortcutSettings::add_menu(QMenu* menu, const QString& prefix) { auto group = add_group(menu->menuAction()->iconText()); for ( QAction* act : menu->actions() ) { if ( !act->isSeparator() && !act->menu() && !act->objectName().isEmpty() ) group->actions.push_back( add_action(act, prefix) ); } QObject::connect(menu->menuAction(), &QAction::changed, menu, [menu, group]{ group->label = menu->menuAction()->iconText(); }); } app::settings::ShortcutGroup * app::settings::ShortcutSettings::add_group(const QString& label) { groups.push_back(ShortcutGroup{label, {}}); return &groups.back(); } app::settings::ShortcutAction * app::settings::ShortcutSettings::action(const QString& slug) { return &actions[slug]; } app::settings::ShortcutAction * app::settings::ShortcutSettings::add_action(QAction* qaction, const QString& prefix) { emit begin_actions_change(); auto sca = action(prefix + qaction->objectName()); sca->icon = qaction->icon(); sca->label = qaction->iconText(); sca->default_shortcut = qaction->shortcut(); if ( sca->overwritten ) qaction->setShortcut(sca->shortcut); else sca->shortcut = qaction->shortcut(); sca->action = qaction; QObject::connect(qaction, &QAction::changed, qaction, [qaction, sca]{ sca->icon = qaction->icon(); sca->label = qaction->iconText(); }); emit end_actions_change(); return sca; } const QList & app::settings::ShortcutSettings::get_groups() const { return groups; } QWidget * app::settings::ShortcutSettings::make_widget(QWidget* parent) { return new KeyboardSettingsWidget(this, parent); } const std::unordered_map & app::settings::ShortcutSettings::get_actions() const { return actions; } const QKeySequence & app::settings::ShortcutSettings::get_shortcut(const QString& action_name) const { return actions.at(action_name).shortcut; } app::settings::ShortcutGroup* app::settings::ShortcutSettings::find_group(const QString& label) { for ( app::settings::ShortcutGroup& group : groups ) if ( group.label == label ) return &group; return {}; } void app::settings::ShortcutSettings::remove_action(ShortcutAction* action) { emit begin_actions_change(); QString name; name = action->action->objectName(); for ( app::settings::ShortcutGroup& group : groups ) { std::vector::iterator it = std::find(group.actions.begin(), group.actions.end(), action); if(it != group.actions.end()) { group.actions.erase(it); break; } } actions.erase(name); emit end_actions_change(); } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/math/bezier/meta.cpp000664 001750 001750 00000003361 14477652011 030143 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "meta.hpp" using namespace glaxnimate; QDataStream& operator<<(QDataStream& ds, const math::bezier::Point& p) { return ds << p.pos << p.tan_in << p.tan_out << qint16(p.type); } QDataStream& operator<<(QDataStream& ds, const math::bezier::Bezier& bez) { ds << quint32(bez.size()) << bez.closed(); for ( const auto& p : bez ) ds << p; return ds; } QDataStream& operator>>(QDataStream& ds, math::bezier::Point& p) { qint16 type = 0; ds >> p.pos >> p.tan_in >> p.tan_out >> type; p.type = math::bezier::PointType(type); return ds; } QDataStream& operator>>(QDataStream& ds, math::bezier::Bezier& bez) { bez.clear(); quint32 size = 0; bool closed = false; ds >> size >> closed; bez.set_closed(closed); for ( quint32 i = 0; i < size; i++ ) { math::bezier::Point p{{}, {}, {}}; ds >> p; bez.push_back(p); } return ds; } void math::bezier::register_meta() { qRegisterMetaType("glaxnimate::math::bezier::Bezier"); qRegisterMetaType("glaxnimate::math::bezier::Point"); #if QT_VERSION_MAJOR < 6 qRegisterMetaTypeStreamOperators("glaxnimate::math::bezier::Bezier"); qRegisterMetaTypeStreamOperators("glaxnimate::math::bezier::Point"); #endif QMetaType::registerConverter(&Point::position); QMetaType::registerConverter([](const QPointF& p) { return Point{p, p, p}; }); } namespace { class BezierAutoRegister { public: BezierAutoRegister() { math::bezier::register_meta(); } }; } // namespace static BezierAutoRegister bezier_reg = {}; mlt-7.22.0/src/modules/glaxnimate/glaxnimate/external/QtAppSetup/src/app/scripting/js/CMakeLists.txt000664 001750 001750 00000000364 14477652011 035176 0ustar00ddennedyddennedy000000 000000 # SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia # # SPDX-License-Identifier: GPL-3.0-or-later target_sources(${PROJECT_SLUG} PRIVATE js_engine.cpp ) target_link_libraries(${PROJECT_SLUG} PUBLIC ${Qt5Qml_LIBRARIES}) mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/shapes/zig_zag.cpp000664 001750 001750 00000012124 14477652011 031016 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "zig_zag.hpp" #include "math/geom.hpp" #include "math/vector.hpp" #include "math/bezier/bezier_length.hpp" GLAXNIMATE_OBJECT_IMPL(glaxnimate::model::ZigZag) using namespace glaxnimate; using namespace glaxnimate::math::bezier; using BezierSolver = glaxnimate::math::bezier::CubicBezierSolver; static double angle_mean(double a, double b) { if ( math::abs(a-b) > math::pi ) return (a + b) / 2 + math::pi; return (a + b) / 2; } static void zig_zag_corner(Bezier& output_bezier, const BezierSolver* segment_before, const BezierSolver* segment_after, float amplitude, int direction, float tangent_length) { QPointF point; double angle; double tan_angle; // We use 0.01 and 0.99 instead of 0 and 1 because they yield better results if ( !segment_before ) { point = segment_after->points()[0]; angle = segment_after->normal_angle(0.01); tan_angle = segment_after->tangent_angle(0.01); } else if ( !segment_after ) { point = segment_before->points()[3]; angle = segment_before->normal_angle(0.99); tan_angle = segment_before->tangent_angle(0.99); } else { point = segment_after->points()[0]; angle = -angle_mean(segment_after->normal_angle(0.01), segment_before->normal_angle(0.99)); tan_angle = angle_mean(segment_after->tangent_angle(0.01), segment_before->tangent_angle(0.99)); } output_bezier.add_point(point + math::from_polar(direction * amplitude, angle)); auto& vertex = output_bezier.back(); // It's ok to float-compare as it's a value we set explicitly to 0 if ( tangent_length != 0 ) { vertex.tan_in = vertex.pos + math::from_polar(-tangent_length, tan_angle); vertex.tan_out = vertex.pos + math::from_polar(tangent_length, tan_angle); } } static int zig_zag_segment(Bezier& output_bezier,const BezierSolver& segment, const LengthData& seg_len, float amplitude, int frequency, int direction, float tangent_length) { for ( int i = 0; i < frequency; i++ ) { auto f = (i + 1.) / (frequency + 1.); auto t = seg_len.at_ratio(f).ratio; auto angle = segment.normal_angle(t); auto point = segment.solve(t); output_bezier.add_point(point + math::from_polar(direction * amplitude, -angle)); auto& vertex = output_bezier.back(); // It's ok to float-compare as it's a value we set explicitly to 0 if ( tangent_length != 0 ) { auto tan_angle = segment.tangent_angle(t); vertex.tan_in = vertex.pos + math::from_polar(-tangent_length, tan_angle); vertex.tan_out = vertex.pos + math::from_polar(tangent_length, tan_angle); } direction = -direction; } return direction; } static Bezier zig_zag_bezier(const Bezier& input_bezier, float amplitude, int frequency, model::ZigZag::Style style) { Bezier output_bezier; output_bezier.set_closed(input_bezier.closed()); auto count = input_bezier.segment_count(); if ( count == 0 ) return output_bezier; auto direction = -1; BezierSolver segment = input_bezier.segment(count - 1); BezierSolver next_segment = input_bezier.segment(0); LengthData seg_len(next_segment, 20); auto tangent_length = style == model::ZigZag::Wave ? seg_len.length() / (frequency + 1.) / 2. : 0; zig_zag_corner(output_bezier, input_bezier.closed() ? &segment : nullptr, &next_segment, amplitude, direction, tangent_length); for ( auto i = 0; i < count; i++ ) { segment = next_segment; direction = zig_zag_segment(output_bezier, segment, seg_len, amplitude, frequency, -direction, tangent_length); if ( i == count - 1 && !input_bezier.closed() ) { zig_zag_corner(output_bezier, &segment, nullptr, amplitude, direction, tangent_length); } else { next_segment = input_bezier.segment((i + 1) % count); seg_len = LengthData (next_segment, 20); zig_zag_corner(output_bezier, &segment, &next_segment, amplitude, direction, tangent_length); } } return output_bezier; } QIcon glaxnimate::model::ZigZag::static_tree_icon() { return QIcon::fromTheme("path-simplify"); } QString glaxnimate::model::ZigZag::static_type_name_human() { return tr("Zig Zag"); } bool glaxnimate::model::ZigZag::process_collected() const { return false; } glaxnimate::math::bezier::MultiBezier glaxnimate::model::ZigZag::process( glaxnimate::model::FrameTime t, const math::bezier::MultiBezier& mbez ) const { if ( mbez.empty() ) return {}; int frequency = math::max(0, qRound(this->frequency.get_at(t))); auto amplitude = this->amplitude.get_at(t); auto point_type = this->style.get(); MultiBezier out; for ( const auto& inbez : mbez.beziers() ) out.beziers().push_back(zig_zag_bezier(inbez, amplitude, frequency, point_type)); return out; } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/svg/svg_mime.hpp000664 001750 001750 00000003231 14477652011 030021 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include "io/svg/svg_parser.hpp" #include "io/svg/parse_error.hpp" #include "io/svg/svg_renderer.hpp" #include "io/mime/mime_serializer.hpp" namespace glaxnimate::io::svg { class SvgMime : public io::mime::MimeSerializer { public: QString slug() const override { return "svg"; } QString name() const override { return QObject::tr("SVG"); } QStringList mime_types() const override { return {"image/svg+xml"}; } QByteArray serialize(const std::vector& selection) const override { io::svg::SvgRenderer svg_rend(io::svg::NotAnimated, io::svg::CssFontType::FontFace); for ( auto node : selection ) svg_rend.write_node(node); return svg_rend.dom().toByteArray(0); } io::mime::DeserializedData deserialize(const QByteArray& data) const override { QBuffer buffer(const_cast(&data)); buffer.open(QIODevice::ReadOnly); auto on_error = [this](const QString& s){message(s);}; try { return io::svg::SvgParser(&buffer, deserialize_group_mode, nullptr, on_error) .parse_to_objects(); } catch ( const io::svg::SvgParseError& err ) { message(err.formatted("Clipboard")); return {}; } } bool can_deserialize() const override { return true; } /// \todo show in settings io::svg::SvgParser::GroupMode deserialize_group_mode = io::svg::SvgParser::Inkscape; private: static Autoreg autoreg; }; } // namespace glaxnimate::io::svg mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/plugin/executor.hpp000664 001750 001750 00000000754 14477652011 030150 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include namespace glaxnimate::plugin { class Plugin; class PluginScript; class Executor { public: virtual bool execute(const plugin::Plugin& plugin, const plugin::PluginScript& script, const QVariantList& in_args) = 0; virtual QVariant get_global(const QString& name) = 0; }; } // namespace glaxnimate::plugin mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/object.cpp000664 001750 001750 00000010466 14477652011 027356 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "object.hpp" #include #include "property/property.hpp" #include "model/document.hpp" #include "app/log/log.hpp" class glaxnimate::model::Object::Private { public: std::unordered_map props; std::vector prop_order; Document* document; FrameTime current_time = 0; }; glaxnimate::model::Object::Object(Document* document) : d(std::make_unique()) { d->document = document; if ( document && thread() != document->thread() ) moveToThread(document->thread()); } glaxnimate::model::Object::~Object() = default; void glaxnimate::model::Object::assign_from(const glaxnimate::model::Object* other) { other->clone_into(this); } void glaxnimate::model::Object::transfer(glaxnimate::model::Document* document) { if ( thread() != document->thread() ) moveToThread(document->thread()); on_transfer(document); d->document = document; for ( auto prop: d->prop_order ) prop->transfer(document); } void glaxnimate::model::Object::clone_into(glaxnimate::model::Object* dest) const { if ( dest->metaObject() != metaObject() ) { app::log::Log log("Object", type_name()); log.stream(app::log::Error) << "trying to clone into" << dest->type_name() << "from" << type_name(); log.stream(app::log::Info) << "make sure clone_covariant is implemented for" << type_name() << "or use GLAXNIMATE_OBJECT"; return; } for ( BaseProperty* prop : d->prop_order ) dest->get_property(prop->name())->assign_from(prop); } void glaxnimate::model::Object::property_value_changed(const BaseProperty* prop, const QVariant& value) { on_property_changed(prop, value); emit property_changed(prop, value); if ( prop->traits().flags & PropertyTraits::Visual ) { d->document->graphics_invalidated(); emit visual_property_changed(prop, value); } } void glaxnimate::model::Object::add_property(glaxnimate::model::BaseProperty* prop) { d->props[prop->name()] = prop; d->prop_order.push_back(prop); } QVariant glaxnimate::model::Object::get(const QString& property) const { auto it = d->props.find(property); if ( it == d->props.end() ) return QVariant{}; return it->second->value(); } glaxnimate::model::BaseProperty * glaxnimate::model::Object::get_property ( const QString& property ) { auto it = d->props.find(property); if ( it == d->props.end() ) return nullptr; return it->second; } bool glaxnimate::model::Object::set(const QString& property, const QVariant& value) { auto it = d->props.find(property); if ( it == d->props.end() ) return false; return it->second->set_value(value); } bool glaxnimate::model::Object::has ( const QString& property ) const { return d->props.find(property) != d->props.end(); } const std::vector& glaxnimate::model::Object::properties() const { return d->prop_order; } QString glaxnimate::model::Object::type_name() const { return detail::naked_type_name(metaObject()->className()); } QString glaxnimate::model::detail::naked_type_name(QString class_name) { int ns = class_name.lastIndexOf(":"); if ( ns != -1 ) class_name = class_name.mid(ns+1); return class_name; } glaxnimate::model::Document * glaxnimate::model::Object::document() const { return d->document; } void glaxnimate::model::Object::push_command(QUndoCommand* cmd) { d->document->push_command(cmd); } bool glaxnimate::model::Object::set_undoable ( const QString& property, const QVariant& value ) { auto it = d->props.find(property); if ( it != d->props.end() ) return it->second->set_undoable(value); return false; } void glaxnimate::model::Object::set_time(glaxnimate::model::FrameTime t) { d->current_time = t; for ( auto prop: d->prop_order ) prop->set_time(t); } glaxnimate::model::FrameTime glaxnimate::model::Object::time() const { return d->current_time; } void glaxnimate::model::Object::stretch_time(qreal multiplier) { for ( const auto& prop : d->prop_order ) prop->stretch_time(multiplier); d->current_time *= multiplier; } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/aep/riff.hpp000664 001750 001750 00000032742 14477652011 027120 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include #include #include #include #include #include #include namespace glaxnimate::io::aep { template struct IntSize; template<> struct IntSize<1> { using uint = std::uint8_t; using sint = std::int8_t; }; template<> struct IntSize<2> { using uint = std::uint16_t; using sint = std::int16_t; }; template<> struct IntSize<3> { using uint = std::uint32_t; using sint = std::int32_t; }; template<> struct IntSize<4> { using uint = std::uint32_t; using sint = std::int32_t; }; template<> struct IntSize<8> { using uint = std::uint64_t; using sint = std::int64_t; }; class Endianness { public: template constexpr T read_uint(const QByteArray& arr) const noexcept { if constexpr ( sizeof(T) == 1 ) { return arr[0]; } else { T v = 0; for ( int i = 0; i < arr.size(); i++ ) { int j = swap() ? arr.size() - i - 1 : i; v <<= 8; v |= std::uint8_t(arr[j]); } return v; } } template constexpr typename IntSize::uint read_uint(const QByteArray& arr) const noexcept { return read_uint::uint>(arr); } template constexpr typename IntSize::sint read_sint(const QByteArray& arr) const noexcept { using uint_t = typename IntSize::uint; using sint_t = typename IntSize::uint; uint_t uint = read_uint(arr); constexpr const uint_t sbit = 1ull << (size * 8 - 1); if ( !(uint & sbit) ) return uint; return -sint_t(~uint + 1); } template constexpr T read_sint(const QByteArray& arr) const noexcept { return read_uint(arr); } /** * \note Expects IEEE 754 floats */ constexpr float read_float32(const QByteArray& arr) const noexcept { union { std::uint32_t vali; float valf; } x {read_uint(arr)}; return x.valf; } /** * \note Expects IEEE 754 floats */ constexpr double read_float64(const QByteArray& arr) const noexcept { union { std::uint64_t vali; double valf; } x {read_uint(arr)}; return x.valf; } template QByteArray write_uint(T val) const { QByteArray out(sizeof(T), 0); for ( int i = 0; i < out.size(); i++ ) { int j = i; if ( byte_order == QSysInfo::Endian::BigEndian ) j = sizeof(T) - 1 - i; out[j] = val & 0xff; val >>= 8; } return out; } /** * \note Expects IEEE 754 floats */ QByteArray write_float32(float val) const noexcept { union { float valf; std::uint32_t vali; } x {val}; return write_uint(x.vali); } /** * \note Expects IEEE 754 floats */ QByteArray write_float64(double val) const noexcept { union { double valf; std::uint64_t vali; } x {val}; return write_uint(x.vali); } static constexpr const Endianness Big() noexcept { return {QSysInfo::BigEndian}; } static constexpr const Endianness Little() noexcept { return {QSysInfo::LittleEndian}; } private: constexpr bool swap() const noexcept { return QSysInfo::ByteOrder == byte_order; } constexpr Endianness(QSysInfo::Endian byte_order) noexcept : byte_order(byte_order) {} QSysInfo::Endian byte_order; }; class RiffError : public std::runtime_error { public: RiffError(QString message) : runtime_error(message.toStdString()), message(std::move(message)) {} QString message; }; class Flags { public: constexpr Flags(std::uint32_t data) noexcept : data(data) {} constexpr bool get(int byte, int bit) const noexcept { return (data >> (8*byte)) & (1 << bit); } private: std::uint32_t data; }; class BinaryReader { public: BinaryReader() : endian(Endianness::Big()), file(nullptr), file_pos(0), length_left(0) {} BinaryReader(Endianness endian, QIODevice* file, std::uint32_t length, qint64 pos) : endian(endian), file(file), file_pos(pos), length_left(length) {} BinaryReader(Endianness endian, QIODevice* file, std::uint32_t length) : endian(endian), file(file), file_pos(file->pos()), length_left(length) {} /* BinaryReader(Endianness endian, QByteArray& data, std::uint32_t length) : endian(endian), buffer(std::make_unique(&data)), file(buffer.get()), length_left(length) {} */ BinaryReader sub_reader(std::uint32_t length) { if ( length > length_left ) throw RiffError(QObject::tr("Not enough data")); length_left -= length; BinaryReader reader{endian, file, length, file_pos}; file_pos += length; return reader; } /** * \brief Creates a sub-reader without affecting the current reader */ BinaryReader sub_reader(std::uint32_t length, std::uint32_t offset) const { if ( length + offset > length_left ) throw RiffError(QObject::tr("Not enough data")); return {endian, file, length, file_pos + offset}; } void set_endianness(const Endianness& endian) { this->endian = endian; } QByteArray read() { return read(length_left); } QByteArray read(std::uint32_t length) { length_left -= length; file_pos += length; auto data = file->read(length); if ( std::uint32_t(data.size()) < length ) throw RiffError(QObject::tr("Not enough data")); return data; } template typename IntSize::uint read_uint() { return endian.read_uint(read(size)); } template typename IntSize::sint read_sint() { return endian.read_sint(read(size)); } std::uint8_t read_uint8() { return read_uint<1>(); } std::uint16_t read_uint16() { return read_uint<2>(); } std::uint32_t read_uint32() { return read_uint<4>(); } std::int16_t read_sint16() { return read_sint<2>(); } std::uint32_t read_sint32() { return read_sint<4>(); } float read_float32() { return endian.read_float32(read(4)); } double read_float64() { return endian.read_float64(read(8)); } void skip(std::uint32_t length) { length_left -= length; file_pos += length; if ( file->skip(length) < length ) throw RiffError(QObject::tr("Not enough data")); } std::int64_t available() const { return length_left; } QString read_utf8(std::uint32_t length) { return QString::fromUtf8(read(length)); } /** * \brief Read a NUL-terminated UTF-8 string */ QString read_utf8_nul(std::uint32_t length) { auto data = read(length); int str_len = data.indexOf('\0'); return QString::fromUtf8(data.data(), str_len == -1 ? length : str_len); } QString read_utf8_nul() { return read_utf8_nul(length_left); } std::uint32_t size() const { return length_left; } void prepare() const { file->seek(file_pos); } /** * \brief Defer data reading to a later point */ void defer() { file->skip(length_left); } template std::vector read_array(T (BinaryReader::*read_fn)(), int count) { std::vector out; out.reserve(count); for ( int i = 0; i < count; i++ ) out.push_back((this->*read_fn)()); return out; } QIODevice* device() const { return file; } private: Endianness endian; // std::unique_ptr buffer; QIODevice* file; qint64 file_pos; std::int64_t length_left; }; struct ChunkId { char name[4] = ""; ChunkId(const QByteArray& arr) { std::memcpy(name, (void*)arr.data(), std::min(4, arr.size())); } bool operator==(const char* ch) const { return std::strncmp(name, ch, 4) == 0; } bool operator!=(const char* ch) const { return std::strncmp(name, ch, 4) != 0; } QString to_string() const { return QString::fromLatin1(QByteArray(name, 4)); } }; struct RiffChunk { ChunkId header; std::uint32_t length = 0; ChunkId subheader = {""}; BinaryReader reader = {}; std::vector> children = {}; using iterator = std::vector>::const_iterator; struct RangeIterator { public: constexpr RangeIterator(const iterator& internal, const char* name, const RiffChunk* chunk) : internal(internal), name(name), chunk(chunk) {} RangeIterator& operator++() { internal = chunk->find(name, internal + 1); return *this; } const RiffChunk& operator*() const { return **internal; } const RiffChunk* operator->() const { return internal->get(); } bool operator==(const RangeIterator& other) const { return other.internal == internal; } bool operator!=(const RangeIterator& other) const { return other.internal != internal; } private: iterator internal; const char* name; const RiffChunk* chunk; }; struct FindRange { RangeIterator a, b; RangeIterator begin() const { return a; } RangeIterator end() const { return b; } }; bool operator==(const char* name) const { if ( header == name ) return true; if ( header == "LIST" ) return subheader == name; return false; } bool operator!=(const char* name) const { return !(*this == name); } BinaryReader data() const { BinaryReader data = reader; data.prepare(); return data; } iterator find(const char* name) const { return find(name, children.begin()); } iterator find(const char* name, iterator from) const { return std::find_if(from, children.end(), [name](const std::unique_ptr& c){ return *c == name; }); } const RiffChunk* child(const char* name) const { auto it = find(name); if ( it == children.end() ) return nullptr; return it->get(); } FindRange find_all(const char* name) const { return {{find(name), name, this}, {children.end(), name, this}}; } void find_multiple( const std::vector& out, const std::vector names ) const { std::size_t found = 0; for ( const auto& child: children ) { for ( std::size_t i = 0; i < names.size(); i++ ) { if ( !*out[i] && *child == names[i] ) { *out[i] = child.get(); found++; if ( found == names.size() ) return; } } } } const ChunkId& name() const { if ( header == "LIST" ) return subheader; return header; } }; class RiffReader { public: virtual ~RiffReader() = default; RiffChunk parse(QIODevice* file) { auto headerraw = file->read(4); ChunkId header = headerraw; Endianness endian = Endianness::Big(); if ( header == "RIFF" ) endian = Endianness::Little(); else if ( header != "RIFX" ) throw RiffError(QObject::tr("Unknown format %1").arg(QString(headerraw))); auto length = endian.read_uint<4>(file->read(4)); BinaryReader reader = BinaryReader(endian, file, length); ChunkId format = reader.read(4); RiffChunk chunk{header, length, format}; chunk.reader = reader; on_root(chunk); return chunk; } protected: RiffChunk read_chunk(BinaryReader& reader) { ChunkId header = reader.read(4); auto length = reader.read_uint<4>(); RiffChunk chunk{header, length}; chunk.reader = reader.sub_reader(length); on_chunk(chunk); if ( length % 2 ) reader.skip(1); return chunk; } std::vector> read_chunks(BinaryReader& reader) { std::vector> chunks; while ( reader.available() ) chunks.push_back(std::make_unique(read_chunk(reader))); return chunks; } virtual void on_root(RiffChunk& chunk) { chunk.children = read_chunks(chunk.reader); } virtual void on_chunk(RiffChunk& chunk) { if ( chunk.header == "LIST" ) { chunk.subheader = chunk.reader.read(4); chunk.children = read_chunks(chunk.reader); } else { chunk.reader.defer(); } } }; } // namespace glaxnimate::io::aep mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/rive/rive_loader.hpp000664 001750 001750 00000001632 14477652011 030657 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include "io/binary_stream.hpp" #include "type_system.hpp" #include "rive_format.hpp" namespace glaxnimate::io::rive { class RiveLoader { public: RiveLoader(BinaryInputStream& stream, RiveFormat* format); std::vector load_object_list(); bool load_document(model::Document* document); const PropertyTable& extra_properties() const; private: Object read_object(); QVariant read_property_value(PropertyType type); PropertyTable read_property_table(); void skip_value(PropertyType type); QByteArray read_raw_string(); QString read_string_utf8(); model::Document* document; BinaryInputStream& stream; RiveFormat* format; PropertyTable extra_props; TypeSystem types; }; } // namespace glaxnimate::io::rive mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/svg/font_weight.hpp000664 001750 001750 00000002510 14477652011 030527 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include "math/vector.hpp" #include "math/math.hpp" namespace glaxnimate::io::svg { /** * Compare: * https://doc.qt.io/qt-5/qfont.html#Weight-enum * https://developer.mozilla.org/en-US/docs/Web/CSS/font-weight#common_weight_name_mapping */ struct WeightConverter { inline static constexpr const std::array qt = { 00, 12, 25, 50, 57, 63, 75, 81, 87, }; inline static constexpr const std::array css = { 100, 200, 300, 400, 500, 600, 700, 900, 950, }; static int convert(int old, const std::array& from, const std::array& to) { int index; for ( index = 0; index < 9; index++ ) { if ( from[index] == old ) return to[index]; else if ( from[index] > old ) break; } if ( index == 9 ) index--; qreal t = (old - from[index]) / qreal(from[index+1] - from[index]); return qRound(math::lerp(to[index], to[index+1], t)); } }; } // namespace glaxnimate::io::svg mlt-7.22.0/src/modules/glaxnimate/glaxnimate/external/QtAppSetup/src/app/utils/qstring_hash.hpp000664 001750 001750 00000000633 14477652011 034362 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include #include #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) namespace std { template<> struct hash { std::size_t operator()(const QString& s) const noexcept { return (size_t) qHash(s); } }; } #endif mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/shapes/repeater.cpp000664 001750 001750 00000010241 14477652011 031171 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "repeater.hpp" #include #include "model/shapes/group.hpp" #include "model/animation/join_animatables.hpp" using namespace glaxnimate; GLAXNIMATE_OBJECT_IMPL(glaxnimate::model::Repeater) QIcon glaxnimate::model::Repeater::static_tree_icon() { return QIcon::fromTheme("table"); } QString glaxnimate::model::Repeater::static_type_name_human() { return tr("Repeater"); } math::bezier::MultiBezier glaxnimate::model::Repeater::process(FrameTime t, const math::bezier::MultiBezier& mbez) const { QTransform matrix = transform->transform_matrix(t); math::bezier::MultiBezier out; math::bezier::MultiBezier copy = mbez; for ( int i = 0; i < copies.get_at(t); i++ ) { out.append(copy); copy.transform(matrix); } return out; } bool glaxnimate::model::Repeater::process_collected() const { return true; } void glaxnimate::model::Repeater::on_paint(QPainter* painter, glaxnimate::model::FrameTime t, glaxnimate::model::VisualNode::PaintMode mode, glaxnimate::model::Modifier*) const { QTransform matrix = transform->transform_matrix(t); auto alpha_s = start_opacity.get_at(t); auto alpha_e = end_opacity.get_at(t); int n_copies = copies.get_at(t); for ( int i = 0; i < n_copies; i++ ) { float alpha_lerp = float(i) / (n_copies == 1 ? 1 : n_copies - 1); auto alpha = math::lerp(alpha_s, alpha_e, alpha_lerp); painter->setOpacity(alpha * painter->opacity()); for ( auto sib : affected() ) { if ( sib->visible.get() ) sib->paint(painter, t, mode); } painter->setTransform(matrix, true); } } template> static void increase_transform(glaxnimate::model::AnimatedProperty& into, const glaxnimate::model::AnimatedProperty& from, Func func = {}) { using Keyframe = glaxnimate::model::Keyframe; for ( int i = 0, e = from.keyframe_count(); i < e; i++ ) { auto into_kf = static_cast(into.keyframe(i)); into_kf->set(func(into_kf->get(), static_cast(from.keyframe(i))->get())); } into.set(func(into.get(), from.get())); } static void increase_transform(glaxnimate::model::Transform* into, const glaxnimate::model::Transform* from) { increase_transform(into->position, from->position); increase_transform(into->anchor_point, from->anchor_point); increase_transform(into->rotation, from->rotation); increase_transform(into->scale, from->scale, [](const QVector2D& a, const QVector2D& b){ return QVector2D(a.x() * b.x(), a.y() * b.y()); }); } std::unique_ptr glaxnimate::model::Repeater::to_path() const { auto group = std::make_unique(document()); group->name.set(name.get()); group->visible.set(visible.get()); group->locked.set(locked.get()); auto child = std::make_unique(document()); for ( auto sib : affected() ) child->shapes.insert(sib->to_path()); JoinAnimatables anim({&start_opacity, &end_opacity}, JoinAnimatables::NoValues); int n_copies = copies.get(); float alpha_lerp = 1; auto func = [&alpha_lerp](float a, float b){ return math::lerp(a, b, alpha_lerp); }; for ( int i = 0; i < n_copies; i++ ) { alpha_lerp = float(i) / (n_copies == 1 ? 1 : n_copies - 1); auto cloned = static_cast(group->shapes.insert_clone(child.get())); anim.apply_to(&cloned->opacity, func, &start_opacity, &end_opacity); if ( i == 0 ) child->transform->assign_from(transform.get()); else increase_transform(child->transform.get(), transform.get()); } group->set_time(time()); return group; } int glaxnimate::model::Repeater::max_copies() const { int max = copies.get(); for ( int i = 0, e = copies.keyframe_count(); i < e; ++i ) { int val = copies.keyframe(i)->get(); if ( val > max ) max = val; } return max; } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/glaxnimate/000775 001750 001750 00000000000 14477652011 027035 5ustar00ddennedyddennedy000000 000000 mlt-7.22.0/src/modules/glaxnimate/glaxnimate/external/QtAppSetup/src/app/settings/settings.hpp000664 001750 001750 00000006372 14477652011 034236 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include #include #include "app/settings/settings_group.hpp" namespace app::settings { /** * \brief Singleton for handling settings */ class Settings { Q_DECLARE_TR_FUNCTIONS(Settings) public: using iterator = std::vector::const_iterator; static Settings& instance() { static Settings singleton; return singleton; } /** * \brief Get the value of the given setting, or add a new (internal) setting if it hasn't been declared */ QVariant define(const QString& group, const QString& setting, const QVariant& default_value); /** * \brief Get the value of a declared setting */ QVariant get_value(const QString& group, const QString& setting) const; /** * \brief Set the value of a declared setting */ bool set_value(const QString& group, const QString& setting, const QVariant& value); /** * \brief Get the default value of a declared setting */ QVariant get_default(const QString& group, const QString& setting) const; /** * \brief Load all settings */ void load(); /** * \brief Save all settings */ void save(); iterator begin() const { return groups_.begin(); } iterator end() const { return groups_.end(); } std::vector& groups() { return groups_; } void add_group(QString slug, utils::TranslatedString label, const QString& icon, SettingList settings); void add_group(CustomSettingsGroup group); private: Settings() = default; Settings(const Settings&) = delete; ~Settings() = default; QHash order; std::vector groups_; }; /** * \brief Get the value of a declared setting */ template T get(const QString& group, const QString& setting) { return Settings::instance().get_value(group, setting).value(); } /** * \brief Get the value of a declared setting * \returns \p default_value if the setting isn't found */ template T get(const QString& group, const QString& setting, const T& defval) { auto var = Settings::instance().get_value(group, setting); if ( var.canConvert() ) return var.value(); return defval; } /** * \brief Get the default value of a declared setting */ template T get_default(const QString& group, const QString& setting) { return Settings::instance().get_default(group, setting).value(); } /** * \brief Set the value of a declared setting */ template bool set(const QString& group, const QString& setting, const T& value) { return Settings::instance().set_value(group, setting, QVariant::fromValue(value)); } /** * \brief Get the value of the given setting, or add a new (internal) setting if it hasn't been declared */ template T define(const QString& group, const QString& setting, const T& default_value) { QVariant var = Settings::instance().define(group, setting, QVariant::fromValue(default_value)); if ( var.canConvert() ) return var.value(); return default_value; } } // namespace app::settings mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/lottie/lottie_format.cpp000664 001750 001750 00000004507 14477652011 031566 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "lottie_format.hpp" #include "lottie_importer.hpp" #include "lottie_exporter.hpp" glaxnimate::io::Autoreg glaxnimate::io::lottie::LottieFormat::autoreg; bool glaxnimate::io::lottie::LottieFormat::on_save(QIODevice& file, const QString&, model::Composition* comp, const QVariantMap& setting_values) { file.write(cbor_write_json(to_json(comp, setting_values["strip"].toBool(), false, setting_values), !setting_values["pretty"].toBool())); return true; } QCborMap glaxnimate::io::lottie::LottieFormat::to_json(model::Composition* comp, bool strip, bool strip_raster, const QVariantMap& settings) { detail::LottieExporterState exp(this, comp, strip, strip_raster, settings); return exp.to_json(); } bool glaxnimate::io::lottie::LottieFormat::load_json(const QByteArray& data, model::Document* document) { QJsonDocument jdoc; try { jdoc = QJsonDocument::fromJson(data); } catch ( const QJsonParseError& err ) { emit error(tr("Could not parse JSON: %1").arg(err.errorString())); return false; } if ( !jdoc.isObject() ) { emit error(tr("No JSON object found")); return false; } QJsonObject top_level = jdoc.object(); detail::LottieImporterState imp{document, this}; imp.load(top_level); return true; } bool glaxnimate::io::lottie::LottieFormat::on_open(QIODevice& file, const QString&, model::Document* document, const QVariantMap&) { return load_json(file.readAll(), document); } std::unique_ptr glaxnimate::io::lottie::LottieFormat::save_settings(model::Composition*) const { return std::make_unique(app::settings::SettingList{ app::settings::Setting("pretty", tr("Pretty"), tr("Pretty print the JSON"), false), app::settings::Setting("strip", tr("Strip"), tr("Strip unused properties"), false), app::settings::Setting("auto_embed", tr("Embed Images"), tr("Automatically embed non-embedded images"), false), app::settings::Setting("old_kf", tr("Legacy Keyframes"), tr("Compatibility with lottie-web versions prior to 5.0.0"), false), }); } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/command/000775 001750 001750 00000000000 14477652011 025713 5ustar00ddennedyddennedy000000 000000 mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/assets/assets.cpp000664 001750 001750 00000017463 14477652011 030720 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "assets.hpp" #include "model/document.hpp" #include "command/object_list_commands.hpp" GLAXNIMATE_OBJECT_IMPL(glaxnimate::model::NamedColorList) GLAXNIMATE_OBJECT_IMPL(glaxnimate::model::GradientColorsList) GLAXNIMATE_OBJECT_IMPL(glaxnimate::model::GradientList) GLAXNIMATE_OBJECT_IMPL(glaxnimate::model::BitmapList) GLAXNIMATE_OBJECT_IMPL(glaxnimate::model::CompositionList) GLAXNIMATE_OBJECT_IMPL(glaxnimate::model::FontList) GLAXNIMATE_OBJECT_IMPL(glaxnimate::model::Assets) void glaxnimate::model::NamedColorList::on_added(glaxnimate::model::NamedColor* color, int position) { connect(color, &Object::property_changed, this, [position, color, this]{ emit color_changed(position, color); }); Ctor::on_added(color, position); emit color_added(position, color); } void glaxnimate::model::NamedColorList::on_removed(glaxnimate::model::NamedColor* color, int position) { disconnect(color, nullptr, this, nullptr); Ctor::on_removed(color, position); emit color_removed(position, color); } QIcon glaxnimate::model::NamedColorList::tree_icon() const { return QIcon::fromTheme("paint-swatch"); } QIcon glaxnimate::model::GradientColorsList::tree_icon() const { return QIcon::fromTheme("paint-gradient-linear"); } QIcon glaxnimate::model::GradientList::tree_icon() const { return QIcon::fromTheme("gradient"); } QIcon glaxnimate::model::BitmapList::tree_icon() const { return QIcon::fromTheme("folder-images"); } QIcon glaxnimate::model::CompositionList::tree_icon() const { return QIcon::fromTheme("folder-videos"); } void glaxnimate::model::CompositionList::on_added(glaxnimate::model::Composition* obj, int position) { obj->attach(); document()->comp_graph().add_composition(obj); emit docnode_child_add_end(obj, position); emit precomp_added(obj, position); } void glaxnimate::model::CompositionList::on_removed(glaxnimate::model::Composition* obj, int position) { obj->detach(); document()->comp_graph().remove_composition(obj); emit docnode_child_remove_end(obj, position); } void glaxnimate::model::FontList::on_added ( model::EmbeddedFont* obj, int position ) { obj->attach(); emit docnode_child_add_end(obj, position); emit font_added(obj); } glaxnimate::model::NamedColor* glaxnimate::model::Assets::add_color(const QColor& color, const QString& name) { auto ptr = std::make_unique(document()); ptr->color.set(color); ptr->name.set(name); auto raw = ptr.get(); push_command(new command::AddObject(&colors->values, std::move(ptr), colors->values.size())); return raw; } glaxnimate::model::Bitmap * glaxnimate::model::Assets::add_image_file(const QString& filename, bool embed) { auto image = std::make_unique(document()); image->filename.set(filename); if ( image->pixmap().isNull() ) return nullptr; image->embed(embed); auto ptr = image.get(); push_command(new command::AddObject(&images->values, std::move(image), images->values.size())); return ptr; } glaxnimate::model::Bitmap * glaxnimate::model::Assets::add_image(const QImage& qimage, const QString& store_as) { auto image = std::make_unique(document()); image->set_pixmap(qimage, store_as); auto ptr = image.get(); push_command(new command::AddObject(&images->values, std::move(image), images->values.size())); return ptr; } glaxnimate::model::GradientColors* glaxnimate::model::Assets::add_gradient_colors(int index) { glaxnimate::model::GradientColors *ptr = new glaxnimate::model::GradientColors(document()); ptr->name.set(ptr->type_name_human()); push_command(new command::AddObject(&gradient_colors->values, std::unique_ptr(ptr), index)); return ptr; } glaxnimate::model::Gradient* glaxnimate::model::Assets::add_gradient(int index) { glaxnimate::model::Gradient *ptr = new glaxnimate::model::Gradient(document()); ptr->name.set(ptr->type_name_human()); push_command(new command::AddObject(&gradients->values, std::unique_ptr(ptr), index)); return ptr; } /*glaxnimate::model::Composition* glaxnimate::model::Assets::add_composition() { auto comp = std::make_unique(document()); auto ptr = comp.get(); push_command(new command::AddObject(&compositions->values, std::move(comp), compositions->values.size())); return ptr; }*/ glaxnimate::model::Composition* glaxnimate::model::Assets::add_comp_no_undo() { auto comp = std::make_unique(document()); return compositions->values.insert(std::move(comp)); } QIcon glaxnimate::model::Assets::tree_icon() const { return QIcon::fromTheme("folder-stash"); } QIcon glaxnimate::model::Assets::instance_icon() const { return tree_icon(); } glaxnimate::model::DocumentNode* glaxnimate::model::detail::defs(glaxnimate::model::Document* doc) { return doc->assets(); } glaxnimate::model::DocumentNode * glaxnimate::model::Assets::docnode_parent() const { return nullptr; } int glaxnimate::model::Assets::docnode_child_count() const { return 6; } glaxnimate::model::DocumentNode * glaxnimate::model::Assets::docnode_child(int index) const { switch ( index ) { case 0: return const_cast(static_cast(colors.get())); case 1: return const_cast(static_cast(images.get())); case 2: return const_cast(static_cast(gradient_colors.get())); case 3: return const_cast(static_cast(gradients.get())); case 4: return const_cast(static_cast(compositions.get())); case 5: return const_cast(static_cast(fonts.get())); default: return nullptr; } } int glaxnimate::model::Assets::docnode_child_index(glaxnimate::model::DocumentNode* dn) const { if ( dn == colors.get() ) return 0; if ( dn == images.get() ) return 1; if ( dn == gradient_colors.get() ) return 2; if ( dn == gradients.get() ) return 3; if ( dn == compositions.get() ) return 4; if ( dn == fonts.get() ) return 5; return -1; } glaxnimate::model::EmbeddedFont* glaxnimate::model::Assets::add_font(const QByteArray& ttf_data) { auto font = std::make_unique(document()); font->data.set(ttf_data); if ( auto old = font_by_index(font->database_index()) ) return old; auto ptr = font.get(); push_command(new command::AddObject(&fonts->values, std::move(font), fonts->values.size())); return ptr; } glaxnimate::model::EmbeddedFont* glaxnimate::model::Assets::add_font(const CustomFont& custom_font) { if ( auto old = font_by_index(custom_font.database_index()) ) return old; auto font = std::make_unique(document(), custom_font); auto ptr = font.get(); push_command(new command::AddObject(&fonts->values, std::move(font), fonts->values.size())); return ptr; } glaxnimate::model::EmbeddedFont * glaxnimate::model::Assets::font_by_index(int database_index) const { for ( const auto& font : fonts->values ) if ( font->database_index() == database_index ) return font.get(); return nullptr; } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/shapes/fill.cpp000664 001750 001750 00000001626 14477652011 030317 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "fill.hpp" GLAXNIMATE_OBJECT_IMPL(glaxnimate::model::Fill) void glaxnimate::model::Fill::on_paint(QPainter* p, glaxnimate::model::FrameTime t, glaxnimate::model::VisualNode::PaintMode, glaxnimate::model::Modifier* modifier) const { p->setBrush(brush(t)); p->setOpacity(p->opacity() * opacity.get_at(t)); p->setPen(Qt::NoPen); math::bezier::MultiBezier bez; if ( modifier ) bez = modifier->collect_shapes_from(affected(), t, {}); else bez = collect_shapes(t, {}); QPainterPath path = bez.painter_path(); path.setFillRule(Qt::FillRule(fill_rule.get())); p->drawPath(path); } QPainterPath glaxnimate::model::Fill::to_painter_path_impl(glaxnimate::model::FrameTime t) const { return collect_shapes(t, {}).painter_path(); } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/svg/svg_html_format.hpp000664 001750 001750 00000002077 14477652011 031415 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include "io/lottie/lottie_html_format.hpp" #include "io/svg/svg_renderer.hpp" namespace glaxnimate::io::svg { class SvgHtmlFormat : public ImportExport { public: QString slug() const override { return "svg_html"; } QString name() const override { return QObject::tr("SVG Preview"); } QStringList extensions() const override { return {"html", "htm"}; } bool can_save() const override { return true; } bool can_open() const override { return false; } private: bool on_save(QIODevice& file, const QString&, model::Composition* comp, const QVariantMap&) override { file.write(lottie::LottieHtmlFormat::html_head(this, comp, {})); file.write("
"); SvgRenderer rend(SMIL, CssFontType::FontFace); rend.write_main(comp); rend.write(&file, true); file.write("
"); return true; } }; } // namespace glaxnimate::io::svg mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/property/object_list_property.hpp000664 001750 001750 00000023505 14477652011 034244 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include "property.hpp" #include "model/document_node.hpp" #define GLAXNIMATE_PROPERTY_LIST_IMPL(type, name) \ public: \ QVariantList get_##name() const \ { \ QVariantList ret; \ for ( const auto & ptr : name ) \ ret.push_back(QVariant::fromValue(ptr.get())); \ return ret; \ } \ private: \ Q_PROPERTY(QVariantList name READ get_##name) \ Q_CLASSINFO(#name, "property list " #type) \ // macro end #define GLAXNIMATE_PROPERTY_LIST(type, name) \ public: \ ObjectListProperty name{this, #name}; \ GLAXNIMATE_PROPERTY_LIST_IMPL(type, name) \ // macro end namespace glaxnimate::model { class ObjectListPropertyBase : public BaseProperty { public: ObjectListPropertyBase(Object* obj, const QString& name) : BaseProperty(obj, name, {PropertyTraits::Object, PropertyTraits::List|PropertyTraits::Visual}) {} /** * \brief Inserts a clone of the passed object * \return The internal object or \b nullptr in case of failure */ virtual Object* insert_clone(Object* object, int index = -1) = 0; bool set_value(const QVariant& val) override { if ( !val.canConvert() ) return false; for ( const auto& v : val.toList() ) { if ( !v.canConvert() ) continue; insert_clone(v.value()); } return true; } bool valid_value(const QVariant& val) const override { return val.canConvert(); } virtual std::vector valid_reference_values(bool allow_null) const = 0; virtual bool is_valid_reference_value(model::DocumentNode *, bool allow_null) const = 0; protected: void object_removed(model::DocumentNode* ptr) { ptr->removed_from_list(); } void object_added(model::DocumentNode* ptr) { ptr->added_to_list(static_cast(object())); } }; namespace detail { template class ObjectListProperty : public ObjectListPropertyBase { public: using value_type = Type; using pointer = std::unique_ptr; using reference = Type&; // using const_reference = const Type&; using iterator = typename std::vector::const_iterator; /** * \brief Utility to perform raw operations on the list of objects * \warning Use with care, this won't invoke any callbacks */ class Raw { public: using iterator = typename std::vector::iterator; using move_iterator = std::move_iterator; void clear() { subject->objects.clear(); } iterator begin() { return subject->objects.begin(); } iterator end() { return subject->objects.end(); } move_iterator move_begin() { return move_iterator(begin()); } move_iterator move_end() { return move_iterator(end()); } private: friend ObjectListProperty; Raw(ObjectListProperty* subject) : subject(subject) {} ObjectListProperty* subject; }; ObjectListProperty( Object* obj, const QString& name, PropertyCallback callback_insert = &DocumentNode::docnode_child_add_end, PropertyCallback callback_remove = &DocumentNode::docnode_child_remove_end, PropertyCallback callback_insert_begin = &DocumentNode::docnode_child_add_begin, PropertyCallback callback_remove_begin = &DocumentNode::docnode_child_remove_begin, PropertyCallback callback_move_begin = &DocumentNode::docnode_child_move_begin, PropertyCallback callback_move_end = &DocumentNode::docnode_child_move_end ) : ObjectListPropertyBase(obj, name), callback_insert(std::move(callback_insert)), callback_remove(std::move(callback_remove)), callback_insert_begin(std::move(callback_insert_begin)), callback_remove_begin(std::move(callback_remove_begin)), callback_move_begin(std::move(callback_move_begin)), callback_move_end(std::move(callback_move_end)) {} value_type* operator[](int i) const { return objects[i].get(); } int size() const { return objects.size(); } bool empty() const { return objects.empty(); } iterator begin() const { return objects.begin(); } iterator end() const { return objects.end(); } value_type* back() const { return objects.back().get(); } value_type* emplace(value_type* p, int position = -1) { return insert(std::unique_ptr(p), position); } value_type* insert(pointer p, int position = -1) { if ( !valid_index(position) ) position = size(); callback_insert_begin(this->object(), position); auto ptr = p.get(); objects.insert(objects.begin()+position, std::move(p)); ptr->set_time(object()->time()); object_added(ptr); on_insert(position); callback_insert(this->object(), ptr, position); value_changed(); return ptr; } bool valid_index(int index) { return index >= 0 && index < int(objects.size()); } pointer remove(int index) { if ( !valid_index(index) ) return {}; callback_remove_begin(object(), index); auto it = objects.begin() + index; auto v = std::move(*it); objects.erase(it); object_removed(v.get()); on_remove(index); callback_remove(object(), v.get(), index); value_changed(); return v; } void move(int index_a, int index_b) { if ( index_b >= size() ) index_b = size() - 1; if ( !valid_index(index_a) || !valid_index(index_b) || index_a == index_b ) return; callback_move_begin(this->object(), index_a, index_b); auto moved = std::move(objects[index_a]); if ( index_a < index_b ) std::move(objects.begin() + index_a + 1, objects.begin() + index_b + 1, objects.begin() + index_a); else std::move_backward(objects.begin() + index_b, objects.begin() + index_a, objects.begin() + index_a + 1); objects[index_b] = std::move(moved); on_move(index_a, index_b); callback_move_end(this->object(), objects[index_b].get(), index_a, index_b); value_changed(); } QVariant value() const override { QVariantList list; for ( const auto& p : objects ) list.append(QVariant::fromValue((Object*)p.get())); return list; } Object* insert_clone(Object* object, int index = -1) override { if ( !object ) return nullptr; auto basep = object->clone(); Type* casted = qobject_cast(basep.get()); if ( casted ) { basep.release(); insert(pointer(casted), index); return casted; } return nullptr; } void set_time(FrameTime t) override { for ( const auto& o : objects ) o->set_time(t); } int index_of(value_type* obj, int not_found = -1) const { for ( int i = 0; i < size(); i++ ) if ( objects[i].get() == obj ) return i; return not_found; } /** * \brief Allows to perform raw operations on the elements * \warning Use with care, no callbacks will be invoked */ Raw raw() { return Raw{this}; } void transfer(Document* doc) override { for ( const auto& obj : objects ) obj->transfer(doc); } std::vector valid_reference_values(bool allow_null) const override { std::vector res; if ( allow_null ) { res.reserve(objects.size() + 1); res.push_back(nullptr); } else { res.reserve(objects.size()); } for ( const auto& c : objects ) res.push_back(c.get()); return res; } bool is_valid_reference_value(model::DocumentNode * value, bool allow_null) const override { if ( !value ) return allow_null; for ( const auto& c : objects ) if ( c.get() == value ) return true; return false; } void stretch_time(qreal multiplier) override { for ( const auto& object : objects ) object->stretch_time(multiplier); } protected: virtual void on_insert(int index) { Q_UNUSED(index); } virtual void on_remove(int index) { Q_UNUSED(index); } virtual void on_move(int index_a, int index_b) { Q_UNUSED(index_a); Q_UNUSED(index_b); } std::vector objects; PropertyCallback callback_insert; PropertyCallback callback_remove; PropertyCallback callback_insert_begin; PropertyCallback callback_remove_begin; PropertyCallback callback_move_begin; PropertyCallback callback_move_end; }; } // namespace detail template class ObjectListProperty : public detail::ObjectListProperty { public: using detail::ObjectListProperty::ObjectListProperty; }; } // namespace glaxnimate::model src/modules/glaxnimate/glaxnimate/external/QtAppSetup/src/app/scripting/python/attribute.hpp000664 001750 001750 00000000377 14477652011 036004 0ustar00ddennedyddennedy000000 000000 mlt-7.22.0/* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #if __GNUC__ >= 4 # define PY_HIDDEN __attribute__ ((visibility ("hidden"))) #else # define PY_HIDDEN #endif mlt-7.22.0/src/modules/glaxnimate/glaxnimate/external/QtAppSetup/src/app/cli.cpp000664 001750 001750 00000022730 14477652011 031274 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "cli.hpp" #include #include #include #include #include "utils/string_view.hpp" QString app::cli::Argument::get_slug(const QStringList& names) { if ( names.empty() ) return {}; QString match; for ( const auto& name: names ) { if ( name.size() > match.size() ) match = name; } for ( int i = 0; i < match.size(); i++ ) { if ( match[i] != '-' ) return match.mid(i); } return {}; } QVariant app::cli::Argument::arg_to_value(const QString& v, bool* ok) const { switch ( type ) { case String: *ok = true; return v; case Int: return v.toInt(ok); case Size: { if ( !v.contains('x') ) { *ok = false; return {}; } auto vec = utils::split_ref(v, 'x'); if ( vec.size() != 2 ) { *ok = false; return {}; } *ok = true; int x = vec[0].toInt(ok); if ( !ok ) return {}; int y = vec[1].toInt(ok); if ( !ok ) return {}; return QSize(x, y); } case Flag: case ShowHelp: case ShowVersion: *ok = false; return {}; } *ok = false; return {}; } QVariant app::cli::Argument::arg_to_value(const QString& arg) const { bool ok = false; QVariant v = arg_to_value(arg, &ok); if ( !ok ) throw ArgumentError( QApplication::tr("%2 is not a valid value for %1") .arg(names[0]).arg(arg) ); return v; } QVariant app::cli::Argument::args_to_value(const QStringList& args, int& index) const { if ( type == Flag ) return true; if ( args.size() - index < nargs ) throw ArgumentError( QApplication::tr("Not enough arguments for %1: needs %2, has %3") .arg(names[0]).arg(nargs).arg(args.size() - index) ); if ( nargs == 1 ) return arg_to_value(args[index++]); QVariantList vals; for ( int i = 0; i < nargs; i++ ) vals.push_back(arg_to_value(args[index++])); return vals; } QString app::cli::Argument::help_text_name() const { QString option_names; for ( const auto& name : names ) option_names += name + ", "; if ( !names.isEmpty() ) option_names.chop(2); if ( !arg_name.isEmpty() ) option_names += " <" + arg_name + ">"; if ( nargs > 1 ) option_names += "..."; return option_names; } bool app::cli::Argument::is_positional() const { return names.size() == 1 && !names[0].startsWith('-') && nargs > 0; } app::cli::Parser& app::cli::Parser::add_argument(Argument arg) { if ( groups.empty() ) groups.push_back({QApplication::tr("Options")}); if ( arg.is_positional() ) { groups.back().args.emplace_back(Positional, positional.size()); positional.emplace_back(std::move(arg)); } else { groups.back().args.emplace_back(Option, options.size()); options.emplace_back(std::move(arg)); } return *this; } app::cli::Parser & app::cli::Parser::add_group(const QString& name) { groups.push_back({name}); return *this; } const app::cli::Argument * app::cli::Parser::option_from_arg(const QString& arg) const { for ( const auto& option : options ) if ( option.names.contains(arg) ) return &option; return nullptr; } void app::cli::ParsedArguments::handle_error(const QString& error) { show_message(error, true); return_value = 1; } void app::cli::ParsedArguments::handle_finish(const QString& message) { show_message(message, false); return_value = 0; } app::cli::ParsedArguments app::cli::Parser::parse(const QStringList& args, int offset) const { int next_positional = 0; ParsedArguments parsed; for ( const auto& option : options ) parsed.values[option.dest] = option.default_value; for ( int index = offset; index < args.size(); ) { if ( args[index].startsWith('-') ) { if ( auto opt = option_from_arg(args[index]) ) { if ( opt->type == Argument::ShowHelp ) { parsed.handle_finish(help_text()); break; } else if ( opt->type == Argument::ShowVersion ) { parsed.handle_finish(version_text()); break; } index++; QVariant val; try { val = opt->args_to_value(args, index); } catch ( const ArgumentError& err ) { parsed.handle_error(err.message()); break; } parsed.values[opt->dest] = val; parsed.defined.insert(opt->dest); if ( opt->type == Argument::Flag && val.toBool() ) parsed.flags.insert(opt->dest); continue; } parsed.handle_error(QApplication::tr("Unknown argument %1").arg(args[index])); index++; break; } if ( next_positional >= int(positional.size()) ) { parsed.handle_error(QApplication::tr("Too many arguments")); break; } auto arg = &positional[next_positional]; parsed.defined.insert(arg->dest); try { parsed.values[arg->dest] = arg->args_to_value(args, index); } catch ( const ArgumentError& err ) { parsed.handle_error(err.message()); break; } next_positional++; } return parsed; } QString app::cli::Parser::version_text() const { return QCoreApplication::applicationName() + " " + QCoreApplication::applicationVersion() + "\n"; } void app::cli::show_message(const QString& msg, bool error) { std::fputs(qUtf8Printable(msg + '\n'), error ? stderr : stdout); } QString app::cli::Parser::help_text() const { QString usage = QCoreApplication::instance()->arguments().constFirst(); if ( !options.empty() ) usage += QApplication::tr(" [options]"); int longest_name = 0; QStringList opt_names; QStringList pos_names; for ( const auto& opt : options ) { QString name = opt.help_text_name(); if ( longest_name < name.size() ) longest_name = name.size(); opt_names.append(name); } for ( const auto& pos : positional ) { usage += " " + pos.arg_name; QString name = pos.help_text_name(); if ( longest_name < name.size() ) longest_name = name.size(); pos_names.append(name); } QString text; text += QApplication::tr("Usage: %1").arg(usage); text += '\n'; text += '\n'; text += description; text += '\n'; for ( const auto& grp : groups ) { text += '\n'; text += grp.name; text += ":\n"; for ( const auto& p : grp.args ) { text += wrap_text( (p.first == Positional ? pos_names : opt_names)[p.second], longest_name, (p.first == Positional ? positional : options)[p.second].description ); text += '\n'; } } return text; } QString app::cli::Parser::wrap_text(const QString& names, int name_max, const QString& description) const { const QLatin1String indentation(" "); // In case the list of option names is very long, wrap it as well int nameIndex = 0; auto nextNameSection = [&]() { QString section = names.mid(nameIndex, name_max); nameIndex += section.size(); return section; }; QString text; int lineStart = 0; int lastBreakable = -1; const int max = 79 - (indentation.size() + name_max + 1); int x = 0; const int len = description.length(); for (int i = 0; i < len; ++i) { ++x; const QChar c = description.at(i); if (c.isSpace()) lastBreakable = i; int breakAt = -1; int nextLineStart = -1; if (x > max && lastBreakable != -1) { // time to break and we know where breakAt = lastBreakable; nextLineStart = lastBreakable + 1; } else if ((x > max - 1 && lastBreakable == -1) || i == len - 1) { // time to break but found nowhere [-> break here], or end of last line breakAt = i + 1; nextLineStart = breakAt; } else if (c == '\n') { // forced break breakAt = i; nextLineStart = i + 1; } if (breakAt != -1) { const int numChars = breakAt - lineStart; text += indentation + nextNameSection().leftJustified(name_max) + QLatin1Char(' '); text += utils::mid_ref(description, lineStart, numChars); text += '\n'; x = 0; lastBreakable = -1; lineStart = nextLineStart; if (lineStart < len && description.at(lineStart).isSpace()) ++lineStart; // don't start a line with a space i = lineStart; } } while (nameIndex < names.size()) { text += indentation + nextNameSection() + '\n'; } return text; } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/transform.hpp000664 001750 001750 00000001572 14477652011 030126 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include "model/animation/animatable.hpp" #include "model/object.hpp" #include namespace glaxnimate::model { class Transform : public Object { GLAXNIMATE_OBJECT(Transform) GLAXNIMATE_ANIMATABLE(QPointF, anchor_point, QPointF(0, 0)) GLAXNIMATE_ANIMATABLE(QPointF, position, QPointF(0, 0)) GLAXNIMATE_ANIMATABLE(QVector2D, scale, QVector2D(1, 1)) GLAXNIMATE_ANIMATABLE(float, rotation, 0, {}) public: using Object::Object; virtual QString type_name_human() const override { return tr("Transform"); } QTransform transform_matrix(FrameTime f, bool auto_orient = false) const; void set_transform_matrix(const QTransform& t); void copy(Transform* other); }; } // namespace glaxnimate::model mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/shapes/layer.hpp000664 001750 001750 00000006121 14477652011 030505 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include "group.hpp" #include "model/property/reference_property.hpp" #include "model/animation_container.hpp" #include "model/mask_settings.hpp" namespace glaxnimate::model { class Layer : public StaticOverrides { GLAXNIMATE_OBJECT(Layer) GLAXNIMATE_SUBOBJECT(AnimationContainer, animation) GLAXNIMATE_PROPERTY_REFERENCE(Layer, parent, &Layer::valid_parents, &Layer::is_valid_parent, &Layer::docnode_on_update_group) /** * \brief Whether the layer will be rendered / exported in other formats */ GLAXNIMATE_PROPERTY(bool, render, true) GLAXNIMATE_SUBOBJECT(MaskSettings, mask) public: class ChildLayerIterator { public: using value_type = VisualNode; using reference = value_type&; using pointer = value_type*; using difference_type = int; using iterator_category = std::forward_iterator_tag; ChildLayerIterator& operator++() { ++index; find_first(); return *this; } pointer operator*() const; pointer operator->() const; bool operator==(const ChildLayerIterator& other) const { return comp == other.comp && parent == other.parent && index == other.index; } bool operator!=(const ChildLayerIterator& other) const { return !(*this == other); } private: ChildLayerIterator(const ShapeListProperty* comp, const Layer* parent, int index) : comp(comp), parent(parent), index(index) { find_first(); } void find_first(); friend Layer; friend Composition; const ShapeListProperty* comp; const Layer* parent; int index; }; using Ctor::Ctor; VisualNode* docnode_group_parent() const override; int docnode_group_child_count() const override; VisualNode* docnode_group_child(int index) const override; QIcon tree_icon() const override; static QIcon static_tree_icon(); static QString static_type_name_human() { return tr("Layer"); } void set_time(FrameTime t) override; /** * \brief Returns the (frame) time relative to this layer * * Useful for stretching / remapping etc. * Always use this to get animated property values, * even if currently it doesn't do anything */ FrameTime relative_time(FrameTime time) const { return time; } bool is_ancestor_of(const Layer* other) const; bool is_top_level() const; void paint(QPainter*, FrameTime, PaintMode, model::Modifier* modifier) const override; QPainterPath to_clip(model::FrameTime t) const override; std::unique_ptr to_path() const override; bool is_valid_parent(DocumentNode* node) const; protected: QPainterPath to_painter_path_impl(model::FrameTime t) const override; private: std::vector valid_parents() const; }; } // namespace glaxnimate::model mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/shapes/000775 001750 001750 00000000000 14477652011 026660 5ustar00ddennedyddennedy000000 000000 mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/property/option_list_property.hpp000664 001750 001750 00000004526 14477652011 034310 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include "property.hpp" #define GLAXNIMATE_PROPERTY_OPTIONS(type, name, defval, container, ...) \ public: \ OptionListProperty name{this, #name, defval, __VA_ARGS__}; \ GLAXNIMATE_PROPERTY_IMPL(type, name) \ Q_PROPERTY(QVariantList name##_options READ name##_options) \ QVariantList name##_options() const { return name.value_options(); } \ // macro end namespace glaxnimate::model { class OptionListPropertyBase : public BaseProperty { Q_GADGET public: enum OptionListFlags { NoFlags = 0, LaxValues = 1, FontCombo = 2, }; using BaseProperty::BaseProperty; virtual ~OptionListPropertyBase() = default; virtual QVariantList value_options() const = 0; int option_list_flags() const { return option_flags; } protected: int option_flags; }; template class OptionListProperty : public detail::PropertyTemplate { public: OptionListProperty( Object* obj, const QString& name, Type default_value, PropertyCallback option_list, PropertyCallback emitter = {}, PropertyCallback validator = {}, PropertyTraits::Flags flags = PropertyTraits::Visual, int option_flags = 0 ) : detail::PropertyTemplate( obj, name, std::move(default_value), std::move(emitter), std::move(validator), PropertyTraits::Flags(flags|PropertyTraits::OptionList) ), option_list(std::move(option_list)) { this->option_flags = option_flags; } Container options() const { return option_list(this->object()); } QVariantList value_options() const override { QVariantList list; for ( const auto& value : option_list(this->object()) ) list.push_back(QVariant::fromValue(value)); return list; } private: PropertyCallback option_list; }; } // namespace glaxnimate::model mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/lottie/tgs_format.hpp000664 001750 001750 00000002110 14477652011 031054 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include "lottie_format.hpp" namespace glaxnimate::io::lottie { class TgsFormat : public LottieFormat { Q_OBJECT public: QString slug() const override { return "tgs"; } QString name() const override { return tr("Telegram Animated Sticker"); } QStringList extensions() const override { return {"tgs"}; } bool can_save() const override { return true; } bool can_open() const override { return true; } std::unique_ptr save_settings(model::Composition*) const override { return {}; } void validate(model::Document* document, model::Composition* comp); private: bool on_save(QIODevice& file, const QString&, model::Composition* comp, const QVariantMap&) override; bool on_open(QIODevice& file, const QString&, model::Document* document, const QVariantMap&) override; static Autoreg autoreg; }; } // namespace glaxnimate::io::lottie mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/shapes/group.cpp000664 001750 001750 00000006372 14477652011 030530 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "group.hpp" #include #include "model/document.hpp" #include "model/shapes/styler.hpp" GLAXNIMATE_OBJECT_IMPL(glaxnimate::model::Group) glaxnimate::model::Group::Group(Document* document) : Ctor(document) { connect(transform.get(), &Object::property_changed, this, &Group::on_transform_matrix_changed); } void glaxnimate::model::Group::on_paint(QPainter* painter, glaxnimate::model::FrameTime time, glaxnimate::model::VisualNode::PaintMode, glaxnimate::model::Modifier*) const { painter->setOpacity( painter->opacity() * opacity.get_at(time) ); } void glaxnimate::model::Group::on_transform_matrix_changed() { propagate_bounding_rect_changed(); emit local_transform_matrix_changed(local_transform_matrix(time())); propagate_transform_matrix_changed(transform_matrix(time()), group_transform_matrix(time())); } void glaxnimate::model::Group::add_shapes(glaxnimate::model::FrameTime t, math::bezier::MultiBezier & bez, const QTransform& parent_transform) const { QTransform trans = transform.get()->transform_matrix(t, auto_orient.get()) * parent_transform; for ( const auto& ch : utils::Range(shapes.begin(), shapes.past_first_modifier()) ) { ch->add_shapes(t, bez, trans); } } QRectF glaxnimate::model::Group::local_bounding_rect(FrameTime t) const { if ( shapes.empty() ) return owner_composition()->rect(); return shapes.bounding_rect(t); } QTransform glaxnimate::model::Group::local_transform_matrix(glaxnimate::model::FrameTime t) const { return transform.get()->transform_matrix(t, auto_orient.get()); } QPainterPath glaxnimate::model::Group::to_painter_path_impl(glaxnimate::model::FrameTime t) const { QPainterPath path; for ( const auto& ch : utils::Range(shapes.begin(), shapes.past_first_modifier()) ) { if ( ch->is_instance() || ch->is_instance() ) path.addPath(ch->to_clip(t)); } return path; } QPainterPath glaxnimate::model::Group::to_clip(FrameTime t) const { return transform.get()->transform_matrix(t, auto_orient.get()).map(to_painter_path(t)); } std::unique_ptr glaxnimate::model::Group::to_path() const { auto clone = std::make_unique(document()); for ( BaseProperty* prop : properties() ) { if ( prop != &shapes ) clone->get_property(prop->name())->assign_from(prop); } for ( const auto& shape : shapes ) { clone->shapes.insert(shape->to_path()); if ( shape->is_instance() ) break; } return clone; } void glaxnimate::model::Group::on_graphics_changed() { ShapeElement::on_graphics_changed(); for ( const auto& shape : shapes ) { if ( shape->is_instance() ) shape->on_graphics_changed(); } } void glaxnimate::model::Group::on_composition_changed(model::Composition*, model::Composition* new_comp) { for ( const auto& shape : shapes ) { shape->refresh_owner_composition(new_comp); } } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/math/polynomial.cpp000664 001750 001750 00000004545 14477652011 030125 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "polynomial.hpp" #include "math.hpp" namespace math = glaxnimate::math; // Returns the real cube root of a value static double cuberoot(double v) { if ( v < 0 ) return -math::pow(-v, 1./3); return math::pow(v, 1./3); } std::vector glaxnimate::math::cubic_roots(double a, double b, double c, double d) { // If a is 0, it's a quadratic if ( qFuzzyIsNull(a) ) return quadratic_roots(b, c, d); // Cardano's algorithm. b /= a; c /= a; d /= a; double p = (3*c - b * b) / 3; double p3 = p / 3; double q = (2 * b*b*b - 9 * b * c + 27 * d) / 27; double q2 = q / 2; double discriminant = q2 * q2 + p3 * p3 * p3; // and some variables we're going to use later on: // 3 real roots: if ( discriminant < 0) { double mp3 = -p / 3; double r = math::sqrt(mp3*mp3*mp3); double t = -q / (2*r); double cosphi = t < -1 ? -1 : t > 1 ? 1 : t; double phi = math::acos(cosphi); double crtr = cuberoot(r); double t1 = 2 * crtr; double root1 = t1 * math::cos(phi / 3) - b / 3; double root2 = t1 * math::cos((phi + 2 * math::pi) / 3) - b / 3; double root3 = t1 * math::cos((phi + 4 * math::pi) / 3) - b / 3; return {root1, root2, root3}; } // 2 real roots if ( qFuzzyIsNull(discriminant) ) { double u1 = q2 < 0 ? cuberoot(-q2) : -cuberoot(q2); double root1 = 2*u1 - b / 3; double root2 = -u1 - b / 3; return {root1, root2}; } // 1 real root, 2 complex roots double sd = math::sqrt(discriminant); double u1 = cuberoot(sd - q2); double v1 = cuberoot(sd + q2); return {u1 - v1 - b / 3}; } std::vector glaxnimate::math::quadratic_roots(double a, double b, double c) { // linear if ( qFuzzyIsNull(a) ) { if ( qFuzzyIsNull(b) ) return {}; return {-c / b}; } double s = b * b - 4 * a * c; // Complex roots if ( s < 0 ) return {}; double single_root = -b / (2 * a); // 1 root if ( qFuzzyIsNull(s) ) return {single_root}; double delta = math::sqrt(s) / (2 * a); // 2 roots return {single_root - delta, single_root + delta}; } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/animation_container.cpp000664 001750 001750 00000003631 14477652011 032125 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "animation_container.hpp" #include "model/factory.hpp" #include "model/document.hpp" GLAXNIMATE_OBJECT_IMPL(glaxnimate::model::AnimationContainer) bool glaxnimate::model::AnimationContainer::time_visible(glaxnimate::model::FrameTime time) const { return first_frame.get() <= time && time < last_frame.get(); } bool glaxnimate::model::AnimationContainer::time_visible() const { return time_visible(time()); } void glaxnimate::model::AnimationContainer::set_time(glaxnimate::model::FrameTime t) { bool old_visible = time_visible(); Object::set_time(t); bool new_visible = time_visible(); if ( old_visible != new_visible ) { emit time_visible_changed(new_visible); emit document()->graphics_invalidated(); } } void glaxnimate::model::AnimationContainer::on_first_frame_changed(float x) { emit time_visible_changed(time_visible()); emit first_frame_changed(x); } void glaxnimate::model::AnimationContainer::on_last_frame_changed(float x) { emit time_visible_changed(time_visible()); emit last_frame_changed(x); } float glaxnimate::model::AnimationContainer::duration() const { return last_frame.get() - first_frame.get(); } QString glaxnimate::model::AnimationContainer::type_name_human() const { return tr("Animation Timing"); } void glaxnimate::model::AnimationContainer::stretch_time(qreal multiplier) { Object::stretch_time(multiplier); first_frame.set(first_frame.get() * multiplier); last_frame.set(last_frame.get() * multiplier); } bool glaxnimate::model::AnimationContainer::validate_first_frame(int f) const { return f >= 0 && (last_frame.get() == -1 || f < last_frame.get()); } bool glaxnimate::model::AnimationContainer::validate_last_frame(int f) const { return f >= 0 && f > first_frame.get(); } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/math/bezier/cubic_struts.cpp000664 001750 001750 00000006002 14477652011 031721 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "cubic_struts.hpp" #include "math/math.hpp" #include "math/geom.hpp" #include "operations.hpp" using namespace glaxnimate; // see https://pomax.github.io/bezierinfo/#abc (this returns A given B) QPointF math::bezier::get_quadratic_handle(const math::bezier::BezierSegment& segment, const QPointF& B, qreal t) { qreal t1 = (1-t); qreal t13 = t1 * t1 * t1; qreal t3 = t * t * t; qreal u = t13 / (t3 + t13); qreal ratio = math::abs((t3 + t13 - 1) / (t3 + t13)); QPointF C = math::lerp(segment[3], segment[0], u); QPointF A; if ( t == 0 ) A = segment[1]; else if ( t == 1 ) A = segment[2]; else A = B + ( B - C ) / ratio; return A; } math::bezier::BezierSegment math::bezier::cubic_segment_from_struts( const math::bezier::BezierSegment& segment, const BezierStruts& struts ) { if ( struts.t == 0 || struts.t == 1) return segment; QPointF A = get_quadratic_handle(segment, struts.B, struts.t); QPointF v1 = A + (struts.e1 - A) / (1-struts.t); QPointF v2 = A + (struts.e2 - A) / struts.t; return { segment[0], segment[0] + (v1 - segment[0]) / struts.t, segment[3] + (v2 - segment[3]) / (1-struts.t), segment[3] }; } // see https://pomax.github.io/bezierinfo/#pointcurves math::bezier::BezierStruts math::bezier::cubic_struts_idealized(const math::bezier::BezierSegment& segment, const QPointF& B) { BezierStruts struts; struts.B = B; qreal d1 = math::length(segment[0] - B); qreal d2 = math::length(segment[3] - B); struts.t = d1 / (d1+d2); QPointF center = circle_center(segment[0], B, segment[3]); qreal tanlen = math::length(segment[3] - segment[0]) / 3; qreal phi = math::fmod( math::atan2(segment[3].y() - segment[0].y(), segment[3].x() - segment[0].x()) - math::atan2(B.y() - segment[0].y(), B.x() - segment[0].x()) + math::tau, math::tau ); if ( phi < math::pi ) tanlen = -tanlen; qreal de1 = struts.t * tanlen; qreal de2 = (1-struts.t) * tanlen; QPointF tangent = struts.B - center; tangent /= math::length(tangent); tangent = { -tangent.y(), tangent.x() }; struts.e1 = struts.B + de1 * tangent; struts.e2 = struts.B - de2 * tangent; return struts; } math::bezier::BezierStruts math::bezier::cubic_struts_projection( const math::bezier::BezierSegment& segment, const QPointF& B, const math::bezier::ProjectResult& projection ) { BezierStruts struts; struts.B = B; struts.t = projection.factor; auto v1 = math::lerp(segment[0], segment[1], struts.t); auto v2 = math::lerp(segment[2], segment[3], struts.t); auto A = get_quadratic_handle(segment, projection.point, struts.t); struts.e1 = math::lerp(v1, A, struts.t) - projection.point + B; struts.e2 = math::lerp(A, v2, struts.t) - projection.point + B; return struts; } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/animation/animatable_path.hpp000664 001750 001750 00000002704 14477652011 033201 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include "animatable.hpp" #include "math/bezier/bezier.hpp" namespace glaxnimate::model { namespace detail { // Intermediare non-templated class so Q_OBJECT works class AnimatedPropertyBezier : public detail::AnimatedProperty { Q_OBJECT public: AnimatedPropertyBezier(Object* object, const QString& name, PropertyCallback emitter = {}) : detail::AnimatedProperty(object, name, {}, std::move(emitter)) {} int size() const { return value_.size(); } bool closed() const { return value_.closed(); } void set_closed(bool closed); Q_INVOKABLE void split_segment(int index, qreal factor); Q_INVOKABLE void remove_point(int index); void remove_points(const std::set& indices); /** * \brief Extends all keyframe values to match \p target * * Each keyframe adds nodes from \p target to have at least \p target.size() nodes */ void extend(const math::bezier::Bezier& target, bool at_end); }; } // namespace detail template<> class AnimatedProperty : public detail::AnimatedPropertyBezier { public: using detail::AnimatedPropertyBezier::AnimatedPropertyBezier; }; } // namespace glaxnimate::model mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/assets/embedded_font.hpp000664 001750 001750 00000002435 14477652011 032173 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include "asset.hpp" #include "model/custom_font.hpp" namespace glaxnimate::model { class EmbeddedFont : public Asset { GLAXNIMATE_OBJECT(EmbeddedFont) GLAXNIMATE_PROPERTY(QByteArray, data, {}, &EmbeddedFont::on_data_changed) GLAXNIMATE_PROPERTY(QString, source_url, {}) GLAXNIMATE_PROPERTY(QString, css_url, {}) Q_PROPERTY(QString family READ family) Q_PROPERTY(QString style_name READ style_name) Q_PROPERTY(int database_index READ database_index) public: EmbeddedFont(model::Document* document); EmbeddedFont(model::Document* document, CustomFont custom_font); QIcon instance_icon() const override; QString type_name_human() const override; QString object_name() const override; bool remove_if_unused(bool clean_lists) override; QString family() const { return custom_font_.family(); } QString style_name() const { return custom_font_.style_name(); } int database_index() const { return custom_font_.database_index(); } const CustomFont& custom_font() const { return custom_font_; } private: void on_data_changed(); CustomFont custom_font_; }; } // namespace glaxnimate::model mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/plugin/plugin.cpp000664 001750 001750 00000022475 14477652011 027607 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "plugin.hpp" #include #include #include #include "app/scripting/script_engine.hpp" #include "app/application.hpp" #include "plugin/action.hpp" #include "plugin/io.hpp" #include "plugin/executor.hpp" using namespace glaxnimate; bool plugin::Plugin::run_script ( const plugin::PluginScript& script, const QVariantList& args ) const { if ( !data_.engine ) { logger().log("Can't run script from a plugin with no engine", app::log::Error); return false; } if ( !PluginRegistry::instance().executor() ) { logger().log("No script executor", app::log::Error); return false; } return PluginRegistry::instance().executor()->execute(*this, script, args); } void plugin::PluginRegistry::load() { QString writable_path = app::Application::instance()->writable_data_path("plugins"); for ( const QString& path : app::Application::instance()->data_paths("plugins") ) { bool writable = path == writable_path; QDir pathdir(path); for ( const auto& entry : pathdir.entryList(QDir::Dirs|QDir::NoDotAndDotDot|QDir::Readable) ) { QDir entrydir(pathdir.absoluteFilePath(entry)); if ( entrydir.exists("plugin.json") ) { load_plugin(entrydir.absoluteFilePath("plugin.json"), writable); } } } emit loaded(); } bool plugin::PluginRegistry::load_plugin ( const QString& path, bool user_installed ) { logger.set_detail(path); QFileInfo file_info(path); if ( !file_info.exists() || !file_info.isFile() || !file_info.isReadable() ) { logger.stream() << "Cannot read plugin file"; return false; } QFile file(file_info.absoluteFilePath()); if ( !file.open(QFile::ReadOnly) ) { logger.stream() << "Cannot read plugin file"; return false; } QJsonDocument jdoc; try { jdoc = QJsonDocument::fromJson(file.readAll()); } catch ( const QJsonParseError& err ) { logger.stream() << "Invalid plugin file:" << err.errorString(); return false; } if ( !jdoc.isObject() ) { logger.stream() << "Invalid plugin file: not an object"; return false; } const QJsonObject jobj = jdoc.object(); PluginData data; data.dir = file_info.dir(); data.id = QFileInfo(data.dir.path()).fileName(); data.version = jobj["version"].toInt(0); auto it = names.find(data.id); int overwrite = -1; if ( it != names.end() ) { Plugin* plug = plugins_[*it].get(); if ( plug->data().version >= data.version ) { logger.stream(app::log::Info) << "Skipping Plugin (newer version exists)"; return false; } if ( plug->enabled() ) { logger.stream(app::log::Info) << "Skipping Plugin (older version is currently enabled)"; return false; } overwrite = *it; } data.engine_name = jobj["engine"].toString(); data.engine = app::scripting::ScriptEngineFactory::instance().engine(data.engine_name); if ( !data.engine) { logger.stream() << "Plugin refers to an unknown engine" << data.engine_name; } data.name = jobj["name"].toString(data.id); data.author = jobj["author"].toString(); data.icon = jobj["icon"].toString(); data.description = jobj["description"].toString(); QJsonArray arr = jobj["services"].toArray(); if ( arr.empty() ) { logger.stream() << "Plugin does not provide any services"; return false; } for ( QJsonValue val : arr ) { if ( !val.isObject() ) logger.stream() << "Skipping invalid service"; else load_service(val.toObject(), data); } if ( data.services.empty() ) { logger.stream() << "Plugin does not provide any valid services"; return false; } std::unique_ptr plugin = std::make_unique(std::move(data), user_installed); if ( overwrite != -1 ) { plugins_[overwrite] = std::move(plugin); } else { names[plugin->data().id] = plugins_.size(); plugins_.push_back(std::move(plugin)); } return true; } void plugin::PluginRegistry::load_service ( const QJsonObject& jobj, plugin::PluginData& data ) const { QString type = jobj["type"].toString(); if ( type == "action" ) { std::unique_ptr act = std::make_unique(); act->script = load_script(jobj["script"].toObject()); if ( !act->script.valid() ) { logger.stream() << "Skipping action with invalid script"; return; } act->label = jobj["label"].toString(); act->tooltip = jobj["tooltip"].toString(); act->icon = jobj["icon"].toString(); if ( act->icon.isEmpty() ) act->icon = data.icon; data.services.emplace_back(std::move(act)); } else if ( type == "format" ) { auto svc = std::make_unique(); svc->save = load_script(jobj["save"].toObject()); svc->open = load_script(jobj["open"].toObject()); if ( !svc->save.valid() && !svc->open.valid() ) { logger.stream() << "Skipping format service with no open nor save"; return; } svc->label = jobj["name"].toString(); for ( auto extv : jobj["extensions"].toArray() ) { QString ext = extv.toString(); if ( ext.startsWith(".") ) { logger.stream() << "Format extensions should not have the leading dot"; ext = ext.mid(1); } if ( ext.isEmpty() ) { logger.stream() << "Empty extension"; continue; } svc->extensions.push_back(ext); } if ( svc->extensions.isEmpty() ) { logger.stream() << "Skipping format service with no extensions"; return; } svc->auto_open = jobj["auto_open"].toBool(true); svc->slug = jobj["slug"].toString(); if ( svc->slug.isEmpty() ) svc->slug = svc->extensions[0]; data.services.emplace_back(std::move(svc)); } else { logger.stream() << "Skipping invalid service type" << type; } } plugin::PluginScript plugin::PluginRegistry::load_script ( const QJsonObject& jobj ) const { PluginScript s; s.module = jobj["module"].toString(); s.function = jobj["function"].toString(); QJsonArray settings = jobj["settings"].toArray(); for ( auto setting : settings ) { load_setting(setting.toObject(), s); } return s; } void plugin::PluginRegistry::load_setting (const QJsonObject& jobj, plugin::PluginScript& script ) const { QString type = jobj["type"].toString(); QString slug = jobj["name"].toString(); if ( slug.isEmpty() ) { logger.stream() << "Skipping setting with no name"; return; } QString label = jobj["label"].toString(slug); QString description = jobj["description"].toString(); QVariant default_value = jobj["default"].toVariant(); if ( type == "info" ) script.settings.emplace_back(slug, label, description); else if ( type == "bool" ) script.settings.emplace_back(slug, label, description, default_value.toBool()); else if ( type == "int" ) script.settings.emplace_back(slug, label, description, default_value.toInt(), jobj["min"].toInt(), jobj["max"].toInt()); else if ( type == "float" ) script.settings.emplace_back(slug, label, description, default_value.toFloat(), jobj["min"].toDouble(), jobj["max"].toDouble()); else if ( type == "string" ) script.settings.emplace_back(slug, label, description, default_value.toString()); else if ( type == "choice" ) script.settings.emplace_back(slug, label, description, app::settings::Setting::String, default_value, load_choices(jobj["choices"])); else if ( type == "color" ) script.settings.emplace_back(slug, label, description, app::settings::Setting::Color, default_value); else logger.stream() << "Unknown type" << type << "for plugin setting" << slug; } QVariantMap plugin::PluginRegistry::load_choices ( const QJsonValue& val ) const { QVariantMap ret; if ( val.isObject() ) { QJsonObject obj = val.toObject(); for ( auto it = obj.begin(); it != obj.end(); ++it ) ret[it.key()] = it->toVariant(); } else if ( val.isArray() ) { for ( auto i : val.toArray() ) { QVariant v = i.toVariant(); ret[v.toString()] = v; } } return ret; } plugin::Plugin * plugin::PluginRegistry::plugin ( const QString& id ) const { auto it = names.find(id); if ( it == names.end() ) return {}; return plugins_[*it].get(); } void plugin::PluginRegistry::set_executor(plugin::Executor* exec) { executor_ = exec; } plugin::Executor * plugin::PluginRegistry::executor() const { return executor_; } QVariant plugin::PluginRegistry::global_parameter(const QString& name) const { if ( !executor_ ) return {}; return executor_->get_global(name); } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/svg/path_parser.hpp000664 001750 001750 00000030300 14477652011 030520 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include #include #include #include "utils/regexp.hpp" #include "math/bezier/bezier.hpp" #include "math/ellipse_solver.hpp" namespace glaxnimate::io::svg::detail { class PathDParser { public: enum TokenType { Command, Parameter, }; using Token = std::variant; private: struct Lexer { Lexer(const QString& d, std::vector& tokens) : d(d), tokens(tokens) {} QString d; int off = 0; std::vector& tokens; QString lexed; QChar ch; bool eof() const { return off >= d.size(); } bool next() { ++off; if ( !eof() ) { ch = d[off]; return true; } ch = {}; return false; } void lex() { static QString cmds = "MLHVCSQTAZ"; ch = d[off]; while ( off < d.size() ) { if ( cmds.contains(ch.toUpper()) ) { tokens.emplace_back(ch.unicode()); next(); } else if ( ch.isSpace() || ch == ',') { next(); } else { lex_value(); } } } void lex_value() { lexed.clear(); if ( ch == '+' || ch == '-' ) { lexed += ch; if ( !next() ) return; } if ( ch.isDigit() ) lex_value_int(); if ( ch == '.' ) { lexed += ch; if ( !next() ) return; lex_value_decimal(); } else if ( ch.toUpper() == 'E' ) { lexed += ch; if ( !next() ) return; lex_value_exponent(); } if ( lexed.isEmpty() ) { next(); return; } tokens.emplace_back(lexed.toDouble()); lexed.clear(); } void lex_value_int() { while ( off < d.size() && ch.isDigit() ) { lexed += ch; next(); } } void lex_value_decimal() { lex_value_int(); if ( ch.toUpper() == 'E' ) { lexed += ch; if ( !next() ) return; lex_value_exponent(); } } void lex_value_exponent() { if ( ch == '+' || ch == '-' ) { lexed += ch; if ( !next() ) return; } lex_value_int(); } }; public: PathDParser(const QString& d) { tokenize(d); } const math::bezier::MultiBezier& parse() { while ( !eof() ) { if ( la_type() == Command ) { ushort cmd = std::get(la()); next_token(); parse_command(cmd); } else { parse_command(implicit); } } return bez; } private: const Token& la() const { return tokens[index]; } void next_token() { ++index; } bool eof() const { return index >= int(tokens.size()); } TokenType la_type() const { return TokenType(la().index()); } void tokenize(const QString& d) { if ( d.isEmpty() ) return; Lexer(d, tokens).lex(); } qreal read_param() { if ( la_type() == Parameter ) { qreal v = std::get(la()); next_token(); return v; } return 0; } QPointF read_vector() { return {read_param(), read_param()}; } void parse_M() { if ( la_type() != Parameter ) { next_token(); return; } p = read_vector(); bez.move_to(p); implicit = 'L'; } void parse_m() { if ( la_type() != Parameter ) { next_token(); return; } p += read_vector(); bez.move_to(p); implicit = 'l'; } void parse_L() { if ( la_type() != Parameter ) { next_token(); return; } p = read_vector(); bez.line_to(p); implicit = 'L'; } void parse_l() { if ( la_type() != Parameter ) { next_token(); return; } p += read_vector(); bez.line_to(p); implicit = 'l'; } void parse_H() { if ( la_type() != Parameter ) { next_token(); return; } p.setX(read_param()); bez.line_to(p); implicit = 'H'; } void parse_h() { if ( la_type() != Parameter ) { next_token(); return; } p.setX(p.x() + read_param()); bez.line_to(p); implicit = 'h'; } void parse_V() { if ( la_type() != Parameter ) { next_token(); return; } p.setY(read_param()); bez.line_to(p); implicit = 'V'; } void parse_v() { if ( la_type() != Parameter ) { next_token(); return; } p.setY(p.y() + read_param()); bez.line_to(p); implicit = 'v'; } void parse_C() { if ( la_type() != Parameter ) { next_token(); return; } QPointF tan_out = read_vector(); QPointF tan_in = read_vector(); p = read_vector(); bez.cubic_to(tan_out, tan_in, p); implicit = 'C'; } void parse_c() { if ( la_type() != Parameter ) { next_token(); return; } QPointF tan_out = p + read_vector(); QPointF tan_in = p + read_vector(); p += read_vector(); bez.cubic_to(tan_out, tan_in, p); implicit = 'c'; } void parse_S() { if ( la_type() != Parameter ) { next_token(); return; } QPointF old_p = p; QPointF tan_in = read_vector(); p = read_vector(); if ( bez.beziers().empty() || bez.beziers().back().empty() ) { bez.cubic_to(old_p, tan_in, p); } else { auto& prev = bez.beziers().back().points().back(); QPointF tan_out = prev.pos - prev.relative_tan_in(); prev.type = math::bezier::Symmetrical; bez.cubic_to(tan_out, tan_in, p); } implicit = 'S'; } void parse_s() { if ( la_type() != Parameter ) { next_token(); return; } QPointF old_p = p; QPointF tan_in = p+read_vector(); p += read_vector(); if ( bez.beziers().empty() || bez.beziers().back().empty() ) { bez.cubic_to(old_p, tan_in, p); } else { auto& prev = bez.beziers().back().points().back(); QPointF tan_out = prev.pos - prev.relative_tan_in(); prev.type = math::bezier::Symmetrical; bez.cubic_to(tan_out, tan_in, p); } implicit = 's'; } void parse_Q() { if ( la_type() != Parameter ) { next_token(); return; } QPointF tan = read_vector(); p = read_vector(); bez.quadratic_to(tan, p); implicit = 'Q'; } void parse_q() { if ( la_type() != Parameter ) { next_token(); return; } QPointF tan = p+read_vector(); p += read_vector(); bez.quadratic_to(tan, p); implicit = 'q'; } void parse_T() { if ( la_type() != Parameter ) { next_token(); return; } QPointF old_p = p; p = read_vector(); if ( bez.beziers().empty() || bez.beziers().back().empty() ) { bez.quadratic_to(old_p, p); } else { auto& prev = bez.beziers().back().points().back(); QPointF tan_out = prev.pos - prev.relative_tan_in(); prev.type = math::bezier::Symmetrical; bez.quadratic_to(tan_out, p); } implicit = 'T'; } void parse_t() { if ( la_type() != Parameter ) { next_token(); return; } QPointF old_p = p; p += read_vector(); if ( bez.beziers().empty() || bez.beziers().back().empty() ) { bez.quadratic_to(old_p, p); } else { auto& prev = bez.beziers().back().points().back(); QPointF tan_out = prev.pos - prev.relative_tan_in(); prev.type = math::bezier::Symmetrical; bez.quadratic_to(tan_out, p); } implicit = 't'; } void do_arc(qreal rx, qreal ry, qreal xrot, bool large, bool sweep, const QPointF& dest) { if ( p == dest ) return; // straight line if ( rx == 0 || ry == 0 ) { p = dest; bez.line_to(p); return; } if ( bez.beziers().empty() || bez.beziers().back().empty() ) return; math::bezier::Bezier points = math::EllipseSolver::from_svg_arc( p, rx, ry, xrot, large, sweep, dest ); auto& target_points = bez.beziers().back().points(); target_points.back().tan_out = points[0].tan_out; target_points.insert(target_points.end(), points.begin()+1, points.end()); p = dest; } void parse_A() { if ( la_type() != Parameter ) { next_token(); return; } QPointF r = read_vector(); qreal xrot = read_param(); qreal large = read_param(); qreal sweep = read_param(); QPointF dest = read_vector(); do_arc(r.x(), r.y(), xrot, large, sweep, dest); implicit = 'A'; } void parse_a() { if ( la_type() != Parameter ) { next_token(); return; } QPointF r = read_vector(); qreal xrot = read_param(); qreal large = read_param(); qreal sweep = read_param(); QPointF dest = p + read_vector(); do_arc(r.x(), r.y(), xrot, large, sweep, dest); implicit = 'a'; } void parse_command(ushort c) { switch ( c ) { case 'M': return parse_M(); case 'm': return parse_m(); case 'L': return parse_L(); case 'l': return parse_l(); case 'H': return parse_H(); case 'h': return parse_h(); case 'V': return parse_V(); case 'v': return parse_v(); case 'C': return parse_C(); case 'c': return parse_c(); case 'S': return parse_S(); case 's': return parse_s(); case 'Q': return parse_Q(); case 'q': return parse_q(); case 'T': return parse_T(); case 't': return parse_t(); case 'A': return parse_A(); case 'a': return parse_a(); case 'Z': case 'z': bez.close(); if ( !bez.empty() && !bez.back().empty() ) p = bez.back()[0].pos; break; default: next_token(); } } std::vector tokens; int index = 0; ushort implicit = 'M'; QPointF p{0, 0}; math::bezier::MultiBezier bez; }; } // namespace glaxnimate::io::svg::detail mlt-7.22.0/src/modules/glaxnimate/glaxnimate/external/QtAppSetup/src/app/utils/qbytearray_hash.hpp000664 001750 001750 00000000645 14477652011 035061 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include #include #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) namespace std { template<> struct hash { std::size_t operator()(const QByteArray& s) const noexcept { return (size_t) qHash(s); } }; } #endif mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/shapes/text.hpp000664 001750 001750 00000010016 14477652011 030353 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include #include "model/property/sub_object_property.hpp" #include "model/property/option_list_property.hpp" #include "model/property/reference_property.hpp" #include "shape.hpp" namespace glaxnimate::model { class Font : public Object { GLAXNIMATE_OBJECT(Font) GLAXNIMATE_PROPERTY_OPTIONS(QString, family, "", QStringList, &Font::families, &Font::on_family_changed, {}, PropertyTraits::Visual, OptionListPropertyBase::FontCombo) GLAXNIMATE_PROPERTY_OPTIONS(float, size, 32, QList, &Font::standard_sizes, &Font::on_font_changed, {}, PropertyTraits::Visual, OptionListPropertyBase::LaxValues) GLAXNIMATE_PROPERTY_OPTIONS(QString, style, "", QStringList, &Font::styles, &Font::on_font_changed, &Font::valid_style, PropertyTraits::Visual) GLAXNIMATE_PROPERTY(float, line_height, 1, &Font::on_font_changed, {}, PropertyTraits::Visual|PropertyTraits::Percent) public: struct CharData { quint32 glyph; QPointF position; }; struct LineData { std::vector glyphs; QRectF bounds; QPointF baseline; QPointF advance; QString text; }; using ParagraphData = std::vector; using CharDataCache = std::unordered_map; explicit Font(Document* doc); ~Font(); const QRawFont& raw_font() const; const QFont& query() const; const QFontMetricsF& metrics() const; void from_qfont(const QFont& f); QStringList styles() const; QStringList families() const; QList standard_sizes() const; QString type_name_human() const override; ParagraphData layout(const QString& string) const; /** * \brief Distance between two baselines */ qreal line_spacing() const; /** * \brief Distance between two baselines for a line_height of 1 */ qreal line_spacing_unscaled() const; QPainterPath path_for_glyph(quint32 glyph, CharDataCache& cache, bool fix_paint) const; signals: void font_changed(); protected: void on_transfer(model::Document* doc) override; private: void on_family_changed(); void on_font_changed(); void refresh_data(bool update_styles); bool valid_style(const QString& style); class Private; std::unique_ptr d; }; class TextShape : public ShapeElement { GLAXNIMATE_OBJECT(TextShape) GLAXNIMATE_PROPERTY(QString, text, {}, &TextShape::on_text_changed, {}, PropertyTraits::Visual) GLAXNIMATE_ANIMATABLE(QPointF, position, QPointF()) GLAXNIMATE_SUBOBJECT(Font, font) GLAXNIMATE_PROPERTY_REFERENCE(model::ShapeElement, path, &TextShape::valid_paths, &TextShape::is_valid_path, &TextShape::path_changed) GLAXNIMATE_ANIMATABLE(float, path_offset, 0, &TextShape::on_text_changed) public: explicit TextShape(model::Document* document); void add_shapes(FrameTime t, math::bezier::MultiBezier& bez, const QTransform& transform) const override; QPainterPath shape_data(FrameTime t) const; QIcon tree_icon() const override; QRectF local_bounding_rect(FrameTime t) const override; QString type_name_human() const override; std::unique_ptr to_path() const override; /** * \brief Position where the next character would be * * ie: where to add another TextShape to make them flow together */ QPointF offset_to_next_character() const; protected: QPainterPath to_painter_path_impl(FrameTime t) const override; private: void on_font_changed(); void on_text_changed(); const QPainterPath& untranslated_path(FrameTime t) const; std::vector valid_paths() const; bool is_valid_path(DocumentNode* node) const; void path_changed(model::ShapeElement* new_path, model::ShapeElement* old_path); mutable Font::CharDataCache cache; mutable QPainterPath shape_cache; }; } // namespace glaxnimate::model mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/comp_graph.hpp000664 001750 001750 00000004006 14477652011 030225 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include namespace glaxnimate::model { class Composition; class PreCompLayer; class Document; /** * \brief Dependency graph between compositions. * * The graph is a directed acyclic graph, rooted in the main composition of a document. * I might not be connected as you could have comps not used anywhere. * * This graph is used to avoid cyclical dependencies. */ class CompGraph { public: /** * \brief Adds a composition and scans it for existing layers */ void add_composition(model::Composition* comp); /** * \brief Remove a composition from the graph */ void remove_composition(model::Composition* comp); /** * \brief Registers \p layer to be a layer in \p comp. */ void add_connection(model::Composition* comp, model::PreCompLayer* layer); /** * \brief Registers \p layer to no longer be a layer in \p comp. */ void remove_connection(model::Composition* comp, model::PreCompLayer* layer); /** * \brief Returns a list of composition used by \p comp, */ std::vector children(model::Composition* comp) const; /** * \brief Returns whether starting from \p ancestor you can find a path to \p descendant using precomp layers. * * A comp is considered being ancestor of itself. */ bool is_ancestor_of(model::Composition* ancestor, model::Composition* descendant) const; /** * \brief Returns a list of compositions that can be added as a child of \p ancestor. * * Basically all the precomps in \p document that are not ancestors of \p ancestor. */ std::vector possible_descendants(model::Composition* ancestor, model::Document* document) const; private: std::unordered_map> layers; }; } // namespace glaxnimate::model mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/utils/range.hpp000664 001750 001750 00000001270 14477652011 027242 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include namespace glaxnimate::utils { template class Range { public: using iterator = Iterator; using value_type = typename Traits::value_type; Range(iterator begin, iterator end) : begin_(begin), end_(end) {} iterator begin() const { return begin_; } iterator end() const { return end_; } iterator cbegin() const { return begin_; } iterator cend() const { return end_; } private: iterator begin_; iterator end_; }; } // namespace glaxnimate::utils mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/command/undo_macro_guard.hpp000664 001750 001750 00000003116 14477652011 031735 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include "model/document.hpp" namespace glaxnimate::command { class UndoMacroGuard { public: UndoMacroGuard() noexcept : document(nullptr) {}; UndoMacroGuard(const QString& name, model::Document* document, bool start_macro = true) : name(name), document(document) { if ( start_macro ) start(); } UndoMacroGuard(const UndoMacroGuard&) = delete; UndoMacroGuard& operator=(const UndoMacroGuard&) = delete; UndoMacroGuard(UndoMacroGuard&& other) noexcept : name(std::move(other.name)), document(other.document), end_macro(other.end_macro) { other.document = nullptr; other.end_macro = false; } UndoMacroGuard& operator=(UndoMacroGuard&& other) noexcept { std::swap(name, other.name); std::swap(document, other.document); std::swap(end_macro, other.end_macro); return *this; } ~UndoMacroGuard() { finish(); } void start() { if ( !end_macro ) { end_macro = true; document->undo_stack().beginMacro(name); } } void finish() { if ( end_macro ) { end_macro = false; document->undo_stack().endMacro(); } } bool started() const noexcept { return end_macro; } private: QString name; model::Document* document; bool end_macro = false; }; } // namespace glaxnimate::command mlt-7.22.0/src/modules/glaxnimate/glaxnimate/external/QtAppSetup/src/app/scripting/000775 001750 001750 00000000000 14477652011 032017 5ustar00ddennedyddennedy000000 000000 mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/000775 001750 001750 00000000000 14506450034 023337 5ustar00ddennedyddennedy000000 000000 mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/avd/avd_parser.cpp000664 001750 001750 00000064743 14477652011 030326 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "avd_parser.hpp" #include #include "io/svg/svg_parser_private.hpp" #include "model/shapes/trim.hpp" using namespace glaxnimate::io::svg; using namespace glaxnimate::io::svg::detail; class glaxnimate::io::avd::AvdParser::Private : public svg::detail::SvgParserPrivate { public: Private( const QDir& resource_path, model::Document* document, const std::function& on_warning, ImportExport* io, QSize forced_size, model::FrameTime default_time ) : SvgParserPrivate(document, on_warning, io, forced_size, default_time), resource_path(resource_path) {} protected: void on_parse_prepare(const QDomElement&) override { for ( const auto& p : shape_parsers ) to_process += dom.elementsByTagName(p.first).count(); for ( const auto& target : ElementRange(dom.elementsByTagName("target")) ) { QString name = target.attribute("name"); if ( name.isEmpty() ) continue; for ( const auto& attr : ElementRange(target) ) { if ( attr.tagName() != "attr" || !attr.attribute("name").endsWith("animation") ) continue; auto iter = animations.find(name); if ( iter == animations.end() ) iter = animations.insert({name, {}}).first; auto& props = iter->second; for ( const auto& anim : ElementRange(attr.elementsByTagName("objectAnimator")) ) { parse_animator(props, anim); } } } } QSizeF get_size(const QDomElement& svg) override { return { len_attr(svg, "width", size.width()), len_attr(svg, "height", size.height()) }; } void on_parse(const QDomElement& root) override { static const Style default_style(Style::Map{ {"fillColor", "black"}, }); if ( root.tagName() == "vector" ) { parse_vector({root, &main->shapes, default_style, false}); } else { if ( root.hasAttribute("drawable") ) { if ( auto res = get_resource(root.attribute("drawable")) ) { if ( res->element.tagName() == "vector" ) parse_vector({res->element, &main->shapes, default_style, false}); } } for ( const auto& ch : ElementRange(root) ) { if ( ch.tagName() == "attr" && ch.attribute("name").endsWith("drawable") ) { for ( const auto& e : ElementRange(ch) ) if ( e.tagName() == "vector" ) parse_vector({e, &main->shapes, default_style, false}); } } } main->name.set( attr(root, "android", "name", "") ); } void parse_shape(const ParseFuncArgs& args) override { auto it = shape_parsers.find(args.element.tagName()); if ( it != shape_parsers.end() ) { mark_progress(); (this->*it->second)(args); } } private: struct Resource { QString name; QDomElement element; model::Asset* asset = nullptr; }; void parse_vector(const ParseFuncArgs& args) { QPointF pos; QVector2D scale{1, 1}; model::Layer* layer = add_layer(args.shape_parent); set_name(layer, args.element); if ( args.element.hasAttribute("viewportWidth") && args.element.hasAttribute("viewportHeight") ) { qreal vbw = len_attr(args.element, "viewportWidth"); qreal vbh = len_attr(args.element, "viewportHeight"); if ( !forced_size.isValid() ) { if ( !args.element.hasAttribute("width") ) size.setWidth(vbw); if ( !args.element.hasAttribute("height") ) size.setHeight(vbh); } if ( vbw != 0 && vbh != 0 ) { scale = QVector2D(size.width() / vbw, size.height() / vbh); if ( forced_size.isValid() ) { auto single = qMin(scale.x(), scale.y()); scale = QVector2D(single, single); } } } layer->transform.get()->position.set(-pos); layer->transform.get()->scale.set(scale); parse_children({args.element, &layer->shapes, args.parent_style, false}); } void parse_animator(AnimateParser::AnimatedProperties& props, const QDomElement& anim) { model::FrameTime start_time = qRound(anim.attribute("startOffset", "0").toDouble() / 1000 * animate_parser.fps); model::FrameTime end_time = qRound(start_time + anim.attribute("duration", "0").toDouble() / 1000 * animate_parser.fps); animate_parser.register_time_range(start_time, end_time); std::vector updated_props; QString name = anim.attribute("propertyName"); if ( !name.isEmpty() ) { auto& prop = props.properties[name]; updated_props.push_back(&prop); parse_animated_prop(prop, name, anim, start_time, end_time); } for ( const auto& value_holder : ElementRange(anim) ) { if ( value_holder.tagName() != "propertyValuesHolder" ) continue; name = value_holder.attribute("propertyName"); if ( !name.isEmpty() ) { auto& prop = props.properties[name]; updated_props.push_back(&prop); parse_animated_prop(prop, name, value_holder, start_time, end_time); } } for ( auto prop : updated_props ) prop->sort(); } model::KeyframeTransition interpolator(const QString& interpolator) { using Type = model::KeyframeTransition::Descriptive; if ( interpolator == "@android:interpolator/fast_out_slow_in" ) return model::KeyframeTransition(Type::Fast, Type::Ease); if ( interpolator == "@android:interpolator/fast_out_linear_in" ) return model::KeyframeTransition(Type::Fast, Type::Linear); if ( interpolator == "@android:interpolator/linear_out_slow_in" ) return model::KeyframeTransition(Type::Linear, Type::Ease); if ( interpolator == "@android:anim/accelerate_decelerate_interpolator" ) return model::KeyframeTransition(Type::Ease, Type::Ease); if ( interpolator == "@android:anim/accelerate_interpolator" ) return model::KeyframeTransition(Type::Ease, Type::Fast); if ( interpolator == "@android:anim/decelerate_interpolator" ) return model::KeyframeTransition(Type::Fast, Type::Ease); if ( interpolator == "@android:anim/linear_interpolator" ) return model::KeyframeTransition(Type::Linear, Type::Linear); // TODO? // @android:anim/anticipate_interpolator // @android:anim/overshoot_interpolator // @android:anim/bounce_interpolator // @android:anim/anticipate_overshoot_interpolator if ( interpolator != "" ) warning(QObject::tr("Unknown interpolator %s").arg(interpolator)); return model::KeyframeTransition(Type::Ease, Type::Ease); } ValueVariant parse_animated_value(const QString& value, ValueVariant::Type type) { switch ( type ) { case ValueVariant::Vector: return value.toDouble(); case ValueVariant::Bezier: return PathDParser(value).parse(); case ValueVariant::String: return value; case ValueVariant::Color: return parse_color(value); } return {}; } void parse_animated_prop( AnimatedProperty& prop, const QString& name, const QDomElement& value_holder, model::FrameTime start_time, model::FrameTime end_time ) { static model::KeyframeTransition transition; ValueVariant::Type type = ValueVariant::Vector; if ( name == "pathData" ) type = ValueVariant::Bezier; else if ( name.endsWith("Color") ) type = ValueVariant::Color; if ( value_holder.hasAttribute("valueFrom") ) { prop.keyframes.push_back({ start_time, parse_animated_value(value_holder.attribute("valueFrom"), type), interpolator(value_holder.attribute("interpolator")) }); } if ( value_holder.hasAttribute("valueTo") ) { prop.keyframes.push_back({ end_time, parse_animated_value(value_holder.attribute("valueTo"), type), model::KeyframeTransition(model::KeyframeTransition::Ease) }); } for ( const auto& kf : ElementRange(value_holder) ) { if ( kf.tagName() != "keyframe" ) continue; auto fraction = kf.attribute("fraction").toDouble(); prop.keyframes.push_back({ math::lerp(start_time, end_time, fraction), parse_animated_value(kf.attribute("value"), type), interpolator(kf.attribute("interpolator")) }); } } void add_shapes(const ParseFuncArgs& args, ShapeCollection&& shapes) { Style style = parse_style(args.element, args.parent_style); auto group = std::make_unique(document); // apply_common_style(group.get(), args.element, style); set_name(group.get(), args.element); add_style_shapes(args, &group->shapes, style); for ( auto& shape : shapes ) group->shapes.insert(std::move(shape)); args.shape_parent->insert(std::move(group)); } Style parse_style(const QDomElement& element, const Style& parent_style) { Style style = parent_style; for ( const auto& domnode : ItemCountRange(element.attributes()) ) { auto attr = domnode.toAttr(); if ( style_atrrs.count(attr.name()) ) style[attr.name()] = attr.value(); } for ( const auto& child : ItemCountRange(element.childNodes()) ) { if ( child.isElement() ) { auto attr = child.toElement(); if ( attr.tagName() == "attr" ) { auto attr_name = attr.attribute("name").split(":").back(); for ( const auto& grandchild : ItemCountRange(child.childNodes()) ) { if ( grandchild.isElement() ) { style[attr_name] = add_as_resource(grandchild.toElement()); break; } } } } } return style; } void set_name(model::DocumentNode* node, const QDomElement& element) { QString name = attr(element, "", "name", node->type_name_human()); node->name.set(name); } void add_style_shapes(const ParseFuncArgs& args, model::ShapeListProperty* shapes, const Style& style) { add_fill(args, shapes, style); add_stroke(args, shapes, style); if ( style.contains("trimPathEnd") || style.contains("trimPathStart") ) add_trim(args, shapes, style); } QColor parse_color(const QString& color) { if ( !color.isEmpty() && color[0] == '#' ) { if ( color.size() == 5 ) return svg::parse_color("#" + color.mid(2) + color[1]); if ( color.size() == 9 ) return svg::parse_color("#" + color.mid(3) + color.mid(1, 2)); } return svg::parse_color(color); } void set_styler_style(model::Styler* styler, const QString& color) { if ( color.isEmpty() ) { styler->visible.set(false); } else if ( color[0] == '@' ) { auto res = get_resource(color); if ( res && res->element.tagName() == "gradient" ) styler->use.set(parse_gradient(res)); } else if ( color[0] == '?' ) { styler->use.set(color_from_theme(color)); } else { styler->color.set(parse_color(color)); } } void add_stroke(const ParseFuncArgs& args, model::ShapeListProperty* shapes, const Style& style) { auto stroke = std::make_unique(document); set_styler_style(stroke.get(), style.get("strokeColor", "")); stroke->opacity.set(percent_1(style.get("strokeAlpha", "1"))); stroke->width.set(parse_unit(style.get("strokeWidth", "1"))); stroke->cap.set(line_cap(style.get("strokeLineCap", "butt"))); stroke->join.set(line_join(style.get("strokeLineJoin", "butt"))); stroke->miter_limit.set(parse_unit(style.get("strokeMiterLimit", "4"))); auto anim = get_animations(args.element); for ( const auto& kf : anim.single("strokeColor") ) stroke->color.set_keyframe(kf.time, kf.values.color())->set_transition(kf.transition); for ( const auto& kf : anim.single("strokeAlpha") ) stroke->opacity.set_keyframe(kf.time, kf.values.scalar())->set_transition(kf.transition); for ( const auto& kf : anim.single("strokeWidth") ) stroke->width.set_keyframe(kf.time, kf.values.scalar())->set_transition(kf.transition); shapes->insert(std::move(stroke)); } const AnimateParser::AnimatedProperties& get_animations(const QDomElement& element) { auto name = element.attribute("name"); return animations[name]; } void add_fill(const ParseFuncArgs& args, model::ShapeListProperty* shapes, const Style& style) { auto fill = std::make_unique(document); set_styler_style(fill.get(), style.get("fillColor", "")); fill->opacity.set(percent_1(style.get("fillAlpha", "1"))); if ( style.get("fillType", "") == "evenOdd" ) fill->fill_rule.set(model::Fill::EvenOdd); auto anim = get_animations(args.element); for ( const auto& kf : anim.single("fillColor") ) fill->color.set_keyframe(kf.time, kf.values.color())->set_transition(kf.transition); for ( const auto& kf : anim.single("fillAlpha") ) fill->opacity.set_keyframe(kf.time, kf.values.scalar())->set_transition(kf.transition); shapes->insert(std::move(fill)); } void add_trim(const ParseFuncArgs& args, model::ShapeListProperty* shapes, const Style& style) { auto trim = std::make_unique(document); trim->start.set(percent_1(style.get("trimPathStart", "1"))); trim->end.set(percent_1(style.get("trimPathEnd", "1"))); trim->offset.set(percent_1(style.get("trimPathOffset", "1"))); auto anim = get_animations(args.element); for ( const auto& kf : anim.single("trimPathStart") ) trim->start.set_keyframe(kf.time, kf.values.scalar())->set_transition(kf.transition); for ( const auto& kf : anim.single("trimPathEnd") ) trim->end.set_keyframe(kf.time, kf.values.scalar())->set_transition(kf.transition); for ( const auto& kf : anim.single("trimPathOffset") ) trim->offset.set_keyframe(kf.time, kf.values.scalar())->set_transition(kf.transition); shapes->insert(std::move(trim)); } model::Gradient* parse_gradient(Resource* res) { if ( res->element.tagName() != "gradient" ) return nullptr; if ( res->asset ) return res->asset->cast(); // Load colors auto colors = document->assets()->add_gradient_colors(); QGradientStops stops; if ( res->element.hasAttribute("startColor") ) stops.push_back({0.0, parse_color(res->element.attribute("startColor"))}); if ( res->element.hasAttribute("centerColor") ) stops.push_back({0.5, parse_color(res->element.attribute("centerColor"))}); if ( res->element.hasAttribute("endColor") ) stops.push_back({1.0, parse_color(res->element.attribute("endColor"))}); for ( QDomElement e : ElementRange(res->element.childNodes()) ) { if ( e.tagName() == "item" ) stops.push_back({ e.attribute("offset", "0").toDouble(), parse_color(e.attribute("color")) }); } colors->colors.set(stops); // Load gradient auto gradient = document->assets()->add_gradient(); gradient->colors.set(colors); QString type = res->element.attribute("type", "linear"); if ( type == "linear" ) gradient->type.set(model::Gradient::Linear); else if ( type == "radial" ) gradient->type.set(model::Gradient::Radial); else if ( type == "sweeo" ) gradient->type.set(model::Gradient::Conical); gradient->start_point.set({ len_attr(res->element, "startX"), len_attr(res->element, "startY"), }); gradient->end_point.set({ len_attr(res->element, "endX"), len_attr(res->element, "endY"), }); // TODO center / radius res->asset = gradient; return gradient; } void parse_transform(model::Transform* trans, const ParseFuncArgs& args) { QPointF anchor = { len_attr(args.element, "pivotX"), len_attr(args.element, "pivotY"), }; trans->anchor_point.set(anchor); trans->position.set(anchor + QPointF{ len_attr(args.element, "translateX"), len_attr(args.element, "translateY"), }); trans->scale.set(QVector2D( percent_1(args.element.attribute("scaleX", "1")), percent_1(args.element.attribute("scaleY", "1")) )); trans->rotation.set(args.element.attribute("rotation", "0").toDouble()); auto anim = get_animations(args.element); for ( const auto& kf : anim.joined({"pivotX", "pivotY", "translateX", "translateY"}) ) { anchor = QPointF(kf.values[0].scalar(), kf.values[1].scalar()); trans->anchor_point.set_keyframe(kf.time, anchor)->set_transition(kf.transition); QPointF pos(kf.values[2].scalar(), kf.values[3].scalar()); trans->position.set_keyframe(kf.time, anchor + pos)->set_transition(kf.transition); } for ( const auto& kf : anim.joined({"scaleX", "scaleY"}) ) { QVector2D scale(kf.values[0].scalar(), kf.values[1].scalar()); trans->scale.set_keyframe(kf.time, scale)->set_transition(kf.transition); } for ( const auto& kf : anim.single("rotation") ) trans->rotation.set_keyframe(kf.time, kf.values.scalar())->set_transition(kf.transition); } std::unique_ptr parse_clip(const QDomElement& element) { auto clip = std::make_unique(document); set_name(clip.get(), element); QString d = element.attribute("pathData"); math::bezier::MultiBezier bez = PathDParser(d).parse(); auto fill = std::make_unique(document); fill->color.set(QColor(255, 255, 255)); clip->shapes.insert(std::move(fill)); std::vector shapes; for ( const auto& bezier : bez.beziers() ) { auto shape = std::make_unique(document); shape->shape.set(bezier); shape->closed.set(bezier.closed()); shapes.push_back(shape.get()); clip->shapes.insert(std::move(shape)); } path_animation(shapes, get_animations(element), "pathData"); return clip; } void parseshape_group(const ParseFuncArgs& args) { std::unique_ptr clip; for ( auto e : ElementRange(args.element.elementsByTagName("clip-path")) ) { clip = parse_clip(e); break; } model::Group* group = nullptr; if ( clip ) { auto obj = std::make_unique(document); group = obj.get(); args.shape_parent->insert(std::move(obj)); } else { auto obj = std::make_unique(document); group = obj.get(); args.shape_parent->insert(std::move(obj)); } set_name(group, args.element); parse_transform(group->transform.get(), args); parse_children({args.element, &group->shapes, args.parent_style, true}); } void parseshape_path(const ParseFuncArgs& args) { QString d = args.element.attribute("pathData"); math::bezier::MultiBezier bez = PathDParser(d).parse(); ShapeCollection shapes; std::vector paths; for ( const auto& bezier : bez.beziers() ) { auto shape = push(shapes); shape->shape.set(bezier); shape->closed.set(bezier.closed()); paths.push_back(shape); } add_shapes(args, std::move(shapes)); path_animation(paths, get_animations(args.element), "pathData"); } Resource* get_resource(const QString& id) { auto iter = resources.find(id); if ( iter != resources.end() ) return &iter->second; if ( resource_path.isRoot() || id.isEmpty() || id[0] != '@' || id.back() == '\0' ) { warning(QObject::tr("Unkown resource id %1").arg(id)); return {}; } QString path = resource_path.filePath(id.mid(1) + ".xml"); QFile resource_file(path); if ( !resource_file.open(QIODevice::ReadOnly) ) { warning(QObject::tr("Could not read file %1").arg(path)); warning(QObject::tr("Could not load resource %1").arg(id)); return {}; } SvgParseError err; QDomDocument resource_dom; if ( !resource_dom.setContent(&resource_file, true, &err.message, &err.line, &err.column) ) { warning(err.formatted(path)); warning(QObject::tr("Could not load resource %1").arg(id)); return {}; } iter = resources.insert({id, {id, resource_dom.documentElement()}}).first; return &iter->second; } QString add_as_resource(const QDomElement& e) { internal_resource_id++; QString id = QString("@(internal)%1").arg(internal_resource_id); id.push_back('\0'); resources[id] = {e.tagName(), e}; return id; } model::NamedColor* color_from_theme(const QString& color) { QString norm_name; if ( color.contains("/") ) norm_name = color.split("/").back(); else norm_name = color.mid(1); auto iter = palette_colors.find(norm_name); if ( iter != palette_colors.end() ) return iter->second; QColor col = Qt::black; auto it2 = theme_colors.find(norm_name); if ( it2 != theme_colors.end() ) col = it2->second; auto asset = document->assets()->add_color(col); palette_colors.emplace(norm_name, asset); return asset; } QDir resource_path; std::map resources; int internal_resource_id = 0; std::map palette_colors; std::map animations; static const std::map shape_parsers; static const std::unordered_set style_atrrs; static const std::unordered_map theme_colors; }; const std::map glaxnimate::io::avd::AvdParser::Private::shape_parsers = { {"group", &glaxnimate::io::avd::AvdParser::Private::parseshape_group}, {"path", &glaxnimate::io::avd::AvdParser::Private::parseshape_path}, }; const std::unordered_set glaxnimate::io::avd::AvdParser::Private::style_atrrs = { "fillColor", "fillAlpha", "fillType", "strokeColor", "strokeAlpha", "strokeWidth", "strokeLineCap", "strokeLineJoin", "strokeMiterLimit", "trimPathStart", "trimPathEnd", "trimPathOffset", }; glaxnimate::io::avd::AvdParser::AvdParser( QIODevice* device, const QDir& resource_path, model::Document* document, const std::function& on_warning, ImportExport* io, QSize forced_size, model::FrameTime default_time ) : d(std::make_unique(resource_path, document, on_warning, io, forced_size, default_time)) { d->load(device); } glaxnimate::io::avd::AvdParser::~AvdParser() = default; void glaxnimate::io::avd::AvdParser::parse_to_document() { d->parse(); } /// Based on the material theme /// Extracted using https://gitlab.com/-/snippets/2502132 const std::unordered_map glaxnimate::io::avd::AvdParser::Private::theme_colors = { {"colorForeground", "#ffffffff"}, {"colorForegroundInverse", "#ff000000"}, {"colorBackground", "#ff303030"}, {"colorBackgroundFloating", "#ff424242"}, {"colorError", "#ff7043"}, {"opacityListDivider", "#1f000000"}, {"textColorPrimary", "#ff000000"}, {"textColorSecondary", "#ff000000"}, {"textColorHighlight", "#ffffffff"}, {"textColorHighlightInverse", "#ffffffff"}, {"navigationBarColor", "#ff000000"}, {"panelColorBackground", "#000"}, {"colorPrimaryDark", "#ff000000"}, {"colorPrimary", "#ff212121"}, {"colorAccent", "#ff80cbc4"}, {"tooltipForegroundColor", "#ff000000"}, {"colorPopupBackground", "#ff303030"}, {"colorListDivider", "#ffffffff"}, {"textColorLink", "#ff80cbc4"}, {"textColorLinkInverse", "#ff80cbc4"}, {"editTextColor", "#ff000000"}, {"windowBackground", "#ff303030"}, {"statusBarColor", "#ff000000"}, {"panelBackground", "#ff303030"}, {"panelColorForeground", "#ff000000"}, {"detailsElementBackground", "#ff303030"}, {"actionMenuTextColor", "#ff000000"}, {"colorEdgeEffect", "#ff212121"}, {"colorControlNormal", "#ff000000"}, {"colorControlActivated", "#ff80cbc4"}, {"colorProgressBackgroundNormal", "#ff000000"}, }; mlt-7.22.0/src/modules/glaxnimate/glaxnimate/external/QtAppSetup/src/app/scripting/js/000775 001750 001750 00000000000 14477652011 032433 5ustar00ddennedyddennedy000000 000000 mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/assets/gradient.cpp000664 001750 001750 00000020616 14477652011 031205 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "gradient.hpp" #include #include "model/document.hpp" #include "model/assets/assets.hpp" #include "command/object_list_commands.hpp" #include "command/animation_commands.hpp" #include "command/undo_macro_guard.hpp" #include "utils/sort_gradient.hpp" using namespace glaxnimate; GLAXNIMATE_OBJECT_IMPL(glaxnimate::model::GradientColors) GLAXNIMATE_OBJECT_IMPL(glaxnimate::model::Gradient) template<> std::optional glaxnimate::model::detail::variant_cast(const QVariant& val) { if ( !val.canConvert() ) { if ( val.canConvert() ) { QGradientStops stops; for ( auto stop : val.toList() ) { if ( stop.canConvert() ) { stops.push_back(stop.value()); } else if ( stop.canConvert() ) { auto sl = stop.toList(); if ( sl.size() == 2 && sl[0].canConvert() && sl[1].canConvert() ) stops.push_back({sl[0].toDouble(), sl[1].value()}); } } return stops; } return {}; } QVariant converted = val; #if QT_VERSION_MAJOR < 6 if ( !converted.convert(qMetaTypeId()) ) #else if ( !converted.convert(QMetaType::fromType()) ) #endif return {}; return converted.value(); } template<> QGradientStops math::lerp(const QGradientStops& a, const QGradientStops& b, double factor) { if ( a.size() != b.size() ) return factor >= 1 ? b : a; QGradientStops mix; mix.reserve(a.size()); for ( int i = 0; i < a.size(); i++ ) mix.push_back({ math::lerp(a[i].first, b[i].first, factor), math::lerp(a[i].second, b[i].second, factor) }); return mix; } QString glaxnimate::model::GradientColors::type_name_human() const { return tr("Gradient"); } QIcon glaxnimate::model::GradientColors::instance_icon() const { QPixmap icon(32, 32); QPainter p(&icon); QLinearGradient g(0, 0, icon.width(), 0); g.setStops(colors.get()); p.fillRect(icon.rect(), g); return icon; } bool glaxnimate::model::GradientColors::remove_if_unused(bool clean_lists) { if ( clean_lists && users().empty() ) { document()->push_command(new command::RemoveObject( this, &document()->assets()->gradient_colors->values )); return true; } return false; } static QVariant split_gradient(QGradientStops colors, int index, float factor, const QColor& new_color) { int before = index; int after = index+1; if ( after >= colors.size() ) { before = colors.size() - 2; after = colors.size() - 1; } colors.push_back({ math::lerp(colors[before].first, colors[after].first, factor), new_color.isValid() ? new_color : math::lerp(colors[before].second, colors[after].second, 0.5) }); utils::sort_gradient(colors); return QVariant::fromValue(colors); } void glaxnimate::model::GradientColors::split_segment(int segment_index, float factor, const QColor& new_color) { command::UndoMacroGuard guard(tr("Add color to %1").arg(name.get()), document()); if ( segment_index < 0 ) segment_index = 0; if ( !colors.animated() ) { colors.set_undoable(split_gradient(colors.get(), segment_index, factor, new_color)); } else { for ( const auto& kf : colors ) document()->push_command(new command::SetKeyframe( &colors, kf.time(), split_gradient(kf.get(), segment_index, factor, new_color), true )); } } void glaxnimate::model::GradientColors::remove_stop(int index) { command::UndoMacroGuard guard(tr("Remove color from %1").arg(name.get()), document()); if ( index < 0 ) index = 0; if ( !colors.animated() ) { auto stops = colors.get(); stops.erase(std::min(stops.begin() + index, stops.end())); colors.set_undoable(QVariant::fromValue(stops)); } else { for ( const auto& kf : colors ) { auto stops = kf.get(); stops.erase(std::min(stops.begin() + index, stops.end())); document()->push_command(new command::SetKeyframe( &colors, kf.time(), QVariant::fromValue(stops), true )); } } } std::vector glaxnimate::model::Gradient::valid_refs() const { return document()->assets()->gradient_colors->values.valid_reference_values(false); } bool glaxnimate::model::Gradient::is_valid_ref ( glaxnimate::model::DocumentNode* node ) const { return document()->assets()->gradient_colors->values.is_valid_reference_value(node, true); } void glaxnimate::model::Gradient::on_ref_visual_changed() { emit style_changed(); } void glaxnimate::model::Gradient::on_ref_changed ( glaxnimate::model::GradientColors* new_ref, glaxnimate::model::GradientColors* old_ref ) { if ( old_ref ) disconnect(old_ref, &GradientColors::colors_changed, this, &Gradient::on_ref_visual_changed); if ( new_ref ) { connect(new_ref, &GradientColors::colors_changed, this, &Gradient::on_ref_visual_changed); } else { detach(); } colors_changed_from(old_ref, new_ref); } QString glaxnimate::model::Gradient::type_name_human() const { return tr("%1 Gradient").arg(gradient_type_name(type.get())); } QBrush glaxnimate::model::Gradient::brush_style ( glaxnimate::model::FrameTime t ) const { if ( type.get() == Radial ) { QRadialGradient g(start_point.get_at(t), radius(t), highlight.get_at(t)); if ( colors.get() ) g.setStops(colors->colors.get_at(t)); g.setSpread(QGradient::PadSpread); return g; } else if ( type.get() == Conical ) { auto start = start_point.get_at(t); auto end = end_point.get_at(t); auto angle = -math::rad2deg(math::atan2(end.y() - start.y(), end.x() - start.x())); QConicalGradient g(start, angle); if ( colors.get() ) g.setStops(colors->colors.get_at(t)); return g; } else { QLinearGradient g(start_point.get_at(t), end_point.get_at(t)); if ( colors.get() ) g.setStops(colors->colors.get_at(t)); g.setSpread(QGradient::PadSpread); return g; } } QBrush glaxnimate::model::Gradient::constrained_brush_style(FrameTime t, const QRectF& bounds) const { if ( type.get() == Radial ) { QRadialGradient g(bounds.center(), bounds.width() / 2); if ( colors.get() ) g.setStops(colors->colors.get_at(t)); return g; } else if ( type.get() == Conical ) { QConicalGradient g(bounds.center(), 02); if ( colors.get() ) g.setStops(colors->colors.get_at(t)); return g; } else { QLinearGradient g(bounds.topLeft(), bounds.topRight()); if ( colors.get() ) g.setStops(colors->colors.get_at(t)); return g; } } void glaxnimate::model::Gradient::fill_icon(QPixmap& icon) const { QPainter p(&icon); p.fillRect(icon.rect(), constrained_brush_style(time(), icon.rect())); } qreal glaxnimate::model::Gradient::radius(glaxnimate::model::FrameTime t) const { return math::length(start_point.get_at(t) - end_point.get_at(t)); } QString glaxnimate::model::Gradient::gradient_type_name(GradientType t) { switch ( t ) { case Linear: return tr("Linear"); case Radial: return tr("Radial"); case Conical: return tr("Conical"); } return {}; } void glaxnimate::model::Gradient::on_property_changed(const glaxnimate::model::BaseProperty*, const QVariant&) { emit style_changed(); } bool glaxnimate::model::Gradient::remove_if_unused(bool) { if ( users().empty() ) { colors.set_undoable(QVariant::fromValue((glaxnimate::model::GradientColors*)nullptr)); document()->push_command(new command::RemoveObject( this, &document()->assets()->gradients->values )); return true; } return false; } src/modules/glaxnimate/glaxnimate/external/QtAppSetup/src/app/settings/keyboard_shortcuts_model.cpp000664 001750 001750 00000015463 14477652011 037411 0ustar00ddennedyddennedy000000 000000 mlt-7.22.0/* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "keyboard_shortcuts_model.hpp" #include "app/widgets/clearable_keysequence_edit.hpp" app::settings::KeyboardShortcutsModel::KeyboardShortcutsModel(app::settings::ShortcutSettings* settings, QObject* parent) : QAbstractItemModel(parent), settings(settings) {} int app::settings::KeyboardShortcutsModel::columnCount(const QModelIndex&) const { return 2; } int app::settings::KeyboardShortcutsModel::rowCount(const QModelIndex& parent) const { if ( !parent.isValid() ) return settings->get_groups().size(); if ( !parent.parent().isValid() && parent.row() < settings->get_groups().size() ) { auto& grp = settings->get_groups()[parent.row()]; return grp.actions.size(); } return 0; } QVariant app::settings::KeyboardShortcutsModel::headerData(int section, Qt::Orientation orientation, int role) const { if ( orientation == Qt::Horizontal && role == Qt::DisplayRole ) return section == 0 ? tr("Name") : tr("Shortcut"); return {}; } Qt::ItemFlags app::settings::KeyboardShortcutsModel::flags(const QModelIndex& index) const { auto flags = QAbstractItemModel::flags(index); if ( index.isValid() && index.parent().isValid() && index.column() == 1 ) flags |= Qt::ItemIsEditable; return flags; } QVariant app::settings::KeyboardShortcutsModel::data(const QModelIndex& index, int role) const { if ( !index.isValid() ) return {}; if ( !index.parent().isValid() ) { if ( role != Qt::DisplayRole || index.column() != 0 || index.row() >= settings->get_groups().size() ) return {}; auto& grp = settings->get_groups()[index.row()]; return grp.label; } int grp_index = index.internalId(); if ( grp_index >= settings->get_groups().size() ) return {}; auto& grp = settings->get_groups()[grp_index]; if ( index.row() >= int(grp.actions.size()) ) return {}; auto act = grp.actions[index.row()]; if ( index.column() == 0 ) { switch ( role ) { case Qt::DisplayRole: return act->label; case Qt::DecorationRole: return act->icon; default: return {}; } } else { switch ( role ) { case Qt::DisplayRole: case Qt::EditRole: return act->shortcut; case DefaultKeyRole: return act->default_shortcut; default: return {}; } } } bool app::settings::KeyboardShortcutsModel::setData(const QModelIndex& index, const QVariant& value, int role) { if ( !index.isValid() ) return false; if ( !index.parent().isValid() ) return false; int grp_index = index.internalId(); if ( grp_index >= settings->get_groups().size() ) return false; if ( index.column() != 1 || role != Qt::EditRole ) return false; auto& grp = settings->get_groups()[grp_index]; if ( index.row() >= int(grp.actions.size()) ) return false; auto act = grp.actions[index.row()]; QKeySequence ks; if ( value.canConvert() ) ks = value.value(); else if ( value.canConvert() ) ks = QKeySequence(value.toString(), QKeySequence::PortableText); else return false; act->overwritten = ks != act->default_shortcut; act->shortcut = ks; if ( act->action ) act->action->setShortcut(ks); emit dataChanged(index, index, {role}); return true; } QModelIndex app::settings::KeyboardShortcutsModel::index(int row, int column, const QModelIndex& parent) const { if ( !parent.isValid() ) { if ( row >= settings->get_groups().size() ) return {}; return createIndex(row, column, 1000 + row); } return createIndex(row, column, parent.internalId()-1000); } QModelIndex app::settings::KeyboardShortcutsModel::parent(const QModelIndex& child) const { if ( !child.isValid() ) return {}; int id = child.internalId(); if ( id >= 1000 ) return {}; return createIndex(id, 0, id+1000); } app::settings::ShortcutAction * app::settings::KeyboardShortcutsModel::action(const QModelIndex& index) const { if ( !index.isValid() ) return nullptr; if ( !index.parent().isValid() ) return nullptr; int grp_index = index.internalId(); if ( grp_index >= settings->get_groups().size() ) return nullptr; auto& grp = settings->get_groups()[grp_index]; if ( index.row() >= int(grp.actions.size()) ) return nullptr; return grp.actions[index.row()]; } void app::settings::KeyboardShortcutsModel::begin_change_data() { emit beginResetModel(); } void app::settings::KeyboardShortcutsModel::end_change_data() { emit endResetModel(); } bool app::settings::KeyboardShortcutsFilterModel::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const { if ( !source_parent.isValid() ) return true; #if QT_VERSION_MAJOR >= 6 auto re = filterRegularExpression(); #else auto re = filterRegExp(); #endif QModelIndex i0 = sourceModel()->index(source_row, 0, source_parent); QModelIndex i1 = sourceModel()->index(source_row, 1, source_parent); return sourceModel()->data(i0).toString().contains(re) || sourceModel()->data(i1).toString().contains(re); } QWidget * app::settings::KeyboardShortcutsDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const { if ( index.data(Qt::EditRole).canConvert() ) return new ClearableKeysequenceEdit(parent); return QStyledItemDelegate::createEditor(parent, option, index); } void app::settings::KeyboardShortcutsDelegate::setEditorData(QWidget* editor, const QModelIndex& index) const { QVariant edit_data = index.data(Qt::EditRole); if ( edit_data.canConvert() ) { ClearableKeysequenceEdit* widget = static_cast(editor); widget->set_key_sequence(edit_data.value()); QVariant default_data = index.data(KeyboardShortcutsModel::DefaultKeyRole); if ( default_data.canConvert() ) widget->set_default_key_sequence(default_data.value()); } return QStyledItemDelegate::setEditorData(editor, index); } void app::settings::KeyboardShortcutsDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const { if ( index.data(Qt::EditRole).canConvert() ) model->setData(index, static_cast(editor)->key_sequence()); return QStyledItemDelegate::setModelData(editor, model, index); } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/video/video_format.hpp000664 001750 001750 00000001612 14477652011 031201 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include "io/base.hpp" #include "io/io_registry.hpp" namespace glaxnimate::io::video { class VideoFormat : public ImportExport { Q_OBJECT public: QString slug() const override { return "video"; } QString name() const override { return tr("Video"); } QStringList extensions() const override; bool can_save() const override { return true; } bool can_open() const override { return false; } std::unique_ptr save_settings(model::Composition*) const override; static QString library_version(); protected: bool on_save(QIODevice& dev, const QString&, model::Composition* comp, const QVariantMap&) override; private: static Autoreg autoreg; }; } // namespace glaxnimate::io::video mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/assets/bitmap.cpp000664 001750 001750 00000013746 14477652011 030672 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "bitmap.hpp" #include #include #include #include #include #include #include "model/document.hpp" #include "model/assets/assets.hpp" #include "command/object_list_commands.hpp" GLAXNIMATE_OBJECT_IMPL(glaxnimate::model::Bitmap) void glaxnimate::model::Bitmap::paint(QPainter* painter) const { painter->drawPixmap(0, 0, image); } void glaxnimate::model::Bitmap::refresh(bool rebuild_embedded) { QImageReader reader; QImage qimage; bool load_data = true; if ( rebuild_embedded || data.get().isEmpty() ) { if ( !filename.get().isEmpty() ) { QFileInfo finfo = file_info(); if ( !finfo.isFile() ) return; reader.setFileName(finfo.absoluteFilePath()); format.set(reader.format()); qimage = reader.read(); if ( rebuild_embedded && embedded() ) data.set(build_embedded(qimage)); load_data = false; } else if ( !url.get().isEmpty() ) { document()->assets()->network_downloader.get(url.get(), [this, rebuild_embedded](QByteArray response){ QImageReader reader; QImage qimage; QBuffer buf(&response); buf.open(QIODevice::ReadOnly); reader.setDevice(&buf); format.set(reader.format()); qimage = reader.read(); if ( rebuild_embedded && embedded() ) data.set(build_embedded(qimage)); image = QPixmap::fromImage(qimage); width.set(image.width()); height.set(image.height()); document()->graphics_invalidated(); emit loaded(); }, this); return; } } if ( load_data ) { QBuffer buf(const_cast(&data.get())); buf.open(QIODevice::ReadOnly); reader.setDevice(&buf); format.set(reader.format()); qimage = reader.read(); } image = QPixmap::fromImage(qimage); width.set(image.width()); height.set(image.height()); emit loaded(); } QByteArray glaxnimate::model::Bitmap::build_embedded(const QImage& img) const { QByteArray new_data; QBuffer buf(&new_data); buf.open(QIODevice::WriteOnly); QImageWriter writer(&buf, format.get().toLatin1()); writer.write(img); return new_data; } bool glaxnimate::model::Bitmap::embedded() const { return !data.get().isEmpty(); } void glaxnimate::model::Bitmap::embed(bool embedded) { if ( embedded == this->embedded() ) return; if ( !embedded ) data.set_undoable({}); else data.set_undoable(build_embedded(image.toImage())); } void glaxnimate::model::Bitmap::on_refresh() { refresh(false); } QIcon glaxnimate::model::Bitmap::instance_icon() const { return image; } bool glaxnimate::model::Bitmap::from_url(const QUrl& url) { if ( url.scheme().isEmpty() || url.scheme() == "file" ) return from_file(url.path()); if ( url.scheme() == "data" ) return from_base64(url.path()); this->url.set(url.toString()); return true; } bool glaxnimate::model::Bitmap::from_file(const QString& file) { filename.set(file); return !image.isNull(); } bool glaxnimate::model::Bitmap::from_base64(const QString& data) { auto chunks = data.split(','); if ( chunks.size() != 2 ) return false; auto mime_settings = chunks[0].split(';'); if ( mime_settings.size() != 2 || mime_settings[1] != "base64" ) return false; auto formats = QImageReader::imageFormatsForMimeType(mime_settings[0].toLatin1()); if ( formats.empty() ) return false; auto decoded = QByteArray::fromBase64(chunks[1].toLatin1()); format.set(formats[0]); this->data.set(decoded); return !image.isNull(); } bool glaxnimate::model::Bitmap::from_raw_data(const QByteArray& data) { QBuffer buf(const_cast(&data)); buf.open(QBuffer::ReadOnly); auto format = QImageReader::imageFormat(&buf); if ( format.isEmpty() ) return false; this->format.set(format); this->data.set(data); return !image.isNull(); } QUrl glaxnimate::model::Bitmap::to_url() const { if ( !embedded() ) { return QUrl::fromLocalFile(file_info().absoluteFilePath()); } QByteArray fmt = format.get().toLatin1(); QByteArray mime_type; for ( const auto& mime : QImageWriter::supportedMimeTypes() ) if ( QImageWriter::imageFormatsForMimeType(mime).contains(fmt) ) { mime_type = mime; break; } if ( mime_type.isEmpty() ) return {}; QString data_url = "data:"; data_url += mime_type; data_url += ";base64,"; data_url += data.get().toBase64(); return QUrl(data_url); } QString glaxnimate::model::Bitmap::object_name() const { if ( embedded() ) return tr("Embedded image"); return QFileInfo(filename.get()).fileName(); } QFileInfo glaxnimate::model::Bitmap::file_info() const { return QFileInfo(document()->io_options().path, filename.get()); } bool glaxnimate::model::Bitmap::remove_if_unused(bool) { if ( users().empty() ) { document()->push_command(new command::RemoveObject( this, &document()->assets()->images->values )); return true; } return false; } void glaxnimate::model::Bitmap::set_pixmap(const QImage& pix, const QString& format) { this->format.set(format); data.set(build_embedded(pix)); } QByteArray glaxnimate::model::Bitmap::image_data() const { if ( !data.get().isEmpty() ) return data.get(); if ( image.isNull() ) return {}; return build_embedded(image.toImage()); } QSize glaxnimate::model::Bitmap::size() const { return {width.get(), height.get()}; } src/modules/glaxnimate/glaxnimate/external/QtAppSetup/src/app/widgets/no_close_on_enter.hpp000664 001750 001750 00000001175 14477652011 035613 0ustar00ddennedyddennedy000000 000000 mlt-7.22.0/* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include namespace app::widgets { class NoCloseOnEnter : public QObject { protected: bool eventFilter(QObject *obj, QEvent *event) { if (event->type() == QEvent::KeyPress) { QKeyEvent *keyEvent = static_cast(event); if ( keyEvent->key() == Qt::Key_Return || keyEvent->key() == Qt::Key_Enter ) return true; } return QObject::eventFilter(obj, event); } }; } // namespace app::widgets mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/utils/iterator.hpp000664 001750 001750 00000004540 14477652011 030002 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include namespace glaxnimate::utils { /** * \brief CRTP that adds all the boilerplate of an iterator * wrapping a random-access iterator. * * All you need to do is overload operator->() and operator*(). */ template class RandomAccessIteratorWrapper { public: using iterator_category = std::random_access_iterator_tag; using difference_type = typename BaseIterator::difference_type; // Iterator RandomAccessIteratorWrapper& operator++() { ++iter; return *this; } // Input/Output Iterator bool operator==(const RandomAccessIteratorWrapper& o) const { return iter == o.iter; } bool operator!=(const RandomAccessIteratorWrapper& o) const { return iter != o.iter; } Iterator operator++(int) { auto copy = *this; ++iter; return copy; } // Forward Iterator RandomAccessIteratorWrapper() = default; // Bidirectional Iterator Iterator& operator--() { --iter; return *cast_derived(); } Iterator operator--(int) { auto copy = *this; --iter; return copy; } // Random Access Iterator Iterator& operator+= (difference_type i) { iter += i; return *cast_derived(); } Iterator operator+ (difference_type i) const { return iter + i; } friend Iterator operator+ (difference_type i, const Iterator& iter) { return iter.iter + i; } Iterator& operator-= (difference_type i) { iter -= i; return *cast_derived(); } Iterator operator- (difference_type i) const { return iter - i; } Iterator operator- (const Iterator& o) const { return iter - o.iter; } bool operator<(const Iterator& o) const { return iter < o.iter; } bool operator<=(const Iterator& o) const { return iter <= o.iter; } bool operator>(const Iterator& o) const { return iter > o.iter; } bool operator>=(const Iterator& o) const { return iter >= o.iter; } protected: using InternalIterator = BaseIterator; using Parent = RandomAccessIteratorWrapper; RandomAccessIteratorWrapper(BaseIterator iter) : iter(std::move(iter)) {} BaseIterator iter; private: Iterator* cast_derived() { return static_cast(this); } const Iterator* cast_derived() const { return static_cast(this); } }; } // namespace glaxnimate::utils mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/raster/raster_mime.hpp000664 001750 001750 00000006260 14477652011 031230 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include #include #include "io/mime/mime_serializer.hpp" #include "io/io_registry.hpp" #include "model/document.hpp" #include "model/shapes/image.hpp" #include "model/assets/assets.hpp" namespace glaxnimate::io::raster { class RasterMime : public io::mime::MimeSerializer { public: QString slug() const override { return "raster"; } QString name() const override { return QObject::tr("Raster Image"); } QStringList mime_types() const override { return {"image/png"}; } QByteArray serialize(const std::vector& selection) const override { QByteArray data; QBuffer buffer(&data); to_image(selection).save(&buffer, "PNG"); return data; } bool can_deserialize() const override { return true; } void to_mime_data(QMimeData& mime, const std::vector& objects) const override { mime.setImageData(to_image(objects)); } static QImage to_image(const std::vector& selection) { if ( selection.empty() ) return {}; std::vector visual_nodes; visual_nodes.reserve(selection.size()); QRectF box; for ( auto node : selection ) { if ( auto visual = node->cast() ) { visual_nodes.push_back(visual); box |= visual->local_bounding_rect(visual->time()); } } QImage image(box.size().toSize(), QImage::Format_ARGB32); image.fill(Qt::transparent); QPainter painter(&image); painter.setRenderHint(QPainter::Antialiasing); painter.translate(-box.topLeft()); for ( auto visual : visual_nodes ) { visual->paint(&painter, visual->time(), model::VisualNode::Render); } return image; } static QImage frame_to_image(const model::VisualNode* node, model::FrameTime time) { if ( !node ) return {}; QImage image(node->local_bounding_rect(time).size().toSize(), QImage::Format_ARGB32); image.fill(Qt::transparent); QPainter painter(&image); painter.setRenderHint(QPainter::Antialiasing); node->paint(&painter, time, model::VisualNode::Render); return image; } io::mime::DeserializedData deserialize(const QByteArray& data) const override { io::mime::DeserializedData out; out.initialize_data(); auto bmp = out.document->assets()->images->values.insert(std::make_unique(out.document.get())); bmp->data.set(data); auto img = std::make_unique(out.document.get()); img->image.set(bmp); QPointF p(bmp->pixmap().width() / 2.0, bmp->pixmap().height() / 2.0); img->transform->anchor_point.set(p); img->transform->position.set(p); out.main->shapes.insert(std::move(img)); return out; } private: static Autoreg autoreg; }; } // namespace glaxnimate::io::mime src/modules/glaxnimate/glaxnimate/external/QtAppSetup/src/app/widgets/keyboard_settings_widget.cpp000664 001750 001750 00000003733 14477652011 037201 0ustar00ddennedyddennedy000000 000000 mlt-7.22.0/* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "keyboard_settings_widget.hpp" #include "ui_keyboard_settings_widget.h" #include #include "app/settings/keyboard_shortcuts_model.hpp" class KeyboardSettingsWidget::Private { public: Private(app::settings::ShortcutSettings* settings) : inner_model(settings), settings(settings) { model.setSourceModel(&inner_model); model.setFilterCaseSensitivity(Qt::CaseInsensitive); } Ui::KeyboardSettingsWidget ui; app::settings::KeyboardShortcutsModel inner_model; app::settings::KeyboardShortcutsFilterModel model; app::settings::KeyboardShortcutsDelegate delegate; app::settings::ShortcutSettings* settings; }; KeyboardSettingsWidget::KeyboardSettingsWidget(app::settings::ShortcutSettings* settings, QWidget* parent) : QWidget(parent), d(std::make_unique(settings)) { d->ui.setupUi(this); d->ui.tree_view->setModel(&d->model); d->ui.tree_view->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents); d->ui.tree_view->header()->setSectionResizeMode(1, QHeaderView::Stretch); d->ui.tree_view->setItemDelegateForColumn(1, &d->delegate); connect(settings, &app::settings::ShortcutSettings::begin_actions_change, &d->inner_model, &app::settings::KeyboardShortcutsModel::begin_change_data); connect(settings, &app::settings::ShortcutSettings::end_actions_change, &d->inner_model, &app::settings::KeyboardShortcutsModel::end_change_data); } KeyboardSettingsWidget::~KeyboardSettingsWidget() = default; void KeyboardSettingsWidget::changeEvent ( QEvent* e ) { QWidget::changeEvent(e); if ( e->type() == QEvent::LanguageChange) { d->ui.retranslateUi(this); } } void KeyboardSettingsWidget::clear_filter() { d->ui.filter->setText(""); } void KeyboardSettingsWidget::filter(const QString& text) { d->model.setFilterFixedString(text); } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/lottie/cbor_write_json.cpp000664 001750 001750 00000026036 14477652011 032107 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "cbor_write_json.hpp" #include #include #include #include /****************************************************************************** * These function are mostly taken from Qt code * See * https://github.com/qt/qtbase/blob/dev/src/corelib/serialization/qjsonwriter.cpp * https://github.com/qt/qtbase/blob/dev/src/corelib/text/qstringconverter_p.h ******************************************************************************/ #ifndef __cpp_char8_t enum char8_t : uchar {}; #endif struct QUtf8BaseTraits { static const bool isTrusted = false; static const bool allowNonCharacters = true; static const bool skipAsciiHandling = false; static const int Error = -1; static const int EndOfString = -2; static bool isValidCharacter(uint u) { return int(u) >= 0; } static void appendByte(uchar *&ptr, uchar b) { *ptr++ = b; } static void appendByte(char8_t *&ptr, char8_t b) { *ptr++ = b; } static uchar peekByte(const uchar *ptr, qsizetype n = 0) { return ptr[n]; } static uchar peekByte(const char8_t *ptr, int n = 0) { return ptr[n]; } static qptrdiff availableBytes(const uchar *ptr, const uchar *end) { return end - ptr; } static qptrdiff availableBytes(const char8_t *ptr, const char8_t *end) { return end - ptr; } static void advanceByte(const uchar *&ptr, qsizetype n = 1) { ptr += n; } static void advanceByte(const char8_t *&ptr, int n = 1) { ptr += n; } static void appendUtf16(ushort *&ptr, ushort uc) { *ptr++ = uc; } static void appendUtf16(char16_t *&ptr, ushort uc) { *ptr++ = char16_t(uc); } static void appendUcs4(ushort *&ptr, uint uc) { appendUtf16(ptr, QChar::highSurrogate(uc)); appendUtf16(ptr, QChar::lowSurrogate(uc)); } static void appendUcs4(char16_t *&ptr, char32_t uc) { appendUtf16(ptr, QChar::highSurrogate(uc)); appendUtf16(ptr, QChar::lowSurrogate(uc)); } static ushort peekUtf16(const ushort *ptr, qsizetype n = 0) { return ptr[n]; } static ushort peekUtf16(const char16_t *ptr, int n = 0) { return ptr[n]; } static qptrdiff availableUtf16(const ushort *ptr, const ushort *end) { return end - ptr; } static qptrdiff availableUtf16(const char16_t *ptr, const char16_t *end) { return end - ptr; } static void advanceUtf16(const ushort *&ptr, qsizetype n = 1) { ptr += n; } static void advanceUtf16(const char16_t *&ptr, int n = 1) { ptr += n; } // it's possible to output to UCS-4 too static void appendUtf16(uint *&ptr, ushort uc) { *ptr++ = uc; } static void appendUtf16(char32_t *&ptr, ushort uc) { *ptr++ = char32_t(uc); } static void appendUcs4(uint *&ptr, uint uc) { *ptr++ = uc; } static void appendUcs4(char32_t *&ptr, uint uc) { *ptr++ = char32_t(uc); } }; namespace QUtf8Functions { /// returns 0 on success; errors can only happen if \a u is a surrogate: /// Error if \a u is a low surrogate; /// if \a u is a high surrogate, Error if the next isn't a low one, /// EndOfString if we run into the end of the string. template inline int toUtf8(ushort u, OutputPtr &dst, InputPtr &src, InputPtr end) { if (!Traits::skipAsciiHandling && u < 0x80) { // U+0000 to U+007F (US-ASCII) - one byte Traits::appendByte(dst, uchar(u)); return 0; } else if (u < 0x0800) { // U+0080 to U+07FF - two bytes // first of two bytes Traits::appendByte(dst, 0xc0 | uchar(u >> 6)); } else { if (!QChar::isSurrogate(u)) { // U+0800 to U+FFFF (except U+D800-U+DFFF) - three bytes if (!Traits::allowNonCharacters && QChar::isNonCharacter(u)) return Traits::Error; // first of three bytes Traits::appendByte(dst, 0xe0 | uchar(u >> 12)); } else { // U+10000 to U+10FFFF - four bytes // need to get one extra codepoint if (Traits::availableUtf16(src, end) == 0) return Traits::EndOfString; ushort low = Traits::peekUtf16(src); if (!QChar::isHighSurrogate(u)) return Traits::Error; if (!QChar::isLowSurrogate(low)) return Traits::Error; Traits::advanceUtf16(src); uint ucs4 = QChar::surrogateToUcs4(u, low); if (!Traits::allowNonCharacters && QChar::isNonCharacter(ucs4)) return Traits::Error; // first byte Traits::appendByte(dst, 0xf0 | (uchar(ucs4 >> 18) & 0xf)); // second of four bytes Traits::appendByte(dst, 0x80 | (uchar(ucs4 >> 12) & 0x3f)); // for the rest of the bytes u = ushort(ucs4); } // second to last byte Traits::appendByte(dst, 0x80 | (uchar(u >> 6) & 0x3f)); } // last byte Traits::appendByte(dst, 0x80 | (u & 0x3f)); return 0; } } static void objectContentToJson(const QCborMap& p, QByteArray &json, int indent, bool compact); static void arrayContentToJson(const QCborArray& a, QByteArray &json, int indent, bool compact); static inline uchar hexdig(uint u) { return (u < 0xa ? '0' + u : 'a' + u - 0xa); } static QByteArray escapedString(const QString &s) { // give it a minimum size to ensure the resize() below always adds enough space QByteArray ba(qMax(s.length(), 16), Qt::Uninitialized); uchar *cursor = reinterpret_cast(const_cast(ba.constData())); const uchar *ba_end = cursor + ba.length(); const ushort *src = reinterpret_cast(s.constBegin()); const ushort *const end = reinterpret_cast(s.constEnd()); while (src != end) { if (cursor >= ba_end - 6) { // ensure we have enough space int pos = cursor - (const uchar *)ba.constData(); ba.resize(ba.size()*2); cursor = (uchar *)ba.data() + pos; ba_end = (const uchar *)ba.constData() + ba.length(); } uint u = *src++; if (u < 0x80) { if (u < 0x20 || u == 0x22 || u == 0x5c) { *cursor++ = '\\'; switch (u) { case 0x22: *cursor++ = '"'; break; case 0x5c: *cursor++ = '\\'; break; case 0x8: *cursor++ = 'b'; break; case 0xc: *cursor++ = 'f'; break; case 0xa: *cursor++ = 'n'; break; case 0xd: *cursor++ = 'r'; break; case 0x9: *cursor++ = 't'; break; default: *cursor++ = 'u'; *cursor++ = '0'; *cursor++ = '0'; *cursor++ = hexdig(u>>4); *cursor++ = hexdig(u & 0xf); } } else { *cursor++ = (uchar)u; } } else if (QUtf8Functions::toUtf8(u, cursor, src, end) < 0) { // failed to get valid utf8 use JSON escape sequence *cursor++ = '\\'; *cursor++ = 'u'; *cursor++ = hexdig(u>>12 & 0x0f); *cursor++ = hexdig(u>>8 & 0x0f); *cursor++ = hexdig(u>>4 & 0x0f); *cursor++ = hexdig(u & 0x0f); } } ba.resize(cursor - (const uchar *)ba.constData()); return ba; } static void valueToJson(const QCborValue &v, QByteArray &json, int indent, bool compact) { QCborValue::Type type = v.type(); switch (type) { case QCborValue::True: json += "true"; break; case QCborValue::False: json += "false"; break; case QCborValue::Integer: json += QByteArray::number(v.toInteger()); break; case QCborValue::Double: { const double d = v.toDouble(); if (qIsFinite(d)) { QByteArray dstr; if ( compact ) { // prec is weird with 'g' so we emulate it QByteArray f = QByteArray::number(d, 'f', 3); QByteArray e = QByteArray::number(d, 'e', 3); dstr = e.size() < f.size() ? e : f; } else { dstr = QByteArray::number(d, 'g', QLocale::FloatingPointShortest); } if ( dstr.endsWith(".000") ) dstr = dstr.left(dstr.size()-4); json += dstr; } else { json += "null"; // +INF || -INF || NaN (see RFC4627#section2.4) } break; } case QCborValue::String: json += '"'; json += escapedString(v.toString()); json += '"'; break; case QCborValue::Array: json += compact ? "[" : "[\n"; arrayContentToJson(v.toArray(), json, indent + (compact ? 0 : 1), compact); json += QByteArray(4*indent, ' '); json += ']'; break; case QCborValue::Map: json += compact ? "{" : "{\n"; objectContentToJson(v.toMap(), json, indent + (compact ? 0 : 1), compact); json += QByteArray(4*indent, ' '); json += '}'; break; case QCborValue::Null: default: json += "null"; } } static void arrayContentToJson(const QCborArray& a, QByteArray &json, int indent, bool compact) { if ( a.empty() ) return; QByteArray indentString(4*indent, ' '); qsizetype i = 0; while (true) { json += indentString; valueToJson(a.at(i), json, indent, compact); if (++i == a.size()) { if (!compact) json += '\n'; break; } json += compact ? "," : ",\n"; } } static void objectContentToJson(const QCborMap& o, QByteArray &json, int indent, bool compact) { if ( o.empty() ) return; QByteArray indentString(4*indent, ' '); auto it = o.begin(); auto end = o.end(); while (true) { json += indentString; json += '"'; json += escapedString(it.key().toString()); json += compact ? "\":" : "\": "; valueToJson(it.value(), json, indent, compact); ++it; if ( it == end ) { if (!compact) json += '\n'; break; } json += compact ? "," : ",\n"; } } QByteArray glaxnimate::io::lottie::cbor_write_json(const QCborMap &o, bool compact) { QByteArray json; json += compact ? "{" : "{\n"; objectContentToJson(o, json, 0, compact); json += compact ? "}" : "}\n"; return json; } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/external/QtAppSetup/src/app/log/log_model.hpp000664 001750 001750 00000001604 14477652011 033251 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include #include "app/log/logger.hpp" namespace app::log { class LogModel : public QAbstractTableModel { Q_OBJECT public: enum Columns { Time, Source, SourceDetail, Message, Count }; LogModel(); void populate(const std::vector& lines); int rowCount(const QModelIndex &) const override; int columnCount(const QModelIndex &) const override; QVariant headerData(int section, Qt::Orientation orientation, int role) const override; QVariant data(const QModelIndex & index, int role) const override; private slots: void on_line(const LogLine& line); private: std::vector lines; }; } // namespace app::log mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/lottie/lottie_importer.hpp000664 001750 001750 00000120102 14477652011 032132 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include #include #include #include #include "lottie_private_common.hpp" #include "io/svg/svg_parser.hpp" #include "model/animation/join_animatables.hpp" namespace glaxnimate::io::lottie::detail { struct FontInfo { QString name; QString family; QString style; }; class LottieImporterState { public: LottieImporterState( model::Document* document, io::lottie::LottieFormat* format ) : document(document), format(format) {} void load(const QJsonObject& json) { load_version(json); load_meta(json["meta"]); main = document->assets()->compositions->values.insert(std::make_unique(document)); auto comps = load_assets(json["assets"].toArray()); load_fonts(json["fonts"]["list"].toArray()); load_composition(json, main); load_comps(comps); } private: void load_version(const QJsonObject& json) { if ( json.contains("v") ) { auto parts = json["v"].toString().split("."); if ( parts.size() == 3 ) { for ( int i = 0; i < 3; i++ ) version[i] = parts[i].toInt(); } } } bool animated(const QJsonObject& obj) { if ( obj.contains("a") ) return obj["a"].toInt(); if ( !obj["k"].isArray() ) return 0; auto karr = obj["k"].toArray(); return karr.size() > 0 && karr[0].isObject() && karr[0].toObject().contains("s"); } template auto make_node(model::Document* document) { auto ptr = std::make_unique(document); current_node = ptr.get(); return ptr; } void warning(QString str, const QJsonObject& json) { if ( json.contains("nm") ) str = json["nm"].toString() + ": " + str; emit format->warning(str); } void load_stretchable_animation_container(const QJsonObject& json, model::StretchableTime* animation) { animation->start_time.set(json["st"].toDouble()); animation->stretch.set(json["sr"].toDouble(1)); } void load_animation_container(const QJsonObject& json, model::AnimationContainer* animation) { animation->first_frame.set(json["ip"].toDouble()); animation->last_frame.set(json["op"].toDouble()); } void load_composition(const QJsonObject& json, model::Composition* composition) { this->composition = composition; invalid_indices.clear(); layer_indices.clear(); deferred.clear(); if ( composition != main ) { composition->width.set(main->width.get()); composition->height.set(main->height.get()); composition->fps.set(main->fps.get()); composition->animation->first_frame.set(main->animation->first_frame.get()); composition->animation->last_frame.set(main->animation->last_frame.get()); } if ( json.contains("fr") ) composition->fps.set(json["fr"].toDouble()); if ( json.contains("w") ) composition->width.set(json["w"].toInt()); if ( json.contains("h") ) composition->height.set(json["h"].toInt()); load_animation_container(json, composition->animation.get()); load_basic(json, composition); { std::set referenced; std::vector layer_jsons; auto layer_array = json["layers"].toArray(); layer_jsons.reserve(layer_array.size()); for ( auto val : layer_array ) { QJsonObject obj = val.toObject(); if ( obj.contains("parent") ) referenced.insert(obj["parent"].toInt()); layer_array.push_back(obj); } for ( auto layer : json["layers"].toArray() ) create_layer(layer.toObject(), referenced); } auto deferred_layers = std::move(deferred); deferred.clear(); for ( const auto& pair: deferred_layers ) load_layer(pair.second, static_cast(pair.first)); } void load_visibility(model::VisualNode* node, const QJsonObject& json) { if ( json.contains("hd") && json["hd"].toBool() ) node->visible.set(false); } void create_layer(const QJsonObject& json, std::set& referenced) { int index = json["ind"].toInt(); if ( !json.contains("ty") || !json["ty"].isDouble() ) { warning(QObject::tr("Missing layer type for %1").arg(index), json); invalid_indices.insert(index); return; } int ty = json["ty"].toInt(); std::unique_ptr inner_shape; bool start_mask = json["td"].toInt(); start_mask = false; if ( ty == 0 ) { inner_shape = load_precomp_layer(json); auto op = this->composition->animation->last_frame.get(); if ( json.contains("parent") || referenced.count(index) || json["ip"].toDouble() != 0 || json["op"].toDouble(op) != op || start_mask ) { auto layer = make_node(document); layer->name.set(inner_shape->name.get()); layer->shapes.insert(std::move(inner_shape), 0); layer_indices[index] = layer.get(); deferred.emplace_back(layer.get(), json); inner_shape = std::move(layer); } } else { auto layer = std::make_unique(document); layer_indices[index] = layer.get(); deferred.emplace_back(layer.get(), json); inner_shape = std::move(layer); } if ( start_mask ) { auto layer = std::make_unique(document); mask = layer.get(); layer->name.set(json["nm"].toString()); layer->shapes.insert(std::move(inner_shape), 0); composition->shapes.insert(std::move(layer), 0); } else { auto tt = json["tt"].toInt(); if ( mask && tt ) { mask->shapes.insert(std::move(inner_shape), 1); auto mode = model::MaskSettings::MaskMode((tt + 1) / 2); mask->mask->mask.set(mode); mask->mask->inverted.set(tt > 0 && tt % 2 == 0); } else { composition->shapes.insert(std::move(inner_shape), 0); } mask = nullptr; } } std::unique_ptr load_precomp_layer(const QJsonObject& json) { auto props = load_basic_setup(json); auto precomp = make_node(document); load_visibility(precomp.get(), json); load_stretchable_animation_container(json, precomp->timing.get()); for ( const FieldInfo& field : fields["__Layer__"] ) props.erase(field.lottie); for ( const QMetaObject* mo = precomp->metaObject(); mo; mo = mo->superClass() ) load_properties( precomp.get(), fields[model::detail::naked_type_name(mo)], json, props ); auto comp = precomp_ids[json["refId"].toString()]; if ( comp ) { precomp->composition.set(comp); if ( !json.contains("nm") ) precomp->name.set(comp->name.get()); } props.erase("w"); props.erase("h"); precomp->size.set(QSize( json["w"].toInt(), json["h"].toInt() )); load_transform(json["ks"].toObject(), precomp->transform.get(), &precomp->opacity); return precomp; } void load_mask(const QJsonObject& json, model::Group* group) { auto fill = make_node(document); fill->color.set(QColor(255, 255, 255)); document->set_best_name(fill.get()); load_animated(&fill->opacity, json["o"], {}); group->shapes.insert(std::move(fill)); auto j_stroke = json["x"].toObject(); if ( animated(j_stroke) || j_stroke["k"].toDouble() != 0 ) { auto stroke = make_node(document); stroke->color.set(QColor(255, 255, 255)); load_animated(&stroke->opacity, json["o"], {}); document->set_best_name(stroke.get()); load_animated(&stroke->width, json["x"], {}); group->shapes.insert(std::move(stroke)); } auto path = make_node(document); document->set_best_name(path.get()); load_animated(&path->shape, json["pt"], {}); group->shapes.insert(std::move(path)); } void load_layer(const QJsonObject& json, model::Layer* layer) { current_node = current_layer = layer; if ( json.contains("parent") ) { int parent_index = json["parent"].toInt(); if ( invalid_indices.count(parent_index) ) { warning( QObject::tr("Cannot use %1 as parent as it couldn't be loaded") .arg(parent_index), json ); } else { auto it = layer_indices.find(parent_index); if ( it == layer_indices.end() ) { warning( QObject::tr("Invalid parent layer %1") .arg(parent_index), json ); } else { auto parent_layer = layer->docnode_parent()->cast(); if ( parent_layer && parent_layer->mask->has_mask() ) parent_layer->parent.set(*it); else layer->parent.set(*it); } } } if ( !json.contains("ip") && !json.contains("op") ) { auto comp = layer->owner_composition(); layer->animation->first_frame.set(comp->animation->first_frame.get()); layer->animation->last_frame.set(comp->animation->last_frame.get()); } else { load_animation_container(json, layer->animation.get()); } if ( !layer->shapes.empty() ) return; auto props = load_basic_setup(json); props.erase("ind"); load_properties(layer, fields["DocumentNode"], json, props); load_properties(layer, fields["__Layer__"], json, props); load_transform(json["ks"].toObject(), layer->transform.get(), &layer->opacity); load_visibility(layer, json); model::Layer* target = layer; props.erase("hasMask"); props.erase("masksProperties"); if ( json.contains("masksProperties") ) { auto masks = json["masksProperties"].toArray(); if ( !masks.empty() ) { layer->mask->mask.set(model::MaskSettings::Alpha); auto clip_p = make_node(document); auto clip = clip_p.get(); layer->shapes.insert(std::move(clip_p)); auto shape_target = std::make_unique(document); target = shape_target.get(); shape_target->name.set(layer->name.get()); shape_target->animation->first_frame.set(layer->animation->first_frame.get()); shape_target->animation->last_frame.set(layer->animation->last_frame.get()); layer->shapes.insert(std::move(shape_target)); document->set_best_name(clip, QObject::tr("Clip")); if ( masks.size() == 1 ) { load_mask(masks[0].toObject(), clip); } else { for ( const auto& mask : masks ) { auto clip_group_p = make_node(document); auto clip_group = clip_group_p.get(); clip->shapes.insert(std::move(clip_group_p)); document->set_best_name(clip_group, QObject::tr("Clip")); load_mask(mask.toObject(), clip_group); } } } } switch ( json["ty"].toInt(-1) ) { case 0: // precomp break; case 1: // solid color { props.erase("sw"); props.erase("sh"); props.erase("sc"); auto color_name = json["sc"].toString(); auto fill = std::make_unique(document); fill->color.set(svg::parse_color(color_name)); target->shapes.insert(std::move(fill)); auto rect = std::make_unique(document); auto w = json["sw"].toDouble(); auto h = json["sh"].toDouble(); rect->size.set(QSizeF(w, h)); rect->position.set(QPointF(w/2, h/2)); target->shapes.insert(std::move(rect)); break; } case 2: // image layer { auto image = make_node(document); image->image.set(bitmap_ids[json["refId"].toString()]); target->shapes.insert(std::move(image)); props.erase("refId"); break; } case 3: // empty break; case 4: // shape props.erase("shapes"); load_shapes(target->shapes, json["shapes"].toArray()); break; case 5: // text props.erase("t"); load_text_layer(target->shapes, json["t"].toObject()); break; default: { QString type = json["ty"].toVariant().toString(); auto it = unsupported_layers.find(json["ty"].toInt()); if ( it != unsupported_layers.end() ) type = *it; warning(QObject::tr("Unsupported layer of type %1").arg(type), json); } } load_basic_check(props); } void load_shapes(model::ShapeListProperty& shapes, const QJsonArray& jshapes) { deferred.clear(); for ( int i = jshapes.size() - 1; i >= 0; i-- ) create_shape(jshapes[i].toObject(), shapes); auto deferred_shapes = std::move(deferred); deferred.clear(); for ( const auto& pair: deferred_shapes ) load_shape(pair.second, static_cast(pair.first)); } void create_shape(const QJsonObject& json, model::ShapeListProperty& shapes) { if ( !json.contains("ty") || !json["ty"].isString() ) { warning(QObject::tr("Missing shape type"), json); return; } QString base_type = json["ty"].toString(); QString type = shape_types.key(base_type); if ( type.isEmpty() ) { type = shape_types_repeat[base_type]; if ( type.isEmpty() ) { // "mm" is marked as unsupported by lottie and it appears in several animations so we ignore the warning if ( base_type != "mm" ) warning(QObject::tr("Unsupported shape type %1").arg(json["ty"].toString()), json); return; } } model::ShapeElement* shape = static_cast( model::Factory::instance().build(type, document) ); if ( !shape ) { warning(QObject::tr("Unsupported shape type %1").arg(json["ty"].toString()), json); return; } deferred.emplace_back(shape, json); shapes.insert(std::unique_ptr(shape), shapes.size()); } std::set load_basic_setup(const QJsonObject& json_obj) { std::set props; for ( auto it = json_obj.begin(); it != json_obj.end(); ++it ) props.insert(it.key()); return props; } void load_basic_check(const std::set& props) { for ( const auto& not_found : props ) { emit format->information( QObject::tr("Unknown field %2%1") .arg(not_found) .arg(object_error_string(nullptr)) ); } } void load_basic(const QJsonObject& json_obj, model::Object* obj) { std::set props = load_basic_setup(json_obj); for ( const QMetaObject* mo = obj->metaObject(); mo; mo = mo->superClass() ) load_properties( obj, fields[model::detail::naked_type_name(mo)], json_obj, props ); load_basic_check(props); } void load_basic(const QJsonObject& json_obj, model::DocumentNode* obj) { load_basic(json_obj, static_cast(obj)); if ( obj->name.get().isEmpty() ) document->set_best_name(obj); } void load_transform(const QJsonObject& transform, model::Transform* tf, model::AnimatableBase* opacity) { load_basic(transform, tf); if ( transform.contains("o") && opacity ) load_animated(opacity, transform["o"], FloatMult(100)); if ( transform.contains("p") ) { auto pos = transform["p"].toObject(); if ( pos.contains("x") && pos.contains("y") ) { model::Document dummydoc(""); model::Object dummy(&dummydoc); model::AnimatedProperty px(&dummy, "", 0); model::AnimatedProperty py(&dummy, "", 0); load_animated(&px, pos["x"], {}); load_animated(&py, pos["y"], {}); model::JoinAnimatables join({&px, &py}); join.apply_to(&tf->position, [](float x, float y) -> QPointF { return QPointF(x, y); }, &px, &py); } else { load_animated(&tf->position, transform["p"], {}); } } } void load_styler(model::Styler* styler, const QJsonObject& json_obj) { load_visibility(styler, json_obj); std::set props = load_basic_setup(json_obj); for ( const QMetaObject* mo = styler->metaObject(); mo; mo = mo->superClass() ) load_properties( styler, fields[model::detail::naked_type_name(mo)], json_obj, props ); if ( json_obj.contains("fillEnabled") ) styler->visible.set(json_obj["fillEnabled"].toBool()); if ( json_obj["ty"].toString().startsWith('g') ) { auto gradient = document->assets()->gradients->values.insert(std::make_unique(document)); styler->use.set(gradient); auto colors = document->assets()->gradient_colors->values.insert(std::make_unique(document)); gradient->colors.set(colors); load_properties(gradient, fields["Gradient"], json_obj, props); if ( json_obj.contains("h") || json_obj.contains("a") ) { model::Document dummydoc(""); model::Object dummy(&dummydoc); model::AnimatedProperty length(&dummy, "", 0); model::AnimatedProperty angle(&dummy, "", 0); if ( json_obj.contains("h") ) load_animated(&length, json_obj["h"], {}); if ( json_obj.contains("a") ) load_animated(&angle, json_obj["a"], {}); glaxnimate::model::JoinAnimatables join({&gradient->start_point, &gradient->end_point, &length, &angle}); join.apply_to(&gradient->highlight, [](const QPointF& p, const QPointF& e, float length, float angle) -> QPointF { angle = math::deg2rad(angle + 90); length = math::length(e - p) * length / 100; return p + math::from_polar(length, angle); }, &gradient->start_point, &gradient->end_point, &length, &angle); } else { gradient->highlight.set(gradient->start_point.get()); } auto jcolors = json_obj["g"].toObject(); load_animated(&colors->colors, jcolors["k"], GradientLoad{jcolors["p"].toInt()}); } else { load_animated(&styler->color, json_obj["c"], {}); } if ( styler->name.get().isEmpty() ) document->set_best_name(styler); load_basic_check(props); } void load_shape(const QJsonObject& json, model::ShapeElement* shape) { current_node = shape; if ( auto styler = shape->cast() ) return load_styler(styler, json); load_basic(json, shape); load_visibility(shape, json); QString type_name = shape->type_name(); if ( type_name == "Group" ) { auto gr = static_cast(shape); QJsonArray shapes = json["it"].toArray(); QJsonObject transform; for ( int i = shapes.size() - 1; i >= 0; i-- ) { QJsonObject shi = shapes[i].toObject(); if ( shi["ty"] == "tr" ) { transform = shi; transform.remove("ty"); shapes.erase(shapes.begin() + i); break; } } if ( !transform.empty() ) load_transform(transform, gr->transform.get(), &gr->opacity); load_shapes(gr->shapes, shapes); } else if ( type_name == "Repeater" ) { auto repeater = static_cast(shape); QJsonObject transform = json["tr"].toObject(); load_animated(&repeater->start_opacity, transform["so"], FloatMult(100)); load_animated(&repeater->end_opacity, transform["eo"], FloatMult(100)); transform.remove("so"); transform.remove("eo"); transform.remove("ty"); load_transform(transform, repeater->transform.get(), nullptr); } else if ( version[0] < 5 && type_name == "Path" && json.contains("closed") ) { auto path = static_cast(shape); path->shape.set_closed(json["closed"].toBool()); } } void load_properties( model::Object* obj, const QVector& fields, const QJsonObject& json_obj, std::set& avail_obj_keys ) { for ( const FieldInfo& field : fields ) { avail_obj_keys.erase(field.lottie); if ( field.mode >= Ignored || !json_obj.contains(field.lottie) ) continue; model::BaseProperty * prop = obj->get_property(field.name); if ( !prop ) { logger.stream() << field.name << "is not a property"; continue; } if ( prop->traits().flags & model::PropertyTraits::Animated ) { load_animated(static_cast(prop), json_obj[field.lottie], field.transform); } else if ( field.mode == AnimatedToStatic ) { load_static(prop, json_obj[field.lottie], field.transform); } else { load_value(prop, json_obj[field.lottie], field.transform); } } } template bool compound_value_2d_raw(const QJsonValue& val, T& out, double mul = 1) { QJsonArray arr = val.toArray(); if ( arr.size() < 2 || !arr[0].isDouble() || !arr[1].isDouble() ) return false; out = T(arr[0].toDouble() * mul, arr[1].toDouble() * mul); return true; } template std::optional compound_value_2d(const QJsonValue& val, double mul = 1) { T v; if ( !compound_value_2d_raw(val, v, mul) ) return {}; return QVariant::fromValue(v); } bool is_scalar(model::BaseProperty * prop) { switch ( prop->traits().type ) { case model::PropertyTraits::Bool: case model::PropertyTraits::Int: case model::PropertyTraits::Float: case model::PropertyTraits::String: case model::PropertyTraits::Uuid: case model::PropertyTraits::Enum: case model::PropertyTraits::Bezier: return true; default: return false; } } bool compound_value_color(const QJsonValue& val, QColor& out) { QJsonArray arr = val.toArray(); if ( version[0] < 5 ) { if ( arr.size() == 3 ) out = QColor::fromRgb( arr[0].toInt(), arr[1].toInt(), arr[2].toInt() ); else if ( arr.size() == 4 ) out = QColor::fromRgb( arr[0].toInt(), arr[1].toInt(), arr[2].toInt(), qMin(255, arr[3].toInt()) ); else return false; return true; } if ( arr.size() == 3 ) out = QColor::fromRgbF( arr[0].toDouble(), arr[1].toDouble(), arr[2].toDouble() ); else if ( arr.size() == 4 ) out = QColor::fromRgbF( arr[0].toDouble(), arr[1].toDouble(), arr[2].toDouble(), qMin(1., arr[3].toDouble()) ); else return false; return true; } std::optional value_to_variant(model::BaseProperty * prop, const QJsonValue& val) { switch ( prop->traits().type ) { case model::PropertyTraits::Bool: case model::PropertyTraits::Int: case model::PropertyTraits::Float: case model::PropertyTraits::String: return val.toVariant(); case model::PropertyTraits::Uuid: { QUuid uuid = val.toVariant().toUuid(); if ( uuid.isNull() ) uuid = QUuid::createUuid(); return QVariant::fromValue(uuid); } case model::PropertyTraits::Point: return compound_value_2d(val); case model::PropertyTraits::Size: return compound_value_2d(val); case model::PropertyTraits::Scale: return compound_value_2d(val, 0.01); case model::PropertyTraits::Color: { QColor col; if ( compound_value_color(val, col) ) return QVariant::fromValue(col); return {}; } case model::PropertyTraits::Bezier: { QJsonObject jsbez = val.toObject(); math::bezier::Bezier bezier; bezier.set_closed(jsbez["c"].toBool()); QJsonArray pos = jsbez["v"].toArray(); QJsonArray tan_in = jsbez["i"].toArray(); QJsonArray tan_out = jsbez["o"].toArray(); int sz = std::min(pos.size(), std::min(tan_in.size(), tan_out.size())); for ( int i = 0; i < sz; i++ ) { QPointF p, ti, to; if ( !compound_value_2d_raw(pos[i], p) ) { emit format->warning( QObject::tr("Invalid bezier point %1 in %2") .arg(i) .arg(property_error_string(prop)) ); continue; } compound_value_2d_raw(tan_in[i], ti); compound_value_2d_raw(tan_out[i], to); bezier.push_back(math::bezier::Point::from_relative(p, ti, to)); } return QVariant::fromValue(bezier); } case model::PropertyTraits::Enum: return val.toInt(); case model::PropertyTraits::Gradient: return val.toArray().toVariantList(); default: logger.stream(app::log::Error) << "Unsupported type" << prop->traits().type << "for" << property_error_string(prop); return {}; } } QString object_error_string(model::Object* ignore) { QString str; if ( current_layer && current_node != current_layer ) str = "(" + current_layer->object_name() + ") "; if ( current_node && current_node != ignore ) str += current_node->object_name() + "."; return str; } QString property_error_string(model::BaseProperty * prop) { QString str = object_error_string(prop->object()); str += prop->object()->object_name() + "." + prop->name(); return str; } void load_value(model::BaseProperty * prop, const QJsonValue& val, const TransformFunc& trans) { auto v = value_to_variant(prop, val); if ( !v || !prop->set_value(trans.from_lottie(*v, 0)) ) emit format->warning(QObject::tr("Invalid value for %1").arg(prop->name())); } void load_static(model::BaseProperty * prop, const QJsonValue& val, const TransformFunc& trans) { if ( val.isObject() ) { QJsonObject obj = val.toObject(); if ( obj.contains("k") ) { load_value(prop, obj["k"], trans); return; } } load_value(prop, val, trans); } void load_animated(model::AnimatableBase* prop, const QJsonValue& val, const TransformFunc& trans) { if ( !val.isObject() ) { emit format->warning(QObject::tr("Invalid value for %1").arg(property_error_string(prop))); return; } QJsonObject obj = val.toObject(); if ( !obj.contains("k") ) { emit format->warning(QObject::tr("Invalid value for %1").arg(property_error_string(prop))); return; } if ( animated(obj) ) { if ( !obj["k"].isArray() ) { emit format->warning(QObject::tr("Invalid keyframes for %1").arg(property_error_string(prop))); return; } bool position = prop->traits().type == model::PropertyTraits::Point; auto karr = obj["k"].toArray(); for ( int i = 0; i < karr.size(); i++ ) { QJsonValue jkf = karr[i]; model::FrameTime time = jkf["t"].toDouble(); QJsonValue s = jkf["s"]; if ( s.isUndefined() && i == karr.size() - 1 && i > 0 ) s = karr[i-1].toObject()["e"]; if ( s.isArray() && is_scalar(prop) ) s = s.toArray()[0]; auto v = value_to_variant(prop, s); model::KeyframeBase* kf = nullptr; if ( v ) kf = prop->set_keyframe(time, trans.from_lottie(*v, time)); if ( kf ) { kf->set_transition({ keyframe_bezier_handle(jkf["o"]), keyframe_bezier_handle(jkf["i"]), bool(jkf["h"].toInt()) }); if ( position ) { auto pkf = static_cast*>(kf); QPointF tan_out; compound_value_2d_raw(jkf["to"], tan_out); tan_out += pkf->get(); QPointF tan_in; if ( i > 0 ) compound_value_2d_raw(karr[i-1].toObject()["ti"], tan_in); tan_in += pkf->get(); pkf->set_point({pkf->get(), tan_in, tan_out}); } } else { QString value; if ( !v ) { value = QObject::tr("(null)"); } else { value = v->toString(); if ( value == "" ) value = QObject::tr("(empty)"); value += " "; #if QT_VERSION_MAJOR >= 6 value += QMetaType(v->userType()).name(); #else value += QMetaType::typeName(v->userType()); #endif } emit format->warning(QObject::tr("Cannot load keyframe at %1 for %2 with value %3") .arg(time).arg(property_error_string(prop)).arg(value) ); } } } else { load_value(prop, obj["k"], trans); } } qreal keyframe_bezier_handle_comp(const QJsonValue& comp) { if ( comp.isArray() ) return comp[0].toDouble(); return comp.toDouble(); } QPointF keyframe_bezier_handle(const QJsonValue& val) { return {keyframe_bezier_handle_comp(val["x"]), keyframe_bezier_handle_comp(val["y"])}; } std::vector> load_assets(const QJsonArray& assets) { std::vector> comps; for ( const auto& assetv : assets ) { QJsonObject asset = assetv.toObject(); if ( asset.contains("e") && asset.contains("p") && asset.contains("w") ) load_asset_bitmap(asset); else if ( asset.contains("layers") ) comps.emplace_back(asset, load_asset_precomp(asset)); } return comps; } void load_comps(const std::vector>& comps) { for ( const auto& p : comps ) load_composition(p.first, p.second); } void load_asset_bitmap(const QJsonObject& asset) { auto bmp = document->assets()->images->values.insert(std::make_unique(document)); QString id = asset["id"].toString(); if ( bitmap_ids.count(id) ) format->warning(io::lottie::LottieFormat::tr("Duplicate Bitmap ID: %1").arg(id)); bitmap_ids[id] = bmp; if ( asset.contains("nm") ) bmp->name.set(asset["nm"].toString()); if ( asset["e"].toInt() ) { bmp->from_url(asset["p"].toString()); } else { QString path = asset["u"].toString(); if ( path.contains("://") ) { path += asset["p"].toString(); bmp->from_url(path); } else { QDir dir(path); bmp->from_file(dir.filePath(asset["p"].toString())); } } } model::Composition* load_asset_precomp(QJsonObject asset) { auto comp = document->assets()->compositions->values.insert(std::make_unique(document)); QString id = asset["id"].toString(); if ( precomp_ids.count(id) ) format->warning(io::lottie::LottieFormat::tr("Duplicate Composition ID: %1").arg(id)); precomp_ids[id] = comp; comp->name.set(id); return comp; } enum class FontOrigin { System = 0, CssUrl = 1, ScriptUrl = 2, FontUrl = 3, }; void load_fonts(const QJsonArray& fonts_arr) { for ( const auto& fontv : fonts_arr ) { QJsonObject font = fontv.toObject(); FontInfo info; info.family = font["fFamily"].toString(); info.name = font["fName"].toString(); info.style = font["fStyle"].toString(); fonts[info.name] = info; FontOrigin font_origin = FontOrigin::System; if ( font.contains("origin") ) { font_origin = FontOrigin(font["origin"].toInt()); } else if ( font.contains("fOrigin") ) { switch ( (font["fOrigin"].toString() + " ")[0].toLatin1() ) { case 'n': font_origin = FontOrigin::System; break; case 'g': font_origin = FontOrigin::CssUrl; break; case 't': font_origin = FontOrigin::ScriptUrl; break; case 'p': font_origin = FontOrigin::FontUrl; break; } } switch ( font_origin ) { case FontOrigin::System: // nothing to do break; case FontOrigin::CssUrl: case FontOrigin::FontUrl: // Queue dynamic font loading document->add_pending_asset(info.family, font["fPath"].toString()); break; case FontOrigin::ScriptUrl: // idk how these work break; } } } FontInfo get_font(const QString& name) { auto it = fonts.find(name); if ( it != fonts.end() ) return *it; return {"", name, "Regular"}; } void load_text_layer(model::ShapeListProperty& shapes, const QJsonObject& text) { // TODO "a" "m" "p" model::Group* prev = nullptr; model::KeyframeTransition jump({}, {}, true); for ( const auto& v : text["d"].toObject()["k"].toArray() ) { auto keyframe = v.toObject(); qreal time = keyframe["t"].toDouble(); auto text_document = keyframe["s"].toObject(); auto group = std::make_unique(document); if ( time > 0 ) group->opacity.set_keyframe(0, 0)->set_transition(jump); group->opacity.set_keyframe(time, 1)->set_transition(jump); if ( prev ) prev->opacity.set_keyframe(time, 0)->set_transition(jump); prev = group.get(); auto fill = std::make_unique(document); QColor color; compound_value_color(text_document["fc"], color); fill->color.set(color); group->shapes.insert(std::move(fill)); auto shape = make_node(document); auto font = get_font(text_document["f"].toString()); shape->font->family.set(font.family); shape->font->style.set(font.style); shape->font->size.set(text_document["s"].toDouble()); shape->text.set(text_document["t"].toString().replace('\r', '\n')); group->shapes.insert(std::move(shape)); shapes.insert(std::move(group), shapes.size()); } } void load_meta(const QJsonValue& meta) { if ( !meta.isObject() ) return; document->info().author = meta["a"].toString(); document->info().description = meta["d"].toString(); for ( const auto& kw : meta["k"].toArray() ) document->info().keywords.push_back(kw.toString()); } model::Document* document; io::lottie::LottieFormat* format; QMap layer_indices; std::set invalid_indices; std::vector> deferred; model::Composition* composition = nullptr; app::log::Log logger{"Lottie Import"}; QMap bitmap_ids; QMap precomp_ids; QMap fonts; model::Layer* mask = nullptr; model::DocumentNode* current_node = nullptr; model::Layer* current_layer = nullptr; std::array version = {5,5,1}; model::Composition* main = nullptr; }; } // namespace glaxnimate::io::lottie::detail src/modules/glaxnimate/glaxnimate/external/QtAppSetup/src/app/widgets/clearable_keysequence_edit.cpp000664 001750 001750 00000002421 14477652011 037427 0ustar00ddennedyddennedy000000 000000 mlt-7.22.0/* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "clearable_keysequence_edit.hpp" #include "ui_clearable_keysequence_edit.h" #include class ClearableKeysequenceEdit::Private { public: Ui::ClearableKeysequenceEdit ui; QKeySequence default_ks; }; ClearableKeysequenceEdit::ClearableKeysequenceEdit(QWidget* parent) : QWidget(parent), d(std::make_unique()) { d->ui.setupUi(this); } ClearableKeysequenceEdit::~ClearableKeysequenceEdit() = default; void ClearableKeysequenceEdit::changeEvent ( QEvent* e ) { QWidget::changeEvent(e); if ( e->type() == QEvent::LanguageChange) { d->ui.retranslateUi(this); } } void ClearableKeysequenceEdit::set_key_sequence(const QKeySequence& ks) { d->ui.sequence_edit->setKeySequence(ks); } void ClearableKeysequenceEdit::set_default_key_sequence(const QKeySequence& ks) { d->default_ks = ks; } QKeySequence ClearableKeysequenceEdit::key_sequence() const { return d->ui.sequence_edit->keySequence(); } void ClearableKeysequenceEdit::use_default() { d->ui.sequence_edit->setKeySequence(d->default_ks); } void ClearableKeysequenceEdit::use_nothing() { d->ui.sequence_edit->setKeySequence({}); } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/assets/brush_style.cpp000664 001750 001750 00000000712 14477652011 031746 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "brush_style.hpp" QIcon glaxnimate::model::BrushStyle::instance_icon() const { if ( icon.isNull() ) { icon = QPixmap(32, 32); fill_icon(icon); } return icon; } QBrush glaxnimate::model::BrushStyle::constrained_brush_style(FrameTime t, const QRectF& ) const { return brush_style(t); } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/options.hpp000664 001750 001750 00000000655 14477652011 027116 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include namespace glaxnimate::io { class ImportExport; struct Options { ImportExport* format = nullptr; QDir path; QString filename; QVariantMap settings; void clear() { *this = {}; } }; } // namespace glaxnimate::io mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/rive/type_system.cpp000664 001750 001750 00000003202 14477652011 030737 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "type_system.hpp" const glaxnimate::io::rive::ObjectDefinition * glaxnimate::io::rive::TypeSystem::get_definition(glaxnimate::io::rive::TypeId type_id) { auto it = defined_objects.find(type_id); if ( it == defined_objects.end() ) { emit type_not_found(int(type_id)); return nullptr; } return &it->second; } bool glaxnimate::io::rive::TypeSystem::gather_definitions(glaxnimate::io::rive::ObjectType& type, glaxnimate::io::rive::TypeId type_id) { auto* def = get_definition(type_id); if ( !def ) return false; type.definitions.push_back(def); if ( def->extends != TypeId::NoType ) { if ( !gather_definitions(type, def->extends) ) return false; } for ( const auto& prop : def->properties ) { type.property_from_name[prop.name] = ∝ type.property_from_id[prop.id] = ∝ type.properties.push_back(&prop); } return true; } const glaxnimate::io::rive::ObjectType * glaxnimate::io::rive::TypeSystem::get_type(glaxnimate::io::rive::TypeId type_id) { auto it = types.find(type_id); if ( it != types.end() ) return &it->second; ObjectType type(type_id); if ( !gather_definitions(type, type_id) ) return nullptr; return &types.emplace(type_id, std::move(type)).first->second; } QString glaxnimate::io::rive::TypeSystem::type_name(glaxnimate::io::rive::TypeId type_id) { if ( auto def = get_definition(type_id) ) return def->name; return {}; } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/rive/rive_serializer.cpp000664 001750 001750 00000006067 14477652011 031564 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "rive_serializer.hpp" #include glaxnimate::io::rive::RiveSerializer::RiveSerializer(QIODevice* file) : stream(file) { } void glaxnimate::io::rive::RiveSerializer::write_header(int vmaj, int vmin, glaxnimate::io::rive::Identifier file_id) { stream.write("RIVE"); stream.write_uint_leb128(vmaj); stream.write_uint_leb128(vmin); stream.write_uint_leb128(file_id); } void glaxnimate::io::rive::RiveSerializer::write_property_table(const glaxnimate::io::rive::PropertyTable& properties) { for ( const auto& p: properties ) stream.write_uint_leb128(p.first); stream.write_byte(0); quint32 current_int = 0; quint32 bit = 0; for ( const auto& p: properties ) { int type = 0; switch ( p.second ) { case PropertyType::VarUint: case PropertyType::Bool: type = 0; break; case PropertyType::Bytes: case PropertyType::String: type = 1; break; case PropertyType::Float: type = 2; break; case PropertyType::Color: type = 3; break; } current_int <<= 2; current_int |= type; bit += 2; if ( bit == 8 ) { stream.write_uint32_le(current_int); bit = 0; current_int = 0; } } if ( bit != 0 ) stream.write_uint32_le(current_int); } void glaxnimate::io::rive::RiveSerializer::write_object(const glaxnimate::io::rive::Object& output) { stream.write_uint_leb128(VarUint(output.type().id)); for ( const auto& p : output.properties() ) { if ( !p.second.isValid() || (p.second.userType() == QMetaType::QString && p.second.toString().isEmpty()) ) continue; stream.write_uint_leb128(p.first->id); write_property_value(p.first->type, p.second); } stream.write_byte(0); } void glaxnimate::io::rive::RiveSerializer::write_property_value(glaxnimate::io::rive::PropertyType id, const QVariant& value) { switch ( id ) { case PropertyType::Bool: stream.write_byte(value.toBool()); return; case PropertyType::VarUint: stream.write_uint_leb128(value.value()); return; case PropertyType::Color: stream.write_uint32_le(value.value().rgba()); return; case PropertyType::Bytes: { auto data = value.toByteArray(); stream.write_uint_leb128(data.size()); stream.write(data); return; } case PropertyType::String: { auto data = value.toString().toUtf8(); stream.write_uint_leb128(data.size()); stream.write(data); return; } case PropertyType::Float: stream.write_float32_le(value.toFloat()); } } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/rive/rive_html_format.hpp000664 001750 001750 00000001401 14477652011 031717 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include "io/base.hpp" namespace glaxnimate::io::rive { class RiveHtmlFormat : public ImportExport { Q_OBJECT public: QString slug() const override { return "rive_html"; } QString name() const override { return tr("RIVE HTML Preview"); } QStringList extensions() const override { return {"html", "htm"}; } bool can_save() const override { return true; } bool can_open() const override { return false; } private: bool on_save(QIODevice& file, const QString& filename, model::Composition* comp, const QVariantMap& setting_values) override; }; } // namespace glaxnimate::io::rive mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/utils/regexp.hpp000664 001750 001750 00000002746 14477652011 027451 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include namespace glaxnimate::utils::regexp { namespace detail { class EndIterator {}; class NotDumbIterator { public: NotDumbIterator(QRegularExpressionMatchIterator dumb) : dumb(std::move(dumb)) { ++*this; } NotDumbIterator& operator++() { end = !this->dumb.hasNext(); if ( !end ) match = this->dumb.next(); return *this; } QRegularExpressionMatch& operator*() { return match; } bool operator!=(const NotDumbIterator& oth) const { return end != oth.end; }; bool operator!=(const EndIterator&) const { return !end; } private: QRegularExpressionMatchIterator dumb; QRegularExpressionMatch match; bool end = false; }; } // namespace detail struct MatchRange { const detail::NotDumbIterator& begin() const { return begin_iter; } const detail::EndIterator& end() const { return end_iter; } detail::NotDumbIterator begin_iter; detail::EndIterator end_iter = {}; }; inline MatchRange find_all(const QRegularExpression& pattern, const QString& subject) { return MatchRange{ detail::NotDumbIterator(pattern.globalMatch(subject)) }; } } // namespace glaxnimate::utils::regexp mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/shapes/image.cpp000664 001750 001750 00000005122 14477652011 030446 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "image.hpp" #include "model/document.hpp" #include "model/assets/assets.hpp" GLAXNIMATE_OBJECT_IMPL(glaxnimate::model::Image) glaxnimate::model::Image::Image(glaxnimate::model::Document* doc) : ShapeElement(doc) { connect(transform.get(), &Object::property_changed, this, &Image::on_transform_matrix_changed); } bool glaxnimate::model::Image::is_valid_image(glaxnimate::model::DocumentNode* node) const { return document()->assets()->images->values.is_valid_reference_value(node, false); } std::vector glaxnimate::model::Image::valid_images() const { return document()->assets()->images->values.valid_reference_values(false); } QRectF glaxnimate::model::Image::local_bounding_rect(glaxnimate::model::FrameTime) const { if ( !image.get() ) return {}; return QRectF(0, 0, image->width.get(), image->height.get()); } void glaxnimate::model::Image::on_paint(QPainter* p, glaxnimate::model::FrameTime, glaxnimate::model::VisualNode::PaintMode, glaxnimate::model::Modifier*) const { if ( image.get() ) image->paint(p); } void glaxnimate::model::Image::on_image_changed(glaxnimate::model::Bitmap* new_use, glaxnimate::model::Bitmap* old_use) { if ( old_use ) { disconnect(old_use, &Bitmap::loaded, this, &Image::on_update_image); } if ( new_use ) { connect(new_use, &Bitmap::loaded, this, &Image::on_update_image); } } void glaxnimate::model::Image::on_update_image() { emit property_changed(&image, {}); } QTransform glaxnimate::model::Image::local_transform_matrix(glaxnimate::model::FrameTime t) const { return transform->transform_matrix(t); } void glaxnimate::model::Image::on_transform_matrix_changed() { propagate_bounding_rect_changed(); emit local_transform_matrix_changed(transform->transform_matrix(time())); emit transform_matrix_changed(transform_matrix(time())); } void glaxnimate::model::Image::add_shapes(FrameTime, math::bezier::MultiBezier&, const QTransform&) const { } QIcon glaxnimate::model::Image::tree_icon() const { return QIcon::fromTheme("x-shape-image"); } QString glaxnimate::model::Image::type_name_human() const { return tr("Image"); } QPainterPath glaxnimate::model::Image::to_painter_path_impl(FrameTime time) const { auto trans = transform.get()->transform_matrix(time); QPainterPath p; p.addPolygon(trans.map(QRectF(QPointF(0, 0), image.get() ? image->pixmap().size() : QSize(0, 0)))); return p; } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/external/QtAppSetup/src/app/settings/widget.hpp000664 001750 001750 00000007276 14477652011 033665 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include #include #include #include #include #include #include #include #include "settings.hpp" namespace app::settings { namespace detail { template struct WidgetInfoImpl { using value_type = decltype((std::declval()->*getter)()); static auto get(const WidgetType* wid) { return (wid->*getter)(); } template static void set(WidgetType* wid, Arg&& value) { (wid->*setter)(std::forward(value)); } }; template struct WidgetInfo; #define WIDGET_DEF(Class, Getter, Setter) template<> struct WidgetInfo : public \ WidgetInfoImpl {} WIDGET_DEF(QSpinBox, value, setValue); WIDGET_DEF(QDoubleSpinBox, value, setValue); WIDGET_DEF(QLineEdit, text, setText); WIDGET_DEF(QCheckBox, isChecked, setChecked); WIDGET_DEF(QRadioButton, isChecked, setChecked); WIDGET_DEF(QComboBox, currentIndex, setCurrentIndex); WIDGET_DEF(QPushButton, isChecked, setChecked); #undef WIDGET_DEF } // namespace detail class WidgetSettingBase { public: virtual ~WidgetSettingBase() {} virtual void define() = 0; virtual void load() = 0; virtual void save() = 0; virtual void reset() = 0; }; /** * \brief Utility to automatically save/restore widget state using settings */ template class WidgetSetting : public WidgetSettingBase { using Info = detail::WidgetInfo; using value_type = typename Info::value_type; public: WidgetSetting(WidgetType* widget, const QString& group, const QString& prefix = {}) : widget(widget), group(group), setting(prefix + widget->objectName()) {} /// Defines the setting void define() override { Info::set(widget, settings::define(group, setting, Info::get(widget))); } /// Loads the setting and updates the widget void load() override { Info::set(widget, settings::get(group, setting, Info::get(widget))); } /// Saves the settings from the widget void save() override { settings::set(group, setting, Info::get(widget)); } /// Resets the setting to its default void reset() override { Info::set(widget, settings::get_default(group, setting)); } private: WidgetType* widget; QString group; QString setting; }; class WidgetSettingGroup : public WidgetSettingBase { public: template void add(WidgetType* widget, Args&&... args) { values.push_back(std::make_unique>(widget, std::forward(args)...)); } /// Defines the setting void define() override { for ( auto& setting : values ) setting->define(); } /// Loads the setting and updates the widget void load() override { for ( auto& setting : values ) setting->load(); } /// Saves the settings from the widget void save() override { for ( auto& setting : values ) setting->save(); } /// Resets the setting to its default void reset() override { for ( auto& setting : values ) setting->reset(); } private: std::vector> values; }; } // namespace app::settings src/modules/glaxnimate/glaxnimate/external/QtAppSetup/src/app/widgets/widget_palette_editor.hpp000664 001750 001750 00000001442 14477652011 036465 0ustar00ddennedyddennedy000000 000000 mlt-7.22.0/* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #ifndef WIDGETPALETTEEDITOR_H #define WIDGETPALETTEEDITOR_H #include #include #include "app/settings/palette_settings.hpp" class WidgetPaletteEditor : public QWidget { Q_OBJECT public: WidgetPaletteEditor ( app::settings::PaletteSettings* settings, QWidget* parent = nullptr ); ~WidgetPaletteEditor(); private slots: void add_palette(); void remove_palette(); void update_color(int row, int column); void select_palette(const QString& name); void apply_palette(); protected: void changeEvent ( QEvent* e ) override; private: class Private; std::unique_ptr d; }; #endif // WIDGETPALETTEEDITOR_H mlt-7.22.0/src/modules/glaxnimate/glaxnimate/external/QtAppSetup/000775 001750 001750 00000000000 14506447333 026450 5ustar00ddennedyddennedy000000 000000 mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/shapes/trim.cpp000664 001750 001750 00000021173 14477652011 030343 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "trim.hpp" #include "math/bezier/bezier_length.hpp" #include "model/shapes/group.hpp" #include "model/shapes/path.hpp" #include "model/animation/join_animatables.hpp" GLAXNIMATE_OBJECT_IMPL(glaxnimate::model::Trim) QIcon glaxnimate::model::Trim::static_tree_icon() { return QIcon::fromTheme("edit-cut"); } QString glaxnimate::model::Trim::static_type_name_human() { return tr("Trim Path"); } bool glaxnimate::model::Trim::process_collected() const { return multiple.get() == Simultaneously; } static void chunk_start(const glaxnimate::math::bezier::Bezier& in, glaxnimate::math::bezier::Bezier& out, const glaxnimate::math::bezier::LengthData::SplitInfo& split, int max = -1) { using namespace glaxnimate::math::bezier; if ( max == -1 ) max = in.closed_size(); // empty if ( split.ratio == 0 && split.index == 0 && max == in.closed_size() ) { out = in; return; } int index = split.index; // gotta split mid segment if ( split.ratio < 1 && split.ratio > 0 ) { auto split_points = CubicBezierSolver(in.segment(split.index)).split(split.ratio); out.push_back(Point( split_points.first[3], split_points.first[2], split_points.second[1], Smooth )); index += 1; //we use the tangents from the split for the next point if ( index < max ) { out.push_back(Point( split_points.second[3], split_points.second[2], in[index].tan_out, in[index].type )); index += 1; } } for ( int i = index; i < max; i++ ) out.push_back(in[i]); } static void chunk_end(const glaxnimate::math::bezier::Bezier& in, glaxnimate::math::bezier::Bezier& out, const glaxnimate::math::bezier::LengthData::SplitInfo& split, int min = 0) { using namespace glaxnimate::math::bezier; if ( split.ratio == 1 && min == 0 ) { out = in; return; } for ( int i = min; i <= split.index; i++ ) out.push_back(in[i]); // gotta split mid segment if ( split.ratio > 0 ) { auto split_points = CubicBezierSolver(in.segment(split.index)).split(split.ratio); // adjust tangents for the pevious point if ( !out.empty() ) out[out.size()-1].tan_out = split_points.first[1]; out.push_back(Point( split_points.first[3], split_points.first[2], split_points.second[1], Smooth )); } } glaxnimate::math::bezier::MultiBezier glaxnimate::model::Trim::process( glaxnimate::model::FrameTime t, const math::bezier::MultiBezier& mbez ) const { if ( mbez.empty() ) return {}; auto offset = this->offset.get_at(t); auto start = this->start.get_at(t); auto end = this->end.get_at(t); // Normalize Inputs offset = math::fmod(offset, 1.f); start = math::bound(0.f, start, 1.f) + offset; end = math::bound(0.f, end, 1.f) + offset; if ( end < start ) std::swap(start, end); // Handle the degenerate cases if ( math::abs(start * 1000 - end * 1000) < 1 ) return {}; if ( qFuzzyIsNull(start) && qFuzzyCompare(end, 1.f) ) return mbez; // Get the bezier chunk ratios // Note that now 0 <= s < e <= 2 struct Chunk { float start; float end; }; std::vector chunks; if ( end <= 1 ) { // Simplest case, the segment is in [0, 1] chunks.push_back({start, end}); } else if ( start > 1 ) { // The whole segment is outside [0, 1] chunks.push_back({start - 1, end - 1}); } else { // The segment goes over the end point, so we need two splits chunks.push_back({start, 1}); chunks.push_back({0, end - 1}); } const int length_steps = 5; math::bezier::MultiBezier out; math::bezier::LengthData length_data(mbez, length_steps); for ( const auto& chunk : chunks ) { auto start_data = length_data.at_ratio(chunk.start); auto end_data = length_data.at_ratio(chunk.end); /* Chunk of a single curve * * [ bez[0] ... bez[start==end] ... bez[n] ] * aa|BBCCCCDDD|ee * * [ seg[0] ... seg[single_start] ... seg[single_end] ... seg[m] ] * aaaaa|BBBBBBBBBBB|CCC|DDDDDDDD|eeeeee */ if ( start_data.index == end_data.index ) { auto single_start_data = start_data.descend(); auto single_end_data = end_data.descend(); math::bezier::Bezier b; /** * Same bezier segment * [ seg[0] ... seg[single_start=single_end] ... seg[m] ] * aaaaa|BBBBBBBBBBBBBBBB|ccccc */ if ( single_start_data.index == single_end_data.index ) { const auto& in = mbez.beziers()[start_data.index]; int index = single_start_data.index; // split the segment at start qreal ratio_start = single_start_data.ratio; auto truncated_segment = math::bezier::CubicBezierSolver(in.segment(index)).split(ratio_start).second; // find the end ratio for the truncated segment and split it there qreal ratio_end = (single_end_data.ratio - ratio_start) / (1-ratio_start); auto result = math::bezier::CubicBezierSolver(truncated_segment).split(ratio_end).first; // add to the bezier b.push_back(math::bezier::Point( result[0], result[0], result[1], math::bezier::Corner )); b.push_back(math::bezier::Point( result[3], result[2], result[3], math::bezier::Corner )); } else { int start_max = single_end_data.index; if ( single_end_data.index == single_start_data.index + 1 ) start_max += 1; chunk_start(mbez.beziers()[start_data.index], b, single_start_data, start_max); int end_min = qMax(start_max, single_start_data.index + 1); chunk_end(mbez.beziers()[start_data.index], b, single_end_data, end_min); } if ( !b.empty() && !out.beziers().empty() && !out.back().empty() && out.back().back().pos == b[0].pos ) { auto& out_bez = out.back(); out_bez.back().tan_out = b[0].tan_out; out_bez.back().type = math::bezier::Corner; out_bez.points().insert(out_bez.end(), b.begin() + 1, b.end()); } else { out.beziers().push_back(b); } continue; } /* Sequential chunk * * [ bez[0] ... bez[start] ... bez[end] ... bez[n] ] * aa|BBBBBBB|CCC|DDDDD|ee */ out.beziers().reserve(end_data.index - start_data.index); /* we skip the "a" part and get the "B" part * [ seg[0] ... seg[single_start] ... seg[m] ] * aaaaaaaaaaaaaaa|BBBBBBBBBBBBBBBBBBBBBBB */ { math::bezier::Bezier b; auto single_start_data = start_data.descend(); chunk_start(mbez.beziers()[start_data.index], b, single_start_data); out.beziers().push_back(b); } // We get the segment between start and end ("C" part) for ( int i = start_data.index + 1; i < end_data.index; i++ ) { out.beziers().push_back(mbez.beziers()[i]); } /* we get the "D" part and skip the "e" part * [ seg[0] ... seg[single_start] ... seg[m] ] * DDDDDDDDDDDDDDDDDDDDDDD|eeeeeeeeeeeeeee */ if ( end_data.ratio > 0 ) { math::bezier::Bezier b; auto single_end_data = end_data.descend(); chunk_end(mbez.beziers()[end_data.index], b, single_end_data); out.beziers().push_back(b); } } for ( auto& bez : out.beziers() ) { if ( !bez.empty() && math::fuzzy_compare(bez[0].pos, bez.back().pos) ) { bez[0].tan_in = bez.back().tan_in; bez.points().pop_back(); bez.close(); } } return out; } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/shapes/polystar.hpp000664 001750 001750 00000003401 14477652011 031244 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include "shape.hpp" namespace glaxnimate::model { class PolyStar : public Shape { GLAXNIMATE_OBJECT(PolyStar) public: enum StarType { Star = 1, Polygon = 2, }; Q_ENUM(StarType) GLAXNIMATE_PROPERTY(StarType, type, Star, {}, {}, PropertyTraits::Visual) GLAXNIMATE_ANIMATABLE(QPointF, position, QPointF()) GLAXNIMATE_ANIMATABLE(float, outer_radius, 0, {}, 0) GLAXNIMATE_ANIMATABLE(float, inner_radius, 0, {}, 0) GLAXNIMATE_ANIMATABLE(float, angle, 0, {}, 0, 360, true) GLAXNIMATE_ANIMATABLE(int, points, 5) GLAXNIMATE_ANIMATABLE(float, outer_roundness, 0, {}, 0, 100, false, PropertyTraits::Percent) GLAXNIMATE_ANIMATABLE(float, inner_roundness, 0, {}, 0, 100, false, PropertyTraits::Percent) public: using Shape::Shape; QIcon tree_icon() const override { if ( type.get() == Star ) return QIcon::fromTheme("draw-star"); return QIcon::fromTheme("draw-polygon"); } QString type_name_human() const override { return tr("PolyStar"); } math::bezier::Bezier to_bezier(FrameTime t) const override; QRectF local_bounding_rect(FrameTime t) const override { float radius = qMax(this->outer_radius.get_at(t), this->inner_radius.get_at(t)); return QRectF(position.get_at(t) - QPointF(radius, radius), QSizeF(radius*2, radius*2)); } bool is_rounded() const; static math::bezier::Bezier draw( StarType type, const QPointF& pos, float r_in, float r_out, float angle_radians, int p, float round_in, float round_out, bool reverse); }; } // namespace glaxnimate::model mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/aep/gradient_xml.cpp000664 001750 001750 00000004202 14477652011 030630 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "io/aep/gradient_xml.hpp" #include "io/svg/detail.hpp" using namespace glaxnimate::io; using namespace glaxnimate::io::aep; CosValue aep::xml_value(const QDomElement& element) { if ( element.tagName() == "prop.map" ) return xml_value(element.firstChildElement()); else if ( element.tagName() == "prop.list" ) return xml_list(element); else if ( element.tagName() == "array" ) return xml_array(element); else if ( element.tagName() == "int" ) return element.text().toDouble(); else if ( element.tagName() == "float" ) return element.text().toDouble(); else if ( element.tagName() == "string" ) return element.text(); else return {}; } CosArray aep::xml_array(const QDomElement& element) { auto data = std::make_unique(); for ( const auto& child : svg::detail::ElementRange(element) ) { if ( child.tagName() != "array.type" ) data->push_back(xml_value(child)); } return data; } CosObject aep::xml_list(const QDomElement& element) { auto data = std::make_unique(); for ( const auto& pair : svg::detail::ElementRange(element, "prop.pair") ) { QString key; CosValue value; for ( const auto& ch : svg::detail::ElementRange(pair) ) { if ( ch.tagName() == "key" ) key = ch.text(); else value = xml_value(ch); } data->emplace(key, std::move(value)); } return data; } Gradient aep::parse_gradient_xml(const CosValue& value) { Gradient gradient; auto& data = get(value, "Gradient Color Data"); gradient.color_stops = get_gradient_stops(data); gradient.alpha_stops = get_gradient_stops(data); return gradient; } Gradient aep::parse_gradient_xml(const QString& xml) { QDomDocument dom; dom.setContent(xml.trimmed()); return parse_gradient_xml(xml_value(dom.documentElement())); } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/io_registry.hpp000664 001750 001750 00000011031 14477652011 027750 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include "base.hpp" #include "mime/mime_serializer.hpp" namespace glaxnimate::io { namespace detail { inline bool compare_ie_ptr(const ImportExport* ptr_a, const ImportExport* ptr_b) noexcept { return ptr_a->priority() > ptr_b->priority(); } inline bool compare_ie_unique_ptr(const std::unique_ptr& ptr_a, const std::unique_ptr& ptr_b) noexcept { return compare_ie_ptr(ptr_a.get(), ptr_b.get()); } } // namespace detail class IoRegistry { public: static IoRegistry& instance() { static IoRegistry factory; return factory; } ImportExport* register_object(std::unique_ptr ie) { using namespace detail; auto iter = std::upper_bound(object_list.begin(), object_list.end(), ie, &compare_ie_unique_ptr); ImportExport* format = ie.get(); object_list.insert(iter, std::move(ie)); if ( format->can_save() ) exporters_.insert(std::upper_bound(exporters_.begin(), exporters_.end(), format, &compare_ie_ptr), format); if ( format->can_open() ) importers_.insert(std::upper_bound(importers_.begin(), importers_.end(), format, &compare_ie_ptr), format); return format; } void unregister(ImportExport* object) { for ( auto it = object_list.begin(); it != object_list.end(); ++it ) { if ( it->get() == object ) { object_list.erase(it); break; } } importers_.erase(std::remove(importers_.begin(), importers_.end(), object), importers_.end()); exporters_.erase(std::remove(exporters_.begin(), exporters_.end(), object), exporters_.end()); } mime::MimeSerializer* register_object(std::unique_ptr ie) { mime_list.push_back(std::move(ie)); mime::MimeSerializer* format = mime_list.back().get(); mime_pointers.push_back(format); return format; } const std::vector& importers() const { return importers_; } const std::vector& exporters() const { return exporters_; } const std::vector& serializers() const { return mime_pointers; } const std::vector>& registered() const { return object_list; } ImportExport* from_extension(const QString& extension, ImportExport::Direction direction) const { int top_priority = std::numeric_limits::min(); ImportExport* best = nullptr; for ( const auto& p : object_list ) { if ( p->can_handle_extension(extension, direction) && p->priority() > top_priority ) { best = p.get(); top_priority = p->priority(); } } return best; } ImportExport* from_filename(const QString& filename, ImportExport::Direction direction) const { int top_priority = std::numeric_limits::min(); ImportExport* best = nullptr; for ( const auto& p : object_list ) { if ( p->can_handle_filename(filename, direction) && p->priority() > top_priority ) { best = p.get(); top_priority = p->priority(); } } return best; } ImportExport* from_slug(const QString& slug) const { for ( const auto& p : object_list ) if ( p->slug() == slug ) return p.get(); return nullptr; } mime::MimeSerializer* serializer_from_slug(const QString& slug) const { for ( const auto& serializer : mime_list ) { if ( serializer->slug() == slug ) return serializer.get(); } return nullptr; } private: std::vector> object_list; std::vector importers_; std::vector exporters_; std::vector> mime_list; std::vector mime_pointers; IoRegistry() = default; ~IoRegistry() = default; }; template class Autoreg { public: template Autoreg(Args&&... args) : registered { static_cast( IoRegistry::instance().register_object(std::make_unique(std::forward(args)...)) ) } {} Derived* const registered; }; } // namespace glaxnimate::io mlt-7.22.0/src/modules/glaxnimate/glaxnimate/external/QtAppSetup/src/app/log/logger.hpp000664 001750 001750 00000002722 14477652011 032571 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include #include "app/log/log_line.hpp" namespace app::log { class Logger; class LogListener { public: LogListener() {} virtual ~LogListener() = default; protected: virtual void on_line(const LogLine& line) = 0; friend class Logger; }; class Logger : public QObject { Q_OBJECT public: static QString severity_name(Severity s) { switch ( s ) { case Info: return "Info"; case Warning: return "Warning"; case Error: return "Error"; default: return "?"; } } static Logger& instance() { static Logger instance; return instance; } template T* add_listener(Args&&... args) { listeners.push_back(std::make_unique(std::forward(args)...)); return static_cast(listeners.back().get()); } public Q_SLOTS: void log(const LogLine& line) { for ( const auto& listener : listeners ) listener->on_line(line); emit logged(line); } signals: void logged(const LogLine& line); private: Logger() = default; ~Logger() = default; std::vector> listeners; }; } // namespace app::log mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/math/bezier/operations.cpp000664 001750 001750 00000013054 14477652011 031400 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "operations.hpp" #include using namespace glaxnimate; // Algorithm from https://www.particleincell.com/2012/bezier-splines/ void math::bezier::auto_smooth(math::bezier::Bezier& curve, int start, int end) { if ( start < 0 || end > curve.size() || end - start < 2 ) return; int n = end - start - 1; // rhs vector std::vector a, b, c; std::vector r; // left most segment a.push_back(0); b.push_back(2); c.push_back(1); r.push_back(curve[start].pos + 2 * curve[start+1].pos); // internal segments for ( int i = 1; i < n - 1; i++ ) { a.push_back(1); b.push_back(4); c.push_back(1); r.push_back(4 * curve[start+i].pos + 2 * curve[start+i+1].pos); } // right segment a.push_back(2); b.push_back(7); c.push_back(0); r.push_back(8 * curve[end-2].pos + curve[end-1].pos); // solves Ax=b with the Thomas algorithm (from Wikipedia) for ( int i = 1; i < n; i++ ) { qreal m = a[i] / b[i-1]; b[i] = b[i] - m * c[i - 1]; r[i] = r[i] - m * r[i-1]; } QPointF last = r[n-1]/b[n-1]; curve[end-2].tan_in = last; for ( int i = n - 2; i >= 0; --i ) { last = (r[i] - c[i] * last) / b[i]; QPointF relative = (last - curve[start+i].pos); curve[start+i].tan_in = curve[start+i].pos - relative; curve[start+i].tan_out = curve[start+i].pos + relative; curve[start+i].type = math::bezier::Smooth; } } static qreal triangle_area(const math::bezier::Bezier& curve, int point) { QPointF prev = curve[point-1].pos; QPointF here = curve[point].pos; QPointF next = curve[point+1].pos; return qAbs( prev.x() * here.y() - here.x() * prev.y() + here.x() * next.y() - next.x() * here.y() + next.x() * prev.y() - prev.x() * next.y() ); } void math::bezier::simplify(math::bezier::Bezier& curve, qreal threshold) { if ( curve.size() < 3 || threshold <= 0 ) return; // Algorithm based on https://bost.ocks.org/mike/simplify/ std::vector tris; tris.reserve(curve.size()); tris.push_back(threshold); // [0] not used but keeping it for my own sanity for ( int i = 1; i < curve.size() - 1; i++ ) tris.push_back(triangle_area(curve, i)); while ( !tris.empty() ) { qreal min = threshold; int index = -1; for ( int i = 0; i < int(tris.size()); i++ ) { if ( tris[i] < min ) { index = i; min = tris[i]; } } if ( index == -1 ) break; tris.erase(tris.begin() + index); curve.points().erase(curve.begin() + index); if ( index < int(tris.size()) ) tris[index] = triangle_area(curve, index); if ( index > 1 ) tris[index-1] = triangle_area(curve, index - 1); } // Fake smoothness auto_smooth(curve, 0, curve.size()); } static math::bezier::ProjectResult project_extreme(int index, qreal t, const QPointF& p) { return {index, t, math::length_squared(p), p}; } static void project_impl(const math::bezier::CubicBezierSolver& solver, const QPointF& p, int index, math::bezier::ProjectResult& best) { static constexpr const double min_dist = 0.01; math::bezier::ProjectResult left = project_extreme(index, 0, solver.points()[0]); math::bezier::ProjectResult right = project_extreme(index, 1, solver.points()[3]); math::bezier::ProjectResult middle = {index, 0, 0, {}}; while ( true ) { middle.factor = (left.factor + right.factor) / 2; middle.point = solver.solve(middle.factor); middle.distance = math::length_squared(middle.point); if ( right.distance < left.distance ) left = middle; else right = middle; auto len = math::length_squared(left.point - right.point); if ( len <= min_dist || !std::isfinite(len) ) break; } if ( right.distance < left.distance ) left = right; if ( left.distance < best.distance ) { best = left; best.point += p; } } static void project_impl(const math::bezier::Bezier& curve, const QPointF& p, int index, math::bezier::ProjectResult& best) { math::bezier::CubicBezierSolver solver{ curve[index].pos - p, curve[index].tan_out - p, curve[index + 1].tan_in - p, curve[index + 1].pos - p }; project_impl(solver, p, index, best); } math::bezier::ProjectResult math::bezier::project(const math::bezier::Bezier& curve, const QPointF& p) { if ( curve.empty() ) return {0, 0, 0, p}; if ( curve.size() == 1 ) return {0, 0, math::length_squared(curve[0].pos - p), curve[0].pos}; ProjectResult best {0, 0, std::numeric_limits::max(), curve[0].pos}; for ( int i = 0; i < curve.size() - 1; i++ ) project_impl(curve, p, i, best); if ( curve.closed() ) project_impl(curve, p, curve.size() - 1, best); return best; } math::bezier::ProjectResult math::bezier::project(const math::bezier::BezierSegment& segment, const QPointF& p) { ProjectResult best {0, 0, std::numeric_limits::max(), segment[0]}; math::bezier::CubicBezierSolver solver{ segment[0] - p, segment[1] - p, segment[2] - p, segment[3] - p }; project_impl(solver, p, 0, best); return best; } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/shapes/precomp_layer.cpp000664 001750 001750 00000007512 14477652011 032232 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "precomp_layer.hpp" #include #include "model/document.hpp" #include "model/assets/composition.hpp" #include "model/assets/assets.hpp" GLAXNIMATE_OBJECT_IMPL(glaxnimate::model::PreCompLayer) glaxnimate::model::PreCompLayer::PreCompLayer(Document* document) : ShapeElement(document) { connect(transform.get(), &Object::property_changed, this, &PreCompLayer::on_transform_matrix_changed); } QIcon glaxnimate::model::PreCompLayer::tree_icon() const { return QIcon::fromTheme("component"); } QString glaxnimate::model::PreCompLayer::type_name_human() const { return tr("Composition Layer"); } glaxnimate::model::FrameTime glaxnimate::model::PreCompLayer::relative_time(glaxnimate::model::FrameTime time) const { return timing->time_to_local(time); } void glaxnimate::model::PreCompLayer::set_time(glaxnimate::model::FrameTime t) { ShapeElement::set_time(relative_time(t)); emit document()->graphics_invalidated(); } std::vector glaxnimate::model::PreCompLayer::valid_precomps() const { auto comps = document()->comp_graph().possible_descendants(owner_composition(), document()); return std::vector(comps.begin(), comps.end()); } bool glaxnimate::model::PreCompLayer::is_valid_precomp(glaxnimate::model::DocumentNode* node) const { auto owncomp = owner_composition(); if ( auto precomp = qobject_cast(node) ) return !document()->comp_graph().is_ancestor_of(precomp, owncomp); return false; } void glaxnimate::model::PreCompLayer::on_paint(QPainter* painter, glaxnimate::model::FrameTime time, glaxnimate::model::VisualNode::PaintMode mode, glaxnimate::model::Modifier*) const { if ( composition.get() ) { time = timing->time_to_local(time); painter->setOpacity( painter->opacity() * opacity.get_at(time) ); painter->setClipRect(QRectF(QPointF(0, 0), size.get()), Qt::IntersectClip); composition->paint(painter, time, mode); } } void glaxnimate::model::PreCompLayer::on_transform_matrix_changed() { propagate_bounding_rect_changed(); emit local_transform_matrix_changed(local_transform_matrix(time())); propagate_transform_matrix_changed(transform_matrix(time()), group_transform_matrix(time())); } QRectF glaxnimate::model::PreCompLayer::local_bounding_rect(FrameTime) const { return QRectF(QPointF(0, 0), size.get()); } QTransform glaxnimate::model::PreCompLayer::local_transform_matrix(glaxnimate::model::FrameTime t) const { return transform.get()->transform_matrix(t); } void glaxnimate::model::PreCompLayer::add_shapes(glaxnimate::model::FrameTime, math::bezier::MultiBezier&, const QTransform&) const { } void glaxnimate::model::PreCompLayer::on_composition_changed(model::Composition* old_comp, model::Composition* new_comp) { if ( old_comp ) document()->comp_graph().remove_connection(old_comp, this); if ( new_comp ) document()->comp_graph().add_connection(new_comp, this); if ( composition.get() ) { if ( !new_comp ) composition->remove_user(&composition); else if ( !old_comp ) composition->add_user(&composition); } } QPainterPath glaxnimate::model::PreCompLayer::to_painter_path_impl(glaxnimate::model::FrameTime time) const { QPainterPath p; if ( composition.get() ) { time = timing->time_to_local(time); for ( const auto& sh : composition->shapes ) p.addPath(sh->to_clip(time)); } return p; } QPainterPath glaxnimate::model::PreCompLayer::to_clip(glaxnimate::model::FrameTime time) const { return transform.get()->transform_matrix(time).map(to_painter_path(time)); } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/animation/keyframe_transition.cpp000664 001750 001750 00000014764 14477652011 034151 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "keyframe_transition.hpp" #include "math/bezier/segment.hpp" #include "math/polynomial.hpp" namespace { constexpr QPointF bound_vec(const QPointF& v) { return { qBound(glaxnimate::math::scalar_type(0), v.x(), glaxnimate::math::scalar_type(1)), v.y() }; } } // namespace glaxnimate::model::KeyframeTransition::Descriptive glaxnimate::model::KeyframeTransition::before_descriptive() const { if ( hold_ ) return Hold; if ( qFuzzyIsNull(bezier_.points()[1].x() - bezier_.points()[1].y()) ) return Linear; if ( bezier_.points()[1].y() == 0 ) return Ease; if ( bezier_.points()[1].y() < 0 ) return Overshoot; if ( bezier_.points()[1].x() < bezier_.points()[1].y() ) return Fast; return Custom; } glaxnimate::model::KeyframeTransition::Descriptive glaxnimate::model::KeyframeTransition::after_descriptive() const { if ( hold_ ) return Hold; if ( qFuzzyIsNull(bezier_.points()[2].x() - bezier_.points()[2].y()) ) return Linear; if ( bezier_.points()[2].y() == 1 ) return Ease; if ( bezier_.points()[2].y() > 1 ) return Overshoot; if ( bezier_.points()[2].x() > bezier_.points()[2].y() ) return Fast; return Custom; } void glaxnimate::model::KeyframeTransition::set_before_descriptive(model::KeyframeTransition::Descriptive d) { switch ( d ) { case Hold: set_hold(true); return; case Linear: bezier_.set<1>(QPointF{1./3., 1./3.}); hold_ = false; break; case Ease: bezier_.set<1>(QPointF{1./3., 0}); hold_ = false; break; case Fast: bezier_.set<1>(QPointF{1./6., 1./3.}); hold_ = false; break; case Overshoot: bezier_.set<1>(QPointF{2./3., -1./3.}); hold_ = false; break; case Custom: hold_ = false; break; } } void glaxnimate::model::KeyframeTransition::set_after_descriptive(model::KeyframeTransition::Descriptive d) { switch ( d ) { case Hold: set_hold(true); return; case Linear: bezier_.set<2>(QPointF{2./3., 2./3.}); hold_ = false; break; case Ease: bezier_.set<2>(QPointF{2./3., 1}); hold_ = false; break; case Fast: bezier_.set<2>(QPointF{5./6., 2./3.}); hold_ = false; break; case Overshoot: bezier_.set<2>(QPointF{1./3., 4./3.}); hold_ = false; break; case Custom: hold_ = false; break; } } void glaxnimate::model::KeyframeTransition::set_after(const QPointF& after) { bezier_.set<2>(bound_vec(after)); } void glaxnimate::model::KeyframeTransition::set_before(const QPointF& before) { bezier_.set<1>(bound_vec(before)); } void glaxnimate::model::KeyframeTransition::set_handles(const QPointF& before, const QPointF& after) { set_before(before); set_after(after); } void glaxnimate::model::KeyframeTransition::set_hold(bool hold) { hold_ = hold; } double glaxnimate::model::KeyframeTransition::lerp_factor(double ratio) const { if ( hold_ ) { if ( ratio >= 1 || qFuzzyCompare(float(ratio), 1.f) ) return 1; return 0; } if ( ratio <= 0 ) return 0; if ( ratio >= 1 ) return 1; double t = bezier_.t_at_value(ratio); return bezier_.solve_component(t, 1); } double glaxnimate::model::KeyframeTransition::bezier_parameter(double ratio) const { if ( ratio <= 0 || hold_ ) return 0; if ( ratio >= 1 ) return 1; return bezier_.t_at_value(ratio); } glaxnimate::model::KeyframeTransition::KeyframeTransition(const QPointF& before_handle, const QPointF& after_handle, bool hold) : bezier_({0, 0}, before_handle, after_handle, {1,1}), hold_(hold) {} glaxnimate::model::KeyframeTransition::KeyframeTransition( glaxnimate::model::KeyframeTransition::Descriptive before, glaxnimate::model::KeyframeTransition::Descriptive after) : KeyframeTransition() { set_before_descriptive(before); set_after_descriptive(after); } glaxnimate::model::KeyframeTransition::KeyframeTransition(glaxnimate::model::KeyframeTransition::Descriptive descriptive) : KeyframeTransition(descriptive, descriptive) { } std::pair glaxnimate::model::KeyframeTransition::split(double x) const { return split_t(bezier_.t_at_value(x)); } std::pair glaxnimate::model::KeyframeTransition::split_t(double t) const { if ( hold_ ) return { {{0, 0}, {1, 1}, true}, {{0, 0}, {1, 1}, true} }; if ( qFuzzyIsNull(t) ) { return { {{0, 0}, {1, 1}, false}, *this }; } else if ( qFuzzyCompare(t, 1) ) { return { *this, {{0, 0}, {1, 1}, false} }; } qreal x = bezier_.solve_component(t, 0); qreal y = bezier_.solve_component(t, 1); math::bezier::BezierSegment left, right; std::tie(left, right) = bezier_.split(t); qreal left_factor_x = 1 / x; qreal left_factor_y = 1 / y; qreal right_factor_x = 1 / (1-x); qreal right_factor_y = 1 / (1-y); qreal right_offset_y = 0; QPointF left_p1{left[1].x() * left_factor_x, left[1].y() * left_factor_y}; QPointF left_p2{left[2].x() * left_factor_x, left[2].y() * left_factor_y}; QPointF right_p1{(right[1].x() - x) / (1-x), (right[1].y() - y) / (1-y)}; QPointF right_p2{(right[2].x() - x) / (1-x), (right[2].y() - y) / (1-y)}; if ( y < 0 ) { left_p1.setY(-left[1].y() / (y - 1)); left_p2.setY(1 - left[2].y() / (y - 1)); } else if ( y > 1 ) { right_p1.setY(-(right[1].y() - 1) / (y - 1)); right_p2.setY(1 - (right[2].y() - 1) / (y - 1)); } return { { {left[1].x() * left_factor_x, left[1].y() * left_factor_y}, {left[2].x() * left_factor_x, left[2].y() * left_factor_y} }, { {(right[1].x() - x) * right_factor_x, right_offset_y + (right[1].y() - y) * right_factor_y}, {(right[2].x() - x) * right_factor_x, right_offset_y + (right[2].y() - y) * right_factor_y} } }; } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/svg/detail.hpp000664 001750 001750 00000006050 14477652011 027457 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include #include #include #include "app/utils/qstring_hash.hpp" namespace glaxnimate::io::svg::detail { extern const std::map xmlns; struct Style { public: using Map = std::map; Style(Map&& map) : map(std::move(map)) {} Style() = default; QString& operator[](const QString& s) { return map[s]; } const QString& operator[](const QString& s) const { return map.at(s); } const QString& get(const QString& k, const QString& def = {}) const { auto it = map.find(k); if ( it == map.end() ) return def; return it->second; } void set(const QString& k, const QString& v) { map[k] = v; } bool contains(const QString& k) const { return map.count(k); } Map map; QColor color = Qt::black; }; extern const std::unordered_set css_atrrs; template struct ItemCountRange { // using value_type = decltype(std::declval().item(0)); struct iterator { auto operator*() const { return range->dom_list.item(index); } iterator& operator++() { index++; return *this; } bool operator != (const iterator& it) const { return range != it.range || index != it.index; } const ItemCountRange* range; int index; }; ItemCountRange(const T& dom_list) : dom_list(dom_list) {} iterator begin() const { return {this, 0}; } iterator end() const { return {this, dom_list.count()}; } int size() const { return dom_list.count(); } T dom_list; }; struct ElementRange { struct iterator { auto operator*() const { return range->dom_list.item(index).toElement(); } iterator& operator++() { index++; while ( index < range->dom_list.count() && !acceptable() ) index++; return *this; } bool operator != (const iterator& it) const { return range != it.range || index != it.index; } bool acceptable() const { if ( !range->dom_list.item(index).isElement() ) return false; if ( range->tag_name.isEmpty() ) return true; return range->dom_list.item(index).toElement().tagName() == range->tag_name; } const ElementRange* range; int index; }; ElementRange(const QDomNodeList& dom_list, QString tag_name = {}) : dom_list(dom_list), tag_name(std::move(tag_name)) {} ElementRange(const QDomElement& el, QString tag_name = {}) : dom_list(el.childNodes()), tag_name(std::move(tag_name)) {} iterator begin() const { return {this, 0}; } iterator end() const { return {this, dom_list.count()}; } int size() const { return dom_list.count(); } QDomNodeList dom_list; QString tag_name; }; } // io::svg::detail mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/plugin/io.cpp000664 001750 001750 00000003400 14477652011 026703 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "io.hpp" #include "plugin.hpp" #include "model/assets/composition.hpp" using namespace glaxnimate; void plugin::IoService::enable() { if ( registered ) disable(); registered = io::IoRegistry::instance().register_object(std::make_unique(this)); } void plugin::IoService::disable() { if ( registered ) io::IoRegistry::instance().unregister(registered); registered = nullptr; } bool plugin::IoFormat::on_open(QIODevice& file, const QString& name, model::Document* document, const QVariantMap& settings) { return service->plugin()->run_script(service->open, { PluginRegistry::instance().global_parameter("window"), QVariant::fromValue(document), QVariant::fromValue(&file), name, QVariant::fromValue(this), settings }); } bool plugin::IoFormat::on_save(QIODevice& file, const QString& name, model::Composition* comp, const QVariantMap& settings) { return service->plugin()->run_script(service->save, { PluginRegistry::instance().global_parameter("window"), QVariant::fromValue(comp->document()), QVariant::fromValue(comp), QVariant::fromValue(&file), name, QVariant::fromValue(this), settings }); } std::unique_ptr glaxnimate::plugin::IoFormat::open_settings() const { return std::make_unique(service->open.settings); } std::unique_ptr glaxnimate::plugin::IoFormat::save_settings(model::Composition*) const { return std::make_unique(service->save.settings); } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/base.hpp000664 001750 001750 00000010727 14477652011 026336 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include #include #include "app/settings/settings_group.hpp" #include "app/log/log_line.hpp" #include "model/document.hpp" #include "model/assets/composition.hpp" namespace glaxnimate::io { class ImportExport : public QObject { Q_OBJECT Q_PROPERTY(QString name READ name) Q_PROPERTY(QString slug READ slug) Q_PROPERTY(QStringList extensions READ extensions) Q_PROPERTY(bool can_open READ can_open) Q_PROPERTY(bool can_save READ can_save) public: enum Direction { Import, Export, }; Q_ENUM(Direction) virtual ~ImportExport() = default; Q_INVOKABLE bool can_handle(glaxnimate::io::ImportExport::Direction direction) const { if ( direction == Import ) return can_open(); else if ( direction == Export ) return can_save(); return false; } Q_INVOKABLE bool can_handle_extension(const QString& extension, glaxnimate::io::ImportExport::Direction direction) const { return can_handle(direction) && extensions().contains(extension); } Q_INVOKABLE bool can_handle_filename(const QString& filename, glaxnimate::io::ImportExport::Direction direction) const { return can_handle_extension(QFileInfo(filename).completeSuffix(), direction); } /** * @pre @p setting_values contains all the settings correctly && can_open() */ bool open(QIODevice& file, const QString& filename, model::Document* document, const QVariantMap& setting_values); /** * @pre @p setting_values contains all the settings correctly && can_open() * @param file File to write to * @param filename Filename for error reporting * @param comp Composition, for formats supporting multiple comps, use comp->document() * @param setting_values Values based on save_settings() */ bool save(QIODevice& file, const QString& filename, model::Composition* comp, const QVariantMap& setting_values); /** * \brief Will save the first comp in the document */ bool save(QIODevice& file, const QString& filename, model::Document* document, const QVariantMap& setting_values); Q_INVOKABLE QByteArray save(glaxnimate::model::Composition* comp, const QVariantMap& setting_values={}, const QString& filename = "data"); Q_INVOKABLE bool load(glaxnimate::model::Document* document, const QByteArray& data, const QVariantMap& setting_values={}, const QString& filename = "data"); virtual QString name() const = 0; virtual QString slug() const = 0; virtual QStringList extensions() const = 0; virtual std::unique_ptr open_settings() const { return {}; } virtual std::unique_ptr save_settings(model::Composition* ) const { return {}; } virtual bool can_open() const = 0; virtual bool can_save() const = 0; /** * \brief Priority when multiple classes support the same file types */ virtual int priority() const { return 0; } /** * \brief File dialog name filter */ Q_INVOKABLE QString name_filter() const; Q_INVOKABLE void warning(const QString& message) { emit this->message(message, app::log::Warning); } Q_INVOKABLE void information(const QString& message) { emit this->message(message, app::log::Info); } Q_INVOKABLE void error(const QString& message) { emit this->message(message, app::log::Error); } protected: virtual bool auto_open() const { return true; } virtual bool on_open(QIODevice& file, const QString& filename, model::Document* document, const QVariantMap& setting_values) { Q_UNUSED(file); Q_UNUSED(filename); Q_UNUSED(document); Q_UNUSED(setting_values); return false; } virtual bool on_save( QIODevice& file, const QString& filename, model::Composition* comp, const QVariantMap& setting_values) { Q_UNUSED(file); Q_UNUSED(filename); Q_UNUSED(comp); Q_UNUSED(setting_values); return false; } signals: void message(const QString& message, app::log::Severity severity); void progress_max_changed(int max); void progress(int value); void completed(bool success); }; } // namespace glaxnimate::io mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/utils/color.hpp000664 001750 001750 00000000716 14477652011 027270 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include namespace glaxnimate::utils::color { inline constexpr qint32 rgba_distance_squared(QRgb c1, qint32 r, qint32 g, qint32 b, qint32 a) noexcept { r -= qRed(c1); g -= qGreen(c1); b -= qBlue(c1); a -= qAlpha(c1); return r*r + g*g + b*b + a*a; } } // namespace glaxnimate::utils::color mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/utils/quantize.cpp000664 001750 001750 00000052170 14477652011 030006 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "quantize.hpp" #include #include #include using namespace glaxnimate; namespace glaxnimate::utils::quantize::detail { static bool freq_sort_cmp(const ColorFrequency& a, const ColorFrequency& b) noexcept { return a.second > b.second; } std::vector color_frequencies_to_palette(std::vector& freq, int max) { std::sort(freq.begin(), freq.end(), detail::freq_sort_cmp); int count = qMin(max, freq.size()); std::vector out; out.reserve(count); for ( int i = 0; i < count; i++ ) out.push_back(freq[i].first); return out; } using Distance = quint32; struct Color { qint32 r; qint32 g; qint32 b; constexpr Color(QRgb rgb) noexcept : r(qRed(rgb)), g(qGreen(rgb)), b(qBlue(rgb)) {} constexpr Color() noexcept : r(0), g(0), b(0) {} constexpr Color(qint32 r, qint32 g, qint32 b) noexcept : r(r), g(g), b(b) {} constexpr Distance distance(const Color& oth) const noexcept { quint32 dr = r - oth.r; quint32 dg = g - oth.g; quint32 db = b - oth.b; return dr * dr + dg * dg + db * db; } constexpr QRgb rgb() const noexcept { return qRgb(r, g, b); } constexpr void weighted_add(const Color& oth, int weight) noexcept { r += oth.r * weight; g += oth.g * weight; b += oth.b * weight; } constexpr Color& operator+=(const Color& oth) noexcept { weighted_add(oth, 1); return *this; } Color mean(qreal total_weight) const noexcept { return { qRound(r / total_weight), qRound(g / total_weight), qRound(b / total_weight) }; } }; using HistogramMap = std::unordered_map; HistogramMap color_frequency_map(QImage image, int alpha_threshold) { if ( image.format() != QImage::Format_RGBA8888 ) image = image.convertToFormat(QImage::Format_RGBA8888); HistogramMap count; const uchar* data = image.constBits(); int n_pixels = image.width() * image.height(); for ( int i = 0; i < n_pixels; i++ ) if ( data[i*4+3] >= alpha_threshold ) ++count[qRgb(data[i*4], data[i*4+1], data[i*4+2])]; return count; } } // utils::quantize::detail std::vector utils::quantize::color_frequencies(const QImage& image, int alpha_threshold) { auto count = detail::color_frequency_map(image, alpha_threshold); return std::vector(count.begin(), count.end()); } std::vector utils::quantize::k_modes(const QImage& image, int k) { auto freq = color_frequencies(image); return detail::color_frequencies_to_palette(freq, k); } namespace glaxnimate::utils::quantize::detail::k_means { struct Point { Color color; quint32 weight; Distance min_distance = std::numeric_limits::max(); int cluster = -1; constexpr Point(const ColorFrequency& p) noexcept : color(p.first), weight(p.second) {} }; struct Cluster { Color centroid; quint32 total_weight = 0; Color sum = {}; constexpr Cluster(const Color& color) noexcept : centroid(color) {} bool update() { if ( total_weight == 0 ) return false; auto old = centroid; centroid.r = qRound(double(sum.r) / total_weight); centroid.g = qRound(double(sum.g) / total_weight); centroid.b = qRound(double(sum.b) / total_weight); total_weight = 0; sum = {}; return old.rgb() != centroid.rgb(); } }; } // utils::quantize::detail std::vector utils::quantize::k_means(const QImage& image, int k, int iterations, KMeansMatch match) { auto freq = color_frequencies(image); // Avoid processing if we don't need to if ( int(freq.size()) <= k ) return detail::color_frequencies_to_palette(freq, k); // Initialize points std::vector points(freq.begin(), freq.end()); freq.clear(); // Keep track of the clusters we already used std::vector cluster_init; cluster_init.reserve(k); std::vector clusters; clusters.reserve(k); // Get the most common color as initial cluster centroid quint32 max_freq = 0; std::vector::iterator best_iter; for ( auto it = points.begin(); it != points.end(); ++it ) { if ( it->weight > max_freq ) { max_freq = it->weight; best_iter = it; } } cluster_init.push_back(*best_iter); clusters.emplace_back(best_iter->color); // remove centroid from points std::swap(*best_iter, points.back()); points.pop_back(); // k-means++-like processing from now on (but deterministic) // ie: always select the centroid the farthest away from the other centroids while ( int(clusters.size()) < k ) { detail::Distance max_dist = 0; for ( auto it = points.begin(); it != points.end(); ++it ) { detail::Distance p_max = 0; for ( const auto& cluster : clusters ) { auto dist = it->color.distance(cluster.centroid); if ( dist < p_max ) p_max = dist; } if ( p_max > max_dist ) { max_dist = p_max; best_iter = it; } } cluster_init.push_back(*best_iter); clusters.emplace_back(best_iter->color); // remove centroid from points std::swap(*best_iter, points.back()); points.pop_back(); } // add back the removed centroids points.insert(points.end(), cluster_init.begin(), cluster_init.end()); cluster_init.clear(); // K-medoids bool loop = true; for ( int epoch = 0; epoch < iterations && loop; epoch++ ) { // Assign points to clusters for ( int i = 0; i < k; i++ ) { const auto& cluster = clusters[i]; for ( auto& p : points ) { auto dist = p.color.distance(cluster.centroid); if (dist < p.min_distance ) { p.min_distance = dist; p.cluster = i; } } } // Move centroids for ( auto& p : points ) { clusters[p.cluster].total_weight += p.weight; clusters[p.cluster].sum.weighted_add(p.color, p.weight); p.min_distance = std::numeric_limits::max(); } // Quit if nothing has changed loop = false; for ( auto& cluster : clusters ) loop = cluster.update() || loop; } // Post-process to find the closest color, reusing total_weight/sum for this if ( match ) { for ( auto& cluster : clusters ) { cluster.total_weight = 0; cluster.sum = {}; } for ( auto& p : points ) { auto& cluster = clusters[p.cluster]; auto score = match == MostFrequent ? p.weight : std::numeric_limits::max() - p.color.distance(cluster.centroid); if ( score > cluster.total_weight ) { cluster.total_weight = score; cluster.sum = p.color; } } for ( auto& cluster : clusters ) { cluster.centroid = cluster.sum; } } std::vector result; result.reserve(k); for ( auto& cluster : clusters ) result.push_back(cluster.centroid.rgb()); return result; } /** * \note Most of the code here is taken from Inkscape (with several changes) * \see https://gitlab.com/inkscape/inkscape/-/blob/master/src/trace/quantize.cpp for the original code */ namespace glaxnimate::utils::quantize::detail::octree { inline Color operator>>(Color rgb, int s) { Color res; res.r = rgb.r >> s; res.g = rgb.g >> s; res.b = rgb.b >> s; return res; } inline bool operator==(Color rgb1, Color rgb2) { return (rgb1.r == rgb2.r && rgb1.g == rgb2.g && rgb1.b == rgb2.b); } inline int childIndex(Color rgb) { return (((rgb.r)&1)<<2) | (((rgb.g)&1)<<1) | (((rgb.b)&1)); } struct Node { Node *parent = nullptr; std::unique_ptr children[8]; // number of children int nchild = 0; // width level of this node int width = 0; // rgb's prefix of that node Color rgb; // number of pixels this node accounts for quint32 weight = 0; // sum of pixels colors this node accounts for Color sum; // number of leaves under this node int nleaf = 0; // minimum impact unsigned long mi = 0; /** * compute the color palette associated to an octree. */ void get_colors(std::vector &colors) { if (nchild == 0) { colors.push_back(sum.mean(weight).rgb()); } else { for (auto & i : children) if (i) i->get_colors(colors); } } void update_mi() { mi = parent ? weight << (2 * parent->width) : 0; } }; /** * builds a single color leaf */ static std::unique_ptr ocnodeLeaf(Color rgb, quint32 weight) { auto node = std::make_unique(); node->width = 0; node->rgb = rgb; node->sum = Color(rgb.r * weight, rgb.g * weight, rgb.b * weight); node->weight = weight; node->nleaf = 1; node->mi = 0; return node; } /** * merge nodes and at location with parent */ static std::unique_ptr octreeMerge(Node *parent, Node *ref, std::unique_ptr node1, std::unique_ptr node2) { if (parent && !ref) parent->nchild++; if ( !node1 ) { node2->parent = parent; return node2; } if ( !node2 ) { node1->parent = parent; return node1; } int dwitdth = node1->width - node2->width; if (dwitdth > 0 && node1->rgb == node2->rgb >> dwitdth) { //place node2 below node1 node1->parent = parent; int i = childIndex(node2->rgb >> (dwitdth - 1)); node1->sum += node2->sum; node1->weight += node2->weight; node1->mi = 0; if (node1->children[i]) node1->nleaf -= node1->children[i]->nleaf; node1->children[i] = octreeMerge(node1.get(), node1->children[i].get(), std::move(node1->children[i]), std::move(node2)); node1->nleaf += node1->children[i]->nleaf; return node1; } else if (dwitdth < 0 && node2->rgb == node1->rgb >> (-dwitdth)) { //place node1 below node2 node2->parent = parent; int i = childIndex(node1->rgb >> (-dwitdth - 1)); node2->sum += node1->sum; node2->weight += node1->weight; node2->mi = 0; if (node2->children[i]) node2->nleaf -= node2->children[i]->nleaf; node2->children[i] = octreeMerge(node2.get(), node2->children[i].get(), std::move(node2->children[i]), std::move(node1)); node2->nleaf += node2->children[i]->nleaf; return node2; } else { //nodes have either no intersection or the same root auto newnode = std::make_unique(); newnode->sum = node1->sum; newnode->sum += node2->sum; newnode->weight = node1->weight + node2->weight; newnode->parent = parent; if (dwitdth == 0 && node1->rgb == node2->rgb) { //merge the nodes in newnode->width = node1->width; // == node2->width newnode->rgb = node1->rgb; // == node2->rgb newnode->nchild = 0; newnode->nleaf = 0; if (node1->nchild == 0 && node2->nchild == 0) { newnode->nleaf = 1; } else { for (int i = 0; i < 8; i++) { if (node1->children[i] || node2->children[i]) { newnode->children[i] = octreeMerge(newnode.get(), newnode->children[i].get(), std::move(node1->children[i]), std::move(node2->children[i])); newnode->nleaf += newnode->children[i]->nleaf; } } } return newnode; } else { //use as a fork node with children and int newwidth = node1->width > node2->width ? node1->width : node2->width; Color rgb1 = node1->rgb >> (newwidth - node1->width); Color rgb2 = node2->rgb >> (newwidth - node2->width); //according to the previous tests != before the loop while ( !(rgb1 == rgb2) ) { rgb1 = rgb1 >> 1; rgb2 = rgb2 >> 1; newwidth++; } newnode->width = newwidth; newnode->rgb = rgb1; // == rgb2 newnode->nchild = 2; newnode->nleaf = node1->nleaf + node2->nleaf; int i1 = childIndex(node1->rgb >> (newwidth - node1->width - 1)); int i2 = childIndex(node2->rgb >> (newwidth - node2->width - 1)); node1->parent = newnode.get(); newnode->children[i1] = std::move(node1); node2->parent = newnode.get(); newnode->children[i2] = std::move(node2); return newnode; } } } /** * remove leaves whose prune impact value is lower than . at most * leaves are removed, and is decreased on each removal. * all parameters including minimal impact values are regenerated. */ static std::unique_ptr ocnodeStrip(std::unique_ptr node, int *count, unsigned long lvl) { if ( !count || !node ) return {}; if (node->nchild == 0) // leaf node { if (!node->mi) node->update_mi(); //mi generation may be required if (node->mi > lvl) return node; //leaf is above strip level (*count)--; return {}; } else { if (node->mi && node->mi > lvl) //node is above strip level return node; node->nchild = 0; node->nleaf = 0; node->mi = 0; std::unique_ptr *lonelychild = nullptr; for (auto & child : node->children) { if ( child ) { child = ocnodeStrip(std::move(child), count, lvl); if ( child ) { lonelychild = &child; node->nchild++; node->nleaf += child->nleaf; if (!node->mi || node->mi > child->mi) node->mi = child->mi; } } } // tree adjustments if (node->nchild == 0) { (*count)++; node->nleaf = 1; node->update_mi(); } else if (node->nchild == 1) { if ((*lonelychild)->nchild == 0) { //remove the leaf under a 1 child node node->nchild = 0; node->nleaf = 1; node->update_mi(); lonelychild->reset(); } else { //make a bridge to over a 1 child node (*lonelychild)->parent = node->parent; return std::move(*lonelychild); } } } return node; } /** * reduce the leaves of an octree to a given number */ static std::unique_ptr octreePrune(std::unique_ptr ref, int ncolor) { int n = ref->nleaf - ncolor; if ( n <= 0 ) return ref; //calling strip with global minimum impact of the tree while ( n > 0 && ref ) { auto mi = ref->mi; ref = ocnodeStrip(std::move(ref), &n, mi); } return ref; } std::unique_ptr add_pixels(Node* ref, ColorFrequency* data, int data_size) { if ( data_size == 1 ) { return ocnodeLeaf(data->first, data->second); } else if ( data_size > 1 ) { std::unique_ptr ref1 = add_pixels(nullptr, data, data_size/2); std::unique_ptr ref2 = add_pixels(nullptr, data + data_size/2, data_size - data_size/2); return octreeMerge(nullptr, ref, std::move(ref1), std::move(ref2)); } return {}; } } // namespace glaxnimate::utils::quantize::detail::octree std::vector utils::quantize::octree(const QImage& image, int k) { using namespace glaxnimate::utils::quantize::detail::octree; auto freq = color_frequencies(image); // Avoid processing if we don't need to if ( int(freq.size()) <= k || k <= 1) return detail::color_frequencies_to_palette(freq, k); std::vector colors; colors.reserve(k); std::unique_ptr tree = add_pixels(nullptr, freq.data(), freq.size()); tree = octreePrune(std::move(tree), k); tree->get_colors(colors); return colors; } static inline qint32 pixel_distance(QRgb p1, QRgb p2) { int r1 = qRed(p1); int g1 = qGreen(p1); int b1 = qBlue(p1); int a1 = qAlpha(p1); int r2 = qRed(p2); int g2 = qGreen(p2); int b2 = qBlue(p2); int a2 = qAlpha(p2); qint32 dr = r1 - r2; qint32 dg = g1 - g2; qint32 db = b1 - b2; qint32 da = a1 - a2; return dr * dr + dg * dg + db * db + da * da; } static inline qint32 closest_match(QRgb pixel, const QVector &clut) { if ( qAlpha(pixel) < 128 ) return clut.size() - 1; int idx = 0; qint32 current_distance = INT_MAX; for ( int i = 0; i < clut.size(); ++i) { int dist = pixel_distance(pixel, clut[i]); if (dist < current_distance) { current_distance = dist; idx = i; } } return idx; } static QImage convert_with_palette(const QImage &src, const QVector &clut) { QImage dest(src.size(), QImage::Format_Indexed8); dest.setColorTable(clut); int h = src.height(); int w = src.width(); QHash cache; for (int y=0; y& colors) { #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) QVector vcolors(colors.begin(), colors.end()); #else QVector vcolors; vcolors.reserve(colors.size()+1); for ( auto color : colors ) vcolors.push_back(color); #endif vcolors.push_back(qRgba(0, 0, 0, 0)); return convert_with_palette(source.convertToFormat(QImage::Format_ARGB32), vcolors); } namespace glaxnimate::utils::quantize::detail::auto_colors { void decrease(QRgb color, HistogramMap& map) { auto it = map.find(color | 0xff000000u); if ( it != map.end() ) it->second -= 1; } } // namespace glaxnimate::utils::quantize::detail::auto_colors std::vector utils::quantize::edge_exclusion_modes(const QImage& image_in, int max_colors, qreal min_frequency) { int alpha_threshold = 128; QImage image = image_in; if ( image.format() != QImage::Format_RGBA8888 ) image = image.convertToFormat(QImage::Format_RGBA8888); detail::HistogramMap colors = detail::color_frequency_map(image, alpha_threshold); if ( int(colors.size()) <= max_colors ) { auto freq = std::vector(colors.begin(), colors.end()); return detail::color_frequencies_to_palette(freq, max_colors); } std::vector output; int min_amount = min_frequency * image.width() * image.height(); while ( int(output.size()) < max_colors && !colors.empty() ) { auto best = colors.begin(); for ( auto it = best; it != colors.end(); ++it ) { if ( it->second > best->second ) best = it; } if ( best->second <= min_amount ) break; auto color = best->first; output.push_back(color); colors.erase(best); for ( int y = 1; y < image.height() - 1; y++ ) { for ( int x = 1; x < image.width() - 1; x++ ) { auto pix = image.pixel(x, y); if ( pix == color ) { detail::auto_colors::decrease(image.pixel(x, y-1), colors); detail::auto_colors::decrease(image.pixel(x, y+1), colors); detail::auto_colors::decrease(image.pixel(x-1, y), colors); detail::auto_colors::decrease(image.pixel(x+1, y), colors); } } } } return output; } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/shapes/round_corners.hpp000664 001750 001750 00000001251 14477652011 032252 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include "path_modifier.hpp" namespace glaxnimate::model { class RoundCorners : public StaticOverrides { GLAXNIMATE_OBJECT(RoundCorners) GLAXNIMATE_ANIMATABLE(float, radius, 0, {}, 0,) public: using Ctor::Ctor; static QIcon static_tree_icon(); static QString static_type_name_human(); math::bezier::MultiBezier process(FrameTime t, const math::bezier::MultiBezier& mbez) const override; protected: bool process_collected() const override; }; } // namespace glaxnimate::model mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/shapes/shape.cpp000664 001750 001750 00000022760 14477652011 030473 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "shape.hpp" #include "utils/range.hpp" #include "styler.hpp" #include "path.hpp" #include "model/animation/join_animatables.hpp" using namespace glaxnimate; class glaxnimate::model::ShapeElement::Private { public: ShapeListProperty* property = nullptr; int position = -1; glaxnimate::model::Composition* owner_composition = nullptr; PathCache cached_path; void update_comp(glaxnimate::model::Composition* comp, ShapeElement* parent) { if ( comp != owner_composition ) { auto old = owner_composition; owner_composition = comp; parent->on_composition_changed(old, comp); } } }; glaxnimate::model::ShapeElement::ShapeElement(glaxnimate::model::Document* document) : VisualNode(document), d(std::make_unique()) { } glaxnimate::model::ShapeElement::~ShapeElement() = default; glaxnimate::model::ShapeListProperty * glaxnimate::model::ShapeElement::owner() const { return d->property; } void glaxnimate::model::ShapeElement::clear_owner() { d->property = nullptr; d->position = -1; d->owner_composition = nullptr; } glaxnimate::model::Composition * glaxnimate::model::ShapeElement::owner_composition() const { return d->owner_composition; } int glaxnimate::model::ShapeElement::position() const { return d->position; } const glaxnimate::model::ShapeListProperty& glaxnimate::model::ShapeElement::siblings() const { return *d->property; } glaxnimate::model::ObjectListProperty::iterator glaxnimate::model::ShapeListProperty::past_first_modifier() const { auto it = std::find_if(begin(), end(), [](const pointer& p){ return qobject_cast(p.get()); }); if ( it != end() ) ++it; return it; } void glaxnimate::model::ShapeElement::refresh_owner_composition(glaxnimate::model::Composition* comp) { d->update_comp(comp, this); } void glaxnimate::model::ShapeElement::set_position(ShapeListProperty* property, int pos) { d->property = property; d->position = pos; position_updated(); if ( property ) { auto parent = d->property->object(); if ( !parent ) d->update_comp(nullptr, this); else if ( auto comp = parent->cast() ) d->update_comp(comp, this); else if ( auto sh = parent->cast() ) d->update_comp(sh->d->owner_composition, this); } } void glaxnimate::model::ShapeElement::on_parent_changed(model::DocumentNode* old_parent, model::DocumentNode* new_parent) { if ( auto old_visual = qobject_cast(old_parent) ) disconnect(this, &VisualNode::bounding_rect_changed, old_visual, &VisualNode::bounding_rect_changed); if ( auto new_visual = qobject_cast(new_parent) ) connect(this, &VisualNode::bounding_rect_changed, new_visual, &VisualNode::bounding_rect_changed); if ( !new_parent ) d->update_comp(nullptr, this); } void glaxnimate::model::ShapeElement::on_property_changed(const glaxnimate::model::BaseProperty* prop, const QVariant&) { if ( prop->traits().flags & PropertyTraits::Visual ) propagate_bounding_rect_changed(); } math::bezier::MultiBezier glaxnimate::model::ShapeElement::shapes(glaxnimate::model::FrameTime t) const { math::bezier::MultiBezier bez; add_shapes(t, bez, {}); return bez; } QPainterPath glaxnimate::model::ShapeElement::to_clip(FrameTime t) const { return to_painter_path(t); } QPainterPath glaxnimate::model::ShapeElement::to_painter_path(FrameTime t) const { if ( d->cached_path.is_dirty(t) ) d->cached_path.set_path(t, to_painter_path_impl(t)); return d->cached_path.path(); } void glaxnimate::model::ShapeElement::on_graphics_changed() { d->cached_path.mark_dirty(); } std::unique_ptr glaxnimate::model::ShapeElement::to_path() const { return std::unique_ptr(static_cast(clone().release())); } QRectF glaxnimate::model::ShapeListProperty::bounding_rect(FrameTime t) const { QRectF rect; for ( const auto& ch : utils::Range(begin(), past_first_modifier()) ) { QRectF local_rect = ch->local_bounding_rect(t); if ( local_rect.isNull() ) continue; QRectF child_rect = ch->local_transform_matrix(t).map(local_rect).boundingRect(); if ( rect.isNull() ) rect = child_rect; else rect |= child_rect; } return rect; } std::unique_ptr glaxnimate::model::Shape::to_path() const { std::vector properties; auto flags = PropertyTraits::Visual|PropertyTraits::Animated; for ( auto prop : this->properties() ) { if ( (prop->traits().flags & flags) == flags ) properties.push_back(static_cast(prop)); } auto path = std::make_unique(document()); path->name.set(name.get()); path->group_color.set(group_color.get()); path->visible.set(visible.get()); if ( !properties.empty() ) { JoinAnimatables ja(std::move(properties)); FrameTime cur_time = ja.properties()[0]->time(); path->set_time(cur_time); if ( ja.animated() ) { for ( const auto & kf : ja ) { auto path_kf = path->shape.set_keyframe(kf.time, to_bezier(kf.time)); path_kf->set_transition(kf.transition()); } } path->shape.set(to_bezier(cur_time)); path->closed.set(path->shape.get().closed()); } return path; } QPainterPath glaxnimate::model::Shape::to_painter_path_impl(FrameTime t) const { QPainterPath p; to_bezier(t).add_to_painter_path(p); return p; } void glaxnimate::model::Shape::add_shapes(FrameTime t, math::bezier::MultiBezier & bez, const QTransform& transform) const { auto shape = to_bezier(t); if ( !transform.isIdentity() ) shape.transform(transform); bez.beziers().emplace_back(std::move(shape)); } glaxnimate::model::ShapeOperator::ShapeOperator(glaxnimate::model::Document* doc) : ShapeElement(doc) { connect(this, &ShapeElement::position_updated, this, &ShapeOperator::update_affected); connect(this, &ShapeElement::siblings_changed, this, &ShapeOperator::update_affected); } void glaxnimate::model::ShapeOperator::do_collect_shapes(const std::vector& shapes, glaxnimate::model::FrameTime t, math::bezier::MultiBezier& bez, const QTransform& transform) const { for ( auto sib : shapes ) { if ( sib->visible.get() ) sib->add_shapes(t, bez, transform); } } math::bezier::MultiBezier glaxnimate::model::ShapeOperator::collect_shapes_from(const std::vector& shapes, glaxnimate::model::FrameTime t, const QTransform& transform) const { math::bezier::MultiBezier bez; if ( visible.get() ) do_collect_shapes(shapes, t, bez, transform); return bez; } math::bezier::MultiBezier glaxnimate::model::ShapeOperator::collect_shapes(FrameTime t, const QTransform& transform) const { if ( bezier_cache.is_dirty(t) ) bezier_cache.set_path(t, collect_shapes_from(affected_elements, t, transform)); return bezier_cache.path(); } void glaxnimate::model::ShapeOperator::update_affected() { if ( !owner() ) return; std::vector curr_siblings; curr_siblings.reserve(owner()->size() - position()); bool skip = skip_stylers(); for ( auto it = owner()->begin() + position() + 1; it < owner()->end(); ++it ) { if ( skip && qobject_cast(it->get()) ) continue; curr_siblings.push_back(it->get()); if ( qobject_cast(it->get()) ) break; } affected_elements = curr_siblings; std::reverse(affected_elements.begin(), affected_elements.end()); } void glaxnimate::model::ShapeOperator::on_graphics_changed() { ShapeElement::on_graphics_changed(); bezier_cache.mark_dirty(); emit shape_changed(); } void glaxnimate::model::Modifier::add_shapes(FrameTime t, math::bezier::MultiBezier& bez, const QTransform& transform) const { bez.append(collect_shapes(t, transform)); } void glaxnimate::model::Modifier::do_collect_shapes(const std::vector& shapes, glaxnimate::model::FrameTime t, math::bezier::MultiBezier& bez, const QTransform& transform) const { bool post = process_collected(); if ( post ) { math::bezier::MultiBezier temp; for ( auto sib : shapes ) { if ( sib->visible.get() ) sib->add_shapes(t, temp, transform); } bez.append(process(t, temp)); } else { for ( auto sib : shapes ) { if ( sib->visible.get() ) { math::bezier::MultiBezier temp; sib->add_shapes(t, temp, transform); bez.append(process(t, temp)); } } } } QPainterPath glaxnimate::model::Modifier::to_painter_path_impl(glaxnimate::model::FrameTime t) const { math::bezier::MultiBezier bez; add_shapes(t, bez, {}); return bez.painter_path(); } QRectF glaxnimate::model::Modifier::local_bounding_rect(glaxnimate::model::FrameTime t) const { return to_painter_path(t).boundingRect(); } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/mask_settings.cpp000664 001750 001750 00000000462 14477652011 030756 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "mask_settings.hpp" GLAXNIMATE_OBJECT_IMPL(glaxnimate::model::MaskSettings) QString glaxnimate::model::MaskSettings::type_name_human() const { return tr("Mask"); } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/shapes/stroke.cpp000664 001750 001750 00000003654 14477652011 030703 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "stroke.hpp" GLAXNIMATE_OBJECT_IMPL(glaxnimate::model::Stroke) void glaxnimate::model::Stroke::on_paint(QPainter* p, glaxnimate::model::FrameTime t, glaxnimate::model::VisualNode::PaintMode, glaxnimate::model::Modifier* modifier) const { QPen pen(brush(t), width.get_at(t)); pen.setCapStyle(Qt::PenCapStyle(cap.get())); pen.setJoinStyle(Qt::PenJoinStyle(join.get())); pen.setMiterLimit(miter_limit.get()); p->setBrush(Qt::NoBrush); p->setPen(pen); p->setOpacity(p->opacity() * opacity.get_at(t)); math::bezier::MultiBezier bez; if ( modifier ) bez = modifier->collect_shapes(t, {}); else bez = collect_shapes(t, {}); p->drawPath(bez.painter_path()); } void glaxnimate::model::Stroke::set_pen_style ( const QPen& pen_style ) { color.set(pen_style.color()); width.set(pen_style.width()); cap.set(glaxnimate::model::Stroke::Cap(pen_style.capStyle())); join.set(glaxnimate::model::Stroke::Join(pen_style.joinStyle())); miter_limit.set(pen_style.miterLimit()); } void glaxnimate::model::Stroke::set_pen_style_undoable(const QPen& pen_style) { color.set_undoable(pen_style.color()); width.set_undoable(pen_style.width()); cap.set_undoable(QVariant::fromValue(glaxnimate::model::Stroke::Cap(pen_style.capStyle()))); join.set_undoable(QVariant::fromValue(glaxnimate::model::Stroke::Join(pen_style.joinStyle()))); miter_limit.set_undoable(pen_style.miterLimit()); } QPainterPath glaxnimate::model::Stroke::to_painter_path_impl(glaxnimate::model::FrameTime t) const { QPainterPathStroker s; s.setWidth(width.get_at(t)); s.setCapStyle(Qt::PenCapStyle(cap.get())); s.setJoinStyle(Qt::PenJoinStyle(join.get())); s.setMiterLimit(miter_limit.get()); return s.createStroke(collect_shapes(t, {}).painter_path()); } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/assets/asset.hpp000664 001750 001750 00000001413 14477652011 030526 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include "model/document_node.hpp" #include "asset_base.hpp" #include "model/property/reference_property.hpp" namespace glaxnimate::model { class Asset : public DocumentNode, public AssetBase { Q_OBJECT public: using DocumentNode::DocumentNode; signals: void users_changed(); protected: int docnode_child_count() const override { return 0; } DocumentNode* docnode_child(int) const override { return nullptr; } int docnode_child_index(DocumentNode*) const override { return -1; } QIcon tree_icon() const override { return instance_icon(); } }; } // namespace glaxnimate::model mlt-7.22.0/src/modules/glaxnimate/glaxnimate/external/QtAppSetup/src/app/settings/settings_group.cpp000664 001750 001750 00000006371 14477652011 035444 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "settings_group.hpp" #include #include "app/settings/widget_builder.hpp" app::settings::SettingsGroup::SettingsGroup(QString slug, utils::TranslatedString label, const QString& icon, SettingList settings) : slug_(std::move(slug)), label_(std::move(label)), icon_(std::move(icon)), settings_(std::move(settings)) { } app::settings::SettingsGroup::SettingsGroup(SettingList settings) : settings_(std::move(settings)) { } QString app::settings::SettingsGroup::slug() const { return slug_; } QString app::settings::SettingsGroup::label() const { return label_; } QIcon app::settings::SettingsGroup::icon() const { return QIcon::fromTheme(icon_); } void app::settings::SettingsGroup::load(QSettings& settings) { auto avail_keys = settings.childKeys(); std::set unprocessed_keys(avail_keys.begin(), avail_keys.end()); for ( const Setting& setting : settings_ ) { unprocessed_keys.erase(setting.slug); values_[setting.slug] = settings.value(setting.slug, setting.default_value); if ( setting.side_effects ) setting.side_effects(values_[setting.slug]); } for ( const QString& key : unprocessed_keys ) values_[key] = settings.value(key); } void app::settings::SettingsGroup::save(QSettings& settings) { for ( const Setting& setting : settings_ ) settings.setValue(setting.slug, setting.get_variant(values_)); } QWidget* app::settings::SettingsGroup::make_widget(QWidget* parent) { return new SettingsGroupWidget(this, parent); } bool app::settings::SettingsGroup::has_visible_settings() const { for ( const auto& set : settings_ ) if ( set.type != Setting::Internal ) return true; return false; } QVariant app::settings::SettingsGroup::get_variant(const QString& setting_slug) const { for ( const Setting& setting : settings_ ) if ( setting.slug == setting_slug ) return setting.get_variant(values_); return {}; } bool app::settings::SettingsGroup::set_variant(const QString& setting_slug, const QVariant& value) { for ( const Setting& setting : settings_ ) { if ( setting.slug == setting_slug ) { if ( !setting.valid_variant(value) ) return false; values_[setting.slug] = value; if ( setting.side_effects ) setting.side_effects(value); return true; } } return false; } QVariant app::settings::SettingsGroup::get_default(const QString& setting_slug) const { for ( const Setting& setting : settings_ ) if ( setting.slug == setting_slug ) return setting.default_value; return {}; } QVariant app::settings::SettingsGroup::define(const QString& setting_slug, const QVariant& default_value) { for ( const Setting& setting : settings_ ) if ( setting.slug == setting_slug ) return setting.get_variant(values_); settings_.push_back(Setting{setting_slug, {}, {}, Setting::Internal, default_value}); auto it = values_.find(setting_slug); if ( it != values_.end() ) return *it; return default_value; } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/svg/css_parser.hpp000664 001750 001750 00000026144 14477652011 030367 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include "detail.hpp" #include #include namespace glaxnimate::io::svg::detail { class CssSelector { public: void set_tag(const QString& tag) { if ( tag != '*' && this->tag.isEmpty() ) specificity += 1; this->tag = tag; } void set_id(const QString& id) { if ( this->id.isEmpty() ) specificity += 100; this->id = id; } void add_class(const QString& class_name) { classes.push_back(class_name); specificity += 10; } bool match(const QDomElement& element, const std::unordered_set& class_names) const { if ( !tag.isEmpty() && tag != "*" && tag != element.tagName() ) return false; if ( !id.isEmpty() && id != element.attribute("id") ) return false; for ( const auto& class_name : classes ) { if ( class_names.count(class_name) == 0 ) return false; } if ( !rule.isEmpty() ) return false; return true; } bool empty() const { return tag.isEmpty() && id.isEmpty() && classes.empty() && rule.isEmpty(); } void set_at_rule(const QString& rule) { this->rule = rule; } const QString& at_rule() const { return this->rule; } private: int specificity = 0; QString tag; QString id; QStringList classes; QString rule; friend struct CssStyleBlock; }; struct CssStyleBlock { CssSelector selector; Style::Map style; void merge_into(Style& output) const { for ( const auto& p : style ) output[p.first] = p.second; } bool operator<(const CssStyleBlock& other) const { return selector.specificity < other.selector.specificity; } }; class CssParser { public: CssParser(std::vector& blocks) : blocks(blocks) { } void parse(const QString& css) { data = css; index = -1; parse_selector(); } private: enum class TokenType { SelectorTag, SelectorClass, SelectorId, SelectorOther, SelectorComma, SelectorAt, BlockBegin, BlockEnd, RuleName, RuleColon, RuleArg, RuleSemicolon, Eof, }; using Token = std::pair; QChar next_ch_raw() { ++index; if ( eof() ) return {}; return data[index]; } bool eof() const { return index >= data.size(); } void back() { if ( !eof() ) --index; } QChar next_ch() { QChar c = next_ch_raw(); // Skip comments if ( c == '/' ) { QChar d = next_ch_raw(); if ( d == '*' ) { while ( true ) { d = next_ch_raw(); if ( eof() ) return {}; if ( d == '*' ) { d = next_ch_raw(); // Treat comments as spaces if ( d == '/' ) return ' '; back(); } } } else { back(); } } return c; } static bool is_identifier_start(const QChar& ch) { return ch.isLetter() || ch == '_' || ch == '-'; } static bool is_identifier(const QChar& ch) { return is_identifier_start(ch) || ch.isNumber(); } QString lex_identifier() { QString id; QChar ch; while ( true ) { ch = next_ch(); if ( is_identifier(ch) ) id += ch; else break; } back(); return id; } QString lex_at_selector() { QString id = "@"; QChar ch; while ( true ) { ch = next_ch(); if ( ch == '{' || ch == ',' ) break; else id += ch; } back(); return id.trimmed(); } Token lex_selector() { QChar ch = next_ch(); if ( eof() ) return {TokenType::Eof, {}}; if ( is_identifier_start(ch) ) return {TokenType::SelectorTag, ch + lex_identifier()}; else if ( ch == '#' ) return {TokenType::SelectorId, lex_identifier()}; else if ( ch == '.' ) return {TokenType::SelectorClass, lex_identifier()}; else if ( ch == ',' ) return {TokenType::SelectorComma, {}}; else if ( ch == '{' ) return {TokenType::BlockBegin, {}}; else if ( ch == '*' ) return {TokenType::SelectorTag, ch}; else if ( ch == '@' ) return {TokenType::SelectorAt, lex_at_selector()}; if ( ch.isSpace() ) { skip_space(); ch = next_ch(); if ( ch == ',' ) return {TokenType::SelectorComma, {}}; if ( ch == '{' ) return {TokenType::BlockBegin, {}}; back(); } return {TokenType::SelectorOther, {}}; } Token ignore_selector() { Token token = {TokenType::Eof, {}}; do { token = lex_selector(); if ( token.first == TokenType::SelectorComma ) return lex_selector(); } while ( token.first != TokenType::Eof && token.first != TokenType::BlockBegin ); return token; } void skip_space() { QChar c; do { c = next_ch(); } while ( !eof() && c.isSpace() ); back(); } void ignore_block() { Token token = {TokenType::Eof, {}}; do { token = lex_selector(); } while ( token.first != TokenType::Eof && token.first != TokenType::BlockEnd ); } bool parse_selector_step(const Token& token) { if ( token.first == TokenType::SelectorClass ) selectors.back().add_class(token.second); else if ( token.first == TokenType::SelectorId ) selectors.back().set_id(token.second); else if ( token.first == TokenType::SelectorTag ) selectors.back().set_tag(token.second); else if ( token.first == TokenType::SelectorAt ) selectors.back().set_at_rule(token.second); else return false; return true; } void parse_selector() { while ( true ) { skip_space(); selectors.clear(); Token token = lex_selector(); if ( token.first == TokenType::BlockBegin ) { ignore_block(); token = lex_selector(); } while ( true ) { selectors.push_back({}); while ( parse_selector_step(token) ) token = lex_selector(); if ( eof() ) return; if ( token.first == TokenType::BlockBegin ) { if ( selectors.back().empty() ) selectors.pop_back(); break; } if ( token.first != TokenType::SelectorComma ) { token = ignore_selector(); selectors.pop_back(); } else { skip_space(); token = lex_selector(); } } if ( selectors.empty() ) ignore_block(); else parse_block(); } } Token lex_rule() { skip_space(); QChar ch = next_ch(); if ( eof() ) return {TokenType::Eof, {}}; if ( is_identifier_start(ch) ) return {TokenType::RuleName, ch + lex_identifier()}; else if ( ch == ':' ) return {TokenType::RuleColon, {}}; else if ( ch == ';' ) return {TokenType::RuleSemicolon, {}}; else if ( ch == '}' ) return {TokenType::BlockEnd, {}}; return {TokenType::RuleArg, ch}; } Token ignore_rule() { Token token = lex_rule(); while ( token.first != TokenType::Eof && token.first != TokenType::RuleSemicolon && token.first != TokenType::BlockEnd ) token = lex_rule(); return token; } void lex_quoted_string(QString& value, QChar terminator) { while ( true ) { QChar ch = next_ch(); if ( eof() ) break; value += ch; if ( ch == terminator ) break; if ( ch == '\\' ) { ch = next_ch(); if ( eof() ) break; value += ch; } } } Token lex_rule_value(QString& value) { if ( value == "\"" || value == "'" ) lex_quoted_string(value, value[0]); while ( true ) { QChar ch = next_ch(); if ( eof() ) return {TokenType::Eof, {}}; else if ( ch == ';' ) return {TokenType::RuleSemicolon, {}}; else if ( ch == '}' ) return {TokenType::BlockEnd, {}}; value += ch; if ( ch == '"' || ch == '\'' ) { lex_quoted_string(value, ch); } } } void parse_block() { rules.clear(); while ( true ) { Token token = lex_rule(); if ( eof() || token.first == TokenType::BlockEnd ) break; if ( token.first != TokenType::RuleName ) { ignore_rule(); continue; } QString name = token.second; if ( lex_rule().first != TokenType::RuleColon ) { ignore_rule(); continue; } token = lex_rule(); if ( eof() || token.first == TokenType::BlockEnd ) break; if ( token.first == TokenType::RuleSemicolon ) continue; QString value = token.second; token = lex_rule_value(value); if ( !value.isEmpty() ) rules[name] = value.trimmed(); if ( eof() || token.first == TokenType::BlockEnd ) break; } for ( const auto& selector : selectors ) blocks.push_back({selector, rules}); rules.clear(); selectors.clear(); } QString data; int index = 0; std::vector& blocks; std::vector selectors; Style::Map rules; }; } // namespace glaxnimate::io::svg::detail mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/math/ellipse_solver.hpp000664 001750 001750 00000002155 14477652011 030771 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include "bezier/bezier.hpp" namespace glaxnimate::math { class EllipseSolver { public: /** * \param center 2D vector, center of the ellipse * \param radii 2D vector, x/y radius of the ellipse * \param xrot Angle between the main axis of the ellipse and the x axis (in radians) */ EllipseSolver(const QPointF& center, const QPointF& radii, qreal xrot); QPointF point(qreal t) const; QPointF derivative(qreal t) const; bezier::Bezier to_bezier(qreal anglestart, qreal angle_delta); static bezier::Bezier from_svg_arc( QPointF start, qreal rx, qreal ry, qreal xrot, bool large, bool sweep, QPointF dest ); private: static qreal _alpha(qreal step); static QPointF _matrix_mul(qreal phi, const QPointF p, qreal sin_mul=1); static qreal _angle(const QPointF& u, const QPointF& v); QPointF center; QPointF radii; qreal xrot; }; } // namespace glaxnimate::math mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/factory.hpp000664 001750 001750 00000004743 14477652011 027565 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include #include #include #include "app/utils/qstring_hash.hpp" namespace glaxnimate::model { class Object; class Document; class Composition; class ShapeElement; namespace detail { QString naked_type_name(QString class_name); inline QString naked_type_name(const QMetaObject* obj) { return naked_type_name(obj->className()); } template QString naked_type_name() { return naked_type_name(&T::staticMetaObject); } template class InternalFactory { private: class Builder { private: class Holder { public: virtual ~Holder() = default; virtual BaseType* construct(Args... args) const = 0; }; template class ConcreteHolder : public Holder { public: BaseType* construct(Args... args) const override { return new Type(args...); } }; public: template static Builder for_type() { return std::unique_ptr(std::make_unique>()); } BaseType* construct(Args... args) const { return constructor->construct(args...); } private: Builder(std::unique_ptr constructor) : constructor(std::move(constructor)) {} std::unique_ptr constructor; }; public: BaseType* build(const QString& name, Args... args) const { auto it = constructors.find(name); if ( it == constructors.end() ) return nullptr; return it->second.construct(args...); } template bool register_type() { constructors.emplace(detail::naked_type_name(), Builder::template for_type()); return true; } private: std::unordered_map constructors; }; } // namespace detail class Factory : public detail::InternalFactory { public: static Factory& instance() { static Factory instance; return instance; } static Object* static_build(const QString& name, model::Document* doc); private: ~Factory() = default; Factory() = default; Factory(const Factory&) = delete; }; } // namespace glaxnimate::model mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/shapes/offset_path.cpp000664 001750 001750 00000024753 14477652011 031701 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "offset_path.hpp" #include "math/geom.hpp" #include "math/vector.hpp" GLAXNIMATE_OBJECT_IMPL(glaxnimate::model::OffsetPath) using namespace glaxnimate; using namespace glaxnimate::math::bezier; static bool point_fuzzy_compare(const QPointF& a, const QPointF& b) { return qFuzzyCompare(a.x(), b.x()) && qFuzzyCompare(a.y(), b.y()); } /* Simple offset of a linear segment */ static std::pair linear_offset(const QPointF& p1, const QPointF& p2, float amount) { auto angle = math::atan2(p2.x() - p1.x(), p2.y() - p1.y()); auto offset = math::from_polar(amount, -angle); return { p1 + offset, p2 + offset }; } template static std::array offset_polygon(std::array points, float amount) { std::array, size - 1> off_lines; for ( int i = 1; i < size; i++ ) { off_lines[i-1] = linear_offset(points[i-1], points[i], amount); } std::array off_points; off_points[0] = off_lines[0].first; off_points[size - 1] = off_lines.back().second; for ( int i = 1; i < size - 1; i++ ) { off_points[i] = math::line_intersection(off_lines[i-1].first, off_lines[i-1].second, off_lines[i].first, off_lines[i].second).value_or(off_lines[i].first); } return off_points; } /* Offset a bezier segment only works well if the segment is flat enough */ static math::bezier::CubicBezierSolver offset_segment(const math::bezier::CubicBezierSolver& segment, float amount) { bool same01 = point_fuzzy_compare(segment.points()[0], segment.points()[1]); bool same23 = point_fuzzy_compare(segment.points()[2], segment.points()[3]); if ( same01 && same23 ) { auto off = linear_offset(segment.points()[0], segment.points().back(), amount); return {off.first, math::lerp(off.first, off.second, 1./3.), math::lerp(off.first, off.second, 2./3.), off.second}; } else if ( same01 ) { auto poly = offset_polygon<3>({segment.points()[0], segment.points()[2], segment.points()[3]}, amount); return {poly[0], poly[0], poly[1], poly[2]}; } else if ( same23 ) { auto poly = offset_polygon<3>({segment.points()[0], segment.points()[1], segment.points()[3]}, amount); return {poly[0], poly[1], poly[2], poly[2]}; } return offset_polygon<4>(segment.points(), amount); } /* Join two segments */ static QPointF join_lines( Bezier& output_bezier, const CubicBezierSolver& seg1, const CubicBezierSolver& seg2, glaxnimate::model::Stroke::Join line_join, float miter_limit ) { QPointF p0 = seg1.points()[3]; QPointF p1 = seg2.points()[0]; if ( line_join == glaxnimate::model::Stroke::BevelJoin ) return p0; // Connected, they don't need a joint if ( point_fuzzy_compare(p0, p1) ) return p0; auto& last_point = output_bezier.points().back(); if ( line_join == glaxnimate::model::Stroke::RoundJoin ) { auto angle_out = seg1.tangent_angle(1); auto angle_in = seg2.tangent_angle(0) + math::pi; auto offset = math::from_polar(100, angle_out + math::pi / 2); auto center = math::line_intersection(p0, p0 + offset, p1, p1 + offset); auto radius = center ? math::distance(*center, p0) : math::distance(p0, p1) / 2; last_point.tan_out = last_point.pos + math::from_polar(2 * radius * math::ellipse_bezier, angle_out); output_bezier.add_point(p1, math::from_polar(2 * radius * math::ellipse_bezier, angle_in)); return p1; } // Miter auto t0 = point_fuzzy_compare(p0, seg1.points()[2]) ? seg1.points()[0] : seg1.points()[2]; auto t1 = point_fuzzy_compare(p1, seg2.points()[1]) ? seg2.points()[3] : seg2.points()[1]; auto intersection = math::line_intersection(t0, p0, p1, t1); if ( intersection && math::distance(*intersection, p0) < miter_limit ) { output_bezier.add_point(*intersection); return *intersection; } return p0; } static std::optional> get_intersection( const CubicBezierSolver&a, const CubicBezierSolver& b) { auto intersect = a.intersections(b, 2, 3, 7); std::size_t i = 0; if ( !intersect.empty() && qFuzzyCompare(intersect[0].first, 1) ) i++; if ( intersect.size() > i ) return intersect[i]; return {}; } static std::pair>, std::vector>> prune_segment_intersection( const std::vector>& a, const std::vector>& b ) { auto out_a = a; auto out_b = b; auto intersect = get_intersection(a.back(), b[0]); if ( intersect ) { out_a.back() = a.back().split(intersect->first).first; out_b[0] = b[0].split(intersect->second).second; } if ( a.size() > 1 && b.size() > 1 ) { intersect = get_intersection(a[0], b.back()); if ( intersect ) { return { {a[0].split(intersect->first).first}, {b.back().split(intersect->second).second}, }; } } return {out_a, out_b}; } void prune_intersections(std::vector>>& segments) { for ( std::size_t i = 1; i < segments.size() ; i++ ) { std::tie(segments[i-1], segments[i]) = prune_segment_intersection(segments[i - 1], segments[i]); } if ( segments.size() > 1 ) std::tie(segments.back(), segments[0]) = prune_segment_intersection(segments.back(), segments[0]); } static std::vector> split_inflections( const math::bezier::CubicBezierSolver& segment ) { /* We split each bezier segment into smaller pieces based on inflection points, this ensures the control point polygon is convex. (A cubic bezier can have none, one, or two inflection points) */ auto flex = segment.inflection_points(); if ( flex.size() == 0 ) { return {segment}; } else if ( flex.size() == 1 || flex[1] == 1 ) { auto split = segment.split(flex[0]); return { split.first, split.second }; } else { auto split_1 = segment.split(flex[0]); float t = (flex[1] - flex[0]) / (1 - flex[0]); auto split_2 = CubicBezierSolver(split_1.second).split(t); return { split_1.first, split_2.first, split_2.second, }; } } static bool needs_more_split(const math::bezier::CubicBezierSolver& segment) { auto n1 = math::from_polar(1, segment.normal_angle(0)); auto n2 = math::from_polar(1, segment.normal_angle(1)); auto s = QPointF::dotProduct(n1, n2); return math::abs(math::acos(s)) >= math::pi / 3; } static std::vector> offset_segment_split( const math::bezier::CubicBezierSolver& segment, float amount ) { std::vector> offset; offset.reserve(6); for ( const auto& chunk: split_inflections(segment) ) { if ( needs_more_split(chunk) ) { auto split = chunk.split(0.5); offset.push_back(offset_segment(split.first, amount)); offset.push_back(offset_segment(split.second, amount)); } else { offset.push_back(offset_segment(chunk, amount)); } } return offset; } static MultiBezier offset_path( // Beziers as collected from the other shapes const MultiBezier& collected_shapes, float amount, model::Stroke::Join line_join, float miter_limit ) { MultiBezier result; for ( const auto& input_bezier : collected_shapes.beziers() ) { int count = input_bezier.segment_count(); Bezier output_bezier; output_bezier.set_closed(input_bezier.closed()); std::vector>> multi_segments; for ( int i = 0; i < count; i++ ) multi_segments.push_back(offset_segment_split(input_bezier.segment(i), amount)); // Open paths are stroked rather than being simply offset if ( !input_bezier.closed() ) { for ( int i = count - 1; i >= 0; i-- ) multi_segments.push_back(offset_segment_split(input_bezier.inverted_segment(i), amount)); } prune_intersections(multi_segments); // Add bezier segments to the output and apply line joints QPointF last_point; const math::bezier::CubicBezierSolver* last_seg = nullptr; for ( const auto& multi_segment : multi_segments ) { if ( last_seg ) last_point = join_lines(output_bezier, *last_seg, multi_segment[0], line_join, miter_limit * amount); last_seg = &multi_segment.back(); for ( const auto& segment : multi_segment ) { if ( !point_fuzzy_compare(segment.points()[0], last_point) || output_bezier.empty() ) output_bezier.add_point(segment.points()[0]); output_bezier.back().tan_out = segment.points()[1]; output_bezier.add_point(segment.points()[3]); output_bezier.back().tan_in = segment.points()[2]; last_point = segment.points()[3]; } } if ( !multi_segments.empty() ) join_lines(output_bezier, *last_seg, multi_segments[0][0], line_join, miter_limit * amount); result.beziers().push_back(output_bezier); } return result; } QIcon glaxnimate::model::OffsetPath::static_tree_icon() { return QIcon::fromTheme("path-offset-dynamic"); } QString glaxnimate::model::OffsetPath::static_type_name_human() { return tr("Offset Path"); } bool glaxnimate::model::OffsetPath::process_collected() const { return false; } glaxnimate::math::bezier::MultiBezier glaxnimate::model::OffsetPath::process( glaxnimate::model::FrameTime t, const math::bezier::MultiBezier& mbez ) const { if ( mbez.empty() ) return {}; auto amount = this->amount.get_at(t); if ( qFuzzyIsNull(amount) ) return mbez; return offset_path(mbez, amount, join.get(), miter_limit.get_at(t)); } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/assets/network_downloader.cpp000664 001750 001750 00000000241 14477652011 033307 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "network_downloader.hpp" mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/command/animation_commands.cpp000664 001750 001750 00000030576 14477652011 032272 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "animation_commands.hpp" #include "model/document.hpp" glaxnimate::command::SetKeyframe::SetKeyframe( model::AnimatableBase* prop, model::FrameTime time, const QVariant& value, bool commit, bool force_insert ) : Parent(QObject::tr("Update %1 keyframe at %2").arg(prop->name()).arg(time), commit), prop(prop), time(time), before(prop->value(time)), after(value), had_before(prop->has_keyframe(time) && !force_insert), force_insert(force_insert) {} void glaxnimate::command::SetKeyframe::undo() { if ( had_before ) prop->set_keyframe(time, before); else prop->remove_keyframe_at_time(time); if ( insert_index > 0 ) prop->keyframe(insert_index-1)->set_transition(trans_before); } void glaxnimate::command::SetKeyframe::redo() { if ( !calculated ) { auto mid = prop->mid_transition(time); model::AnimatableBase::SetKeyframeInfo info; auto kf = prop->set_keyframe(time, after, &info, force_insert); if ( kf && info.insertion && info.index > 0 && info.index + 1 < prop->keyframe_count() ) { if ( mid.type != model::AnimatableBase::MidTransition::Middle ) { insert_index = -1; } else { insert_index = info.index; auto kf_before = prop->keyframe(info.index - 1); trans_before = kf_before->transition(); left = mid.from_previous; right = mid.to_next; } } } else { prop->set_keyframe(time, after, nullptr, force_insert); } if ( insert_index > 0 ) { prop->keyframe(insert_index-1)->set_transition(left); prop->keyframe(insert_index)->set_transition(right); } } bool glaxnimate::command::SetKeyframe::merge_with(const SetKeyframe& other) { if ( other.prop != prop ) return false; after = other.after; return true; } glaxnimate::command::RemoveKeyframeTime::RemoveKeyframeTime( model::AnimatableBase* prop, model::FrameTime time ) : QUndoCommand(QObject::tr("Remove %1 keyframe at %2").arg(prop->name()).arg(time)), prop(prop), time(time), index(prop->keyframe_index(time)), before(prop->keyframe(index)->value()) { if ( index > 0 ) { prev_transition_after = prev_transition_before = prop->keyframe(index-1)->transition(); if ( !prev_transition_after.hold() ) prev_transition_after.set_after(prop->keyframe(index)->transition().after()); } } void glaxnimate::command::RemoveKeyframeTime::undo() { prop->set_keyframe(time, before); if ( index > 0 ) prop->keyframe(index-1)->set_transition(prev_transition_before); } void glaxnimate::command::RemoveKeyframeTime::redo() { if ( index > 0 ) prop->keyframe(index-1)->set_transition(prev_transition_after); prop->remove_keyframe(index); } glaxnimate::command::RemoveKeyframeIndex::RemoveKeyframeIndex( model::AnimatableBase* prop, int index ) : QUndoCommand(QObject::tr("Remove %1 keyframe %2").arg(prop->name()).arg(index)), prop(prop), index(index), time(prop->keyframe(index)->time()), before(prop->keyframe(index)->value()) { if ( index > 0 ) { prev_transition_after = prev_transition_before = prop->keyframe(index-1)->transition(); if ( !prev_transition_after.hold() ) prev_transition_after.set_after(prop->keyframe(index)->transition().after()); } } void glaxnimate::command::RemoveKeyframeIndex::undo() { prop->set_keyframe(time, before, nullptr, true); if ( index > 0 ) prop->keyframe(index-1)->set_transition(prev_transition_before); } void glaxnimate::command::RemoveKeyframeIndex::redo() { if ( index > 0 ) prop->keyframe(index-1)->set_transition(prev_transition_after); prop->remove_keyframe(index); } glaxnimate::command::SetMultipleAnimated::SetMultipleAnimated(model::AnimatableBase* prop, QVariant after, bool commit) : SetMultipleAnimated( auto_name(prop), {prop}, {}, {after}, commit ) {} glaxnimate::command::SetMultipleAnimated::SetMultipleAnimated( const QString& name, const std::vector& props, const QVariantList& before, const QVariantList& after, bool commit ) : Parent(name, commit), props(props), before(before), after(after), keyframe_after(props[0]->object()->document()->record_to_keyframe()), time(props[0]->time()) { bool add_before = before.empty(); for ( auto prop : props ) { if ( add_before ) this->before.push_back(prop->value()); keyframe_before.push_back(prop->has_keyframe(time)); add_0.push_back(time != 0 && !prop->animated() && prop->object()->document()->record_to_keyframe()); } } glaxnimate::command::SetMultipleAnimated::SetMultipleAnimated(const QString& name, bool commit) : Parent(name, commit) { } void glaxnimate::command::SetMultipleAnimated::push_property(model::AnimatableBase* prop, const QVariant& after_val) { keyframe_after = prop->object()->document()->record_to_keyframe(); time = prop->time(); int insert = props.size(); props.push_back(prop); before.insert(before.begin() + insert, prop->value()); after.insert(after.begin() + insert, after_val); keyframe_before.push_back(prop->has_keyframe(time)); add_0.push_back(!prop->animated() && prop->object()->document()->record_to_keyframe()); } void glaxnimate::command::SetMultipleAnimated::push_property_not_animated(model::BaseProperty* prop, const QVariant& after_val) { props_not_animated.push_back(prop); before.push_back(prop->value()); after.push_back(after_val); } void glaxnimate::command::SetMultipleAnimated::undo() { for ( int i = 0; i < int(props.size()); i++ ) { auto prop = props[i]; if ( add_0[i] ) prop->remove_keyframe_at_time(0); if ( keyframe_after ) { if ( keyframe_before[i] ) { prop->set_keyframe(time, before[i]); } else { prop->remove_keyframe_at_time(time); prop->set_value(before[i]); } } else { if ( keyframe_before[i] ) prop->set_keyframe(time, before[i]); else if ( !prop->animated() || prop->time() == time ) prop->set_value(before[i]); } } for ( int i = 0; i < int(props_not_animated.size()); i++ ) { props_not_animated[i]->set_value(before[i+props.size()]); } } void glaxnimate::command::SetMultipleAnimated::redo() { for ( int i = 0; i < int(props.size()); i++ ) { auto prop = props[i]; if ( add_0[i] ) prop->set_keyframe(0, before[i]); if ( keyframe_after ) prop->set_keyframe(time, after[i]); else if ( !prop->animated() || prop->time() == time ) prop->set_value(after[i]); } for ( int i = 0; i < int(props_not_animated.size()); i++ ) { props_not_animated[i]->set_value(after[i+props.size()]); } } bool glaxnimate::command::SetMultipleAnimated::merge_with(const SetMultipleAnimated& other) { if ( other.props.size() != props.size() || keyframe_after != other.keyframe_after || time != other.time || other.props_not_animated.size() != props_not_animated.size()) return false; for ( int i = 0; i < int(props.size()); i++ ) if ( props[i] != other.props[i] ) return false; for ( int i = 0; i < int(props_not_animated.size()); i++ ) if ( props_not_animated[i] != other.props_not_animated[i] ) return false; after = other.after; return true; } QString glaxnimate::command::SetMultipleAnimated::auto_name(model::AnimatableBase* prop) { bool key_before = prop->has_keyframe(prop->time()); bool key_after = prop->object()->document()->record_to_keyframe(); if ( key_after && !key_before ) return QObject::tr("Add keyframe for %1 at %2").arg(prop->name()).arg(prop->time()); if ( key_before ) return QObject::tr("Update %1 at %2").arg(prop->name()).arg(prop->time()); return QObject::tr("Update %1").arg(prop->name()); } bool glaxnimate::command::SetMultipleAnimated::empty() const { return props.empty() && props_not_animated.empty(); } glaxnimate::command::SetKeyframeTransition::SetKeyframeTransition( model::AnimatableBase* prop, int keyframe_index, const model::KeyframeTransition& transition ) : QUndoCommand(QObject::tr("Update keyframe transition")), prop(prop), keyframe_index(keyframe_index), undo_value(keyframe()->transition()), redo_value(transition) { } glaxnimate::command::SetKeyframeTransition::SetKeyframeTransition( model::AnimatableBase* prop, int keyframe_index, model::KeyframeTransition::Descriptive desc, const QPointF& point, bool before_transition ) : SetKeyframeTransition(prop, keyframe_index, prop->keyframe(keyframe_index)->transition()) { if ( desc == model::KeyframeTransition::Custom ) { if ( before_transition ) redo_value.set_before(point); else redo_value.set_after(point); } else { if ( before_transition ) redo_value.set_before_descriptive(desc); else redo_value.set_after_descriptive(desc); } } void glaxnimate::command::SetKeyframeTransition::undo() { keyframe()->set_transition(undo_value); } void glaxnimate::command::SetKeyframeTransition::redo() { keyframe()->set_transition(redo_value); } glaxnimate::model::KeyframeBase* glaxnimate::command::SetKeyframeTransition::keyframe() const { return prop->keyframe(keyframe_index); } glaxnimate::command::MoveKeyframe::MoveKeyframe( model::AnimatableBase* prop, int keyframe_index, model::FrameTime time_after ) : QUndoCommand(QObject::tr("Move keyframe")), prop(prop), keyframe_index_before(keyframe_index), time_before(prop->keyframe(keyframe_index)->time()), time_after(time_after) {} void glaxnimate::command::MoveKeyframe::undo() { prop->move_keyframe(keyframe_index_after, time_before); } void glaxnimate::command::MoveKeyframe::redo() { keyframe_index_after = prop->move_keyframe(keyframe_index_before, time_after); } int glaxnimate::command::MoveKeyframe::redo_index() const { return keyframe_index_after; } glaxnimate::command::RemoveAllKeyframes::RemoveAllKeyframes(model::AnimatableBase* prop, QVariant after) : QUndoCommand(QObject::tr("Remove animations from %1").arg(prop->name())), prop(prop), before(prop->value()), after(std::move(after)) { int count = prop->keyframe_count(); keyframes.reserve(count); for ( int i = 0; i < count; i++ ) { auto kf = prop->keyframe(i); keyframes.push_back({ kf->time(), kf->value(), kf->transition() }); } } void glaxnimate::command::RemoveAllKeyframes::redo() { prop->clear_keyframes(); prop->set_value(after); } void glaxnimate::command::RemoveAllKeyframes::undo() { for ( const auto& kf : keyframes ) { prop->set_keyframe(kf.time, kf.value, nullptr, true)->set_transition(kf.transition); } prop->set_time(prop->time()); prop->set_value(before); } glaxnimate::command::SetPositionBezier::SetPositionBezier( model::detail::AnimatedPropertyPosition* prop, math::bezier::Bezier after, bool commit, const QString& name ) : SetPositionBezier(prop, prop->bezier(), std::move(after), commit, name) { } glaxnimate::command::SetPositionBezier::SetPositionBezier( model::detail::AnimatedPropertyPosition* prop, math::bezier::Bezier before, math::bezier::Bezier after, bool commit, const QString& name ) : Parent(name.isEmpty() ? QObject::tr("Update animation path") : name, commit), property(prop), before(std::move(before)), after(std::move(after)) { } bool glaxnimate::command::SetPositionBezier::merge_with(const glaxnimate::command::SetPositionBezier& other) { return property == other.property; } void glaxnimate::command::SetPositionBezier::undo() { property->set_bezier(before); } void glaxnimate::command::SetPositionBezier::redo() { property->set_bezier(after); } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/command/object_list_commands.hpp000664 001750 001750 00000006573 14477652011 032621 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include "model/property/object_list_property.hpp" namespace glaxnimate::command { template> class AddObject : public QUndoCommand { public: AddObject( PropT* object_parent, std::unique_ptr object, int position = -1, QUndoCommand* parent = nullptr, const QString& name = {} ) : QUndoCommand(name.isEmpty() ? QObject::tr("Create %1").arg(object->object_name()) : name, parent), object_parent(object_parent), object_(std::move(object)), position(position == -1 ? object_parent->size() : position) {} void undo() override { object_ = object_parent->remove(position); } void redo() override { object_parent->insert(std::move(object_), position); } ItemT* object() const { if ( object_ ) return object_.get(); return (*object_parent)[position]; } private: PropT* object_parent; std::unique_ptr object_; int position; }; template> class RemoveObject : public QUndoCommand { public: RemoveObject(ItemT* object, PropT* object_parent, QUndoCommand* parent = nullptr) : QUndoCommand(QObject::tr("Remove %1").arg(object->object_name()), parent), object_parent(object_parent), position(object_parent->index_of(object, -1)) {} RemoveObject(int index, PropT* object_parent, QUndoCommand* parent = nullptr) : QUndoCommand(QObject::tr("Remove %1").arg((*object_parent)[index]->object_name()), parent), object_parent(object_parent), position(index) {} void undo() override { object_parent->insert(std::move(object), position); } void redo() override { object = object_parent->remove(position); } private: PropT* object_parent; std::unique_ptr object; int position; }; template> class MoveObject : public QUndoCommand { public: MoveObject( ItemT* object, PropT* parent_before, PropT* parent_after, int position_after, QUndoCommand* parent = nullptr ) : QUndoCommand(QObject::tr("Move Object"), parent), parent_before(parent_before), position_before(parent_before->index_of(object, -1)), parent_after(parent_after), position_after(position_after) {} void undo() override { if ( parent_before == parent_after ) parent_before->move(position_before, position_after); else if ( auto object = parent_after->remove(position_after) ) parent_before->insert(std::move(object), position_before); } void redo() override { if ( parent_before == parent_after ) parent_before->move(position_before, position_after); else if ( auto object = parent_before->remove(position_before) ) parent_after->insert(std::move(object), position_after); } private: PropT* parent_before; int position_before; PropT* parent_after; int position_after; }; } // namespace glaxnimate::command mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/command/shape_commands.cpp000664 001750 001750 00000011066 14477652011 031404 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "shape_commands.hpp" #include "model/shapes/group.hpp" #include "model/assets/composition.hpp" #include "model/document.hpp" using namespace glaxnimate; namespace { /** * \returns The parent node for \p shape */ model::VisualNode* shape_parent(model::ShapeElement* shape) { return static_cast(shape->owner()->object()); } /** * \returns The parent node for \p shape */ model::VisualNode* shape_parent(model::VisualNode* shape) { if ( auto se = qobject_cast(shape) ) return shape_parent(se); return nullptr; } /** * \brief Represents a sequence of nested nodes to reach */ struct PathToLayer { PathToLayer() = default; explicit PathToLayer(model::VisualNode* node) { composition = nullptr; while ( node && !composition ) { composition = qobject_cast(node); if ( composition ) break; if ( auto group = qobject_cast(node) ) { steps.push_back(group); node = shape_parent(group); } else { return; } } } std::vector steps; model::Composition* composition = nullptr; model::ShapeListProperty* lowest() const { if ( !steps.empty() ) return &steps.front()->shapes; return &composition->shapes; } model::ShapeListProperty* combine(const PathToLayer& other) { if ( other.composition != composition ) return nullptr; int i = 0; for ( int e = std::min(steps.size(), other.steps.size()); i < e; i++ ) if ( steps[i] != other.steps[i] ) break; if ( i < int(steps.size()) ) steps.erase(steps.begin()+i, steps.end()); return lowest(); } }; } // namespace command::GroupShapes::Data command::GroupShapes::collect_shapes(const std::vector& selection) { if ( selection.empty() ) return {}; Data data; PathToLayer collected; int i = 0; for ( ; i < int(selection.size()) && !data.parent; i++ ) { collected = PathToLayer(shape_parent(selection[i])); data.parent = collected.lowest(); } for ( ; i < int(selection.size()) && data.parent; i++ ) { data.parent = collected.combine(PathToLayer(shape_parent(selection[i]))); if ( !data.parent ) return {}; } data.elements.reserve(selection.size()); for ( auto n : selection ) data.elements.push_back(static_cast(n)); return data; } command::GroupShapes::GroupShapes(const command::GroupShapes::Data& data) : detail::RedoInCtor(QObject::tr("Group Shapes")) { if ( data.parent ) { std::unique_ptr grp = std::make_unique(data.parent->object()->document()); group = grp.get(); data.parent->object()->document()->set_best_name(group); (new AddShape(data.parent, std::move(grp), data.parent->size(), this))->redo(); for ( int i = 0; i < int(data.elements.size()); i++ ) { (new MoveShape(data.elements[i], data.elements[i]->owner(), &group->shapes, i, this))->redo(); } } } void command::detail::RedoInCtor::redo() { if ( !did ) { QUndoCommand::redo(); did = true; } } void command::detail::RedoInCtor::undo() { QUndoCommand::undo(); did = false; } command::UngroupShapes::UngroupShapes(model::Group* group) : detail::RedoInCtor(QObject::tr("Ungroup Shapes")) { int pos = group->owner()->index_of(group); (new RemoveShape(group, group->owner(), this))->redo(); for ( int i = 0, e = group->shapes.size(); i < e; i++ ) { (new MoveShape(group->shapes[0], group->shapes[0]->owner(), group->owner(), pos+i, this))->redo(); } } command::AddShape * command::duplicate_shape ( model::ShapeElement* shape ) { std::unique_ptr new_shape ( static_cast(shape->clone().release()) ); new_shape->refresh_uuid(); new_shape->recursive_rename(); new_shape->set_time(shape->docnode_parent()->time()); return new command::AddShape( shape->owner(), std::move(new_shape), shape->owner()->index_of(shape)+1, nullptr, QObject::tr("Duplicate %1").arg(shape->object_name()) ); } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/external/QtAppSetup/src/app/scripting/js/js_engine.hpp000664 001750 001750 00000006752 14477652011 035117 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include "app/scripting/script_engine.hpp" namespace app::scripting::js { class JsContext : public ScriptExecutionContext { public: JsContext(const ScriptEngine* js_engine) : js_engine(js_engine) { qml_engine.installExtensions(QJSEngine::ConsoleExtension); } QJSValue convert(const QVariant& val) { if ( val.userType() == QMetaType::QVariantMap ) { auto jsv = qml_engine.newObject(); auto cont = val.toMap(); for ( auto it = cont.begin(); it != cont.end(); ++it ) jsv.setProperty(it.key(), convert(*it)); return jsv; } else if ( val.userType() == QMetaType::QVariantList ) { auto cont = val.toList(); auto jsv = qml_engine.newArray(cont.size()); for ( int i = 0; i < cont.size(); i++ ) jsv.setProperty(i, convert(cont[i])); return jsv; } else if ( val.type() == QVariant::Int ) { return QJSValue(val.toInt()); } else if ( val.type() == QVariant::String ) { return QJSValue(val.toString()); } else if ( val.canConvert() ) { QObject* obj = val.value(); if ( obj ) { auto jsv = qml_engine.newQObject(obj); QQmlEngine::setObjectOwnership(obj, QQmlEngine::CppOwnership); return jsv; } } return QJSValue{QJSValue::NullValue}; } void expose(const QString& name, const QVariant& val) override { qml_engine.globalObject().setProperty(name, convert(val)); } QString eval_to_string(const QString& code) override { return qml_engine.evaluate(code+";").toString(); } bool run_from_module ( const QDir& path, ///< Path containing the module file const QString& module, ///< Module name to load const QString& function,///< Function to call const QVariantList& args///< Arguments to pass the function ) override { QStringList import_path = qml_engine.importPathList(); qml_engine.addImportPath(path.path()); QJSValue module_obj = qml_engine.importModule(module); if ( module_obj.isError() || !module_obj.hasProperty(function) ) return false; QJSValue func = module_obj.property(function); if ( !func.isCallable() ) return false; QJSValueList jsargs; for ( const auto& arg : args ) jsargs.push_back(convert(arg)); QJSValue ret = func.call(jsargs); if ( ret.isError() ) throw ScriptError(ret.toString()); qml_engine.setImportPathList(import_path); return true; } const ScriptEngine* engine() const override { return js_engine; } void app_module(const QString&) override {} private: QQmlEngine qml_engine; const ScriptEngine* js_engine; }; class JsEngine : public ScriptEngine { public: QString slug() const override { return "js"; } QString label() const override { return "ECMAScript"; } ScriptContext create_context() const override { return std::make_unique(this); } private: static Autoregister autoreg; }; } // namespace app::scripting::js mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/visitor.cpp000664 001750 001750 00000001561 14477652011 027603 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "visitor.hpp" #include "model/document_node.hpp" #include "model/document.hpp" #include "model/assets/assets.hpp" #include "model/assets/composition.hpp" void glaxnimate::model::Visitor::visit(glaxnimate::model::Document* doc, model::Composition* main, bool skip_locked) { on_visit(doc, main); visit(doc->assets(), skip_locked); on_visit_end(doc, main); } void glaxnimate::model::Visitor::visit(glaxnimate::model::DocumentNode* node, bool skip_locked) { if ( skip_locked ) { auto visual = node->cast(); if ( visual && visual->locked.get() ) return; } on_visit(node); for ( auto ch : node->docnode_children() ) visit(ch, skip_locked); on_visit_end(node); } modules/glaxnimate/glaxnimate/external/QtAppSetup/src/app/scripting/python/register_machinery.hpp000664 001750 001750 00000007510 14477652011 037660 0ustar00ddennedyddennedy000000 000000 mlt-7.22.0/src/* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include #include #include #include "app/scripting/python/casters.hpp" #include "app/scripting/python/attribute.hpp" namespace py = pybind11; namespace app::scripting::python { struct PY_HIDDEN PyPropertyInfo { const char* name = nullptr; py::cpp_function get; py::cpp_function set; }; struct PY_HIDDEN PyMethodInfo { const char* name = nullptr; py::cpp_function method; }; struct PY_HIDDEN PyEnumInfo { const char* name = nullptr; py::handle enum_handle; }; PyPropertyInfo register_property(const QMetaProperty& prop, const QMetaObject& cls); PyMethodInfo register_method(const QMetaMethod& meth, py::handle& handle, const QMetaObject& cls); template PyEnumInfo register_enum(const QMetaEnum& meta, py::handle& scope) { py::enum_ pyenum(scope, meta.name()); for ( int i = 0; i < meta.keyCount(); i++ ) pyenum.value(meta.key(i), EnumT(meta.value(i))); return {meta.name(), pyenum}; } template struct enums; template struct enums : public enums { void process(py::handle& scope, std::vector& out) { out.push_back(register_enum(QMetaEnum::fromType(), scope)); enums::process(scope, out); } }; template<> struct enums<> { void process(py::handle&, std::vector&) {} }; template py::class_ declare_from_meta(py::handle scope) { const QMetaObject& meta = CppClass::staticMetaObject; const char* name = meta.className(); const char* clean_name = std::strrchr(name, ':'); if ( clean_name == nullptr ) clean_name = name; else clean_name++; return py::class_ (scope, clean_name); } template py::class_ register_from_meta(py::handle scope, enums reg_enums = {}) { py::class_ reg = declare_from_meta(scope); register_from_meta(reg, reg_enums); return reg; } template py::class_& register_from_meta(py::class_& reg, enums reg_enums = {}) { const QMetaObject& meta = CppClass::staticMetaObject; for ( int i = meta.propertyOffset(); i < meta.propertyCount(); i++ ) { PyPropertyInfo pyprop = register_property(meta.property(i), meta); if ( pyprop.name ) reg.def_property(pyprop.name, pyprop.get, pyprop.set, ""); } for ( int i = meta.methodOffset(); i < meta.methodCount(); i++ ) { PyMethodInfo pymeth = register_method(meta.method(i), reg, meta); if ( pymeth.name ) reg.attr(pymeth.name) = pymeth.method; } if ( meta.classInfoOffset() < meta.classInfoCount() ) { py::dict classinfo; for ( int i = meta.classInfoOffset(); i < meta.classInfoCount(); i++ ) { auto info = meta.classInfo(i); classinfo[info.name()] = info.value(); } reg.attr("__classinfo__") = classinfo; } std::vector enum_info; reg_enums.process(reg, enum_info); for ( const auto& info : enum_info ) reg.attr(info.name) = info.enum_handle; return reg; } namespace detail { template QString qdebug_operator_to_string(const T& o) { QString data; QDebug(&data) << o; return data; } } // namespace detail template auto qdebug_operator_to_string() { return &detail::qdebug_operator_to_string; } } // namespace app::scripting::python mlt-7.22.0/src/modules/glaxnimate/glaxnimate/external/QtAppSetup/src/app/env.hpp000664 001750 001750 00000006541 14477652011 031324 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include #include #ifdef Q_OS_WIN # define ENV_SEPARATOR ";" #else # define ENV_SEPARATOR ":" #endif namespace app { class Environment { public: class Variable { public: explicit Variable(const char* name) : name(name) {} operator const QString&() const { ensure_loaded(); return value; } const QString& get(const QString& default_value = {}) const { ensure_loaded(default_value); return value; } bool is_set() const { return qEnvironmentVariableIsSet(name); } void operator=(const QString& value) const { set(value); } void set(const QString& value) const { this->value = value; loaded = true; qputenv(name, value.toUtf8()); } void erase() const { value.clear(); loaded = true; qunsetenv(name); } const QString& load(const QString& default_value = {}) const { value = qEnvironmentVariable(name, default_value); loaded = true; return value; } bool operator==(const QString& val) const { return get() == val; } bool operator!=(const QString& val) const { return get() != val; } void push_back(const QString& item, const QString& separator=ENV_SEPARATOR) { ensure_loaded(); if ( !value.isEmpty() ) value += separator; value += item; qputenv(name, value.toUtf8()); } void push_back(const QStringList& items, const QString& separator=ENV_SEPARATOR) { if ( items.empty() ) return; push_back(items.join(separator), separator); } void push_front(const QString& item, const QString& separator=ENV_SEPARATOR) { ensure_loaded(); QString new_value = item; if ( !value.isEmpty() ) value += separator; new_value += value; set(new_value); } void push_front(const QStringList& items, const QString& separator=ENV_SEPARATOR) { if ( items.empty() ) return; push_front(items.join(separator), separator); } QStringList to_list(const QString& separator=ENV_SEPARATOR) const { ensure_loaded(); return value.split(separator); } bool empty() const { ensure_loaded({}); return value.isEmpty(); } private: void ensure_loaded(const QString& default_value = {}) const { if ( !loaded ) load(default_value); } QByteArray name; mutable QString value; mutable bool loaded = false; }; Variable operator[](const char* name) { return Variable(name); } bool contains(const char* name) { return qEnvironmentVariableIsSet(name); } void erase(const char* name) { qunsetenv(name); } }; } // namespace app mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/animated_properties.hpp000664 001750 001750 00000016153 14477652011 031461 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include "math/bezier/bezier.hpp" #include "model/animation/keyframe_transition.hpp" #include "model/animation/frame_time.hpp" #include "model/animation/join_animatables.hpp" namespace glaxnimate::io::detail { struct ValueVariant { public: enum Type { Vector, Bezier, String, Color }; ValueVariant(qreal v) : value_(std::vector{v}) {} ValueVariant(std::vector v = {}) : value_(std::move(v)) {} ValueVariant(math::bezier::MultiBezier v ) : value_(std::move(v)) {} ValueVariant(QString v) : value_(std::move(v)) {} ValueVariant(QColor v) : value_(std::move(v)) {} ValueVariant(const QVariant& v) { if ( v.userType() == QMetaType::QColor ) value_ = v.value(); else if ( v.userType() == QMetaType::QString ) value_ = v.value(); else if ( v.canConvert() ) value_ = std::vector(1, v.toReal()); } Type type() const { return Type(value_.index()); } qreal scalar() const { return vector()[0]; } const std::vector& vector() const { return std::get(value_); } const math::bezier::MultiBezier& bezier() const { return std::get(value_); } const QString& string() const { return std::get(value_); } const QColor& color() const { return std::get(value_); } ValueVariant lerp(const ValueVariant& other, qreal t) const { if ( type() != other.type() ) return *this; switch ( type() ) { case Type::Vector: return math::lerp(vector(), other.vector(), t); case Type::Bezier: if ( bezier().size() == 1 && other.bezier().size() == 1 ) { math::bezier::MultiBezier mb; mb.beziers().push_back(bezier()[0].lerp(other.bezier()[0], t)); return mb; } return *this; case Type::String: return t < 1 ? string() : other.string(); case Type::Color: return math::lerp(color(), other.color(), t); } return {}; } bool compatible(const ValueVariant& other) const { if ( type() != other.type() ) return false; if ( type() == Vector ) return vector().size() == other.vector().size(); return true; } private: std::variant, math::bezier::MultiBezier, QString, QColor> value_; }; struct PropertyKeyframe { model::FrameTime time; ValueVariant values; model::KeyframeTransition transition; bool operator< (const PropertyKeyframe& o) const { return time < o.time; } }; struct JoinedPropertyKeyframe { model::FrameTime time; std::vector values; model::KeyframeTransition transition; }; struct AnimatedProperty { std::vector keyframes; math::bezier::Bezier motion; bool auto_orient = false; void sort() { std::sort(keyframes.begin(), keyframes.end()); } }; struct JoinedProperty { std::variant prop; int index = 0; bool at_end() const { if ( prop.index() == 0 ) return index + 1 == int(std::get<0>(prop)->keyframes.size()); return true; } const PropertyKeyframe* keyframe(int off = 0) const { return &std::get<0>(prop)->keyframes[index+off]; } template decltype(auto) get() const noexcept { return std::get(prop); } }; struct AnimatedProperties { std::map properties; virtual ~AnimatedProperties() {} virtual bool prepare_joined(std::vector&) const { return true; } bool has(const QString& name) const { return properties.count(name); } std::vector single(const QString& prop_name) const { auto it = properties.find(prop_name); if ( it == properties.end() || it->second.keyframes.size() < 2 ) return {}; return it->second.keyframes; } std::vector joined(const std::vector& prop_names) const { std::vector props; props.reserve(prop_names.size()); int found = 0; for ( const auto& name : prop_names ) { auto it = properties.find(name); if ( it == properties.end() || it->second.keyframes.size() < 2 ) { props.push_back({&name}); } else { props.push_back({&it->second}); found++; } } if ( !found ) return {}; if ( !prepare_joined(props) ) return {}; std::vector keyframes; bool cont = true; while ( cont ) { model::FrameTime time = std::numeric_limits::max(); for ( const auto& p : props ) { if ( p.prop.index() == 0 && p.keyframe()->time < time ) time = p.keyframe()->time; } std::vector values; values.reserve(props.size()); std::vector transitions; transitions.resize(found); cont = false; for ( auto& p : props ) { if ( p.prop.index() == 0 ) { auto kf = p.keyframe(); if ( (p.at_end() && kf->time <= time) || (p.index == 0 && kf->time > time) ) { values.push_back(kf->values); } else if ( kf->time == time ) { p.index++; values.push_back(kf->values); transitions.push_back(kf->transition); cont = true; } else { auto kf1 = p.keyframe(-1); qreal x = math::unlerp(kf1->time, kf->time, time); qreal t = kf1->transition.lerp_factor(x); values.push_back(kf1->values.lerp(kf->values, t)); transitions.push_back(kf1->transition.split(x).first); cont = true; } } else { values.push_back(p.get<2>()); } } keyframes.push_back({time, std::move(values), model::JoinAnimatables::Keyframe::mix_transitions(transitions)}); } return keyframes; } }; } // namespace glaxnimate::io::detail mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/utils/trace_wrapper.hpp000664 001750 001750 00000003274 14477652011 031012 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include "math/bezier/bezier.hpp" #include "model/shapes/image.hpp" #include "model/shapes/group.hpp" #include "utils/trace.hpp" namespace glaxnimate::utils::trace { class TraceWrapper : public QObject { Q_OBJECT public: struct TraceResult { QColor color; math::bezier::MultiBezier bezier; std::vector rects; }; enum Preset { ComplexPreset, FlatPreset, PixelPreset, }; explicit TraceWrapper(model::Image* image); explicit TraceWrapper(model::Composition* comp, const QImage& image, const QString& name); ~TraceWrapper(); void trace_mono(const QColor& color, bool inverted, int alpha_threshold, std::vector& result); void trace_exact(const std::vector& colors, int tolerance, std::vector& result); void trace_closest(const std::vector& colors, std::vector& result); void trace_pixel(std::vector& result); model::Group* apply(std::vector& result, qreal stroke_width); QSize size() const; TraceOptions& options(); const QImage& image() const; const std::vector& eem_colors() const; Preset preset_suggestion() const; void trace_preset(Preset preset, int complex_posterization, std::vector &colors, std::vector& result); signals: void progress_max_changed(int max); void progress_changed(int value); private: class Private; std::unique_ptr d; }; } // namespace glaxnimate::utils::trace mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/math/bezier/meta.hpp000664 001750 001750 00000001124 14477652011 030143 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include "bezier.hpp" QDataStream& operator<<(QDataStream& ds, const glaxnimate::math::bezier::Point& p); QDataStream& operator<<(QDataStream& ds, const glaxnimate::math::bezier::Bezier& bez); QDataStream& operator>>(QDataStream& ds, glaxnimate::math::bezier::Point& p); QDataStream& operator>>(QDataStream& ds, glaxnimate::math::bezier::Bezier& bez); namespace glaxnimate::math::bezier { void register_meta(); } // namespace glaxnimate::math::bezier mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/rive/rive_exporter.hpp000664 001750 001750 00000051650 14477652011 031266 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include "rive_serializer.hpp" #include "model/document.hpp" #include "model/assets/assets.hpp" #include "model/shapes/precomp_layer.hpp" #include "model/shapes/rect.hpp" #include "model/shapes/ellipse.hpp" #include "model/shapes/polystar.hpp" #include "model/shapes/path.hpp" #include "model/shapes/fill.hpp" #include "model/shapes/stroke.hpp" #include "model/shapes/image.hpp" #include "rive_format.hpp" namespace glaxnimate::io::rive { namespace detail { inline const QVariant& noop(const QVariant& v, model::FrameTime) { return v; } } // namespace name class RiveExporter { public: explicit RiveExporter(QIODevice* file, ImportExport* format) : serializer(file), format(format) { serializer.write_header(7, 0, 0); serializer.write_property_table({}); write_object(TypeId::Backboard); } void write_document(model::Document* document) { write_assets(document->assets()->images.get()); for ( const auto& comp : document->assets()->compositions->values ) write_composition(comp.get(), comp->size()); } private: void write_assets(const model::BitmapList* assets) { for ( const auto& image : assets->values ) { write_bitmap(image.get()); } } void write_bitmap(model::Bitmap* image) { auto name = image->name.get(); if ( name.isEmpty() ) name = image->filename.get(); // idk what this is used for, let's just set it to a unique value the lazy way Identifier asset_id = reinterpret_cast(image); auto obj = types.object(TypeId::ImageAsset); if ( !obj ) return; object_ids[image] = next_asset++; obj.set("name", name); obj.set("width", image->width.get()); obj.set("height", image->height.get()); obj.set("assetId", asset_id); serializer.write_object(obj); auto data = image->image_data(); if ( !data.isEmpty() ) { auto contents = types.object(TypeId::FileAssetContents); if ( !contents ) return; obj.set("bytes", data); } } bool write_object(TypeId type, const QVariantMap& props = {}) { auto obj = types.object(type); if ( !obj ) return false; for ( auto it = props.begin(); it != props.end(); ++it ) obj.set(it.key(), *it); serializer.write_object(obj); return true; } void write_composition(model::Composition* comp, QSizeF size) { object_ids[comp] = next_artboard++; next_artboard_child = 1; animations.clear(); if ( !write_object(TypeId::Artboard, { {"name", comp->name.get()}, {"width", size.width()}, {"height", size.height()}, {"x", (24 + size.width()) * (next_artboard - 1)} }) ) return; for ( const auto& shape : comp->shapes ) write_shape(shape.get(), 0); write_object(TypeId::LinearAnimation, {{"loopValue", 1}}); for ( const auto& anim : animations ) { write_object(TypeId::KeyedObject, {{"objectId", QVariant::fromValue(anim.first)}}); for ( const auto& obj : anim.second ) serializer.write_object(obj); } write_object(TypeId::StateMachine, {}); write_object(TypeId::StateMachineLayer, {}); write_object(TypeId::AnimationState, {{"animationId", 0}}); write_object(TypeId::EntryState, {}); write_object(TypeId::StateTransition, {{"stateToId", 0}}); write_object(TypeId::AnyState, {}); write_object(TypeId::ExitState, {}); } void write_shape(model::ShapeElement* element, Identifier parent_id) { auto id = next_artboard_child++; object_ids[element] = id; if ( auto layer = element->cast() ) { auto object = shape_object(TypeId::Node, element, parent_id); write_group(object, layer, id); } else if ( auto group = element->cast() ) { auto object = shape_object(TypeId::Shape, element, parent_id); write_group(object, group, id); } else if ( auto shape = element->cast() ) { write_rect(shape, id, parent_id); } else if ( auto shape = element->cast() ) { write_ellipse(shape, id, parent_id); } else if ( auto shape = element->cast() ) { write_polystar(shape, id, parent_id); } else if ( auto shape = element->cast() ) { auto object = shape_object(TypeId::Fill, element, parent_id); object.set("isVisible", shape->visible.get()); /// \todo fillRule serializer.write_object(object); write_styler(shape, id); } else if ( auto shape = element->cast() ) { auto object = shape_object(TypeId::Stroke, element, parent_id); write_property(object, "thickness", shape->width, id, &detail::noop); object.set("isVisible", shape->visible.get()); /// \todo cap + join serializer.write_object(object); write_styler(shape, id); } else if ( auto shape = element->cast() ) { auto object = shape_object(TypeId::Image, element, parent_id); write_transform(object, shape->transform.get(), id, shape->local_bounding_rect(0)); auto asset_id = object_ids.find(shape->image.get()); if ( asset_id != object_ids.end() ) object.set("assetId", asset_id->second); serializer.write_object(object); } else if ( auto shape = element->cast() ) { write_precomp_layer(shape, id, parent_id); } else if ( auto shape = element->cast() ) { write_path(shape, id, parent_id); } else { serializer.write_object(shape_object(TypeId::Shape, element, parent_id)); } } void write_rect(model::Rect* shape, Identifier id, Identifier parent_id) { auto object = shape_object(TypeId::Rectangle, shape, parent_id); write_position(object, shape->position, id); write_property(object, "width", shape->size, id, [](const QVariant& v, model::FrameTime) { return QVariant::fromValue(v.toSizeF().width()); } ); write_property(object, "height", shape->size, id, [](const QVariant& v, model::FrameTime) { return QVariant::fromValue(v.toSizeF().height()); } ); write_property(object, "cornerRadiusTL", shape->rounded, id, &detail::noop); write_property(object, "cornerRadiusTR", shape->rounded, id, &detail::noop); write_property(object, "cornerRadiusBL", shape->rounded, id, &detail::noop); write_property(object, "cornerRadiusBR", shape->rounded, id, &detail::noop); serializer.write_object(object); } void write_ellipse(model::Ellipse* shape, Identifier id, Identifier parent_id) { auto object = shape_object(TypeId::Ellipse, shape, parent_id); write_position(object, shape->position, id); write_property(object, "width", shape->size, id, [](const QVariant& v, model::FrameTime) { return QVariant::fromValue(v.toSizeF().width()); } ); write_property(object, "height", shape->size, id, [](const QVariant& v, model::FrameTime) { return QVariant::fromValue(v.toSizeF().height()); } ); serializer.write_object(object); } void write_polystar(model::PolyStar* shape, Identifier id, Identifier parent_id) { auto type = shape->type.get() == model::PolyStar::Star ? TypeId::Star : TypeId::Polygon; auto object = shape_object(type, shape, parent_id); /// \todo cornerRadius write_position(object, shape->position, id); write_property(object, "points", shape->points, id, &detail::noop); write_property(object, "width", shape->outer_radius, id, &detail::noop); write_property(object, "height", shape->outer_radius, id, &detail::noop); if ( type == TypeId::Star ) { write_property(object, "innerRadius", shape->inner_radius, id, [shape](const QVariant& v, model::FrameTime t) { auto outer = shape->outer_radius.get_at(t); return QVariant::fromValue(qFuzzyIsNull(outer) ? 0 : v.toDouble() / outer); } ); } serializer.write_object(object); } void write_precomp_layer(model::PreCompLayer* shape, Identifier id, Identifier parent_id) { auto object = shape_object(TypeId::Rectangle, shape, parent_id); write_transform(object, shape->transform.get(), id, shape->local_bounding_rect(0)); write_property(object, "opacity", shape->opacity, id, &detail::noop); if ( auto comp = shape->composition.get() ) { Identifier comp_index = 1; for ( const auto& declared_comp : shape->document()->assets()->compositions->values ) { if ( declared_comp.get() == comp ) break; comp_index++; } object.set("artboardId", comp_index); } serializer.write_object(object); } void write_path(model::Path* shape, Identifier id, Identifier parent_id) { auto object = shape_object(TypeId::PointsPath, shape, parent_id); object.set("isClosed", shape->closed.get()); serializer.write_object(object); auto first_point_id = next_artboard_child; auto animated = shape->shape.keyframe_count() > 1; for ( const auto& point: shape->shape.get() ) { Object pobj; auto pto = point.polar_tan_out(); auto pti = point.polar_tan_in(); if ( animated || ( point.type == math::bezier::PointType::Corner && (!qFuzzyIsNull(pto.length) || !qFuzzyIsNull(pti.length)) ) ) { pobj = types.object(TypeId::CubicDetachedVertex); pobj.set("outRotation", pto.angle); pobj.set("outDistance", pto.length); pobj.set("inRotation", pti.angle); pobj.set("inDistance", pti.length); } else if ( point.type == math::bezier::PointType::Symmetrical) { pobj = types.object(TypeId::CubicMirroredVertex); pobj.set("rotation", pto.angle); pobj.set("distance", pto.length); } else if ( point.type == math::bezier::PointType::Smooth ) { pobj = types.object(TypeId::CubicAsymmetricVertex); pobj.set("rotation", pto.angle); pobj.set("outDistance", pto.length); pobj.set("inDistance", pti.length); } else { pobj = types.object(TypeId::StraightVertex); } pobj.set("parentId", id); pobj.set("x", point.pos.x()); pobj.set("y", point.pos.y()); serializer.write_object(pobj); next_artboard_child++; } if ( animated ) { auto type = types.get_type(TypeId::CubicDetachedVertex); const Identifier prop_x = 0; const Identifier prop_y = 1; const Identifier prop_in_rot = 2; const Identifier prop_in_len = 3; const Identifier prop_out_rot = 4; const Identifier prop_out_len = 5; auto kf_type = types.get_type(TypeId::KeyFrameDouble); std::array>, 6> props_template{{ {type->property("x")->id, {}}, {type->property("y")->id, {}}, {type->property("inRotation")->id, {}}, {type->property("inDistance")->id, {}}, {type->property("outRotation")->id, {}}, {type->property("outDistance")->id, {}} }}; int point_count = next_artboard_child - first_point_id; for ( auto pt_id = 0; pt_id < point_count; pt_id++ ) { auto props = props_template; for ( const auto& kf : shape->shape ) { if ( int(pt_id) >= kf.get().size() ) { format->error(QObject::tr("Bezier has mismatching number of points")); continue; } for ( auto& prop: props ) { Object rive_kf(kf_type); /// \todo interpolations rive_kf.set("interpolationType", 1); rive_kf.set("frame", kf.time()); prop.second.push_back(std::move(rive_kf)); } auto point = kf.get()[pt_id]; auto pto = point.polar_tan_out(); auto pti = point.polar_tan_in(); props[prop_x].second.back().set("value", point.pos.x()); props[prop_y].second.back().set("value", point.pos.y()); props[prop_in_rot].second.back().set("value", pti.angle); props[prop_in_len].second.back().set("value", pti.length); props[prop_out_rot].second.back().set("value", pto.angle + math::pi); props[prop_out_len].second.back().set("value", pto.length); } auto& keyed_point = animations[first_point_id + pt_id]; for ( const auto& prop : props ) { keyed_point.emplace_back(types.get_type(TypeId::KeyedProperty)); keyed_point.back().set("propertyKey", prop.first); for ( auto& rkf : prop.second ) keyed_point.emplace_back(std::move(rkf)); } } } } Object shape_object(TypeId type_id, model::DocumentNode* shape, Identifier parent_id) { auto object = types.object(type_id); object.set("name", shape->name.get()); object.set("parentId", parent_id); return object; } void write_group(Object& object, model::Group* group, Identifier id) { write_property(object, "opacity", group->opacity, id, &detail::noop); write_transform(object, group->transform.get(), id, group->local_bounding_rect(0)); serializer.write_object(object); for ( const auto& shape : group->shapes ) write_shape(shape.get(), id); } template void write_property( Object& object, const QString& name, const model::AnimatedProperty& prop, Identifier object_id, const FuncT& transform) { auto rive_prop = object.type().property(name); if ( !rive_prop ) { format->warning(QObject::tr("Unknown property %1 of %2 (%3, %4)") .arg(name) .arg(int(object.type().id)) .arg(types.type_name(object.type().id)) .arg(prop.object()->type_name_human()) ); return; } object.set(rive_prop, transform(prop.value(), 0)); if ( !prop.animated() ) return; const ObjectType* kf_type = nullptr; QString attr; switch ( rive_prop->type ) { case PropertyType::Float: case PropertyType::VarUint: attr = "value"; kf_type = types.get_type(TypeId::KeyFrameDouble); break; case PropertyType::Color: attr = "colorValue"; kf_type = types.get_type(TypeId::KeyFrameColor); break; default: break; } if ( !kf_type ) { format->warning(QObject::tr("Unknown keyframe type for property %1 of %2 (%3, %4)") .arg(name) .arg(int(object.type().id)) .arg(types.type_name(object.type().id)) .arg(prop.object()->type_name_human()) ); return; } auto& keyed_object = animations[object_id]; auto keyed_prop = types.object(TypeId::KeyedProperty); keyed_prop.set("propertyKey", rive_prop->id); keyed_object.emplace_back(std::move(keyed_prop)); for ( const auto& kf : prop ) { Object rive_kf(kf_type); /// \todo interpolations rive_kf.set("interpolationType", 1); rive_kf.set(attr, transform(kf.value(), kf.time())); rive_kf.set("frame", kf.time()); keyed_object.emplace_back(std::move(rive_kf)); } } void write_position(Object& object, const model::AnimatedProperty& prop, Identifier object_id) { write_property(object, "x", prop, object_id, [](const QVariant& v, model::FrameTime) { return QVariant::fromValue(v.toPointF().x()); } ); write_property(object, "y", prop, object_id, [](const QVariant& v, model::FrameTime) { return QVariant::fromValue(v.toPointF().y()); } ); } void write_transform(Object& object, model::Transform* trans, Identifier object_id, const QRectF& box) { if ( object.type().property("originX") ) { write_position(object, trans->position, object_id); if ( box.width() > 0 ) { write_property(object, "originX", trans->anchor_point, object_id, [&box](const QVariant& v, model::FrameTime) { return QVariant::fromValue( (v.toPointF().x() - box.left()) / box.width() ); } ); } if ( box.height() > 0 ) { write_property(object, "originY", trans->anchor_point, object_id, [&box](const QVariant& v, model::FrameTime) { return QVariant::fromValue( (v.toPointF().y() - box.top()) / box.height() ); } ); } } else { /// \todo Handle animated anchor point auto anchor = trans->anchor_point.get(); write_property(object, "x", trans->position, object_id, [anchor](const QVariant& v, model::FrameTime) { return QVariant::fromValue(v.toPointF().x() - anchor.x()); } ); write_property(object, "y", trans->position, object_id, [anchor](const QVariant& v, model::FrameTime) { return QVariant::fromValue(v.toPointF().y() - anchor.y()); } ); } write_property(object, "rotation", trans->rotation, object_id, &detail::noop); write_property(object, "scaleX", trans->scale, object_id, [](const QVariant& v, model::FrameTime) { return QVariant::fromValue(v.value().x()); } ); write_property(object, "scaleY", trans->scale, object_id, [](const QVariant& v, model::FrameTime) { return QVariant::fromValue(v.value().x()); } ); } void write_styler(model::Styler* shape, Identifier object_id) { auto use = shape->use.get(); auto id = next_artboard_child++; if ( auto grad = use->cast() ) { auto object = shape_object( grad->type.get() == model::Gradient::Radial ? TypeId::RadialGradient : TypeId::LinearGradient, grad, object_id ); write_property(object, "opacity", shape->color, id, &detail::noop); serializer.write_object(object); /// \todo finish } else if ( auto col = use->cast() ) { auto object = shape_object(TypeId::SolidColor, col, object_id); write_property(object, "colorValue", col->color, id, &detail::noop); serializer.write_object(object); } else { auto object = shape_object(TypeId::SolidColor, shape, object_id); write_property(object, "colorValue", shape->color, id, &detail::noop); serializer.write_object(object); } } Identifier next_asset = 0; Identifier next_artboard = 0; Identifier next_artboard_child = 0; std::unordered_map object_ids; RiveSerializer serializer; ImportExport* format; std::unordered_map> animations; TypeSystem types; }; } // namespace glaxnimate::io::rive mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/animation/animatable.hpp000664 001750 001750 00000074440 14477652011 032173 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include #include #include #include "model/animation/keyframe_transition.hpp" #include "model/property/property.hpp" #include "math/math.hpp" #include "math/bezier/point.hpp" #include "math/bezier/solver.hpp" #include "math/bezier/bezier_length.hpp" /* * Arguments: type, name, default, emitter, flags * For float: type, name, default, emitter, min, max, flags */ #define GLAXNIMATE_ANIMATABLE(type, name, ...) \ public: \ glaxnimate::model::AnimatedProperty name{this, #name, __VA_ARGS__}; \ glaxnimate::model::AnimatableBase* get_##name() { return &name; } \ private: \ Q_PROPERTY(glaxnimate::model::AnimatableBase* name READ get_##name) \ Q_CLASSINFO(#name, "property animated " #type) \ // macro end namespace glaxnimate::model { class KeyframeBase : public QObject { Q_OBJECT Q_PROPERTY(QVariant value READ value) Q_PROPERTY(double time READ time) public: explicit KeyframeBase(FrameTime time) : time_ { time } {} virtual ~KeyframeBase() = default; virtual QVariant value() const = 0; virtual bool set_value(const QVariant& value) = 0; FrameTime time() const { return time_; } void set_time(FrameTime t) { time_ = t; } /** * \brief Transition into the next value */ const KeyframeTransition& transition() const { return transition_; } void set_transition(const KeyframeTransition& trans) { transition_ = trans; emit transition_changed(transition_.before_descriptive(), transition_.after_descriptive()); } void stretch_time(qreal multiplier) { time_ *= multiplier; } /** * \brief Splits a keyframe into multiple segments * \param other The keyframe following this * \param splits Array of splits in [0, 1], indicating the fractions at which splits shall occur * \pre \p other must be the same type of keyframe as \b this * \returns An array of keyframes matching the splits, * this will include a copy of \p other, which might have been modified slightly. * This should be used as \p this for the next keyframe */ std::vector> split(const KeyframeBase* other, std::vector splits) const; std::unique_ptr clone() const { auto clone = do_clone(); clone->set_transition(transition_); return clone; } signals: void transition_changed(KeyframeTransition::Descriptive before, KeyframeTransition::Descriptive after); protected: virtual std::unique_ptr do_clone() const = 0; class KeyframeSplitter { public: virtual ~KeyframeSplitter() = default; virtual void step(const QPointF& p) = 0; virtual std::unique_ptr left(const QPointF& p) const = 0; virtual std::unique_ptr right(const QPointF& p) const = 0; virtual std::unique_ptr last() const = 0; }; virtual std::unique_ptr splitter(const KeyframeBase* other) const = 0; private: FrameTime time_; KeyframeTransition transition_; }; class AnimatableBase : public QObject, public BaseProperty { Q_OBJECT Q_PROPERTY(int keyframe_count READ keyframe_count) Q_PROPERTY(QVariant value READ value WRITE set_undoable) Q_PROPERTY(bool animated READ animated) public: enum KeyframeStatus { NotAnimated, ///< Value is not animated Tween, ///< Value is animated but the given time isn't a keyframe IsKeyframe, ///< Value is animated and the given time is a keyframe Mismatch ///< Value is animated and the current value doesn't match the animated value }; struct SetKeyframeInfo { bool insertion; int index; }; struct MidTransition { enum Type { Invalid, SingleKeyframe, Middle, }; Type type = Invalid; QVariant value; model::KeyframeTransition from_previous; model::KeyframeTransition to_next; }; using BaseProperty::BaseProperty; virtual ~AnimatableBase() = default; /** * \brief Number of keyframes */ virtual int keyframe_count() const = 0; /** * \param i Keyframe index * \pre \p i in [0, keyframe_count()) * \return the Corresponding keyframe or nullptr if not found * * keyframe(i).time() < keyframe(j).time() <=> i < j */ virtual const KeyframeBase* keyframe(int i) const = 0; virtual KeyframeBase* keyframe(int i) = 0; /** * \brief Sets a value at a keyframe * \param time Time to set the value at * \param value Value to set * \param info If not nullptr, it will be written to with information about what has been node * \param force_insert If \b true, it will always add a new keyframe * \post value(time) == \p value && animate() == true * \return The keyframe or nullptr if it couldn't be added. * If there is already a keyframe at \p time the returned value might be an existing keyframe */ virtual KeyframeBase* set_keyframe(FrameTime time, const QVariant& value, SetKeyframeInfo* info = nullptr, bool force_insert = false) = 0; /** * \brief Removes the keyframe at index \p i */ virtual void remove_keyframe(int i) = 0; /** * \brief Removes all keyframes * \post !animated() */ virtual void clear_keyframes() = 0; /** * \brief Removes the keyframe with the given time * \returns whether a keyframe was found and removed */ virtual bool remove_keyframe_at_time(FrameTime time) = 0; /** * \brief Get the value at the given time */ virtual QVariant value(FrameTime time) const = 0; bool set_undoable(const QVariant& val, bool commit=true) override; using BaseProperty::value; /** * \brief Moves a keyframe * \param keyframe_index Index of the keyframe to move * \param time New time for the keyframe * \return The new index for that keyframe */ virtual int move_keyframe(int keyframe_index, FrameTime time) = 0; /** * If animated(), whether the current value has been changed over the animated value */ Q_INVOKABLE virtual bool value_mismatch() const = 0; bool assign_from(const BaseProperty* prop) override; /** * \brief Set the current time * \post value() == value(time) */ void set_time(FrameTime time) override { current_time = time; on_set_time(time); } FrameTime time() const { return current_time; } /** * \brief Set the value for the given keyframe */ bool set_keyframe_value(int keyframe_index, const QVariant& value) { if ( auto kf = keyframe(keyframe_index) ) return kf->set_value(value); return false; } /** * \brief Whether it has multiple keyframes */ bool animated() const { return keyframe_count() != 0; } /** * \brief Index of the keyframe whose time lays in the transition * \pre animated() * * If all keyframes are after \p time, returns 0 * This means keyframe(keyframe_index(t)) is always valid when animated */ Q_INVOKABLE int keyframe_index(double time) const { auto kfcount = keyframe_count(); for ( int i = 0; i < kfcount; i++ ) { auto kftime = keyframe(i)->time(); if ( kftime == time ) return i; else if ( kftime > time ) return std::max(0, i-1); } return kfcount - 1; } int keyframe_index(KeyframeBase* kf) const { auto kfcount = keyframe_count(); for ( int i = 0; i < kfcount; i++ ) { if ( keyframe(i) == kf ) return i; } return -1; } KeyframeStatus keyframe_status(FrameTime time) const { if ( !animated() ) return NotAnimated; if ( value_mismatch() ) return Mismatch; if ( keyframe(keyframe_index(time))->time() == time ) return IsKeyframe; return Tween; } bool has_keyframe(FrameTime time) const { if ( !animated() ) return false; return keyframe(keyframe_index(time))->time() == time; } MidTransition mid_transition(FrameTime time) const; /** * \brief Clears all keyframes and creates an associated undo action * \param value Value to be set after clearing */ virtual void clear_keyframes_undoable(QVariant value = {}); /** * \brief Adds a keyframe at the given time */ virtual void add_smooth_keyframe_undoable(FrameTime time, const QVariant& value); signals: void keyframe_added(int index, KeyframeBase* keyframe); void keyframe_removed(int index); void keyframe_updated(int index, KeyframeBase* keyframe); protected: virtual void on_set_time(FrameTime time) = 0; MidTransition do_mid_transition(const KeyframeBase* kf_before, const KeyframeBase* kf_after, qreal ratio, int index) const; virtual QVariant do_mid_transition_value(const KeyframeBase* kf_before, const KeyframeBase* kf_after, qreal ratio) const = 0; FrameTime current_time = 0; }; template class Keyframe : public KeyframeBase { public: using value_type = Type; using reference = const Type&; Keyframe(FrameTime time, Type value) : KeyframeBase(time), value_(std::move(value)) {} void set(reference value) { value_ = value; } reference get() const { return value_; } QVariant value() const override { return QVariant::fromValue(value_); } bool set_value(const QVariant& val) override { if ( auto v = detail::variant_cast(val) ) { set(*v); return true; } return false; } value_type lerp(const Keyframe& other, double t) const { return math::lerp(value_, other.get(), this->transition().lerp_factor(t)); } protected: std::unique_ptr do_clone() const override { return std::make_unique(time(), value_); } class TypedKeyframeSplitter : public KeyframeSplitter { public: TypedKeyframeSplitter(const Keyframe* a, const Keyframe* b) : a(a), b(b) {} void step(const QPointF&) override {} std::unique_ptr left(const QPointF& p) const override { return std::make_unique( math::lerp(a->time(), b->time(), p.x()), math::lerp(a->get(), b->get(), p.y()) ); } std::unique_ptr right(const QPointF& p) const override { return std::make_unique( math::lerp(a->time(), b->time(), p.x()), math::lerp(a->get(), b->get(), p.y()) ); } std::unique_ptr last() const override { return b->clone(); } const Keyframe* a; const Keyframe* b; }; virtual std::unique_ptr splitter(const KeyframeBase* other) const override { return std::make_unique(this, static_cast(other)); } private: Type value_; }; template<> class Keyframe : public KeyframeBase { public: using value_type = QPointF; using reference = const QPointF&; Keyframe(FrameTime time, const QPointF& value) : KeyframeBase(time), point_(value) {} Keyframe(FrameTime time, const math::bezier::Point& value) : KeyframeBase(time), point_(value), linear(point_is_linear(value)) {} void set(reference value) { point_.translate_to(value); } reference get() const { return point_.pos; } QVariant value() const override { return QVariant::fromValue(point_); } bool set_value(const QVariant& val) override { if ( val.userType() == QMetaType::QPointF ) { set(val.value()); return true; } else if ( auto v = detail::variant_cast(val) ) { set_point(*v); return true; } return false; } value_type lerp(const Keyframe& other, double t) const { auto factor = transition().lerp_factor(t); if ( linear && other.linear ) return math::lerp(get(), other.get(), factor); auto solver = bezier_solver(other); math::bezier::LengthData len(solver, 20); return solver.solve(len.at_ratio(factor).ratio); } void set_point(const math::bezier::Point& point) { point_ = point; linear = point_is_linear(point); } const math::bezier::Point& point() const { return point_; } math::bezier::CubicBezierSolver bezier_solver(const Keyframe& other) const { return math::bezier::CubicBezierSolver( point_.pos, point_.tan_out, other.point_.tan_in, other.point_.pos ); } bool is_linear() const { return linear; } protected: std::unique_ptr do_clone() const override { return std::make_unique(time(), point_); } class PointKeyframeSplitter; std::unique_ptr splitter(const KeyframeBase* other) const override; private: static bool point_is_linear(const math::bezier::Point& point) { return point.tan_in == point.pos && point.tan_out == point.pos; } math::bezier::Point point_; bool linear = true; }; template class AnimatedProperty; namespace detail { template class AnimatedProperty : public AnimatableBase { public: using keyframe_type = Keyframe; using value_type = typename Keyframe::value_type; using reference = typename Keyframe::reference; class iterator { public: using value_type = keyframe_type; using pointer = const value_type*; using reference = const value_type&; using difference_type = int; using iterator_category = std::random_access_iterator_tag; iterator(const AnimatedProperty* prop, int index) noexcept : prop(prop), index(index) {} reference operator*() const { return *prop->keyframes_[index]; } pointer operator->() const { return prop->keyframes_[index].get(); } bool operator==(const iterator& other) const noexcept { return prop == other.prop && index == other.index; } bool operator!=(const iterator& other) const noexcept { return prop != other.prop || index != other.index; } bool operator<=(const iterator& other) const noexcept { return prop < other.prop || (prop == other.prop && index <= other.index); } bool operator<(const iterator& other) const noexcept { return prop < other.prop || (prop == other.prop && index < other.index); } bool operator>=(const iterator& other) const noexcept { return prop > other.prop || (prop == other.prop && index >= other.index); } bool operator>(const iterator& other) const noexcept { return prop > other.prop || (prop == other.prop && index > other.index); } iterator& operator++() noexcept { index++; return *this; } iterator operator++(int) noexcept { auto copy = this; index++; return copy; } iterator& operator--() noexcept { index--; return *this; } iterator operator--(int) noexcept { auto copy = this; index--; return copy; } iterator& operator+=(difference_type d) noexcept { index += d; return *this; } iterator operator+(difference_type d) const noexcept { auto copy = this; return copy += d; } iterator& operator-=(difference_type d) noexcept { index -= d; return *this; } iterator operator-(difference_type d) const noexcept { auto copy = this; return copy -= d; } difference_type operator-(const iterator& other) const noexcept { return index - other.index; } private: const AnimatedProperty* prop; int index; }; AnimatedProperty( Object* object, const QString& name, reference default_value, PropertyCallback emitter = {}, int flags = 0 ) : AnimatableBase( object, name, PropertyTraits::from_scalar( PropertyTraits::Animated|PropertyTraits::Visual|flags )), value_{default_value}, emitter(std::move(emitter)) {} int keyframe_count() const override { return keyframes_.size(); } const keyframe_type* keyframe(int i) const override { if ( i < 0 || i >= int(keyframes_.size()) ) return nullptr; return keyframes_[i].get(); } keyframe_type* keyframe(int i) override { if ( i < 0 || i >= int(keyframes_.size()) ) return nullptr; return keyframes_[i].get(); } QVariant value() const override { return QVariant::fromValue(value_); } QVariant value(FrameTime time) const override { return QVariant::fromValue(get_at(time)); } keyframe_type* set_keyframe(FrameTime time, const QVariant& val, SetKeyframeInfo* info = nullptr, bool force_insert = false) override { if ( auto v = detail::variant_cast(val) ) return static_cast*>(this)->set_keyframe(time, *v, info, force_insert); return nullptr; } void remove_keyframe(int i) override { if ( i >= 0 && i <= int(keyframes_.size()) ) { keyframes_.erase(keyframes_.begin() + i); emit this->keyframe_removed(i); value_changed(); } } void clear_keyframes() override { int n = keyframes_.size(); keyframes_.clear(); for ( int i = n - 1; i >= 0; i-- ) emit this->keyframe_removed(i); } bool remove_keyframe_at_time(FrameTime time) override { for ( auto it = keyframes_.begin(); it != keyframes_.end(); ++it ) { if ( (*it)->time() == time ) { int index = it - keyframes_.begin(); keyframes_.erase(it); emit this->keyframe_removed(index); on_keyframe_updated(time, index-1, index); return true; } } return false; } bool set_value(const QVariant& val) override { if ( auto v = detail::variant_cast(val) ) return static_cast*>(this)->set(*v); return false; } bool valid_value(const QVariant& val) const override { if ( detail::variant_cast(val) ) return true; return false; } bool set(reference val) { value_ = val; mismatched_ = !keyframes_.empty(); this->value_changed(); emitter(this->object(), value_); return true; } keyframe_type* set_keyframe(FrameTime time, reference value, SetKeyframeInfo* info = nullptr, bool force_insert = false) { // First keyframe if ( keyframes_.empty() ) { value_ = value; this->value_changed(); emitter(this->object(), value_); keyframes_.push_back(std::make_unique(time, value)); emit this->keyframe_added(0, keyframes_.back().get()); if ( info ) *info = {true, 0}; return keyframes_.back().get(); } // Current time, update value_ if ( time == this->time() ) { value_ = value; this->value_changed(); emitter(this->object(), value_); } // Find the right keyframe int index = this->keyframe_index(time); auto kf = keyframe(index); // Time matches, update if ( kf->time() == time && !force_insert ) { kf->set(value); emit this->keyframe_updated(index, kf); on_keyframe_updated(time, index-1, index+1); if ( info ) *info = {false, index}; return kf; } // First keyframe not at 0, might have to add the new keyframe at 0 if ( index == 0 && kf->time() > time ) { keyframes_.insert(keyframes_.begin(), std::make_unique(time, value)); emit this->keyframe_added(0, keyframes_.front().get()); on_keyframe_updated(time, -1, 1); if ( info ) *info = {true, 0}; return keyframes_.front().get(); } // Insert somewhere in the middle auto it = keyframes_.insert( keyframes_.begin() + index + 1, std::make_unique(time, value) ); emit this->keyframe_added(index + 1, it->get()); on_keyframe_updated(time, index, index+2); if ( info ) *info = {true, index+1}; return it->get(); } value_type get() const { return value_; } value_type get_at(FrameTime time) const { if ( time == this->time() ) return value_; return get_at_impl(time).second; } bool value_mismatch() const override { return mismatched_; } int move_keyframe(int keyframe_index, FrameTime time) override { if ( keyframe_index < 0 || keyframe_index >= int(keyframes_.size()) ) return keyframe_index; int new_index = 0; for ( ; new_index < int(keyframes_.size()); new_index++ ) { if ( keyframes_[new_index]->time() > time ) break; } if ( new_index > keyframe_index ) new_index--; keyframes_[keyframe_index]->set_time(time); if ( keyframe_index != new_index ) { QPointF incoming(-1, -1); if ( keyframe_index > 0 ) { auto trans_before_src = keyframes_[keyframe_index - 1]->transition(); incoming = trans_before_src.after(); trans_before_src.set_after(keyframes_[keyframe_index]->transition().after()); keyframes_[keyframe_index - 1]->set_transition(trans_before_src); } auto move = std::move(keyframes_[keyframe_index]); keyframes_.erase(keyframes_.begin() + keyframe_index); keyframes_.insert(keyframes_.begin() + new_index, std::move(move)); int ia = keyframe_index; int ib = new_index; if ( ia > ib ) std::swap(ia, ib); if ( new_index > 0 ) { auto trans_before_dst = keyframes_[new_index - 1]->transition(); QPointF outgoing = trans_before_dst.after(); if ( incoming.x() != -1 ) { trans_before_dst.set_after(incoming); keyframes_[new_index - 1]->set_transition(trans_before_dst); } auto trans_moved = keyframes_[new_index]->transition(); trans_moved.set_after(outgoing); keyframes_[new_index]->set_transition(trans_moved); } for ( ; ia <= ib; ia++ ) emit this->keyframe_updated(ia, keyframes_[ia].get()); } else { emit this->keyframe_updated(keyframe_index, keyframes_[keyframe_index].get()); } return new_index; } iterator begin() const { return iterator{this, 0}; } iterator end() const { return iterator{this, int(keyframes_.size())}; } void stretch_time(qreal multiplier) override { for ( std::size_t i = 0; i < keyframes_.size(); i++ ) { keyframes_[i]->stretch_time(multiplier); emit keyframe_updated(i, keyframes_[i].get()); } current_time *= multiplier; } protected: void on_set_time(FrameTime time) override { if ( !keyframes_.empty() ) { const keyframe_type* kf; std::tie(kf, value_) = get_at_impl(time); this->value_changed(); emitter(this->object(), value_); } mismatched_ = false; } void on_keyframe_updated(FrameTime kf_time, int prev_index, int next_index) { auto cur_time = time(); // if no keyframes or the current keyframe is being modified => update value_ if ( !keyframes_.empty() && cur_time != kf_time ) { if ( kf_time > cur_time ) { // if the modified keyframe is far ahead => don't update value_ if ( prev_index >= 0 && keyframes_[prev_index]->time() > cur_time ) return; } else { // if the modified keyframe is far behind => don't update value_ if ( next_index < int(keyframes_.size()) && keyframes_[next_index]->time() < cur_time ) return; } } on_set_time(cur_time); } std::pair get_at_impl(FrameTime time) const { if ( keyframes_.empty() ) return {nullptr, value_}; const keyframe_type* first = keyframe(0); int count = keyframe_count(); if ( count < 2 || first->time() >= time ) return {first, first->get()}; // We have at least 2 keyframes and time is after the first keyframe int index = this->keyframe_index(time); first = keyframe(index); // Only one keyframe needed to get the value if ( index == count - 1 || first->time() == time ) return {first, first->get()}; // Interpolate between two keyframes const keyframe_type* second = keyframe(index+1); double scaled_time = (time - first->time()) / (second->time() - first->time()); return {nullptr, first->lerp(*second, scaled_time)}; } QVariant do_mid_transition_value(const KeyframeBase* kf_before, const KeyframeBase* kf_after, qreal ratio) const override { return QVariant::fromValue( static_cast(kf_before)->lerp( *static_cast(kf_after), ratio ) ); } value_type value_; std::vector> keyframes_; bool mismatched_ = false; PropertyCallback emitter; }; // Intermediare non-templated class so Q_OBJECT works class AnimatedPropertyPosition: public detail::AnimatedProperty { Q_OBJECT public: AnimatedPropertyPosition( Object* object, const QString& name, reference default_value, PropertyCallback emitter = {}, int flags = 0 ) : detail::AnimatedProperty(object, name, default_value, std::move(emitter), flags) { } void set_closed(bool closed); Q_INVOKABLE void split_segment(int index, qreal factor); Q_INVOKABLE bool set_bezier(glaxnimate::math::bezier::Bezier bezier); Q_INVOKABLE glaxnimate::math::bezier::Bezier bezier() const; void remove_points(const std::set& indices); keyframe_type* set_keyframe(FrameTime time, const QVariant& val, SetKeyframeInfo* info = nullptr, bool force_insert = false) override; keyframe_type* set_keyframe(FrameTime time, reference value, SetKeyframeInfo* info = nullptr, bool force_insert = false); bool set_value(const QVariant& val) override; bool valid_value(const QVariant& val) const override; void add_smooth_keyframe_undoable(FrameTime time, const QVariant& value) override; /** * \brief Gets the bezier derivative at the given time * \returns The derivative point, if defined */ std::optional derivative_at(FrameTime time) const; signals: /// Invoked on set_bezier() void bezier_set(const math::bezier::Bezier& bezier); }; } // namespace detail template class AnimatedProperty : public detail::AnimatedProperty { public: using detail::AnimatedProperty::AnimatedProperty; }; template<> class AnimatedProperty : public detail::AnimatedProperty { public: AnimatedProperty( Object* object, const QString& name, reference default_value, PropertyCallback emitter = {}, float min = std::numeric_limits::lowest(), float max = std::numeric_limits::max(), bool cycle = false, int flags = 0 ) : detail::AnimatedProperty(object, name, default_value, std::move(emitter), flags), min_(min), max_(max), cycle_(cycle) { } float max() const { return max_; } float min() const { return min_; } bool set(reference val) { return detail::AnimatedProperty::set(bound(val)); } using AnimatableBase::set_keyframe; keyframe_type* set_keyframe(FrameTime time, reference value, SetKeyframeInfo* info = nullptr, bool force_insert = false) { return detail::AnimatedProperty::set_keyframe(time, bound(value), info, force_insert); } private: float bound(float value) const { return cycle_ ? math::fmod(value, max_) : math::bound(min_, value, max_) ; } float min_; float max_; bool cycle_; }; template<> class AnimatedProperty : public detail::AnimatedPropertyPosition { public: using detail::AnimatedPropertyPosition::AnimatedPropertyPosition; }; } // namespace glaxnimate::model mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/shapes/ellipse.cpp000664 001750 001750 00000001634 14477652011 031025 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "ellipse.hpp" GLAXNIMATE_OBJECT_IMPL(glaxnimate::model::Ellipse) QIcon glaxnimate::model::Ellipse::tree_icon() const { return QIcon::fromTheme("draw-ellipse"); } QString glaxnimate::model::Ellipse::type_name_human() const { return tr("Ellipse"); } glaxnimate::math::bezier::Bezier glaxnimate::model::Ellipse::to_bezier(FrameTime t) const { QSizeF sz = size.get_at(t); auto bezier = math::EllipseSolver(position.get_at(t), QPointF(sz.width()/2, sz.height()/2), 0).to_bezier(-math::pi/2, math::tau); if ( reversed.get() ) bezier.reverse(); return bezier; } QRectF glaxnimate::model::Ellipse::local_bounding_rect(FrameTime t) const { QSizeF sz = size.get_at(t); return QRectF(position.get_at(t) - QPointF(sz.width()/2, sz.height()/2), sz); } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/utils/tar.cpp000664 001750 001750 00000020165 14477652011 026733 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "tar.hpp" #include #include #include "app/log/log.hpp" class glaxnimate::utils::tar::ArchiveEntry::Private { public: Private(archive_entry *entry) : entry(entry), #if ARCHIVE_VERSION_NUMBER > 3001002 path(QString::fromUtf8(archive_entry_pathname_utf8(entry))) #else path(archive_entry_pathname(entry)) #endif {} archive_entry *entry; QString path; }; glaxnimate::utils::tar::ArchiveEntry::ArchiveEntry(std::unique_ptr d) : d(std::move(d)) {} glaxnimate::utils::tar::ArchiveEntry::ArchiveEntry(const glaxnimate::utils::tar::ArchiveEntry& oth) : d(std::make_unique(*oth.d)) { } glaxnimate::utils::tar::ArchiveEntry & glaxnimate::utils::tar::ArchiveEntry::operator=(const ArchiveEntry& oth) { *d = *oth.d; return *this; } glaxnimate::utils::tar::ArchiveEntry::ArchiveEntry(glaxnimate::utils::tar::ArchiveEntry && oth) = default; glaxnimate::utils::tar::ArchiveEntry & glaxnimate::utils::tar::ArchiveEntry::operator=(ArchiveEntry && oth) = default; glaxnimate::utils::tar::ArchiveEntry::~ArchiveEntry() = default; bool glaxnimate::utils::tar::ArchiveEntry::operator==(const glaxnimate::utils::tar::ArchiveEntry& oth) const { if ( !d != !oth.d ) return false; if ( !d ) return true; return d->entry == oth.d->entry; } bool glaxnimate::utils::tar::ArchiveEntry::operator!=(const glaxnimate::utils::tar::ArchiveEntry& oth) const { return !(*this == oth); } const QString & glaxnimate::utils::tar::ArchiveEntry::path() const { return d->path; } bool glaxnimate::utils::tar::ArchiveEntry::valid() const { return d && d->entry; } class glaxnimate::utils::tar::TapeArchive::Private { public: Private(TapeArchive* parent) : parent(parent) {} void open(const QString& filename) { input = archive_read_new(); archive_read_support_format_all(input); archive_read_support_filter_all(input); int result = archive_read_open_filename(input, filename.toStdString().c_str(), 10240); if ( result < 0 ) { handle_message(result, input); close(); } else { finished = false; } } void load_data(const QByteArray& data) { input = archive_read_new(); archive_read_support_format_all(input); archive_read_support_filter_all(input); int result = archive_read_open_memory(input, (void*)data.data(), data.size()); if ( result < 0 ) { handle_message(result, input); close(); } else { finished = false; } } int copy_data(archive *output) { int result; const void *buff; size_t size; int64_t offset; while ( true ) { result = archive_read_data_block(input, &buff, &size, &offset); if ( result == ARCHIVE_EOF ) return ARCHIVE_OK; if ( result < ARCHIVE_OK ) { handle_message(result, input); return result; } result = archive_write_data_block(output, buff, size, offset); if ( result < ARCHIVE_OK ) { handle_message(result, output); return result; } } } void extract_begin() { output = archive_write_disk_new(); archive_write_disk_set_options(output, ARCHIVE_EXTRACT_TIME|ARCHIVE_EXTRACT_PERM|ARCHIVE_EXTRACT_ACL|ARCHIVE_EXTRACT_FFLAGS); archive_write_disk_set_standard_lookup(output); } archive_entry* next_entry() { if ( !input || !output || finished ) return nullptr; while ( true ) { archive_entry *entry; int result = archive_read_next_header(input, &entry); if ( result == ARCHIVE_EOF ) { finished = true; return nullptr; } if ( result < ARCHIVE_OK ) handle_message(result, input); if ( result == ARCHIVE_FAILED ) continue; if ( result == ARCHIVE_FATAL ) { finished = true; return nullptr; } if ( archive_entry_size(entry) < 0 ) continue; return entry; } } bool extract(const glaxnimate::utils::tar::ArchiveEntry& entry, const QDir& destination) { QString output_file_path = destination.absoluteFilePath(entry.d->path); archive_entry_set_pathname(entry.d->entry, output_file_path.toStdString().c_str()); int result = archive_write_header(output, entry.d->entry); if ( result < ARCHIVE_OK ) { handle_message(result, output); } else { result = copy_data(output); if ( result == ARCHIVE_FAILED ) return false; if ( result == ARCHIVE_FATAL ) { finished = true; return false; } } result = archive_write_finish_entry(output); if ( result < ARCHIVE_OK ) handle_message(result, output); if ( result == ARCHIVE_FATAL ) finished = true; return result >= ARCHIVE_WARN; } void handle_message(int result, archive* arch) { if ( result < ARCHIVE_OK ) { QString message = archive_error_string(arch); app::log::Severity severity = app::log::Info; if ( result == ARCHIVE_FATAL ) { error = message; severity = app::log::Error; } else if ( result < ARCHIVE_WARN ) { severity = app::log::Warning; } app::log::Log("tar").log(message, severity); parent->message(message, severity); } } void extract_end() { if ( output ) { archive_write_close(output); archive_write_free(output); output = nullptr; } } void close() { extract_end(); if ( input ) { archive_read_close(input); archive_read_free(input); input = nullptr; } } archive* input = nullptr; archive* output = nullptr; TapeArchive* parent; QString error; bool finished = true; }; glaxnimate::utils::tar::TapeArchive::TapeArchive(const QString& filename) : d(std::make_unique(this)) { d->open(filename); } glaxnimate::utils::tar::TapeArchive::TapeArchive(const QByteArray& data) : d(std::make_unique(this)) { d->load_data(data); } glaxnimate::utils::tar::TapeArchive::~TapeArchive() { d->close(); } const QString & glaxnimate::utils::tar::TapeArchive::error() const { return d->error; } bool glaxnimate::utils::tar::TapeArchive::finished() const { return d->finished; } glaxnimate::utils::tar::ArchiveEntry glaxnimate::utils::tar::TapeArchive::next() { if ( d->finished ) return ArchiveEntry({}); if ( !d->output ) d->extract_begin(); if ( auto entry = d->next_entry() ) return ArchiveEntry(std::make_unique(entry)); d->extract_end(); return ArchiveEntry({}); } bool glaxnimate::utils::tar::TapeArchive::extract(const glaxnimate::utils::tar::ArchiveEntry& entry, const QDir& destination) { return d->extract(entry, destination); } glaxnimate::utils::tar::TapeArchive::iterator glaxnimate::utils::tar::TapeArchive::begin() { return iterator(this, next()); } glaxnimate::utils::tar::TapeArchive::iterator glaxnimate::utils::tar::TapeArchive::end() { return iterator(this, ArchiveEntry({})); } QString glaxnimate::utils::tar::libarchive_version() { int vint = ARCHIVE_VERSION_NUMBER; int patch = vint % 1000; vint /= 1000; int minor = vint % 1000; vint /= 1000; return QString("%1.%2.%3").arg(vint).arg(minor).arg(patch); } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/shapes/path.cpp000664 001750 001750 00000000305 14477652011 030316 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "path.hpp" GLAXNIMATE_OBJECT_IMPL(glaxnimate::model::Path) mlt-7.22.0/src/modules/glaxnimate/glaxnimate/external/QtAppSetup/src/app/scripting/script_engine.hpp000664 001750 001750 00000007100 14477652011 035357 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include #include #include #include #include #include #include "app/qstring_exception.hpp" namespace app::scripting { class ScriptError : public QStringException<>{ using Ctor::Ctor; }; class ScriptEngine; /** * \brief Context for running scripts (implementation) */ class ScriptExecutionContext : public QObject { Q_OBJECT public: ScriptExecutionContext() = default; virtual ~ScriptExecutionContext() = default; /** * \brief Exposes \p obj as a global variable called \p name */ virtual void expose(const QString& name, const QVariant& obj) = 0; /** * \brief Evaluates \p code and serializes the result (if any) into a string * \throws ScriptError on errors with the script */ virtual QString eval_to_string(const QString& code) = 0; /** * \brief Marks an app module that must be loaded */ virtual void app_module(const QString& name) = 0; /** * \brief Runs a function from a file * \return Whether the call was successful */ virtual bool run_from_module ( const QDir& path, ///< Path containing the module file const QString& module, ///< Module name to load, meaning of this depends on the engine const QString& function,///< Function to call const QVariantList& args///< Arguments to pass the function ) = 0; /** * \brief Engine that created the context */ virtual const ScriptEngine* engine() const = 0; signals: void stderr_line(const QString&); void stdout_line(const QString&); private: ScriptExecutionContext(const ScriptExecutionContext&) = delete; ScriptExecutionContext& operator=(const ScriptExecutionContext&) = delete; }; /** * \brief Script context holder */ using ScriptContext = std::unique_ptr; /** * \brief Scripting system metadata */ class ScriptEngine { public: virtual ~ScriptEngine() = default; /** * \brief short machine-readable name for the language / engine */ virtual QString slug() const = 0; /** * \brief Human-readable name */ virtual QString label() const = 0; /** * \brief Creates an execution context to run scripts */ virtual ScriptContext create_context() const = 0; protected: template struct Autoregister; }; class ScriptEngineFactory { public: static ScriptEngineFactory& instance() { static ScriptEngineFactory instance; return instance; } void register_engine(std::unique_ptr eng) { engines_.push_back(std::move(eng)); } const ScriptEngine* engine(const QString& slug) const { for ( const auto& engine: engines_ ) if ( engine->slug() == slug ) return engine.get(); return nullptr; } const std::vector>& engines() const { return engines_; } private: ScriptEngineFactory() = default; ScriptEngineFactory(const ScriptEngineFactory&) = delete; ~ScriptEngineFactory() = default; std::vector> engines_; }; template struct ScriptEngine::Autoregister { template Autoregister(Args&&... args) { ScriptEngineFactory::instance().register_engine(std::make_unique(std::forward(args)...)); } }; } // namespace app::scripting mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/svg/svg_parser.cpp000664 001750 001750 00000150265 14477652011 030373 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "svg_parser.hpp" #include "svg_parser_private.hpp" using namespace glaxnimate::io::svg::detail; class glaxnimate::io::svg::SvgParser::Private : public SvgParserPrivate { public: Private( model::Document* document, const std::function& on_warning, ImportExport* io, QSize forced_size, model::FrameTime default_time, GroupMode group_mode, QDir default_asset_path ) : SvgParserPrivate(document, on_warning, io, forced_size, default_time), group_mode(group_mode), default_asset_path(default_asset_path) {} protected: void on_parse_prepare(const QDomElement&) override { for ( const auto& p : shape_parsers ) to_process += dom.elementsByTagName(p.first).count(); } QSizeF get_size(const QDomElement& svg) override { return { len_attr(svg, "width", size.width()), len_attr(svg, "height", size.height()) }; } void on_parse(const QDomElement& svg) override { dpi = attr(svg, "inkscape", "export-xdpi", "96").toDouble(); QPointF pos; QVector2D scale{1, 1}; if ( svg.hasAttribute("viewBox") ) { auto vb = split_attr(svg, "viewBox"); if ( vb.size() == 4 ) { qreal vbx = vb[0].toDouble(); qreal vby = vb[1].toDouble(); qreal vbw = vb[2].toDouble(); qreal vbh = vb[3].toDouble(); if ( !forced_size.isValid() ) { if ( !svg.hasAttribute("width") ) size.setWidth(vbw); if ( !svg.hasAttribute("height") ) size.setHeight(vbh); } pos = -QPointF(vbx, vby); if ( vbw != 0 && vbh != 0 ) { scale = QVector2D(size.width() / vbw, size.height() / vbh); if ( forced_size.isValid() ) { auto single = qMin(scale.x(), scale.y()); scale = QVector2D(single, single); } } } } for ( const auto& link_node : ItemCountRange(dom.elementsByTagName("link")) ) { auto link = link_node.toElement(); if ( link.attribute("rel") == "stylesheet" ) { QString url = link.attribute("href"); if ( !url.isEmpty() ) document->add_pending_asset("", url); } } parse_css(); parse_assets(); parse_metadata(); model::Layer* parent_layer = add_layer(&main->shapes); parent_layer->transform.get()->position.set(-pos); parent_layer->transform.get()->scale.set(scale); parent_layer->name.set( attr(svg, "sodipodi", "docname", svg.attribute("id", parent_layer->type_name_human())) ); Style default_style(Style::Map{ {"fill", "black"}, }); parse_children({svg, &parent_layer->shapes, parse_style(svg, default_style), false}); main->name.set( attr(svg, "sodipodi", "docname", "") ); } void parse_shape(const ParseFuncArgs& args) override { if ( handle_mask(args) ) return; parse_shape_impl(args); } private: void parse_css() { CssParser parser(css_blocks); for ( const auto& style : ItemCountRange(dom.elementsByTagName("style")) ) { QString data; for ( const auto & child : ItemCountRange(style.childNodes()) ) { if ( child.isText() || child.isCDATASection() ) data += child.toCharacterData().data(); } if ( data.contains("@font-face") ) document->add_pending_asset("", data.toUtf8()); parser.parse(data); } std::stable_sort(css_blocks.begin(), css_blocks.end()); } void parse_defs(const QDomNode& node) { if ( !node.isElement() ) return; auto defs = node.toElement(); for ( const auto& def : ElementRange(defs) ) { if ( def.tagName().startsWith("animate") ) { QString link = attr(def, "xlink", "href"); if ( link.isEmpty() || link[0] != '#' ) continue; animate_parser.store_animate(link.mid(1), def); } } } void parse_assets() { std::vector later; for ( const auto& domnode : ItemCountRange(dom.elementsByTagName("linearGradient")) ) parse_gradient_node(domnode, later); for ( const auto& domnode : ItemCountRange(dom.elementsByTagName("radialGradient")) ) parse_gradient_node(domnode, later); std::vector unprocessed; while ( !later.empty() && unprocessed.size() != later.size() ) { unprocessed.clear(); for ( const auto& element : later ) parse_brush_style_check(element, unprocessed); std::swap(later, unprocessed); } for ( const auto& defs : ItemCountRange(dom.elementsByTagName("defs")) ) parse_defs(defs); } void parse_gradient_node(const QDomNode& domnode, std::vector& later) { if ( !domnode.isElement() ) return; auto gradient = domnode.toElement(); QString id = gradient.attribute("id"); if ( id.isEmpty() ) return; if ( parse_brush_style_check(gradient, later) ) parse_gradient_nolink(gradient, id); } bool parse_brush_style_check(const QDomElement& element, std::vector& later) { QString link = attr(element, "xlink", "href"); if ( link.isEmpty() ) return true; if ( !link.startsWith("#") ) return false; auto it = brush_styles.find(link); if ( it != brush_styles.end() ) { brush_styles["#" + element.attribute("id")] = it->second; return false; } auto it1 = gradients.find(link); if ( it1 != gradients.end() ) { parse_gradient(element, element.attribute("id"), it1->second); return false; } later.push_back(element); return false; } QGradientStops parse_gradient_stops(const QDomElement& gradient) { QGradientStops stops; for ( const auto& domnode : ItemCountRange(gradient.childNodes()) ) { if ( !domnode.isElement() ) continue; auto stop = domnode.toElement(); if ( stop.tagName() != "stop" ) continue; Style style = parse_style(stop, {}); if ( !style.contains("stop-color") ) continue; QColor color = parse_color(style["stop-color"], QColor()); color.setAlphaF(color.alphaF() * style.get("stop-opacity", "1").toDouble()); stops.push_back({stop.attribute("offset", "0").toDouble(), color}); } utils::sort_gradient(stops); return stops; } void parse_gradient_nolink(const QDomElement& gradient, const QString& id) { QGradientStops stops = parse_gradient_stops(gradient); if ( stops.empty() ) return; if ( stops.size() == 1 ) { auto col = std::make_unique(document); col->name.set(id); col->color.set(stops[0].second); brush_styles["#"+id] = col.get(); auto anim = parse_animated(gradient.firstChildElement("stop")); for ( const auto& kf : anim.single("stop-color") ) col->color.set_keyframe(kf.time, kf.values.color())->set_transition(kf.transition); document->assets()->colors->values.insert(std::move(col)); return; } auto colors = std::make_unique(document); colors->name.set(id); colors->colors.set(stops); gradients["#"+id] = colors.get(); auto ptr = colors.get(); document->assets()->gradient_colors->values.insert(std::move(colors)); parse_gradient(gradient, id, ptr); } void parse_gradient(const QDomElement& element, const QString& id, model::GradientColors* colors) { auto gradient = std::make_unique(document); QTransform gradient_transform; if ( element.hasAttribute("gradientTransform") ) gradient_transform = svg_transform(element.attribute("gradientTransform"), {}).transform; if ( element.tagName() == "linearGradient" ) { if ( !element.hasAttribute("x1") || !element.hasAttribute("x2") || !element.hasAttribute("y1") || !element.hasAttribute("y2") ) return; gradient->type.set(model::Gradient::Linear); gradient->start_point.set(gradient_transform.map(QPointF( len_attr(element, "x1"), len_attr(element, "y1") ))); gradient->end_point.set(gradient_transform.map(QPointF( len_attr(element, "x2"), len_attr(element, "y2") ))); auto anim = parse_animated(element); for ( const auto& kf : anim.joined({"x1", "y1"}) ) gradient->start_point.set_keyframe(kf.time, {kf.values[0].vector()[0], kf.values[1].vector()[0]})->set_transition(kf.transition); for ( const auto& kf : anim.joined({"x2", "y2"}) ) gradient->end_point.set_keyframe(kf.time, {kf.values[0].vector()[0], kf.values[1].vector()[0]})->set_transition(kf.transition); } else if ( element.tagName() == "radialGradient" ) { if ( !element.hasAttribute("cx") || !element.hasAttribute("cy") || !element.hasAttribute("r") ) return; gradient->type.set(model::Gradient::Radial); QPointF c = QPointF( len_attr(element, "cx"), len_attr(element, "cy") ); gradient->start_point.set(gradient_transform.map(c)); if ( element.hasAttribute("fx") ) gradient->highlight.set(gradient_transform.map(QPointF( len_attr(element, "fx"), len_attr(element, "fy") ))); else gradient->highlight.set(gradient_transform.map(c)); gradient->end_point.set(gradient_transform.map(QPointF( c.x() + len_attr(element, "r"), c.y() ))); auto anim = parse_animated(element); for ( const auto& kf : anim.joined({"cx", "cy"}) ) gradient->start_point.set_keyframe(kf.time, gradient_transform.map(QPointF{kf.values[0].vector()[0], kf.values[1].vector()[0]}) )->set_transition(kf.transition); for ( const auto& kf : anim.joined({"fx", "fy"}) ) gradient->highlight.set_keyframe(kf.time, gradient_transform.map(QPointF{kf.values[0].vector()[0], kf.values[1].vector()[0]}) )->set_transition(kf.transition); for ( const auto& kf : anim.joined({"cx", "cy", "r"}) ) gradient->end_point.set_keyframe(kf.time, gradient_transform.map(QPointF{kf.values[0].vector()[0] + kf.values[2].vector()[0], kf.values[1].vector()[0]}) )->set_transition(kf.transition); } else { return; } gradient->name.set(id); gradient->colors.set(colors); brush_styles["#"+id] = gradient.get(); document->assets()->gradients->values.insert(std::move(gradient)); } Style parse_style(const QDomElement& element, const Style& parent_style) { Style style = parent_style; auto class_names_list = element.attribute("class").split(" ", #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) Qt::SkipEmptyParts #else QString::SkipEmptyParts #endif ); std::unordered_set class_names(class_names_list.begin(), class_names_list.end()); for ( const auto& rule : css_blocks ) { if ( rule.selector.match(element, class_names) ) rule.merge_into(style); } if ( element.hasAttribute("style") ) { for ( const auto& item : element.attribute("style").split(';') ) { auto split = ::utils::split_ref(item, ':'); if ( split.size() == 2 ) { QString name = split[0].trimmed().toString(); if ( !name.isEmpty() && css_atrrs.count(name) ) style[name] = split[1].trimmed().toString(); } } } for ( const auto& domnode : ItemCountRange(element.attributes()) ) { auto attr = domnode.toAttr(); if ( css_atrrs.count(attr.name()) ) style[attr.name()] = attr.value(); } for ( auto it = style.map.begin(); it != style.map.end(); ) { if ( it->second == "inherit" ) { QString parent = parent_style.get(it->first, ""); if ( parent.isEmpty() || parent == "inherit" ) { it = style.map.erase(it); continue; } it->second = parent; } ++it; } if ( !style.contains("fill") ) style.set("fill", parent_style.get("fill")); style.color = parse_color(style.get("color", ""), parent_style.color); return style; } bool handle_mask(const ParseFuncArgs& args) { QString mask_ref; if ( args.element.hasAttribute("clip-path") ) mask_ref = args.element.attribute("clip-path"); else if ( args.element.hasAttribute("mask") ) mask_ref = args.element.attribute("mask"); if ( mask_ref.isEmpty() ) return false; auto match = url_re.match(mask_ref); if ( !match.hasMatch() ) return false; QString id = match.captured(1).mid(1); QDomElement mask_element = element_by_id(id); if ( mask_element.isNull() ) return false; Style style = parse_style(args.element, args.parent_style); auto layer = add_layer(args.shape_parent); apply_common_style(layer, args.element, style); set_name(layer, args.element); layer->mask->mask.set(model::MaskSettings::Alpha); QDomElement element = args.element; QDomElement trans_copy = dom.createElement("g"); trans_copy.setAttribute("style", element.attribute("style")); element.removeAttribute("style"); trans_copy.setAttribute("transform", element.attribute("transform")); element.removeAttribute("transform"); for ( const auto& attr : detail::css_atrrs ) element.removeAttribute(attr); Style mask_style; mask_style["stroke"] = "none"; parse_g_to_layer({ mask_element, &layer->shapes, mask_style, false }); parse_shape_impl({ element, &layer->shapes, style, false }); parse_transform(trans_copy, layer, layer->transform.get()); return true; } void parse_shape_impl(const ParseFuncArgs& args) { auto it = shape_parsers.find(args.element.tagName()); if ( it != shape_parsers.end() ) { mark_progress(); (this->*it->second)(args); } } void parse_transform( const QDomElement& element, model::Group* node, model::Transform* transform ) { auto bb = node->local_bounding_rect(0); bool anchor_from_inkscape = false; QPointF center = bb.center(); if ( element.hasAttributeNS(detail::xmlns.at("inkscape"), "transform-center-x") ) { anchor_from_inkscape = true; qreal ix = element.attributeNS(detail::xmlns.at("inkscape"), "transform-center-x").toDouble(); qreal iy = -element.attributeNS(detail::xmlns.at("inkscape"), "transform-center-y").toDouble(); center += QPointF(ix, iy); } bool anchor_from_rotate = false; if ( element.hasAttribute("transform") ) { auto trans = svg_transform( element.attribute("transform"), transform->transform_matrix(transform->time()) ); transform->set_transform_matrix(trans.transform); anchor_from_rotate = trans.anchor_set; if ( trans.anchor_set ) center = trans.anchor; } /// Adjust anchor point QPointF delta_pos; if ( anchor_from_rotate ) { transform->anchor_point.set(center); delta_pos = center; } else if ( anchor_from_inkscape ) { auto matrix = transform->transform_matrix(transform->time()); QPointF p1 = matrix.map(QPointF(0, 0)); transform->anchor_point.set(center); matrix = transform->transform_matrix(transform->time()); QPointF p2 = matrix.map(QPointF(0, 0)); delta_pos = p1 - p2; } transform->position.set(transform->position.get() + delta_pos); auto anim = animate_parser.parse_animated_transform(element); if ( !anim.apply_motion(transform->position, delta_pos, &node->auto_orient) ) { for ( const auto& kf : anim.single("translate") ) transform->position.set_keyframe(kf.time, QPointF{kf.values.vector()[0], kf.values.vector()[1]} + delta_pos)->set_transition(kf.transition); } for ( const auto& kf : anim.single("scale") ) transform->scale.set_keyframe(kf.time, QVector2D(kf.values.vector()[0], kf.values.vector()[1]))->set_transition(kf.transition); for ( const auto& kf : anim.single("rotate") ) { transform->rotation.set_keyframe(kf.time, kf.values.vector()[0])->set_transition(kf.transition); if ( kf.values.vector().size() == 3 ) { QPointF p = {kf.values.vector()[1], kf.values.vector()[2]}; transform->anchor_point.set_keyframe(kf.time, p)->set_transition(kf.transition); transform->position.set_keyframe(kf.time, p)->set_transition(kf.transition); } } } struct ParsedTransformInfo { QTransform transform; QPointF anchor = {}; bool anchor_set = false; }; ParsedTransformInfo svg_transform(const QString& attr, const QTransform& trans) { ParsedTransformInfo info{trans}; for ( const QRegularExpressionMatch& match : utils::regexp::find_all(transform_re, attr) ) { auto args = double_args(match.captured(2)); if ( args.empty() ) { warning("Missing transformation parameters"); continue; } QString name = match.captured(1); if ( name == "translate" ) { info.transform.translate(args[0], args.size() > 1 ? args[1] : 0); } else if ( name == "scale" ) { info.transform.scale(args[0], (args.size() > 1 ? args[1] : args[0])); } else if ( name == "rotate" ) { qreal ang = args[0]; if ( args.size() > 2 ) { qreal x = args[1]; qreal y = args[2]; info.anchor = {x, y}; info.anchor_set = true; // info.transform.translate(-x, -y); info.transform.rotate(ang); // info.transform.translate(x, y); } else { info.transform.rotate(ang); } } else if ( name == "skewX" ) { info.transform *= QTransform( 1, 0, 0, qTan(args[0]), 1, 0, 0, 0, 1 ); } else if ( name == "skewY" ) { info.transform *= QTransform( 1, qTan(args[0]), 0, 0, 1, 0, 0, 0, 1 ); } else if ( name == "matrix" ) { if ( args.size() == 6 ) { info.transform *= QTransform( args[0], args[1], 0, args[2], args[3], 0, args[4], args[5], 1 ); } else { warning("Wrong translation matrix"); } } else { warning(QString("Unknown transformation %1").arg(name)); } } return info; } void add_shapes(const ParseFuncArgs& args, ShapeCollection&& shapes) { Style style = parse_style(args.element, args.parent_style); auto group = std::make_unique(document); apply_common_style(group.get(), args.element, style); set_name(group.get(), args.element); add_style_shapes(args, &group->shapes, style); for ( auto& shape : shapes ) group->shapes.insert(std::move(shape)); // parse_transform at the end so the bounding box isn't empty parse_transform(args.element, group.get(), group->transform.get()); args.shape_parent->insert(std::move(group)); } void apply_common_style(model::VisualNode* node, const QDomElement& element, const Style& style) { if ( style.get("display") == "none" || style.get("visibility") == "hidden" ) node->visible.set(false); node->locked.set(attr(element, "sodipodi", "insensitive") == "true"); node->set("opacity", percent_1(style.get("opacity", "1"))); node->get("transform").value(); } void set_name(model::DocumentNode* node, const QDomElement& element) { QString name = attr(element, "inkscape", "label"); if ( name.isEmpty() ) { name = attr(element, "android", "name"); if ( name.isEmpty() ) name = element.attribute("id"); } node->name.set(name); } void add_style_shapes(const ParseFuncArgs& args, model::ShapeListProperty* shapes, const Style& style) { QString paint_order = style.get("paint-order", "normal"); if ( paint_order == "normal" ) paint_order = "fill stroke"; for ( const auto& sr : paint_order.split(' ', #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) Qt::SkipEmptyParts #else QString::SkipEmptyParts #endif ) ) { if ( sr == "fill" ) add_fill(args, shapes, style); else if ( sr == "stroke" ) add_stroke(args, shapes, style); } } void display_to_opacity(model::VisualNode* node, const detail::AnimateParser::AnimatedProperties& anim, model::AnimatedProperty& opacity, Style* style) { if ( !anim.has("display") ) return; if ( opacity.keyframe_count() > 2 ) { warning("Either animate `opacity` or `display`, not both"); return; } if ( style ) style->map.erase("display"); model::KeyframeTransition hold; hold.set_hold(true); for ( const auto& kf : anim.single("display") ) { opacity.set_keyframe(kf.time, kf.values.string() == "none" ? 0 : 1)->set_transition(hold); } node->visible.set(true); } void add_stroke(const ParseFuncArgs& args, model::ShapeListProperty* shapes, const Style& style) { QString stroke_color = style.get("stroke", "transparent"); if ( stroke_color == "none" ) return; auto stroke = std::make_unique(document); set_styler_style(stroke.get(), stroke_color, style.color); stroke->opacity.set(percent_1(style.get("stroke-opacity", "1"))); stroke->width.set(parse_unit(style.get("stroke-width", "1"))); stroke->cap.set(line_cap(style.get("stroke-linecap", "butt"))); stroke->join.set(line_join(style.get("stroke-linejoin", "miter"))); stroke->miter_limit.set(parse_unit(style.get("stroke-miterlimit", "4"))); auto anim = parse_animated(args.element); for ( const auto& kf : anim.single("stroke") ) stroke->color.set_keyframe(kf.time, kf.values.color())->set_transition(kf.transition); for ( const auto& kf : anim.single("stroke-opacity") ) stroke->opacity.set_keyframe(kf.time, kf.values.vector()[0])->set_transition(kf.transition); for ( const auto& kf : anim.single("stroke-width") ) stroke->width.set_keyframe(kf.time, kf.values.vector()[0])->set_transition(kf.transition); display_to_opacity(stroke.get(), anim, stroke->opacity, nullptr); shapes->insert(std::move(stroke)); } void set_styler_style(model::Styler* styler, const QString& color_str, const QColor& current_color) { if ( !color_str.startsWith("url") ) { styler->color.set(parse_color(color_str, current_color)); return; } auto match = url_re.match(color_str); if ( match.hasMatch() ) { QString id = match.captured(1); auto it = brush_styles.find(id); if ( it != brush_styles.end() ) { styler->use.set(it->second); return; } } styler->color.set(current_color); } void add_fill(const ParseFuncArgs& args, model::ShapeListProperty* shapes, const Style& style) { QString fill_color = style.get("fill", ""); auto fill = std::make_unique(document); set_styler_style(fill.get(), fill_color, style.color); fill->opacity.set(percent_1(style.get("fill-opacity", "1"))); if ( style.get("fill-rule", "") == "evenodd" ) fill->fill_rule.set(model::Fill::EvenOdd); auto anim = parse_animated(args.element); for ( const auto& kf : anim.single("fill") ) fill->color.set_keyframe(kf.time, kf.values.color())->set_transition(kf.transition); for ( const auto& kf : anim.single("fill-opacity") ) fill->opacity.set_keyframe(kf.time, kf.values.vector()[0])->set_transition(kf.transition); if ( fill_color == "none" ) fill->visible.set(false); display_to_opacity(fill.get(), anim, fill->opacity, nullptr); shapes->insert(std::move(fill)); } QColor parse_color(const QString& color_str, const QColor& current_color) { if ( color_str.isEmpty() || color_str == "currentColor" ) return current_color; return glaxnimate::io::svg::parse_color(color_str); } void parseshape_rect(const ParseFuncArgs& args) { ShapeCollection shapes; auto rect = push(shapes); qreal w = len_attr(args.element, "width", 0); qreal h = len_attr(args.element, "height", 0); rect->position.set(QPointF( len_attr(args.element, "x", 0) + w / 2, len_attr(args.element, "y", 0) + h / 2 )); rect->size.set(QSizeF(w, h)); qreal rx = len_attr(args.element, "rx", 0); qreal ry = len_attr(args.element, "ry", 0); rect->rounded.set(qMax(rx, ry)); auto anim = parse_animated(args.element); /// \todo handle offset anim.apply_motion(rect->position); for ( const auto& kf : anim.joined({"x", "y", "width", "height"}) ) rect->position.set_keyframe(kf.time, { kf.values[0].vector()[0] + kf.values[2].vector()[0] / 2, kf.values[1].vector()[0] + kf.values[3].vector()[0] / 2 })->set_transition(kf.transition); for ( const auto& kf : anim.joined({"width", "height"}) ) rect->size.set_keyframe(kf.time, {kf.values[0].vector()[0], kf.values[1].vector()[0]})->set_transition(kf.transition); for ( const auto& kf : anim.joined({"rx", "ry"}) ) rect->rounded.set_keyframe(kf.time, qMax(kf.values[0].vector()[0], kf.values[1].vector()[0]))->set_transition(kf.transition); add_shapes(args, std::move(shapes)); } void parseshape_ellipse(const ParseFuncArgs& args) { ShapeCollection shapes; auto ellipse = push(shapes); ellipse->position.set(QPointF( len_attr(args.element, "cx", 0), len_attr(args.element, "cy", 0) )); qreal rx = len_attr(args.element, "rx", 0); qreal ry = len_attr(args.element, "ry", 0); ellipse->size.set(QSizeF(rx * 2, ry * 2)); auto anim = parse_animated(args.element); anim.apply_motion(ellipse->position); for ( const auto& kf : anim.joined({"cx", "cy"}) ) ellipse->position.set_keyframe(kf.time, {kf.values[0].vector()[0], kf.values[1].vector()[0]})->set_transition(kf.transition); for ( const auto& kf : anim.joined({"rx", "ry"}) ) ellipse->size.set_keyframe(kf.time, {kf.values[0].vector()[0]*2, kf.values[1].vector()[0]*2})->set_transition(kf.transition); add_shapes(args, std::move(shapes)); } void parseshape_circle(const ParseFuncArgs& args) { ShapeCollection shapes; auto ellipse = push(shapes); ellipse->position.set(QPointF( len_attr(args.element, "cx", 0), len_attr(args.element, "cy", 0) )); qreal d = len_attr(args.element, "r", 0) * 2; ellipse->size.set(QSizeF(d, d)); auto anim = parse_animated(args.element); anim.apply_motion(ellipse->position); for ( const auto& kf : anim.joined({"cx", "cy"}) ) ellipse->position.set_keyframe(kf.time, {kf.values[0].vector()[0], kf.values[1].vector()[0]})->set_transition(kf.transition); for ( const auto& kf : anim.single({"r"}) ) ellipse->size.set_keyframe(kf.time, {kf.values.vector()[0]*2, kf.values.vector()[0]*2})->set_transition(kf.transition); add_shapes(args, std::move(shapes)); } void parseshape_g(const ParseFuncArgs& args) { switch ( group_mode ) { case Groups: parse_g_to_shape(args); break; case Layers: parse_g_to_layer(args); break; case Inkscape: if ( args.in_group ) parse_g_to_shape(args); else if ( attr(args.element, "inkscape", "groupmode") == "layer" ) parse_g_to_layer(args); else parse_g_to_shape(args); break; } } void parse_g_to_layer(const ParseFuncArgs& args) { Style style = parse_style(args.element, args.parent_style); auto layer = add_layer(args.shape_parent); parse_g_common( {args.element, &layer->shapes, style, false}, layer, layer->transform.get(), style ); } void parse_g_to_shape(const ParseFuncArgs& args) { Style style = parse_style(args.element, args.parent_style); auto ugroup = std::make_unique(document); auto group = ugroup.get(); args.shape_parent->insert(std::move(ugroup)); parse_g_common( {args.element, &group->shapes, style, true}, group, group->transform.get(), style ); } void parse_g_common( const ParseFuncArgs& args, model::Group* g_node, model::Transform* transform, Style& style ) { apply_common_style(g_node, args.element, args.parent_style); auto anim = parse_animated(args.element); for ( const auto& kf : anim.single("opacity") ) g_node->opacity.set_keyframe(kf.time, kf.values.vector()[0])->set_transition(kf.transition); display_to_opacity(g_node, anim, g_node->opacity, &style); set_name(g_node, args.element); // Avoid doubling opacity values style.map.erase("opacity"); parse_children(args); parse_transform(args.element, g_node, transform); } std::vector parse_bezier_impl(const ParseFuncArgs& args, const math::bezier::MultiBezier& bez) { if ( bez.beziers().empty() ) return {}; ShapeCollection shapes; std::vector paths; for ( const auto& bezier : bez.beziers() ) { model::Path* shape = push(shapes); paths.push_back(shape); shape->shape.set(bezier); shape->closed.set(bezier.closed()); } add_shapes(args, std::move(shapes)); return paths; } model::Path* parse_bezier_impl_single(const ParseFuncArgs& args, const math::bezier::Bezier& bez) { ShapeCollection shapes; auto path = push(shapes); path->shape.set(bez); add_shapes(args, {std::move(shapes)}); return path; } detail::AnimateParser::AnimatedProperties parse_animated(const QDomElement& element) { return animate_parser.parse_animated_properties(element); } void parseshape_line(const ParseFuncArgs& args) { math::bezier::Bezier bez; bez.add_point(QPointF( len_attr(args.element, "x1", 0), len_attr(args.element, "y1", 0) )); bez.line_to(QPointF( len_attr(args.element, "x2", 0), len_attr(args.element, "y2", 0) )); auto path = parse_bezier_impl_single(args, bez); for ( const auto& kf : parse_animated(args.element).joined({"x1", "y1", "x2", "y2"}) ) { math::bezier::Bezier bez; bez.add_point({kf.values[0].vector()[0], kf.values[1].vector()[0]}); bez.add_point({kf.values[2].vector()[0], kf.values[3].vector()[0]}); path->shape.set_keyframe(kf.time, bez)->set_transition(kf.transition); } } math::bezier::Bezier build_poly(const std::vector& coords, bool close) { math::bezier::Bezier bez; if ( coords.size() < 4 ) { if ( !coords.empty() ) warning("Not enough `points` for `polygon` / `polyline`"); return bez; } bez.add_point(QPointF(coords[0], coords[1])); for ( int i = 2; i < int(coords.size()); i+= 2 ) bez.line_to(QPointF(coords[i], coords[i+1])); if ( close ) bez.close(); return bez; } void handle_poly(const ParseFuncArgs& args, bool close) { auto path = parse_bezier_impl_single(args, build_poly(double_args(args.element.attribute("points", "")), close)); if ( !path ) return; for ( const auto& kf : parse_animated(args.element).single("points") ) path->shape.set_keyframe(kf.time, build_poly(kf.values.vector(), close))->set_transition(kf.transition); } void parseshape_polyline(const ParseFuncArgs& args) { handle_poly(args, false); } void parseshape_polygon(const ParseFuncArgs& args) { handle_poly(args, true); } void parseshape_path(const ParseFuncArgs& args) { if ( parse_star(args) ) return; QString d = args.element.attribute("d"); math::bezier::MultiBezier bez = PathDParser(d).parse(); /// \todo sodipodi:nodetypes auto paths = parse_bezier_impl(args, bez); path_animation(paths, parse_animated(args.element), "d" ); } bool parse_star(const ParseFuncArgs& args) { if ( attr(args.element, "sodipodi", "type") != "star" ) return false; qreal randomized = attr(args.element, "inkscape", "randomized", "0").toDouble(); if ( !qFuzzyCompare(randomized, 0.0) ) return false; qreal rounded = attr(args.element, "inkscape", "rounded", "0").toDouble(); if ( !qFuzzyCompare(rounded, 0.0) ) return false; ShapeCollection shapes; auto shape = push(shapes); shape->points.set( attr(args.element, "sodipodi", "sides").toInt() ); auto flat = attr(args.element, "inkscape", "flatsided"); shape->type.set( flat == "true" ? model::PolyStar::Polygon : model::PolyStar::Star ); shape->position.set(QPointF( attr(args.element, "sodipodi", "cx").toDouble(), attr(args.element, "sodipodi", "cy").toDouble() )); shape->outer_radius.set(attr(args.element, "sodipodi", "r1").toDouble()); shape->inner_radius.set(attr(args.element, "sodipodi", "r2").toDouble()); shape->angle.set( math::rad2deg(attr(args.element, "sodipodi", "arg1").toDouble()) +90 ); add_shapes(args, std::move(shapes)); return true; } void parseshape_use(const ParseFuncArgs& args) { QString id = attr(args.element, "xlink", "href"); if ( !id.startsWith('#') ) return; id.remove(0, 1); QDomElement element = element_by_id(id); if ( element.isNull() ) return; Style style = parse_style(args.element, args.parent_style); auto group = std::make_unique(document); apply_common_style(group.get(), args.element, style); set_name(group.get(), args.element); parse_shape({element, &group->shapes, style, true}); group->transform.get()->position.set( QPointF(len_attr(args.element, "x", 0), len_attr(args.element, "y", 0)) ); parse_transform(args.element, group.get(), group->transform.get()); args.shape_parent->insert(std::move(group)); } QString find_asset_file(const QString& path) { QFileInfo finfo(path); if ( finfo.exists() ) return path; else if ( default_asset_path.exists(path) ) return default_asset_path.filePath(path); else if ( default_asset_path.exists(finfo.fileName()) ) return default_asset_path.filePath(finfo.fileName()); return {}; } bool open_asset_file(model::Bitmap* image, const QString& path) { if ( path.isEmpty() ) return false; auto file = find_asset_file(path); if ( file.isEmpty() ) return false; return image->from_file(file); } void parseshape_image(const ParseFuncArgs& args) { auto bitmap = std::make_unique(document); bool open = false; QString href = attr(args.element, "xlink", "href"); QUrl url = href; if ( url.isRelative() ) open = open_asset_file(bitmap.get(), href); if ( !open ) { if ( url.isLocalFile() ) open = open_asset_file(bitmap.get(), url.toLocalFile()); else open = bitmap->from_url(url); } if ( !open ) { QString path = attr(args.element, "sodipodi", "absref"); open = open_asset_file(bitmap.get(), path); } if ( !open ) warning(QString("Could not load image %1").arg(href)); auto image = std::make_unique(document); image->image.set(document->assets()->images->values.insert(std::move(bitmap))); QTransform trans; if ( args.element.hasAttribute("transform") ) trans = svg_transform(args.element.attribute("transform"), trans).transform; trans.translate( len_attr(args.element, "x", 0), len_attr(args.element, "y", 0) ); image->transform->set_transform_matrix(trans); args.shape_parent->insert(std::move(image)); } struct TextStyle { QString family = "sans-serif"; int weight = QFont::Normal; QFont::Style style = QFont::StyleNormal; qreal line_spacing = 0; qreal size = 64; bool keep_space = false; QPointF pos; }; TextStyle parse_text_style(const ParseFuncArgs& args, const TextStyle& parent) { TextStyle out = parent; Style style = parse_style(args.element, args.parent_style); if ( style.contains("font-family") ) out.family = style["font-family"]; if ( style.contains("font-style") ) { QString slant = style["font-style"]; if ( slant == "normal" ) out.style = QFont::StyleNormal; else if ( slant == "italic" ) out.style = QFont::StyleItalic; else if ( slant == "oblique" ) out.style = QFont::StyleOblique; } if ( style.contains("font-size") ) { QString size = style["font-size"]; static const std::map size_names = { {{"xx-small"}, {8}}, {{"x-small"}, {16}}, {{"small"}, {32}}, {{"medium"}, {64}}, {{"large"}, {128}}, {{"x-large"}, {256}}, {{"xx-large"}, {512}}, }; if ( size == "smaller" ) out.size /= 2; else if ( size == "larger" ) out.size *= 2; else if ( size_names.count(size) ) out.size = size_names.at(size); else out.size = parse_unit(size); } if ( style.contains("font-weight") ) { QString weight = style["font-weight"]; if ( weight == "bold" ) out.weight = 700; else if ( weight == "normal" ) out.weight = 400; else if ( weight == "bolder" ) out.weight = qMin(1000, out.weight + 100); else if ( weight == "lighter") out.weight = qMax(1, out.weight - 100); else out.weight = weight.toInt(); } if ( style.contains("line-height") ) out.line_spacing = parse_unit(style["line-height"]); if ( args.element.hasAttribute("xml:space") ) out.keep_space = args.element.attribute("xml:space") == "preserve"; if ( args.element.hasAttribute("x") ) out.pos.setX(len_attr(args.element, "x", 0)); if ( args.element.hasAttribute("y") ) out.pos.setY(len_attr(args.element, "y", 0)); return out; } QString trim_text(const QString& text) { QString trimmed = text.simplified(); if ( !text.isEmpty() && text.back().isSpace() ) trimmed += ' '; return trimmed; } void apply_text_style(model::Font* font, const TextStyle& style) { font->family.set(style.family); font->size.set(unit_convert(style.size, "px", "pt")); QFont qfont; qfont.setFamily(style.family); qfont.setWeight(QFont::Weight(WeightConverter::convert(style.weight, WeightConverter::css, WeightConverter::qt))); qfont.setStyle(style.style); QFontDatabase db; QString style_string = db.styleString(qfont); font->style.set(style_string); } QPointF parse_text_element(const ParseFuncArgs& args, const TextStyle& parent_style) { TextStyle style = parse_text_style(args, parent_style); Style css_style = parse_style(args.element, args.parent_style); auto anim = parse_animated(args.element); model::TextShape* last = nullptr; QPointF offset; QPointF pos = style.pos; QString text; for ( const auto & child : ItemCountRange(args.element.childNodes()) ) { ParseFuncArgs child_args = {child.toElement(), args.shape_parent, css_style, args.in_group}; if ( child.isElement() ) { last = nullptr; style.pos = pos + offset; offset = parse_text_element(child_args, style); } else if ( child.isText() || child.isCDATASection() ) { text += child.toCharacterData().data(); if ( !last ) { ShapeCollection shapes; last = push(shapes); last->position.set(pos + offset); apply_text_style(last->font.get(), style); for ( const auto& kf : anim.joined({"x", "y"}) ) { last->position.set_keyframe( kf.time, offset + QPointF(kf.values[0].vector()[0], kf.values[1].vector()[0]) )->set_transition(kf.transition); } add_shapes(child_args, std::move(shapes)); } last->text.set(style.keep_space ? text : trim_text(text)); offset = last->offset_to_next_character(); } } return offset; } void parseshape_text(const ParseFuncArgs& args) { parse_text_element(args, {}); } void parse_metadata() { auto meta = dom.elementsByTagNameNS(xmlns.at("cc"), "Work"); if ( meta.count() == 0 ) return; auto work = query_element({"metadata", "RDF", "Work"}, dom.documentElement()); document->info().author = query({"creator", "Agent", "title"}, work); document->info().description = query({"description"}, work); for ( const auto& domnode : ItemCountRange(query_element({"subject", "Bag"}, work).childNodes()) ) { if ( domnode.isElement() ) { auto child = domnode.toElement(); if ( child.tagName() == "li" ) document->info().keywords.push_back(child.text()); } } } GroupMode group_mode; std::vector css_blocks; QDir default_asset_path; static const std::map shape_parsers; static const QRegularExpression transform_re; static const QRegularExpression url_re; }; const std::map glaxnimate::io::svg::SvgParser::Private::shape_parsers = { {"g", &glaxnimate::io::svg::SvgParser::Private::parseshape_g}, {"rect", &glaxnimate::io::svg::SvgParser::Private::parseshape_rect}, {"ellipse", &glaxnimate::io::svg::SvgParser::Private::parseshape_ellipse}, {"circle", &glaxnimate::io::svg::SvgParser::Private::parseshape_circle}, {"line", &glaxnimate::io::svg::SvgParser::Private::parseshape_line}, {"polyline",&glaxnimate::io::svg::SvgParser::Private::parseshape_polyline}, {"polygon", &glaxnimate::io::svg::SvgParser::Private::parseshape_polygon}, {"path", &glaxnimate::io::svg::SvgParser::Private::parseshape_path}, {"use", &glaxnimate::io::svg::SvgParser::Private::parseshape_use}, {"image", &glaxnimate::io::svg::SvgParser::Private::parseshape_image}, {"text", &glaxnimate::io::svg::SvgParser::Private::parseshape_text}, }; const QRegularExpression glaxnimate::io::svg::detail::SvgParserPrivate::unit_re{R"(([-+]?(?:[0-9]*\.[0-9]+|[0-9]+)([eE][-+]?[0-9]+)?)([a-z]*))"}; const QRegularExpression glaxnimate::io::svg::SvgParser::Private::transform_re{R"(([a-zA-Z]+)\s*\(([^\)]*)\))"}; const QRegularExpression glaxnimate::io::svg::SvgParser::Private::url_re{R"(url\s*\(\s*(#[-a-zA-Z0-9_]+)\s*\)\s*)"}; const QRegularExpression glaxnimate::io::svg::detail::AnimateParser::separator{"\\s*,\\s*|\\s+"}; const QRegularExpression glaxnimate::io::svg::detail::AnimateParser::clock_re{R"((?:(?:(?[0-9]+):)?(?:(?[0-9]{2}):)?(?[0-9]+(?:\.[0-9]+)?))|(?:(?[0-9]+(?:\.[0-9]+)?)(?h|min|s|ms)))"}; const QRegularExpression glaxnimate::io::svg::detail::AnimateParser::frame_separator_re{"\\s*;\\s*"}; glaxnimate::io::svg::SvgParser::SvgParser( QIODevice* device, GroupMode group_mode, model::Document* document, const std::function& on_warning, ImportExport* io, QSize forced_size, model::FrameTime default_time, QDir default_asset_path ) : d(std::make_unique(document, on_warning, io, forced_size, default_time, group_mode, default_asset_path)) { d->load(device); } glaxnimate::io::svg::SvgParser::~SvgParser() { } glaxnimate::io::mime::DeserializedData glaxnimate::io::svg::SvgParser::parse_to_objects() { glaxnimate::io::mime::DeserializedData data; data.initialize_data(); d->parse(data.document.get()); return data; } void glaxnimate::io::svg::SvgParser::parse_to_document() { d->parse(); } static qreal hex(const QString& s, int start, int size) { return utils::mid_ref(s, start, size).toInt(nullptr, 16) / (size == 2 ? 255.0 : 15.0); } QColor glaxnimate::io::svg::parse_color(const QString& string) { if ( string.isEmpty() ) return {}; // #fff #112233 if ( string[0] == '#' ) { if ( string.size() == 4 || string.size() == 5 ) { qreal alpha = string.size() == 4 ? 1. : hex(string, 4, 1); return QColor::fromRgbF(hex(string, 1, 1), hex(string, 2, 1), hex(string, 3, 1), alpha); } else if ( string.size() == 7 || string.size() == 9 ) { qreal alpha = string.size() == 7 ? 1. : hex(string, 7, 2); return QColor::fromRgbF(hex(string, 1, 2), hex(string, 3, 2), hex(string, 5, 2), alpha); } return QColor(); } // transparent if ( string == "transparent" || string == "none" ) return QColor(0, 0, 0, 0); QRegularExpressionMatch match; // rgba(123, 123, 123, 0.7) static QRegularExpression rgba{R"(^rgba\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9.eE]+)\s*\)$)"}; match = rgba.match(string); if ( match.hasMatch() ) return QColor(match.captured(1).toInt(), match.captured(2).toInt(), match.captured(3).toInt(), match.captured(4).toDouble() * 255); // rgb(123, 123, 123) static QRegularExpression rgb{R"(^rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)$)"}; match = rgb.match(string); if ( match.hasMatch() ) return QColor(match.captured(1).toInt(), match.captured(2).toInt(), match.captured(3).toInt()); // rgba(60%, 30%, 20%, 0.7) static QRegularExpression rgba_pc{R"(^rgba\s*\(\s*([0-9.eE]+)%\s*,\s*([0-9.eE]+)%\s*,\s*([0-9.eE]+)%\s*,\s*([0-9.eE]+)\s*\)$)"}; match = rgba_pc.match(string); if ( match.hasMatch() ) return QColor::fromRgbF(match.captured(1).toDouble() / 100, match.captured(2).toDouble() / 100, match.captured(3).toDouble() / 100, match.captured(4).toDouble()); // rgb(60%, 30%, 20%) static QRegularExpression rgb_pc{R"(^rgb\s*\(\s*([0-9.eE]+)%\s*,\s*([0-9.eE]+)%\s*,\s*([0-9.eE]+)%\s*\)$)"}; match = rgb_pc.match(string); if ( match.hasMatch() ) return QColor::fromRgbF(match.captured(1).toDouble() / 100, match.captured(2).toDouble() / 100, match.captured(3).toDouble() / 100); // hsl(60, 30%, 20%) static QRegularExpression hsl{R"(^hsl\s*\(\s*([0-9.eE]+)\s*,\s*([0-9.eE]+)%\s*,\s*([0-9.eE]+)%\s*\)$)"}; match = rgb_pc.match(string); if ( match.hasMatch() ) return QColor::fromHslF(match.captured(1).toDouble() / 360, match.captured(2).toDouble() / 100, match.captured(3).toDouble() / 100); // hsla(60, 30%, 20%, 0.7) static QRegularExpression hsla{R"(^hsla\s*\(\s*([0-9.eE]+)\s*,\s*([0-9.eE]+)%\s*,\s*([0-9.eE]+)%\s*,\s*([0-9.eE]+)\s*\)$)"}; match = rgb_pc.match(string); if ( match.hasMatch() ) return QColor::fromHslF(match.captured(1).toDouble() / 360, match.captured(2).toDouble() / 100, match.captured(3).toDouble() / 100, match.captured(4).toDouble()); // red return QColor(string); } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/utils/gzip.hpp000664 001750 001750 00000002407 14477652011 027122 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include #include #include namespace glaxnimate::utils::gzip { using ErrorFunc = std::function; bool compress(const QByteArray& input, QIODevice& output, const ErrorFunc& on_error, int level = 9, quint32* compressed_size = nullptr); bool decompress(QIODevice& input, QByteArray& output, const ErrorFunc& on_error); bool decompress(const QByteArray& input, QByteArray& output, const ErrorFunc& on_error); bool is_compressed(QIODevice& input); bool is_compressed(const QByteArray& input); class GzipStream : public QIODevice { public: GzipStream(QIODevice* target, const ErrorFunc& on_error); ~GzipStream(); bool atEnd() const override; bool isSequential() const override { return true; } bool open(QIODevice::OpenMode mode) override; qint64 ouput_size() const; protected: qint64 readData(char * data, qint64 maxlen) override; qint64 writeData(const char * data, qint64 len) override; private: class Private; std::unique_ptr d; }; QString zlib_version(); } // namespace glaxnimate::utils::gzip mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/svg/svg_renderer.cpp000664 001750 001750 00000135032 14477652011 030700 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "svg_renderer.hpp" #include "model/document.hpp" #include "model/shapes/group.hpp" #include "model/shapes/layer.hpp" #include "model/shapes/precomp_layer.hpp" #include "model/shapes/rect.hpp" #include "model/shapes/ellipse.hpp" #include "model/shapes/path.hpp" #include "model/shapes/polystar.hpp" #include "model/shapes/fill.hpp" #include "model/shapes/stroke.hpp" #include "model/shapes/image.hpp" #include "model/shapes/text.hpp" #include "model/shapes/repeater.hpp" #include "model/animation/join_animatables.hpp" #include "model/custom_font.hpp" #include "math/math.hpp" #include "detail.hpp" #include "font_weight.hpp" #include "io/utils.hpp" using namespace glaxnimate::io::svg::detail; using namespace glaxnimate; class io::svg::SvgRenderer::Private { public: void collect_defs(model::Composition* comp) { if ( !at_start ) return; fps = comp->fps.get(); ip = comp->animation->first_frame.get(); op = comp->animation->last_frame.get(); if ( ip >= op ) animated = NotAnimated; at_start = false; defs = element(svg, "defs"); for ( const auto& color : comp->document()->assets()->colors->values ) write_named_color(defs, color.get()); for ( const auto& color : comp->document()->assets()->gradient_colors->values ) write_gradient_colors(defs, color.get()); for ( const auto& gradient : comp->document()->assets()->gradients->values ) write_gradient(defs, gradient.get()); auto view = element(svg, "sodipodi:namedview"); view.setAttribute("inkscape:pagecheckerboard", "true"); view.setAttribute("borderlayer", "true"); view.setAttribute("bordercolor", "#666666"); view.setAttribute("pagecolor", "#ffffff"); view.setAttribute("inkscape:document-units", "px"); add_fonts(comp->document()); write_meta(comp); } void write_meta(model::Composition* comp) { auto rdf = element(element(svg, "metadata"), "rdf:RDF"); auto work = element(rdf, "cc:Work"); element(work, "dc:format").appendChild(dom.createTextNode("image/svg+xml")); QString dc_type = animated ? "MovingImage" : "StillImage"; element(work, "dc:type").setAttribute("rdf:resource", "http://purl.org/dc/dcmitype/" + dc_type); element(work, "dc:title").appendChild(dom.createTextNode(comp->name.get())); auto document = comp->document(); if ( document->info().empty() ) return; if ( !document->info().author.isEmpty() ) element(element(element(work, "dc:creator"), "cc:Agent"), "dc:title").appendChild(dom.createTextNode(document->info().author)); if ( !document->info().description.isEmpty() ) element(work, "dc:description").appendChild(dom.createTextNode(document->info().description)); if ( !document->info().keywords.empty() ) { auto bag = element(element(work, "dc:subject"), "rdf:Bag"); for ( const auto& kw: document->info().keywords ) element(bag, "rdf:li").appendChild(dom.createTextNode(kw)); } } void add_fonts(model::Document* document) { if ( font_type == CssFontType::None ) return; QString css; static QString font_face = R"( @font-face { font-family: '%1'; font-style: %2; font-weight: %3; src: url(%4); } )"; for ( const auto & font : document->assets()->fonts->values ) { auto custom = font->custom_font(); if ( !custom.is_valid() ) continue; QRawFont raw = custom.raw_font(); auto type = qMin(suggested_type(font.get()), font_type); if ( type == CssFontType::Link ) { auto link = element(svg, "link"); link.setAttribute("xmlns", "http://www.w3.org/1999/xhtml"); link.setAttribute("rel", "stylesheet"); link.setAttribute("href", font->css_url.get()); link.setAttribute("type", "text/css"); } else if ( type == CssFontType::FontFace ) { css += font_face .arg(custom.family()) .arg(WeightConverter::convert(raw.weight(), WeightConverter::qt, WeightConverter::css)) .arg(raw.style() == QFont::StyleNormal ? 0 : 1) .arg(font->source_url.get()) ; } else if ( type == CssFontType::Embedded ) { QString base64_encoded = font->data.get().toBase64(QByteArray::Base64UrlEncoding); QString format = model::CustomFontDatabase::font_data_format(font->data.get()) == model::FontFileFormat::OpenType ? "opentype" : "ttf"; css += font_face .arg(custom.family()) .arg(WeightConverter::convert(raw.weight(), WeightConverter::qt, WeightConverter::css)) .arg(raw.style() == QFont::StyleNormal ? 0 : 1) .arg("data:application/x-font-" + format + ";charset=utf-8;base64," + base64_encoded) ; } } if ( !css.isEmpty() ) element(svg, "style").appendChild(dom.createTextNode(css)); } QDomElement element(QDomNode parent, const char* tag) { QDomElement e = dom.createElement(tag); parent.appendChild(e); return e; } void write_composition(QDomElement& parent, model::Composition* comp) { for ( const auto& lay : comp->shapes ) write_shape(parent, lay.get(), false); } void write_visibility_attributes(QDomElement& parent, model::VisualNode* node) { if ( !node->visible.get() ) parent.setAttribute("display", "none"); if ( node->locked.get() ) parent.setAttribute("sodipodi:insensitive", "true"); } void write_shapes(QDomElement& parent, const model::ShapeListProperty& shapes, bool has_mask = false) { if ( shapes.empty() ) return; auto it = shapes.begin(); if ( has_mask ) ++it; for ( ; it != shapes.end(); ++it ) write_shape(parent, it->get(), false); } QString styler_to_css(model::Styler* styler) { if ( styler->use.get() ) return "url(#" + non_uuid_ids_map[styler->use.get()] + ")"; if ( styler->color.get().alpha() == 0 ) return "transparent"; return styler->color.get().name(); } QDomElement write_styler_shapes(QDomElement& parent, model::Styler* styler, const Style::Map& style) { if ( styler->affected().size() == 1 ) { write_shape_shape(parent, styler->affected()[0], style); write_visibility_attributes(parent, styler); parent.setAttribute("id", id(styler)); return parent; } auto g = start_group(parent, styler); write_style(g, style); write_visibility_attributes(g, styler); g.setAttribute("id", id(styler)); for ( model::ShapeElement* subshape : styler->affected() ) { write_shape_shape(g, subshape, style); } return g; } QString unlerp_time(model::FrameTime time) const { return QString::number(math::unlerp(ip, op, time), 'f'); } struct AnimationData { struct Attribute { QString attribute; QStringList values = {}; }; AnimationData(SvgRenderer::Private* parent, const std::vector& attrs, int n_keyframes, qreal time_stretch, model::FrameTime time_start) : parent(parent), time_stretch(time_stretch), time_start(time_start) { attributes.reserve(attrs.size()); for ( const auto& attr : attrs ) { attributes.push_back({attr}); attributes.back().values.reserve(n_keyframes); } } QString key_spline(const model::KeyframeTransition& trans) { return QString("%1 %2 %3 %4") .arg(trans.before().x(), 0, 'f') .arg(trans.before().y(), 0, 'f') .arg(trans.after().x(), 0, 'f') .arg(trans.after().y(), 0, 'f') ; } void add_values(const std::vector& vals) { for ( std::size_t i = 0; i != attributes.size(); i++ ) attributes[i].values.push_back(vals[i]); } void add_keyframe(model::FrameTime time, const std::vector& vals, const model::KeyframeTransition& trans) { if ( time < parent->ip || time > parent->op ) return; if ( key_times.empty() && time > parent->ip ) { key_times.push_back("0"); key_splines.push_back("0 0 1 1"); add_values(vals); } else if ( hold && last + 1 < time ) { key_times.push_back(parent->unlerp_time(time - 1)); key_splines.push_back("0 0 1 1"); for ( std::size_t i = 0; i != attributes.size(); i++ ) attributes[i].values.push_back(attributes[i].values.back()); } key_times.push_back(parent->unlerp_time(time)); key_splines.push_back(key_spline(trans)); for ( std::size_t i = 0; i != attributes.size(); i++ ) attributes[i].values.push_back(vals[i]); last = time; hold = trans.hold(); } void add_dom( QDomElement& element, const char* tag = "animate", const QString& type = {}, const QString& path = {}, bool auto_orient = false ) { if ( last < parent->op && path.isEmpty() ) { key_times.push_back("1"); for ( auto& attr : attributes ) { if ( !attr.values.empty() ) attr.values.push_back(attr.values.back()); } } else { key_splines.pop_back(); } QString key_times_str = key_times.join("; "); QString key_splines_str = key_splines.join("; "); for ( const auto& data : attributes ) { QDomElement animation = parent->element(element, tag); animation.setAttribute("begin", parent->clock(time_start + time_stretch * parent->ip)); animation.setAttribute("dur", parent->clock(time_start + time_stretch * parent->op-parent->ip)); animation.setAttribute("attributeName", data.attribute); animation.setAttribute("calcMode", "spline"); if ( !path.isEmpty() ) { animation.setAttribute("path", path); if ( auto_orient ) animation.setAttribute("rotate", "auto"); } animation.setAttribute("keyTimes", key_times_str); animation.setAttribute("keySplines", key_splines_str); animation.setAttribute("repeatCount", "indefinite"); if ( !type.isEmpty() ) animation.setAttribute("type", type); } } SvgRenderer::Private* parent; std::vector attributes; QStringList key_times = {}; QStringList key_splines = {}; model::FrameTime last = 0; bool hold = false; qreal time_stretch = 1; model::FrameTime time_start = 0; }; void write_property( QDomElement& element, model::AnimatableBase* property, const QString& attr ) { element.setAttribute(attr, property->value().toString()); if ( animated ) { if ( property->keyframe_count() < 2 ) return; auto keyframes = split_keyframes(property); AnimationData data(this, {attr}, keyframes.size(), time_stretch, time_start); for ( int i = 0; i < int(keyframes.size()); i++ ) { auto kf = keyframes[i].get(); data.add_keyframe(time_to_global(kf->time()), {kf->value().toString()}, kf->transition()); } data.add_dom(element); } } qreal time_to_global(qreal time) { for ( auto it = timing.rbegin(), end = timing.rend(); it != end; ++it ) time = (*it)->time_from_local(time); return time; } template void write_properties( QDomElement& element, std::vector properties, const std::vector& attrs, const Callback& callback ) { auto jflags = animated == NotAnimated ? model::JoinAnimatables::NoKeyframes : model::JoinAnimatables::Normal; model::JoinedAnimatable j(std::move(properties), {}, jflags); { auto vals = callback(j.current_value()); for ( std::size_t i = 0; i != attrs.size(); i++ ) element.setAttribute(attrs[i], vals[i]); } if ( j.animated() && animated ) { auto keys = split_keyframes(&j); AnimationData data(this, attrs, keys.size(), time_stretch, time_start); for ( const auto& kf : keys ) data.add_keyframe(time_to_global(kf->time()), callback(j.value_at(kf->time())), kf->transition()); data.add_dom(element); } } static std::vector callback_point(const std::vector& values) { return callback_point_result(values[0].toPointF()); } static std::vector callback_point_result(const QPointF& c) { return std::vector{ QString::number(c.x()), QString::number(c.y()) }; } void write_shape_rect(QDomElement& parent, model::Rect* rect, const Style::Map& style) { auto e = element(parent, "rect"); write_style(e, style); write_properties(e, {&rect->position, &rect->size}, {"x", "y"}, [](const std::vector& values){ QPointF c = values[0].toPointF(); QSizeF s = values[1].toSizeF(); return std::vector{ QString::number(c.x() - s.width()/2), QString::number(c.y() - s.height()/2) }; } ); write_properties(e, {&rect->size}, {"width", "height"}, [](const std::vector& values){ QSizeF s = values[0].toSizeF(); return std::vector{ QString::number(s.width()), QString::number(s.height()) }; } ); write_property(e, &rect->rounded, "ry"); } void write_shape_ellipse(QDomElement& parent, model::Ellipse* ellipse, const Style::Map& style) { auto e = element(parent, "ellipse"); write_style(e, style); write_properties(e, {&ellipse->position}, {"cx", "cy"}, &Private::callback_point); write_properties(e, {&ellipse->size}, {"rx", "ry"}, [](const std::vector& values){ QSizeF s = values[0].toSizeF(); return std::vector{ QString::number(s.width() / 2), QString::number(s.height() / 2) }; } ); } void write_shape_star(QDomElement& parent, model::PolyStar* star, const Style::Map& style) { model::FrameTime time = star->time(); auto e = write_bezier(parent, star, style); if ( star->outer_roundness.animated() || !qFuzzyIsNull(star->outer_roundness.get()) || star->inner_roundness.animated() || !qFuzzyIsNull(star->inner_roundness.get()) ) return; set_attribute(e, "sodipodi:type", "star"); set_attribute(e, "inkscape:randomized", "0"); // inkscape:rounded Works differently than lottie so we leave it as 0 set_attribute(e, "inkscape:rounded", "0"); int sides = star->points.get_at(time); set_attribute(e, "sodipodi:sides", sides); set_attribute(e, "inkscape:flatsided", star->type.get() == model::PolyStar::Polygon); QPointF c = star->position.get_at(time); set_attribute(e, "sodipodi:cx", c.x()); set_attribute(e, "sodipodi:cy", c.y()); set_attribute(e, "sodipodi:r1", star->outer_radius.get_at(time)); set_attribute(e, "sodipodi:r2", star->inner_radius.get_at(time)); qreal angle = math::deg2rad(star->angle.get_at(time) - 90); set_attribute(e, "sodipodi:arg1", angle); set_attribute(e, "sodipodi:arg2", angle + math::pi / sides); } void write_shape_text(QDomElement& parent, model::TextShape* text, Style::Map style) { QFontInfo font_info(text->font->query()); // QFontInfo is broken, so we do something else QFontDatabase db; int weight = db.weight(font_info.family(), font_info.styleName()); QFont::Style font_style = db.italic(font_info.family(), font_info.styleName()) ? QFont::StyleItalic : QFont::StyleNormal; // Convert weight weight = WeightConverter::convert(weight, WeightConverter::qt, WeightConverter::css); style["font-family"] = font_info.family(); style["font-size"] = QString("%1pt").arg(font_info.pointSizeF()); style["line-height"] = QString("%1px").arg(text->font->line_spacing()); style["font-weight"] = QString::number(weight); switch ( font_style ) { case QFont::StyleNormal: style["font-style"] = "normal"; break; case QFont::StyleItalic: style["font-style"] = "italic"; break; case QFont::StyleOblique: style["font-style"] = "oblique"; break; } auto e = element(parent, "text"); write_style(e, style); write_properties(e, {&text->position}, {"x", "y"}, &Private::callback_point); model::Font::CharDataCache cache; for ( const auto& line : text->font->layout(text->text.get()) ) { auto tspan = element(e, "tspan"); tspan.appendChild(dom.createTextNode(line.text)); set_attribute(tspan, "sodipodi:role", "line"); write_properties(tspan, {&text->position}, {"x", "y"}, [base=line.baseline](const std::vector& values){ return callback_point_result(values[0].toPointF() + base); }); tspan.setAttribute("xml:space", "preserve"); } } void write_shape_shape(QDomElement& parent, model::ShapeElement* shape, const Style::Map& style) { if ( auto rect = qobject_cast(shape) ) { write_shape_rect(parent, rect, style); } else if ( auto ellipse = qobject_cast(shape) ) { write_shape_ellipse(parent, ellipse, style); } else if ( auto star = qobject_cast(shape) ) { write_shape_star(parent, star, style); } else if ( auto text = shape->cast() ) { write_shape_text(parent, text, style); } else if ( !qobject_cast(shape) ) { write_bezier(parent, shape, style); } } void write_styler_attrs(QDomElement& element, model::Styler* styler, const QString& attr) { if ( styler->use.get() ) { element.setAttribute(attr, "url(#" + non_uuid_ids_map[styler->use.get()] + ")"); return; } write_property(element, &styler->color, attr); write_property(element, &styler->opacity, attr+"-opacity"); } void write_image(model::Image* img, QDomElement& parent) { if ( img->image.get() ) { auto e = element(parent, "image"); set_attribute(e, "x", 0); set_attribute(e, "y", 0); set_attribute(e, "width", img->image->width.get()); set_attribute(e, "height", img->image->height.get()); transform_to_attr(e, img->transform.get()); set_attribute(e, "xlink:href", img->image->to_url().toString()); } } void write_stroke(model::Stroke* stroke, QDomElement& parent) { Style::Map style; style["fill"] = "none"; if ( !animated ) { style["stroke"] = styler_to_css(stroke); style["stroke-opacity"] = QString::number(stroke->opacity.get()); style["stroke-width"] = QString::number(stroke->width.get()); } switch ( stroke->cap.get() ) { case model::Stroke::Cap::ButtCap: style["stroke-linecap"] = "butt"; break; case model::Stroke::Cap::RoundCap: style["stroke-linecap"] = "round"; break; case model::Stroke::Cap::SquareCap: style["stroke-linecap"] = "square"; break; } switch ( stroke->join.get() ) { case model::Stroke::Join::BevelJoin: style["stroke-linejoin"] = "bevel"; break; case model::Stroke::Join::RoundJoin: style["stroke-linejoin"] = "round"; break; case model::Stroke::Join::MiterJoin: style["stroke-linejoin"] = "miter"; style["stroke-miterlimit"] = QString::number(stroke->miter_limit.get()); break; } style["stroke-dasharray"] = "none"; QDomElement g = write_styler_shapes(parent, stroke, style); if ( animated ) { write_styler_attrs(g, stroke, "stroke"); write_property(g, &stroke->width, "stroke-width"); } } void write_fill(model::Fill* fill, QDomElement& parent) { Style::Map style; if ( !animated ) { style["fill"] = styler_to_css(fill); style["fill-opacity"] = QString::number(fill->opacity.get()); } style["stroke"] = "none"; QDomElement g = write_styler_shapes(parent, fill, style); if ( animated ) write_styler_attrs(g, fill, "fill"); } void write_precomp_layer(model::PreCompLayer* layer, QDomElement& parent) { if ( layer->composition.get() ) { timing.push_back(layer->timing.get()); auto clip = element(defs, "clipPath"); set_attribute(clip, "id", "clip_" + id(layer)); set_attribute(clip, "clipPathUnits", "userSpaceOnUse"); auto clip_rect = element(clip, "rect"); set_attribute(clip_rect, "x", "0"); set_attribute(clip_rect, "y", "0"); set_attribute(clip_rect, "width", layer->size.get().width()); set_attribute(clip_rect, "height", layer->size.get().height()); auto e = start_layer(parent, layer); transform_to_attr(e, layer->transform.get()); write_property(e, &layer->opacity, "opacity"); write_visibility_attributes(parent, layer); time_stretch = layer->timing->stretch.get(); time_start = layer->timing->start_time.get(); write_composition(e, layer->composition.get()); time_stretch = 1; time_start = 0; timing.pop_back(); } } void write_repeater_vis(QDomElement& element, model::Repeater* repeater, int index, int n_copies) { element.setAttribute("display", index < repeater->copies.get() ? "block" : "none"); float alpha_lerp = float(index) / (n_copies == 1 ? 1 : n_copies - 1); model::JoinAnimatables opacity({&repeater->start_opacity, &repeater->end_opacity}, model::JoinAnimatables::NoValues); auto opacity_func = [&alpha_lerp](float a, float b){ return math::lerp(a, b, alpha_lerp); }; set_attribute(element, "opacity", opacity.combine_current_value(opacity_func)); if ( animated ) { int kf_count = repeater->copies.keyframe_count(); if ( kf_count >= 2 ) { AnimationData anim_display(this, {"display"}, kf_count, time_stretch, time_start); for ( int i = 0; i < kf_count; i++ ) { auto kf = repeater->copies.keyframe(i); anim_display.add_keyframe(time_to_global(kf->time()), {index < kf->get() ? "block" : "none"}, kf->transition()); } anim_display.add_dom(element); } if ( opacity.animated() ) { AnimationData anim_opacity(this, {"opacity"}, opacity.keyframes().size(), time_stretch, time_start); for ( const auto& keyframe : opacity.keyframes() ) { anim_opacity.add_keyframe( time_to_global(keyframe.time), {QString::number(opacity.combine_value_at(keyframe.time, opacity_func))}, keyframe.transition() ); } } } } void write_repeater(model::Repeater* repeater, QDomElement& parent, bool force_draw) { int n_copies = repeater->max_copies(); if ( n_copies < 1 ) return; QDomElement container = start_group(parent, repeater); QString base_id = id(repeater); QString prev_clone_id = base_id + "_0"; QDomElement og = element(container, "g"); og.setAttribute("id", prev_clone_id); for ( const auto& sib : repeater->affected() ) write_shape(og, sib, force_draw); write_repeater_vis(og, repeater, 0, n_copies); for ( int i = 1; i < n_copies; i++ ) { QString clone_id = base_id + "_" + QString::number(i);; QDomElement use = element(container, "use"); use.setAttribute("xlink:href", "#" + prev_clone_id); use.setAttribute("id", clone_id); write_repeater_vis(use, repeater, i, n_copies); transform_to_attr(use, repeater->transform.get()); prev_clone_id = clone_id; } } void write_shape(QDomElement& parent, model::ShapeElement* shape, bool force_draw) { if ( auto grp = qobject_cast(shape) ) { write_group_shape(parent, grp); } else if ( auto stroke = qobject_cast(shape) ) { if ( stroke->visible.get() ) write_stroke(stroke, parent); } else if ( auto fill = qobject_cast(shape) ) { if ( fill->visible.get() ) write_fill(fill, parent); } else if ( auto img = qobject_cast(shape) ) { write_image(img, parent); } else if ( auto layer = qobject_cast(shape) ) { write_precomp_layer(layer, parent); } else if ( auto repeater = qobject_cast(shape) ) { write_repeater(repeater, parent, force_draw); } else if ( force_draw ) { write_shape_shape(parent, shape, {}); write_visibility_attributes(parent, shape); set_attribute(parent, "id", id(shape)); } } QDomElement write_bezier(QDomElement& parent, model::ShapeElement* shape, const Style::Map& style) { QDomElement path = element(parent, "path"); write_style(path, style); QString d; QString nodetypes; std::tie(d, nodetypes) = path_data(shape->shapes(shape->time())); set_attribute(path, "d", d); set_attribute(path, "sodipodi:nodetypes", nodetypes); if ( animated ) { std::vector props; for ( auto prop : shape->properties() ) { if ( prop->traits().flags & model::PropertyTraits::Animated ) props.push_back(static_cast(prop)); } model::JoinAnimatables j(std::move(props), model::JoinAnimatables::NoValues); if ( j.animated() ) { AnimationData data(this, {"d"}, j.keyframes().size(), time_stretch, time_start); for ( const auto& kf : j ) data.add_keyframe(time_to_global(kf.time), {path_data(shape->shapes(kf.time)).first}, kf.transition()); data.add_dom(path); } } return path; } /** * \brief Creates a element for recurse_parents * \param parent DOM element to add the into * \param ancestor Ancestor layer (to create the for) * \param descendant Descendant layer */ QDomElement start_layer_recurse_parents(const QDomElement& parent, model::Layer* ancestor, model::Layer* descendant) { QDomElement g = element(parent, "g"); g.setAttribute("id", id(descendant) + "_" + id(ancestor)); g.setAttribute("inkscape:label", QObject::tr("%1 (%2)").arg(descendant->object_name()).arg(ancestor->object_name())); g.setAttribute("inkscape:groupmode", "layer"); transform_to_attr(g, ancestor->transform.get()); return g; } /** * \brief Creates nested elements for each layer parent (using the parent property) * \param parent DOM element to add the elements into * \param ancestor Ancestor layer (searched recursively for parents) * \param descendant Descendant layer */ QDomElement recurse_parents(const QDomElement& parent, model::Layer* ancestor, model::Layer* descendant) { if ( !ancestor->parent.get() ) return start_layer_recurse_parents(parent, ancestor, descendant); return start_layer_recurse_parents(recurse_parents(parent, ancestor->parent.get(), descendant), ancestor, descendant); } void write_group_shape(QDomElement& parent, model::Group* group) { QDomElement g; bool has_mask = false; if ( auto layer = group->cast() ) { if ( !layer->render.get() ) return; if ( layer->parent.get() ) { QDomElement parent_g = recurse_parents(parent, layer->parent.get(), layer); g = start_layer(parent_g, group); } else { g = start_layer(parent, group); } if ( layer->mask->has_mask() ) { has_mask = true; QDomElement clip = element(defs, "mask"); QString mask_id = "clip_" + id(layer); clip.setAttribute("id", mask_id); clip.setAttribute("mask-type", "alpha"); if ( layer->shapes.size() > 1 ) write_shape(clip, layer->shapes[0], false); g.setAttribute("mask", "url(#" + mask_id + ")"); } if ( animated && layer->visible.get() ) { auto* lay_range = layer->animation.get(); auto* doc_range = layer->owner_composition()->animation.get(); bool has_start = lay_range->first_frame.get() > doc_range->first_frame.get(); bool has_end = lay_range->last_frame.get() < doc_range->last_frame.get(); if ( has_start || has_end ) { QDomElement animation = element(g, "animate"); animation.setAttribute("begin", clock(ip)); animation.setAttribute("dur", clock(op-ip)); animation.setAttribute("calcMode", "discrete"); animation.setAttribute("attributeName", "display"); animation.setAttribute("repeatCount", "indefinite"); QString times; QString vals; times += "0;"; if ( has_start ) { vals += "none;inline;"; times += unlerp_time(lay_range->first_frame.get()) + ";"; } else { vals += "inline;"; } if ( has_end ) { vals += "none;"; times += unlerp_time(lay_range->last_frame.get()) + ";"; } animation.setAttribute("values", vals); animation.setAttribute("keyTimes", times); } } } else { g = start_group(parent, group); } transform_to_attr(g, group->transform.get(), group->auto_orient.get()); write_property(g, &group->opacity, "opacity"); write_visibility_attributes(g, group); write_shapes(g, group->shapes, has_mask); } template QDomElement transform_property( QDomElement& e, const char* name, PropT* prop, const Callback& callback, const QString& path = {}, bool auto_orient = false ) { model::JoinAnimatables j({prop}, model::JoinAnimatables::NoValues); auto parent = e.parentNode(); QDomElement g = dom.createElement("g"); parent.insertBefore(g, e); parent.removeChild(e); g.appendChild(e); if ( j.animated() ) { AnimationData data(this, {"transform"}, j.keyframes().size(), time_stretch, time_start); if ( !path.isEmpty() ) { for ( const auto& kf : j ) data.add_keyframe(time_to_global(kf.time), {""}, kf.transition()); data.add_dom(g, "animateMotion", "", path, auto_orient); } else { for ( const auto& kf : j ) data.add_keyframe(time_to_global(kf.time), {callback(prop->get_at(kf.time))}, kf.transition()); data.add_dom(g, "animateTransform", name); } } g.setAttribute("transform", QString("%1(%2)").arg(name).arg(callback(prop->get()))); return g; } void transform_to_attr(QDomElement& parent, model::Transform* transf, bool auto_orient = false) { if ( animated && (transf->position.animated() || transf->scale.animated() || transf->rotation.animated() || transf->anchor_point.animated()) ) { QDomElement subject = parent; subject = transform_property(subject, "translate", &transf->anchor_point, [](const QPointF& val){ return QString("%1 %2").arg(-val.x()).arg(-val.y()); }); subject = transform_property(subject, "scale", &transf->scale, [](const QVector2D& val){ return QString("%1 %2").arg(val.x()).arg(val.y()); }); subject = transform_property(subject, "rotate", &transf->rotation, [](qreal val){ return QString::number(val); }); math::bezier::MultiBezier mb; mb.beziers().push_back(transf->position.bezier()); subject = transform_property(subject, "translate", &transf->position, [](const QPointF& val){ return QString("%1 %2").arg(val.x()).arg(val.y()); }, path_data(mb).first, auto_orient); } else { auto matr = transf->transform_matrix(transf->time()); parent.setAttribute("transform", QString("matrix(%1, %2, %3, %4, %5, %6)") .arg(matr.m11()) .arg(matr.m12()) .arg(matr.m21()) .arg(matr.m22()) .arg(matr.m31()) .arg(matr.m32()) ); } } void write_style(QDomElement& element, const Style::Map& s) { QString st; for ( auto it : s ) { st.append(it.first); st.append(':'); st.append(it.second); st.append(';'); } element.setAttribute("style", st); } QDomElement start_group(QDomElement& parent, model::DocumentNode* node) { QDomElement g = element(parent, "g"); g.setAttribute("id", id(node)); g.setAttribute("inkscape:label", node->object_name()); return g; } QDomElement start_layer(QDomElement& parent, model::DocumentNode* node) { auto g = start_group(parent, node); g.setAttribute("inkscape:groupmode", "layer"); return g; } QString id(model::DocumentNode* node) { return node->type_name() + "_" + node->uuid.get().toString(QUuid::Id128); } /// Avoid locale nonsense by defining these functions (on ASCII chars) manually static constexpr bool valid_id_start(char c) noexcept { return ( c >= 'a' && c <= 'z') || ( c >= 'A' && c <= 'Z') || c == '_'; } static constexpr bool valid_id(char c) noexcept { return valid_id_start(c) || ( c >= '0' && c <= '9') || c == '-'; } void write_named_color(QDomElement& parent, model::NamedColor* color) { auto gradient = element(parent, "linearGradient"); gradient.setAttribute("osb:paint", "solid"); QString id = pretty_id(color->name.get(), color); non_uuid_ids_map[color] = id; gradient.setAttribute("id", id); auto stop = element(gradient, "stop"); stop.setAttribute("offset", "0"); write_property(stop, &color->color, "stop-color"); } QString pretty_id(const QString& s, model::DocumentNode* node) { if ( s.isEmpty() ) return id(node); QByteArray str = s.toLatin1(); QString id_attempt; if ( !valid_id_start(str[0]) ) id_attempt.push_back('_'); for ( char c : str ) { if ( c == ' ' ) id_attempt.push_back('_'); else if ( valid_id(c) ) id_attempt.push_back(c); } if ( id_attempt.isEmpty() ) return id(node); QString id_final = id_attempt; int i = 1; while ( non_uuid_ids.count(id_final) ) id_final = id_attempt + QString::number(i++); return id_final; } template std::enable_if_t && !std::is_same_v> set_attribute(QDomElement& e, const QString& name, T val) { // not using e.setAttribute overloads to bypass locale settings e.setAttribute(name, QString::number(val)); } void set_attribute(QDomElement& e, const QString& name, bool val) { e.setAttribute(name, val ? "true" : "false"); } void set_attribute(QDomElement& e, const QString& name, const char* val) { e.setAttribute(name, val); } void set_attribute(QDomElement& e, const QString& name, const QString& val) { e.setAttribute(name, val); } void write_gradient_colors(QDomElement& parent, model::GradientColors* gradient) { auto e = element(parent, "linearGradient"); QString id = pretty_id(gradient->name.get(), gradient); non_uuid_ids_map[gradient] = id; e.setAttribute("id", id); if ( animated && gradient->colors.keyframe_count() > 1 ) { int n_stops = std::numeric_limits::max(); for ( const auto& kf : gradient->colors ) if ( kf.get().size() < n_stops ) n_stops = kf.get().size(); auto stops = gradient->colors.get(); for ( int i = 0; i < n_stops; i++ ) { AnimationData data(this, {"offset", "stop-color"}, gradient->colors.keyframe_count(), time_stretch, time_start); for ( const auto& kf : gradient->colors ) { auto stop = kf.get()[i]; data.add_keyframe( time_to_global(kf.time()), {QString::number(stop.first), stop.second.name()}, kf.transition() ); } auto s = element(e, "stop"); s.setAttribute("stop-opacity", "1"); set_attribute(s, "offset", stops[i].first); s.setAttribute("stop-color", stops[i].second.name()); data.add_dom(s); } } else { for ( const auto& stop : gradient->colors.get() ) { auto s = element(e, "stop"); s.setAttribute("stop-opacity", "1"); set_attribute(s, "offset", stop.first); s.setAttribute("stop-color", stop.second.name()); } } } void write_gradient(QDomElement& parent, model::Gradient* gradient) { QDomElement e; if ( gradient->type.get() == model::Gradient::Radial || gradient->type.get() == model::Gradient::Conical ) { e = element(parent, "radialGradient"); write_properties(e, {&gradient->start_point}, {"cx", "cy"}, &Private::callback_point); write_properties(e, {&gradient->highlight}, {"fx", "fy"}, &Private::callback_point); write_properties(e, {&gradient->start_point, &gradient->end_point}, {"r"}, [](const std::vector& values) -> std::vector { return { QString::number( math::length(values[1].toPointF() - values[0].toPointF()) )}; }); } else { e = element(parent, "linearGradient"); write_properties(e, {&gradient->start_point}, {"x1", "y1"}, &Private::callback_point); write_properties(e, {&gradient->end_point}, {"x2", "y2"}, &Private::callback_point); } QString id = pretty_id(gradient->name.get(), gradient); non_uuid_ids_map[gradient] = id; e.setAttribute("id", id); e.setAttribute("gradientUnits", "userSpaceOnUse"); auto it = non_uuid_ids_map.find(gradient->colors.get()); if ( it != non_uuid_ids_map.end() ) e.setAttribute("xlink:href", "#" + it->second); } QString clock(model::FrameTime time) { return QString::number(time / fps, 'f'); } std::vector timing; QDomDocument dom; qreal fps = 60; qreal ip = 0; qreal op = 60; bool at_start = true; std::set non_uuid_ids; std::map non_uuid_ids_map; AnimationType animated; QDomElement svg; QDomElement defs; CssFontType font_type; qreal time_stretch = 1; model::FrameTime time_start = 0; }; io::svg::SvgRenderer::SvgRenderer(AnimationType animated, CssFontType font_type) : d(std::make_unique()) { d->animated = animated; d->font_type = font_type; d->svg = d->dom.createElement("svg"); d->dom.appendChild(d->svg); d->svg.setAttribute("xmlns", detail::xmlns.at("svg")); for ( const auto& p : detail::xmlns ) { if ( !p.second.contains("android") ) d->svg.setAttribute("xmlns:" + p.first, p.second); } d->write_style(d->svg, { {"fill", "none"}, {"stroke", "none"} }); d->svg.setAttribute("inkscape:export-xdpi", "96"); d->svg.setAttribute("inkscape:export-ydpi", "96"); d->svg.setAttribute("version", "1.1"); } io::svg::SvgRenderer::~SvgRenderer() { } void io::svg::SvgRenderer::write_composition(model::Composition* comp) { d->collect_defs(comp); auto g = d->start_layer(d->svg, comp); d->write_composition(g, comp); } void io::svg::SvgRenderer::write_main(model::Composition* comp) { if ( d->at_start ) { QString w = QString::number(comp->width.get()); QString h = QString::number(comp->height.get()); d->svg.setAttribute("width", w); d->svg.setAttribute("height", h); d->svg.setAttribute("viewBox", QString("0 0 %1 %2").arg(w).arg(h)); d->svg.appendChild(d->dom.createElement("title")).appendChild(d->dom.createTextNode(comp->name.get())); write_composition(comp); } else { write_composition(comp); } } void io::svg::SvgRenderer::write_shape(model::ShapeElement* shape) { d->collect_defs(shape->owner_composition()); d->write_shape(d->svg, shape, true); } void io::svg::SvgRenderer::write_node(model::DocumentNode* node) { if ( auto co = qobject_cast(node) ) write_main(co); else if ( auto sh = qobject_cast(node) ) write_shape(sh); } QDomDocument io::svg::SvgRenderer::dom() const { return d->dom; } void io::svg::SvgRenderer::write(QIODevice* device, bool indent) { device->write(d->dom.toByteArray(indent ? 4 : -1)); } glaxnimate::io::svg::CssFontType glaxnimate::io::svg::SvgRenderer::suggested_type(model::EmbeddedFont* font) { if ( !font->css_url.get().isEmpty() ) return CssFontType::Link; if ( !font->source_url.get().isEmpty() ) return CssFontType::FontFace; if ( !font->data.get().isEmpty() ) return CssFontType::Embedded; return CssFontType::None; } static char bezier_node_type(const math::bezier::Point& p) { switch ( p.type ) { case math::bezier::PointType::Smooth: return 's'; case math::bezier::PointType::Symmetrical: return 'z'; case math::bezier::PointType::Corner: default: return 'c'; } } std::pair glaxnimate::io::svg::path_data(const math::bezier::MultiBezier& shape) { QString d; QString nodetypes; for ( const math::bezier::Bezier& b : shape.beziers() ) { if ( b.empty() ) continue; d += QString("M %1,%2 C").arg(b[0].pos.x()).arg(b[0].pos.y()); nodetypes += bezier_node_type(b[0]); for ( int i = 1; i < b.size(); i++ ) { d += QString(" %1,%2 %3,%4 %5,%6") .arg(b[i-1].tan_out.x()).arg(b[i-1].tan_out.y()) .arg(b[i].tan_in.x()).arg(b[i].tan_in.y()) .arg(b[i].pos.x()).arg(b[i].pos.y()) ; nodetypes += bezier_node_type(b[i]); } if ( b.closed() ) { d += QString(" %1,%2 %3,%4 %5,%6") .arg(b.back().tan_out.x()).arg(b.back().tan_out.y()) .arg(b[0].tan_in.x()).arg(b[0].tan_in.y()) .arg(b[0].pos.x()).arg(b[0].pos.y()) ; d += " Z"; } } return {d, nodetypes}; } src/modules/glaxnimate/glaxnimate/external/QtAppSetup/src/app/settings/keyboard_shortcuts.hpp000664 001750 001750 00000003475 14477652011 036236 0ustar00ddennedyddennedy000000 000000 mlt-7.22.0/* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include #include #include #include #include #include "app/utils/qstring_hash.hpp" #include "custom_settings_group.hpp" namespace app::settings { struct ShortcutAction { QIcon icon; QString label; QKeySequence shortcut; QKeySequence default_shortcut; bool overwritten = false; QPointer action; }; struct ShortcutGroup { QString label; std::vector actions; }; class ShortcutSettings : public QObject, public CustomSettingsGroupBase { Q_OBJECT public: QString slug() const override { return "shortcuts"; } QString label() const override { return QObject::tr("Keyboard Shortcuts"); } QIcon icon() const override { return QIcon::fromTheme("input-keyboard"); } QWidget * make_widget(QWidget * parent) override; void load(QSettings & settings) override; void save(QSettings & settings) override; ShortcutGroup* add_group(const QString& label); void add_menu(QMenu* menu, const QString& prefix = {}); ShortcutAction* action(const QString& slug); ShortcutAction* add_action(QAction* action, const QString& prefix = {}); const QList& get_groups() const; const std::unordered_map& get_actions() const; const QKeySequence& get_shortcut(const QString& action_name) const; ShortcutGroup* find_group(const QString& label); void remove_action(ShortcutAction* action); signals: void begin_actions_change(); void end_actions_change(); private: QList groups; std::unordered_map actions; }; } // namespace app::settings src/modules/glaxnimate/glaxnimate/external/QtAppSetup/src/app/scripting/python/CMakeLists.txt000664 001750 001750 00000000707 14477652011 036025 0ustar00ddennedyddennedy000000 000000 mlt-7.22.0# SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia # # SPDX-License-Identifier: GPL-3.0-or-later target_sources(${PROJECT_SLUG} PRIVATE python_engine.cpp register_machinery.cpp ) message(STATUS "Python3_INCLUDE_DIRS ${Python3_INCLUDE_DIRS}") message(STATUS "Python3_LIBRARIES ${Python3_LIBRARIES}") message(STATUS "Python3_EXECUTABLE ${Python3_EXECUTABLE}") target_link_libraries(${PROJECT_SLUG} PUBLIC ${Python3_LIBRARIES}) mlt-7.22.0/src/modules/glaxnimate/glaxnimate/external/QtAppSetup/src/app/qstring_exception.hpp000664 001750 001750 00000000773 14477652011 034302 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include namespace app { template class QStringException : public Base { protected: using Ctor = QStringException; public: QStringException(const QString& what) : Base(what.toStdString()) {} QString message() const { return QString(this->what()); } }; } // namespace app mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/shapes/zig_zag.hpp000664 001750 001750 00000001570 14477652011 031026 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include "path_modifier.hpp" namespace glaxnimate::model { class ZigZag : public StaticOverrides { GLAXNIMATE_OBJECT(ZigZag) GLAXNIMATE_ANIMATABLE(float, amplitude, 10) GLAXNIMATE_ANIMATABLE(float, frequency, 10, {}, 0) public: enum Style { Saw = 1, Wave = 2 }; Q_ENUM(Style) GLAXNIMATE_PROPERTY(Style, style, Saw, nullptr, nullptr, PropertyTraits::Visual) public: using Ctor::Ctor; static QIcon static_tree_icon(); static QString static_type_name_human(); math::bezier::MultiBezier process(FrameTime t, const math::bezier::MultiBezier& mbez) const override; protected: bool process_collected() const override; }; } // namespace glaxnimate::model mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/avd/avd_format.cpp000664 001750 001750 00000002526 14477652011 030311 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "avd_format.hpp" #include "avd_parser.hpp" #include "io/svg/parse_error.hpp" #include "avd_renderer.hpp" glaxnimate::io::Autoreg glaxnimate::io::avd::AvdFormat::autoreg; bool glaxnimate::io::avd::AvdFormat::on_open(QIODevice& file, const QString& filename, model::Document* document, const QVariantMap& options) { auto on_error = [this](const QString& s){warning(s);}; try { QSize forced_size = options["forced_size"].toSize(); model::FrameTime default_time = options["default_time"].toFloat(); auto resource_path = QFileInfo(filename).dir(); AvdParser(&file, resource_path, document, on_error, this, forced_size, default_time).parse_to_document(); return true; } catch ( const svg::SvgParseError& err ) { error(err.formatted(QFileInfo(filename).baseName())); return false; } } bool glaxnimate::io::avd::AvdFormat::on_save(QIODevice& file, const QString&, model::Composition* comp, const QVariantMap&) { auto on_error = [this](const QString& s){warning(s);}; AvdRenderer rend(on_error); rend.render(comp); auto dom = rend.single_file(); file.write(dom.toByteArray(4)); return true; } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/object.hpp000664 001750 001750 00000010724 14477652011 027360 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include #include #include #include "model/animation/frame_time.hpp" #include "model/factory.hpp" class QUndoCommand; /** * \brief Sets up declarations of concrete Object sub-classes * \note Call GLAXNIMATE_OBJECT_IMPL for every class declared with GLAXNIMATE_OBJECT */ #define GLAXNIMATE_OBJECT(cls) \ private: \ Q_OBJECT \ static bool _reg; \ std::unique_ptr clone_impl() const override; \ public: \ std::unique_ptr clone_covariant() const; \ // macro end /** * \brief Registers a class declared with GLAXNIMATE_OBJECT to be constructed * with model::Factory */ #define GLAXNIMATE_OBJECT_IMPL(cls) \ bool cls::_reg{glaxnimate::model::Factory::instance().register_type()}; \ std::unique_ptr cls::clone_covariant() const \ { \ auto object = std::make_unique(this->document()); \ this->clone_into(object.get()); \ return object; \ } \ std::unique_ptr cls::clone_impl() const \ { \ return clone_covariant(); \ } \ // macro end namespace glaxnimate::model { class ObjectListPropertyBase; class BaseProperty; class Document; class Object : public QObject { Q_OBJECT public: explicit Object(Document* document); ~Object(); std::unique_ptr clone() const { return clone_impl(); } std::unique_ptr clone_covariant() const { auto object = std::make_unique(document()); clone_into(object.get()); return object; } virtual void assign_from(const Object* other); QVariant get(const QString& property) const; bool set(const QString& property, const QVariant& value); bool set_undoable(const QString& property, const QVariant& value); bool has(const QString& property) const; const std::vector& properties() const; BaseProperty* get_property(const QString& property); virtual QString object_name() const { return type_name_human(); } virtual QString type_name_human() const { return tr("Unknown Object"); } virtual void set_time(FrameTime t); FrameTime time() const; QString type_name() const; Document* document() const; void transfer(Document* document); void push_command(QUndoCommand* cmd); template T* cast() { return qobject_cast(this); } template const T* cast() const { return qobject_cast(this); } template bool is_instance() const { return metaObject()->inherits(&T::staticMetaObject); } virtual void stretch_time(qreal multiplier); signals: void property_changed(const model::BaseProperty* prop, const QVariant& value); void visual_property_changed(const model::BaseProperty* prop, const QVariant& value); void removed(); protected: virtual void on_property_changed(const BaseProperty* prop, const QVariant& value) { Q_UNUSED(prop); Q_UNUSED(value); } void clone_into(Object* dest) const; virtual void on_transfer(model::Document* doc) {Q_UNUSED(doc)}; class Autoreg { public: Autoreg(const QMetaObject&); Autoreg(const Autoreg&) = delete; Autoreg& operator=(const Autoreg&) = delete; }; private: virtual std::unique_ptr clone_impl() const { return clone_covariant(); } void add_property(BaseProperty* prop); void property_value_changed(const BaseProperty* prop, const QVariant& value); friend BaseProperty; class Private; std::unique_ptr d; }; } // namespace glaxnimate::model mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/aep/string_decoder.cpp000664 001750 001750 00000002346 14477652011 031155 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include #include "io/aep/string_decoder.hpp" #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) #include QString glaxnimate::io::aep::decode_string(const QByteArray& data) { auto encoding = QStringConverter::encodingForData(data); if ( encoding ) return QStringDecoder(*encoding).decode(data); return QStringDecoder(QStringConverter::Utf8).decode(data); } QString glaxnimate::io::aep::decode_utf16(const QByteArray& data, bool big_endian) { auto encoding = big_endian ? QStringConverter::Utf16BE : QStringConverter::Utf16LE; return QStringDecoder(encoding).decode(data); } #else #include QString glaxnimate::io::aep::decode_string(const QByteArray& data) { auto utf8 = QTextCodec::codecForName("UTF-8"); auto encoding = QTextCodec::codecForUtfText(data, utf8); return encoding->toUnicode(data); } QString glaxnimate::io::aep::decode_utf16(const QByteArray& data, bool big_endian) { const char* name = big_endian ? "UFT-16BE" : "UFT-16BE"; auto encoding = QTextCodec::codecForName(name); return encoding->toUnicode(data); } #endif mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/lottie/lottie_html_format.cpp000664 001750 001750 00000004473 14477652011 032614 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "lottie_html_format.hpp" #include "lottie_exporter.hpp" #include "cbor_write_json.hpp" QByteArray glaxnimate::io::lottie::LottieHtmlFormat::html_head(ImportExport* ie, model::Composition* comp, const QString& extra) { return QString( R"( %4: %5 %3 )") .arg(comp->width.get()) .arg(comp->height.get()) .arg(extra) .arg(comp->object_name()) .arg(ie->name()) .toUtf8() ; } bool glaxnimate::io::lottie::LottieHtmlFormat::on_save(QIODevice& file, const QString&, model::Composition* comp, const QVariantMap& settings) { file.write(html_head(this, comp, "")); file.write(R"(
)") .arg(settings["renderer"].toString()) .toUtf8() ); return true; } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/svg/000775 001750 001750 00000000000 14477652011 025503 5ustar00ddennedyddennedy000000 000000 mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/aep/aepx.hpp000664 001750 001750 00000005772 14477652011 027132 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include "aep_riff.hpp" #include "io/svg/detail.hpp" namespace glaxnimate::io::aep { class AepxConverter { public: RiffChunk aepx_to_chunk(const QDomElement& element) { QString header = element.tagName(); if ( header == "ProjectXMPMetadata" ) { return chunk("XMPM", text(element.text())); } else if ( header == "string" ) { return chunk("Utf8", text(element.text())); } else if ( header == "numS" ) { std::uint32_t val = element.firstChildElement().text().toUInt(); auto data = buffer(Endianness::Big().write_uint(val)); return chunk(header, data); } else if ( header == "ppSn" ) { std::uint32_t val = element.firstChildElement().text().toDouble(); auto data = buffer(Endianness::Big().write_float64(val)); return chunk(header, data); } else if ( element.hasAttribute("bdata") ) { return chunk(header, hex(element.attribute("bdata"))); } ChunkId riff_header = header.toLatin1(); ChunkId subheader = {""}; if ( header == "AfterEffectsProject" ) { riff_header = {"RIFX"}; } else if ( !AepRiff::is_fake_list(riff_header) ) { subheader = riff_header; riff_header = {"LIST"}; } return {riff_header, 0, subheader, {}, read_chunk_list(svg::detail::ElementRange(element))}; } std::vector> read_chunk_list(const svg::detail::ElementRange& range) { std::vector> out; out.reserve(range.size()); for ( const auto& el : range ) out.push_back(std::make_unique(aepx_to_chunk(el))); return out; } private: struct BinaryData { QByteArray data; QBuffer file; std::uint32_t length; }; RiffChunk chunk(const QString& header, BinaryData* data, const QString& subheader = {}) { return { header.toLatin1(), data->length, subheader.toLatin1(), {Endianness::Big(), &data->file, data->length, 0} }; } BinaryData* buffer(QByteArray&& content) { data.push_back(std::make_unique()); data.back()->length = content.size(); data.back()->data = std::move(content); data.back()->file.setBuffer(&data.back()->data); data.back()->file.open(QIODevice::ReadOnly); return data.back().get(); } BinaryData* hex(const QString& hex) { return buffer(QByteArray::fromHex(hex.toLatin1())); } BinaryData* text(const QString& string) { return buffer(string.toUtf8()); } std::vector> data; }; } // namespace glaxnimate::io::aep mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/glaxnimate/glaxnimate_format.cpp000664 001750 001750 00000017252 14477652011 033251 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "glaxnimate_format.hpp" #include #include #include "app_info.hpp" #include "math/bezier/bezier.hpp" #include "model/assets/assets.hpp" #include "app/utils/string_view.hpp" using namespace glaxnimate; io::Autoreg io::glaxnimate::GlaxnimateFormat::autoreg; const int glaxnimate::io::glaxnimate::GlaxnimateFormat::format_version = 8; bool io::glaxnimate::GlaxnimateFormat::on_save(QIODevice& file, const QString&, model::Composition* comp, const QVariantMap&) { return file.write(to_json(comp->document()).toJson(QJsonDocument::Indented)); } QJsonObject io::glaxnimate::GlaxnimateFormat::format_metadata() { QJsonObject object; object["generator"] = AppInfo::instance().name(); object["generator_version"] = AppInfo::instance().version(); object["format_version"] = format_version; return object; } QJsonDocument io::glaxnimate::GlaxnimateFormat::to_json ( model::Document* document ) { QJsonObject doc_obj; doc_obj["format"] = format_metadata(); doc_obj["metadata"] = QJsonObject::fromVariantMap(document->metadata()); QJsonObject info; info["author"] = document->info().author; info["description"] = document->info().description; QJsonArray keywords; for ( const auto& kw: document->info().keywords ) keywords.push_back(kw); info["keywords"] = keywords; doc_obj["info"] = info; doc_obj["assets"] = to_json(document->assets()); return QJsonDocument(doc_obj); } QJsonObject io::glaxnimate::GlaxnimateFormat::to_json ( model::Object* object ) { QJsonObject obj; obj["__type__"] = object->type_name(); for ( model::BaseProperty* prop : object->properties() ) obj[prop->name()] = to_json(prop); return obj; } namespace { QJsonValue point_to_json(const QPointF& v) { QJsonObject o; o["x"] = v.x(); o["y"] = v.y(); return o; } } // namespace QJsonValue io::glaxnimate::GlaxnimateFormat::to_json ( model::BaseProperty* property ) { if ( property->traits().flags & model::PropertyTraits::List ) { QJsonArray arr; for ( const QVariant& val : property->value().toList() ) { arr.push_back(to_json(val, property->traits())); } return arr; } else if ( property->traits().flags & model::PropertyTraits::Animated ) { model::AnimatableBase* anim = static_cast(property); bool position = anim->traits().type == model::PropertyTraits::Point; QJsonObject jso; if ( !anim->animated() ) { jso["value"] = to_json(anim->value(), property->traits()); } else { QJsonArray keyframes; for ( int i = 0, e = anim->keyframe_count(); i < e; i++ ) { auto kf = anim->keyframe(i); QJsonObject jkf; jkf["time"] = kf->time(); jkf["value"] = to_json(kf->value(), property->traits()); if ( !kf->transition().hold() ) { jkf["before"] = to_json(kf->transition().before()); jkf["after"] = to_json(kf->transition().after()); } if ( position ) { auto pkf = static_cast*>(kf); jkf["tan_in"] = point_to_json(pkf->point().tan_in); jkf["tan_out"] = point_to_json(pkf->point().tan_out); jkf["point_type"] = pkf->point().type; } keyframes.push_back(jkf); } jso["keyframes"] = keyframes; } return jso; } return to_json(property->value(), property->traits()); } QJsonValue io::glaxnimate::GlaxnimateFormat::to_json ( const QVariant& value, model::PropertyTraits traits ) { switch ( traits.type ) { case model::PropertyTraits::Object: if ( auto obj = value.value() ) return to_json(obj); return {}; case model::PropertyTraits::ObjectReference: if ( auto dn = value.value() ) return QJsonValue::fromVariant(dn->uuid.get()); return {}; case model::PropertyTraits::Enum: return value.toString(); case model::PropertyTraits::Bezier: { math::bezier::Bezier bezier = value.value(); QJsonObject jsbez; jsbez["closed"] = bezier.closed(); QJsonArray points; for ( const auto& p : bezier ) { QJsonObject jsp; jsp["pos"] = point_to_json(p.pos); jsp["tan_in"] = point_to_json(p.tan_in); jsp["tan_out"] = point_to_json(p.tan_out); jsp["type"] = p.type; points.push_back(jsp); } jsbez["points"] = points; return jsbez; } case model::PropertyTraits::Gradient: { QJsonArray stops; for ( const auto& stop : value.value() ) { QJsonObject jstop; jstop["offset"] = stop.first; jstop["color"] = to_json(stop.second); stops.push_back(jstop); } return stops; } default: return to_json(value); } } QJsonValue io::glaxnimate::GlaxnimateFormat::to_json ( const QVariant& value ) { if ( !value.isValid() ) return {}; switch ( value.userType() ) { case QMetaType::Bool: case QMetaType::Int: case QMetaType::UInt: case QMetaType::Short: case QMetaType::UShort: case QMetaType::Long: case QMetaType::ULong: case QMetaType::LongLong: case QMetaType::ULongLong: case QMetaType::Double: case QMetaType::Float: case QMetaType::QChar: case QMetaType::QString: case QMetaType::QJsonArray: case QMetaType::QJsonObject: case QMetaType::QJsonValue: case QMetaType::QUuid: return QJsonValue::fromVariant(value); case QMetaType::QByteArray: return QString(value.toByteArray().toBase64()); case QVariant::Invalid: return {}; case QMetaType::QSize: { auto v = value.toSize(); QJsonObject o; o["width"] = v.width(); o["height"] = v.height(); return o; } case QMetaType::QSizeF: { auto v = value.toSizeF(); QJsonObject o; o["width"] = v.width(); o["height"] = v.height(); return o; } case QMetaType::QPoint: { auto v = value.toPoint(); QJsonObject o; o["x"] = v.x(); o["y"] = v.y(); return o; } case QMetaType::QVector2D: { auto v = value.value(); QJsonObject o; o["x"] = v.x(); o["y"] = v.y(); return o; } case QVariant::PointF: return point_to_json(value.toPointF()); case QVariant::Color: { auto v = value.value(); QString col = v.name(); if ( v.alpha() != 255 ) col += ::utils::right_ref(QString::number(v.alpha()|0x100, 16), 2); return col; } } if ( value.canConvert() ) return point_to_json(value.toPointF()); return {}; } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/shapes/repeater.hpp000664 001750 001750 00000002161 14477652011 031200 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include "shape.hpp" #include "model/transform.hpp" #include "model/property/sub_object_property.hpp" namespace glaxnimate::model { class Repeater : public StaticOverrides { GLAXNIMATE_OBJECT(Repeater) GLAXNIMATE_SUBOBJECT(Transform, transform) GLAXNIMATE_ANIMATABLE(int, copies, 1) GLAXNIMATE_ANIMATABLE(float, start_opacity, 1, {}, 0, 1, false, PropertyTraits::Percent) GLAXNIMATE_ANIMATABLE(float, end_opacity, 1, {}, 0, 1, false, PropertyTraits::Percent) public: using Ctor::Ctor; static QIcon static_tree_icon(); static QString static_type_name_human(); std::unique_ptr to_path() const override; int max_copies() const; protected: math::bezier::MultiBezier process(FrameTime t, const math::bezier::MultiBezier& mbez) const override; void on_paint(QPainter* p, FrameTime t, PaintMode, model::Modifier*) const override; bool process_collected() const override; }; } // namespace glaxnimate::model mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/app_info.cpp000664 001750 001750 00000002077 14477652011 026602 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "app_info.hpp" #include #include "application_info_generated.hpp" QString glaxnimate::AppInfo::name() const { return QObject::tr("Glaxnimate"); } QString glaxnimate::AppInfo::slug() const { return PROJECT_SLUG; } QString glaxnimate::AppInfo::version() const { return PROJECT_VERSION; } QString glaxnimate::AppInfo::organization() const { return PROJECT_SLUG; } QUrl glaxnimate::AppInfo::url_docs() const { return QUrl(URL_DOCS); } QUrl glaxnimate::AppInfo::url_issues() const { return QUrl(URL_ISSUES); } QString glaxnimate::AppInfo::description() const { return PROJECT_DESCRIPTION; } QUrl glaxnimate::AppInfo::url_donate() const { return QUrl(URL_DONATE); } void glaxnimate::AppInfo::init_qapplication() const { qApp->setApplicationName(slug()); qApp->setApplicationVersion(version()); qApp->setOrganizationName(organization()); qApp->setApplicationDisplayName(name()); } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/aep/cos.hpp000664 001750 001750 00000031656 14477652011 026761 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include #include #include #include #include #include "string_decoder.hpp" namespace glaxnimate::io::aep { enum class CosTokenType { // /foo Identifier, // 123 Number, // (foo) String, // HexString, // true Boolean, // << ObjectStart, // >> ObjectEnd, // [ ArrayStart, // ] ArrayEnd, // null Null, // end of file Eof, }; class CosError : public std::runtime_error { public: CosError(QString message) : runtime_error(message.toStdString()), message(std::move(message)) {} QString message; }; struct CosValue { enum class Index { Null, Number, String, Boolean, Bytes, Object, Array }; using Object = std::unique_ptr>; using Array = std::unique_ptr>; template CosValue(T&& v) : value(std::forward(v)) {} CosValue() = default; CosValue(CosValue& v) = delete; CosValue(const CosValue& v) = delete; CosValue(CosValue&& v) = default; CosValue& operator=(CosValue&& v) = default; template const auto& get() const { if ( Ind != type() ) throw CosError("Invalid COS value type"); return std::get(value); } Index type() const { return Index(value.index()); } std::variant< std::nullptr_t, double, QString, bool, QByteArray, Object, Array > value = nullptr; }; using CosObject = CosValue::Object; using CosArray = CosValue::Array; struct CosToken { CosTokenType type = CosTokenType::Eof; CosValue value = {}; CosToken() = default; CosToken(CosToken&&) = default; CosToken& operator=(CosToken&&) = default; }; class CosLexer { public: CosLexer(QByteArray data) : data(std::move(data)) {} CosToken next_token() { int ch; while ( true ) { ch = get_char(); if ( ch == -1 ) return CosToken(); else if ( ch == '%' ) lex_comment(); else if ( !std::isspace(ch) ) break; } // << if ( ch == '<' ) { ch = get_char(); if ( ch == '<' ) return {CosTokenType::ObjectStart}; else if ( ch == -1 ) throw_lex("<"); else if ( std::isxdigit(ch) ) return lex_hex_string(ch); else throw_lex(QString("<") + QChar(ch)); } // >> if ( ch == '>' ) { auto d = get_char(); if ( d != '>' ) { QString tok{QChar(ch)}; if ( d != -1 ) tok += QChar(d); throw_lex(tok, ">>"); } return {CosTokenType::ObjectEnd}; } // [ if ( ch == '[' ) return {CosTokenType::ArrayStart}; // ] if ( ch == ']' ) return {CosTokenType::ArrayEnd}; // /foo if ( ch == '/' ) { return lex_identifier(); } // (foo) if ( ch == '(' ) { return lex_string(); } // Keyword if ( std::isalpha(ch) ) return lex_keyword(ch); // Number if ( std::isdigit(ch) || ch == '-' || ch == '+' || ch == '.' ) return lex_number(ch); throw_lex(QString() + QChar(ch)); } [[noreturn]] void throw_lex(const QString& token, const QString& exp = {}) { QString msg = "Unknown COS token %1"; msg = msg.arg(token); if ( !exp.isEmpty() ) { msg += ", expected "; msg += exp; } throw CosError(msg); } int get_char() { if ( offset >= data.size() ) return -1; int ch = std::uint8_t(data[offset]); offset += 1; return ch; } void unget() { offset -= 1; if ( offset < 0 ) throw CosError("Buffer underflow"); } void lex_comment() { while ( true ) { auto ch = get_char(); if ( ch == -1 || ch == '\n' ) break; } } CosToken lex_number(int ch) { if ( ch == '.' ) return lex_number_fract(QString(QChar(ch))); else if ( ch == '+' || ch == '-' ) return lex_number_int(get_char(), QChar(ch)); else return lex_number_int(ch, '+'); } CosToken lex_number_int(int ch, QChar sign) { QString head; head += sign; while ( true ) { if ( ch == '.' ) { return lex_number_fract(head + QChar(ch)); } else if ( ch == -1 ) { break; } else if ( std::isdigit(ch) ) { head += QChar(ch); ch = get_char(); } else { unget(); break; } } return {CosTokenType::Number, head.toDouble()}; } CosToken lex_number_fract(QString num) { while ( true ) { int ch = get_char(); if ( ch == -1 ) { break; } else if ( std::isdigit(ch) ) { num += QChar(ch); } else { unget(); break; } } return {CosTokenType::Number, num.toDouble()}; } CosToken lex_keyword(char start) { QString kw(start); while ( true ) { auto ch = get_char(); if ( ch == -1 ) { break; } else if ( std::isalpha(ch) ) { kw += QChar(ch); } else { unget(); break; } } if ( kw == "true" ) return {CosTokenType::Boolean, true}; if ( kw == "false" ) return {CosTokenType::Boolean, false}; if ( kw == "null") return {CosTokenType::Null}; throw CosError("Unknown keyword " + kw); } CosToken lex_string() { QByteArray string; while ( true ) { auto ch = lex_string_char(); if ( ch == -1 ) break; string.push_back(ch); } return {CosTokenType::String, decode_string(string)}; } int lex_string_char() { auto ch = get_char(); if ( ch == -1 ) throw CosError("Unterminated String"); if ( ch == ')' ) return -1; if ( ch == '\\' ) return lex_string_escape(); if ( ch == '\r' ) { if ( get_char() != '\n' ) unget(); return '\n'; } else if ( ch == '\n' ) { if ( get_char() != '\r' ) unget(); return '\n'; } return ch; } bool is_octal(char ch) { return '0' <= ch && ch <= '7'; } char lex_string_escape() { auto ch = get_char(); if ( ch == -1 ) throw CosError("Unterminated string"); switch ( ch ) { case 'b': return '\b'; case 'n': return '\n'; case 'f': return '\f'; case 'r': return '\r'; case '(': case ')': case '\\': return ch; } if ( is_octal(ch) ) { QString octal{QChar(ch)}; for ( auto i = 0; i < 2; i++ ) { ch = get_char(); if ( ch == -1 ) break; if ( !is_octal(ch) ) { unget(); break; } octal += QChar(ch); } return octal.toInt(nullptr, 8); } throw CosError("Invalid escape sequence"); } CosToken lex_hex_string(char head) { QByteArray data; data.push_back(head); while ( true ) { auto ch = get_char(); if ( ch == -1 ) { throw CosError("Unterminated hex string"); } else if ( std::isxdigit(ch) ) { data.push_back(ch); } else if ( ch == '>' ) { if ( data.size() % 2 != 0 ) data.push_back('0'); break; } else if ( !std::isspace(ch) ) { throw CosError(QString("Invalid character in hex string: ") + QChar(ch)); } } return {CosTokenType::HexString, QByteArray::fromHex(data)}; } CosToken lex_identifier() { QString ident = ""; const QString special = "()[]<>/%"; while ( true ) { auto ch = get_char(); if ( ch == -1 ) break; if ( ch < 0x21 || ch > 0x7e ) { unget(); break; } if ( ch == '#' ) { QByteArray hexstr; for ( auto i = 0; i < 2; i++ ) { ch = get_char(); if ( ch == -1 || !std::isxdigit(ch) ) throw CosError("Invalid Identifier"); hexstr += std::uint8_t(ch); } ident += QChar(hexstr.toInt(nullptr, 16)); } else if ( special.indexOf(QChar(ch)) != -1 ) { unget(); break; } else { ident += QChar(ch); } } return {CosTokenType::Identifier, ident}; } private: QByteArray data; int offset = 0; }; class CosParser { public: CosParser(QByteArray data) : lexer(std::move(data)) {} CosValue parse() { lex(); if ( lookahead.type == CosTokenType::Identifier ) return parse_object_content(); auto val = parse_value(); if ( lookahead.type == CosTokenType::Eof ) return val; CosArray arr = parse_array_content(); arr->emplace(arr->begin(), std::move(val)); return arr; } private: CosToken lookahead; CosLexer lexer; void lex() { lookahead = lexer.next_token(); } CosObject parse_object_content() { CosObject value = std::make_unique(); while ( true ) { if ( lookahead.type == CosTokenType::Eof || lookahead.type == CosTokenType::ObjectEnd ) break; expect(CosTokenType::Identifier); auto key = lookahead.value.get(); lex(); auto val = parse_value(); value->emplace(key, std::move(val)); } return value; } void expect(CosTokenType token_type) { if ( lookahead.type != token_type ) throw CosError(QString("Expected token %1, got %2").arg(int(token_type)).arg(int(lookahead.type))); } CosArray parse_array_content() { CosArray value = std::make_unique(); while ( true ) { if ( lookahead.type == CosTokenType::Eof || lookahead.type == CosTokenType::ArrayEnd ) break; value->push_back(parse_value()); } return value; } CosValue parse_value() { CosValue val; switch ( lookahead.type ) { case CosTokenType::String: case CosTokenType::HexString: case CosTokenType::Null: case CosTokenType::Boolean: case CosTokenType::Identifier: case CosTokenType::Number: val = std::move(lookahead.value); lex(); return val; case CosTokenType::ObjectStart: lex(); val = parse_object_content(); expect(CosTokenType::ObjectEnd); lex(); return val; case CosTokenType::ArrayStart: lex(); val = parse_array_content(); expect(CosTokenType::ArrayEnd); lex(); return val; default: throw CosError(QString("Expected token COS value, got %1").arg(int(lookahead.type))); } } }; } // namespace glaxnimate::io::aep mlt-7.22.0/src/modules/glaxnimate/glaxnimate/external/QtAppSetup/src/app/log/listener_store.hpp000664 001750 001750 00000000764 14477652011 034357 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include "app/log/logger.hpp" namespace app::log { class ListenerStore: public LogListener { public: const std::vector& lines() const { return lines_; } protected: void on_line(const LogLine& line) override { lines_.push_back(line); } private: std::vector lines_; }; } // namespace app::log mlt-7.22.0/src/modules/glaxnimate/glaxnimate/external/QtAppSetup/src/app/widgets/settings_dialog.ui000664 001750 001750 00000005525 14477652011 035210 0ustar00ddennedyddennedy000000 000000 app::SettingsDialog 0 0 938 706 Settings .. 0 0 128 0 64 64 0 0 256 0 Qt::Horizontal QDialogButtonBox::Ok buttonBox accepted() app::SettingsDialog accept() 373 433 377 508 list_widget currentRowChanged(int) stacked_widget setCurrentIndex(int) 54 108 300 138 apply_settings() mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/plugin/action.cpp000664 001750 001750 00000005433 14477652011 027561 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "action.hpp" #include "plugin.hpp" #include "app/settings/widget_builder.hpp" using namespace glaxnimate; const std::vector & plugin::PluginActionRegistry::enabled() const { return enabled_actions; } QAction * plugin::PluginActionRegistry::make_qaction ( plugin::ActionService* action ) { QAction* act = new QAction; act->setIcon(action->plugin()->make_icon(action->icon)); if ( action->label.isEmpty() ) act->setText(action->plugin()->data().name); else act->setText(action->label); act->setToolTip(action->tooltip); connect(act, &QAction::triggered, action, &ActionService::trigger); connect(action, &ActionService::disabled, act, &QAction::deleteLater); act->setData(QVariant::fromValue(action)); act->setObjectName("action_plugin_" + action->plugin()->data().name.toLower() + "_" + action->label.toLower()); return act; } void plugin::PluginActionRegistry::add_action ( plugin::ActionService* action ) { auto it = find(action); if ( it != enabled_actions.end() && *it == action ) return; ActionService* sibling_before = nullptr; if ( it != enabled_actions.end() ) sibling_before = *it; enabled_actions.insert(it, action); emit action_added(action, sibling_before); } void plugin::PluginActionRegistry::remove_action ( plugin::ActionService* action ) { auto it = find(action); if ( it == enabled_actions.end() || *it != action ) return; enabled_actions.erase(it); emit action_removed(action); } bool plugin::PluginActionRegistry::compare(plugin::ActionService* a, plugin::ActionService* b) { if ( a->plugin()->data().id == b->plugin()->data().id ) { if ( a->label == b->label ) return a < b; return a->label < b->label; } return a->plugin()->data().id < b->plugin()->data().id; } std::vector::iterator plugin::PluginActionRegistry::find(plugin::ActionService* as) { auto it = std::lower_bound(enabled_actions.begin(), enabled_actions.end(), as, &PluginActionRegistry::compare); return it; } QIcon plugin::ActionService::service_icon() const { return plugin()->make_icon(icon); } void plugin::ActionService::trigger() const { QVariantMap settings_value; if ( !script.settings.empty() ) { if ( !app::settings::WidgetBuilder().show_dialog( script.settings, settings_value, plugin()->data().name ) ) return; } plugin()->run_script(script, { PluginRegistry::instance().global_parameter("window"), PluginRegistry::instance().global_parameter("document"), settings_value }); } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/lottie/lottie_format.hpp000664 001750 001750 00000002417 14477652011 031571 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include "io/base.hpp" #include "io/io_registry.hpp" namespace glaxnimate::io::lottie { class LottieFormat : public ImportExport { Q_OBJECT public: QString slug() const override { return "lottie"; } QString name() const override { return tr("Lottie Animation"); } QStringList extensions() const override { return {"json"}; } bool can_save() const override { return true; } bool can_open() const override { return true; } std::unique_ptr save_settings(model::Composition*) const override; QCborMap to_json(model::Composition* comp, bool strip = false, bool strip_raster = false, const QVariantMap& settings = {}); bool load_json(const QByteArray& data, model::Document* document); private: bool on_save(QIODevice& file, const QString& filename, model::Composition* comp, const QVariantMap& setting_values) override; bool on_open(QIODevice& file, const QString& filename, model::Document* document, const QVariantMap& setting_values) override; private: static Autoreg autoreg; }; } // namespace glaxnimate::io::lottie mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/custom_font.cpp000664 001750 001750 00000016464 14477652011 030454 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "custom_font.hpp" #include #include #include #include "app/utils/qbytearray_hash.hpp" glaxnimate::model::FontFileFormat glaxnimate::model::CustomFontDatabase::font_data_format(const QByteArray& data) { QByteArray head = data.left(4); if ( head == "OTTO" ) return FontFileFormat::OpenType; if ( head == QByteArray("\0\1\0\0", 4) ) return FontFileFormat::TrueType; if ( head == "wOF2" ) return FontFileFormat::Woff2; if ( head == "wOFF" ) return FontFileFormat::Woff; return FontFileFormat::Unknown; } class glaxnimate::model::CustomFontDatabase::CustomFontData { public: CustomFontData() = default; CustomFontData(const QRawFont& font, int database_index, const QByteArray& data_hash, const QByteArray& data) : font(font), database_index(database_index), data_hash(data_hash), data(data) {} QString family_name() const { return font.familyName(); } QRawFont font; int database_index = -1; QByteArray data_hash; QByteArray data; QString source_url; QString css_url; std::set name_aliases; }; class glaxnimate::model::CustomFontDatabase::Private { public: std::unordered_map fonts; // we keep track of hashes to avoid registering the exact same file twice std::unordered_map hashes; std::unordered_map> name_aliases; void tag_alias(const DataPtr& data, const QString& name) { if ( !name.isEmpty() && name != data->family_name() && data->name_aliases.insert(name).second ) name_aliases[name].push_back(data->database_index); } void uninstall(std::unordered_map::iterator iterator) { for ( const auto& name : iterator->second->name_aliases ) { auto iter = name_aliases.find(name); if ( iter != name_aliases.end() ) { if ( iter->second.size() <= 1 ) name_aliases.erase(iter); else iter->second.erase(std::find(iter->second.begin(), iter->second.end(), iterator->second->database_index)); } } hashes.erase(iterator->second->data_hash); QFontDatabase::removeApplicationFont(iterator->first); fonts.erase(iterator); } void remove_reference(int font) { auto it = fonts.find(font); if ( it == fonts.end() ) return; if ( it->second.use_count() == 1 ) uninstall(it); } DataPtr install(const QString& name_alias, const QByteArray& data) { auto hash = QCryptographicHash::hash(data, QCryptographicHash::Sha1); auto hashit = hashes.find(hash); if ( hashit != hashes.end() ) { auto item = fonts.at(hashit->second); tag_alias(item, name_alias); return item; } QRawFont raw(data, 16); if ( !raw.isValid() ) return {}; int index = QFontDatabase::addApplicationFontFromData(data); if ( index == -1 ) return {}; hashes[hash] = index; auto ptr = std::make_shared(raw, index, hash, data); fonts.emplace(index, ptr); tag_alias(ptr, name_alias); return ptr; } }; glaxnimate::model::CustomFontDatabase::CustomFontDatabase() : d(std::make_unique()) { } glaxnimate::model::CustomFontDatabase::~CustomFontDatabase() { } glaxnimate::model::CustomFontDatabase & glaxnimate::model::CustomFontDatabase::instance() { static CustomFontDatabase instance; return instance; } std::vector glaxnimate::model::CustomFontDatabase::fonts() const { std::vector fonts; fonts.reserve(d->fonts.size()); for ( const auto& font : d->fonts ) fonts.emplace_back(font.second); return fonts; } glaxnimate::model::CustomFont glaxnimate::model::CustomFontDatabase::add_font(const QString& name_alias, const QByteArray& ttf_data) { return d->install(name_alias, ttf_data); } glaxnimate::model::CustomFont glaxnimate::model::CustomFontDatabase::get_font(int database_index) { auto it = d->fonts.find(database_index); if ( it == d->fonts.end() ) return {}; return it->second; } QFont glaxnimate::model::CustomFontDatabase::font(const QString& family, const QString& style_name, qreal size) const { auto it = d->name_aliases.find(family); if ( it == d->name_aliases.end() ) { QFont font(family); font.setPointSizeF(size); font.setStyleName(style_name); return font; } CustomFontData* match = d->fonts.at(it->second[0]).get(); for ( int id : it->second ) { const auto& font = d->fonts.at(id); if ( font->font.styleName() == style_name ) { match = font.get(); break; } } QFont font(match->family_name()); font.setPointSizeF(size); font.setStyleName(style_name); return font; } std::unordered_map> glaxnimate::model::CustomFontDatabase::aliases() const { std::unordered_map> map; for ( const auto& p : d->name_aliases ) { std::set names; for ( const auto& id : p.second ) names.insert(d->fonts.at(id)->family_name()); map[p.first] = names; } return map; } glaxnimate::model::CustomFont::CustomFont(CustomFontDatabase::DataPtr dd) : d(std::move(dd)) { if ( !d ) d = std::make_shared(); } glaxnimate::model::CustomFont::CustomFont() : CustomFont(std::make_shared()) { } glaxnimate::model::CustomFont::CustomFont(int database_index) : CustomFont(CustomFontDatabase::instance().get_font(database_index)) { } glaxnimate::model::CustomFont::~CustomFont() { if ( d ) { int index = d->database_index; if ( index != -1 ) { d = {}; CustomFontDatabase::instance().d->remove_reference(index); } } } bool glaxnimate::model::CustomFont::is_valid() const { return d->database_index != -1; } int glaxnimate::model::CustomFont::database_index() const { return d->database_index; } QString glaxnimate::model::CustomFont::family() const { return d->family_name(); } QString glaxnimate::model::CustomFont::style_name() const { return d->font.styleName(); } QFont glaxnimate::model::CustomFont::font(int size) const { QFont font(family(), size); font.setStyleName(style_name()); return font; } const QRawFont & glaxnimate::model::CustomFont::raw_font() const { return d->font; } QByteArray glaxnimate::model::CustomFont::data() const { return d->data; } void glaxnimate::model::CustomFont::set_css_url(const QString& url) { d->css_url = url; } void glaxnimate::model::CustomFont::set_source_url(const QString& url) { d->source_url = url; } const QString & glaxnimate::model::CustomFont::css_url() const { return d->css_url; } const QString & glaxnimate::model::CustomFont::source_url() const { return d->source_url; } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/assets/assets.hpp000664 001750 001750 00000013607 14477652011 030721 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include "model/object.hpp" #include "model/property/object_list_property.hpp" #include "model/property/sub_object_property.hpp" #include "named_color.hpp" #include "bitmap.hpp" #include "gradient.hpp" #include "composition.hpp" #include "embedded_font.hpp" #include "network_downloader.hpp" namespace glaxnimate::model { namespace detail { DocumentNode* defs(model::Document* doc); } // detail template class AssetListBase : public DocumentNode { protected: using Ctor = AssetListBase; public: ObjectListProperty values{this, "values", &AssetListBase::on_added, &AssetListBase::on_removed, &AssetListBase::docnode_child_add_begin, &AssetListBase::docnode_child_remove_begin, &AssetListBase::docnode_child_move_begin, &AssetListBase::docnode_child_move_end }; public: using DocumentNode::DocumentNode; DocumentNode* docnode_parent() const override { return detail::defs(document()); } int docnode_child_count() const override { return values.size(); } DocumentNode* docnode_child(int index) const override { return values[index]; } int docnode_child_index(DocumentNode* dn) const override { return values.index_of(static_cast(dn)); } QIcon instance_icon() const override { return tree_icon(); } protected: virtual void on_added(T* obj, int row) { obj->attach(); emit docnode_child_add_end(obj, row); } virtual void on_removed(T* obj, int row) { obj->detach(); emit docnode_child_remove_end(obj, row); } }; #define ASSET_LIST_CLASS(type) \ GLAXNIMATE_PROPERTY_LIST_IMPL(type, values) \ public: \ using Ctor::Ctor; \ // END class NamedColorList : public AssetListBase { GLAXNIMATE_OBJECT(NamedColorList) ASSET_LIST_CLASS(NamedColor) public: QIcon tree_icon() const override; QString type_name_human() const override { return tr("Swatch"); } signals: void color_changed(int position, model::NamedColor* color); void color_added(int position, model::NamedColor* color); void color_removed(int position, model::NamedColor* color); protected: void on_added(model::NamedColor* color, int position) override; void on_removed(model::NamedColor* color, int position) override; }; class BitmapList : public AssetListBase { GLAXNIMATE_OBJECT(BitmapList) ASSET_LIST_CLASS(Bitmap) public: QIcon tree_icon() const override; QString type_name_human() const override { return tr("Images"); } }; class GradientColorsList : public AssetListBase { GLAXNIMATE_OBJECT(GradientColorsList) ASSET_LIST_CLASS(GradientColors) public: QIcon tree_icon() const override; QString type_name_human() const override { return tr("Gradient Colors"); } }; class GradientList : public AssetListBase { GLAXNIMATE_OBJECT(GradientList) ASSET_LIST_CLASS(Gradient) public: QIcon tree_icon() const override; QString type_name_human() const override { return tr("Gradients"); } }; class CompositionList : public AssetListBase { GLAXNIMATE_OBJECT(CompositionList) ASSET_LIST_CLASS(Composition) public: QIcon tree_icon() const override; protected: void on_added(model::Composition* obj, int position) override; void on_removed(model::Composition* obj, int position) override; QString type_name_human() const override { return tr("Compositions"); } signals: void precomp_added(model::Composition* obj, int position); }; class FontList : public AssetListBase { GLAXNIMATE_OBJECT(FontList) ASSET_LIST_CLASS(EmbeddedFont) public: QIcon tree_icon() const override { return QIcon::fromTheme("font"); } protected: void on_added(model::EmbeddedFont* obj, int position) override; signals: void font_added(model::EmbeddedFont* font); protected: QString type_name_human() const override { return tr("Fonts"); } }; class Assets : public DocumentNode { GLAXNIMATE_OBJECT(Assets) GLAXNIMATE_SUBOBJECT(NamedColorList, colors) GLAXNIMATE_SUBOBJECT(BitmapList, images) GLAXNIMATE_SUBOBJECT(GradientColorsList, gradient_colors) GLAXNIMATE_SUBOBJECT(GradientList, gradients) GLAXNIMATE_SUBOBJECT(CompositionList, compositions) GLAXNIMATE_SUBOBJECT(FontList, fonts) public: using DocumentNode::DocumentNode; Q_INVOKABLE glaxnimate::model::NamedColor* add_color(const QColor& color, const QString& name = {}); Q_INVOKABLE glaxnimate::model::Bitmap* add_image_file(const QString& filename, bool embed); Q_INVOKABLE glaxnimate::model::Bitmap* add_image(const QImage& image, const QString& store_as = "png"); Q_INVOKABLE glaxnimate::model::GradientColors* add_gradient_colors(int index = -1); Q_INVOKABLE glaxnimate::model::Gradient* add_gradient(int index = -1); Q_INVOKABLE glaxnimate::model::EmbeddedFont* add_font(const QByteArray& ttf_data); glaxnimate::model::EmbeddedFont* add_font(const CustomFont& font); Q_INVOKABLE glaxnimate::model::EmbeddedFont* font_by_index(int database_index) const; glaxnimate::model::Composition* add_comp_no_undo(); DocumentNode* docnode_parent() const override; int docnode_child_count() const override; DocumentNode* docnode_child(int index) const override; int docnode_child_index(DocumentNode* dn) const override; QIcon tree_icon() const override; QIcon instance_icon() const override; QString type_name_human() const override { return tr("Assets"); } NetworkDownloader network_downloader; }; } // namespace glaxnimate::model mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/utils.cpp000664 001750 001750 00000003135 14477652011 026552 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "utils.hpp" std::vector> glaxnimate::io::split_keyframes(model::AnimatableBase* prop) { std::vector> split_kfs; std::unique_ptr previous = prop->keyframe(0)->clone(); for ( int i = 1, e = prop->keyframe_count(); i < e; i++ ) { if ( previous->transition().hold() ) { split_kfs.push_back(std::move(previous)); previous = prop->keyframe(i)->clone(); continue; } std::array raw_splits; std::tie(raw_splits[0], raw_splits[1]) = previous->transition().bezier().extrema(1); std::vector splits; for ( qreal t : raw_splits ) { QPointF p = previous->transition().bezier().solve(t); if ( p.y() < 0 || p.y() > 1 ) splits.push_back(t); } if ( splits.size() == 0 ) { split_kfs.push_back(std::move(previous)); previous = prop->keyframe(i)->clone(); } else { auto next = prop->keyframe(i); auto next_segment = previous->split(next, splits); previous = std::move(next_segment.back()); split_kfs.insert(split_kfs.end(), std::make_move_iterator(next_segment.begin()), std::make_move_iterator(next_segment.end() - 1)); } } split_kfs.push_back(std::move(previous)); return split_kfs; } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/shapes/fill.hpp000664 001750 001750 00000002166 14477652011 030324 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include #include "styler.hpp" #include "model/animation/animatable.hpp" namespace glaxnimate::model { class Fill : public StaticOverrides { GLAXNIMATE_OBJECT(Fill) public: enum Rule { NonZero = Qt::WindingFill, EvenOdd = Qt::OddEvenFill, }; private: Q_ENUM(Rule); GLAXNIMATE_PROPERTY(Rule, fill_rule, NonZero, nullptr, nullptr, PropertyTraits::Visual) public: using Ctor::Ctor; QRectF local_bounding_rect(FrameTime t) const override { return collect_shapes(t, {}).bounding_box(); } static QIcon static_tree_icon() { return QIcon::fromTheme("format-fill-color"); } static QString static_type_name_human() { return tr("Fill"); } protected: QPainterPath to_painter_path_impl(FrameTime t) const override; void on_paint(QPainter* p, FrameTime t, PaintMode, model::Modifier* modifier) const override; }; } // namespace glaxnimate::model mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/shapes/path_modifier.cpp000664 001750 001750 00000006425 14477652011 032205 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "path_modifier.hpp" #include "model/shapes/group.hpp" #include "model/shapes/path.hpp" #include "model/animation/join_animatables.hpp" static void to_path_value( const glaxnimate::math::bezier::MultiBezier& mbez, std::vector& paths, glaxnimate::model::Group* group, glaxnimate::model::Document* document ) { for ( int i = 0; i < mbez.size(); i++ ) { if ( i >= int(paths.size()) ) { auto new_path = std::make_unique(document); paths.push_back(new_path.get()); group->shapes.insert(std::move(new_path)); } auto path = paths[i]; path->shape.set(mbez.beziers()[i]); } } static void to_path_frame( const glaxnimate::math::bezier::MultiBezier& mbez, std::vector& paths, glaxnimate::model::FrameTime t, const glaxnimate::model::KeyframeTransition& transition, glaxnimate::model::Group* group, glaxnimate::model::Document* document ) { for ( int i = 0; i < mbez.size(); i++ ) { if ( i >= int(paths.size()) ) { auto new_path = std::make_unique(document); if ( t > 0 ) { glaxnimate::model::KeyframeTransition trans; trans.set_hold(true); new_path->shape.set_keyframe(0, glaxnimate::math::bezier::Bezier{})->set_transition({trans}); } paths.push_back(new_path.get()); group->shapes.insert(std::move(new_path)); } auto path = paths[i]; path->shape.set_keyframe(t, mbez.beziers()[i])->set_transition(transition); } } std::unique_ptr glaxnimate::model::PathModifier::to_path() const { auto group = std::make_unique(document()); group->name.set(name.get()); group->group_color.set(group_color.get()); group->visible.set(visible.get()); std::vector properties; auto flags = PropertyTraits::Visual|PropertyTraits::Animated; for ( auto prop : this->properties() ) { if ( (prop->traits().flags & flags) == flags ) properties.push_back(static_cast(prop)); } JoinAnimatables ja(std::move(properties), JoinAnimatables::NoValues); FrameTime cur_time = ja.properties()[0]->time(); std::vector paths; if ( ja.animated() ) { for ( const auto & kf : ja ) { auto bez = collect_shapes(kf.time, {}); to_path_frame(bez, paths, kf.time, kf.transition(), group.get(), document()); } } else { auto bez = collect_shapes(time(), {}); to_path_value(bez, paths, group.get(), document()); } group->set_time(cur_time); return group; } void glaxnimate::model::PathModifier::on_paint(QPainter* painter, glaxnimate::model::FrameTime t, glaxnimate::model::VisualNode::PaintMode mode, glaxnimate::model::Modifier*) const { for ( auto sib : affected() ) sib->paint(painter, t, mode, const_cast(this)); } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/external/QtAppSetup/src/app/cli.hpp000664 001750 001750 00000007064 14477652011 031304 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include #include #include #include namespace app::cli { class ArgumentError : public std::invalid_argument { public: ArgumentError(const QString& what) : std::invalid_argument(what.toStdString()) {} QString message() const { return QString(what()); } }; struct Argument { enum Type { Flag, String, Int, Size, ShowHelp, ShowVersion }; QStringList names; QString description; Type type = String; QString arg_name; QString dest; int nargs = 0; QVariant default_value; Argument( const QStringList& names, const QString& description, Type type, const QVariant& default_value = {}, const QString& arg_name = {}, const QString& dest = {}, int nargs = 1 ) : names(names), description(description), type(type), arg_name(arg_name), dest(dest), nargs(nargs), default_value(default_value) { if ( this->dest.isEmpty() ) this->dest = get_slug(names); if ( this->arg_name.isEmpty() ) this->arg_name = this->dest; } Argument( const QStringList& names, const QString& description ) : names(names), description(description), type(Flag), default_value(false) { if ( this->dest.isEmpty() ) this->dest = get_slug(names); // positional if ( names.size() == 1 && !names[0].startsWith('-') ) { type = String; nargs = 1; arg_name = names[0]; } } bool is_positional() const; static QString get_slug(const QStringList& names); QVariant arg_to_value(const QString& v, bool* ok) const; QVariant arg_to_value(const QString& v) const; QVariant args_to_value(const QStringList& args, int& index) const; QString help_text_name() const; }; void show_message(const QString& msg, bool error = false); struct ParsedArguments { QVariantMap values; QSet defined; QSet flags; std::optional return_value; bool has_flag(const QString& name) const { return flags.contains(name); } QVariant value(const QString& name) const { return values[name]; } bool is_defined(const QString& name) const { return defined.contains(name); } void handle_error(const QString& error); void handle_finish(const QString& message); }; class Parser { private: enum RefType { Option, Positional }; struct ArgumentGroup { QString name; std::vector> args = {}; }; public: explicit Parser(const QString& description) : description(description) {} app::cli::Parser& add_argument(Argument arg); ParsedArguments parse(const QStringList& args, int offset = 1) const; app::cli::Parser& add_group(const QString& name); const Argument* option_from_arg(const QString& arg) const; QString version_text() const; QString help_text() const; private: QString wrap_text(const QString& names, int name_max, const QString& description) const; QString description; std::vector options = {}; std::vector positional = {}; std::vector groups = {}; }; } // namespace app::cli mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/aep/ae_project.hpp000664 001750 001750 00000036324 14477652011 030305 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include "model/animation/frame_time.hpp" #include "math/math.hpp" #include "math/vector.hpp" #include "utils/iterator.hpp" namespace glaxnimate::io::aep { using Id = std::uint32_t; struct PropertyIterator; struct PropertyPair; struct PropertyBase { enum Type { Null, PropertyGroup, Property, TextProperty, EffectInstance, Mask }; virtual ~PropertyBase() noexcept = default; virtual Type class_type() const noexcept { return Null; } const PropertyBase* get(const QString& key) const; virtual const PropertyPair* get_pair(const QString& key) const { Q_UNUSED(key); return nullptr; } explicit operator bool() const { return class_type() != Null; } const PropertyBase& operator[](const QString& key) const { auto prop = get(key); if ( prop ) return *prop; static PropertyBase null_property; return null_property; } virtual PropertyIterator begin() const; virtual PropertyIterator end() const; }; struct PropertyPair { QString match_name; std::unique_ptr value; }; inline const PropertyBase* PropertyBase::get(const QString& key) const { if ( auto p = get_pair(key) ) return p->value.get(); return nullptr; } struct PropertyIterator : public utils::RandomAccessIteratorWrapper::const_iterator> { public: PropertyIterator(InternalIterator it = {}) : Parent(it) {} const PropertyPair* operator->() const { return iter.operator->(); } const PropertyPair& operator*() const { return *iter; } }; inline PropertyIterator PropertyBase::begin() const { return {}; } inline PropertyIterator PropertyBase::end() const { return {}; } struct PropertyGroup : PropertyBase { bool visible = true; QString name = ""; std::vector properties; Type class_type() const noexcept override { return PropertyBase::PropertyGroup; } const PropertyPair* property_pair(const QString& match_name) const { for ( const auto& prop : properties ) { if ( prop.match_name == match_name ) return ∝ } return nullptr; } const PropertyBase* property(const QString& match_name) const { if ( auto p = property_pair(match_name) ) return p->value.get(); return nullptr; } const PropertyPair* get_pair(const QString& key) const override { return property_pair(key); } PropertyIterator begin() const override { return properties.begin(); } PropertyIterator end() const override { return properties.end(); } }; enum class KeyframeTransitionType { Linear = 1, Bezier = 2, Hold = 3, }; enum class KeyframeBezierMode { Normal, Continuous, Auto, }; struct BezierData { bool closed = false; QPointF minimum; QPointF maximum; std::vector points; /** * \brief Converts points from the weird bounding box notation to absolute */ QPointF convert_point(const QPointF& p) const { return { math::lerp(minimum.x(), maximum.x(), p.x()), math::lerp(minimum.y(), maximum.y(), p.y()), }; } QPointF converted_point(int index) const { return convert_point(points[index]); } }; template struct GradientStop { double offset; double mid_point; Type value; }; template class GradientStops : public std::vector> { public: using Stop = GradientStop; using std::vector::vector; int size() const { return std::vector::size(); } T value_at(double t, int& index) const { if ( this->empty() ) return 1; if ( this->size() == 1 ) return this->front().value; if ( t >= this->back().offset || index + 1 >= this->size() ) { index = this->size(); return this->back().value; } while ( t >= (*this)[index+1].offset ) index++; if ( index + 1 >= this->size() ) return this->back().value; const auto& before = (*this)[index]; const auto& after = (*this)[index+1]; auto factor = math::unlerp(before.offset, after.offset, t); if ( !qFuzzyCompare(before.mid_point, 0.5) ) { auto vbefore = before.value; auto vafter = after.value; auto vmid = math::lerp(before.value, after.value, before.mid_point); if ( factor < after.mid_point ) { factor = math::unlerp(0., before.mid_point, factor); vafter = vmid; } else { factor = math::unlerp(before.mid_point, 1., factor); vbefore = vmid; } return math::lerp(vbefore, vafter, factor); } return math::lerp(before.value, after.value, factor); } GradientStops split_midpoints() const { double midpoint = 0.5; Stop previous; GradientStops stops; for ( const auto& stop : *this ) { if ( !qFuzzyCompare(midpoint, 0.5) ) { auto midoffset = math::lerp(previous.offset, stop.offset, midpoint); auto midvalue = math::lerp(previous.value, stop.value, midpoint); stops.push_back({midoffset, 0.5, midvalue}); } midpoint = stop.mid_point; stops.push_back({stop.offset, 0.5, stop.value}); previous = stop; } return stops; } }; struct Gradient { GradientStops alpha_stops; GradientStops color_stops; QGradientStops to_qt() const { QGradientStops stops; int index = 0; for ( const auto& stop : color_stops.split_midpoints() ) { auto alpha = alpha_stops.value_at(stop.offset, index); auto color = stop.value; color.setAlphaF(alpha); stops.push_back({stop.offset, color}); } return stops; } }; enum class LabelColors { None, Red, Yellow, Aqua, Pink, Lavender, Peach, SeaFoam, Blue, Green, Purple, Orange, Brown, Fuchsia, Cyan, Sandstone, DarkGreen, }; struct Marker { model::FrameTime duration = 0; LabelColors label_color = LabelColors::None; bool is_protected = false; QString name = ""; }; struct Font { QString family; }; enum class TextTransform { Normal, SmallCaps, AllCaps }; enum class TextVerticalAlign { Normal, Superscript, Subscript, }; enum class TextJustify { Left, Right, Center, JustifyLastLineLeft, JustifyLastLineRight, JustifyLastLineCenter, JustifyLastLineFull, }; struct LineStyle { TextJustify text_justify = TextJustify::Left; // Number of characters the style applies to // Note that in AE LineStyle represents a line // Rather than an arbitrary span of text int character_count = 0; }; struct CharacterStyle { int font_index = 0; double size = 0; bool faux_bold = false; bool faux_italic = false; TextTransform text_transform = TextTransform::Normal; TextVerticalAlign vertical_align = TextVerticalAlign::Normal; QColor fill_color; QColor stroke_color; bool stroke_enabled = false; bool stroke_over_fill = false; double stroke_width = 0; // Number of characters the style applies to int character_count = 0; }; struct TextDocument { QString text; std::vector line_styles; std::vector character_styles; }; enum class LayerSource { Layer = 0, Effects = -1, Masks = -2 }; struct LayerSelection { Id layer_id; LayerSource layer_source = LayerSource::Layer; }; class PropertyValue { public: enum Index { None, Vector2D, Vector3D, Color, Number, Gradient, BezierData, Marker, TextDocument, LayerSelection }; template PropertyValue(T&& v) : value(std::forward(v)) {} PropertyValue() = default; PropertyValue(PropertyValue& v) = delete; PropertyValue(const PropertyValue& v) = delete; PropertyValue(PropertyValue&& v) = default; PropertyValue& operator=(PropertyValue&& v) = default; Index type() const { return Index(value.index()); } std::variant< std::nullptr_t, QPointF, QVector3D, QColor, qreal, aep::Gradient, aep::BezierData, aep::Marker, aep::TextDocument, aep::LayerSelection > value = nullptr; qreal magnitude() const { switch ( type() ) { default: case None: return 0; case Vector2D: return math::length(std::get(value)); case Vector3D: return math::length(std::get(value)); case Color: { const auto& c = std::get(value); return math::hypot(c.red(), c.green(), c.blue(), c.alpha()); } case Number: return std::get(value); } } }; struct Keyframe { PropertyValue value; model::FrameTime time = 0; std::vector in_influence; std::vector in_speed; std::vector out_influence; std::vector out_speed; QPointF in_tangent; QPointF out_tangent; KeyframeTransitionType transition_type = KeyframeTransitionType::Linear; KeyframeBezierMode bezier_mode = KeyframeBezierMode::Normal; bool roving = false; LabelColors label_color = LabelColors::None; }; enum class PropertyType { Color, NoValue, Position, MultiDimensional, LayerSelection, Integer, MaskIndex, }; struct Property : PropertyBase { bool split = false; bool animated = false; bool is_component = false; int components = 0; PropertyValue value; std::vector keyframes; PropertyType type = PropertyType::MultiDimensional; std::optional expression; Type class_type() const noexcept override { return PropertyBase::Property; } }; struct TextProperty : PropertyBase { std::vector fonts; aep::Property documents; Type class_type() const noexcept override { return PropertyBase::TextProperty; } }; enum class LayerQuality { Wireframe, Draft, Best }; enum class LayerType { AssetLayer, LightLayer, CameraLayer, TextLayer, ShapeLayer }; enum TrackMatteType { None, Alpha, AlphaInverted, Luma, LumaInverted, }; struct Layer { Id id = 0; LayerQuality quality = LayerQuality::Draft; model::FrameTime start_time = 0; qreal time_stretch = 1; model::FrameTime in_time = 0; model::FrameTime out_time = 0; bool is_guide = false; bool bicubic_sampling = false; bool auto_orient = false; bool is_adjustment = false; bool threedimensional = false; bool solo = false; bool is_null = false; bool visible = true; bool effects_enabled = false; bool motion_blur = false; bool locked = false; bool shy = false; bool continuously_rasterize = false; Id asset_id = 0; LabelColors label_color = LabelColors::None; QString name = ""; LayerType type = LayerType::ShapeLayer; Id parent_id = 0; TrackMatteType matte_mode = TrackMatteType::None; Id matte_id = 0; PropertyGroup properties; }; enum class EffectParameterType { Layer = 0, Scalar = 2, Angle = 3, Boolean = 4, Color = 5, Vector2D = 6, Enum = 7, Group = 9, Slider = 10, Unknown = 15, Vector3D = 16, }; struct EffectParameter { QString match_name = ""; QString name = ""; EffectParameterType type = EffectParameterType::Unknown; PropertyValue default_value; PropertyValue last_value; }; struct EffectDefinition { QString match_name; QString name; std::vector parameters; std::map parameter_map; }; struct EffectInstance : PropertyBase { QString name; aep::PropertyGroup parameters; Type class_type() const noexcept override { return PropertyBase::EffectInstance; } }; enum class MaskMode { None, Add, Subtract, Intersect, Darken, Lighten, Difference }; struct Mask : PropertyBase { bool inverted = false; bool locked = false; MaskMode mode = MaskMode::Add; aep::PropertyGroup properties; Type class_type() const noexcept override { return PropertyBase::Mask; } const PropertyPair* get_pair(const QString& key) const override { return properties.property_pair(key); } }; struct FolderItem { enum Type { Composition, Folder, Asset, Solid }; Id id; QString name = ""; LabelColors label_color = LabelColors::None; virtual ~FolderItem() noexcept = default; virtual Type type() const noexcept = 0; }; struct Composition : FolderItem { std::vector> layers; std::uint16_t resolution_x = 0; std::uint16_t resolution_y = 0; double time_scale = 0; model::FrameTime playhead_time = 0; model::FrameTime in_time = 0; model::FrameTime out_time = 0; model::FrameTime duration = 0; QColor color; bool shy = false; bool motion_blur = false; bool frame_blending = false; bool preserve_framerate = false; bool preserve_resolution = false; double width = 0; double height = 0; std::uint32_t pixel_ratio_width = 1; std::uint32_t pixel_ratio_height = 1; double framerate = 0; std::uint16_t shutter_angle = 0; std::int32_t shutter_phase = 0; std::uint32_t samples_limit = 0; std::uint32_t samples_per_frame = 0; std::unique_ptr markers; std::vector> views; Type type() const noexcept override { return FolderItem::Composition; } model::FrameTime time_to_frames(model::FrameTime time) const { return time / time_scale; }; }; struct Asset : FolderItem { int width = 0; int height = 0; }; struct FileAsset : Asset { QFileInfo path; Type type() const noexcept override { return FolderItem::Asset; } }; struct Solid : Asset { QColor color; Type type() const noexcept override { return FolderItem::Solid; } }; struct Folder : FolderItem { std::vector> items; template T* add() { auto item = std::make_unique(); auto ptr = item.get(); items.push_back(std::move(item)); return ptr; } Type type() const noexcept override { return FolderItem::Folder; } }; struct Project { std::unordered_map assets; Folder folder; std::vector compositions; std::unordered_map effects; FolderItem* current_item = nullptr; }; } // namespace glaxnimate::io::aep mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/lottie/validation.cpp000664 001750 001750 00000004431 14477652011 031044 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "validation.hpp" #include "model/visitor.hpp" #include "model/shapes/image.hpp" #include "model/assets/composition.hpp" using namespace glaxnimate; using namespace glaxnimate::io::lottie; void glaxnimate::io::lottie::ValidationVisitor::on_visit(model::Document*, model::Composition* main) { if ( !main ) return; if ( fixed_size.isValid() ) { qreal width = main->height.get(); if ( width != fixed_size.width() ) fmt->error(LottieFormat::tr("Invalid width: %1, should be %2").arg(width).arg(fixed_size.width())); qreal height = main->height.get(); if ( height != fixed_size.height() ) fmt->error(LottieFormat::tr("Invalid height: %1, should be %2").arg(height).arg(fixed_size.height())); } if ( !allowed_fps.empty() ) { qreal fps = main->fps.get(); if ( std::find(allowed_fps.begin(), allowed_fps.end(), fps) == allowed_fps.end() ) { QStringList allowed; for ( auto f : allowed_fps ) allowed.push_back(QString::number(f)); fmt->error(LottieFormat::tr("Invalid fps: %1, should be %2").arg(fps).arg(allowed.join(" or "))); } } if ( max_frames > 0 ) { auto duration = main->animation->duration(); if ( duration > max_frames ) fmt->error(LottieFormat::tr("Too many frames: %1, should be less than %2").arg(duration).arg(max_frames)); } } namespace { class DiscordVisitor : public ValidationVisitor { public: explicit DiscordVisitor(LottieFormat* fmt) : ValidationVisitor(fmt) { allowed_fps.push_back(60); fixed_size = QSize(320, 320); } private: using ValidationVisitor::on_visit; void on_visit(model::DocumentNode * node) override { if ( qobject_cast(node) ) { show_error(node, LottieFormat::tr("Images are not supported"), app::log::Error); } } }; } // namespace void glaxnimate::io::lottie::validate_discord(model::Document* document, model::Composition* main, glaxnimate::io::lottie::LottieFormat* format) { DiscordVisitor(format).visit(document, main); } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/shapes/group.hpp000664 001750 001750 00000004040 14477652011 030523 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include "shape.hpp" #include "model/transform.hpp" #include "model/property/sub_object_property.hpp" #include "utils/range.hpp" namespace glaxnimate::model { class Group : public StaticOverrides { GLAXNIMATE_OBJECT(Group) public: GLAXNIMATE_PROPERTY_LIST(ShapeElement, shapes) GLAXNIMATE_SUBOBJECT(Transform, transform) GLAXNIMATE_ANIMATABLE(float, opacity, 1, &Group::opacity_changed, 0, 1, false, PropertyTraits::Percent) GLAXNIMATE_PROPERTY(bool, auto_orient, false, &Group::on_transform_matrix_changed, {}, PropertyTraits::Visual|PropertyTraits::Hidden) public: Group(Document* document); int docnode_child_count() const override { return shapes.size(); } DocumentNode* docnode_child(int index) const override { return shapes[index]; } int docnode_child_index(DocumentNode* obj) const override { return shapes.index_of(static_cast(obj)); } static QIcon static_tree_icon() { return QIcon::fromTheme("object-group"); } static QString static_type_name_human() { return tr("Group"); } void add_shapes(model::FrameTime t, math::bezier::MultiBezier & bez, const QTransform& transform) const override; QRectF local_bounding_rect(FrameTime t) const override; QTransform local_transform_matrix(model::FrameTime t) const override; QPainterPath to_clip(model::FrameTime t) const override; std::unique_ptr to_path() const override; signals: void opacity_changed(float op); protected: QPainterPath to_painter_path_impl(model::FrameTime t) const override; void on_paint(QPainter*, FrameTime, PaintMode, model::Modifier*) const override; void on_graphics_changed() override; void on_composition_changed(model::Composition* old_comp, model::Composition* new_comp) override; private slots: void on_transform_matrix_changed(); }; } // namespace glaxnimate::model mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/math/polynomial.hpp000664 001750 001750 00000001005 14477652011 030116 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include namespace glaxnimate::math { /** * \brief Returns the real roots of * a x^3 + b x^2 + c x + d = 0 */ std::vector cubic_roots(double a, double b, double c, double d); /** * \brief Returns the real roots of * a x^2 + b x + c = 0 */ std::vector quadratic_roots(double a, double b, double c); } // namespace glaxnimate::math mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/mime/000775 001750 001750 00000000000 14477652011 025633 5ustar00ddennedyddennedy000000 000000 mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/animation_container.hpp000664 001750 001750 00000002765 14477652011 032141 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include "model/object.hpp" #include "model/property/property.hpp" namespace glaxnimate::model { /** * \brief Helper class for document nodes that enclose an animation */ class AnimationContainer: public Object { GLAXNIMATE_OBJECT(AnimationContainer) GLAXNIMATE_PROPERTY(float, first_frame, 0, &AnimationContainer::on_first_frame_changed, &AnimationContainer::validate_first_frame, PropertyTraits::Visual) GLAXNIMATE_PROPERTY(float, last_frame, -1, &AnimationContainer::on_last_frame_changed, &AnimationContainer::validate_last_frame, PropertyTraits::Visual) Q_PROPERTY(bool time_visible READ time_visible) Q_PROPERTY(float duration READ duration) public: using Object::Object; /** * \brief Whether time() is within first/last frame */ bool time_visible() const; bool time_visible(FrameTime t) const; void set_time(FrameTime t) override; float duration() const; QString type_name_human() const override; void stretch_time(qreal multiplier) override; signals: void first_frame_changed(float); void last_frame_changed(float); void time_visible_changed(bool visible); private slots: void on_first_frame_changed(float); void on_last_frame_changed(float); private: bool validate_first_frame(int f) const; bool validate_last_frame(int f) const; }; } // namespace glaxnimate::model mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/math/bezier/cubic_struts.hpp000664 001750 001750 00000001753 14477652011 031736 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include "segment.hpp" namespace glaxnimate::math::bezier { struct BezierStruts { QPointF B; ///< Point on a bezier segment qreal t; ///< Bezier parameter in (0,1) (should never be 0 or 1) QPointF e1; ///< Linear handles in de casteljau lerp(e1, e2, t) = B QPointF e2; }; QPointF get_quadratic_handle(const math::bezier::BezierSegment& segment, const QPointF& B, qreal t); math::bezier::BezierSegment cubic_segment_from_struts( const math::bezier::BezierSegment& segment, const BezierStruts& struts ); BezierStruts cubic_struts_idealized(const math::bezier::BezierSegment& segment, const QPointF& B); struct ProjectResult; BezierStruts cubic_struts_projection( const math::bezier::BezierSegment& segment, const QPointF& B, const math::bezier::ProjectResult& projection ); } // namespace glaxnimate::math::bezier mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/math/bezier/bezier_length.cpp000664 001750 001750 00000007500 14477652011 032035 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "bezier_length.hpp" glaxnimate::math::bezier::LengthData::LengthData(const Solver& segment, int steps) { if ( steps == 0 ) return; children_.reserve(steps); QPointF p = segment.points()[0]; for ( int i = 1; i <= steps; i++ ) { qreal t = qreal(i) / steps; QPointF q = segment.solve(t); auto l = math::length(p - q); length_ += l; children_.push_back({t, l, length_}); p = q; } } glaxnimate::math::bezier::LengthData::LengthData(const Bezier& bez, int steps) { children_.reserve(bez.size()); int count = bez.segment_count(); for ( int i = 0; i < count; i++ ) { children_.emplace_back(bez.segment(i), steps); length_ += children_.back().length_; children_.back().cumulative_length_ = length_; } } glaxnimate::math::bezier::LengthData::LengthData(const MultiBezier& mbez, int steps) { children_.reserve(mbez.size()); for ( const auto& bez : mbez.beziers() ) { children_.emplace_back(bez, steps); length_ += children_.back().length_; children_.back().cumulative_length_ = length_; } } glaxnimate::math::bezier::LengthData::SplitInfo glaxnimate::math::bezier::LengthData::at_ratio(qreal ratio) const { return at_length(length_ * ratio); } glaxnimate::math::bezier::LengthData::SplitInfo glaxnimate::math::bezier::LengthData::at_length(qreal length) const { if ( length <= 0 ) return {0, 0., 0., &children_.front()}; if ( length >= length_ ) return { int(children_.size() - 1), 1., length - (children_.size() == 1 ? 0 : children_[children_.size() - 2].length_), &children_.back() }; qreal prev_length = 0; for ( int i = 0; i < int(children_.size()); i++ ) { const auto& child = children_[i]; if ( child.cumulative_length_ > length ) { qreal residual_length = length - prev_length; qreal ratio = qFuzzyIsNull(child.length_) ? 0 : residual_length / child.length_; if ( child.leaf_ ) ratio = math::lerp(i == 0 ? 0 : children_[i - 1].t_, children_[i].t_, ratio); return {i, ratio, residual_length, &child}; } prev_length = child.cumulative_length_; } return {int(children_.size() - 1), 1., length, &children_.back()}; } qreal glaxnimate::math::bezier::LengthData::length() const noexcept { return length_; } glaxnimate::math::bezier::LengthData::LengthData(qreal t, qreal length, qreal cumulative_length) : t_(t), length_(length), cumulative_length_(cumulative_length), leaf_(true) {} qreal glaxnimate::math::bezier::LengthData::from_ratio(qreal ratio) const { if ( ratio <= 0 ) return 0; if ( ratio >= 1 ) return length_; for ( int i = 0; i < int(children_.size()); i++ ) { if ( qFuzzyCompare(children_[i].t_, ratio) ) return children_[i].cumulative_length_; if ( children_[i].t_ >= ratio ) { if ( i == 0 ) { qreal factor = ratio * children_[i].t_; return factor * children_[i].cumulative_length_; } qreal factor = (ratio - children_[i-1].t_) * (children_[i].t_ - children_[i-1].t_); return math::lerp(children_[i-1].cumulative_length_, children_[i].cumulative_length_, factor); } } return length_; } qreal glaxnimate::math::bezier::LengthData::child_start(int index) const { if ( index == 0 ) return 0; return children_[index - 1].cumulative_length_; } qreal glaxnimate::math::bezier::LengthData::child_end(int index) const { return children_[index].cumulative_length_; } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/plugin/plugin.hpp000664 001750 001750 00000007375 14477652011 027616 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include #include "app/log/log.hpp" #include "plugin/service.hpp" namespace app::scripting { class ScriptEngine; } // namespace app::scripting namespace glaxnimate::plugin { struct PluginData { QDir dir; QString id; int version = 0; const app::scripting::ScriptEngine* engine = nullptr; QString engine_name; QString name; QString author; QString icon; QString description; std::vector> services; }; class Plugin { public: Plugin(PluginData data, bool user_installed) : data_(std::move(data)), user_installed_(user_installed) { icon_ = QIcon::fromTheme("libreoffice-extension"); icon_ = make_icon(data_.icon); for ( const auto& ps : data_.services ) ps->set_plugin(this); } const PluginData& data() const { return data_; } QString file(const QString& path) const { if ( !QDir::isRelativePath(path) ) return {}; if ( !data_.dir.exists(path) ) return {}; return data_.dir.cleanPath(path); } QIcon make_icon(const QString& icon) const { if ( !icon.isEmpty() ) { if ( icon.startsWith("theme:") ) return QIcon::fromTheme(icon.mid(6)); if ( data_.dir.exists(icon) ) return QIcon(data_.dir.absoluteFilePath(icon)); } return icon_; } bool available() const { return data_.engine; } bool can_enable() const { return !enabled_ && data_.engine; } bool can_disable() const { return enabled_; } void enable() { if ( !can_enable() ) return; for ( const auto& svc : data_.services ) svc->enable(); enabled_ = true; } void disable() { if ( !can_disable() ) return; for ( const auto& svc : data_.services ) svc->disable(); enabled_ = false; } bool user_installed() const { return user_installed_; } const QIcon& icon() const { return icon_; } bool run_script(const PluginScript& script, const QVariantList& args) const; bool enabled() const { return enabled_; } app::log::Log logger() const { return {"Plugins", data_.name}; } private: PluginData data_; bool enabled_ = false; bool user_installed_ = false; QIcon icon_; }; class Executor; class PluginRegistry : public QObject { Q_OBJECT public: static PluginRegistry& instance() { static PluginRegistry instance; return instance; } void load(); const std::vector>& plugins() const { return plugins_; } bool load_plugin(const QString& path, bool user_installed); Plugin* plugin(const QString& id) const; Executor* executor() const; void set_executor(Executor* exec); QVariant global_parameter(const QString& name) const; signals: void loaded(); private: PluginRegistry() = default; PluginRegistry(const PluginRegistry&) = delete; ~PluginRegistry() = default; void load_service(const QJsonObject& jobj, PluginData& data) const; PluginScript load_script(const QJsonObject& jobj) const; void load_setting(const QJsonObject& jobj, PluginScript& script) const; QVariantMap load_choices(const QJsonValue& val) const; std::vector> plugins_; Executor* executor_ = nullptr; QMap names; app::log::Log logger{"Plugins"}; }; } // namespace glaxnimate::plugin mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/rive/rive_format.cpp000664 001750 001750 00000012570 14477652011 030677 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "rive_format.hpp" #include #include #include "rive_loader.hpp" #include "rive_exporter.hpp" glaxnimate::io::Autoreg glaxnimate::io::rive::RiveFormat::autoreg; bool glaxnimate::io::rive::RiveFormat::on_open(QIODevice& file, const QString&, model::Document* document, const QVariantMap&) { BinaryInputStream stream(&file); if ( stream.read(4) != "RIVE" ) { error(tr("Unsupported format")); return false; } auto vmaj = stream.read_uint_leb128(); auto vmin = stream.read_uint_leb128(); stream.read_uint_leb128(); // file id if ( stream.has_error() ) { error(tr("Could not read header")); return false; } if ( vmaj != RiveFormat::format_version ) { error(tr("Loading unsupported rive file version %1.%2, the only supported version is %3").arg(vmaj).arg(vmin).arg(7)); return false; } if ( stream.has_error() ) { error(tr("Could not read property table")); return false; } return RiveLoader(stream, this).load_document(document); } bool glaxnimate::io::rive::RiveFormat::on_save(QIODevice& device, const QString&, model::Composition* comp, const QVariantMap&) { RiveExporter exporter(&device, this); exporter.write_document(comp->document()); return true; } static QString property_type_to_string(glaxnimate::io::rive::PropertyType type) { switch ( type ) { case glaxnimate::io::rive::PropertyType::VarUint: return "VarUint"; case glaxnimate::io::rive::PropertyType::Bool: return "bool"; case glaxnimate::io::rive::PropertyType::String: return "string"; case glaxnimate::io::rive::PropertyType::Bytes: return "bytes"; case glaxnimate::io::rive::PropertyType::Float: return "float"; case glaxnimate::io::rive::PropertyType::Color: return "color"; } return "?"; } QJsonDocument glaxnimate::io::rive::RiveFormat::to_json(const QByteArray& binary_data) { BinaryInputStream stream(binary_data); if ( stream.read(4) != "RIVE" ) return {}; auto vmaj = stream.read_uint_leb128(); auto vmin = stream.read_uint_leb128(); auto file_id = stream.read_uint_leb128(); if ( stream.has_error() || vmaj != RiveFormat::format_version ) return {}; RiveLoader loader(stream, this); QJsonArray summary; QJsonArray objects; int id = 0; bool has_artboard = false; for ( const auto& rive_obj : loader.load_object_list() ) { if ( !rive_obj ) { summary.push_back("Invalid"); objects.push_back("Invalid"); continue; } if ( rive_obj.type().id == TypeId::Artboard ) { has_artboard = true; id = 0; } QJsonObject summary_obj; QJsonObject obj; QJsonArray types; for ( const auto& def : rive_obj.type().definitions ) { QJsonObject jdef; jdef["id"] = int(def->type_id); jdef["name"] = def->name; types.push_back(jdef); } obj["class"] = types; QJsonArray props; for ( const auto& p : rive_obj.type().properties ) { QJsonObject prop; prop["id"] = int(p->id); prop["name"] = p->name; prop["type"] = property_type_to_string(p->type); auto iter = rive_obj.properties().find(p); QJsonValue val; if ( iter != rive_obj.properties().end() && iter->second.isValid() ) { if ( iter->second.userType() == QMetaType::QColor ) val = iter->second.value().name(); else if ( iter->second.userType() == QMetaType::ULongLong || iter->second.userType() == QMetaType::ULong ) val = iter->second.toInt(); else if ( iter->second.userType() == QMetaType::QByteArray ) val = QString::fromLatin1(iter->second.toByteArray().toBase64()); else val = QJsonValue::fromVariant(iter->second); summary_obj[iter->first->name] = val; } prop["value"] = val; props.push_back(prop); } obj["properties"] = props; QJsonObject summary_obj_parent; summary_obj_parent[!rive_obj ? "?" : rive_obj.definition()->name] = summary_obj; if ( has_artboard ) { summary_obj_parent["-id"] = id; obj["object_id"] = id; id++; } objects.push_back(obj); summary.push_back(summary_obj_parent); } QJsonObject header; QJsonArray version; version.push_back(int(vmaj)); version.push_back(int(vmin)); header["version"] = version; header["file_id"] = int(file_id); QJsonArray extra_props; for ( const auto& p : loader.extra_properties() ) { QJsonObject prop; prop["id"] = int(p.first); prop["type"] = property_type_to_string(p.second); } header["toc"] = extra_props; QJsonObject root; root["brief"] = summary; root["detail"] = objects; root["header"] = header; return QJsonDocument(root); } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/assets/asset_base.cpp000664 001750 001750 00000000231 14477652011 031510 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "asset_base.hpp" src/modules/glaxnimate/glaxnimate/external/QtAppSetup/src/app/scripting/python/casters.hpp000664 001750 001750 00000024416 14477652011 035445 0ustar00ddennedyddennedy000000 000000 mlt-7.22.0/* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #undef slots #include #include #include namespace pybind11::detail { template <> struct type_caster { public: /** * This macro establishes the name 'QString' in * function signatures and declares a local variable * 'value' of type QString */ PYBIND11_TYPE_CASTER(QString, _("QString")); /** * Conversion part 1 (Python->C++): convert a PyObject into a QString * instance or return false upon failure. The second argument * indicates whether implicit conversions should be applied. */ bool load(handle src, bool ic) { type_caster stdc; if ( stdc.load(src, ic) ) { value = QString::fromStdString(stdc); return true; } return false; } /** * Conversion part 2 (C++ -> Python): convert an QString instance into * a Python object. The second and third arguments are used to * indicate the return value policy and parent object (for * ``return_value_policy::reference_internal``) and are generally * ignored by implicit casters. */ static handle cast(QString src, return_value_policy policy, handle parent) { return type_caster::cast(src.toStdString(), policy, parent); } }; template <> struct type_caster { public: PYBIND11_TYPE_CASTER(QUuid, _("QUuid")); bool load(handle src, bool ic); static handle cast(QUuid src, return_value_policy policy, handle parent); }; template <> struct type_caster { public: struct CustomConverter { std::function load; std::function cast; }; PYBIND11_TYPE_CASTER(QVariant, _("QVariant")); bool load(handle src, bool ic); static handle cast(QVariant, return_value_policy policy, handle parent); template static void add_custom_type() { custom_converters[qMetaTypeId()] = { [](const handle& src, QVariant& value){ auto caster = pybind11::detail::make_caster(); if ( caster.load(src, false) ) { value = QVariant::fromValue(pybind11::detail::cast_op(caster)); return true; } return false; }, [](const QVariant& src, return_value_policy policy, const handle& parent){ return pybind11::detail::make_caster::cast(src.value(), policy, parent); } }; } private: static std::map custom_converters; }; template <> struct type_caster { public: PYBIND11_TYPE_CASTER(QByteArray, _("QByteArray")); bool load(handle src, bool) { PyObject *source = src.ptr(); if ( !PyBytes_Check(source) ) return false; char* buffer; Py_ssize_t len; if ( PyBytes_AsStringAndSize(source, &buffer, &len) == -1) return false; value = QByteArray(buffer, len); return true; } static handle cast(const QByteArray& data, return_value_policy, handle) { PyObject * obj = PyBytes_FromStringAndSize(data.data(), data.size()); if ( obj ) return obj; return {}; } }; template <> struct type_caster { public: PYBIND11_TYPE_CASTER(QDateTime, _("QDateTime")); bool load(handle src, bool) { if ( !PyDateTimeAPI ) PyDateTime_IMPORT; PyObject *source = src.ptr(); if ( !PyDateTime_Check(source) ) return false; value = QDateTime( QDate( PyDateTime_GET_YEAR(source), PyDateTime_GET_MONTH(source), PyDateTime_GET_DAY(source) ), QTime( PyDateTime_DATE_GET_HOUR(source), PyDateTime_DATE_GET_MINUTE(source), PyDateTime_DATE_GET_SECOND(source), PyDateTime_DATE_GET_MICROSECOND(source) / 1000 ) ); return true; } static handle cast(QDateTime val, return_value_policy, handle) { if ( !PyDateTimeAPI ) PyDateTime_IMPORT; return PyDateTime_FromDateAndTime( val.date().year(), val.date().month(), val.date().day(), val.time().hour(), val.time().minute(), val.time().second(), val.time().msec() * 1000 ); } }; template <> struct type_caster { public: PYBIND11_TYPE_CASTER(QDate, _("QDate")); bool load(handle src, bool) { if ( !PyDateTimeAPI ) PyDateTime_IMPORT; PyObject *source = src.ptr(); if ( !PyDate_Check(source) ) return false; value = QDate( PyDateTime_GET_YEAR(source), PyDateTime_GET_MONTH(source), PyDateTime_GET_DAY(source) ); return true; } static handle cast(QDate val, return_value_policy, handle) { if ( !PyDateTimeAPI ) PyDateTime_IMPORT; return PyDate_FromDate( val.year(), val.month(), val.day() ); } }; template <> struct type_caster { public: PYBIND11_TYPE_CASTER(QTime, _("QTime")); bool load(handle src, bool) { if ( !PyDateTimeAPI ) PyDateTime_IMPORT; PyObject *source = src.ptr(); if ( PyTime_Check(source) ) { value = QTime( PyDateTime_TIME_GET_HOUR(source), PyDateTime_TIME_GET_MINUTE(source), PyDateTime_TIME_GET_SECOND(source), PyDateTime_TIME_GET_MICROSECOND(source) / 1000 ); return true; } if ( PyDateTime_Check(source) ) { value = QTime( PyDateTime_DATE_GET_HOUR(source), PyDateTime_DATE_GET_MINUTE(source), PyDateTime_DATE_GET_SECOND(source), PyDateTime_DATE_GET_MICROSECOND(source) / 1000 ); return true; } return false; } static handle cast(QTime val, return_value_policy, handle) { if ( !PyDateTimeAPI ) PyDateTime_IMPORT; return PyTime_FromTime( val.hour(), val.minute(), val.second(), val.msec() * 1000 ); } }; template struct type_caster> : list_caster, Type> {}; #if QT_VERSION_MAJOR < 6 // in qt 6 QVector is the same as QList template struct type_caster> : list_caster, Type> {}; #endif template <> struct type_caster : list_caster {}; template struct qt_map_caster { using key_conv = make_caster; using value_conv = make_caster; bool load(handle src, bool convert) { if (!isinstance(src)) return false; auto d = reinterpret_borrow(src); value.clear(); for (auto it : d) { key_conv kconv; value_conv vconv; if (!kconv.load(it.first.ptr(), convert) || !vconv.load(it.second.ptr(), convert)) return false; value.insert(cast_op(std::move(kconv)), cast_op(std::move(vconv))); } return true; } template static handle cast(T &&src, return_value_policy policy, handle parent) { dict d; return_value_policy policy_key = policy; return_value_policy policy_value = policy; if (!std::is_lvalue_reference::value) { policy_key = return_value_policy_override::policy(policy_key); policy_value = return_value_policy_override::policy(policy_value); } for (auto it = src.begin(); it != src.end(); ++it ) { auto key = reinterpret_steal(key_conv::cast(forward_like(it.key()), policy_key, parent)); auto value = reinterpret_steal(value_conv::cast(forward_like(*it), policy_value, parent)); if (!key || !value) return handle(); d[key] = value; } return d.release(); } PYBIND11_TYPE_CASTER(Type, _("Dict[") + key_conv::name + _(", ") + value_conv::name + _("]")); }; template struct type_caster> : qt_map_caster, Key, Value> { }; template struct type_caster> : qt_map_caster, Key, Value> { }; #if QT_VERSION_MAJOR < 6 // In Qt 6 same as std::pair, already defined by pybind template struct type_caster> { using cast1 = make_caster; using cast2 = make_caster; using pair_t = QPair; PYBIND11_TYPE_CASTER(pair_t, _("QPair[") + cast1::name + _(",") + cast2::name + _("]")); bool load(handle src, bool convert) { if (!isinstance(src)) return false; auto d = reinterpret_borrow(src); if ( d.size() != 2 ) return false; cast1 conv1; cast2 conv2; if ( !conv1.load(d[0].ptr(), convert) || !conv2.load(d[1].ptr(), convert) ) return false; value.first = cast_op(std::move(conv1)); value.second = cast_op(std::move(conv2)); return true; } static handle cast(const pair_t& val, return_value_policy policy, handle parent) { tuple t(2); t[0] = reinterpret_steal(cast1::cast(val.first, policy, parent)); t[1] = reinterpret_steal(cast2::cast(val.second, policy, parent)); return t.release(); } }; #endif template <> struct type_caster { public: PYBIND11_TYPE_CASTER(QImage, _("QImage")); bool load(handle src, bool ic); static handle cast(QImage src, return_value_policy policy, handle parent); }; } // namespace pybind11::detail mlt-7.22.0/src/modules/glaxnimate/glaxnimate/external/QtAppSetup/src/app/widgets/000775 001750 001750 00000000000 14477652011 031463 5ustar00ddennedyddennedy000000 000000 mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/aep/000775 001750 001750 00000000000 14477652011 025451 5ustar00ddennedyddennedy000000 000000 mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/shapes/inflate_deflate.cpp000664 001750 001750 00000003207 14477652011 032474 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "inflate_deflate.hpp" GLAXNIMATE_OBJECT_IMPL(glaxnimate::model::InflateDeflate) QIcon glaxnimate::model::InflateDeflate::static_tree_icon() { return QIcon::fromTheme("zoom-draw"); } QString glaxnimate::model::InflateDeflate::static_type_name_human() { return tr("Inflate and Deflate"); } bool glaxnimate::model::InflateDeflate::process_collected() const { return false; } glaxnimate::math::bezier::MultiBezier glaxnimate::model::InflateDeflate::process(glaxnimate::model::FrameTime t, const math::bezier::MultiBezier& mbez) const { if ( mbez.empty() ) return {}; auto amount = this->amount.get_at(t); if ( amount == 0 ) return mbez; QPointF center; qreal count = 0; for ( const auto& bez : mbez.beziers() ) { for ( const auto& point : bez ) { center += point.pos; } count += bez.size(); } if ( count == 0 ) return mbez; center /= count; math::bezier::MultiBezier out; for ( const auto& in_bez : mbez.beziers() ) { math::bezier::Bezier out_bez; for ( const auto& point : in_bez ) { out_bez.points().push_back(math::bezier::Point( math::lerp(point.pos, center, amount), math::lerp(point.tan_in, center, -amount), math::lerp(point.tan_out, center, -amount) )); } if ( in_bez.closed() ) out_bez.close(); out.beziers().push_back(out_bez); } return out; } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/command/property_commands.hpp000664 001750 001750 00000005607 14477652011 032201 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include "command/base.hpp" #include "model/property/property.hpp" namespace glaxnimate::command { class SetPropertyValue : public MergeableCommand { public: SetPropertyValue(model::BaseProperty* prop, const QVariant& value, bool commit = true) : SetPropertyValue(prop, prop->value(), value, commit) {} SetPropertyValue(model::BaseProperty* prop, const QVariant& before, const QVariant& after, bool commit = true, const QString& name = {}) : Parent(name.isEmpty() ? QObject::tr("Update %1").arg(prop->name()) : name, commit), prop(prop), before(before), after(after) {} void undo() override { prop->set_value(before); } void redo() override { prop->set_value(after); } bool merge_with(const SetPropertyValue& other) { if ( other.prop != prop ) return false; after = other.after; return true; } private: model::BaseProperty* prop; QVariant before; QVariant after; }; class SetMultipleProperties : public MergeableCommand { public: template SetMultipleProperties( const QString& name, bool commit, const QVector& props, Args... vals ) : SetMultipleProperties(name, props, {}, {QVariant::fromValue(vals)...}, commit) {} /** * \pre props.size() == after.size() && (props.size() == before.size() || before.empty()) * * If before.empty() it will be populated by the properties */ SetMultipleProperties( const QString& name, const QVector& props, const QVariantList& before, const QVariantList& after, bool commit ) : Parent(name, commit), props(props), before(before), after(after) { if ( before.empty() ) for ( auto prop : props ) this->before.push_back(prop->value()); } void undo() override { for ( int i = 0; i < props.size(); i++ ) props[i]->set_value(before[i]); } void redo() override { for ( int i = 0; i < props.size(); i++ ) props[i]->set_value(after[i]); } bool merge_with(const SetMultipleProperties& other) { if ( other.props.size() != props.size() ) return false; for ( int i = 0; i < props.size(); i++ ) if ( props[i] != other.props[i] ) return false; after = other.after; return true; } private: QVector props; QVariantList before; QVariantList after; }; } // namespace glaxnimate::command mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/document.cpp000664 001750 001750 00000016410 14477652011 027721 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "document.hpp" #include #include "io/glaxnimate/glaxnimate_format.hpp" #include "model/assets/assets.hpp" #include "model/assets/pending_asset.hpp" class glaxnimate::model::Document::Private { public: using NameIndex = unsigned long long; Private(Document* doc) : assets(doc) { io_options.format = io::glaxnimate::GlaxnimateFormat::instance(); } std::pair name_index(const QString& name) const { static QRegularExpression detect_numbers("^(.*) ([0-9]+)$"); QRegularExpressionMatch match = detect_numbers.match(name); if ( match.hasMatch() ) { return {match.captured(1), match.captured(2).toULongLong()}; } return {name, 0}; } void increase(std::pair pair) { auto iter = name_indices.find(pair.first); if ( iter != name_indices.end() ) { if ( iter->second < pair.second ) iter->second = pair.second; } else { name_indices.emplace(std::move(pair)); } } void decrease(const std::pair& pair) { if ( pair.second == 0 ) return; auto iter = name_indices.find(pair.first); if ( iter != name_indices.end() ) { if ( iter->second == pair.second ) iter->second -= 1; } } QString name_suggestion(const QString& base_name) { auto index_pair = name_index(base_name); auto iter = name_indices.find(index_pair.first); if ( iter == name_indices.end() ) return base_name; return QString("%1 %2").arg(iter->first).arg(iter->second + 1); } int add_pending_asset(QUrl url, QByteArray data, const QString& name_alias) { int id = max_pending_id; ++max_pending_id; pending_assets[id] = {id, std::move(url), std::move(data), name_alias}; return id; } QUndoStack undo_stack; QVariantMap metadata; io::Options io_options; FrameTime current_time = 0; bool record_to_keyframe = false; Assets assets; glaxnimate::model::CompGraph comp_graph; std::unordered_map name_indices; std::map pending_assets; int max_pending_id = 0; DocumentInfo info; QUuid uuid; }; glaxnimate::model::Document::Document(const QString& filename) : d ( std::make_unique(this) ) { d->io_options.filename = filename; d->uuid = QUuid::createUuid(); } glaxnimate::model::Document::~Document() = default; QString glaxnimate::model::Document::filename() const { return d->io_options.filename; } QUuid glaxnimate::model::Document::uuid() const { return d->uuid; } QVariantMap & glaxnimate::model::Document::metadata() { return d->metadata; } QUndoStack & glaxnimate::model::Document::undo_stack() { return d->undo_stack; } const glaxnimate::io::Options & glaxnimate::model::Document::io_options() const { return d->io_options; } void glaxnimate::model::Document::set_io_options(const io::Options& opt) { bool em = opt.filename != d->io_options.filename; d->io_options = opt; if ( em ) emit filename_changed(d->io_options.filename); } glaxnimate::model::DocumentNode * glaxnimate::model::Document::find_by_uuid(const QUuid& n) const { return d->assets.docnode_find_by_uuid(n); } glaxnimate::model::DocumentNode * glaxnimate::model::Document::find_by_name(const QString& name) const { return d->assets.docnode_find_by_name(name); } QVariantList glaxnimate::model::Document::find_by_type_name(const QString& type_name) const { return d->assets.find_by_type_name(type_name); } bool glaxnimate::model::Document::redo() { if ( ! d->undo_stack.canRedo() ) return false; d->undo_stack.redo(); return true; } bool glaxnimate::model::Document::undo() { if ( ! d->undo_stack.canUndo() ) return false; d->undo_stack.undo(); return true; } glaxnimate::model::FrameTime glaxnimate::model::Document::current_time() const { return d->current_time; } void glaxnimate::model::Document::set_current_time(glaxnimate::model::FrameTime t) { d->assets.set_time(t); emit current_time_changed(d->current_time = t); } bool glaxnimate::model::Document::record_to_keyframe() const { return d->record_to_keyframe; } void glaxnimate::model::Document::set_record_to_keyframe(bool r) { emit(record_to_keyframe_changed(d->record_to_keyframe = r)); } void glaxnimate::model::Document::push_command(QUndoCommand* cmd) { d->undo_stack.push(cmd); } QString glaxnimate::model::Document::get_best_name(glaxnimate::model::DocumentNode* node, const QString& suggestion) const { if ( !node ) return {}; if ( suggestion.isEmpty() ) return d->name_suggestion(node->type_name_human()); return d->name_suggestion(suggestion); } void glaxnimate::model::Document::set_best_name(glaxnimate::model::DocumentNode* node, const QString& suggestion) const { if ( node ) node->name.set(get_best_name(node, suggestion)); } glaxnimate::model::Assets * glaxnimate::model::Document::assets() const { return &d->assets; } glaxnimate::model::Object * glaxnimate::model::Document::assets_obj() const { return assets(); } void glaxnimate::model::Document::set_metadata(const QVariantMap& meta) { d->metadata = meta; } glaxnimate::model::CompGraph & glaxnimate::model::Document::comp_graph() { return d->comp_graph; } void glaxnimate::model::Document::decrease_node_name(const QString& old_name) { if ( !old_name.isEmpty() ) d->decrease(d->name_index(old_name)); } void glaxnimate::model::Document::increase_node_name(const QString& new_name) { if ( !new_name.isEmpty() ) d->increase(d->name_index(new_name)); } void glaxnimate::model::Document::stretch_time(qreal multiplier) { qreal time = d->current_time; d->assets.stretch_time(multiplier); set_current_time(qRound(time * multiplier)); } int glaxnimate::model::Document::add_pending_asset(const QString& name, const QByteArray& data) { return d->add_pending_asset({}, data, name); } int glaxnimate::model::Document::add_pending_asset(const QString& name, const QUrl& url) { return d->add_pending_asset(url, {}, name); } int glaxnimate::model::Document::add_pending_asset(const PendingAsset& ass) { return d->add_pending_asset(ass.url, ass.data, ass.name_alias); } void glaxnimate::model::Document::mark_asset_loaded(int id) { auto it = d->pending_assets.find(id); if ( it != d->pending_assets.end() ) it->second.loaded = true; } std::vector glaxnimate::model::Document::pending_assets() { std::vector assets; assets.reserve(d->pending_assets.size()); for ( const auto& ass : d->pending_assets ) assets.push_back(ass.second); return assets; } void glaxnimate::model::Document::clear_pending_assets() { for ( auto& ass : d->pending_assets ) ass.second.loaded = true; } glaxnimate::model::Document::DocumentInfo & glaxnimate::model::Document::info() { return d->info; } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/external/QtAppSetup/src/app/debug/000775 001750 001750 00000000000 14477652011 031103 5ustar00ddennedyddennedy000000 000000 mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/utils/pseudo_mutex.hpp000664 001750 001750 00000001366 14477652011 030675 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include namespace glaxnimate::utils { class PseudoMutex { public: bool try_lock() noexcept { if ( locked ) return false; locked = true; return true; } void lock() noexcept { locked = true; } void unlock() noexcept { locked = false; } explicit operator bool() const noexcept { return locked; } std::unique_lock get_lock() { return std::unique_lock(*this, std::try_to_lock); } private: bool locked = false; }; } // namespace glaxnimate::utils mlt-7.22.0/src/modules/glaxnimate/glaxnimate/external/QtAppSetup/src/app/settings/setting.hpp000664 001750 001750 00000007710 14477652011 034050 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include #include #include #include "app/utils/translated_string.hpp" namespace app::settings { struct Setting { enum Type { Internal, Info, Bool, Int, Float, String, Color, }; Setting(QString slug, utils::TranslatedString label, utils::TranslatedString description) : type(Info), slug(std::move(slug)), label(std::move(label)), description(std::move(description)) {} Setting(QString slug, utils::TranslatedString label, utils::TranslatedString description, bool default_value) : type(Bool), slug(std::move(slug)), label(std::move(label)), description(std::move(description)), default_value(default_value) {} Setting(QString slug, utils::TranslatedString label, utils::TranslatedString description, int default_value, int min, int max) : type(Int), slug(std::move(slug)), label(std::move(label)), description(std::move(description)), default_value(default_value), min(min), max(max) {} Setting(QString slug, utils::TranslatedString label, utils::TranslatedString description, float default_value, float min, float max) : type(Float), slug(std::move(slug)), label(std::move(label)), description(std::move(description)), default_value(default_value), min(min), max(max) {} Setting(QString slug, utils::TranslatedString label, utils::TranslatedString description, const QString& default_value) : type(String), slug(std::move(slug)), label(std::move(label)), description(std::move(description)), default_value(default_value) {} Setting(QString slug, utils::TranslatedString label, utils::TranslatedString description, Type type, QVariant default_value, QVariantMap choices = {}, std::function side_effects = {} ) : type(type), slug(std::move(slug)), label(std::move(label)), description(std::move(description)), default_value(std::move(default_value)), choices(std::move(choices)), side_effects(std::move(side_effects)) {} Setting(QString slug, utils::TranslatedString label, utils::TranslatedString description, const QColor& default_value) : type(Color), slug(std::move(slug)), label(std::move(label)), description(std::move(description)), default_value(QVariant::fromValue(default_value)) {} QVariant get_variant(const QVariantMap& map) const { auto it = map.find(slug); if ( it != map.end() && valid_variant(*it) ) return *it; return default_value; } template CastType get(const QVariantMap& map) const { return get_variant(map).value(); } bool valid_variant(const QVariant& v) const { switch ( type ) { case Info: case Internal: return true; case Bool: return v.canConvert(); case Int: return v.canConvert(); case Float: return v.canConvert(); case String: return v.canConvert(); case Color: return v.canConvert(); default: return false; } } Type type; QString slug; utils::TranslatedString label; utils::TranslatedString description; QVariant default_value; float min = -1; float max = -1; QVariantMap choices; std::function side_effects; }; using SettingList = std::vector; } // namespace app::settings mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/glaxnimate/glaxnimate_mime.cpp000664 001750 001750 00000010267 14477652011 032707 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "glaxnimate_mime.hpp" #include #include "import_state.hpp" #include "model/shapes/shape.hpp" #include "model/assets/assets.hpp" #include "model/visitor.hpp" #include "app/log/log.hpp" using namespace glaxnimate; io::Autoreg io::glaxnimate::GlaxnimateMime::autoreg; namespace { class GetDeps : public model::Visitor { public: GetDeps(const std::vector& objects) : skip(objects.begin(), objects.end()) {} void on_visit(model::DocumentNode * node) override { for ( auto property : node->properties() ) { if ( property->traits().type == model::PropertyTraits::ObjectReference && property->name() != "parent" ) { auto ptr = static_cast(property)->get_ref(); if ( !ptr || skip.count(ptr)) continue; skip.insert(ptr); referenced[ptr->uuid.get().toString()] = ptr; on_visit(ptr); } } } std::set skip; std::map referenced; }; } // namespace QStringList io::glaxnimate::GlaxnimateMime::mime_types() const { return {"application/vnd.glaxnimate.rawr+json"}; } QJsonDocument io::glaxnimate::GlaxnimateMime::serialize_json(const std::vector& objects) { QJsonArray arr; GetDeps gd(objects); for ( auto object : objects ) { gd.visit(object); arr.push_back(GlaxnimateFormat::to_json(object)); } for ( const auto& p: gd.referenced ) arr.push_front(GlaxnimateFormat::to_json(p.second)); return QJsonDocument(arr); } QByteArray io::glaxnimate::GlaxnimateMime::serialize(const std::vector& objects) const { return serialize_json(objects).toJson(QJsonDocument::Compact); } io::mime::DeserializedData io::glaxnimate::GlaxnimateMime::deserialize(const QByteArray& data) const { QJsonDocument jdoc; try { jdoc = QJsonDocument::fromJson(data); } catch ( const QJsonParseError& err ) { message(GlaxnimateFormat::tr("Could not parse JSON: %1").arg(err.errorString())); return {}; } if ( !jdoc.isArray() ) { message(GlaxnimateFormat::tr("No JSON object found")); return {}; } QJsonArray input_objects = jdoc.array(); io::mime::DeserializedData output; output.initialize_data(); detail::ImportState state(nullptr, output.document.get()); for ( auto json_val : input_objects ) { if ( !json_val.isObject() ) continue; QJsonObject json_object = json_val.toObject(); auto obj = model::Factory::instance().build(json_object["__type__"].toString(), output.document.get()); if ( !obj ) continue; if ( auto shape = qobject_cast(obj) ) { output.main->shapes.emplace(shape); } else if ( auto composition = qobject_cast(obj) ) { output.main->assign_from(composition); delete composition; } else if ( auto color = qobject_cast(obj) ) { output.document->assets()->colors->values.emplace(color); } else if ( auto bitmap = qobject_cast(obj) ) { output.document->assets()->images->values.emplace(bitmap); } else if ( auto gradient = qobject_cast(obj) ) { output.document->assets()->gradients->values.emplace(gradient); } else if ( auto gradient_colors = qobject_cast(obj) ) { output.document->assets()->gradient_colors->values.emplace(gradient_colors); } else { app::log::Log("I/O").stream() << "Could not deserialize " << obj->type_name(); delete obj; continue; } state.load_object(obj, json_object); } state.resolve(); return output; } src/modules/glaxnimate/glaxnimate/external/QtAppSetup/src/app/settings/keyboard_shortcuts_model.hpp000664 001750 001750 00000003725 14477652011 037414 0ustar00ddennedyddennedy000000 000000 mlt-7.22.0/* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include #include #include "keyboard_shortcuts.hpp" namespace app::settings { class KeyboardShortcutsModel : public QAbstractItemModel { Q_OBJECT public: enum { DefaultKeyRole = Qt::UserRole }; KeyboardShortcutsModel(ShortcutSettings* settings, QObject* parent = nullptr); QVariant headerData(int section, Qt::Orientation orientation, int role) const override; int columnCount(const QModelIndex & parent) const override; int rowCount(const QModelIndex & parent) const override; Qt::ItemFlags flags(const QModelIndex & index) const override; QVariant data(const QModelIndex & index, int role) const override; bool setData(const QModelIndex & index, const QVariant & value, int role) override; QModelIndex index(int row, int column, const QModelIndex & parent) const override; QModelIndex parent(const QModelIndex & child) const override; ShortcutAction* action(const QModelIndex & index) const; public slots: void begin_change_data(); void end_change_data(); private: ShortcutSettings* settings; }; class KeyboardShortcutsFilterModel : public QSortFilterProxyModel { public: using QSortFilterProxyModel::QSortFilterProxyModel; bool filterAcceptsRow(int source_row, const QModelIndex & source_parent) const override; }; class KeyboardShortcutsDelegate : public QStyledItemDelegate { public: using QStyledItemDelegate::QStyledItemDelegate; QWidget * createEditor(QWidget * parent, const QStyleOptionViewItem & option, const QModelIndex & index) const override; void setEditorData(QWidget * editor, const QModelIndex & index) const override; void setModelData(QWidget * editor, QAbstractItemModel * model, const QModelIndex & index) const override; }; } // namespace app::settings mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/lottie/lottie_private_common.hpp000664 001750 001750 00000024677 14477652011 033337 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include "app/log/log.hpp" #include "model/shapes/group.hpp" #include "model/shapes/layer.hpp" #include "model/shapes/precomp_layer.hpp" #include "model/shapes/rect.hpp" #include "model/shapes/ellipse.hpp" #include "model/shapes/path.hpp" #include "model/shapes/polystar.hpp" #include "model/shapes/fill.hpp" #include "model/shapes/stroke.hpp" #include "model/shapes/image.hpp" #include "model/shapes/text.hpp" #include "model/shapes/repeater.hpp" #include "lottie_format.hpp" namespace glaxnimate::io::lottie::detail { class ValueTransform { public: virtual ~ValueTransform() {} virtual QVariant to_lottie(const QVariant& v, model::FrameTime) const = 0; virtual QVariant from_lottie(const QVariant& v, model::FrameTime) const = 0; }; class FloatMult : public ValueTransform { public: explicit FloatMult(float factor) : factor(factor) {} QVariant to_lottie(const QVariant& v, model::FrameTime) const override { return v.toFloat() * factor; } QVariant from_lottie(const QVariant& v, model::FrameTime) const override { return v.toFloat() / factor; } private: float factor; }; class EnumMap : public ValueTransform { public: EnumMap(QMap values) : values(std::move(values)) {} QVariant to_lottie(const QVariant& v, model::FrameTime) const override { return values[v.toInt()]; } QVariant from_lottie(const QVariant& v, model::FrameTime) const override { return values.key(v.toInt()); } QMap values; }; class IntBool : public ValueTransform { public: QVariant to_lottie(const QVariant& v, model::FrameTime) const override { return int(v.toBool()); } QVariant from_lottie(const QVariant& v, model::FrameTime) const override { return bool(v.toInt()); } private: float factor; }; class GradientLoad : public ValueTransform { public: GradientLoad(int count) : count(count) {} QVariant to_lottie(const QVariant&, model::FrameTime) const override { return {}; } QVariant from_lottie(const QVariant& v, model::FrameTime) const override { auto vlist = v.toList(); if ( vlist.size() < count * 4 ) return {}; QGradientStops s; s.reserve(count); bool alpha = vlist.size() >= count * 6; for ( int i = 0; i < count; i++ ) { s.push_back({ vlist[i*4].toDouble(), QColor::fromRgbF( vlist[i*4+1].toDouble(), vlist[i*4+2].toDouble(), vlist[i*4+3].toDouble(), alpha ? vlist[count*4+2*i+1].toDouble() : 1 ) }); } return QVariant::fromValue(s); } int count = 0; }; class TransformFunc { public: template>> TransformFunc(const T& t) : trans(std::make_shared(t)) {} TransformFunc() {} TransformFunc(TransformFunc&&) = default; TransformFunc(const TransformFunc&) = default; QVariant to_lottie(const QVariant& v, model::FrameTime t) const { if ( !trans ) return v; return trans->to_lottie(v, t); } QVariant from_lottie(const QVariant& v, model::FrameTime t) const { if ( !trans ) return v; return trans->from_lottie(v, t); } private: std::shared_ptr trans; }; enum FieldMode { Auto, AnimatedToStatic, Ignored, Custom }; struct FieldInfo { QString name; QString lottie; bool essential; FieldMode mode; TransformFunc transform; FieldInfo(const char* lottie, const char* name, TransformFunc transform = {}, bool essential = true) : name(name), lottie(lottie), essential(essential), mode(Auto), transform(std::move(transform)) {} FieldInfo(const char* lottie, FieldMode mode = Ignored) : lottie(lottie), essential(false), mode(mode) {} FieldInfo(const char* lottie, const char* name, FieldMode mode, bool essential = true) : name(name), lottie(lottie), essential(essential), mode(mode) {} }; // static mapping data const QMap> fields = { {"DocumentNode", { FieldInfo{"nm", "name", {}, false}, FieldInfo{"mn", "uuid", {}, false}, }}, {"Composition", { FieldInfo("layers", Custom), FieldInfo("id", Custom), FieldInfo{"op", Custom}, FieldInfo{"ip", Custom}, FieldInfo("v", Custom), FieldInfo{"fr", "fps"}, FieldInfo{"w", "width"}, FieldInfo{"h", "height"}, FieldInfo("ddd"), FieldInfo("assets"), FieldInfo("comps"), FieldInfo("fonts"), FieldInfo("chars"), FieldInfo("markers"), FieldInfo("motion_blur"), FieldInfo("tgs"), FieldInfo("meta", Custom), FieldInfo("props"), FieldInfo("slots"), }}, // Layer is converted explicitly {"__Layer__", { FieldInfo{"op", Custom}, FieldInfo{"ip", Custom}, FieldInfo("ddd"), FieldInfo("hd", Custom), FieldInfo("ty", Custom), FieldInfo("parent", Custom), FieldInfo("sr"), FieldInfo("ks", Custom), FieldInfo("ao", "auto_orient", IntBool()), FieldInfo{"st", Custom}, FieldInfo("bm"), FieldInfo("tt"), FieldInfo("td"), FieldInfo("ind", Custom), FieldInfo("cl"), FieldInfo("ln"), FieldInfo("hasMasks", Custom), FieldInfo("masksProperties", Custom), FieldInfo("ef"), FieldInfo("bounds"), // old, no longer there FieldInfo("ct"), }}, {"Transform", { FieldInfo{"a", "anchor_point"}, FieldInfo("px", Custom), FieldInfo("py", Custom), FieldInfo("pz", Custom), FieldInfo{"p", "position"}, FieldInfo{"s", "scale"}, FieldInfo{"r", "rotation"}, FieldInfo("rx", Custom), FieldInfo("ry", Custom), FieldInfo("rz", Custom), FieldInfo("o"), FieldInfo("sk"), FieldInfo("sa"), FieldInfo("nm"), }}, {"ShapeElement", { FieldInfo{"ty", Custom}, FieldInfo{"ix"}, FieldInfo{"cix"}, FieldInfo{"bm"}, FieldInfo{"hd", Custom}, }}, {"Shape", { FieldInfo{"d", "reversed", EnumMap{{ {1, 3}, {0, 1}, }}}, FieldInfo{"closed", Custom}, // Old attribute }}, {"Rect", { FieldInfo{"p", "position"}, FieldInfo{"s", "size"}, FieldInfo{"r", "rounded"}, }}, {"Ellipse", { FieldInfo{"p", "position"}, FieldInfo{"s", "size"}, }}, {"Path", { FieldInfo{"ks", "shape"}, FieldInfo{"ind"}, }}, {"PolyStar", { FieldInfo{"p", "position"}, FieldInfo{"or", "outer_radius"}, FieldInfo{"ir", "inner_radius"}, FieldInfo{"is", "inner_roundness", FloatMult(100)}, FieldInfo{"os", "outer_roundness", FloatMult(100)}, FieldInfo{"r", "angle"}, FieldInfo{"pt", "points"}, FieldInfo{"sy", "type"}, }}, {"Group", { FieldInfo{"np"}, FieldInfo{"it", Custom}, }}, {"Styler", { FieldInfo{"o", "opacity", FloatMult(100)}, FieldInfo{"c", Custom}, FieldInfo{"fillEnabled", Custom}, }}, {"Fill", { FieldInfo{"r", "fill_rule", EnumMap{{ {model::Fill::NonZero, 1}, {model::Fill::EvenOdd, 2}, }}}, }}, {"Stroke", { FieldInfo{"lc", "cap", EnumMap{{ {model::Stroke::ButtCap, 1}, {model::Stroke::RoundCap, 2}, {model::Stroke::SquareCap, 3}, }}}, FieldInfo{"lj", "join", EnumMap{{ {model::Stroke::MiterJoin, 1}, {model::Stroke::RoundJoin, 2}, {model::Stroke::BevelJoin, 3}, }}}, FieldInfo{"ml", "miter_limit"}, FieldInfo{"w", "width"}, FieldInfo{"d"}, }}, {"Bitmap", { FieldInfo{"h", "height"}, FieldInfo{"w", "width"}, FieldInfo{"id", Custom}, FieldInfo{"p", Custom}, FieldInfo{"u", Custom}, FieldInfo{"e", Custom}, }}, {"Gradient", { FieldInfo{"s", "start_point"}, FieldInfo{"e", "end_point"}, FieldInfo{"t", "type"}, FieldInfo{"h", Custom}, /// \todo FieldInfo{"a", Custom}, /// \todo FieldInfo{"g", Custom}, }}, {"PreCompLayer", { FieldInfo{"refId", Custom}, FieldInfo{"w", Custom}, FieldInfo{"h", Custom}, FieldInfo{"tm"}, }}, {"Repeater", { FieldInfo{"c", "copies"}, FieldInfo{"o"}, FieldInfo{"m"}, FieldInfo{"tr", Custom}, }}, {"Trim", { FieldInfo{"s", "start", FloatMult(100)}, FieldInfo{"e", "end", FloatMult(100)}, FieldInfo{"o", "offset", FloatMult(360)}, FieldInfo{"m", "multiple"}, }}, {"InflateDeflate", { FieldInfo{"a", "amount", FloatMult(100)}, }}, {"RoundCorners", { FieldInfo{"r", "radius"}, }}, {"OffsetPath", { FieldInfo{"a", "amount"}, FieldInfo{"lj", "join", EnumMap{{ {model::Stroke::MiterJoin, 1}, {model::Stroke::RoundJoin, 2}, {model::Stroke::BevelJoin, 3}, }}}, FieldInfo{"ml", "miter_limit"}, }}, {"ZigZag", { FieldInfo{"s", "amplitude"}, FieldInfo{"r", "frequency"}, FieldInfo{"pt", "style", AnimatedToStatic}, }}, }; const QMap shape_types = { {"Rect", "rc"}, {"PolyStar", "sr"}, {"Ellipse", "el"}, {"Path", "sh"}, {"Group", "gr"}, {"Layer", "gr"}, {"Fill", "fl"}, {"Stroke", "st"}, // "gf" (Gradient Fill) and "gs" (Gradient Stroke) are handled by fill/stroke // "tr" is not a shape but a property of groups // "mm" (Merge), "tw" (Twist), are not supported by lottie {"Trim", "tm"}, {"Repeater", "rp"}, {"RoundCorners", "rd"}, {"InflateDeflate", "pb"}, {"OffsetPath", "op"}, {"ZigZag", "zz"}, }; const QMap shape_types_repeat = { {"gf", "Fill"}, {"gs", "Stroke"}, }; const QMap unsupported_layers = { {6, "Audio"}, {7, "Pholder Video"}, {8, "Image Sequence"}, {9, "Video"}, }; } // namespace glaxnimate::io::lottie::detail mlt-7.22.0/src/modules/glaxnimate/glaxnimate/external/QtAppSetup/src/app/utils/string_view.hpp000664 001750 001750 00000002361 14477652011 034230 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include #if QT_VERSION > QT_VERSION_CHECK(5, 15, 2) #include namespace utils { inline QStringView left_ref(const QString& s, int n) { return QStringView{s}.left(n); } inline QStringView right_ref(const QString& s, int n) { return QStringView{s}.right(n); } inline QStringView mid_ref(const QString& s, int a, int b) { return QStringView{s}.mid(a, b); } template inline auto split_ref(const QString& s, Args&&... args) { return QStringView{s}.split(std::forward(args)...); } using StringView = QStringView; } // namespace utils #else #include namespace utils { inline QStringRef left_ref(const QString& s, int n) { return s.leftRef(n); } inline QStringRef right_ref(const QString& s, int n) { return s.rightRef(n); } inline QStringRef mid_ref(const QString& s, int a, int b) { return s.midRef(a, b); } template inline auto split_ref(const QString& s, Args&&... args) { return s.splitRef(std::forward(args)...); } using StringView = QStringRef; } // namespace utils #endif mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/assets/bitmap.hpp000664 001750 001750 00000003707 14477652011 030673 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include #include #include #include "model/assets/asset.hpp" namespace glaxnimate::model { class Bitmap : public Asset { GLAXNIMATE_OBJECT(Bitmap) GLAXNIMATE_PROPERTY(QByteArray, data, {}, &Bitmap::on_refresh) GLAXNIMATE_PROPERTY(QString, filename, {}, &Bitmap::on_refresh) GLAXNIMATE_PROPERTY(QString, url, {}, &Bitmap::on_refresh) GLAXNIMATE_PROPERTY_RO(QString, format, {}) GLAXNIMATE_PROPERTY_RO(int, width, -1) GLAXNIMATE_PROPERTY_RO(int, height, -1) Q_PROPERTY(bool embedded READ embedded WRITE embed) Q_PROPERTY(QImage image READ get_image) public: using Asset::Asset; void paint(QPainter* painter) const; bool embedded() const; QIcon instance_icon() const override; QString type_name_human() const override { return tr("Bitmap"); } bool from_url(const QUrl& url); bool from_file(const QString& file); bool from_base64(const QString& data); bool from_raw_data(const QByteArray& data); QUrl to_url() const; QString object_name() const override; QFileInfo file_info() const; const QPixmap& pixmap() const { return image; } void set_pixmap(const QImage& qimage, const QString& format); bool remove_if_unused(bool clean_lists) override; QImage get_image() const { return image.toImage(); } /** * \brief If `embedded()` returns `data`, otherwise tries to load the data based on filename */ QByteArray image_data() const; QSize size() const; public slots: void refresh(bool rebuild_embedded); void embed(bool embedded); private: QByteArray build_embedded(const QImage& img) const; private slots: void on_refresh(); signals: void loaded(); private: QPixmap image; }; } // namespace glaxnimate::model mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/assets/gradient.hpp000664 001750 001750 00000005044 14477652011 031210 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include "model/assets/brush_style.hpp" #include "model/animation/animatable.hpp" #include "math/vector.hpp" namespace glaxnimate::math { template<> QGradientStops lerp(const QGradientStops& a, const QGradientStops& b, double factor); } // namespace glaxnimate::math namespace glaxnimate::model { namespace detail { template<> std::optional variant_cast(const QVariant& val); } // namespace detail class GradientColors : public Asset { GLAXNIMATE_OBJECT(GradientColors) GLAXNIMATE_ANIMATABLE(QGradientStops, colors, {}, &GradientColors::colors_changed) public: using Asset::Asset; QIcon instance_icon() const override; QString type_name_human() const override; bool remove_if_unused(bool clean_lists) override; Q_INVOKABLE void split_segment(int segment_index, float factor = 0.5, const QColor& new_color = {}); Q_INVOKABLE void remove_stop(int index); signals: void colors_changed(const QGradientStops&); }; class Gradient : public BrushStyle { GLAXNIMATE_OBJECT(Gradient) public: enum GradientType { Linear = 1, Radial = 2, Conical = 3 }; Q_ENUM(GradientType) GLAXNIMATE_PROPERTY_REFERENCE(GradientColors, colors, &Gradient::valid_refs, &Gradient::is_valid_ref, &Gradient::on_ref_changed) GLAXNIMATE_PROPERTY(GradientType, type, Linear, {}, {}, PropertyTraits::Visual) GLAXNIMATE_ANIMATABLE(QPointF, start_point, {}) GLAXNIMATE_ANIMATABLE(QPointF, end_point, {}) GLAXNIMATE_ANIMATABLE(QPointF, highlight, {}) public: using BrushStyle::BrushStyle; QString type_name_human() const override; QBrush brush_style(FrameTime t) const override; QBrush constrained_brush_style(FrameTime t, const QRectF& bounds) const override; Q_INVOKABLE qreal radius(double t) const; static QString gradient_type_name(GradientType t); bool remove_if_unused(bool clean_lists) override; private: std::vector valid_refs() const; bool is_valid_ref(DocumentNode* node) const; void on_ref_changed(GradientColors* new_ref, GradientColors* old_ref); void on_ref_visual_changed(); void fill_icon(QPixmap& icon) const override; void on_property_changed(const BaseProperty* prop, const QVariant& value) override; signals: void colors_changed_from(GradientColors* old_use, GradientColors* new_use); }; } // namespace glaxnimate::model src/modules/glaxnimate/glaxnimate/external/QtAppSetup/src/app/widgets/keyboard_settings_widget.hpp000664 001750 001750 00000001312 14477652011 037175 0ustar00ddennedyddennedy000000 000000 mlt-7.22.0/* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #ifndef KEYBOARDSETTINGSWIDGET_H #define KEYBOARDSETTINGSWIDGET_H #include #include #include "app/settings/keyboard_shortcuts.hpp" class KeyboardSettingsWidget : public QWidget { Q_OBJECT public: KeyboardSettingsWidget(app::settings::ShortcutSettings* settings, QWidget* parent = nullptr); ~KeyboardSettingsWidget(); protected: void changeEvent ( QEvent* e ) override; private slots: void clear_filter(); void filter(const QString& text); private: class Private; std::unique_ptr d; }; #endif // KEYBOARDSETTINGSWIDGET_H mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/rive/type_def.cpp000664 001750 001750 00000072312 14477652011 030161 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ /** * NOTE: This file is generated automatically, do not edit manually * To generate this file run * ./external/rive_typedef.py -t source >src/core/io/rive/type_def.cpp */ #include "type_def.hpp" using namespace glaxnimate::io::rive; std::unordered_map glaxnimate::io::rive::defined_objects = { { TypeId::Artboard, { "Artboard", TypeId::Artboard, TypeId::WorldTransformComponent, { {"clip", 196, PropertyType::Bool}, {"width", 7, PropertyType::Float}, {"height", 8, PropertyType::Float}, {"x", 9, PropertyType::Float}, {"y", 10, PropertyType::Float}, {"originX", 11, PropertyType::Float}, {"originY", 12, PropertyType::Float}, {"defaultStateMachineId", 236, PropertyType::VarUint}, } } }, { TypeId::Node, { "Node", TypeId::Node, TypeId::TransformComponent, { {"x", 13, PropertyType::Float}, {"y", 14, PropertyType::Float}, } } }, { TypeId::Shape, { "Shape", TypeId::Shape, TypeId::Drawable, { } } }, { TypeId::Ellipse, { "Ellipse", TypeId::Ellipse, TypeId::ParametricPath, { } } }, { TypeId::StraightVertex, { "StraightVertex", TypeId::StraightVertex, TypeId::PathVertex, { {"radius", 26, PropertyType::Float}, } } }, { TypeId::CubicDetachedVertex, { "CubicDetachedVertex", TypeId::CubicDetachedVertex, TypeId::CubicVertex, { {"inRotation", 84, PropertyType::Float}, {"inDistance", 85, PropertyType::Float}, {"outRotation", 86, PropertyType::Float}, {"outDistance", 87, PropertyType::Float}, } } }, { TypeId::Rectangle, { "Rectangle", TypeId::Rectangle, TypeId::ParametricPath, { {"linkCornerRadius", 164, PropertyType::Bool}, {"cornerRadiusTL", 31, PropertyType::Float}, {"cornerRadiusTR", 161, PropertyType::Float}, {"cornerRadiusBL", 162, PropertyType::Float}, {"cornerRadiusBR", 163, PropertyType::Float}, } } }, { TypeId::Triangle, { "Triangle", TypeId::Triangle, TypeId::ParametricPath, { } } }, { TypeId::Component, { "Component", TypeId::Component, TypeId::NoType, { {"name", 4, PropertyType::String}, {"parentId", 5, PropertyType::VarUint}, } } }, { TypeId::ContainerComponent, { "ContainerComponent", TypeId::ContainerComponent, TypeId::Component, { } } }, { TypeId::Path, { "Path", TypeId::Path, TypeId::Node, { {"pathFlags", 128, PropertyType::VarUint}, } } }, { TypeId::Drawable, { "Drawable", TypeId::Drawable, TypeId::Node, { {"blendModeValue", 23, PropertyType::VarUint}, {"drawableFlags", 129, PropertyType::VarUint}, } } }, { TypeId::PathVertex, { "PathVertex", TypeId::PathVertex, TypeId::Vertex, { } } }, { TypeId::ParametricPath, { "ParametricPath", TypeId::ParametricPath, TypeId::Path, { {"width", 20, PropertyType::Float}, {"height", 21, PropertyType::Float}, {"originX", 123, PropertyType::Float}, {"originY", 124, PropertyType::Float}, } } }, { TypeId::PointsPath, { "PointsPath", TypeId::PointsPath, TypeId::Path, { {"isClosed", 32, PropertyType::Bool}, } } }, { TypeId::RadialGradient, { "RadialGradient", TypeId::RadialGradient, TypeId::LinearGradient, { } } }, { TypeId::SolidColor, { "SolidColor", TypeId::SolidColor, TypeId::Component, { {"colorValue", 37, PropertyType::Color}, } } }, { TypeId::GradientStop, { "GradientStop", TypeId::GradientStop, TypeId::Component, { {"colorValue", 38, PropertyType::Color}, {"position", 39, PropertyType::Float}, } } }, { TypeId::Fill, { "Fill", TypeId::Fill, TypeId::ShapePaint, { {"fillRule", 40, PropertyType::VarUint}, } } }, { TypeId::ShapePaint, { "ShapePaint", TypeId::ShapePaint, TypeId::ContainerComponent, { {"isVisible", 41, PropertyType::Bool}, } } }, { TypeId::LinearGradient, { "LinearGradient", TypeId::LinearGradient, TypeId::ContainerComponent, { {"startX", 42, PropertyType::Float}, {"startY", 33, PropertyType::Float}, {"endX", 34, PropertyType::Float}, {"endY", 35, PropertyType::Float}, {"opacity", 46, PropertyType::Float}, } } }, { TypeId::Backboard, { "Backboard", TypeId::Backboard, TypeId::NoType, { } } }, { TypeId::Stroke, { "Stroke", TypeId::Stroke, TypeId::ShapePaint, { {"thickness", 47, PropertyType::Float}, {"cap", 48, PropertyType::VarUint}, {"join", 49, PropertyType::VarUint}, {"transformAffectsStroke", 50, PropertyType::Bool}, } } }, { TypeId::KeyedObject, { "KeyedObject", TypeId::KeyedObject, TypeId::NoType, { {"objectId", 51, PropertyType::VarUint}, } } }, { TypeId::KeyedProperty, { "KeyedProperty", TypeId::KeyedProperty, TypeId::NoType, { {"propertyKey", 53, PropertyType::VarUint}, } } }, { TypeId::Animation, { "Animation", TypeId::Animation, TypeId::NoType, { {"name", 55, PropertyType::String}, } } }, { TypeId::CubicInterpolator, { "CubicInterpolator", TypeId::CubicInterpolator, TypeId::NoType, { {"x1", 63, PropertyType::Float}, {"y1", 64, PropertyType::Float}, {"x2", 65, PropertyType::Float}, {"y2", 66, PropertyType::Float}, } } }, { TypeId::KeyFrame, { "KeyFrame", TypeId::KeyFrame, TypeId::NoType, { {"frame", 67, PropertyType::VarUint}, {"interpolationType", 68, PropertyType::VarUint}, {"interpolatorId", 69, PropertyType::VarUint}, } } }, { TypeId::KeyFrameDouble, { "KeyFrameDouble", TypeId::KeyFrameDouble, TypeId::KeyFrame, { {"value", 70, PropertyType::Float}, } } }, { TypeId::LinearAnimation, { "LinearAnimation", TypeId::LinearAnimation, TypeId::Animation, { {"fps", 56, PropertyType::VarUint}, {"duration", 57, PropertyType::VarUint}, {"speed", 58, PropertyType::Float}, {"loopValue", 59, PropertyType::VarUint}, {"workStart", 60, PropertyType::VarUint}, {"workEnd", 61, PropertyType::VarUint}, {"enableWorkArea", 62, PropertyType::Bool}, } } }, { TypeId::CubicAsymmetricVertex, { "CubicAsymmetricVertex", TypeId::CubicAsymmetricVertex, TypeId::CubicVertex, { {"rotation", 79, PropertyType::Float}, {"inDistance", 80, PropertyType::Float}, {"outDistance", 81, PropertyType::Float}, } } }, { TypeId::CubicMirroredVertex, { "CubicMirroredVertex", TypeId::CubicMirroredVertex, TypeId::CubicVertex, { {"rotation", 82, PropertyType::Float}, {"distance", 83, PropertyType::Float}, } } }, { TypeId::CubicVertex, { "CubicVertex", TypeId::CubicVertex, TypeId::PathVertex, { } } }, { TypeId::KeyFrameColor, { "KeyFrameColor", TypeId::KeyFrameColor, TypeId::KeyFrame, { {"value", 88, PropertyType::Color}, } } }, { TypeId::TransformComponent, { "TransformComponent", TypeId::TransformComponent, TypeId::WorldTransformComponent, { {"rotation", 15, PropertyType::Float}, {"scaleX", 16, PropertyType::Float}, {"scaleY", 17, PropertyType::Float}, } } }, { TypeId::SkeletalComponent, { "SkeletalComponent", TypeId::SkeletalComponent, TypeId::TransformComponent, { } } }, { TypeId::Bone, { "Bone", TypeId::Bone, TypeId::SkeletalComponent, { {"length", 89, PropertyType::Float}, } } }, { TypeId::RootBone, { "RootBone", TypeId::RootBone, TypeId::Bone, { {"x", 90, PropertyType::Float}, {"y", 91, PropertyType::Float}, } } }, { TypeId::ClippingShape, { "ClippingShape", TypeId::ClippingShape, TypeId::Component, { {"sourceId", 92, PropertyType::VarUint}, {"fillRule", 93, PropertyType::VarUint}, {"isVisible", 94, PropertyType::Bool}, } } }, { TypeId::Skin, { "Skin", TypeId::Skin, TypeId::ContainerComponent, { {"xx", 104, PropertyType::Float}, {"yx", 105, PropertyType::Float}, {"xy", 106, PropertyType::Float}, {"yy", 107, PropertyType::Float}, {"tx", 108, PropertyType::Float}, {"ty", 109, PropertyType::Float}, } } }, { TypeId::Tendon, { "Tendon", TypeId::Tendon, TypeId::Component, { {"boneId", 95, PropertyType::VarUint}, {"xx", 96, PropertyType::Float}, {"yx", 97, PropertyType::Float}, {"xy", 98, PropertyType::Float}, {"yy", 99, PropertyType::Float}, {"tx", 100, PropertyType::Float}, {"ty", 101, PropertyType::Float}, } } }, { TypeId::Weight, { "Weight", TypeId::Weight, TypeId::Component, { {"values", 102, PropertyType::VarUint}, {"indices", 103, PropertyType::VarUint}, } } }, { TypeId::CubicWeight, { "CubicWeight", TypeId::CubicWeight, TypeId::Weight, { {"inValues", 110, PropertyType::VarUint}, {"inIndices", 111, PropertyType::VarUint}, {"outValues", 112, PropertyType::VarUint}, {"outIndices", 113, PropertyType::VarUint}, } } }, { TypeId::TrimPath, { "TrimPath", TypeId::TrimPath, TypeId::Component, { {"start", 114, PropertyType::Float}, {"end", 115, PropertyType::Float}, {"offset", 116, PropertyType::Float}, {"modeValue", 117, PropertyType::VarUint}, } } }, { TypeId::DrawTarget, { "DrawTarget", TypeId::DrawTarget, TypeId::Component, { {"drawableId", 119, PropertyType::VarUint}, {"placementValue", 120, PropertyType::VarUint}, } } }, { TypeId::DrawRules, { "DrawRules", TypeId::DrawRules, TypeId::ContainerComponent, { {"drawTargetId", 121, PropertyType::VarUint}, } } }, { TypeId::KeyFrameId, { "KeyFrameId", TypeId::KeyFrameId, TypeId::KeyFrame, { {"value", 122, PropertyType::VarUint}, } } }, { TypeId::Polygon, { "Polygon", TypeId::Polygon, TypeId::ParametricPath, { {"points", 125, PropertyType::VarUint}, {"cornerRadius", 126, PropertyType::Float}, } } }, { TypeId::Star, { "Star", TypeId::Star, TypeId::Polygon, { {"innerRadius", 127, PropertyType::Float}, } } }, { TypeId::StateMachine, { "StateMachine", TypeId::StateMachine, TypeId::Animation, { } } }, { TypeId::StateMachineComponent, { "StateMachineComponent", TypeId::StateMachineComponent, TypeId::NoType, { {"name", 138, PropertyType::String}, } } }, { TypeId::StateMachineInput, { "StateMachineInput", TypeId::StateMachineInput, TypeId::StateMachineComponent, { } } }, { TypeId::StateMachineNumber, { "StateMachineNumber", TypeId::StateMachineNumber, TypeId::StateMachineInput, { {"value", 140, PropertyType::Float}, } } }, { TypeId::StateMachineLayer, { "StateMachineLayer", TypeId::StateMachineLayer, TypeId::StateMachineComponent, { } } }, { TypeId::StateMachineTrigger, { "StateMachineTrigger", TypeId::StateMachineTrigger, TypeId::StateMachineInput, { } } }, { TypeId::StateMachineBool, { "StateMachineBool", TypeId::StateMachineBool, TypeId::StateMachineInput, { {"value", 141, PropertyType::Bool}, } } }, { TypeId::LayerState, { "LayerState", TypeId::LayerState, TypeId::StateMachineLayerComponent, { } } }, { TypeId::AnimationState, { "AnimationState", TypeId::AnimationState, TypeId::LayerState, { {"animationId", 149, PropertyType::VarUint}, } } }, { TypeId::AnyState, { "AnyState", TypeId::AnyState, TypeId::LayerState, { } } }, { TypeId::EntryState, { "EntryState", TypeId::EntryState, TypeId::LayerState, { } } }, { TypeId::ExitState, { "ExitState", TypeId::ExitState, TypeId::LayerState, { } } }, { TypeId::StateTransition, { "StateTransition", TypeId::StateTransition, TypeId::StateMachineLayerComponent, { {"stateToId", 151, PropertyType::VarUint}, {"flags", 152, PropertyType::VarUint}, {"duration", 158, PropertyType::VarUint}, {"exitTime", 160, PropertyType::VarUint}, } } }, { TypeId::StateMachineLayerComponent, { "StateMachineLayerComponent", TypeId::StateMachineLayerComponent, TypeId::NoType, { } } }, { TypeId::TransitionCondition, { "TransitionCondition", TypeId::TransitionCondition, TypeId::NoType, { {"inputId", 155, PropertyType::VarUint}, } } }, { TypeId::TransitionTriggerCondition, { "TransitionTriggerCondition", TypeId::TransitionTriggerCondition, TypeId::TransitionCondition, { } } }, { TypeId::TransitionValueCondition, { "TransitionValueCondition", TypeId::TransitionValueCondition, TypeId::TransitionCondition, { {"opValue", 156, PropertyType::VarUint}, } } }, { TypeId::TransitionNumberCondition, { "TransitionNumberCondition", TypeId::TransitionNumberCondition, TypeId::TransitionValueCondition, { {"value", 157, PropertyType::Float}, } } }, { TypeId::TransitionBoolCondition, { "TransitionBoolCondition", TypeId::TransitionBoolCondition, TypeId::TransitionValueCondition, { } } }, { TypeId::BlendState, { "BlendState", TypeId::BlendState, TypeId::LayerState, { } } }, { TypeId::BlendStateDirect, { "BlendStateDirect", TypeId::BlendStateDirect, TypeId::BlendState, { } } }, { TypeId::BlendAnimation, { "BlendAnimation", TypeId::BlendAnimation, TypeId::NoType, { {"animationId", 165, PropertyType::VarUint}, } } }, { TypeId::BlendAnimation1D, { "BlendAnimation1D", TypeId::BlendAnimation1D, TypeId::BlendAnimation, { {"value", 166, PropertyType::Float}, } } }, { TypeId::BlendState1D, { "BlendState1D", TypeId::BlendState1D, TypeId::BlendState, { {"inputId", 167, PropertyType::VarUint}, } } }, { TypeId::BlendAnimationDirect, { "BlendAnimationDirect", TypeId::BlendAnimationDirect, TypeId::BlendAnimation, { {"inputId", 168, PropertyType::VarUint}, } } }, { TypeId::BlendStateTransition, { "BlendStateTransition", TypeId::BlendStateTransition, TypeId::StateTransition, { {"exitBlendAnimationId", 171, PropertyType::VarUint}, } } }, { TypeId::Constraint, { "Constraint", TypeId::Constraint, TypeId::Component, { {"strength", 172, PropertyType::Float}, } } }, { TypeId::TargetedConstraint, { "TargetedConstraint", TypeId::TargetedConstraint, TypeId::Constraint, { {"targetId", 173, PropertyType::VarUint}, } } }, { TypeId::IKConstraint, { "IKConstraint", TypeId::IKConstraint, TypeId::TargetedConstraint, { {"invertDirection", 174, PropertyType::Bool}, {"parentBoneCount", 175, PropertyType::VarUint}, } } }, { TypeId::DistanceConstraint, { "DistanceConstraint", TypeId::DistanceConstraint, TypeId::TargetedConstraint, { {"distance", 177, PropertyType::Float}, {"modeValue", 178, PropertyType::VarUint}, } } }, { TypeId::TransformConstraint, { "TransformConstraint", TypeId::TransformConstraint, TypeId::TransformSpaceConstraint, { } } }, { TypeId::KeyFrameBool, { "KeyFrameBool", TypeId::KeyFrameBool, TypeId::KeyFrame, { {"value", 181, PropertyType::Bool}, } } }, { TypeId::TransformComponentConstraint, { "TransformComponentConstraint", TypeId::TransformComponentConstraint, TypeId::TransformSpaceConstraint, { {"minMaxSpaceValue", 195, PropertyType::VarUint}, {"copyFactor", 182, PropertyType::Float}, {"minValue", 183, PropertyType::Float}, {"maxValue", 184, PropertyType::Float}, {"offset", 188, PropertyType::Bool}, {"doesCopy", 189, PropertyType::Bool}, {"min", 190, PropertyType::Bool}, {"max", 191, PropertyType::Bool}, } } }, { TypeId::TransformComponentConstraintY, { "TransformComponentConstraintY", TypeId::TransformComponentConstraintY, TypeId::TransformComponentConstraint, { {"copyFactorY", 185, PropertyType::Float}, {"minValueY", 186, PropertyType::Float}, {"maxValueY", 187, PropertyType::Float}, {"doesCopyY", 192, PropertyType::Bool}, {"minY", 193, PropertyType::Bool}, {"maxY", 194, PropertyType::Bool}, } } }, { TypeId::TranslationConstraint, { "TranslationConstraint", TypeId::TranslationConstraint, TypeId::TransformComponentConstraintY, { } } }, { TypeId::ScaleConstraint, { "ScaleConstraint", TypeId::ScaleConstraint, TypeId::TransformComponentConstraintY, { } } }, { TypeId::RotationConstraint, { "RotationConstraint", TypeId::RotationConstraint, TypeId::TransformComponentConstraint, { } } }, { TypeId::TransformSpaceConstraint, { "TransformSpaceConstraint", TypeId::TransformSpaceConstraint, TypeId::TargetedConstraint, { {"sourceSpaceValue", 179, PropertyType::VarUint}, {"destSpaceValue", 180, PropertyType::VarUint}, } } }, { TypeId::WorldTransformComponent, { "WorldTransformComponent", TypeId::WorldTransformComponent, TypeId::ContainerComponent, { {"opacity", 18, PropertyType::Float}, } } }, { TypeId::NestedArtboard, { "NestedArtboard", TypeId::NestedArtboard, TypeId::Drawable, { {"artboardId", 197, PropertyType::VarUint}, } } }, { TypeId::NestedAnimation, { "NestedAnimation", TypeId::NestedAnimation, TypeId::ContainerComponent, { {"animationId", 198, PropertyType::VarUint}, } } }, { TypeId::NestedStateMachine, { "NestedStateMachine", TypeId::NestedStateMachine, TypeId::NestedAnimation, { } } }, { TypeId::NestedSimpleAnimation, { "NestedSimpleAnimation", TypeId::NestedSimpleAnimation, TypeId::NestedLinearAnimation, { {"speed", 199, PropertyType::Float}, {"isPlaying", 201, PropertyType::Bool}, } } }, { TypeId::NestedLinearAnimation, { "NestedLinearAnimation", TypeId::NestedLinearAnimation, TypeId::NestedAnimation, { {"mix", 200, PropertyType::Float}, } } }, { TypeId::NestedRemapAnimation, { "NestedRemapAnimation", TypeId::NestedRemapAnimation, TypeId::NestedLinearAnimation, { {"time", 202, PropertyType::Float}, } } }, { TypeId::Asset, { "Asset", TypeId::Asset, TypeId::NoType, { {"name", 203, PropertyType::String}, } } }, { TypeId::Image, { "Image", TypeId::Image, TypeId::Drawable, { {"assetId", 206, PropertyType::VarUint}, } } }, { TypeId::Folder, { "Folder", TypeId::Folder, TypeId::Asset, { } } }, { TypeId::FileAsset, { "FileAsset", TypeId::FileAsset, TypeId::Asset, { {"assetId", 204, PropertyType::VarUint}, } } }, { TypeId::DrawableAsset, { "DrawableAsset", TypeId::DrawableAsset, TypeId::FileAsset, { {"height", 207, PropertyType::Float}, {"width", 208, PropertyType::Float}, } } }, { TypeId::ImageAsset, { "ImageAsset", TypeId::ImageAsset, TypeId::DrawableAsset, { } } }, { TypeId::FileAssetContents, { "FileAssetContents", TypeId::FileAssetContents, TypeId::NoType, { {"bytes", 212, PropertyType::Bytes}, } } }, { TypeId::Vertex, { "Vertex", TypeId::Vertex, TypeId::ContainerComponent, { {"x", 24, PropertyType::Float}, {"y", 25, PropertyType::Float}, } } }, { TypeId::MeshVertex, { "MeshVertex", TypeId::MeshVertex, TypeId::Vertex, { {"u", 215, PropertyType::Float}, {"v", 216, PropertyType::Float}, } } }, { TypeId::Mesh, { "Mesh", TypeId::Mesh, TypeId::ContainerComponent, { {"triangleIndexBytes", 223, PropertyType::Bytes}, } } }, { TypeId::Text, { "Text", TypeId::Text, TypeId::Node, { {"value", 218, PropertyType::String}, } } }, { TypeId::ContourMeshVertex, { "ContourMeshVertex", TypeId::ContourMeshVertex, TypeId::MeshVertex, { } } }, { TypeId::ForcedEdge, { "ForcedEdge", TypeId::ForcedEdge, TypeId::Component, { {"fromId", 219, PropertyType::VarUint}, {"toId", 220, PropertyType::VarUint}, } } }, { TypeId::TextRun, { "TextRun", TypeId::TextRun, TypeId::Drawable, { {"pointSize", 221, PropertyType::Float}, {"textLength", 222, PropertyType::VarUint}, } } }, { TypeId::StateMachineListener, { "StateMachineListener", TypeId::StateMachineListener, TypeId::StateMachineComponent, { {"targetId", 224, PropertyType::VarUint}, {"listenerTypeValue", 225, PropertyType::VarUint}, } } }, { TypeId::ListenerTriggerChange, { "ListenerTriggerChange", TypeId::ListenerTriggerChange, TypeId::ListenerInputChange, { } } }, { TypeId::ListenerInputChange, { "ListenerInputChange", TypeId::ListenerInputChange, TypeId::ListenerAction, { {"inputId", 227, PropertyType::VarUint}, } } }, { TypeId::ListenerBoolChange, { "ListenerBoolChange", TypeId::ListenerBoolChange, TypeId::ListenerInputChange, { {"value", 228, PropertyType::VarUint}, } } }, { TypeId::ListenerNumberChange, { "ListenerNumberChange", TypeId::ListenerNumberChange, TypeId::ListenerInputChange, { {"value", 229, PropertyType::Float}, } } }, { TypeId::LayeredAsset, { "LayeredAsset", TypeId::LayeredAsset, TypeId::DrawableAsset, { } } }, { TypeId::LayerImageAsset, { "LayerImageAsset", TypeId::LayerImageAsset, TypeId::ImageAsset, { {"layer", 233, PropertyType::VarUint}, {"x", 234, PropertyType::Float}, {"y", 235, PropertyType::Float}, } } }, { TypeId::NestedInput, { "NestedInput", TypeId::NestedInput, TypeId::Component, { {"inputId", 237, PropertyType::VarUint}, } } }, { TypeId::NestedTrigger, { "NestedTrigger", TypeId::NestedTrigger, TypeId::NestedInput, { } } }, { TypeId::NestedBool, { "NestedBool", TypeId::NestedBool, TypeId::NestedInput, { {"nestedValue", 238, PropertyType::Bool}, } } }, { TypeId::NestedNumber, { "NestedNumber", TypeId::NestedNumber, TypeId::NestedInput, { {"nestedValue", 239, PropertyType::Float}, } } }, { TypeId::ListenerAction, { "ListenerAction", TypeId::ListenerAction, TypeId::NoType, { } } }, { TypeId::ListenerAlignTarget, { "ListenerAlignTarget", TypeId::ListenerAlignTarget, TypeId::ListenerAction, { {"targetId", 240, PropertyType::VarUint}, } } }, }; mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/property/property.cpp000664 001750 001750 00000001435 14477652011 031654 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "property.hpp" #include "model/object.hpp" #include "command/property_commands.hpp" glaxnimate::model::BaseProperty::BaseProperty(Object* object, const QString& name, PropertyTraits traits) : object_(object), name_(name), traits_(traits) { if ( object ) object_->add_property(this); } void glaxnimate::model::BaseProperty::value_changed() { object_->property_value_changed(this, value()); } bool glaxnimate::model::BaseProperty::set_undoable ( const QVariant& val, bool commit ) { if ( !valid_value(val) ) return false; object_->push_command(new command::SetPropertyValue(this, value(), val, commit)); return true; } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/external/QtAppSetup/src/app/application.cpp000664 001750 001750 00000005611 14477652011 033027 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "application.hpp" #include #include #include #include "app/log/log.hpp" void app::Application::initialize() { on_initialize(); on_initialize_translations(); on_initialize_settings(); app::settings::Settings::instance().load(); } QString app::Application::writable_data_path(const QString& name) const { QString search = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); if ( !search.isEmpty() ) { return QDir::cleanPath(QDir(search).absoluteFilePath(name)); } return QString(); } QStringList app::Application::data_paths(const QString& name) const { QStringList found; for ( const QDir& d: data_roots() ) { if ( d.exists(name) ) found << QDir::cleanPath(d.absoluteFilePath(name)); } found.removeDuplicates(); return found; } QStringList app::Application::data_paths_unchecked(const QString& name) const { QStringList filter; for ( const QDir& d: data_roots() ) { filter << QDir::cleanPath(d.absoluteFilePath(name)); } filter.removeDuplicates(); return filter; } QList app::Application::data_roots() const { QList search; // std paths for ( const QString& str : QStandardPaths::standardLocations(QStandardPaths::AppDataLocation) ) search.push_back(QDir(str)); // executable dir QDir binpath(QCoreApplication::applicationDirPath()); #ifdef Q_OS_WIN // some Windows apps do not use a bin subfolder search.push_back(binpath.filePath(QString("share/%1/%2").arg(organizationName()).arg(applicationName()))); #endif binpath.cdUp(); search.push_back(binpath.filePath(QString("share/%1/%2").arg(organizationName()).arg(applicationName()))); #ifdef Q_OS_MAC // some macOS app bundles use a Resources subfolder search.push_back(binpath.filePath(QString("Resources/%1/%2").arg(organizationName()).arg(applicationName()))); #endif return search; } QString app::Application::data_file(const QString& name) const { QStringList found; for ( const QDir& d: data_roots() ) { if ( d.exists(name) ) return QDir::cleanPath(d.absoluteFilePath(name)); } return {}; } QSettings app::Application::qsettings() const { return QSettings(writable_data_path("settings.ini"), QSettings::IniFormat); } bool app::Application::notify(QObject* receiver, QEvent* e) { try { return QApplication::notify(receiver, e); } catch ( const std::exception& exc ) { log::Log("Event", QMetaEnum::fromType().valueToKey(e->type())).stream(log::Error) << "Exception:" << exc.what(); return false; } } void app::Application::on_initialize_translations() { app::TranslationService::instance().initialize(); } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/lottie/cbor_write_json.hpp000664 001750 001750 00000000503 14477652011 032103 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include namespace glaxnimate::io::lottie { QByteArray cbor_write_json(const QCborMap& obj, bool compact); } // namespace glaxnimate::io::lottie src/modules/glaxnimate/glaxnimate/external/QtAppSetup/src/app/widgets/clearable_keysequence_edit.hpp000664 001750 001750 00000001434 14477652011 037437 0ustar00ddennedyddennedy000000 000000 mlt-7.22.0/* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #ifndef CLEARABLEKEYSEQUENCEEDIT_H #define CLEARABLEKEYSEQUENCEEDIT_H #include #include #include class ClearableKeysequenceEdit : public QWidget { Q_OBJECT public: ClearableKeysequenceEdit(QWidget* parent = nullptr); ~ClearableKeysequenceEdit(); void set_key_sequence(const QKeySequence& ks); void set_default_key_sequence(const QKeySequence& ks); QKeySequence key_sequence() const; protected: void changeEvent ( QEvent* e ) override; private slots: void use_default(); void use_nothing(); private: class Private; std::unique_ptr d; }; #endif // CLEARABLEKEYSEQUENCEEDIT_H mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/mime/mime_serializer.cpp000664 001750 001750 00000003476 14477652011 031531 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "mime_serializer.hpp" #include "app/log/log.hpp" #include "model/object.hpp" #include "json_mime.hpp" #include "io/svg/svg_mime.hpp" #include "model/document.hpp" #include "model/assets/assets.hpp" glaxnimate::io::Autoreg glaxnimate::io::mime::JsonMime::autoreg; glaxnimate::io::Autoreg glaxnimate::io::svg::SvgMime::autoreg; glaxnimate::io::mime::DeserializedData glaxnimate::io::mime::MimeSerializer::from_mime_data(const QMimeData& data) const { if ( !can_deserialize() ) return {}; for ( const QString& mime : mime_types() ) if ( data.hasFormat(mime) ) return deserialize(data.data(mime)); return {}; } void glaxnimate::io::mime::MimeSerializer::message(const QString& message, app::log::Severity severity) const { app::log::Log(slug()).log(message, severity); } glaxnimate::io::mime::DeserializedData glaxnimate::io::mime::MimeSerializer::deserialize(const QByteArray&) const { return {}; } glaxnimate::io::mime::DeserializedData::DeserializedData() = default; glaxnimate::io::mime::DeserializedData::DeserializedData(DeserializedData &&) = default; glaxnimate::io::mime::DeserializedData & glaxnimate::io::mime::DeserializedData::operator=(DeserializedData &&) = default; glaxnimate::io::mime::DeserializedData::~DeserializedData() = default; bool glaxnimate::io::mime::DeserializedData::empty() const { return !document || main->shapes.empty(); } void glaxnimate::io::mime::DeserializedData::initialize_data() { document = std::make_unique(""); main = document->assets()->compositions->values.insert(std::make_unique(document.get())); } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/property/000775 001750 001750 00000000000 14477652011 027261 5ustar00ddennedyddennedy000000 000000 mlt-7.22.0/src/modules/glaxnimate/glaxnimate/external/QtAppSetup/src/app/utils/translated_string.hpp000664 001750 001750 00000001434 14477652011 035417 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include namespace app::utils { class TranslatedString { public: // Invoke using QT_TRANSLATE_NOOP("Settings", "") TranslatedString(const char* input) : input(input) {} TranslatedString(const QString& already_translated) : already_translated(std::move(already_translated)) {} TranslatedString() = default; operator QString() const { if ( !input || input[0] == '\0' ) return already_translated; return QCoreApplication::translate("Settings", input); }; private: const char* input = nullptr; QString already_translated; }; } // namespace app::utils mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/math/000775 001750 001750 00000000000 14477652011 025226 5ustar00ddennedyddennedy000000 000000 mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/assets/brush_style.hpp000664 001750 001750 00000001465 14477652011 031761 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include #include "model/assets/asset.hpp" namespace glaxnimate::model { class BrushStyle : public Asset { Q_OBJECT public: using User = ReferenceProperty; using Asset::Asset; QIcon instance_icon() const override; virtual QBrush brush_style(FrameTime t) const = 0; virtual QBrush constrained_brush_style(FrameTime t, const QRectF& bounds) const; signals: void style_changed(); protected: virtual void fill_icon(QPixmap& icon) const = 0; void invalidate_icon() { icon = {}; emit style_changed(); } private: mutable QPixmap icon; }; } // namespace glaxnimate::model mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/avd/avd_parser.hpp000664 001750 001750 00000001434 14477652011 030317 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include #include #include "io/base.hpp" namespace glaxnimate::io::avd { class AvdParser { private: public: /** * \throws SvgParseError on error */ AvdParser( QIODevice* device, const QDir& resource_path, model::Document* document, const std::function& on_warning = {}, ImportExport* io = nullptr, QSize forced_size = {}, model::FrameTime default_time = 180 ); ~AvdParser(); void parse_to_document(); class Private; private: std::unique_ptr d; }; } // namespace glaxnimate::io::avd mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/rive/rive_serializer.hpp000664 001750 001750 00000001203 14477652011 031554 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include "io/binary_stream.hpp" #include "type_system.hpp" namespace glaxnimate::io::rive { class RiveSerializer { public: explicit RiveSerializer(QIODevice* file); void write_header(int vmaj, int vmin, Identifier file_id); void write_property_table(const PropertyTable& properties); void write_object(const Object& output); void write_property_value(PropertyType id, const QVariant& value); private: BinaryOutputStream stream; }; } // namespace glaxnimate::io::rive mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/svg/svg_format.cpp000664 001750 001750 00000006370 14477652011 030364 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "svg_format.hpp" #include #include "utils/gzip.hpp" #include "svg_parser.hpp" #include "parse_error.hpp" #include "svg_renderer.hpp" #include "model/assets/assets.hpp" glaxnimate::io::Autoreg glaxnimate::io::svg::SvgFormat::autoreg; bool glaxnimate::io::svg::SvgFormat::on_open(QIODevice& file, const QString& filename, model::Document* document, const QVariantMap& options) { /// \todo layer mode setting SvgParser::GroupMode mode = SvgParser::Inkscape; auto on_error = [this](const QString& s){warning(s);}; try { QSize forced_size = options["forced_size"].toSize(); model::FrameTime default_time = options["default_time"].toFloat(); auto default_asset_path = QFileInfo(filename).dir(); if ( utils::gzip::is_compressed(file) ) { utils::gzip::GzipStream decompressed(&file, on_error); decompressed.open(QIODevice::ReadOnly); SvgParser(&decompressed, mode, document, on_error, this, forced_size, default_time, default_asset_path).parse_to_document(); return true; } SvgParser(&file, mode, document, on_error, this, forced_size, default_time, default_asset_path).parse_to_document(); return true; } catch ( const SvgParseError& err ) { error(err.formatted(QFileInfo(filename).baseName())); return false; } } std::unique_ptr glaxnimate::io::svg::SvgFormat::save_settings(model::Composition* comp) const { CssFontType max = CssFontType::None; for ( const auto & font : comp->document()->assets()->fonts->values ) { auto type = SvgRenderer::suggested_type(font.get()); if ( type > max ) max = type; } if ( max == CssFontType::None ) return {}; QVariantMap choices; if ( max >= CssFontType::Link ) choices[tr("External Stylesheet")] = int(CssFontType::Link); if ( max >= CssFontType::FontFace ) choices[tr("Font face with external url")] = int(CssFontType::FontFace); if ( max >= CssFontType::Embedded ) choices[tr("Embedded data")] = int(CssFontType::Embedded); choices[tr("Ignore")] = int(CssFontType::None); return std::make_unique(app::settings::SettingList{ app::settings::Setting("font_type", tr("External Fonts"), tr("How to include external font"), app::settings::Setting::Int, int(qMin(max, CssFontType::FontFace)), choices) }); } bool glaxnimate::io::svg::SvgFormat::on_save(QIODevice& file, const QString& filename, model::Composition* comp, const QVariantMap& options) { auto on_error = [this](const QString& s){warning(s);}; SvgRenderer rend(SMIL, CssFontType(options["font_type"].toInt())); rend.write_main(comp); if ( filename.endsWith(".svgz") || options.value("compressed", false).toBool() ) { utils::gzip::GzipStream compressed(&file, on_error); compressed.open(QIODevice::WriteOnly); rend.write(&compressed, false); } else { rend.write(&file, true); } return true; } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/lottie/lottie_exporter.hpp000664 001750 001750 00000057537 14477652011 032166 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include #include "cbor_write_json.hpp" #include "lottie_private_common.hpp" #include "model/animation/join_animatables.hpp" #include "app_info.hpp" #include "io/utils.hpp" namespace glaxnimate::io::lottie::detail { inline QLatin1String operator "" _l(const char* c, std::size_t sz) { return QLatin1String(c, sz); } // inline QString debug_value(QCborValue v) // { // QCborMap map; // map["v"_l] = v; // return QString(cbor_write_json(map, false)); // } class LottieExporterState { static constexpr const char* version = "5.7.1"; public: explicit LottieExporterState(ImportExport* format, model::Composition* comp, bool strip, bool strip_raster, const QVariantMap& settings ) : format(format), main(comp), document(comp->document()), strip(strip), strip_raster( strip_raster ), auto_embed(settings["auto_embed"].toBool()), old_kf(settings["old_kf"].toBool()) {} QCborMap to_json() { return convert_main(main); } void convert_animation_container(model::AnimationContainer* animation, QCborMap& json) { json["ip"_l] = animation->first_frame.get(); json["op"_l] = animation->last_frame.get(); } void convert_composition(model::Composition* composition, QCborMap& json) { QCborArray layers; for ( const auto& layer : composition->shapes ) if ( !strip || layer->visible.get() ) convert_layer(layer_type(layer.get()), layer.get(), layers); json["layers"_l] = layers; } QCborMap convert_main(model::Composition* animation) { layer_indices.clear(); QCborMap json; json["v"_l] = version; convert_animation_container(animation->animation.get(), json); convert_object_basic(animation, json); json["assets"_l] = convert_assets(animation); convert_composition(animation, json); if ( !strip ) convert_meta(json); return json; } void convert_meta(QCborMap& json) { QCborMap meta; meta["g"_l] = AppInfo::instance().name() + ' ' + AppInfo::instance().version(); if ( !document->info().description.isEmpty() ) meta["d"_l] = document->info().description; if ( !document->info().author.isEmpty() ) meta["a"_l] = document->info().author; if ( !document->info().keywords.isEmpty() ) { QCborArray k; for ( const auto& kw : document->info().keywords ) k.push_back(kw); meta["k"_l] = k; } json["meta"_l] = meta; } int layer_index(model::DocumentNode* layer) { if ( !layer ) return -1; if ( !layer_indices.contains(layer->uuid.get()) ) layer_indices[layer->uuid.get()] = layer_indices.size(); return layer_indices[layer->uuid.get()]; } QCborMap wrap_layer_shape(model::ShapeElement* shape, model::Layer* forced_parent) { QCborMap json; json["ddd"_l] = 0; json["ty"_l] = 4; convert_fake_layer_parent(forced_parent, json); json["ind"_l] = layer_index(shape); json["st"_l] = 0; if ( !shape->visible.get() ) json["hd"_l] = true; if ( auto grp = shape->cast() ) { QCborMap transform; convert_transform(grp->transform.get(), &grp->opacity, transform); json["ks"_l] = transform; json["ao"_l] = int(grp->auto_orient.get()); json["shapes"_l] = convert_shapes(grp->shapes, false); } else { QCborMap transform; model::Transform tf(document); convert_transform(&tf, nullptr, transform); json["ks"_l] = transform; QCborArray shapes; shapes.push_back(convert_shape(shape, false)); json["shapes"_l] = shapes; } return json; } enum class LayerType { Shape, Layer, Image, PreComp }; LayerType layer_type(model::ShapeElement* shape) { auto meta = shape->metaObject(); if ( meta->inherits(&model::Layer::staticMetaObject) ) return LayerType::Layer; if ( meta->inherits(&model::Image::staticMetaObject) ) return LayerType::Image; if ( meta->inherits(&model::PreCompLayer::staticMetaObject) ) return LayerType::PreComp; return LayerType::Shape; } QCborMap convert_single_layer(LayerType type, model::ShapeElement* shape, QCborArray& output, model::Layer* forced_parent, bool force_all_shapes) { switch ( type ) { case LayerType::Shape: return wrap_layer_shape(shape, forced_parent); case LayerType::Image: return convert_image_layer(static_cast(shape), forced_parent); case LayerType::PreComp: return convert_precomp_layer(static_cast(shape), forced_parent); case LayerType::Layer: break; } auto layer = static_cast(shape); int parent_index = layer_index(forced_parent ? forced_parent : layer->parent.get()); QCborMap json; json["ddd"_l] = 0; json["ty"_l] = 3; int index = layer_index(layer); json["ind"_l] = index; json["st"_l] = 0; if ( !shape->visible.get() ) json["hd"_l] = true; convert_animation_container(layer->animation.get(), json); convert_object_properties(layer, fields["DocumentNode"], json); convert_object_properties(layer, fields["__Layer__"], json); QCborMap transform; convert_transform(layer->transform.get(), &layer->opacity, transform); json["ks"_l] = transform; if ( parent_index != -1 ) json["parent"_l] = parent_index; if ( !layer->shapes.empty() ) { std::vector children_types; children_types.reserve(layer->shapes.size()); bool all_shapes = true; if ( !force_all_shapes ) { for ( const auto& shape : layer->shapes ) { children_types.push_back(layer_type(shape.get())); if ( children_types.back() != LayerType::Shape ) all_shapes = false; } } if ( all_shapes && !layer->mask->has_mask() ) { json["ty"_l] = 4; json["shapes"_l] = convert_shapes(layer->shapes, false); } else { int i = 0; QCborMap mask; if ( layer->mask->has_mask() && !layer->shapes.empty() ) { if ( layer->shapes[0]->visible.get() ) { mask = convert_single_layer(children_types[0], layer->shapes[0], output, layer, true); if ( !mask.isEmpty() ) mask["td"_l] = 1; } i = 1; } for ( ; i < layer->shapes.size(); i++ ) { if ( !strip || layer->shapes[i]->visible.get() ) convert_layer(children_types[i], layer->shapes[i], output, layer, mask); } } } return json; } QCborMap convert_layer(LayerType type, model::ShapeElement* shape, QCborArray& output, model::Layer* forced_parent = nullptr, const QCborMap& mask = {}) { if ( !shape->visible.get() ) return {}; model::Layer* layer = nullptr; if ( type == LayerType::Layer ) { layer = static_cast(shape); if ( !layer->render.get() ) return {}; } auto json = convert_single_layer(type, shape, output, forced_parent, false); if ( !mask.isEmpty() ) { json["tt"_l] = 1; output.push_front(json); output.push_front(mask); } else { output.push_front(json); } return json; } void convert_transform(model::Transform* tf, model::AnimatableBase* opacity, QCborMap& json) { convert_object_basic(tf, json); if ( opacity ) json["o"_l] = convert_animated(opacity, FloatMult(100)); else json["o"_l] = fake_animated(100); } QCborArray point_to_lottie(const QPointF& vv) { return QCborArray{vv.x(), vv.y()}; } QCborValue value_from_variant(const QVariant& v) { switch ( v.userType() ) { case QMetaType::QPointF: return point_to_lottie(v.toPointF()); case QMetaType::QVector2D: { auto vv = v.value() * 100; return QCborArray{vv.x(), vv.y()}; } case QMetaType::QSizeF: { auto vv = v.toSizeF(); return QCborArray{vv.width(), vv.height()}; } case QMetaType::QColor: { auto vv = v.value().toRgb(); return QCborArray{vv.redF(), vv.greenF(), vv.blueF()}; } case QMetaType::QUuid: return v.toString(); } if ( v.userType() == qMetaTypeId() ) { math::bezier::Bezier bezier = v.value(); QCborMap jsbez; jsbez["c"_l] = bezier.closed(); QCborArray pos, tan_in, tan_out; for ( const auto& p : bezier ) { pos.push_back(point_to_lottie(p.pos)); tan_in.push_back(point_to_lottie(p.tan_in - p.pos)); tan_out.push_back(point_to_lottie(p.tan_out - p.pos)); } jsbez["v"_l] = pos; jsbez["i"_l] = tan_in; jsbez["o"_l] = tan_out; return jsbez; } else if ( v.userType() == qMetaTypeId() ) { return point_to_lottie(v.value().pos); } else if ( v.userType() == qMetaTypeId() ) { QCborArray weird_ass_representation; auto gradient = v.value(); bool alpha = false; for ( const auto& stop : gradient ) { weird_ass_representation.push_back(stop.first); weird_ass_representation.push_back(stop.second.redF()); weird_ass_representation.push_back(stop.second.greenF()); weird_ass_representation.push_back(stop.second.blueF()); alpha = alpha || stop.second.alpha() != 0; } if ( alpha ) { for ( const auto& stop : gradient ) { weird_ass_representation.push_back(stop.first); weird_ass_representation.push_back(stop.second.alphaF()); } } return weird_ass_representation; } else if ( v.userType() >= QMetaType::User && v.canConvert() ) { return v.toInt(); } return QCborValue::fromVariant(v); } void convert_object_from_meta(model::Object* obj, const QMetaObject* mo, QCborMap& json_obj) { if ( auto super = mo->superClass() ) convert_object_from_meta(obj, super, json_obj); auto it = fields.find(model::detail::naked_type_name(mo)); if ( it != fields.end() ) convert_object_properties(obj, *it, json_obj); } void convert_object_basic(model::Object* obj, QCborMap& json_obj) { convert_object_from_meta(obj, obj->metaObject(), json_obj); } void convert_object_properties(model::Object* obj, const QVector& fields, QCborMap& json_obj) { for ( const auto& field : fields ) { if ( field.mode != Auto || (strip && !field.essential) ) continue; model::BaseProperty * prop = obj->get_property(field.name); if ( !prop ) { logger.stream() << field.name << "is not a property"; continue; } if ( prop->traits().flags & model::PropertyTraits::Animated ) { json_obj[field.lottie] = convert_animated(static_cast(prop), field.transform); } else { json_obj[field.lottie] = value_from_variant(field.transform.to_lottie(prop->value(), 0)); } } } QCborValue keyframe_value_from_variant(const QVariant& v) { auto cb = value_from_variant(v); if ( cb.isArray() ) return cb; return QCborArray{cb}; } QCborMap convert_animated( model::AnimatableBase* prop, const TransformFunc& transform_values ) { bool position = prop->traits().type == model::PropertyTraits::Point; QCborMap jobj; if ( prop->keyframe_count() > 1 ) { jobj["a"_l] = 1; std::vector> split_kfs = split_keyframes(prop); QCborArray keyframes; QCborMap jkf; for ( int i = 0, e = split_kfs.size(); i < e; i++ ) { auto kf = split_kfs[i].get(); QVariant v = transform_values.to_lottie(kf->value(), kf->time()); QCborValue kf_value = keyframe_value_from_variant(v); if ( i != 0 ) { if ( old_kf ) jkf["e"_l] = kf_value; if ( position ) { auto pkf = static_cast*>(kf); jkf["ti"_l] = point_to_lottie(pkf->point().tan_in - pkf->get()); } keyframes.push_back(jkf); } jkf.clear(); jkf["t"_l] = kf->time(); jkf["s"_l] = kf_value; if ( i != e - 1 ) { if ( kf->transition().hold() ) { jkf["h"_l] = 1; } else { jkf["h"_l] = 0; jkf["o"_l] = keyframe_bezier_handle(kf->transition().before()); jkf["i"_l] = keyframe_bezier_handle(kf->transition().after()); } } if ( position ) { auto pkf = static_cast*>(kf); jkf["to"_l] = point_to_lottie(pkf->point().tan_out - pkf->get()); } } if ( position ) jkf.remove("to"_l); keyframes.push_back(jkf); jobj["k"_l] = keyframes; } else { jobj["a"_l] = 0; QVariant v = transform_values.to_lottie(prop->value(), 0); jobj["k"_l] = value_from_variant(v); } return jobj; } QCborMap keyframe_bezier_handle(const QPointF& p) { QCborMap jobj; QCborArray x; x.push_back(p.x()); QCborArray y; y.push_back(p.y()); jobj["x"_l] = x; jobj["y"_l] = y; return jobj; } void convert_styler(model::Styler* shape, QCborMap& jsh) { auto used = shape->use.get(); auto gradient = qobject_cast(used); if ( !gradient || !gradient->colors.get() ) { auto color_prop = &shape->color; if ( auto color = qobject_cast(used) ) color_prop = &color->color; jsh["c"_l] = convert_animated(color_prop, {}); auto join_func = [](const std::vector& args) -> QVariant { return args[0].value().alphaF() * args[1].toFloat() * 100; }; model::JoinedAnimatable join({color_prop, &shape->opacity}, join_func); jsh["o"_l] = convert_animated(&join, {}); return; } convert_object_basic(gradient, jsh); if ( shape->type_name() == "Fill" ) jsh["ty"_l] = "gf"; else jsh["ty"_l] = "gs"; /// \todo highlight jsh["h"_l] = fake_animated(0); jsh["a"_l] = fake_animated(0); auto colors = gradient->colors.get(); QCborMap jcolors; jcolors["p"_l] = colors->colors.get().size(); jcolors["k"_l] = convert_animated(&colors->colors, {}); jsh["g"_l] = jcolors; } QCborMap convert_shape(model::ShapeElement* shape, bool force_hidden) { if ( auto text = shape->cast() ) { auto conv = text->to_path(); return convert_shape(conv.get(), force_hidden || !shape->visible.get()); } QCborMap jsh; jsh["ty"_l] = shape_types[shape->type_name()]; // jsh["d"] = 0; if ( force_hidden || !shape->visible.get() ) jsh["hd"_l] = true; convert_object_basic(shape, jsh); if ( auto gr = qobject_cast(shape) ) { if ( qobject_cast(gr) ) format->information(io::lottie::LottieFormat::tr("Lottie only supports layers in the top level")); else if ( gr->auto_orient.get() ) format->information(io::lottie::LottieFormat::tr("Lottie only supports auto-orient layers in the top level")); auto shapes = convert_shapes(gr->shapes, force_hidden || !gr->visible.get()); QCborMap transform; transform["ty"_l] = "tr"; convert_transform(gr->transform.get(), &gr->opacity, transform); shapes.push_back(transform); jsh["it"_l] = shapes; } else if ( auto styler = shape->cast() ) { convert_styler(styler, jsh); } else if ( auto polystar = shape->cast() ) { if ( polystar->type.get() == model::PolyStar::Polygon ) { jsh.remove("is"_l); jsh.remove("ir"_l); } } else if ( auto styler = shape->cast() ) { QCborMap transform; convert_transform(styler->transform.get(), nullptr, transform); transform.remove("o"_l); transform["so"_l] = convert_animated(&styler->start_opacity, FloatMult(100)); transform["eo"_l] = convert_animated(&styler->end_opacity, FloatMult(100)); jsh["o"_l] = fake_animated(0); jsh["m"_l] = 1; jsh["tr"_l] = transform; } return jsh; } QCborMap fake_animated(const QCborValue& val) { QCborMap fake; fake["a"_l] = 0; fake["k"_l] = val; return fake; } QCborArray convert_shapes(const model::ShapeListProperty& shapes, bool force_hidden) { QCborArray jshapes; for ( const auto& shape : shapes ) { if ( shape->is_instance() ) format->warning(io::lottie::LottieFormat::tr("Images cannot be grouped with other shapes, they must be inside a layer")); else if ( shape->is_instance() ) format->warning(io::lottie::LottieFormat::tr("Composition layers cannot be grouped with other shapes, they must be inside a layer")); else if ( !strip || shape->visible.get() ) jshapes.push_front(convert_shape(shape.get(), force_hidden)); } return jshapes; } QCborArray convert_assets(model::Composition* animation) { QCborArray assets; if ( !strip_raster ) { for ( const auto& bmp : document->assets()->images->values ) { if ( auto_embed && !bmp->embedded() ) { auto clone = bmp->clone_covariant(); clone->embed(true); assets.push_back(convert_bitmat(clone.get())); } else { assets.push_back(convert_bitmat(bmp.get())); } } } for ( const auto& comp : document->assets()->compositions->values ) { if ( comp.get() != animation ) assets.push_back(convert_precomp(comp.get())); } return assets; } QCborMap convert_bitmat(model::Bitmap* bmp) { QCborMap out; convert_object_basic(bmp, out); out["id"_l] = bmp->uuid.get().toString(); out["e"_l] = int(bmp->embedded()); if ( bmp->embedded() ) { out["u"_l] = ""; out["p"_l] = bmp->to_url().toString(); } else { auto finfo = bmp->file_info(); out["u"_l] = finfo.absolutePath(); out["p"_l] = finfo.fileName(); } return out; } void convert_fake_layer_parent(model::Layer* parent, QCborMap& json) { if ( parent ) { convert_animation_container(parent->animation.get(), json); json["parent"_l] = layer_index(parent); } else { convert_animation_container(main->animation.get(), json); } } void convert_fake_layer(model::DocumentNode* node, model::Layer* parent, QCborMap& json) { json["ddd"_l] = 0; if ( !strip ) { json["nm"_l] = node->name.get(); json["mn"_l] = node->uuid.get().toString(); } convert_fake_layer_parent(parent, json); json["ind"_l] = layer_index(node); } QCborMap convert_image_layer(model::Image* image, model::Layer* parent) { QCborMap json; convert_fake_layer(image, parent, json); if ( !strip_raster ) json["ty"_l] = 2; json["ind"_l] = layer_index(image); json["st"_l] = 0; QCborMap transform; convert_object_basic(image->transform.get(), transform); transform["o"_l] = QCborMap{ {"a"_l, 0}, {"k"_l, 100}, }; json["ks"_l] = transform; if ( !strip_raster && image->image.get() ) json["refId"_l] = image->image->uuid.get().toString(); return json; } QCborMap convert_precomp(model::Composition* comp) { QCborMap out; convert_object_basic(comp, out); out["id"_l] = comp->uuid.get().toString(); convert_composition(comp, out); return out; } QCborMap convert_precomp_layer(model::PreCompLayer* layer, model::Layer* parent) { QCborMap json; json["ty"_l] = 0; convert_fake_layer(layer, parent, json); json["ind"_l] = layer_index(layer); json["st"_l] = layer->timing->start_time.get(); json["sr"_l] = layer->timing->stretch.get(); QCborMap transform; convert_transform(layer->transform.get(), &layer->opacity, transform); json["ks"_l] = transform; if ( layer->composition.get() ) json["refId"_l] = layer->composition->uuid.get().toString(); json["w"_l] = layer->size.get().width(); json["h"_l] = layer->size.get().height(); return json; } ImportExport* format; model::Composition* main; model::Document* document; bool strip; QMap layer_indices; app::log::Log logger{"Lottie Export"}; model::Layer* mask = 0; bool strip_raster; bool auto_embed; bool old_kf; }; } // namespace glaxnimate::io::lottie::detail mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/rive/type_system.hpp000664 001750 001750 00000007615 14477652011 030760 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include "type_def.hpp" #include "app/utils/qstring_hash.hpp" namespace glaxnimate::io::rive { class Object; struct PropertyAnimation { const Property* property = nullptr; std::vector keyframes = {}; }; class ObjectType { public: ObjectType(TypeId id = TypeId::NoType) : id(id) {} TypeId id = TypeId::NoType; std::vector properties; std::vector definitions; std::unordered_map property_from_id; std::unordered_map property_from_name; const Property* property(const QString& name) const { auto it = property_from_name.find(name); if ( it == property_from_name.end() ) return nullptr; return it->second; } const Property* property(Identifier id) const { auto it = property_from_id.find(id); if ( it == property_from_id.end() ) return nullptr; return it->second; } }; class Object { public: Object(const ObjectType* type = nullptr) : type_(type) {} const ObjectType& type() const { return *type_; } const std::unordered_map& properties() const { return properties_; } template bool set(const QString& name, T value) { if ( auto prop = type_->property(name) ) { properties_[prop].setValue(value); return true; } return false; } bool set(const QString& name, const QVariant& value) { if ( auto prop = type_->property(name) ) { properties_[prop] = value; return true; } return false; } void set(const Property* prop, const QVariant& value) { properties_[prop] = value; } template T get(const QString& name, T value = {}) const { if ( auto prop = type_->property(name) ) { auto it = properties_.find(prop); if ( it != properties_.end() ) return it->second.value(); } return value; } QVariant get_variant(const QString& name) { if ( auto prop = type_->property(name) ) { auto it = properties_.find(prop); if ( it != properties_.end() ) return it->second; } return {}; } bool has(const QString& name) const { if ( auto prop = type_->property(name) ) return properties_.count(prop); return false; } std::vector& animations() { return animations_; } bool has_type(TypeId type) const { for ( const auto& def : type_->definitions ) if ( def->type_id == type ) return true; return false; } std::vector& children() { return children_; } explicit operator bool() const { return type_; } const ObjectDefinition* definition() const { return type_->definitions[0]; } private: const ObjectType* type_; std::unordered_map properties_; std::vector animations_; std::vector children_; }; class TypeSystem : public QObject { Q_OBJECT public: const ObjectDefinition* get_definition(TypeId type_id); const ObjectType* get_type(TypeId type_id); Object object(TypeId type_id) { return Object(get_type(type_id)); } QString type_name(TypeId type_id); signals: void type_not_found(int type_id); private: bool gather_definitions(ObjectType& type, TypeId type_id); std::unordered_map types; }; } // namespace glaxnimate::io::rive mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/command/structure_commands.cpp000664 001750 001750 00000003717 14477652011 032350 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "structure_commands.hpp" #include "shape_commands.hpp" using namespace glaxnimate; command::DeferredCommandBase::~DeferredCommandBase() = default; bool command::DeferredCommandBase::has_action() const { return bool(d); } void command::DeferredCommandBase::redo() { if ( d ) d->redo(); } void command::DeferredCommandBase::undo() { if ( d ) d->undo(); } bool command::ReorderCommand::resolve_position(model::ShapeElement* shape, int& new_position) { if ( new_position < 0 ) { switch ( command::ReorderCommand::SpecialPosition(new_position) ) { case command::ReorderCommand::MoveUp: new_position = shape->position() + 1; break; case command::ReorderCommand::MoveDown: new_position = shape->position() - 1; break; case command::ReorderCommand::MoveTop: new_position = shape->owner()->size() - 1; break; case command::ReorderCommand::MoveBottom: new_position = 0; break; } } if ( new_position == shape->position() || new_position < 0 || new_position >= shape->owner()->size() ) return false; return true; } std::unique_ptr reorder_shape(model::ShapeElement* shape, int new_position) { if ( !command::ReorderCommand::resolve_position(shape, new_position) ) return {}; return std::make_unique(shape, shape->owner(), shape->owner(), new_position); } command::ReorderCommand::ReorderCommand(model::ShapeElement* shape, int new_position) : DeferredCommandBase(name(shape)) { d = reorder_shape(shape, new_position); } QString command::ReorderCommand::name(model::DocumentNode* node) { return QObject::tr("Move %1").arg(node->object_name()); } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/shapes/image.hpp000664 001750 001750 00000002622 14477652011 030455 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include "model/assets/bitmap.hpp" #include "model/shapes/shape.hpp" #include "model/transform.hpp" #include "model/property/reference_property.hpp" #include "model/property/sub_object_property.hpp" namespace glaxnimate::model { class Image : public ShapeElement { GLAXNIMATE_OBJECT(Image) GLAXNIMATE_SUBOBJECT(Transform, transform) GLAXNIMATE_PROPERTY_REFERENCE(Bitmap, image, &Image::valid_images, &Image::is_valid_image, &Image::on_image_changed) public: Image(model::Document* doc); void add_shapes(FrameTime, math::bezier::MultiBezier&, const QTransform&) const override; QIcon tree_icon() const override; QString type_name_human() const override; QRectF local_bounding_rect(FrameTime t) const override; QTransform local_transform_matrix(model::FrameTime t) const override; protected: QPainterPath to_painter_path_impl(FrameTime t) const override; void on_paint(QPainter* p, FrameTime t, PaintMode, model::Modifier*) const override; private slots: void on_transform_matrix_changed(); private: std::vector valid_images() const; bool is_valid_image(DocumentNode* node) const; void on_image_changed(Bitmap* new_use, Bitmap* old_use); void on_update_image(); }; } // namespace glaxnimate::model mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/shapes/trim.hpp000664 001750 001750 00000002200 14477652011 030336 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include "path_modifier.hpp" namespace glaxnimate::model { class Trim : public StaticOverrides { GLAXNIMATE_OBJECT(Trim) GLAXNIMATE_ANIMATABLE(float, start, 0, {}, 0, 1, false, PropertyTraits::Percent) GLAXNIMATE_ANIMATABLE(float, end, 1, {}, 0, 1, false, PropertyTraits::Percent) GLAXNIMATE_ANIMATABLE(float, offset, 0, {}, std::numeric_limits::lowest(), std::numeric_limits::max(), false, PropertyTraits::Percent) public: enum MultipleShapes { Individually = 1, Simultaneously = 2, }; Q_ENUM(MultipleShapes) GLAXNIMATE_PROPERTY(MultipleShapes, multiple, Individually, {}, {}, PropertyTraits::Visual) public: using Ctor::Ctor; static QIcon static_tree_icon(); static QString static_type_name_human(); math::bezier::MultiBezier process(FrameTime t, const math::bezier::MultiBezier& mbez) const override; protected: bool process_collected() const override; }; } // namespace glaxnimate::model mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/math/vector.hpp000664 001750 001750 00000010032 14477652011 027235 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include #include #include #include #include "math/math.hpp" class QVector2D; class QVector3D; class QVector4D; class QPointF; namespace glaxnimate::math { namespace detail { template struct VecSize; template<> struct VecSize { static constexpr int value = 2; }; template<> struct VecSize { static constexpr int value = 3; }; template<> struct VecSize { static constexpr int value = 4; }; template<> struct VecSize { static constexpr int value = 2; }; template struct VecScalar { using type = std::decay_t()[0])>; }; template<> struct VecScalar { using type = qreal; }; template<> struct VecScalar { using type = qreal; }; template using scalar_type = typename detail::VecScalar>::type; template constexpr const scalar_type& get(const VecT& vt, int off) noexcept { return reinterpret_cast::type*>(&vt)[off]; } template constexpr scalar_type& get(VecT& vt, int off) noexcept { return reinterpret_cast::type*>(&vt)[off]; } template struct LengthHelper { static constexpr scalar_type sumsq(const VecT& v) noexcept { return get(v, d-1) * get(v, d-1) + LengthHelper::sumsq(v); } }; template struct LengthHelper { static constexpr scalar_type sumsq(const VecT& v) noexcept { return get(v, 0) * get(v, 0); } }; } // namespace detail using detail::scalar_type; using detail::get; inline QColor lerp(const QColor& a, const QColor& b, double factor) { return QColor::fromRgbF( lerp(a.redF(), b.redF(), factor), lerp(a.greenF(), b.greenF(), factor), lerp(a.blueF(), b.blueF(), factor), lerp(a.alphaF(), b.alphaF(), factor) ); } template constexpr std::vector lerp(const std::vector& a, const std::vector& b, double factor) { if ( a.size() != b.size() ) return a; std::vector c; c.reserve(a.size()); for ( std::size_t i = 0; i < a.size(); i++ ) c.push_back(lerp(a[i], b[i], factor)); return c; } template constexpr scalar_type length_squared(const VecT& v) { return detail::LengthHelper::value>::sumsq(v); } /** * \brief 2-norm length of a vector */ template constexpr scalar_type length(const VecT& v) { return std::sqrt(length_squared(v)); } /** * \brief 2-norm distance between two points */ template constexpr scalar_type distance(const VecT& a, const VecT& b) { return length(b - a); } /** * \brief Angle to the x axis of a 2D cartesian vector */ template scalar_type angle(const VecT& cartesian) { return std::atan2(detail::get(cartesian, 1), detail::get(cartesian, 0)); } template VecT from_polar(scalar_type length, scalar_type angle) { return {std::cos(angle) * length, std::sin(angle) * length}; } template struct PolarVector { using scalar = scalar_type; scalar length; scalar angle; constexpr PolarVector() noexcept : PolarVector(0, 0) {} constexpr PolarVector(scalar length, scalar angle) noexcept : length(length), angle(angle) {} PolarVector(const VecT& cartesian) : length(math::length(cartesian)), angle(math::angle(cartesian)) {} VecT to_cartesian() const { return {std::cos(angle) * length, std::sin(angle) * length}; } }; template bool fuzzy_compare(const VecT& a, const VecT& b) { for ( int i = 0; i < detail::VecSize::value; i++ ) if ( !qFuzzyCompare(detail::get(a, i), detail::get(b, i)) ) return false; return true; } } // namespace glaxnimate::math mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/document_node.cpp000664 001750 001750 00000025034 14477652011 030730 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "document_node.hpp" #include "document.hpp" #include #include #include "model/shapes/shape.hpp" #include "model/property/reference_property.hpp" #include "model/property/sub_object_property.hpp" #include "utils/pseudo_mutex.hpp" class glaxnimate::model::DocumentNode::Private { public: std::unordered_set users; utils::PseudoMutex detaching; DocumentNode* list_parent = nullptr; }; glaxnimate::model::DocumentNode::DocumentNode(glaxnimate::model::Document* document) : DocumentNode(document, std::make_unique()) { } glaxnimate::model::DocumentNode::DocumentNode(glaxnimate::model::Document* document, std::unique_ptr d) : Object ( document ), d(std::move(d)) { uuid.set_value(QUuid::createUuid()); } glaxnimate::model::DocumentNode::~DocumentNode() = default; void glaxnimate::model::DocumentNode::removed_from_list() { auto old = d->list_parent; d->list_parent = nullptr; document()->decrease_node_name(name.get()); on_parent_changed(old, d->list_parent); emit removed(); } void glaxnimate::model::DocumentNode::added_to_list ( glaxnimate::model::DocumentNode* new_parent ) { auto old = d->list_parent; d->list_parent = new_parent; document()->increase_node_name(name.get()); on_parent_changed(old, d->list_parent); } void glaxnimate::model::DocumentNode::on_name_changed(const QString& name, const QString& old_name) { if ( old_name != name ) { document()->decrease_node_name(old_name); document()->increase_node_name(name); emit name_changed(name); } } glaxnimate::model::DocumentNode * glaxnimate::model::DocumentNode::docnode_parent() const { return d->list_parent; } bool glaxnimate::model::DocumentNode::docnode_is_instance(const QString& type_name) const { if ( type_name.isEmpty() ) return true; for ( const QMetaObject* meta = metaObject(); meta; meta = meta->superClass() ) { if ( detail::naked_type_name(meta->className()) == type_name ) return true; } return false; } void glaxnimate::model::DocumentNode::recursive_rename() { document()->set_best_name(this, name.get()); for ( auto child : docnode_children() ) child->recursive_rename(); } void glaxnimate::model::DocumentNode::refresh_uuid() { uuid.set_value(QUuid::createUuid()); for ( auto prop : properties() ) { if ( prop->traits().type == PropertyTraits::Object ) { if ( prop->traits().flags & PropertyTraits::List ) { for ( auto v : prop->value().toList() ) { if ( auto obj = v.value() ) obj->refresh_uuid(); } } else { if ( auto obj = qobject_cast(static_cast(prop)->sub_object()) ) obj->refresh_uuid(); } } } } QString glaxnimate::model::DocumentNode::object_name() const { if ( name.get().isEmpty() ) return type_name_human(); return name.get(); } void glaxnimate::model::DocumentNode::add_user(glaxnimate::model::DocumentNode::User* user) { if ( !d->detaching ) { d->users.insert(user); emit users_changed(); } } void glaxnimate::model::DocumentNode::remove_user(glaxnimate::model::DocumentNode::User* user) { if ( !d->detaching ) { d->users.erase(user); emit users_changed(); } } const std::unordered_set & glaxnimate::model::DocumentNode::users() const { return d->users; } void glaxnimate::model::DocumentNode::attach() { if ( auto lock = d->detaching.get_lock() ) { for ( auto user : d->users ) user->set_ref(this); } } void glaxnimate::model::DocumentNode::detach() { if ( auto lock = d->detaching.get_lock() ) { for ( auto user : d->users ) user->set_ref(nullptr); } } bool glaxnimate::model::DocumentNode::is_descendant_of(const model::DocumentNode* other) const { if ( !other ) return false; if ( other == this ) return true; auto parent = docnode_parent(); if ( parent ) return parent->is_descendant_of(other); return false; } class glaxnimate::model::VisualNode::Private : public DocumentNode::Private { public: std::unique_ptr group_icon; }; glaxnimate::model::VisualNode::VisualNode(model::Document* document) : DocumentNode(document, std::make_unique()) { } glaxnimate::model::VisualNode::Private * glaxnimate::model::VisualNode::dd() const { return static_cast(d.get()); } glaxnimate::model::VisualNode* glaxnimate::model::VisualNode::docnode_group_parent() const { return nullptr; } int glaxnimate::model::VisualNode::docnode_group_child_count() const { return 0; } glaxnimate::model::VisualNode* glaxnimate::model::VisualNode::docnode_group_child(int) const { return nullptr; } glaxnimate::model::VisualNode* glaxnimate::model::VisualNode::docnode_fuzzy_parent() const { if ( auto p = docnode_group_parent() ) return p; return docnode_visual_parent(); } QColor glaxnimate::model::VisualNode::docnode_group_color() const { if ( !docnode_valid_color() ) { if ( auto parent = docnode_fuzzy_parent() ) return parent->docnode_group_color(); return Qt::transparent; } return group_color.get(); } glaxnimate::model::VisualNode* glaxnimate::model::VisualNode::docnode_visual_child(int index) const { return static_cast(docnode_child(index)); } glaxnimate::model::VisualNode* glaxnimate::model::VisualNode::docnode_visual_parent() const { auto p = docnode_parent(); if ( p ) return p->cast(); return nullptr; } void glaxnimate::model::VisualNode::on_group_color_changed(const QColor&) { if ( dd()->group_icon && !dd()->group_icon->isNull() ) { if ( docnode_valid_color() ) dd()->group_icon->fill(group_color.get()); else dd()->group_icon->fill(Qt::white); } docnode_on_update_group(true); } void glaxnimate::model::VisualNode::docnode_on_update_group(bool) { // if ( force || docnode_valid_color() ) { emit docnode_group_color_changed(docnode_group_color()); for ( auto gc : docnode_group_children() ) gc->docnode_on_update_group(); for ( auto gc : docnode_visual_children() ) gc->docnode_on_update_group(); } emit group_transform_matrix_changed(group_transform_matrix(time())); } bool glaxnimate::model::VisualNode::docnode_valid_color() const { QColor col = group_color.get(); return col.isValid() && col.alpha() > 0; } QIcon glaxnimate::model::VisualNode::instance_icon() const { if ( !docnode_valid_color() ) { if ( auto parent = docnode_fuzzy_parent() ) return parent->instance_icon(); } if ( !dd()->group_icon ) { dd()->group_icon = std::make_unique(33, 33); dd()->group_icon->fill(docnode_group_color()); } return *dd()->group_icon; } bool glaxnimate::model::VisualNode::docnode_locked_recursive() const { for ( const VisualNode* n = this; n; n = n->docnode_visual_parent() ) { if ( n->locked.get() ) return true; } return false; } void glaxnimate::model::VisualNode::paint(QPainter* painter, FrameTime time, PaintMode mode, glaxnimate::model::Modifier* modifier) const { if ( !visible.get() ) return; painter->save(); painter->setTransform(group_transform_matrix(time), true); on_paint(painter, time, mode, modifier); for ( auto c : docnode_visual_children() ) { c->paint(painter, time, mode, modifier); if ( c->is_instance() ) break; } painter->restore(); } bool glaxnimate::model::VisualNode::docnode_selectable() const { if ( !visible.get() || locked.get() ) return false; if ( auto p = docnode_visual_parent() ) return p->docnode_selectable(); return true; } bool glaxnimate::model::VisualNode::docnode_visible_recursive() const { if ( !visible.get() ) return false; if ( auto p = docnode_visual_parent() ) return p->docnode_visible_recursive(); return true; } QTransform glaxnimate::model::VisualNode::transform_matrix(glaxnimate::model::FrameTime t) const { auto matrix = local_transform_matrix(t); glaxnimate::model::VisualNode* parent = docnode_visual_parent(); if ( parent ) matrix *= parent->transform_matrix(t); parent = docnode_group_parent(); if ( parent ) matrix *= parent->transform_matrix(t); return matrix; } QTransform glaxnimate::model::VisualNode::group_transform_matrix(glaxnimate::model::FrameTime t) const { auto parent = docnode_group_parent(); if ( parent ) return local_transform_matrix(t) * parent->transform_matrix(t); return local_transform_matrix(t); } void glaxnimate::model::VisualNode::on_visible_changed(bool visible) { emit docnode_visible_changed(visible); emit docnode_visible_recursive_changed(visible); for ( auto ch : docnode_visual_children() ) ch->propagate_visible(visible); } void glaxnimate::model::VisualNode::propagate_visible(bool visible) { if ( !this->visible.get() ) return; emit docnode_visible_recursive_changed(visible); for ( auto ch : docnode_visual_children() ) ch->propagate_visible(visible && this->visible.get()); } void glaxnimate::model::VisualNode::propagate_transform_matrix_changed(const QTransform& t_global, const QTransform& t_group) { emit transform_matrix_changed(t_global); emit group_transform_matrix_changed(t_group); for ( auto ch : docnode_group_children() ) { auto ltm = ch->local_transform_matrix(ch->time()); ch->propagate_transform_matrix_changed(ltm * t_global, ltm * t_group); } for ( auto ch : docnode_visual_children() ) { auto ltm = ch->local_transform_matrix(ch->time()); ch->propagate_transform_matrix_changed(ltm * t_global, ltm); } } void glaxnimate::model::VisualNode::propagate_bounding_rect_changed() { on_graphics_changed(); emit bounding_rect_changed(); if ( auto parent = docnode_visual_parent() ) parent->propagate_bounding_rect_changed(); } src/modules/glaxnimate/glaxnimate/external/QtAppSetup/src/app/scripting/python/python_engine.cpp000664 001750 001750 00000014600 14477652011 036634 0ustar00ddennedyddennedy000000 000000 mlt-7.22.0/* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "python_engine.hpp" #include "app/scripting/python/register_machinery.hpp" #include "app/log/log.hpp" #include "app/env.hpp" app::scripting::ScriptEngine::Autoregister app::scripting::python::PythonEngine::autoreg; static int counter = 0; class PY_HIDDEN CaptureStream { public: using OwnerT = app::scripting::ScriptExecutionContext; using SignalT = void (OwnerT::*)(const QString&); CaptureStream(OwnerT* owner = nullptr, SignalT signal = nullptr) : owner(owner), signal(signal) {} ~CaptureStream() { if ( owner ) { py::delattr(stream, "write"); py::delattr(stream, "flush"); } } void setup(OwnerT* owner, SignalT signal, py::object dest) { try { dest.attr("write") = py::cpp_function([this](const QString& s){ write(s); }); dest.attr("flush") = py::cpp_function([this](){ flush(); }); dest.attr("isatty") = py::cpp_function([](){ return false; }); stream = dest; this->owner = owner; this->signal = signal; } catch ( const py::error_already_set& pyexc ) { app::log::Log("Python").stream(app::log::Error) << "Could not initialize stream capture:" << pyexc.what(); } } void write(const QString& data) { if ( data.isEmpty() ) return; int from = 0; int to = data.indexOf('\n'); while ( to != -1 ) { QString txt; if ( !buf.isEmpty() ) { txt = buf; buf.clear(); } txt += data.mid(from, to-from); (owner->*signal)(txt); from = to+1; to = data.indexOf('\n', from); } buf += data.mid(from); } void flush() { (owner->*signal)(buf); buf.clear(); } private: OwnerT* owner; SignalT signal; QString buf; py::object stream; }; class PY_HIDDEN app::scripting::python::PythonContext::Private { public: void init_capture(PythonContext* ctx) { sys = py::module::import("sys"); py::object py_stdout = sys.attr("stdout"); py::object py_stderr = sys.attr("stderr"); py_stdin = py::module::import("io").attr("StringIO")(); sys.attr("stdin") = py_stdin; if ( !py_stdout.is(py::none()) ) { stdout_cap.setup(ctx, &PythonContext::stdout_line, py_stdout); stderr_cap.setup(ctx, &PythonContext::stderr_line, py_stderr); } } std::vector my_modules; py::dict globals; py::function compile; const ScriptEngine* engine; py::module sys; CaptureStream stderr_cap, stdout_cap; py::object py_stdin; }; app::scripting::python::PythonContext::PythonContext(const ScriptEngine* engine) { /// @note Not thread safe, pybind11 doesn't support multiple interpreters at once if ( counter == 0 ) py::initialize_interpreter(); counter++; d = std::make_unique(); d->globals = py::globals(); d->compile = py::function(py::module(d->globals["__builtins__"]).attr("compile")); d->engine = engine; d->init_capture(this); } app::scripting::python::PythonContext::~PythonContext() { d.reset(); counter--; if ( counter == 0 ) py::finalize_interpreter(); } void app::scripting::python::PythonContext::expose(const QString& name, const QVariant& obj) { try { d->globals[name.toStdString().c_str()] = obj; } catch ( const py::error_already_set& pyexc ) { throw ScriptError(pyexc.what()); } } QString app::scripting::python::PythonContext::eval_to_string(const QString& code) { std::string std_code = code.toStdString(); bool eval = false; try { d->compile(std_code, "", "eval"); eval = true; } catch ( const py::error_already_set& ) {} try { if ( eval ) return QString::fromStdString(py::repr(py::eval(std_code)).cast()); py::exec(std_code); return {}; } catch ( const py::error_already_set& pyexc ) { throw ScriptError(pyexc.what()); } catch ( const py::builtin_exception& pyexc ) { throw ScriptError(pyexc.what()); } } void app::scripting::python::PythonContext::app_module ( const QString& name ) { try { auto sname = name.toStdString(); const char* cname = sname.c_str(); d->my_modules.push_back(py::module::import(cname)); d->globals[cname] = d->my_modules.back(); } catch ( const py::error_already_set& pyexc ) { log::Log("Python", name).log(pyexc.what(), log::Error); } } class PY_HIDDEN ModuleSetter { public: ModuleSetter(py::module& sys, const QString& append) : sys(sys) { python_path = py::list(sys.attr("path")); py::list python_path_new(sys.attr("path")); python_path_new.append(append); py::setattr(sys, "path", python_path_new); } ~ModuleSetter() { py::setattr(sys, "path", python_path); } py::module& sys; py::list python_path; }; bool app::scripting::python::PythonContext::run_from_module ( const QDir& path, const QString& module, const QString& function, const QVariantList& args ) { ModuleSetter{d->sys, path.path()}; try { py::module exec_module = py::module::import(module.toStdString().c_str()); std::string std_func = function.toStdString(); if ( !py::hasattr(exec_module, std_func.c_str()) ) return false; py::tuple py_args(args.size()); int i = 0; for ( const auto& arg : args ) py_args[i++] = arg; exec_module.attr(std_func.c_str())(*py_args); } catch ( const py::error_already_set& pyexc ) { throw ScriptError(pyexc.what()); } return true; } const app::scripting::ScriptEngine * app::scripting::python::PythonContext::engine() const { return d->engine; } app::scripting::ScriptContext app::scripting::python::PythonEngine::create_context() const { return std::make_unique(this); } void app::scripting::python::PythonEngine::add_module_search_paths(const QStringList& paths) { app::Environment::Variable("PYTHONPATH").push_back(paths); } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/avd/avd_renderer.cpp000664 001750 001750 00000050237 14477652011 030631 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "avd_renderer.hpp" #include "io/svg/detail.hpp" #include "io/svg/svg_renderer.hpp" #include "model/document.hpp" #include "model/shapes/group.hpp" #include "model/shapes/trim.hpp" #include "model/shapes/fill.hpp" #include "model/shapes/stroke.hpp" #include "model/shapes/path.hpp" #include "model/animation/join_animatables.hpp" class glaxnimate::io::avd::AvdRenderer::Private { public: using PropRet = std::vector>; struct Keyframe { QString value; // TODO interpolators }; class AnimationHelper { public: Private* parent = nullptr; QString name; std::map> keyframes = {}; bool animated() const { return !keyframes.empty(); } static QString attr(const QString& name) { return "android:" + name; } qreal frame_to_ms(model::FrameTime f) const { return f * 1000 / parent->fps; } template void render_properties( QDomElement& element, std::vector properties, const Callback& callback ) { model::JoinAnimatables j(std::move(properties), model::JoinAnimatables::Normal); auto xml_values = callback(j.current_value()); for ( const auto& p : xml_values ) element.setAttribute(attr(p.first), p.second); if ( j.animated() ) { for ( const auto& kf : j ) { xml_values = callback(kf.values); for ( const auto& p : xml_values ) { keyframes[p.first][frame_to_ms(kf.time)] = Keyframe{p.second}; } } } } /// \todo Option for propertyValuesHolder+keyframe? QDomElement render_object_animators() const { QDomElement target = parent->dom.createElement("target"); target.setAttribute("android:name", name); QDomElement attr = parent->dom.createElement("aapt:attr"); target.appendChild(attr); attr.setAttribute("name", "android:animation"); QDomElement set = parent->dom.createElement("set"); attr.appendChild(set); for ( const auto& prop : keyframes ) { QString type; if ( prop.first == "pathData" ) type = "pathType"; else if ( prop.first.contains("Color") ) type = "colorType"; else type = "floatType"; auto iter = prop.second.begin(); while ( iter != prop.second.end() ) { auto start = iter->first; QDomElement anim = parent->dom.createElement("objectAnimator"); anim.setAttribute("android:propertyName", prop.first); anim.setAttribute("android:valueType", type); anim.setAttribute("android:startOffset", QString::number(start)); anim.setAttribute("android:valueFrom", iter->second.value); ++iter; if ( iter == prop.second.end() ) break; anim.setAttribute("android:valueTo", iter->second.value); anim.setAttribute("android:duration", QString::number(iter->first - start)); set.appendChild(anim); } } return target; } }; void render(model::Composition* comp) { fps = comp->fps.get(); vector = dom.createElement("vector"); vector.setAttribute("android:width", QString("%1dp").arg(comp->width.get())); vector.setAttribute("android:height", QString("%1dp").arg(comp->height.get())); vector.setAttribute("android:viewportWidth", QString::number(comp->width.get())); vector.setAttribute("android:viewportHeight", QString::number(comp->height.get())); render_comp(comp, vector); } void render_comp(model::Composition* comp, QDomElement& parent) { parent.setAttribute("android:name", unique_name(comp, false)); for ( const auto& layer : comp->shapes ) render_element(layer.get(), parent); } void render_element(model::ShapeElement* elm, QDomElement& parent) { if ( auto l = elm->cast() ) { render_layer(l, parent); } else if ( auto g = elm->cast() ) { render_group(g, parent); } else if ( elm->is_instance() ) { warning(QObject::tr("%s should be in a group").arg(elm->object_name())); } else if ( !elm->is_instance() && !elm->is_instance() ) { warning(QObject::tr("%s is not supported").arg(elm->type_name_human())); } } void warning(const QString& s) { if ( on_warning ) on_warning(s); } QDomElement render_layer_parents(model::Layer* lay, QDomElement& parent) { if ( auto parlay = lay->parent.get() ) { auto p = render_layer_parents(parlay, parent); QDomElement group = dom.createElement("group"); p.appendChild(group); render_transform(parlay->transform.get(), group, unique_name(parlay, true) ); return p; } return parent; } void render_layer(model::Layer* lay, QDomElement& parent) { auto parent_element = parent; QDomElement p = render_layer_parents(lay, parent); auto elm = render_group(lay, p); if ( lay->mask->mask.get() != model::MaskSettings::NoMask ) { auto mask = render_clip_path(lay->shapes[0]); elm.insertBefore(mask, {}); } } QString unique_name(model::DocumentNode* node, bool is_duplicate) { QString base = node->name.get(); if ( base.isEmpty() ) base = "item_" + node->uuid.get().toString(QUuid::Id128); QString name = base; if ( is_duplicate ) name += "_" + QString::number(unique_id++); while ( names.count(name) ) { name = base + "_" + QString::number(unique_id++); } names.insert(name); return name; } QDomElement render_group(model::Group* group, QDomElement& parent) { QDomElement elm = dom.createElement("group"); parent.appendChild(elm); render_transform(group->transform.get(), elm, unique_name(group, false)); model::Fill* fill = nullptr; model::Stroke* stroke = nullptr; model::Trim* trim = nullptr; std::vector> children; std::vector shapes; std::vector groups; for ( const auto& ch : group->shapes ) { if ( auto f = ch->cast() ) fill = f; else if ( auto s = ch->cast() ) stroke = s; else if ( auto t = ch->cast() ) trim = t; else if ( auto g = ch->cast() ) { groups.push_back(g); children.push_back(g); } else if ( auto s = ch->cast() ) { shapes.push_back(s); children.push_back(s); } else { warning(QObject::tr("%s are not supported").arg(ch->type_name_human())); } } bool unify_shapes = !groups.empty(); if ( !shapes.empty() ) unify_shapes = true; else if ( trim ) unify_shapes = trim->multiple.get() == model::Trim::Simultaneously; if ( unify_shapes ) { for ( const auto& g : groups ) render_group(g, elm); if ( !shapes.empty() ) { QString name = shapes.size() == 1 ? unique_name(shapes[0], false) : unique_name(group, true); render_shapes(shapes, name, elm, fill, stroke, trim); } } else { for ( const auto& ch : children ) { if ( ch.index() == 0 ) render_shapes({std::get<0>(ch)}, unique_name(std::get<0>(ch), false), elm, fill, stroke, trim); else render_group(std::get<1>(ch), elm); } } return elm; } void render_shapes( const std::vector& shapes, const QString& name, QDomElement& parent, model::Fill* fill, model::Stroke* stroke, model::Trim* trim ) { if ( shapes.empty() ) return; auto path = dom.createElement("path"); parent.appendChild(path); path.setAttribute("android:name", name); render_shapes_to_path_data(shapes, name, path); render_fill(fill, name, path); render_stroke(stroke, name, path); render_trim(trim, name, path); } void render_shapes_to_path_data(const std::vector& shapes, const QString& name, QDomElement& elem) { std::vector> saved; std::vector paths; paths.reserve(shapes.size()); for ( const auto& sh : shapes ) { if ( auto p = sh->cast() ) { paths.push_back(&p->shape); } else { auto conv = sh->to_path(); collect_paths(conv.get(), paths); saved.push_back(std::move(conv)); } } auto& anim = animator(name); anim.render_properties(elem, paths, [](const std::vector& v) -> PropRet { return { {"pathData", paths_to_path_data(v)}, }; }); } void collect_paths(model::ShapeElement* element, std::vector& paths) { if ( auto p = element->cast() ) { paths.push_back(&p->shape); } else if ( auto g = element->cast() ) { for ( const auto& c : g->shapes ) collect_paths(c.get(), paths); } } static QString paths_to_path_data(const std::vector& paths) { math::bezier::MultiBezier bez; for ( const auto& path : paths ) bez.beziers().push_back(path.value()); return svg::path_data(bez).first; } void render_fill(model::Fill* fill, const QString& name, QDomElement& element) { if ( !fill ) return; render_styler_color(fill, name, "fillColor", element); auto& anim = animator(name); anim.render_properties(element, {&fill->opacity}, [](const std::vector& v) -> PropRet { return { {"fillAlpha", QString::number(v[0].toDouble())}, }; }); element.setAttribute("android:fillType", fill->fill_rule.get() == model::Fill::EvenOdd ? "evenOdd" : "nonZero"); } void render_stroke(model::Stroke* stroke, const QString& name, QDomElement& element) { if ( !stroke ) return; render_styler_color(stroke, name, "strokeColor", element); auto& anim = animator(name); anim.render_properties(element, {&stroke->opacity}, [](const std::vector& v) -> PropRet { return { {"strokeAlpha", QString::number(v[0].toDouble())}, }; }); anim.render_properties(element, {&stroke->width}, [](const std::vector& v) -> PropRet { return { {"strokeWidth", QString::number(v[0].toDouble())}, }; }); element.setAttribute("android:strokeWidth", QString::number(stroke->width.get())); element.setAttribute("android:strokeMiterLimit", QString::number(stroke->miter_limit.get())); switch ( stroke->cap.get() ) { case model::Stroke::RoundCap: element.setAttribute("android:strokeLineCap", "round"); break; case model::Stroke::ButtCap: element.setAttribute("android:strokeLineCap", "butt"); break; case model::Stroke::SquareCap: element.setAttribute("android:strokeLineCap", "square"); break; } switch ( stroke->join.get() ) { case model::Stroke::RoundJoin: element.setAttribute("android:strokeLineJoin", "round"); break; case model::Stroke::MiterJoin: element.setAttribute("android:strokeLineJoin", "miter"); break; case model::Stroke::BevelJoin: element.setAttribute("android:strokeLineJoin", "bevel"); break; } } void render_styler_color(model::Styler* styler, const QString& name, const QString& attr, QDomElement& element) { auto use = styler->use.get(); if ( auto color = use->cast() ) { auto& anim = animator(name); anim.render_properties(element, {&color->color}, [&attr](const std::vector& v) -> PropRet { return { {attr, render_color(v[0].value())}, };}); } else if ( auto gradient = use->cast() ) { render_gradient(attr, gradient, element); } else { auto& anim = animator(name); anim.render_properties(element, {&styler->color}, [&attr](const std::vector& v) -> PropRet { return { {attr, render_color(v[0].value())}, };}); } } void render_gradient(const QString& attr_name, model::Gradient* gradient, QDomElement& element) { auto attr = dom.createElement("aapt:attr"); attr.setAttribute("name", "android:" + attr_name); element.appendChild(attr); auto gradel = dom.createElement("gradient"); attr.appendChild(gradel); switch ( gradient->type.get() ) { case model::Gradient::Linear: gradel.setAttribute("android:type", "linear"); break; case model::Gradient::Radial: gradel.setAttribute("android:type", "radial"); break; case model::Gradient::Conical: gradel.setAttribute("android:type", "sweep"); break; } gradel.setAttribute("startX", gradient->start_point.get().x()); gradel.setAttribute("startY", gradient->start_point.get().y()); gradel.setAttribute("endX", gradient->end_point.get().x()); gradel.setAttribute("endY", gradient->end_point.get().y()); if ( auto cols = gradient->colors.get() ) { for ( const auto& stop : cols->colors.get() ) { auto item = dom.createElement("item"); item.setAttribute("android:color", render_color(stop.second)); item.setAttribute("android:offset", QString::number(stop.first)); } } } void render_trim(model::Trim* trim, const QString& name, QDomElement& element) { if ( !trim ) return; auto& anim = animator(name); anim.render_properties(element, {&trim->start}, [](const std::vector& v) -> PropRet { return { {"trimPathStart", QString::number(v[0].toDouble())}, };}); anim.render_properties(element, {&trim->end}, [](const std::vector& v) -> PropRet { return { {"trimPathEnd", QString::number(v[0].toDouble())}, };}); anim.render_properties(element, {&trim->offset}, [](const std::vector& v) -> PropRet { return { {"trimPathOffset", QString::number(v[0].toDouble())}, };}); } QDomElement render_clip_path(model::ShapeElement* element) { QDomElement clip = dom.createElement("clip-path"); QString name = unique_name(element, false); clip.setAttribute("android:name", name); if ( auto group = element->cast() ) { std::vector shapes = group->docnode_find_by_type(); render_shapes_to_path_data(shapes, name, clip); } else if ( auto shape = element->cast() ) { render_shapes_to_path_data({shape}, name, clip); } else { warning(QObject::tr("%s cannot be a clip path").arg(element->object_name())); return {}; } return clip; } static QString color_comp(int comp) { return QString::number(comp, 16).rightJustified(2, '0'); } static QString render_color(const QColor& color) { return "#" + color_comp(color.alpha()) + color_comp(color.red()) + color_comp(color.green()) + color_comp(color.blue()); } void render_transform(model::Transform* trans, QDomElement& elm, const QString& name) { auto& anim = animator(name); anim.render_properties(elm, {&trans->anchor_point, &trans->position}, [](const std::vector& v) -> PropRet { auto ap = v[0].toPointF(); auto pos = v[1].toPointF() - ap; return { {"pivotX", QString::number(ap.x())}, {"pivotY", QString::number(ap.y())}, {"translateX", QString::number(pos.x())}, {"translateY", QString::number(pos.y())}, }; }); anim.render_properties(elm, {&trans->scale}, [](const std::vector& v) -> PropRet { auto scale = v[0].value(); return { {"scaleX", QString::number(scale.x())}, {"scaleY", QString::number(scale.y())}, }; }); anim.render_properties(elm, {&trans->rotation}, [](const std::vector& v) -> PropRet { return { {"rotation", QString::number(v[0].toDouble())}, }; }); } void render_anim(QDomElement& container) { for ( const auto& p : animations ) { if ( p.second.animated() ) container.appendChild(p.second.render_object_animators()); } } AnimationHelper& animator(const QString& name) { auto iter = animations.find(name); if ( iter == animations.end() ) iter = animations.insert({name, {this, name}}).first; return iter->second; } int fps = 60; int unique_id = 0; QDomDocument dom; QDomElement vector; std::map animations; std::function on_warning; std::unordered_set names; }; glaxnimate::io::avd::AvdRenderer::AvdRenderer(const std::function& on_warning) : d(std::make_unique()) { d->on_warning = on_warning; } glaxnimate::io::avd::AvdRenderer::~AvdRenderer() { } void glaxnimate::io::avd::AvdRenderer::render(model::Composition* comp) { d->render( comp); } QDomElement glaxnimate::io::avd::AvdRenderer::graphics() { return d->vector; } QDomDocument glaxnimate::io::avd::AvdRenderer::single_file() { QDomDocument dom; auto av = dom.createElement("animated-vector"); dom.appendChild(av); av.setAttribute("xmlns", svg::detail::xmlns.at("android")); for ( const auto& p : svg::detail::xmlns ) { if ( p.second.contains("android") ) av.setAttribute("xmlns:" + p.first, p.second); } auto attr = dom.createElement("aapt:attr"); av.appendChild(attr); attr.setAttribute("name", "android:drawable"); attr.appendChild(graphics()); d->render_anim(av); return dom; } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/math/bezier/operations.hpp000664 001750 001750 00000001734 14477652011 031407 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include "bezier.hpp" namespace glaxnimate::math::bezier { /** * \brief Turns all points in the curve to smooth, sutomatically setting tangents */ void auto_smooth(Bezier& curve, int start, int end); /** * \brief Reduces the number of points in a bezier curve */ void simplify(Bezier& curve, qreal threshold); struct ProjectResult { int index = 0; qreal factor = 0; qreal distance = std::numeric_limits::max(); QPointF point = {}; }; /** * \brief Projects a point onto a bezier curve * \param curve The target bezier * \param p The point to project * \returns ProjectResult with the point on \p curve closest to \p p */ ProjectResult project(const Bezier& curve, const QPointF& p); ProjectResult project(const BezierSegment& segment, const QPointF& p); } // namespace glaxnimate::math mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/shapes/precomp_layer.hpp000664 001750 001750 00000004240 14477652011 032232 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include "model/property/reference_property.hpp" #include "model/stretchable_time.hpp" #include "model/shapes/shape.hpp" #include "model/assets/composition.hpp" namespace glaxnimate::model { class PreCompLayer : public ShapeElement { GLAXNIMATE_OBJECT(PreCompLayer) GLAXNIMATE_SUBOBJECT(StretchableTime, timing) GLAXNIMATE_PROPERTY_REFERENCE(Composition, composition, &PreCompLayer::valid_precomps, &PreCompLayer::is_valid_precomp, &PreCompLayer::composition_changed) GLAXNIMATE_PROPERTY(QSizeF, size, {}) GLAXNIMATE_SUBOBJECT(Transform, transform) GLAXNIMATE_ANIMATABLE(float, opacity, 1, &PreCompLayer::opacity_changed, 0, 1, false, PropertyTraits::Percent) public: PreCompLayer(Document* document); QIcon tree_icon() const override; QString type_name_human() const override; void set_time(FrameTime t) override; /** * \brief Returns the (frame) time relative to this layer * * Useful for stretching / remapping etc. * Always use this to get animated property values, * even if currently it doesn't do anything */ FrameTime relative_time(FrameTime time) const; QRectF local_bounding_rect(FrameTime t) const override; QTransform local_transform_matrix(model::FrameTime t) const override; void add_shapes(model::FrameTime t, math::bezier::MultiBezier & bez, const QTransform& transform) const override; QPainterPath to_clip(model::FrameTime t) const override; signals: void opacity_changed(float op); void composition_changed(); protected: QPainterPath to_painter_path_impl(model::FrameTime t) const override; void on_paint(QPainter*, FrameTime, PaintMode, model::Modifier*) const override; void on_composition_changed(model::Composition* old_comp, model::Composition* new_comp) override; private slots: void on_transform_matrix_changed(); private: std::vector valid_precomps() const; bool is_valid_precomp(DocumentNode* node) const; void refresh_owner_composition(); }; } // namespace glaxnimate::model mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/animation/keyframe_transition.hpp000664 001750 001750 00000004745 14477652011 034154 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include"math/bezier/solver.hpp" #include #include namespace glaxnimate::model { /** * \brief Describes the easing between two keyframes */ class KeyframeTransition { Q_GADGET public: enum Descriptive { Hold, Linear, Ease, Fast, Overshoot, Custom, }; Q_ENUM(Descriptive) KeyframeTransition() = default; KeyframeTransition(const QPointF& before_handle, const QPointF& after_handle, bool hold = false); explicit KeyframeTransition(Descriptive before, Descriptive after); explicit KeyframeTransition(Descriptive descriptive); const math::bezier::CubicBezierSolver& bezier() const { return bezier_; } bool hold() const { return hold_; } Descriptive before_descriptive() const; Descriptive after_descriptive() const; QPointF before() const { return bezier_.points()[1]; } QPointF after() const { return bezier_.points()[2]; } /** * \brief Get interpolation factor * \param ratio in [0, 1]. Determines the time ratio (0 = before, 1 = after) * \return A value in [0, 1]: the corresponding interpolation factor * * If the bezier is defined as B(t) = (x,y). This gives y given x. */ double lerp_factor(double ratio) const; /** * \brief Get the bezier parameter at the given time * \param ratio in [0, 1]. Determines the time ratio (0 = before, 1 = after) * \return A value in [0, 1]: the corresponding bezier parameter * * If the bezier is defined as B(t) = (x,y). This gives t given x. */ double bezier_parameter(double ratio) const; void set_hold(bool hold); void set_before_descriptive(Descriptive d); void set_after_descriptive(Descriptive d); void set_handles(const QPointF& before, const QPointF& after); void set_before(const QPointF& before); void set_after(const QPointF& after); /** * \brief Split the transition at \p x * \return The transitions before/after the split */ std::pair split(double x) const; std::pair split_t(double t) const; private: math::bezier::CubicBezierSolver bezier_ { QPointF(0, 0), QPointF(0, 0), QPointF(1, 1), QPointF(1, 1) }; bool hold_ = false; }; } // namespace glaxnimate::model mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/aep/gradient_xml.hpp000664 001750 001750 00000005676 14477652011 030655 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include #include "cos.hpp" #include "ae_project.hpp" namespace glaxnimate::io::aep { CosArray xml_array(const QDomElement& element); CosObject xml_map(const QDomElement& element); CosObject xml_list(const QDomElement& element); CosValue xml_value(const QDomElement& element); inline const CosValue& get_value(const CosObject& v, const QString& key) { return v->at(key); } inline const CosValue& get_value(const CosArray& v, int key) { return v->at(key); } inline const CosValue& get_value(const CosValue& v, const QString& key) { return v.get()->at(key); } inline const CosValue& get_value(const CosValue& v, int key) { return v.get()->at(key); } template const CosValue& get(const V& v) { return v; } template const CosValue& get(const V& v, const H& key, const T&... keys) { return get(get_value(v, key), keys...); } template const auto& get_as(const V& v, const T&... keys) { return get(v, keys...).template get(); } struct GradientStopAlpha { static constexpr const char* const name1 = "Alpha Stops"; static constexpr const char* const name2 = "Stops Alpha"; using Value = double; static Value get(const CosArray::element_type* arr) { return arr->at(2).get(); } }; struct GradientStopColor { static constexpr const char* const name1 = "Color Stops"; static constexpr const char* const name2 = "Stops Color"; using Value = QColor; static Value get(const CosArray::element_type* arr) { return QColor::fromRgbF( arr->at(2).get(), arr->at(3).get(), arr->at(4).get() ); } }; template GradientStops get_gradient_stops(const CosValue& data) { using Stop = GradientStop; GradientStops stops; for ( auto& stop : *get_as(data, Policy::name1, "Stops List") ) { auto& stop_arr = get(stop.second, Policy::name2); auto ptr = stop_arr.template get().get(); qreal offset = get_as(stop_arr, 0); qreal midpoint = get_as(stop_arr, 1); auto value = Policy::get(ptr); stops.push_back(Stop{offset, midpoint, value}); } std::sort(stops.begin(), stops.end(), [](const Stop& a, const Stop& b) { return a.offset <= b.offset; }); return stops; } Gradient parse_gradient_xml(const CosValue& value); Gradient parse_gradient_xml(const QString& xml); } // namespace glaxnimate::io::aep mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/plugin/io.hpp000664 001750 001750 00000003304 14477652011 026713 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include "io/io_registry.hpp" #include "service.hpp" namespace glaxnimate::plugin { class IoService : public PluginService { public: ServiceType type() const override { return ServiceType::IoFormat; } QString name() const override { return label; } void enable() override; void disable() override; QIcon service_icon() const override { return QIcon::fromTheme("document-save"); } QString slug; QString label; QStringList extensions; PluginScript open; PluginScript save; bool auto_open; io::ImportExport* registered = nullptr; }; class IoFormat : public io::ImportExport { Q_OBJECT public: IoFormat(IoService* service) : service(service) {} QString slug() const override { return service->slug; } QString name() const override { return service->label; } QStringList extensions() const override { return service->extensions; } bool can_save() const override { return service->save.valid(); } bool can_open() const override { return service->open.valid(); } std::unique_ptr open_settings() const override; std::unique_ptr save_settings(model::Composition*) const override; protected: bool auto_open() const override { return service->auto_open; } bool on_save(QIODevice& file, const QString&, model::Composition* comp, const QVariantMap&) override; bool on_open(QIODevice& file, const QString&, model::Document* document, const QVariantMap&) override; private: IoService* service; }; } // namespace glaxnimate::plugin mlt-7.22.0/src/modules/glaxnimate/glaxnimate/external/QtAppSetup/src/app/log/listener_file.hpp000664 001750 001750 00000001536 14477652011 034140 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include "app/log/logger.hpp" namespace app::log { class ListenerFile: public LogListener { public: explicit ListenerFile(const QString& filename) : file(filename) { file.open(QFile::WriteOnly|QFile::Text); } protected: void on_line(const LogLine& line) override { QString log; log += line.time.toString(Qt::ISODate); log += ' '; log += Logger::severity_name(line.severity); log += ' '; log += line.source; log += ' '; log += line.source_detail; log += ' '; log += line.message; log += '\n'; file.write(log.toUtf8()); file.flush(); } private: QFile file; }; } // namespace app::log mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/math/bezier/point.cpp000664 001750 001750 00000001723 14477652011 030346 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "point.hpp" using namespace glaxnimate; void math::bezier::Point::adjust_handles_from_type() { if ( type != math::bezier::PointType::Corner ) { math::PolarVector p_in(tan_in - pos); math::PolarVector p_out(tan_out - pos); qreal in_angle = (p_in.angle + p_out.angle + pi) / 2; if ( p_in.angle < p_out.angle ) in_angle += pi; p_in.angle = in_angle; p_out.angle = in_angle + pi; if ( type == math::bezier::PointType::Symmetrical ) p_in.length = p_out.length = (p_in.length + p_out.length) / 2; tan_in = p_in.to_cartesian() + pos; tan_out = p_out.to_cartesian() + pos; } } void math::bezier::Point::transform(const QTransform& t) { pos = t.map(pos); tan_in = t.map(tan_in); tan_out = t.map(tan_out); } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/utils/quantize.hpp000664 001750 001750 00000003207 14477652011 030010 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include namespace glaxnimate::utils::quantize { using ColorFrequency = std::pair; /** * \brief Returns the \p k colors that appear most frequently in \p image. */ std::vector k_modes(const QImage& image, int k); enum KMeansMatch { None, MostFrequent, Closest, }; /** * \brief k-means Algorithm */ std::vector k_means(const QImage& image, int k, int iterations, KMeansMatch match); /** * \brief Octree Algorithm */ std::vector octree(const QImage& image, int k); /** * \brief Edge cutoff * * Selects the most frequent color, then excludes areas within 1 pixel of that color * Then it repeats until all the colors have been found * * \param image Image to get the colors for * \param max_colors Maximum number of colors * \param min_frequency A color must have at least min_frequency * image.width * image.height pixels to be selected */ std::vector edge_exclusion_modes(const QImage& image, int max_colors, qreal min_frequency = 0.0005); /** * \brief Counts pixel values and returns a list of [rgba, count] pairs * \param image The image to analyze * \param alpha_threshold Minimum alpha value [0-255] for a color to be included */ std::vector color_frequencies(const QImage& image, int alpha_threshold = 128); /** * \brief Returns a quantized image with the given colors */ QImage quantize(const QImage& source, const std::vector& colors); } // namespace glaxnimate::utils::quantize mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/plugin/000775 001750 001750 00000000000 14477652011 025573 5ustar00ddennedyddennedy000000 000000 mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/aep/aep_loader.cpp000664 001750 001750 00000132011 14477652011 030246 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "aep_loader.hpp" #include "model/shapes/rect.hpp" #include "model/shapes/ellipse.hpp" #include "model/shapes/fill.hpp" #include "model/shapes/stroke.hpp" #include "model/shapes/image.hpp" #include "model/shapes/polystar.hpp" #include "model/shapes/path.hpp" #include "model/shapes/trim.hpp" #include "model/shapes/offset_path.hpp" #include "model/shapes/inflate_deflate.hpp" #include "model/shapes/zig_zag.hpp" #include "model/shapes/round_corners.hpp" #include "model/shapes/repeater.hpp" #include "model/shapes/precomp_layer.hpp" #include "model/shapes/text.hpp" #include "model/animation/join_animatables.hpp" using namespace glaxnimate::io::aep; using namespace glaxnimate; using glaxnimate::io::ImportExport; static constexpr std::array label_colors = { 0x00000000, // None 0xffb4393b, // Red 0xffe2d759, // Yellow 0xffabcbc8, // Aqua 0xffe5bcca, // Pink 0xffa9aac9, // Lavender 0xffe5c19f, // Peach 0xffb4c7b4, // Sea Foam 0xff687fdd, // Blue 0xff4ea350, // Green 0xff8d3299, // Purple 0xffe79228, // Orange 0xff7e442c, // Brown 0xfff371d5, // Fuchsia 0xff43a2a4, // Cyan 0xffa7967a, // Sandstone 0xff203f1f // Dark Green }; void glaxnimate::io::aep::AepLoader::load_project() { for ( const auto& comp : project.compositions ) get_comp(comp->id); for ( const auto& pair : project.assets ) load_asset(pair.second); for ( const auto& comp : project.compositions ) load_comp(*comp); } void glaxnimate::io::aep::AepLoader::load_asset(const glaxnimate::io::aep::FolderItem* item) { if ( item->type() == FolderItem::Asset ) { auto image = std::make_unique(document); auto asset = static_cast(item); if ( asset->path.exists() ) { image->filename.set(asset->path.filePath()); } else { // Handle collected assets QFileInfo path(asset_path.filePath(asset->path.fileName())); if ( !path.exists() ) warning(AepFormat::tr("External asset not found: %1").arg(asset->path.filePath())); else image->filename.set(path.filePath()); } image->name.set(item->name); images[item->id] = image.get(); document->assets()->images->values.insert(std::move(image)); asset_size[item->id] = QPointF(asset->width, asset->height); } else if ( item->type() == FolderItem::Solid ) { auto color = std::make_unique(document); auto solid = static_cast(item); color->color.set(solid->color); color->name.set(solid->name); colors[item->id] = {color.get(), solid}; document->assets()->colors->values.insert(std::move(color)); asset_size[item->id] = QPointF(solid->width, solid->height); } else if ( item->type() == FolderItem::Composition ) { auto aecomp = static_cast(item); asset_size[item->id] = QPointF(aecomp->width, aecomp->height); auto comp = get_comp(item->id); comp->width.set(aecomp->width); comp->height.set(aecomp->height); comp->name.set(aecomp->name); } } void glaxnimate::io::aep::AepLoader::warning(const QString& msg) { io->warning(msg); } void glaxnimate::io::aep::AepLoader::info(const QString& msg) { io->information(msg); } static bool unknown_mn(glaxnimate::io::ImportExport* io, const QString& context, const QString& mn) { io->information(AepFormat::tr("Unknown property \"%1\" of \"%2\"").arg(mn).arg(context)); return true; } model::Composition * glaxnimate::io::aep::AepLoader::get_comp(glaxnimate::io::aep::Id id) { if ( !id ) return nullptr; auto& comp = comps[id]; if ( !comp ) comp = document->assets()->add_comp_no_undo(); return comp; } struct glaxnimate::io::aep::AepLoader::CompData { struct PendingLayer { model::Layer* layer; Id parent = 0; Id track_matte = 0; }; void resolve() { for ( const auto& p : pending ) { if ( p.parent ) p.layer->parent.set(layers.at(p.parent)); /// \todo track matte } } model::Composition* comp; const Composition* ae_comp; std::unordered_map layers = {}; std::vector pending = {}; }; void glaxnimate::io::aep::AepLoader::load_comp(const glaxnimate::io::aep::Composition& ae_comp) { auto comp = get_comp(ae_comp.id); comp->name.set(ae_comp.name); comp->width.set(ae_comp.width); comp->height.set(ae_comp.height); comp->fps.set(ae_comp.framerate); comp->animation->first_frame.set(ae_comp.in_time); comp->animation->last_frame.set(ae_comp.out_time); comp->group_color.set(ae_comp.color); comp->group_color.set(label_colors[int(ae_comp.label_color)]); CompData data{comp, &ae_comp}; for ( const auto& layer : ae_comp.layers ) load_layer(*layer, data); data.resolve(); } namespace { template T convert_value(const PropertyValue& v) { return std::get(v.value); } template<> float convert_value(const PropertyValue& v) { return convert_value(v); } template<> int convert_value(const PropertyValue& v) { return convert_value(v); } template<> QPointF convert_value(const PropertyValue& v) { if ( v.type() == PropertyValue::Vector2D ) return std::get(v.value); auto p = convert_value(v.value); return {p.x(), p.y()}; } template<> QVector2D convert_value(const PropertyValue& v) { if ( v.type() == PropertyValue::Vector2D ) { auto p = std::get(v.value); return QVector2D(p.x(), p.y()); } else { auto p = convert_value(v.value); return {p.x(), p.y()}; } } template<> QSizeF convert_value(const PropertyValue& v) { auto p = convert_value(v.value); return {p.x(), p.y()}; } template<> math::bezier::Bezier convert_value(const PropertyValue& v) { const auto& aebez = std::get(v.value); math::bezier::Bezier bez; int count = aebez.points.size(); for ( int i = 0; i < count; i += 3 ) { /// \todo smooth etc? math::bezier::Point p(aebez.convert_point(aebez.points[i])); if ( i > 0 ) p.tan_in = aebez.convert_point(aebez.points[i-1]); else p.tan_in = aebez.convert_point(aebez.points.back()); p.tan_out = aebez.convert_point(aebez.points[i+1]); if ( i == count - 1 && aebez.closed && math::fuzzy_compare(bez[0].pos, p.pos) ) { bez[0].tan_in = p.tan_in; break; } bez.push_back(p); } bez.set_closed(aebez.closed); return bez; } template<> QGradientStops convert_value(const PropertyValue& v) { return convert_value(v).to_qt(); } template struct DefaultConverter { T operator()(const PropertyValue& v) const { return convert_value(v); } }; template> bool load_property(model::Property& prop, const Property& ae_prop, const Converter& conv = {}) { if ( ae_prop.value.type() ) prop.set(conv(ae_prop.value)); else if ( !ae_prop.keyframes.empty() && ae_prop.keyframes[0].value.type() ) prop.set(conv(ae_prop.keyframes[0].value)); else return false; return true; } template void kf_extra_data(model::Keyframe* kf, const Keyframe& aekf) { (void)kf; (void)aekf; } template<> void kf_extra_data(model::Keyframe* kf, const Keyframe& aekf) { auto p = kf->get(); kf->set_point(math::bezier::Point( p, p + aekf.in_tangent, p + aekf.out_tangent )); } qreal vector_length(const std::vector& v) { qreal len = 0; for ( double a : v ) len += a * a; return math::sqrt(len); } model::KeyframeTransition keyframe_transition(const Property& prop, const Keyframe& kf, const Keyframe& next_kf) { qreal duration = next_kf.time - kf.time; if ( qFuzzyIsNull(duration) ) return model::KeyframeTransition(model::KeyframeTransition::Linear); qreal average_speed = 0; if ( prop.type == PropertyType::Position ) { math::bezier::BezierSegment bez; if ( kf.value.type() == PropertyValue::Vector2D ) { bez[0] = std::get(kf.value.value); bez[3] = std::get(next_kf.value.value); } else { auto p = std::get(kf.value.value); bez[0] = {p.x(), p.y()}; p = std::get(next_kf.value.value); bez[3] = {p.x(), p.y()}; } bez[1] = kf.out_tangent; bez[2] = kf.in_tangent; average_speed = math::bezier::LengthData(math::bezier::CubicBezierSolver(bez), 20).length(); } else if ( prop.type == PropertyType::NoValue ) { average_speed = 1; } else { average_speed = math::abs(kf.value.magnitude() - next_kf.value.magnitude()); } average_speed /= duration; qreal out_influence = vector_length(kf.out_influence); qreal in_influence = vector_length(kf.in_influence); qreal out_speed = vector_length(kf.out_speed); qreal in_speed = vector_length(kf.in_speed); QPointF ease_out; QPointF ease_in; ease_out.setX(out_influence); ease_in.setX(1 - in_influence); if ( qFuzzyIsNull(average_speed) ) { ease_out.setY(out_influence); ease_in.setY(1 - in_influence); } else { ease_out.setY(out_influence * out_speed / average_speed); ease_in.setY(1 - in_influence * in_speed / average_speed); } return model::KeyframeTransition(ease_out, ease_in); } template> bool load_property( model::AnimatedProperty& prop, const Property& ae_prop, const Converter& conv = {} ) { if ( !ae_prop.animated && ae_prop.value.type() ) { prop.set(conv(ae_prop.value)); return true; } for ( std::size_t i = 0; i < ae_prop.keyframes.size(); i++ ) { const auto& aekf = ae_prop.keyframes[i]; auto kf = prop.set_keyframe(aekf.time, conv(aekf.value)); kf_extra_data(kf, aekf); /// \todo easing if ( aekf.transition_type == KeyframeTransitionType::Hold ) kf->set_transition(model::KeyframeTransition(model::KeyframeTransition::Hold)); else if ( aekf.transition_type == KeyframeTransitionType::Linear ) kf->set_transition(model::KeyframeTransition(model::KeyframeTransition::Linear)); else if ( i + 1 < ae_prop.keyframes.size() ) kf->set_transition(keyframe_transition(ae_prop, aekf, ae_prop.keyframes[i+1])); } return true; } template> void load_property_check( ImportExport* io, PropT& prop, const PropertyBase& ae_prop, const QString& match_name, const Converter& conv = {} ) { if ( ae_prop.class_type() != PropertyBase::Property ) { io->warning(AepFormat::tr("Expected property for %1").arg(match_name)); return; } try { if ( !load_property(prop, static_cast(ae_prop), conv) ) io->warning(AepFormat::tr("Could convert %1").arg(match_name)); } catch ( const std::bad_variant_access& ) { io->error(AepFormat::tr("Invalid value for %1").arg(match_name)); } } template> bool load_property(ImportExport* io, PropT& prop, const PropertyPair& ae_prop, const char* match_name, const Converter& conv = {}) { if ( ae_prop.match_name != match_name ) return false; load_property_check(io, prop, *ae_prop.value, ae_prop.match_name, conv); return true; } bool convert_shape_reverse(const PropertyValue& v) { return convert_value(v) == 3; } template T convert_divide(const PropertyValue& v) { return convert_value(v) / Divisor; } template T convert_enum(const PropertyValue& v) { return T(convert_value(v)); } template<> model::Fill::Rule convert_enum(const PropertyValue& v) { if ( convert_value(v) == 2 ) return model::Fill::Rule::EvenOdd; return model::Fill::Rule::NonZero; } template<> model::Stroke::Cap convert_enum(const PropertyValue& v) { switch ( convert_value(v) ) { default: case 1: return model::Stroke::Cap::ButtCap; case 2: return model::Stroke::Cap::RoundCap; case 3: return model::Stroke::Cap::SquareCap; } } template<> model::Stroke::Join convert_enum(const PropertyValue& v) { switch ( convert_value(v) ) { default: case 1: return model::Stroke::Join::MiterJoin; case 2: return model::Stroke::Join::RoundJoin; case 3: return model::Stroke::Join::BevelJoin; } } struct AnchorMult { QPointF operator()(const PropertyValue& v) const { auto a = convert_value(v); return {a.x() * p.x(), a.y() * p.y()}; } QPointF p; }; bool load_position_component(io::ImportExport* io, const PropertyGroup& group, int suffix, model::AnimatedProperty& out, bool force) { auto pair = group.get_pair(QString("ADBE Position_%1").arg(suffix)); if ( !pair ) return false; if ( pair->value->class_type() != PropertyBase::Property ) return false; const Property& prop = static_cast(*pair->value); if ( !prop.is_component && !force ) return false; load_property_check(io, out, prop, pair->match_name); return true; } void load_transform(io::ImportExport* io, model::Transform* tf, const PropertyBase& prop, model::AnimatedProperty* opacity, const QPointF& anchor_mult, bool divide_100) { if ( prop.class_type() != PropertyBase::PropertyGroup ) { io->warning(AepFormat::tr("Expected property group for transform")); return; } const PropertyGroup& g = static_cast(prop); bool is_3d = false; int split_position = 1; for ( const auto& p : g.properties ) { if ( p.match_name.endsWith("Anchor Point") || p.match_name.endsWith("Anchor") ) load_property_check(io, tf->anchor_point, *p.value, p.match_name, AnchorMult{anchor_mult}); else if ( p.match_name.endsWith("Position") ) { if ( p.value->class_type() == PropertyBase::Property ) { const Property& pos_prop = static_cast(*p.value); if ( pos_prop.split ) { split_position = 2; } else { split_position = 0; load_property_check(io, tf->position, *p.value, p.match_name); } } } else if ( p.match_name.endsWith("Scale") ) load_property_check(io, tf->scale, *p.value, p.match_name, divide_100 ? &convert_divide<100, QVector2D> : &convert_divide<1, QVector2D>); else if ( p.match_name.endsWith("Rotation") || p.match_name.endsWith("Rotate Z") ) load_property_check(io, tf->rotation, *p.value, p.match_name); else if ( opacity && p.match_name.endsWith("Opacity") ) load_property_check(io, *opacity, *p.value, p.match_name, divide_100 ? &convert_divide<100> : &convert_divide<1>); else if ( p.match_name.endsWith("Rotate X") || p.match_name.endsWith("Rotate Y") || p.match_name.endsWith("Orientation") || p.match_name.endsWith("Position_2") ) is_3d = true; else if ( !p.match_name.endsWith("Position_1") && !p.match_name.endsWith("Position_0") && !p.match_name.endsWith("Opacity") && !p.match_name.endsWith("Envir Appear in Reflect") ) io->information(AepFormat::tr("Unknown property \"%1\"").arg(p.match_name)); } if ( split_position ) { model::Document dummydoc(""); model::Object dummy(&dummydoc); model::AnimatedProperty ax(&dummy, "", 0); model::AnimatedProperty ay(&dummy, "", 0); bool force_split = split_position == 2; bool xok = load_position_component(io, g, 0, ax, force_split); bool yok = load_position_component(io, g, 1, ay, force_split); if ( split_position == 1 ) force_split = xok || yok; if ( force_split ) { model::JoinAnimatables join({&ax, &ay}); join.apply_to(&tf->position, [](float x, float y) -> QPointF { return QPointF(x, y); }, &ax, &ay); } } if ( is_3d ) { /// \todo figure a way of determining whether the transform is actually 3D /// as layer transfoms seem to often have the 3D properties (void)is_3d; // warning(AepFormat::tr("3D transforms are not supported")); } } template struct PropertyConverterBase { virtual ~PropertyConverterBase() noexcept = default; virtual void load(ImportExport* io, Obj* object, const PropertyBase& ae_prop) const = 0; virtual void set_default(Obj* object) const = 0; }; template> struct PropertyConverter : PropertyConverterBase { PropertyConverter(PropT (Base::*prop), const char* match_name, const Converter& converter, const std::optional& default_value = {}) : prop(prop), match_name(match_name), converter(converter), default_value(default_value) {} void load(ImportExport* io, Obj* object, const PropertyBase& ae_prop) const override { load_property_check(io, object->*prop, ae_prop, match_name, converter); } void set_default(Obj* object) const override { if ( default_value ) (object->*prop).set(*default_value); } PropT Base::*prop; QString match_name; Converter converter = {}; std::optional default_value ; }; template struct ObjectConverterBase { virtual ~ObjectConverterBase() noexcept = default; virtual std::unique_ptr load(ImportExport* io, model::Document* document, const PropertyPair& prop) const = 0; }; template struct ObjectConverterFunctor : public ObjectConverterBase { template ObjectConverterFunctor(F&& functor) : functor(std::forward(functor)) {} std::unique_ptr load(ImportExport* io, model::Document* document, const PropertyPair& prop) const override { return functor(io, document, prop); } FuncT functor; }; struct FallbackConverterBase { virtual ~FallbackConverterBase() noexcept = default; virtual void set_default() const = 0; virtual void load_property(ImportExport* io, model::Document* document, const PropertyPair& prop_parent, const PropertyPair& prop) const = 0; }; template struct FallbackConverter; template struct ObjectConverter : public ObjectConverterBase { std::unique_ptr load(ImportExport* io, model::Document* document, const PropertyPair& prop) const override { return load_object(io, document, prop); } void set_default(Obj* object, FallbackConverterBase* fallback) const { for ( const auto& conv : converters ) if ( conv.second ) conv.second->set_default(object); if ( fallback ) fallback->set_default(); } void load_property(Obj* object, ImportExport* io, model::Document* document, const PropertyPair& prop_parent, const PropertyPair& prop, FallbackConverterBase* fallback) const { auto it = converters.find(prop.match_name); if ( it == converters.end() ) { if ( fallback ) fallback->load_property(io, document, prop_parent, prop); else unknown_mn(io, prop_parent.match_name, prop.match_name); } else if ( it->second ) { it->second->load(io, object, *prop.value); } } void load_properties(Obj* object, ImportExport* io, model::Document* document, const PropertyPair& prop, FallbackConverterBase* fallback = nullptr) const { set_default(object, fallback); for ( const auto& p : *prop.value ) this->load_property(object, io, document, prop, p, fallback); } std::unique_ptr load_object(ImportExport* io, model::Document* document, const PropertyPair& prop) const { auto object = std::make_unique(document); load_properties(object.get(), io, document, prop); return object; } template> ObjectConverter& prop(PropT O2::* property, const char* match_name, const Converter& conv = {}) { auto ptr = std::make_unique>(property, match_name, conv); converters.emplace(match_name, std::move(ptr)); return *this; } template> ObjectConverter& prop(PropT O2::*property, const char* match_name, const Converter& conv, const T& default_value) { converters.emplace(match_name, std::make_unique>(property, match_name, conv, default_value)); return *this; } ObjectConverter& ignore(const char* match_name) { converters.emplace(match_name, nullptr); return *this; } FallbackConverter fallback(Obj* object, FallbackConverterBase* next) const { return {object, this, next}; } std::unordered_map>> converters; }; template struct FallbackConverter : public FallbackConverterBase { FallbackConverter(Obj* object, const ObjectConverter* converter, FallbackConverterBase* next) : object(object), converter(converter), next(next) {} void set_default() const override { converter->set_default(object, next); } void load_property(ImportExport* io, model::Document* document, const PropertyPair& prop_parent, const PropertyPair& prop) const override { converter->load_property(object, io, document, prop_parent, prop, next); } Obj* object; const ObjectConverter* converter; FallbackConverterBase* next; }; template struct ObjectFactory { std::unique_ptr load(ImportExport* io, model::Document* document, const PropertyPair& prop) const { auto it = converters.find(prop.match_name); if ( it == converters.end() ) return {}; return it->second->load(io, document, prop); } template void obj(const char* match_name, FuncT&& func) { assert(converters.count(match_name) == 0); auto up = std::make_unique>>(std::forward(func)); converters.emplace(match_name, std::move(up)); } template ObjectConverter& obj(const char* match_name) { assert(converters.count(match_name) == 0); auto up = std::make_unique>(); auto ptr = up.get(); converters.emplace(match_name, std::move(up)); return *ptr; } std::unordered_map>> converters; }; std::unique_ptr create_shape(ImportExport* io, model::Document* document, const PropertyPair& prop); std::unique_ptr load_shape(ImportExport* io, model::Document* document, const PropertyPair& prop) { auto shape = create_shape(io, document, prop); if ( shape && prop.value->class_type() == PropertyBase::PropertyGroup ) { const auto& gp = static_cast(*prop.value); shape->visible.set(gp.visible); } return shape; } const ObjectConverter& gradient_converter() { static ObjectConverter gradient; static bool initialized = false; if ( !initialized ) { initialized = true; gradient .prop(&model::Gradient::type, "ADBE Vector Grad Type", &convert_enum) .prop(&model::Gradient::start_point, "ADBE Vector Grad Start Pt") .prop(&model::Gradient::end_point, "ADBE Vector Grad End Pt") .ignore("ADBE Vector Grad HiLite Length") /// \todo .ignore("ADBE Vector Grad HiLite Angle") /// \todo ; } return gradient; } const ObjectConverter& gradient_stop_converter() { static ObjectConverter gradient; static bool initialized = false; if ( !initialized ) { initialized = true; gradient .prop(&model::GradientColors::colors, "ADBE Vector Grad Colors") ; } return gradient; } template std::unique_ptr load_gradient(const ObjectConverter* base_converter, ImportExport* io, model::Document* document, const PropertyPair& prop) { auto shape = std::make_unique(document); auto grad_colors = document->assets()->gradient_colors->values.insert( std::make_unique(document) ); auto grad = document->assets()->gradients->values.insert( std::make_unique(document) ); grad->end_point.set({100, 0}); // default value grad->colors.set(grad_colors); shape->use.set(grad); auto f1 = gradient_stop_converter().fallback(grad_colors, nullptr); auto f2 = gradient_converter().fallback(grad, &f1); base_converter->load_properties(shape.get(), io, document, prop, &f2); auto* highlight_len = prop.value->get_pair("ADBE Vector Grad HiLite Length"); auto* highlight_angle = prop.value->get_pair("ADBE Vector Grad HiLite Angle"); if ( highlight_len || highlight_angle ) { model::Document dummydoc(""); model::Object dummy(&dummydoc); model::AnimatedProperty length(&dummy, "", 0); model::AnimatedProperty angle(&dummy, "", 0); if ( highlight_len ) load_property_check(io, length, *highlight_len->value, highlight_len->match_name); if ( highlight_angle ) load_property_check(io, angle, *highlight_angle->value, highlight_angle->match_name); model::JoinAnimatables join({&grad->start_point, &grad->end_point, &length, &angle}); join.apply_to(&grad->highlight, [](const QPointF& p, const QPointF& e, float length, float angle) -> QPointF { angle = math::deg2rad(angle + 90); length = math::length(e - p) * length / 100; return p + math::from_polar(length, angle); }, &grad->start_point, &grad->end_point, &length, &angle); } else { grad->highlight.set(grad->start_point.get()); } return shape; } /** * \brief Checks for the default rectangle created by AE with merge path * bodymovin has a similar check on export */ bool is_merge_rect(const model::ShapeElement& shape) { auto path = shape.cast(); if ( !path ) return false; if ( path->shape.animated() ) return false; const auto& bez = path->shape.get(); if ( !bez.closed() || bez.size() != 4 ) return false; for ( const auto& p : bez ) if ( !math::fuzzy_compare(p.pos, p.tan_in) || !math::fuzzy_compare(p.pos, p.tan_out) ) return false; std::array x; std::array y; for ( int i = 0; i < 4; i++ ) { x[i] = bez[i].pos.x(); y[i] = bez[i].pos.y(); } std::sort(x.begin(), x.end()); std::sort(y.begin(), y.end()); return qFuzzyIsNull(x[0]-x[1]) && qFuzzyIsNull(x[2]-x[3]) && qFuzzyIsNull(y[0]-y[1]) && qFuzzyIsNull(y[2]-y[3]); } bool skip_merge(const PropertyPair& prop) { if ( prop.match_name != "ADBE Vector Filter - Merge" ) return false; auto type = prop.value->get("ADBE Vector Merge Type"); if ( !type || type->class_type() != PropertyBase::Property ) return false; auto type_prop = static_cast(type); if ( type_prop->animated ) return false; if ( !qFuzzyCompare(type_prop->value.magnitude(), 4) ) return false; return true; } void load_shape_list(ImportExport* io, model::Document* document, const PropertyBase& properties, model::ShapeListProperty& shapes) { model::ShapeElement* merge_rect = nullptr; for ( const auto& prop : properties ) { if ( merge_rect && skip_merge(prop) ) { shapes.remove(shapes.index_of(merge_rect)); merge_rect = nullptr; continue; } if ( auto shape = load_shape(io, document, prop) ) { if ( !merge_rect && is_merge_rect(*shape) ) merge_rect = shape.get(); shapes.insert(std::move(shape), 0); } } } const ObjectFactory& shape_factory() { static ObjectFactory factory; static bool initialized = false; if ( !initialized ) { initialized = true; factory.obj("ADBE Vector Group", [](ImportExport* io, model::Document* document, const PropertyPair& prop) { auto gp = std::make_unique(document); load_transform(io, gp->transform.get(), (*prop.value)["ADBE Vector Transform Group"], &gp->opacity, {1, 1}, true); load_shape_list(io, document, (*prop.value)["ADBE Vectors Group"], gp->shapes); return gp; }); factory.obj("ADBE Vector Shape - Rect") .prop(&model::Rect::reversed, "ADBE Vector Shape Direction", &convert_shape_reverse) .prop(&model::Rect::position, "ADBE Vector Rect Position") .prop(&model::Rect::size, "ADBE Vector Rect Size") .prop(&model::Rect::rounded, "ADBE Vector Rect Roundness") ; factory.obj("ADBE Vector Shape - Ellipse") .prop(&model::Ellipse::reversed, "ADBE Vector Shape Direction", &convert_shape_reverse) .prop(&model::Ellipse::position, "ADBE Vector Ellipse Position") .prop(&model::Ellipse::size, "ADBE Vector Ellipse Size") ; factory.obj("ADBE Vector Shape - Star") .prop(&model::PolyStar::reversed, "ADBE Vector Shape Direction", &convert_shape_reverse) .prop(&model::PolyStar::position, "ADBE Vector Star Position") .prop(&model::PolyStar::type, "ADBE Vector Star Type", &convert_enum) .prop(&model::PolyStar::points, "ADBE Vector Star Points") .prop(&model::PolyStar::angle, "ADBE Vector Star Rotation") .prop(&model::PolyStar::inner_radius, "ADBE Vector Star Inner Radius") .prop(&model::PolyStar::outer_radius, "ADBE Vector Star Outer Radius") .prop(&model::PolyStar::inner_roundness, "ADBE Vector Star Inner Roundess", &convert_divide<100>) .prop(&model::PolyStar::outer_roundness, "ADBE Vector Star Outer Roundess", &convert_divide<100>) ; factory.obj("ADBE Vector Shape - Group") .prop(&model::Path::reversed, "ADBE Vector Shape Direction", &convert_shape_reverse) .prop(&model::Path::shape, "ADBE Vector Shape") ; const auto* fill = &factory.obj("ADBE Vector Graphic - Fill") .ignore("ADBE Vector Blend Mode") .prop(&model::Fill::color, "ADBE Vector Fill Color", {}, QColor(255, 0, 0)) .prop(&model::Fill::opacity, "ADBE Vector Fill Opacity", &convert_divide<100>) .prop(&model::Fill::fill_rule, "ADBE Vector Fill Rule", &convert_enum) .ignore("ADBE Vector Composite Order") /// \todo could be parsed ; const auto* stroke = &factory.obj("ADBE Vector Graphic - Stroke") .ignore("ADBE Vector Blend Mode") .prop(&model::Stroke::color, "ADBE Vector Stroke Color", {}, QColor(255, 255, 255)) .prop(&model::Stroke::opacity, "ADBE Vector Stroke Opacity", &convert_divide<100>) .prop(&model::Stroke::width, "ADBE Vector Stroke Width", {}, 2) .prop(&model::Stroke::cap, "ADBE Vector Stroke Line Cap", &convert_enum, model::Stroke::ButtCap) .prop(&model::Stroke::join, "ADBE Vector Stroke Line Join", &convert_enum, model::Stroke::MiterJoin) .prop(&model::Stroke::miter_limit, "ADBE Vector Stroke Miter Limit", {}, 4) .ignore("ADBE Vector Stroke Dashes") .ignore("ADBE Vector Stroke Taper") .ignore("ADBE Vector Stroke Wave") .ignore("ADBE Vector Composite Order") /// \todo could be parsed ; factory.obj("ADBE Vector Filter - RC") .prop(&model::RoundCorners::radius, "ADBE Vector RoundCorner Radius", {}, 10) ; factory.obj("ADBE Vector Filter - Trim") .prop(&model::Trim::start, "ADBE Vector Trim Start", &convert_divide<100>) .prop(&model::Trim::end, "ADBE Vector Trim End", &convert_divide<100>) .prop(&model::Trim::offset, "ADBE Vector Trim Offset", &convert_divide<360>) .prop(&model::Trim::multiple, "ADBE Vector Trim Type", &convert_enum) ; factory.obj("ADBE Vector Filter - Offset") .prop(&model::OffsetPath::amount, "ADBE Vector Offset Amount") .prop(&model::OffsetPath::join, "ADBE Vector Offset Line Join", &convert_enum, model::Stroke::MiterJoin) .prop(&model::OffsetPath::miter_limit, "ADBE Vector Offset Miter Limit", {}, 4) ; factory.obj("ADBE Vector Filter - PB") .prop(&model::InflateDeflate::amount, "ADBE Vector PuckerBloat Amount", &convert_divide<100>) ; factory.obj("ADBE Vector Filter - Zigzag") .prop(&model::ZigZag::amplitude, "ADBE Vector Zigzag Size", {}, 5) .prop(&model::ZigZag::frequency, "ADBE Vector Zigzag Detail", {}, 10) .prop(&model::ZigZag::style, "ADBE Vector Zigzag Points", &convert_enum) ; factory.obj("ADBE Vector Graphic - G-Fill", [fill](ImportExport* io, model::Document* document, const PropertyPair& prop) { return load_gradient(fill, io, document, prop); }); factory.obj("ADBE Vector Graphic - G-Stroke", [stroke](ImportExport* io, model::Document* document, const PropertyPair& prop) { return load_gradient(stroke, io, document, prop); }); factory.obj("ADBE Vector Filter - Repeater", [](ImportExport* io, model::Document* document, const PropertyPair& prop) { auto shape = std::make_unique(document); if ( auto tf = prop.value->get("ADBE Vector Repeater Transform") ) { load_transform(io, shape->transform.get(), *tf, nullptr, {1, 1}, false); const char* pmn = "ADBE Vector Repeater Start Opacity"; if ( auto o = tf->get(pmn) ) load_property_check(io, shape->start_opacity, *o, pmn, &convert_divide<100>); pmn = "ADBE Vector Repeater End Opacity"; if ( auto o = tf->get(pmn) ) load_property_check(io, shape->end_opacity, *o, pmn, &convert_divide<100>); } if ( auto copies = prop.value->get("ADBE Vector Repeater Copies") ) { load_property_check(io, shape->copies, *copies, "ADBE Vector Repeater Copies"); } return shape; }); } return factory; }; std::unique_ptr create_shape(ImportExport* io, model::Document* document, const PropertyPair& prop) { if ( auto shape = shape_factory().load(io, document, prop) ) return shape; io->information(AepFormat::tr("Unknown shape %1").arg(prop.match_name)); return nullptr; } } // namespace void glaxnimate::io::aep::AepLoader::load_layer(const glaxnimate::io::aep::Layer& ae_layer, CompData& data) { auto ulayer = std::make_unique(document); auto layer = ulayer.get(); data.comp->shapes.insert(std::move(ulayer), 0); data.layers[ae_layer.id] = layer; if ( ae_layer.parent_id || ae_layer.matte_id ) data.pending.push_back({layer, ae_layer.parent_id, ae_layer.matte_id}); layer->name.set(ae_layer.name); layer->render.set(!ae_layer.is_guide); layer->animation->first_frame.set(ae_layer.in_time); layer->animation->last_frame.set(ae_layer.out_time); layer->visible.set(ae_layer.properties.visible); /// \todo could be nice to toggle visibility based on solo/shy layer->group_color.set(label_colors[int(ae_layer.label_color)]); layer->transform->position.set({ data.comp->width.get() / 2., data.comp->height.get() / 2., }); QPointF anchor{1, 1}; auto it = asset_size.find(ae_layer.asset_id); if ( it != asset_size.end() && !ae_layer.is_null ) { anchor = it->second; layer->transform->anchor_point.set(anchor / 2); } load_transform(io, layer->transform.get(), ae_layer.properties["ADBE Transform Group"], &layer->opacity, anchor, false); layer->auto_orient.set(ae_layer.auto_orient); /// \todo masks "ADBE Mask Parade" if ( ae_layer.is_null ) return; else if ( ae_layer.asset_id ) asset_layer(layer, ae_layer, data); else if ( ae_layer.type == LayerType::ShapeLayer ) shape_layer(layer, ae_layer, data); else if ( ae_layer.type == LayerType::TextLayer ) text_layer(layer, ae_layer, data); auto mask_parade = ae_layer.properties.get("ADBE Mask Parade"); if ( mask_parade ) { layer->mask->mask.set(model::MaskSettings::Alpha); auto clip_p = std::make_unique(document); auto clip = clip_p.get(); layer->shapes.insert(std::move(clip_p), 0); document->set_best_name(clip, QObject::tr("Clip")); for ( const auto& mask : *mask_parade ) { if ( mask.match_name != "ADBE Mask Atom" ) continue; auto group = std::make_unique(document); auto fill = std::make_unique(document); fill->color.set(QColor(255, 255, 255)); document->set_best_name(fill.get()); auto mask_opacity = mask.value->get_pair("ADBE Mask Opacity"); if ( mask_opacity ) load_property_check(io, fill->opacity, *mask_opacity->value, mask_opacity->match_name, &convert_divide<100>); group->shapes.insert(std::move(fill)); if ( auto expansion = mask.value->get_pair("ADBE Mask Offset") ) { auto offset = std::make_unique(document); document->set_best_name(offset.get()); load_property_check(io, offset->amount, *expansion->value, expansion->match_name); group->shapes.insert(std::move(offset)); } if ( auto shape = mask.value->get_pair("ADBE Mask Shape") ) { auto path = std::make_unique(document); document->set_best_name(path.get()); load_property_check(io, path->shape, *shape->value, shape->match_name, {}); path->closed.set(true); group->shapes.insert(std::move(path)); } clip->shapes.insert(std::move(group)); } } } void glaxnimate::io::aep::AepLoader::shape_layer( model::Layer* layer, const glaxnimate::io::aep::Layer& ae_layer, glaxnimate::io::aep::AepLoader::CompData& ) { load_shape_list(io, document, ae_layer.properties["ADBE Root Vectors Group"], layer->shapes); } void glaxnimate::io::aep::AepLoader::asset_layer( model::Layer* layer, const Layer& ae_layer, CompData& ) { auto img_it = images.find(ae_layer.asset_id); if ( img_it != images.end() ) { auto image = std::make_unique(document); image->image.set(img_it->second); image->name.set(img_it->second->name.get()); if ( layer->name.get().isEmpty() ) layer->name.set(image->name.get()); layer->shapes.insert(std::move(image)); return; } auto comp_it = comps.find(ae_layer.asset_id); if ( comp_it != comps.end() ) { /// \todo ADBE Time Remapping /// \todo Time stretch / start_time auto precomp = std::make_unique(document); precomp->timing->start_time.set(ae_layer.start_time); precomp->timing->stretch.set(ae_layer.time_stretch); precomp->composition.set(comp_it->second); precomp->name.set(comp_it->second->name.get()); precomp->size.set(comp_it->second->size()); if ( layer->name.get().isEmpty() ) layer->name.set(precomp->name.get()); layer->shapes.insert(std::move(precomp)); return; } auto solid_it = colors.find(ae_layer.asset_id); if ( solid_it != colors.end() ) { auto fill = std::make_unique(document); fill->color.set(solid_it->second.asset->color.get()); fill->use.set(solid_it->second.asset); layer->shapes.insert(std::move(fill)); auto rect = std::make_unique(document); rect->size.set(QSizeF( solid_it->second.solid->width, solid_it->second.solid->height )); rect->position.set(QPointF( solid_it->second.solid->width / 2, solid_it->second.solid->height / 2 )); layer->shapes.insert(std::move(rect)); if ( layer->name.get().isEmpty() ) layer->name.set(solid_it->second.asset->name.get()); return; } warning(AepFormat::tr("Unknown asset type for %1").arg(ae_layer.name.isEmpty() ? "Layer" : ae_layer.name)); } namespace { std::unique_ptr text_to_shapes( const TextDocument& doc, const std::vector& fonts, model::Document* document ) { auto group = std::make_unique(document); auto pt = std::make_unique(document); auto text = pt.get(); group->shapes.insert(std::move(pt)); text->text.set(doc.text); if ( !doc.character_styles.empty() ) { /// \todo Style text spans const auto& style = doc.character_styles[0]; /// \todo figure out weight etc if ( style.font_index >= 0 && style.font_index < int(fonts.size()) ) text->font->family.set(fonts[style.font_index].family); text->font->size.set(style.size); std::unique_ptr fill; std::unique_ptr stroke; if ( style.stroke_enabled ) { stroke = std::make_unique(document); stroke->color.set(style.stroke_color); stroke->width.set(style.stroke_width); } /// \todo fill enabled? fill = std::make_unique(document); fill->color.set(style.fill_color); if ( style.stroke_over_fill ) { if ( fill ) group->shapes.insert(std::move(fill)); if ( stroke ) group->shapes.insert(std::move(stroke)); } else { if ( stroke ) group->shapes.insert(std::move(stroke)); if ( fill ) group->shapes.insert(std::move(fill)); } } return group; } } // namespace void glaxnimate::io::aep::AepLoader::text_layer(model::Layer* layer, const Layer& ae_layer, CompData&) { auto prop = ae_layer.properties["ADBE Text Properties"]["ADBE Text Document"]; if ( prop.class_type() != PropertyBase::TextProperty ) return; const auto& tprop = static_cast(prop); if ( tprop.documents.value.type() == PropertyValue::TextDocument ) { layer->shapes.insert(text_to_shapes( std::get(tprop.documents.value.value), tprop.fonts, document )); return; } if ( tprop.documents.keyframes.empty() ) return; /// \todo animated text const auto& kf = tprop.documents.keyframes[0]; if ( kf.value.type() == PropertyValue::TextDocument ) { layer->shapes.insert(text_to_shapes( std::get(kf.value.value), tprop.fonts, document )); return; } } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/assets/named_color.cpp000664 001750 001750 00000001646 14477652011 031674 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "named_color.hpp" #include "model/document.hpp" #include "model/assets/assets.hpp" #include "command/object_list_commands.hpp" GLAXNIMATE_OBJECT_IMPL(glaxnimate::model::NamedColor) QString glaxnimate::model::NamedColor::type_name_human() const { return tr("Unnamed Color"); } QBrush glaxnimate::model::NamedColor::brush_style(FrameTime t) const { return color.get_at(t); } void glaxnimate::model::NamedColor::fill_icon(QPixmap& icon) const { icon.fill(color.get_at(0)); } bool glaxnimate::model::NamedColor::remove_if_unused(bool clean_lists) { if ( clean_lists && users().empty() ) { document()->push_command(new command::RemoveObject( this, &document()->assets()->colors->values )); return true; } return false; } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/external/QtAppSetup/src/app/log/000775 001750 001750 00000000000 14477652011 030576 5ustar00ddennedyddennedy000000 000000 mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/mask_settings.hpp000664 001750 001750 00000001474 14477652011 030767 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include "model/object.hpp" #include "model/property/reference_property.hpp" #include "model/animation/frame_time.hpp" #include "model/shapes/shape.hpp" namespace glaxnimate::model { class MaskSettings : public Object { GLAXNIMATE_OBJECT(MaskSettings) public: enum MaskMode { NoMask = 0, Alpha = 1, }; Q_ENUM(MaskMode) GLAXNIMATE_PROPERTY(MaskMode, mask, NoMask, {}, {}, PropertyTraits::Visual) GLAXNIMATE_PROPERTY(bool, inverted, false, {}, {}, PropertyTraits::Visual) public: using Object::Object; QString type_name_human() const override; bool has_mask() const { return mask.get(); } }; } // namespace glaxnimate::model mlt-7.22.0/src/modules/glaxnimate/glaxnimate/external/QtAppSetup/src/app/widgets/settings_dialog.cpp000664 001750 001750 00000003336 14477652011 035353 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "settings_dialog.hpp" #include "ui_settings_dialog.h" #include #include "app/application.hpp" #include "app/settings/settings.hpp" #include "app/widgets/no_close_on_enter.hpp" class app::SettingsDialog::Private : public app::Ui::SettingsDialog { public: app::widgets::NoCloseOnEnter ncoe; }; static QIcon best_icon(const QIcon& icon, const QSize& target_size) { for ( const auto& size : icon.availableSizes() ) if ( size.width() >= target_size.width() ) return icon; return icon.pixmap(target_size);; } app::SettingsDialog::SettingsDialog ( QWidget* parent ) : QDialog(parent), d(std::make_unique()) { d->setupUi(this); installEventFilter(&d->ncoe); for ( const auto& group : app::settings::Settings::instance().groups() ) { if ( group->has_visible_settings() ) { new QListWidgetItem(best_icon(group->icon(), d->list_widget->iconSize()), group->label(), d->list_widget); d->stacked_widget->addWidget(group->make_widget(d->stacked_widget)); } } d->list_widget->setCurrentRow(0); } app::SettingsDialog::~SettingsDialog() = default; void app::SettingsDialog::changeEvent(QEvent *e) { QDialog::changeEvent(e); if ( e->type() == QEvent::LanguageChange) { d->retranslateUi(this); int i = 0; for ( const auto& group : app::settings::Settings::instance() ) { if ( group->has_visible_settings() ) { d->list_widget->item(i)->setText(group->label()); i++; } } } } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/invoke.hpp000664 001750 001750 00000001157 14477652011 027405 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include namespace glaxnimate::model::detail { template auto invoke_impl(const FuncT& fun, std::index_sequence, const std::tuple& args) { return fun(std::get(args)...); } template auto invoke(const FuncT& fun, const Args&... t) { return invoke_impl(fun, std::make_index_sequence(), std::make_tuple(t...)); } } // namespace glaxnimate::model::detail mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/shapes/shape.hpp000664 001750 001750 00000015253 14477652011 030477 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include "model/document_node.hpp" #include "math/bezier/bezier.hpp" #include "model/property/object_list_property.hpp" namespace glaxnimate::model { using ShapeListProperty = ObjectListProperty; class Composition; template class PathCache { public: bool is_dirty(FrameTime time) const { return time != cached_time || dirty; } void mark_dirty() { dirty = true; } const T& path() const { return cached_path; } void set_path(FrameTime time, const T& path) { cached_time = time; dirty = false; cached_path = path; } private: bool dirty = true; T cached_path = {}; FrameTime cached_time = 0; }; /** * \brief Base class for all shape elements */ class ShapeElement : public VisualNode { Q_OBJECT public: explicit ShapeElement(model::Document* document); ~ShapeElement(); int docnode_child_count() const override { return 0; } DocumentNode* docnode_child(int) const override { return nullptr; } int docnode_child_index(DocumentNode*) const override { return -1; } /** * \brief Index within its parent */ int position() const; virtual void add_shapes(FrameTime t, math::bezier::MultiBezier& bez, const QTransform& transform) const = 0; math::bezier::MultiBezier shapes(FrameTime t) const; ShapeListProperty* owner() const; Composition* owner_composition() const; void clear_owner(); virtual QPainterPath to_clip(FrameTime t) const; QPainterPath to_painter_path(FrameTime t) const; virtual std::unique_ptr to_path() const; signals: void position_updated(); void siblings_changed(); protected: const ShapeListProperty& siblings() const; void on_property_changed(const BaseProperty* prop, const QVariant& value) override; void on_parent_changed(model::DocumentNode* old_parent, model::DocumentNode* new_parent) override; void refresh_owner_composition(glaxnimate::model::Composition* comp); virtual void on_composition_changed(model::Composition* old_comp, model::Composition* new_comp) { Q_UNUSED(old_comp); Q_UNUSED(new_comp); } virtual QPainterPath to_painter_path_impl(FrameTime t) const = 0; void on_graphics_changed() override; private: void set_position(ShapeListProperty* property, int pos); class Private; std::unique_ptr d; friend ShapeListProperty; friend class Group; }; template<> class ObjectListProperty : public detail::ObjectListProperty { public: using detail::ObjectListProperty::ObjectListProperty; /** * \brief End iterator for a range that includes a modifier then stops */ iterator past_first_modifier() const; QRectF bounding_rect(FrameTime t) const; protected: void update_pos(int index) { int i; for ( i = size() - 1; i >= index; i-- ) objects[i]->set_position(this, i); for ( ; i >= 0; i-- ) objects[i]->siblings_changed(); } void on_insert(int index) override { update_pos(index); } void on_remove(int index) override { update_pos(index); } void on_move(int index_a, int index_b) override { if ( index_b < index_a ) std::swap(index_a, index_b); for ( int i = index_a; i <= index_b; i++ ) objects[i]->set_position(this, i); for ( int i = 0; i <= index_b; i++ ) objects[i]->siblings_changed(); } }; class Path; /** * \brief Classes that define shapes on their own (but not necessarily style) */ class Shape : public ShapeElement { Q_OBJECT GLAXNIMATE_PROPERTY(bool, reversed, false, {}, {}, PropertyTraits::Visual|PropertyTraits::Hidden) public: using ShapeElement::ShapeElement; virtual math::bezier::Bezier to_bezier(FrameTime t) const = 0; void add_shapes(FrameTime t, math::bezier::MultiBezier & bez, const QTransform& transform) const override; std::unique_ptr to_path() const override; protected: QPainterPath to_painter_path_impl(FrameTime t) const override; }; /** * \brief Base class for types that perform operations on their sibling shapes */ class ShapeOperator : public ShapeElement { Q_OBJECT public: ShapeOperator(model::Document* doc); math::bezier::MultiBezier collect_shapes(FrameTime t, const QTransform& transform) const; math::bezier::MultiBezier collect_shapes_from(const std::vector& shapes, FrameTime t, const QTransform& transform) const; const std::vector& affected() const { return affected_elements; } protected: virtual void do_collect_shapes(const std::vector& shapes, FrameTime t, math::bezier::MultiBezier& bez, const QTransform& transform) const; virtual bool skip_stylers() const { return true; } void on_graphics_changed() override; private slots: void update_affected(); signals: void shape_changed(); private: std::vector affected_elements; mutable PathCache bezier_cache; }; /** * \brief Base class for elements that modify other shapes */ class Modifier : public ShapeOperator { Q_OBJECT public: using ShapeOperator::ShapeOperator; void add_shapes(FrameTime t, math::bezier::MultiBezier& bez, const QTransform& transform) const override; QRectF local_bounding_rect(FrameTime t) const override; virtual math::bezier::MultiBezier process(FrameTime t, const math::bezier::MultiBezier& mbez) const = 0; protected: QPainterPath to_painter_path_impl(FrameTime t) const override; /** * \brief Whether to process on the whole thing (or individual objects) */ virtual bool process_collected() const = 0; void do_collect_shapes(const std::vector& shapes, FrameTime t, math::bezier::MultiBezier& bez, const QTransform& transform) const override; virtual bool skip_stylers() const override { return false; } }; /** * \brief CRTP to override some methods using static functions * (so said methods can be accessed with no object) */ template class StaticOverrides : public Base { public: using Ctor = StaticOverrides; using Base::Base; QIcon tree_icon() const override { return Derived::static_tree_icon(); } QString type_name_human() const override { return Derived::static_type_name_human(); } static QString static_class_name() { return detail::naked_type_name(); } }; } // namespace glaxnimate::model mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/shapes/stroke.hpp000664 001750 001750 00000003457 14477652011 030711 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include #include #include "styler.hpp" #include "model/animation/animatable.hpp" namespace glaxnimate::model { class Stroke : public StaticOverrides { GLAXNIMATE_OBJECT(Stroke) public: enum Cap { ButtCap = Qt::FlatCap, RoundCap = Qt::RoundCap, SquareCap = Qt::SquareCap, }; enum Join { MiterJoin = Qt::MiterJoin, RoundJoin = Qt::RoundJoin, BevelJoin = Qt::BevelJoin, }; private: Q_ENUM(Cap); Q_ENUM(Join); GLAXNIMATE_ANIMATABLE(float, width, 1, {}, 0) GLAXNIMATE_PROPERTY(Cap, cap, RoundCap, nullptr, nullptr, PropertyTraits::Visual) GLAXNIMATE_PROPERTY(Join, join, RoundJoin, nullptr, nullptr, PropertyTraits::Visual) GLAXNIMATE_PROPERTY(float, miter_limit, 0, nullptr, nullptr, PropertyTraits::Visual) public: using Ctor::Ctor; QRectF local_bounding_rect(FrameTime t) const override { if ( !visible.get() ) return {}; qreal half_width = width.get_at(t) / 2; return collect_shapes(t, {}).bounding_box().adjusted( -half_width, -half_width, half_width, half_width ); } static QIcon static_tree_icon() { return QIcon::fromTheme("format-stroke-color"); } static QString static_type_name_human() { return tr("Stroke"); } void set_pen_style(const QPen& p); void set_pen_style_undoable(const QPen& p); protected: QPainterPath to_painter_path_impl(FrameTime t) const override; void on_paint(QPainter* p, FrameTime t, PaintMode, model::Modifier* modifier) const override; }; } // namespace glaxnimate::model mlt-7.22.0/src/modules/glaxnimate/glaxnimate/external/QtAppSetup/src/app/settings/settings_group.hpp000664 001750 001750 00000003056 14477652011 035446 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include #include "app/utils/translated_string.hpp" #include "app/settings/setting.hpp" #include "custom_settings_group.hpp" namespace app::settings { class SettingsGroup : public CustomSettingsGroupBase { public: using iterator = SettingList::const_iterator; SettingsGroup(QString slug, utils::TranslatedString label, const QString& icon, SettingList settings); SettingsGroup(SettingList settings); QString slug() const override; QString label() const override; QIcon icon() const override; void load(QSettings& settings) override; void save(QSettings& settings) override; QWidget* make_widget(QWidget* parent) override; bool has_visible_settings() const override; QVariant get_variant(const QString& setting_slug) const override; bool set_variant(const QString& setting_slug, const QVariant& value) override; QVariant get_default(const QString& setting_slug) const override; QVariant define(const QString& setting_slug, const QVariant& default_value) override; iterator begin() const { return settings_.begin(); } iterator end() const { return settings_.end(); } SettingList& settings() { return settings_; } QVariantMap& values() { return values_; } private: QString slug_; utils::TranslatedString label_; QString icon_; SettingList settings_; QVariantMap values_; }; } // namespace app::settings mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/assets/composition.cpp000664 001750 001750 00000004053 14477652011 031750 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "composition.hpp" #include "model/document.hpp" #include "model/assets/assets.hpp" #include "command/object_list_commands.hpp" #include using namespace glaxnimate; GLAXNIMATE_OBJECT_IMPL(glaxnimate::model::Composition) QIcon glaxnimate::model::Composition::tree_icon() const { return QIcon::fromTheme("video-x-generic"); } QString glaxnimate::model::Composition::type_name_human() const { return tr("Composition"); } bool glaxnimate::model::Composition::remove_if_unused(bool clean_lists) { if ( clean_lists && users().empty() ) { document()->push_command(new command::RemoveObject( this, &document()->assets()->compositions->values )); return true; } return false; } glaxnimate::model::DocumentNode * glaxnimate::model::Composition::docnode_parent() const { return document()->assets()->compositions.get(); } int glaxnimate::model::Composition::docnode_child_index(glaxnimate::model::DocumentNode* dn) const { return shapes.index_of(static_cast(dn)); } QRectF glaxnimate::model::Composition::local_bounding_rect(FrameTime) const { return rect(); } QImage glaxnimate::model::Composition::render_image(float time, QSize image_size, const QColor& background) const { QSizeF real_size = size(); if ( !image_size.isValid() ) image_size = real_size.toSize(); QImage image(image_size, QImage::Format_RGBA8888); if ( !background.isValid() ) image.fill(Qt::transparent); else image.fill(background); QPainter painter(&image); painter.setRenderHint(QPainter::Antialiasing); painter.scale( image_size.width() / real_size.width(), image_size.height() / real_size.height() ); paint(&painter, time, VisualNode::Render); return image; } QImage glaxnimate::model::Composition::render_image() const { return render_image(document()->current_time(), size()); } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/external/QtAppSetup/src/000775 001750 001750 00000000000 14506447765 027250 5ustar00ddennedyddennedy000000 000000 src/modules/glaxnimate/glaxnimate/external/QtAppSetup/src/app/settings/palette_settings.cpp000664 001750 001750 00000011702 14477652011 035661 0ustar00ddennedyddennedy000000 000000 mlt-7.22.0/* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "palette_settings.hpp" #include #include #include #include #include "app/widgets/widget_palette_editor.hpp" #include "app/utils/string_view.hpp" app::settings::PaletteSettings::PaletteSettings() : default_palette(QGuiApplication::palette(), true) { } QString app::settings::PaletteSettings::slug() const { return "palette"; } QIcon app::settings::PaletteSettings::icon() const { return QIcon::fromTheme("preferences-desktop-theme-global"); } QString app::settings::PaletteSettings::label() const { return QObject::tr("Widget Theme"); } void app::settings::PaletteSettings::save ( QSettings& settings ) { settings.setValue("theme", selected); settings.setValue("style", style); settings.beginWriteArray("themes"); int i = 0; for ( auto it = palettes.begin(); it != palettes.end(); ++it ) { if ( !it->built_in ) { settings.setArrayIndex(i); write_palette(settings, it.key(), *it); ++i; } } settings.endArray(); } void app::settings::PaletteSettings::write_palette ( QSettings& settings, const QString& name, const QPalette& palette ) { settings.setValue("name", name); for ( const auto& p : roles() ) { settings.setValue(p.first + "_active", color_to_string(palette.color(QPalette::Active, p.second))); settings.setValue(p.first + "_inactive", color_to_string(palette.color(QPalette::Inactive, p.second))); settings.setValue(p.first + "_disabled", color_to_string(palette.color(QPalette::Disabled, p.second))); } } void app::settings::PaletteSettings::load_palette ( const QSettings& settings, bool mark_built_in ) { QString name = settings.value("name").toString(); if ( name.isEmpty() ) return; auto it = palettes.find(name); if ( it != palettes.end() && it->built_in && !mark_built_in ) return; Palette palette; palette.built_in = mark_built_in; for ( const auto& p : roles() ) { palette.setColor(QPalette::Active, p.second, string_to_color(settings.value(p.first + "_active").toString())); palette.setColor(QPalette::Inactive, p.second, string_to_color(settings.value(p.first + "_inactive").toString())); palette.setColor(QPalette::Disabled, p.second, string_to_color(settings.value(p.first + "_disabled").toString())); } palettes.insert(name, palette); } void app::settings::PaletteSettings::load ( QSettings& settings ) { selected = settings.value("theme").toString(); style = settings.value("style").toString(); if ( !style.isEmpty() ) set_style(style); int n = settings.beginReadArray("themes"); for ( int i = 0; i < n; i++ ) { settings.setArrayIndex(i); load_palette(settings); } settings.endArray(); apply_palette(palette()); } const QPalette& app::settings::PaletteSettings::palette() const { auto it = palettes.find(selected); if ( it == palettes.end() ) return default_palette; return *it; } const std::vector > & app::settings::PaletteSettings::roles() { static std::vector > roles; if ( roles.empty() ) { QSet blacklisted = { "Background", "Foreground", "NColorRoles" }; QMetaEnum me = QMetaEnum::fromType(); for ( int i = 0; i < me.keyCount(); i++ ) { if ( blacklisted.contains(me.key(i)) ) continue; roles.emplace_back( me.key(i), QPalette::ColorRole(me.value(i)) ); } } return roles; } void app::settings::PaletteSettings::set_selected ( const QString& name ) { selected = name; apply_palette(palette()); } QWidget * app::settings::PaletteSettings::make_widget ( QWidget* parent ) { return new WidgetPaletteEditor(this, parent); } void app::settings::PaletteSettings::apply_palette(const QPalette& palette) { QGuiApplication::setPalette(palette); QApplication::setPalette(palette); for ( auto window : QApplication::topLevelWidgets() ) window->setPalette(palette); } QString app::settings::PaletteSettings::color_to_string(const QColor& c) { QString s = c.name(); if ( c.alpha() < 255 ) s += utils::right_ref(QString::number(0x100|c.alpha(), 16), 2); return s; } QColor app::settings::PaletteSettings::string_to_color(const QString& s) { if ( s.startsWith('#') && s.length() == 9 ) { QColor c(utils::left_ref(s, 7)); c.setAlpha(s.right(2).toInt(nullptr, 16)); return c; } return QColor(s); } void app::settings::PaletteSettings::set_style(const QString& name) { QApplication::setStyle(QStyleFactory::create(name)); style = name; } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/raster/spritesheet_format.cpp000664 001750 001750 00000006042 14477652011 032621 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "spritesheet_format.hpp" #include "model/assets/composition.hpp" #include #include #include glaxnimate::io::Autoreg glaxnimate::io::raster::SpritesheetFormat::autoreg; QStringList glaxnimate::io::raster::SpritesheetFormat::extensions() const { QStringList formats; formats << "png"; for ( const auto& fmt : QImageWriter::supportedImageFormats() ) if ( fmt != "jpg" && fmt != "svg" ) formats << QString::fromUtf8(fmt); return formats; } std::unique_ptr glaxnimate::io::raster::SpritesheetFormat::save_settings(model::Composition* comp) const { int first_frame = comp->animation->first_frame.get(); int last_frame = comp->animation->last_frame.get(); int frames = last_frame - first_frame; return std::make_unique(app::settings::SettingList{ app::settings::Setting("frame_width", tr("Frame Width"), tr("Width of each frame"), comp->width.get(), 1, 999'999), app::settings::Setting("frame_height", tr("Frame Height"), tr("Height of each frame"), comp->height.get(), 1, 999'999), app::settings::Setting("columns", tr("Columns"), tr("Number of columns in the sheet"), std::ceil(math::sqrt(frames)), 1, 64), app::settings::Setting("frame_step", tr("Time Step"), tr("By how much each rendered frame should increase time (in frames)"), 1, 1, 16), }); } bool glaxnimate::io::raster::SpritesheetFormat::on_save(QIODevice& file, const QString& filename, model::Composition* comp, const QVariantMap& setting_values) { Q_UNUSED(filename); int frame_w = setting_values["frame_width"].toInt(); int frame_h = setting_values["frame_height"].toInt(); int columns = setting_values["columns"].toInt(); int frame_step = setting_values["frame_step"].toInt(); if ( frame_w <= 0 || frame_h <= 0 || columns <= 0 || frame_step <= 0 ) return false; int first_frame = comp->animation->first_frame.get(); int last_frame = comp->animation->last_frame.get(); int frames = (last_frame - first_frame) / frame_step; int rows = frames / columns; qreal scale_x = qreal(frame_w) / comp->width.get(); qreal scale_y = qreal(frame_h) / comp->height.get(); QImage bmp(frame_w * columns, frame_h * rows, QImage::Format_ARGB32); QPainter painter(&bmp); for ( int i = first_frame; i <= last_frame; i += frame_step ) { painter.save(); painter.scale(scale_x, scale_y); painter.translate((i % columns) * frame_w, (i / columns) * frame_h); painter.setClipRect(0, 0, frame_w, frame_h); comp->paint(&painter, i, model::VisualNode::Render); painter.restore(); } painter.end(); QImageWriter writer(&file, {}); writer.setOptimizedWrite(true); if ( writer.write(bmp) ) return true; error(writer.errorString()); return false; } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/external/QtAppSetup/src/app/translation_service.cpp000664 001750 001750 00000007476 14477652011 034615 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "translation_service.hpp" #include #include #include #include #include "app/application.hpp" #include "app/log/log.hpp" void app::TranslationService::initialize ( QString default_lang_code ) { if ( !default_lang_code.isEmpty() ) { QString name = language_name(default_lang_code); if ( !name.isEmpty() ) register_translation(name,default_lang_code, QString()); } QDir translations = Application::instance()->data_file("translations"); QStringList translation_files = translations.entryList({"*.qm"}); QRegularExpression re("[^_]+_([^.]+)\\.qm"); foreach ( QString file, translation_files ) { auto match = re.match(file); if ( match.hasMatch() ) { QString code = match.captured(1); QString name = language_name(code); if ( !name.isEmpty() ) register_translation(name,code,translations.absoluteFilePath(file)); } else { log::LogStream("Translations") << "Unrecognised translation file name pattern:" << file; } } change_lang_code(QLocale::system().name()); } QString app::TranslationService::language_name(QString lang_code) { QLocale lang_loc = QLocale(lang_code); QString name = lang_loc.nativeLanguageName(); QString specifier; if ( lang_code.contains("_") ) { if ( lang_loc.script() != QLocale::AnyScript ) specifier = QLocale::scriptToString(lang_loc.script()); if ( lang_loc.country() != QLocale::AnyCountry ) { if ( !specifier.isEmpty() ) specifier += ", "; specifier = lang_loc.nativeCountryName(); } } if ( !name.isEmpty() ) { name[0] = name[0].toUpper(); if ( !specifier.isEmpty() ) name += " (" + specifier + ")"; } return name; } void app::TranslationService::register_translation(QString name, QString code, QString file) { lang_names[name] = code; if ( !file.isEmpty() ) { translators[code] = new QTranslator; if ( !translators[code]->load(file) ) { log::Log("Translations").log( QString("Error on loading translation file %1 for language %2 (%3)") .arg(file).arg(name).arg(code) ); } } } QString app::TranslationService::current_language_name() { return lang_names.key(current_language); } QString app::TranslationService::current_language_code() { return current_language; } QTranslator *app::TranslationService::translator() { return translators[current_language]; } const QMap& app::TranslationService::available_languages() { return lang_names; } void app::TranslationService::change_lang_code(QString code) { if ( !translators.contains(code) ) { QString base_code = code.left(code.lastIndexOf('_')); // en_US -> en bool found = false; foreach ( QString installed_code, translators.keys() ) { if ( installed_code.left(installed_code.lastIndexOf('_')) == base_code ) { code = installed_code; found = true; break; } } if ( !found ) { log::Log("Translations").log( QString("There is no translation for language %1 (%2)") .arg(language_name(code)) .arg(code) ); return; } } QCoreApplication* app = QCoreApplication::instance(); app->removeTranslator(translator()); current_language = code; app->installTranslator(translator()); } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/command/base.hpp000664 001750 001750 00000002154 14477652011 027340 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include namespace glaxnimate::command { enum class Id { SetPropertyValue, SetMultipleProperties, SetKeyframe, SetMultipleAnimated, SetPositionBezier, // For additional commands, use values increadising from here CustomCommand, }; template class MergeableCommand : public QUndoCommand { public: int id() const final { return int(id_enum); } bool mergeWith ( const QUndoCommand * other ) final { if ( commit ) return false; auto oth = static_cast(other); if ( static_cast(this)->merge_with(*oth) ) { commit = oth->commit; return true; } return false; } protected: using Parent = MergeableCommand; MergeableCommand(const QString& name, bool commit = true) : QUndoCommand(name), commit(commit) {} bool commit; }; } // namespace glaxnimate::command src/modules/glaxnimate/glaxnimate/external/QtAppSetup/src/app/settings/custom_settings_group.hpp000664 001750 001750 00000002515 14477652011 036760 0ustar00ddennedyddennedy000000 000000 mlt-7.22.0/* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include #include namespace app::settings { class CustomSettingsGroupBase { public: virtual ~CustomSettingsGroupBase() = default; virtual QString slug() const = 0; virtual QString label() const = 0; virtual QIcon icon() const = 0; virtual void load(QSettings& settings) = 0; virtual void save(QSettings& settings) = 0; virtual QWidget* make_widget(QWidget* parent) = 0; virtual bool has_visible_settings() const { return true; } virtual QVariant get_variant(const QString& setting) const { Q_UNUSED(setting); return {}; } virtual bool set_variant(const QString& setting_slug, const QVariant& value) { Q_UNUSED(setting_slug); Q_UNUSED(value); return false; } virtual QVariant get_default(const QString& setting_slug) const { Q_UNUSED(setting_slug); return {}; } virtual QVariant define(const QString& setting_slug, const QVariant& default_value) { Q_UNUSED(setting_slug); Q_UNUSED(default_value); return {}; } }; using CustomSettingsGroup = std::unique_ptr; } // namespace app::settings src/modules/glaxnimate/glaxnimate/external/QtAppSetup/src/app/widgets/widget_palette_editor.ui000664 001750 001750 00000022457 14477652011 036324 0ustar00ddennedyddennedy000000 000000 mlt-7.22.0 WidgetPaletteEditor 0 0 643 464 Style 0 0 0 0 System Default 0 0 Add... .. Remove .. 0 0 Active Disabled Apply .. 0 0 Preview Enabled Widgets true Theme Preview Check That 1 A B C QDialogButtonBox::No|QDialogButtonBox::Yes Placeholder This true Items Items 30 Qt::Vertical palette_view cellChanged(int,int) WidgetPaletteEditor update_color(int,int) 102 299 307 463 pushButton clicked() WidgetPaletteEditor add_palette() 224 41 323 463 pushButton_3 clicked() WidgetPaletteEditor remove_palette() 316 41 388 463 pushButton_2 clicked() WidgetPaletteEditor apply_palette() 184 445 244 463 combo_saved currentIndexChanged(QString) WidgetPaletteEditor select_palette(QString) 58 40 92 463 checkBox toggled(bool) preview_widget setEnabled(bool) 466 22 469 43 add_palette() update_color(int,int) remove_palette() apply_palette() select_palette(QString) mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/utils/trace.cpp000664 001750 001750 00000021472 14477652011 027245 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "trace.hpp" #include "potracelib.h" #include "utils/color.hpp" using namespace glaxnimate; class utils::trace::TraceOptions::Private { public: potrace_param_s* params; static const constexpr qreal alphamax_max = 1.3334; }; utils::trace::TraceOptions::TraceOptions() : d(std::make_unique()) { d->params = potrace_param_default(); } utils::trace::TraceOptions::~TraceOptions() { potrace_param_free(d->params); } int utils::trace::TraceOptions::min_area() const { return d->params->turdsize; } void utils::trace::TraceOptions::set_min_area(int min_area) { d->params->turdsize = min_area; } qreal utils::trace::TraceOptions::smoothness() const { return d->params->alphamax / Private::alphamax_max; } void utils::trace::TraceOptions::set_smoothness(qreal smoothness) { d->params->alphamax = smoothness * Private::alphamax_max; } static inline constexpr QRgb rgba888(const uchar* pixel) noexcept { return qRgba(pixel[0], pixel[1], pixel[2], pixel[3]); } static inline constexpr uchar rgba888_alpha(const uchar* pixel) noexcept { return pixel[3]; } class utils::trace::Tracer::Private { public: struct CurveWrapper { potrace_curve_s* curve; QPointF point(int index, int off) const { const auto& p = curve->c[index][off]; return {p.x, p.y}; } void to_bezier(math::bezier::MultiBezier& mbez) const { if ( curve->n < 2 ) return; mbez.move_to(point(curve->n-1, 2)); for ( int i = 0; i < curve->n - 1; i++ ) { if ( curve->tag[i] == POTRACE_CURVETO ) { mbez.cubic_to(point(i, 0), point(i, 1), point(i, 2)); } else { mbez.line_to(point(i, 1)); mbez.line_to(point(i, 2)); } } if ( curve->tag[curve->n - 1] == POTRACE_CURVETO ) { mbez.beziers().back().points().back().tan_out = point(curve->n - 1, 0); mbez.beziers().back()[0].tan_in = point(curve->n - 1, 1); } else { mbez.line_to(point(curve->n - 1, 1)); } mbez.close(); } }; static void progress_callback(double progress, void* privdata) { reinterpret_cast(privdata)->progress(progress); } int get_bit_alpha(const uchar* pixel) const noexcept { return rgba888_alpha(pixel) >= target_alpha; } int get_bit_alpha_neg(const uchar* pixel) const noexcept { return rgba888_alpha(pixel) < target_alpha; } int get_bit_color(const uchar* pixel) const noexcept { return rgba888(pixel) == target_color; } int get_bit_color_tolerance(const uchar* pixel) const noexcept { return utils::color::rgba_distance_squared(target_color, pixel[0], pixel[1], pixel[2], pixel[3]) <= target_tolerance; } int get_bit_index(const uchar* pixel) const noexcept { return *pixel == target_color; } using callback_type = int (Private::*)(const uchar*) const noexcept; callback_type callback = &Private::get_bit_alpha; int target_alpha = 128; QRgb target_color; qint32 target_tolerance = 0; QImage image; potrace_param_s params; }; utils::trace::Tracer::Tracer(const QImage& image, const TraceOptions& options) : d(std::make_unique()) { d->image = image; d->params = *options.d->params; d->params.progress = { &Private::progress_callback, this, 0, 100, 1 }; } utils::trace::Tracer::~Tracer() = default; bool utils::trace::Tracer::trace(math::bezier::MultiBezier& mbez) { QImage::Format target_format = d->callback == &Private::get_bit_index ? QImage::Format_Indexed8 : QImage::Format_RGBA8888; if ( d->image.format() != target_format ) d->image = d->image.convertToFormat(target_format); int line_len = d->image.width() / sizeof(potrace_word); if ( d->image.width() % sizeof(potrace_word) ) line_len += 1; static constexpr int N = sizeof(potrace_word) * CHAR_BIT; const int x_off = d->callback == &Private::get_bit_index ? 1 : 4; std::vector data(line_len * d->image.height(), 0); for ( int y = 0, h = d->image.height(), w = d->image.width(); y < h; y++ ) { auto line = d->image.constScanLine(y); for ( int x = 0; x < w; x++ ) { (data.data() + y*line_len)[x/N] |= (1ul << (N-1-x%N)) * (d.get()->*d->callback)(line+x*x_off); } } potrace_bitmap_s bitmap{ d->image.width(), d->image.height(), line_len, data.data() }; potrace_state_t *result = potrace_trace(&d->params, &bitmap); if ( result->status == POTRACE_STATUS_OK ) { for ( auto path = result->plist; path; path = path->next ) { Private::CurveWrapper{&path->curve}.to_bezier(mbez); } potrace_state_free(result); return true; } potrace_state_free(result); return false; } QString utils::trace::Tracer::potrace_version() { return ::potrace_version(); } void utils::trace::Tracer::set_progress_range(double min, double max) { d->params.progress.min = min; d->params.progress.max = max; } void utils::trace::Tracer::set_target_alpha(int threshold, bool invert) { d->target_alpha = threshold; d->callback = invert ? &Private::get_bit_alpha_neg : &Private::get_bit_alpha; } void utils::trace::Tracer::set_target_color(const QColor& color, qint32 tolerance) { d->target_color = color.rgba(); d->target_tolerance = tolerance; d->callback = tolerance > 0 ? &Private::get_bit_color_tolerance : &Private::get_bit_color; } void utils::trace::Tracer::set_target_index(uchar index) { d->target_color = index; d->callback = &Private::get_bit_index; } struct PixelRect { QRectF rect; QRgb color; }; struct PixelTraceData { std::map last_rects; QList all_rects; void merge_up(PixelRect* last_rect, std::map& rects) { if ( !last_rect ) return; auto yrect = get_rect(last_rect->rect.left()); if ( yrect && yrect->rect.width() == last_rect->rect.width() && yrect->color == last_rect->color ) { yrect->rect.setBottom(yrect->rect.bottom()+1); rects[last_rect->rect.left()] = yrect; for ( auto it = all_rects.begin(); it != all_rects.end(); ++it ) if ( &*it == last_rect ) { all_rects.erase(it); break; } } } PixelRect* get_rect(int left) { auto yrect = last_rects.find(left); if ( yrect == last_rects.end() ) return nullptr; return &*yrect->second; } PixelRect* add_rect(QRgb color, int x, int y) { all_rects.push_back({QRectF(x, y, 1, 1), color}); return &all_rects.back(); } }; std::map > utils::trace::trace_pixels(QImage image) { if ( image.format() != QImage::Format_RGBA8888 ) image = image.convertToFormat(QImage::Format_RGBA8888); int w = image.width(); int h = image.height(); PixelTraceData data; for ( int y = 0; y < h; y++ ) { std::map rects; QRgb last_color = 0; PixelRect* last_rect = nullptr; auto line = image.constScanLine(y); for ( int x = 0; x < w; x++ ) { if ( rgba888_alpha(line+x*4) == 0 ) { last_color = 0; last_rect = nullptr; continue; } QRgb colort = rgba888(line+x*4); auto yrect = data.get_rect(x); if ( colort == last_color ) { last_rect->rect.setRight(last_rect->rect.right() + 1); } else if ( yrect && colort == yrect->color && yrect->rect.width() == 1 ) { yrect->rect.setBottom(yrect->rect.bottom()+1); rects[x] = yrect; last_rect = nullptr; colort = 0; } else { data.merge_up(last_rect, rects); last_rect = data.add_rect(colort, x, y); rects.emplace(x, last_rect); } last_color = colort; } data.merge_up(last_rect, rects); std::swap(data.last_rects, rects); } std::map > traced; for ( const auto& r : data.all_rects ) traced[r.color].push_back(r.rect); return traced; } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/aep/aep_format.cpp000664 001750 001750 00000003061 14477652011 030272 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "aep_format.hpp" #include "aep_parser.hpp" #include "aep_loader.hpp" #include "aepx.hpp" glaxnimate::io::Autoreg glaxnimate::io::aep::AepFormat::autoreg; glaxnimate::io::Autoreg glaxnimate::io::aep::AepxFormat::autoreg; bool glaxnimate::io::aep::AepFormat::riff_to_document(const RiffChunk& chunk, model::Document* document, const QString& filename) { AepParser parser(this); try { Project project = parser.parse(chunk); QFileInfo finfo(filename); AepLoader loader(document, project, finfo.dir(), this); loader.load_project(); return true; } catch ( const AepError& err ) { return false; } } bool glaxnimate::io::aep::AepFormat::on_open(QIODevice& file, const QString& filename, model::Document* document, const QVariantMap&) { AepRiff riff_parser; try { RiffChunk chunk = riff_parser.parse(&file); return riff_to_document(chunk, document, filename); } catch ( const RiffError& r ) { error(tr("Could not load file: %1").arg(r.message)); return false; } } bool glaxnimate::io::aep::AepxFormat::on_open(QIODevice& file, const QString& filename, model::Document* document, const QVariantMap&) { QDomDocument dom; dom.setContent(file.readAll()); AepxConverter aepx; return riff_to_document(aepx.aepx_to_chunk(dom.documentElement()), document, filename); } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/video/000775 001750 001750 00000000000 14477652011 026012 5ustar00ddennedyddennedy000000 000000 mlt-7.22.0/src/modules/glaxnimate/glaxnimate/external/QtAppSetup/src/app/utils/000775 001750 001750 00000000000 14477652011 031155 5ustar00ddennedyddennedy000000 000000 mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/shapes/rect.cpp000664 001750 001750 00000002623 14477652011 030324 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "rect.hpp" GLAXNIMATE_OBJECT_IMPL(glaxnimate::model::Rect) glaxnimate::math::bezier::Bezier glaxnimate::model::Rect::to_bezier(model::FrameTime t) const { math::bezier::Bezier bezier; QRectF bb = local_bounding_rect(t); float rounded = this->rounded.get_at(t); float max_r = std::min(bb.width()/2, bb.height()/2); if ( rounded > max_r ) rounded = max_r; if ( rounded == 0 && !this->rounded.animated() ) { bezier.add_point(bb.topRight()); bezier.add_point(bb.bottomRight()); bezier.add_point(bb.bottomLeft()); bezier.add_point(bb.topLeft()); } else { QPointF hh(rounded/2, 0); QPointF vh(0, rounded/2); QPointF hd(rounded, 0); QPointF vd(0, rounded); bezier.add_point(bb.topRight()+vd, -vh); bezier.add_point(bb.bottomRight()-vd, {0,0}, vh); bezier.add_point(bb.bottomRight()-hd, hh); bezier.add_point(bb.bottomLeft()+hd, {0,0}, -hh); bezier.add_point(bb.bottomLeft()-vd, vh); bezier.add_point(bb.topLeft()+vd, {0,0}, -vh); bezier.add_point(bb.topLeft()+hd, -hh); bezier.add_point(bb.topRight()-hd, {0,0}, hh); } bezier.close(); if ( reversed.get() ) bezier.reverse(); return bezier; } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/shapes/offset_path.hpp000664 001750 001750 00000001546 14477652011 031701 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include "path_modifier.hpp" #include "stroke.hpp" namespace glaxnimate::model { class OffsetPath : public StaticOverrides { GLAXNIMATE_OBJECT(OffsetPath) GLAXNIMATE_ANIMATABLE(float, amount, 0) GLAXNIMATE_ANIMATABLE(float, miter_limit, 100, {}, 0) GLAXNIMATE_PROPERTY(glaxnimate::model::Stroke::Join, join, Stroke::RoundJoin, nullptr, nullptr, PropertyTraits::Visual) public: using Ctor::Ctor; static QIcon static_tree_icon(); static QString static_type_name_human(); math::bezier::MultiBezier process(FrameTime t, const math::bezier::MultiBezier& mbez) const override; protected: bool process_collected() const override; }; } // namespace glaxnimate::model mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/svg/animate_parser.hpp000664 001750 001750 00000035717 14477652011 031223 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include #include #include "model/animation/keyframe_transition.hpp" #include "model/animation/frame_time.hpp" #include "model/animation/join_animatables.hpp" #include "math/bezier/bezier.hpp" #include "app/utils/string_view.hpp" #include "path_parser.hpp" #include "detail.hpp" #include "io/animated_properties.hpp" namespace glaxnimate::io::svg { QColor parse_color(const QString& string); } // namespace glaxnimate::io::svg namespace glaxnimate::io::svg::detail { using namespace glaxnimate::io::detail; class AnimateParser { public: struct AnimatedProperties : public glaxnimate::io::detail::AnimatedProperties { QDomElement element; bool apply_motion(model::AnimatedProperty& prop, const QPointF& delta_pos = {}, model::Property* auto_orient = nullptr) const { auto motion = properties.find("motion"); if ( motion == properties.end() ) return false; if ( auto_orient ) auto_orient->set(motion->second.auto_orient); for ( const auto& kf : motion->second.keyframes ) prop.set_keyframe(kf.time, QPointF())->set_transition(kf.transition); if ( qFuzzyIsNull(math::length(delta_pos)) ) { prop.set_bezier(motion->second.motion); } else { math::bezier::Bezier bez = motion->second.motion; for ( auto& p : bez ) p.translate(delta_pos); prop.set_bezier(bez); } return true; } bool prepare_joined(std::vector& props) const override { for ( auto& p : props ) { if ( p.prop.index() == 1 ) { if ( !element.hasAttribute(*p.get<1>()) ) return false; p.prop = split_values(element.attribute(*p.get<1>())); } } return true; } }; static std::vector split_values(const QString& v) { if ( !v.contains(separator) ) { bool ok = false; qreal val = v.toDouble(&ok); if ( ok ) return {val}; QColor c(v); if ( c.isValid() ) return {c.redF(), c.greenF(), c.blueF(), c.alphaF()}; return {}; } auto split = ::utils::split_ref(v, separator, #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) Qt::SkipEmptyParts #else QString::SkipEmptyParts #endif ); std::vector values; values.reserve(split.size()); for ( const auto& r : split ) values.push_back(r.toDouble()); return values; } model::FrameTime clock_to_frame(const QString& clock) { auto match = clock_re.match(clock, 0, QRegularExpression::PartialPreferCompleteMatch); if ( !match.hasMatch() ) return 0; static constexpr const qreal minutes = 60; static constexpr const qreal hours = 60*60; static const std::map units = { {"ms", 0.001}, {"s", 1.0}, {"min", minutes}, {"h", hours} }; if ( !match.captured("unit").isEmpty() ) return match.captured("timecount").toDouble() * units.at(match.captured("unit")) * fps; return ( match.captured("hours").toDouble() * hours + match.captured("minutes").toDouble() * minutes + match.captured("seconds").toDouble() ) * fps; } ValueVariant parse_value(const QString& str, ValueVariant::Type type) const { switch ( type ) { case ValueVariant::Vector: return split_values(str); case ValueVariant::Bezier: return PathDParser(str).parse(); case ValueVariant::String: return str; case ValueVariant::Color: return parse_color(str); } return {}; } std::vector get_values(const QDomElement& animate) { QString attr = animate.attribute("attributeName"); ValueVariant::Type type = ValueVariant::Vector; if ( attr == "d" ) type = ValueVariant::Bezier; else if ( attr == "display" ) type = ValueVariant::String; else if ( attr == "fill" || attr == "stroke" || attr == "stop-color" ) type = ValueVariant::Color; std::vector values; if ( animate.hasAttribute("values") ) { auto val_str = animate.attribute("values").split(frame_separator_re, #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) Qt::SkipEmptyParts #else QString::SkipEmptyParts #endif ); values.reserve(val_str.size()); for ( const auto& val : val_str ) values.push_back(parse_value(val, type)); if ( values.size() < 2 ) { warning("Not enough values in `animate`"); return {}; } for ( uint i = 1; i < values.size(); i++ ) { if ( !values[i].compatible(values[0]) ) { warning("Mismatching `values` in `animate`"); return {}; } } } else { if ( animate.hasAttribute("from") ) { values.push_back(parse_value(animate.attribute("from"), type)); } else { if ( attr == "transform" ) { warning("You need to set `values` or `from` in `animateTransform`"); return {}; } else if ( !animate.hasAttribute(attr) ) { warning("Missing `from` in `animate`"); return {}; } values.push_back(parse_value(animate.attribute(attr), type)); } if ( animate.hasAttribute("to") ) { values.push_back(parse_value(animate.attribute("to"), type)); } else if ( type == ValueVariant::Vector && animate.hasAttribute("by") ) { auto by = split_values(animate.attribute("to")); if ( by.size() != values[0].vector().size() ) { warning("Mismatching `by` and `from` in `animate`"); return {}; } for ( uint i = 0; i < by.size(); i++ ) by[i] += values[0].vector()[i]; values.push_back(std::move(by)); } else { warning("Missing `to` or `by` in `animate`"); return {}; } } if ( type == ValueVariant::Bezier ) { for ( const auto& v : values ) { if ( v.bezier().size() != 1 ) { warning("Can only load animated `d` if each keyframe has exactly 1 path"); return {}; } } } return values; } void register_time_range(model::FrameTime start_time, model::FrameTime end_time) { if ( !kf_range_initialized ) { kf_range_initialized = true; min_kf = start_time; max_kf = end_time; } else { if ( min_kf > start_time ) min_kf = start_time; if ( max_kf < end_time ) max_kf = end_time; } } void parse_animate(const QDomElement& animate, AnimatedProperty& prop, bool motion) { if ( !prop.keyframes.empty() ) { warning("Multiple `animate` for the same property"); return; } model::FrameTime start_time = 0; model::FrameTime end_time = 0; if ( animate.hasAttribute("begin") ) start_time = clock_to_frame(animate.attribute("begin")); if ( animate.hasAttribute("dur") ) end_time = start_time + clock_to_frame(animate.attribute("dur")); else if ( animate.hasAttribute("end") ) end_time = clock_to_frame(animate.attribute("end")); if ( start_time >= end_time ) { warning("Invalid timings in `animate`"); return; } register_time_range(start_time, end_time); std::vector values; if ( motion ) { if ( !animate.hasAttribute("path") ) { warning("Missing path for animateMotion"); return; } if ( animate.hasAttribute("rotate") ) { QString rotate = animate.attribute("rotate"); if ( rotate == "auto" ) { prop.auto_orient = true; } else { bool is_number = false; int degrees = rotate.toInt(&is_number) % 360; if ( !is_number || degrees != 0 ) { warning("The only supported values for animateMotion.rotate are auto or 0"); prop.auto_orient = rotate == "auto-reverse"; } } } auto mbez = PathDParser(animate.attribute("path")).parse(); /// \todo Automatically add Hold transitions for sub-bezier end points for ( const auto& b : mbez.beziers() ) { for ( const auto& p : b ) { values.push_back(std::vector{p.pos.x(), p.pos.y()}); prop.motion.push_back(p); } } /// \todo keyPoints } else { values = get_values(animate); } if ( values.empty() ) return; std::vector times; times.reserve(values.size()); if ( animate.hasAttribute("keyTimes") ) { auto strings = animate.attribute("keyTimes").split(frame_separator_re, #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) Qt::SkipEmptyParts #else QString::SkipEmptyParts #endif ); if ( strings.size() != int(values.size()) ) { warning(QString("`keyTimes` (%1) and `values` (%2) mismatch").arg(strings.size()).arg(int(values.size()))); return; } for ( const auto& s : strings ) times.push_back(math::lerp(start_time, end_time, s.toDouble())); } else { for ( qreal i = 0; i < values.size(); i++ ) times.push_back(math::lerp(start_time, end_time, i/(values.size()-1))); } std::vector transitions; QString calc = animate.attribute("calcMode", "linear"); if ( calc == "spline" ) { if ( !animate.hasAttribute("keySplines") ) { warning("Missing `keySplines`"); return; } auto splines = animate.attribute("keySplines").split(frame_separator_re, #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) Qt::SkipEmptyParts #else QString::SkipEmptyParts #endif ); if ( splines.size() != int(values.size()) - 1 ) { warning("Wrong number of `keySplines` values"); return; } transitions.reserve(values.size()); for ( const auto& spline : splines ) { auto params = split_values(spline); if ( params.size() != 4 ) { warning("Invalid value for `keySplines`"); return; } transitions.push_back({{params[0], params[1]}, {params[2], params[3]}}); } transitions.emplace_back(); } else { model::KeyframeTransition def; if ( calc == "discrete" ) def.set_hold(true); transitions = std::vector(values.size(), def); } prop.keyframes.reserve(values.size()); for ( uint i = 0; i < values.size(); i++ ) prop.keyframes.push_back({times[i], values[i], transitions[i]}); } void store_animate(const QString& target, const QDomElement& animate) { stored_animate[target].push_back(animate); } template AnimatedProperties parse_animated_elements(const QDomElement& parent, const FuncT& func) { AnimatedProperties props; props.element = parent; for ( const auto& child: ElementRange(parent) ) func(child, props); if ( parent.hasAttribute("id") ) { auto it = stored_animate.find(parent.attribute("id")); if ( it != stored_animate.end() ) { for ( const auto& child: it->second ) func(child, props); } } return props; } AnimatedProperties parse_animated_properties(const QDomElement& parent) { return parse_animated_elements(parent, [this](const QDomElement& child, AnimatedProperties& props){ if ( child.tagName() == "animate" && child.hasAttribute("attributeName") ) parse_animate(child, props.properties[child.attribute("attributeName")], false); else if ( child.tagName() == "animateMotion" ) parse_animate(child, props.properties["motion"], true); }); } AnimatedProperties parse_animated_transform(const QDomElement& parent) { return parse_animated_elements(parent, [this](const QDomElement& child, AnimatedProperties& props){ if ( child.tagName() == "animateTransform" && child.hasAttribute("type") && child.attribute("attributeName") == "transform" ) parse_animate(child, props.properties[child.attribute("type")], false); else if ( child.tagName() == "animateMotion" ) parse_animate(child, props.properties["motion"], true); }); } void warning(const QString& msg) { if ( on_warning ) on_warning(msg); } qreal fps = 60; qreal min_kf = 0; qreal max_kf = 0; bool kf_range_initialized = false; std::function on_warning; std::unordered_map> stored_animate; static const QRegularExpression separator; static const QRegularExpression clock_re; static const QRegularExpression frame_separator_re; }; } // namespace glaxnimate::io::svg::detail mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/assets/network_downloader.hpp000664 001750 001750 00000005731 14477652011 033325 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include #include #include namespace glaxnimate::model { class NetworkDownloader: public QObject { Q_OBJECT private: struct PendingRequest { PendingRequest(QNetworkReply* reply) : reply(reply) {} PendingRequest(const PendingRequest&) = delete; PendingRequest& operator=(const PendingRequest&) = delete; PendingRequest(PendingRequest&& oth) : reply(oth.reply) { oth.reply = nullptr; } PendingRequest& operator=(PendingRequest&& oth) { std::swap(reply, oth.reply); return *this; } ~PendingRequest() { if ( reply ) { aborted = true; if ( reply->isRunning() ) reply->abort(); reply->deleteLater(); } } QNetworkReply* reply = nullptr; qint64 received = 0; qint64 total = 0; bool aborted = false; }; public: template void get(const QUrl& url, const Func& callback, QObject* receiver = nullptr) { auto reply = manager.get(QNetworkRequest(url)); pending.insert({reply, PendingRequest(reply)}); connect(reply, &QNetworkReply::downloadProgress, this, &NetworkDownloader::on_download_progress); connect(reply, &QNetworkReply::finished, receiver ? receiver : this, [this, reply, callback]{ if ( !reply->error() ) callback(reply->readAll()); auto it = pending.find(reply); if ( it != pending.end() && !it->second.aborted ) { total -= it->second.total; received -= it->second.received; pending.erase(it); if ( pending.empty() ) emit download_finished(); } }); } private slots: void on_download_progress(qint64 bytes_received, qint64 bytes_total) { if ( bytes_total == -1 ) bytes_total = 0; QObject* request = sender(); auto it = pending.find(request); if ( it == pending.end() ) return; if ( bytes_total != it->second.total ) { total += bytes_total - it->second.total; it->second.total = bytes_total; } it->second.received = bytes_received; received += bytes_received; if ( bytes_total > 0 ) emit download_progress(received, total); } signals: void download_progress(qint64 bytes_received, qint64 bytes_total); void download_finished(); private: QNetworkAccessManager manager; std::unordered_map pending; qint64 total = 0; qint64 received = 0; }; } // namespace glaxnimate::model mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/raster/raster_format.cpp000664 001750 001750 00000004753 14477652011 031571 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "raster_format.hpp" #include #include "io/raster/raster_mime.hpp" #include "utils/trace_wrapper.hpp" glaxnimate::io::Autoreg glaxnimate::io::raster::RasterMime::autoreg; glaxnimate::io::Autoreg glaxnimate::io::raster::RasterFormat::autoreg; QStringList glaxnimate::io::raster::RasterFormat::extensions() const { QStringList formats; for ( const auto& fmt : QImageReader::supportedImageFormats() ) if ( fmt != "gif" && fmt != "webp" && fmt != "svg" ) formats << QString::fromUtf8(fmt); return formats; } bool glaxnimate::io::raster::RasterFormat::on_open(QIODevice& dev, const QString& filename, model::Document* document, const QVariantMap& settings) { auto main = document->assets()->add_comp_no_undo(); main->animation->last_frame.set(main->fps.get()); model::FrameTime default_time = settings["default_time"].toFloat(); main->animation->last_frame.set(default_time == 0 ? default_time : 180); #ifndef WITHOUT_POTRACE if ( settings.value("trace", {}).toBool() ) { QImageReader reader; reader.setDevice(&dev); QImage image = reader.read(); if ( image.isNull() ) return false; utils::trace::TraceWrapper trace(main, image, filename); std::vector colors; std::vector result; auto preset = trace.preset_suggestion(); trace.trace_preset(preset, 16, colors, result); trace.apply(result, preset == utils::trace::TraceWrapper::PixelPreset ? 0 : 1); return true; } #endif auto bmp = document->assets()->images->values.insert(std::make_unique(document)); if ( auto file = qobject_cast(&dev) ) bmp->filename.set(file->fileName()); else bmp->data.set(dev.readAll()); auto img = std::make_unique(document); img->image.set(bmp); QPointF p(bmp->pixmap().width() / 2.0, bmp->pixmap().height() / 2.0); if ( !filename.isEmpty() ) img->name.set(QFileInfo(filename).baseName()); img->transform->anchor_point.set(p); img->transform->position.set(p); main->shapes.insert(std::move(img)); main->width.set(bmp->pixmap().width()); main->height.set(bmp->pixmap().height()); return !bmp->pixmap().isNull(); } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/command/shape_commands.hpp000664 001750 001750 00000002463 14477652011 031412 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include "model/shapes/shape.hpp" #include "object_list_commands.hpp" namespace glaxnimate::model { class Group; } namespace glaxnimate::command { using AddShape = AddObject; using RemoveShape = RemoveObject; using MoveShape = MoveObject; namespace detail { class RedoInCtor : public QUndoCommand { public: void undo() override; void redo() override; protected: using QUndoCommand::QUndoCommand; private: bool did = true; }; } // namespace detail class GroupShapes : public detail::RedoInCtor { public: struct Data { std::vector elements; model::ShapeListProperty* parent = nullptr; }; GroupShapes(const Data& data); static Data collect_shapes(const std::vector& selection); private: model::Group* group = nullptr; }; class UngroupShapes : public detail::RedoInCtor { public: UngroupShapes(model::Group* group); }; AddShape* duplicate_shape(model::ShapeElement* shape); } // namespace glaxnimate::command mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/shapes/styler.cpp000664 001750 001750 00000003435 14477652011 030713 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "styler.hpp" #include "model/document.hpp" #include "model/assets/named_color.hpp" std::vector glaxnimate::model::Styler::valid_uses() const { auto v = document()->assets()->gradients->values.valid_reference_values(true); auto v2 = document()->assets()->colors->values.valid_reference_values(false); v.insert(v.end(), v2.begin(), v2.end()); return v; } bool glaxnimate::model::Styler::is_valid_use(DocumentNode* node) const { return document()->assets()->gradients->values.is_valid_reference_value(node, true) || document()->assets()->colors->values.is_valid_reference_value(node, false); } void glaxnimate::model::Styler::on_use_changed(glaxnimate::model::BrushStyle* new_use, glaxnimate::model::BrushStyle* old_use) { QColor reset; if ( old_use ) { disconnect(old_use, &BrushStyle::style_changed, this, &Styler::on_update_style); if ( auto old_col = qobject_cast(old_use) ) reset = old_col->color.get(); } if ( new_use ) { connect(new_use, &BrushStyle::style_changed, this, &Styler::on_update_style); if ( auto new_col = qobject_cast(new_use) ) reset = new_col->color.get(); } if ( reset.isValid() ) color.set(reset); emit use_changed(new_use); emit use_changed_from(old_use, new_use); } void glaxnimate::model::Styler::on_update_style() { emit property_changed(&use, use.value()); } QBrush glaxnimate::model::Styler::brush(FrameTime t) const { if ( use.get() ) return use->brush_style(t); return color.get_at(t); } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/command/animation_commands.hpp000664 001750 001750 00000016540 14477652011 032272 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include "base.hpp" #include "command/base.hpp" #include "model/animation/animatable.hpp" #include "model/document.hpp" #include "model/object.hpp" namespace glaxnimate::command { class SetKeyframe : public MergeableCommand { public: SetKeyframe( model::AnimatableBase* prop, model::FrameTime time, const QVariant& value, bool commit, bool force_insert = false ); void undo() override; void redo() override; bool merge_with(const SetKeyframe& other); private: model::AnimatableBase* prop; model::FrameTime time; QVariant before; QVariant after; bool had_before; bool calculated = false; int insert_index = -1; model::KeyframeTransition trans_before; model::KeyframeTransition left; model::KeyframeTransition right; bool force_insert = false; }; class RemoveKeyframeTime : public QUndoCommand { public: RemoveKeyframeTime( model::AnimatableBase* prop, model::FrameTime time ); void undo() override; void redo() override; private: model::AnimatableBase* prop; model::FrameTime time; int index; QVariant before; model::KeyframeTransition prev_transition_before; model::KeyframeTransition prev_transition_after; }; class RemoveKeyframeIndex: public QUndoCommand { public: RemoveKeyframeIndex( model::AnimatableBase* prop, int index ); void undo() override; void redo() override; private: model::AnimatableBase* prop; int index; model::FrameTime time; QVariant before; model::KeyframeTransition prev_transition_before; model::KeyframeTransition prev_transition_after; }; class RemoveAllKeyframes : public QUndoCommand { public: RemoveAllKeyframes(model::AnimatableBase* prop, QVariant value); void undo() override; void redo() override; private: struct Keframe { model::FrameTime time; QVariant value; model::KeyframeTransition transition; }; model::AnimatableBase* prop; std::vector keyframes; QVariant before; QVariant after; }; /** * \brief Command that sets multiple animated properties at once, * setting keyframes based on the document record_to_keyframe */ class SetMultipleAnimated : public MergeableCommand { public: SetMultipleAnimated(model::AnimatableBase* prop, QVariant after, bool commit); template SetMultipleAnimated( const QString& name, bool commit, const std::vector& props, Args... vals ) : SetMultipleAnimated(name, props, {}, {QVariant::fromValue(vals)...}, commit) {} /** * \pre props.size() == after.size() && (props.size() == before.size() || before.empty()) * * If before.empty() it will be populated by the properties */ SetMultipleAnimated( const QString& name, const std::vector& props, const QVariantList& before, const QVariantList& after, bool commit ); SetMultipleAnimated(const QString& name, bool commit); void push_property(model::AnimatableBase* prop, const QVariant& after); void push_property_not_animated(model::BaseProperty* prop, const QVariant& after); void undo() override; void redo() override; bool merge_with(const SetMultipleAnimated& other); const std::vector& properties() const { return props; } bool empty() const; private: static QString auto_name(model::AnimatableBase* prop); std::vector props; QVariantList before; QVariantList after; std::vector keyframe_before; bool keyframe_after; model::FrameTime time; std::vector add_0; std::vector props_not_animated; }; class SetKeyframeTransition : public QUndoCommand { public: SetKeyframeTransition( model::AnimatableBase* prop, int keyframe_index, model::KeyframeTransition::Descriptive desc, const QPointF& point, bool before_transition ); SetKeyframeTransition( model::AnimatableBase* prop, int keyframe_index, const model::KeyframeTransition& transition ); void undo() override; void redo() override; private: model::KeyframeBase* keyframe() const; model::AnimatableBase* prop; int keyframe_index; model::KeyframeTransition undo_value; model::KeyframeTransition redo_value; }; class MoveKeyframe : public QUndoCommand { public: MoveKeyframe( model::AnimatableBase* prop, int keyframe_index, model::FrameTime time_after ); void undo() override; void redo() override; /** * \brief The index after redo() * \pre redo() called at least once */ int redo_index() const; private: model::AnimatableBase* prop; int keyframe_index_before; int keyframe_index_after = -1; model::FrameTime time_before; model::FrameTime time_after; }; template class StretchTimeCommand: public QUndoCommand { public: /** * \pre multiplier > 0 */ StretchTimeCommand(T* target, qreal multiplier) : QUndoCommand(QObject::tr("Stretch Time")), target(target), multiplier(multiplier) {} void undo() override { target->stretch_time(1/multiplier); if constexpr ( !std::is_same_v ) target->set_time(target->document()->current_time()); } void redo() override { target->stretch_time(multiplier); if constexpr ( !std::is_same_v ) target->set_time(target->document()->current_time()); } private: T* target; qreal multiplier; }; /** * \brief Command that sets the path of an animated position */ class SetPositionBezier : public MergeableCommand { public: SetPositionBezier(model::detail::AnimatedPropertyPosition* prop, math::bezier::Bezier after, bool commit, const QString& name = ""); SetPositionBezier(model::detail::AnimatedPropertyPosition* prop, math::bezier::Bezier before, math::bezier::Bezier after, bool commit, const QString& name = ""); void undo() override; void redo() override; bool merge_with(const SetPositionBezier& other); private: model::detail::AnimatedPropertyPosition* property; math::bezier::Bezier before; math::bezier::Bezier after; }; /** * \brief Undo command whose children are done and undone in custom order */ class ReorderedUndoCommand : public QUndoCommand { public: using QUndoCommand::QUndoCommand; void add_command(std::unique_ptr cmd, int order_redo, int order_undo) { undo_map[order_undo] = cmd.get(); redo_map[order_redo] = std::move(cmd); } void undo() override { for ( const auto& p : undo_map ) p.second->undo(); } void redo() override { for ( const auto& p : redo_map ) p.second->redo(); } private: std::map> redo_map; std::map undo_map; }; } // namespace glaxnimate::command mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/visitor.hpp000664 001750 001750 00000001555 14477652011 027613 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include namespace glaxnimate::model { class Document; class Composition; class DocumentNode; class Visitor { public: virtual ~Visitor() {} void visit(model::Document* doc, model::Composition* main, bool skip_locked = false); void visit(model::DocumentNode* node, bool skip_locked = false); private: virtual void on_visit(model::DocumentNode* node) = 0; virtual void on_visit_end(model::DocumentNode* node) { Q_UNUSED(node) } virtual void on_visit(model::Document* document, model::Composition* main) { Q_UNUSED(document) Q_UNUSED(main) } virtual void on_visit_end(model::Document* document, model::Composition* main) { Q_UNUSED(document) Q_UNUSED(main) } }; } // namespace glaxnimate::model mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/property/reference_property.cpp000664 001750 001750 00000000612 14477652011 033666 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "reference_property.hpp" #include "model/document.hpp" void glaxnimate::model::ReferencePropertyBase::transfer(model::Document* doc) { auto ref = get_ref(); if ( ref && !is_valid_option(ref) ) set_ref(doc->find_by_uuid(ref->uuid.get())); } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/animation/000775 001750 001750 00000000000 14477652011 027354 5ustar00ddennedyddennedy000000 000000 mlt-7.22.0/src/modules/glaxnimate/glaxnimate/external/QtAppSetup/src/app/scripting/python/000775 001750 001750 00000000000 14477652011 033340 5ustar00ddennedyddennedy000000 000000 mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/binary_stream.cpp000664 001750 001750 00000006142 14477652011 030252 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "binary_stream.hpp" #include #include glaxnimate::io::BinaryInputStream::BinaryInputStream(QIODevice* file): BinaryInputStream(file->readAll()) { } glaxnimate::io::BinaryInputStream::BinaryInputStream(QByteArray data): data(std::move(data)), data_start(this->data.data()), data_end(data_start + this->data.size()) { } void glaxnimate::io::BinaryInputStream::on_overflow() { error = true; } bool glaxnimate::io::BinaryInputStream::eof() const { return data_start >= data_end; } bool glaxnimate::io::BinaryInputStream::has_error() const { return error; } QByteArray glaxnimate::io::BinaryInputStream::read(qint64 max_size) { if ( data_start + max_size < data_end ) { data_start += max_size; return QByteArray(data_start - max_size, max_size); } on_overflow(); return {}; } quint8 glaxnimate::io::BinaryInputStream::next() { if ( data_start < data_end ) { ++data_start; return data_start[-1]; } on_overflow(); return {}; } quint32 glaxnimate::io::BinaryInputStream::read_uint32_le() { static_assert(sizeof(quint32) == 4); auto data = read(4); if ( data.size() == 4 ) { return qFromLittleEndian(data.data()); } else { return 0; } } glaxnimate::io::Float32 glaxnimate::io::BinaryInputStream::read_float32_le() { static_assert(sizeof(Float32) == 4); auto data = read(4); if ( data.size() == 4 ) { return qFromLittleEndian(data.data()); } else { on_overflow(); return 0; } } glaxnimate::io::VarUint glaxnimate::io::BinaryInputStream::read_uint_leb128() { VarUint result = 0; VarUint shift = 0; while (true) { quint8 byte = next(); if ( error ) return 0; result |= VarUint(byte & 0x7f) << shift; if ( !(byte & 0x80) ) return result; shift += 7; } } glaxnimate::io::BinaryOutputStream::BinaryOutputStream(QIODevice* file) : file(file) { } void glaxnimate::io::BinaryOutputStream::write(const QByteArray& data) { file->write(data); } void glaxnimate::io::BinaryOutputStream::write_byte(quint8 v) { file->putChar(v); } void glaxnimate::io::BinaryOutputStream::write_float32_le(glaxnimate::io::Float32 v) { static_assert(sizeof(Float32) == 4); std::array data; qToLittleEndian(v, data.data()); file->write((const char*)data.data(), 4); } void glaxnimate::io::BinaryOutputStream::write_uint32_le(quint32 v) { static_assert(sizeof(quint32) == 4); std::array data; qToLittleEndian(v, data.data()); file->write((const char*)data.data(), 4); } void glaxnimate::io::BinaryOutputStream::write_uint_leb128(glaxnimate::io::VarUint v) { while ( true ) { quint8 byte = v & 0x7f; v >>= 7; if ( v == 0 ) { write_byte(byte); break; } write_byte(byte | 0x80); } } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/external/QtAppSetup/src/app/utils/desktop.hpp000664 001750 001750 00000000541 14477652011 033337 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include namespace app::desktop { inline bool open_file(const QString& path) { return QDesktopServices::openUrl(QUrl::fromLocalFile(path)); } } // namespace app::desktop mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/mime/json_mime.hpp000664 001750 001750 00000001566 14477652011 030334 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include "io/glaxnimate/glaxnimate_mime.hpp" namespace glaxnimate::io::mime { class JsonMime : public io::mime::MimeSerializer { public: QString slug() const override { return "json"; } QString name() const override { return QObject::tr("JSON"); } QStringList mime_types() const override { return {"application/json", "text/plain"}; } QByteArray serialize(const std::vector& selection) const override { QJsonDocument json = io::glaxnimate::GlaxnimateMime::serialize_json(selection); return json.toJson(QJsonDocument::Indented); } bool can_deserialize() const override { return false; } private: static Autoreg autoreg; }; } // namespace glaxnimate::io::mime mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/utils/tar.hpp000664 001750 001750 00000004210 14477652011 026731 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include #include #include #include "app/log/log_line.hpp" namespace glaxnimate::utils::tar { class ArchiveEntry { public: ArchiveEntry(const ArchiveEntry& oth); ArchiveEntry(ArchiveEntry&& oth); ArchiveEntry& operator=(const ArchiveEntry& oth); ArchiveEntry& operator=(ArchiveEntry&& oth); bool operator==(const ArchiveEntry& oth) const; bool operator!=(const ArchiveEntry& oth) const; ~ArchiveEntry(); const QString& path() const; bool valid() const; private: class Private; ArchiveEntry(std::unique_ptr d); std::unique_ptr d; friend class TapeArchive; }; class TapeArchive : public QObject { Q_OBJECT public: class iterator { public: iterator(TapeArchive* archive, ArchiveEntry entry) : archive(archive), entry(std::move(entry)) {} const ArchiveEntry* operator->() const { return &entry; } const ArchiveEntry& operator*() const { return entry; } iterator& operator++() { entry = archive->next(); return *this; }; bool operator==(const iterator& oth) const { return entry == oth.entry && archive == oth.archive; } bool operator!=(const iterator& oth) const { return !(*this == oth); } private: TapeArchive* archive; ArchiveEntry entry; }; explicit TapeArchive(const QString& filename); explicit TapeArchive(const QByteArray& data); ~TapeArchive(); bool extract(const ArchiveEntry& entry, const QDir& destination); bool finished() const; ArchiveEntry next(); iterator begin(); iterator end(); const QString& error() const; signals: void message(const QString& message, app::log::Severity Severity); private: class Private; std::unique_ptr d; friend class ArchiveEntry; }; QString libarchive_version(); } // namespace glaxnimate::utils::tar mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/math/geom.cpp000664 001750 001750 00000004115 14477652011 026662 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "geom.hpp" #include #include "math.hpp" using namespace glaxnimate; QPointF math::line_closest_point(const QPointF& line_a, const QPointF& line_b, const QPointF& p) { QPointF a_to_p = p - line_a; QPointF a_to_b = line_b - line_a; qreal atb2 = length_squared(a_to_b); qreal atp_dot_atb = QPointF::dotProduct(a_to_p, a_to_b); qreal t = atp_dot_atb / atb2; return line_a + a_to_b * t; } // Algorithm from http://ambrsoft.com/TrigoCalc/Circle3D.htm QPointF math::circle_center(const QPointF& p1, const QPointF& p2, const QPointF& p3) { qreal x1 = p1.x(); qreal x2 = p2.x(); qreal x3 = p3.x(); qreal y1 = p1.y(); qreal y2 = p2.y(); qreal y3 = p3.y(); qreal A = 2 * (x1 * (y2 - y3) - y1 * (x2 - x3) + x2 * y3 - x3 * y2); qreal p12 = x1*x1 + y1*y1; qreal p22 = x2*x2 + y2*y2; qreal p32 = x3*x3 + y3*y3; qreal B = p12 * (y3 - y2) + p22 * (y1 - y3) + p32 * (y2 - y1); qreal C = p12 * (x2 - x3) + p22 * (x3 - x1) + p32 * (x1 - x2); return { - B / A, - C / A }; } // Custom implementation rather than using QVector3D to keep precision static std::array cross_product(const std::array& a, const std::array& b) { return { a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0], }; } std::optional math::line_intersection(const QPointF& start1, const QPointF& end1, const QPointF& start2, const QPointF& end2) { std::array v1{start1.x(), start1.y(), 1}; std::array v2{end1.x(), end1.y(), 1}; std::array v3{start2.x(), start2.y(), 1}; std::array v4{end2.x(), end2.y(), 1}; std::array cp = cross_product( cross_product(v1, v2), cross_product(v3, v4) ); // More forgiving than qFuzzyIsNull if ( math::abs(cp[2]) <= 0.00001 ) return {}; return QPointF(cp[0] / cp[2], cp[1] / cp[2]); } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/shapes/path.hpp000664 001750 001750 00000002374 14477652011 030333 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #pragma once #include "shape.hpp" #include "model/animation/animatable_path.hpp" namespace glaxnimate::model { class Path : public Shape { GLAXNIMATE_OBJECT(Path) using PointType = math::bezier::PointType; Q_ENUM(PointType) public: GLAXNIMATE_ANIMATABLE(math::bezier::Bezier, shape, &Path::shape_changed) GLAXNIMATE_PROPERTY(bool, closed, false, &Path::closed_changed) public: using Shape::Shape; QIcon tree_icon() const override { return QIcon::fromTheme("draw-bezier-curves"); } QString type_name_human() const override { return tr("Path"); } math::bezier::Bezier to_bezier(FrameTime t) const override { auto bezier = shape.get_at(t); if ( reversed.get() ) bezier.reverse(); return bezier; } QRectF local_bounding_rect(FrameTime t) const override { return shape.get_at(t).bounding_box(); } private: void closed_changed(bool closed) { shape.set_closed(closed); } signals: void shape_changed(const math::bezier::Bezier& bez); }; } // namespace glaxnimate::model mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/math/bezier/bezier.cpp000664 001750 001750 00000015710 14477652011 030476 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "bezier.hpp" using namespace glaxnimate; QRectF math::bezier::Bezier::bounding_box() const { if ( size() < 2 ) return {}; auto pair = solver_for_point(0).bounds(); QRectF box(pair.first, pair.second); for ( int i = 1; i < size() - 1; i++ ) { pair = solver_for_point(i).bounds(); box |= QRectF(pair.first, pair.second); } if ( closed_ ) { pair = solver_for_point(size()-1).bounds(); box |= QRectF(pair.first, pair.second); } return box; } void math::bezier::Bezier::split_segment(int index, qreal factor) { if ( points_.empty() ) return; if ( index < 0 ) { points_.insert(points_.begin(), points_[0]); return; } else if ( index >= size() ) { points_.insert(points_.end(), points_.back()); return; } auto split_points = solver_for_point(index).split(factor); points_[index].tan_out = split_points.first[1]; points_[(index+1) % size()].tan_in = split_points.second[2]; auto type = Smooth; if ( factor <= 0 ) type = points_[index].type; else if ( factor >= 1 ) type = points_[(index+1) % size()].type; points_.insert(points_.begin() + index + 1, Point( split_points.first[3], split_points.first[2], split_points.second[1], type )); } math::bezier::Point math::bezier::Bezier::split_segment_point(int index, qreal factor) const { if ( index < 0 ) return points_[0]; else if ( index >= size() ) return points_.back(); if ( factor <= 0 ) return points_[index]; else if ( factor >= 1 ) return points_[(index+1) % size()]; auto split_points = solver_for_point(index).split(factor); return Point( split_points.first[3], split_points.first[2], split_points.second[1], Smooth ); } void math::bezier::Bezier::add_to_painter_path(QPainterPath& out) const { if ( size() < 2 ) return; out.moveTo(points_[0].pos); for ( int i = 1; i < size(); i++ ) { out.cubicTo(points_[i-1].tan_out, points_[i].tan_in, points_[i].pos); } if ( closed_ ) { out.cubicTo(points_.back().tan_out, points_[0].tan_in, points_[0].pos); out.closeSubpath(); } } math::bezier::Bezier math::bezier::Bezier::lerp(const math::bezier::Bezier& other, qreal factor) const { if ( other.closed_ != closed_ || other.size() != size() ) return *this; math::bezier::Bezier lerped; lerped.closed_ = closed_; lerped.points_.reserve(size()); for ( int i = 0; i < size(); i++ ) lerped.points_.push_back(Point::from_relative( math::lerp(points_[i].pos, other.points_[i].pos, factor), math::lerp( points_[i].tan_in - points_[i].pos, other.points_[i].tan_in - other.points_[i].pos, factor ), math::lerp( points_[i].tan_out - points_[i].pos, other.points_[i].tan_out - other.points_[i].pos, factor ) )); return lerped; } void math::bezier::Bezier::reverse() { std::reverse(points_.begin(), points_.end()); if ( closed_ && points_.size() > 1 ) { auto back = points_.back(); points_.pop_back(); points_.insert(points_.begin(), back); } for ( auto& p : points_ ) std::swap(p.tan_in, p.tan_out); } math::bezier::BezierSegment math::bezier::Bezier::segment(int index) const { return { points_[index].pos, points_[index].tan_out, points_[(index+1) % points_.size()].tan_in, points_[(index+1) % points_.size()].pos }; } math::bezier::BezierSegment math::bezier::Bezier::inverted_segment(int index) const { return { points_[(index+1) % points_.size()].pos, points_[(index+1) % points_.size()].tan_in, points_[index].tan_out, points_[index].pos }; } void math::bezier::Bezier::set_segment(int index, const math::bezier::BezierSegment& s) { points_[index].pos = s[0]; points_[index].drag_tan_out(s[1]); points_[(index+1) % points_.size()].pos = s[3]; points_[(index+1) % points_.size()].drag_tan_in(s[2]); } math::bezier::Bezier math::bezier::Bezier::transformed(const QTransform& t) const { auto copy = *this; copy.transform(t); return copy; } void math::bezier::Bezier::transform(const QTransform& t) { for ( auto& p : points_ ) p.transform(t); } int glaxnimate::math::bezier::Bezier::segment_count() const { return closed_ || points_.empty() ? points_.size() : points_.size() - 1; } math::bezier::Bezier math::bezier::Bezier::removed_points(const std::set& indices) const { math::bezier::Bezier new_bez; new_bez.set_closed(closed_); for ( int i = 0; i < size(); i++ ) if ( !indices.count(i) ) new_bez.push_back(points_[i]); return new_bez; } void glaxnimate::math::bezier::Bezier::add_close_point() { if ( closed_ && !points_.empty() && !math::fuzzy_compare(points_[0].pos, points_.back().pos) ) { points_.push_back(points_[0]); points_.back().tan_out = points_[0].tan_in = points_[0].pos; } } QRectF math::bezier::MultiBezier::bounding_box() const { if ( beziers_.empty() ) return {}; QRectF box; for ( const Bezier& bez : beziers_ ) { QRectF bb = bez.bounding_box(); if ( box.isNull() ) box = bb; else if ( !bb.isNull() ) box |= bb; } return box; } void math::bezier::MultiBezier::append(const QPainterPath& path) { std::array data; int data_i = 0; for ( int i = 0; i < path.elementCount(); i++ ) { auto element = path.elementAt(i); switch ( element.type ) { case QPainterPath::MoveToElement: if ( !beziers_.empty() && beziers_.back()[0].pos == beziers_.back().back().pos ) close(); move_to(element); break; case QPainterPath::LineToElement: line_to(element); break; case QPainterPath::CurveToElement: data_i = 0; data[0] = element; break; case QPainterPath::CurveToDataElement: ++data_i; data[data_i] = element; if ( data_i == 2 ) { cubic_to(data[0], data[1], data[2]); data_i = -1; } break; } } } void math::bezier::MultiBezier::transform(const QTransform& t) { for ( auto& bez : beziers_ ) bez.transform(t); } math::bezier::MultiBezier math::bezier::MultiBezier::from_painter_path(const QPainterPath& path) { math::bezier::MultiBezier bez; bez.append(path); return bez; } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/svg/svg_parser.hpp000664 001750 001750 00000002615 14477652011 030373 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include #include #include #include "io/mime/mime_serializer.hpp" #include "io/base.hpp" namespace glaxnimate::model { class Composition; class Document; class DocumentNode; class Object; } // namespace glaxnimate::model namespace glaxnimate::io::svg { class SvgParser { public: // How to parse elements enum GroupMode { Groups, ///< As group shapes Layers, ///< As shape layers Inkscape, ///< Follow inkscape:groupmode }; /** * \throws SvgParseError on error */ SvgParser( QIODevice* device, GroupMode group_mode, model::Document* document, const std::function& on_warning = {}, ImportExport* io = nullptr, QSize forced_size = {}, model::FrameTime default_time = 180, QDir default_asset_path = {} ); ~SvgParser(); void parse_to_document(); io::mime::DeserializedData parse_to_objects(); class Private; private: std::unique_ptr d; }; /** * \brief Parses a CSS color string * \see https://www.w3.org/wiki/CSS/Properties/color */ QColor parse_color(const QString& color_str); } // namespace glaxnimate::io::svg mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/shapes/ellipse.hpp000664 001750 001750 00000001251 14477652011 031025 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include "shape.hpp" #include "math/ellipse_solver.hpp" namespace glaxnimate::model { class Ellipse : public Shape { GLAXNIMATE_OBJECT(Ellipse) GLAXNIMATE_ANIMATABLE(QPointF, position, QPointF()) GLAXNIMATE_ANIMATABLE(QSizeF, size, QSizeF()) public: using Shape::Shape; QIcon tree_icon() const override; QString type_name_human() const override; math::bezier::Bezier to_bezier(FrameTime t) const override; QRectF local_bounding_rect(FrameTime t) const override; }; } // namespace glaxnimate::model mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/svg/svg_renderer.hpp000664 001750 001750 00000002302 14477652011 030676 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include #include "model/shapes/shape.hpp" namespace glaxnimate::model { class EmbeddedFont; } // namespace glaxnimate::model namespace glaxnimate::io::svg { enum AnimationType { NotAnimated, SMIL }; enum class CssFontType { None, Embedded, FontFace, Link, }; class SvgRenderer { public: SvgRenderer(AnimationType animated, CssFontType font_type); ~SvgRenderer(); void write_composition(model::Composition* comp); void write_main(model::Composition* comp); void write_shape(model::ShapeElement* shape); void write_node(model::DocumentNode* node); QDomDocument dom() const; void write(QIODevice* device, bool indent); static CssFontType suggested_type(model::EmbeddedFont* font); private: class Private; std::unique_ptr d; }; /** * \brief Converts a multi bezier into path data * \returns pair of [path data, sodipodi nodetypes] */ std::pair path_data(const math::bezier::MultiBezier& shape); } // namespace glaxnimate::io::svg mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/stretchable_time.cpp000664 001750 001750 00000001263 14477652011 031421 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "stretchable_time.hpp" GLAXNIMATE_OBJECT_IMPL(glaxnimate::model::StretchableTime) bool glaxnimate::model::StretchableTime::validate_stretch(float stretch) { return stretch > 0; } float glaxnimate::model::StretchableTime::time_to_local(float global) const { return (global - start_time.get()) / stretch.get(); } float glaxnimate::model::StretchableTime::time_from_local(float local) const { return local * stretch.get() + start_time.get(); } QString glaxnimate::model::StretchableTime::type_name_human() const { return tr("Timing"); } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/avd/avd_format.hpp000664 001750 001750 00000001652 14477652011 030315 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include "io/base.hpp" #include "io/io_registry.hpp" namespace glaxnimate::io::avd { class AvdFormat : public ImportExport { Q_OBJECT public: QString slug() const override { return "avd"; } QString name() const override { return tr("Android Vector Drawable"); } QStringList extensions() const override { return {"xml"}; } bool can_save() const override { return true; } bool can_open() const override { return true; } protected: bool on_open(QIODevice& file, const QString&, model::Document* document, const QVariantMap& options) override; bool on_save(QIODevice & file, const QString & filename, model::Composition* comp, const QVariantMap & setting_values) override; private: static Autoreg autoreg; }; } // namespace glaxnimate::io::avd mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/glaxnimate/glaxnimate_importer.cpp000664 001750 001750 00000002474 14477652011 033622 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "glaxnimate_format.hpp" #include "import_state.hpp" #include "model/assets/assets.hpp" using namespace glaxnimate; bool io::glaxnimate::GlaxnimateFormat::on_open ( QIODevice& file, const QString&, model::Document* document, const QVariantMap& ) { QJsonDocument jdoc; try { jdoc = QJsonDocument::fromJson(file.readAll()); } catch ( const QJsonParseError& err ) { error(tr("Could not parse JSON: %1").arg(err.errorString())); return false; } if ( !jdoc.isObject() ) { error(tr("No JSON object found")); return false; } QJsonObject top_level = jdoc.object(); int document_version = top_level["format"].toObject()["format_version"].toInt(0); if ( document_version > format_version ) warning(tr("Opening a file from a newer version of Glaxnimate")); detail::ImportState state(this, document, document_version); state.load_document(top_level); if ( document->assets()->compositions->values.empty() ) { document->assets()->compositions->values.insert(std::make_unique(document)); error(tr("Missing composition")); return false; } return true; } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/external/QtAppSetup/src/app/log/listener_stderr.hpp000664 001750 001750 00000001261 14477652011 034517 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include "app/log/logger.hpp" namespace app::log { class ListenerStderr: public LogListener { protected: void on_line(const LogLine& line) override { QMessageLogger logger; QDebug stream = logger.warning(); if ( line.severity == Info ) stream = logger.info(); stream << line.time.toString(Qt::ISODate) << Logger::severity_name(line.severity) << line.source << line.source_detail << line.message ; } }; } // namespace app::log mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/property/sub_object_property.hpp000664 001750 001750 00000004625 14477652011 034064 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include "model/object.hpp" #include "model/property/property.hpp" #define GLAXNIMATE_SUBOBJECT(type, name) \ public: \ SubObjectProperty name{this, #name}; \ type* get_##name() { return name.get(); } \ private: \ Q_PROPERTY(type* name READ get_##name) \ // macro end namespace glaxnimate::model { class SubObjectPropertyBase : public BaseProperty { public: SubObjectPropertyBase(Object* obj, const QString& name) : BaseProperty(obj, name, {PropertyTraits::Object}) {} virtual const model::Object* sub_object() const = 0; virtual model::Object* sub_object() = 0; }; template class SubObjectProperty : public SubObjectPropertyBase { public: SubObjectProperty(Object* obj, const QString& name) : SubObjectPropertyBase(obj, name), sub_obj(obj->document()) {} const Type* operator->() const { return &sub_obj; } Type* operator->() { return &sub_obj; } QVariant value() const override { return QVariant::fromValue(const_cast(&sub_obj)); } bool valid_value(const QVariant & v) const override { return v.value(); } bool set_value(const QVariant& val) override { if ( !val.canConvert() ) return false; if ( Type* t = val.value() ) return set_clone(t); return false; } Type* set_clone(Type* object) { if ( !object ) return nullptr; sub_obj.assign_from(object); return &sub_obj; } Type* get() { return &sub_obj; } const Type* get() const { return &sub_obj; } model::Object * sub_object() override { return &sub_obj; } const model::Object * sub_object() const override { return &sub_obj; } void set_time(FrameTime t) override { sub_obj.set_time(t); } void transfer(Document* doc) override { sub_obj.transfer(doc); } void stretch_time(qreal multiplier) override { sub_obj.stretch_time(multiplier); } private: Type sub_obj; }; } // namespace glaxnimate::model mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/aep/string_decoder.hpp000664 001750 001750 00000000560 14477652011 031156 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include namespace glaxnimate::io::aep { QString decode_string(const QByteArray& data); QString decode_utf16(const QByteArray& data, bool big_endian); } // namespace glaxnimate::io::aep mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/lottie/lottie_html_format.hpp000664 001750 001750 00000001561 14477652011 032614 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include "io/base.hpp" namespace glaxnimate::io::lottie { class LottieHtmlFormat : public ImportExport { Q_OBJECT public: QString slug() const override { return "lottie_html"; } QString name() const override { return tr("Lottie HTML Preview"); } QStringList extensions() const override { return {"html", "htm"}; } bool can_save() const override { return true; } bool can_open() const override { return false; } static QByteArray html_head(ImportExport* ie, model::Composition* comp, const QString& extra); private: bool on_save(QIODevice& file, const QString& filename, model::Composition* comp, const QVariantMap& setting_values) override; }; } // namespace glaxnimate::io::lottie mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/lottie/000775 001750 001750 00000000000 14477652011 026204 5ustar00ddennedyddennedy000000 000000 mlt-7.22.0/src/modules/glaxnimate/glaxnimate/external/QtAppSetup/src/app/000775 001750 001750 00000000000 14506447607 030023 5ustar00ddennedyddennedy000000 000000 mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/math/math.hpp000664 001750 001750 00000004234 14477652011 026673 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include #include namespace glaxnimate::math { constexpr const qreal pi = 3.14159265358979323846; constexpr const qreal tau = pi*2; constexpr const qreal sqrt_2 = M_SQRT2; constexpr const qreal ellipse_bezier = 0.5519; using std::sqrt; using std::sin; using std::cos; using std::tan; using std::acos; using std::asin; using std::atan; using std::atan2; using std::pow; template constexpr Numeric sign(Numeric x) noexcept { return x < 0 ? -1 : 1; } constexpr qreal rad2deg(qreal rad) noexcept { return rad / pi * 180; } constexpr qreal deg2rad(qreal rad) noexcept { return rad * pi / 180; } template Numeric fmod(Numeric x, Numeric y) { return x < 0 ? std::fmod(std::fmod(x, y) + y, y) : std::fmod(x, y) ; } template constexpr inline const T & min(const T &a, const T &b) noexcept { return (a < b) ? a : b; } template constexpr inline const T & max(const T &a, const T &b) noexcept { return (a < b) ? b : a; } template constexpr inline const T &bound(const T &vmin, const T &val, const T &vmax) noexcept { return max(vmin, min(vmax, val)); } template constexpr inline T abs(T t) noexcept { return t < 0 ? -t : t; } /** * \brief Reverses linear interpolation * \param a First value interpolated from * \param b Second value interpolated from * \param c Interpolation result * \pre a < b && a <= c <= b * \returns Factor \p f so that lerp(a, b, f) == c */ template constexpr qreal unlerp(const T& a, const T& b, const T& c) { return qreal(c-a) / qreal(b-a); } template constexpr T lerp(const T& a, const T& b, double factor) { return a * (1-factor) + b * factor; } inline qreal sum_squared() { return 0; } template inline qreal sum_squared(H head, T... args) { return qreal(head) * head + sum_squared(args...); } template inline qreal hypot(T... args) { return sqrt(sum_squared(args...)); } } // namespace glaxnimate::math mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/glaxnimate/glaxnimate_format.hpp000664 001750 001750 00000002772 14477652011 033257 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include #include #include "io/base.hpp" #include "io/io_registry.hpp" namespace glaxnimate::io::glaxnimate { class GlaxnimateFormat : public ImportExport { Q_OBJECT public: static const int format_version; QString slug() const override { return "glaxnimate"; } QString name() const override { return tr("Glaxnimate Animation"); } // RAWR = Reasonable Animation at Whatever Resolution QStringList extensions() const override { return {"rawr"}; } bool can_save() const override { return true; } bool can_open() const override { return true; } static QJsonDocument to_json(model::Document* document); static QJsonObject to_json(model::Object* object); static QJsonValue to_json(model::BaseProperty* property); static QJsonValue to_json(const QVariant& value); static QJsonValue to_json(const QVariant& value, model::PropertyTraits traits); static QJsonObject format_metadata(); static GlaxnimateFormat* instance() { return autoreg.registered; } protected: bool on_save(QIODevice& file, const QString&, model::Composition* comp, const QVariantMap&) override; bool on_open(QIODevice& file, const QString&, model::Document* document, const QVariantMap&) override; private: static Autoreg autoreg; }; } // namespace glaxnimate::io::glaxnimate mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/assets/pending_asset.hpp000664 001750 001750 00000000563 14477652011 032237 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include namespace glaxnimate::model { struct PendingAsset { int id = 0; QUrl url; QByteArray data; QString name_alias; bool loaded = false; }; } // namespace glaxnimate::model mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/app_info.hpp000664 001750 001750 00000002200 14477652011 026573 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include #include #include namespace glaxnimate { class AppInfo { public: static AppInfo& instance() { static AppInfo singleton; return singleton; } /** * \brief Project machine-readable name */ QString slug() const; /** * \brief Project machine-readable org name */ QString organization() const; /** * \brief Project version */ QString version() const; /** * \brief Project human-readable name */ QString name() const; /** * \brief Documentation URL */ QUrl url_docs() const; /** * \brief Bug reporting URL */ QUrl url_issues() const; /** * \brief Donation URL */ QUrl url_donate() const; /** * \brief Application description */ QString description() const; void init_qapplication() const; private: AppInfo() = default; ~AppInfo() = default; }; } // namespace glaxnimate mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/rive/rive_loader.cpp000664 001750 001750 00000077733 14477652011 030671 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "rive_loader.hpp" #include "io/animated_properties.hpp" #include "model/assets/assets.hpp" #include "model/shapes/precomp_layer.hpp" #include "model/shapes/rect.hpp" #include "model/shapes/ellipse.hpp" #include "model/shapes/polystar.hpp" #include "model/shapes/path.hpp" #include "model/shapes/fill.hpp" #include "model/shapes/stroke.hpp" #include "model/shapes/image.hpp" using namespace glaxnimate; using namespace glaxnimate::io; using namespace glaxnimate::io::rive; namespace { struct Artboard { Artboard() = default; Artboard(Object* first, Object* last) : object(first), children(object), child_count(last - first + 1) {} Object* operator->() const { return object; } Object* object = nullptr; Object* children = nullptr; Identifier child_count = 0; VarUint timeline_duration = 0; VarUint keyframe_timeline_duration = 0; model::Composition* comp = nullptr; QSizeF size; }; template T load_property_get_keyframe(const detail::JoinedPropertyKeyframe& kf, std::size_t index); template<> Float32 load_property_get_keyframe(const detail::JoinedPropertyKeyframe& kf, std::size_t index) { return kf.values[index].vector()[0]; } template<> VarUint load_property_get_keyframe(const detail::JoinedPropertyKeyframe& kf, std::size_t index) { return kf.values[index].vector()[0]; } template<> QColor load_property_get_keyframe(const detail::JoinedPropertyKeyframe& kf, std::size_t index) { return kf.values[index].color(); } template void load_property_impl(Object* rive, PropT& property, const detail::AnimatedProperties& animations, const std::array& names, T... defvals, const Func& value_func, std::index_sequence) { property.set(value_func(rive->get(names[Ind], defvals)...)); for ( const auto& kf : animations.joined(std::vector(names.begin(), names.end())) ) property.set_keyframe(kf.time, value_func(load_property_get_keyframe(kf, Ind)...))->set_transition(kf.transition); } template void load_property(Object* rive, PropT& property, const detail::AnimatedProperties& animations, const std::array& names, T... defvals, const Func& value_func) { load_property_impl(rive, property, animations, names, defvals..., value_func, std::index_sequence_for{}); } template void load_property_vector_impl(Object* rive, PropT& property, const detail::AnimatedProperties& animations, const std::array& names, T... defvals, const Func& value_func, std::index_sequence) { property.set( std::get( value_func(rive->get(names[Ind], defvals)...) ) ); for ( const auto& kf : animations.joined(std::vector(names.begin(), names.end())) ) property.set_keyframe(kf.time, std::get(value_func(load_property_get_keyframe(kf, Ind)...)) )->set_transition(kf.transition); } template void expand(T...) {} template void load_properties_impl(Object* rive, const std::tuple& properties, const detail::AnimatedProperties& animations, const std::array& names, T... defvals, const Func& value_func, std::index_sequence ind, std::index_sequence) { expand( (load_property_vector_impl( rive, *std::get(properties), animations, names, defvals..., value_func, ind ), 0) ... ); } template void load_properties( Object* rive, std::tuple properties, const detail::AnimatedProperties& animations, const std::array& names, T... defvals, const Func& value_func) { load_properties_impl( rive, properties, animations, names, defvals..., value_func, std::index_sequence_for{}, std::index_sequence_for{}); } template void load_property(Object* rive, PropT& property, const detail::AnimatedProperties& animations, const char* name, T defval = {}) { property.set(rive->get(name, defval)); for ( const auto& kf : animations.joined({name}) ) property.set_keyframe(kf.time, load_property_get_keyframe(kf, 0))->set_transition(kf.transition); } QPointF make_point(Float32 x, Float32 y) { return QPointF(x, y); } struct Asset { Object* object = nullptr; model::Asset* asset = nullptr; }; struct LoadCotext { void new_artboard(Object* object) { artboards[object] = Artboard(object, &objects.back()); artboard = &artboards[object]; artboards_id.push_back(artboard); artboard->comp = document->assets()->compositions->values.insert(std::make_unique(document)); artboard->size = QSizeF( object->get("width"), object->get("height") ); } Object* artboard_child(Identifier id) const { if ( artboard && id < artboard->child_count ) return artboard->children + id; return nullptr; } LoadCotext(RiveFormat* format, model::Document* document) : document(document), format(format) { main = document->assets()->compositions->values.insert(std::make_unique(document)); } void preprocess_object(Object* object) { if ( object->type().id == TypeId::Artboard ) { new_artboard(object); } else if ( object->type().id == TypeId::KeyedObject ) { if ( !artboard ) { format->warning(QObject::tr("Unexpected Keyed Object")); return; } auto id = object->get("objectId", artboard->child_count); keyed_object = artboard_child(id); keyed_property = nullptr; if ( !keyed_object ) { format->warning(QObject::tr("Invalid Keyed Object id %1").arg(id)); return; } } else if ( object->type().id == TypeId::KeyedProperty ) { if ( !keyed_object ) { format->warning(QObject::tr("Unexpected Keyed Property")); return; } auto id = object->get("propertyKey"); auto prop = keyed_object->type().property(id); if ( !prop ) { format->warning(QObject::tr("Unknown Keyed Property id %1").arg(id)); return; } keyed_object->animations().push_back({prop, {}}); keyed_property = &keyed_object->animations().back(); } else if ( object->type().id == TypeId::LinearAnimation ) { if ( !artboard ) { format->warning(QObject::tr("Unexpected Animation")); return; } auto duration = object->get("duration"); if ( duration > artboard->timeline_duration ) artboard->timeline_duration = duration; } else if ( object->type().id == TypeId::ImageAsset ) { assets.push_back({object, load_image_asset(object)}); } else if ( object->type().id == TypeId::FileAssetContents ) { if ( assets.empty() ) { format->warning(QObject::tr("Unexpected Asset Contents")); return; } auto data = object->get("bytes"); if ( data.isEmpty() ) return; if ( auto img = qobject_cast(assets.back().asset) ) { if ( !img->from_raw_data(data) ) format->warning(QObject::tr("Invalid Image Data")); } } else if ( object->has_type(TypeId::Asset) ) { assets.push_back({object, nullptr}); } else if ( object->has_type(TypeId::KeyFrame) ) { if ( !keyed_property ) { format->warning(QObject::tr("Unexpected Keyframe")); return; } auto frame = object->get("duration"); if ( frame > artboard->keyframe_timeline_duration ) artboard->keyframe_timeline_duration = frame; keyed_property->keyframes.push_back(object); } else if ( object->has("parentId") ) { auto parent_id = object->get("parentId"); auto parent = artboard_child(parent_id); if ( !parent ) format->warning(QObject::tr("Could not find parent with id %1").arg(parent_id)); else parent->children().push_back(object); } } void process_object(Object* object) { if ( object->type().id == TypeId::Artboard ) { process_artboard(object); } } void process_artboard(Object* object) { const auto& artboard = artboards.at(object); artboard.comp->name.set(object->get("name")); add_shapes(object, artboard.comp->shapes); auto precomp_layer = std::make_unique(document); precomp_layer->name.set(artboard.comp->name.get()); precomp_layer->size.set(artboard.size.toSize()); detail::AnimatedProperties animations = load_animations(object); load_transform(object, precomp_layer->transform.get(), animations, QRectF(QPointF(0, 0), artboard.size)); precomp_layer->opacity.set(object->get("opacity", 1)); precomp_layer->composition.set(artboard.comp); float last_frame = artboard.timeline_duration == 0 ? artboard.keyframe_timeline_duration : artboard.timeline_duration; main->animation->last_frame.set(qMax(main->animation->last_frame.get(), last_frame)); if ( document->assets()->compositions->values.size() == 1 ) { main->width.set(precomp_layer->size.get().width()); main->height.set(precomp_layer->size.get().height()); } main->shapes.insert(std::move(precomp_layer)); } void add_shapes(Object* parent, model::ObjectListProperty& prop) { std::vector> shapes; for ( Object* child : parent->children() ) { if ( child == parent ) { format->error(QObject::tr("Parent circular reference detected")); continue; } auto shape = load_shape(child); if ( shape ) { if ( child->has_type(TypeId::Node) ) shapes.emplace_back(std::move(shape)); else prop.insert(std::move(shape)); } } for ( auto it = shapes.rbegin(); it != shapes.rend(); ++it ) prop.insert(std::move(*it)); } std::unique_ptr load_shape_layer(Object* shape, const detail::AnimatedProperties& animations) { auto layer = std::make_unique(document); load_shape_group(shape, layer.get(), animations); return layer; } void load_transform(Object* rive, model::Transform* transform, const detail::AnimatedProperties& animations, const QRectF& bbox) { load_property(rive, transform->position, animations, {"x", "y"}, 0, 0, &make_point); if ( rive->type().property("originX") ) { load_property(rive, transform->anchor_point, animations, {"originX", "originY"}, 0.5, 0.5, [&bbox](Float32 ox, Float32 oy){ return QPointF( math::lerp(bbox.left(), bbox.right(), ox), math::lerp(bbox.top(), bbox.bottom(), oy) ); } ); } /*load_properties( rive, std::make_tuple(&transform->position, &transform->anchor_point), animations, {"x", "y", "originX", "originY"}, 0, 0, 0.5, 0.5, [&bbox] ( Float32 x, Float32 y, Float32 ox, Float32 oy ) { QPointF anchor( math::lerp(bbox.left(), bbox.right(), ox), math::lerp(bbox.top(), bbox.bottom(), oy) ); return std::make_tuple(QPointF(x, y) - anchor, anchor); } );*/ load_property(rive, transform->rotation, animations, "rotation"); load_property(rive, transform->scale, animations, {"scaleX", "scaleX"}, 1, 1, [](Float32 x, Float32 y){ return QVector2D(x, y); }); } void load_shape_group(Object* shape, model::Group* group, const detail::AnimatedProperties& animations) { load_property(shape, group->opacity, animations, "opacity", 1); group->name.set(shape->get("name")); add_shapes(shape, group->shapes); auto box = group->local_bounding_rect(0); load_transform(shape, group->transform.get(), animations, box); } std::unique_ptr load_shape(Object* object) { detail::AnimatedProperties animations = load_animations(object); switch ( object->type().id ) { case TypeId::Shape: case TypeId::Node: return load_shape_layer(object, animations); case TypeId::Rectangle: return load_rectangle(object, animations); case TypeId::Ellipse: return load_ellipse(object, animations); case TypeId::Fill: return load_fill(object, animations); case TypeId::Stroke: return load_stroke(object, animations); case TypeId::Polygon: return load_polygon(object, animations, model::PolyStar::Polygon); case TypeId::Star: return load_polygon(object, animations, model::PolyStar::Star); case TypeId::Triangle: return load_triangle(object, animations); case TypeId::PointsPath: return load_path(object, animations); case TypeId::NestedArtboard: return load_precomp(object, animations); case TypeId::Image: return load_image(object, animations); case TypeId::TrimPath: case TypeId::Bone: case TypeId::RootBone: case TypeId::ClippingShape: case TypeId::Text: /// \todo default: return {}; } } std::unique_ptr load_rectangle(Object* object, const detail::AnimatedProperties& animations) { auto group = std::make_unique(document); auto shape = std::make_unique(document); shape->name.set(object->get("name")); load_property(object, shape->rounded, animations, {"cornerRadiusTL", "cornerRadiusBL", "cornerRadiusBR", "cornerRadiusTR"}, 0, 0, 0, 0, [](Float32 tl, Float32 bl, Float32 br, Float32 tr){ return (tl + bl + br + tr) / 4; } ); load_property(object, shape->size, animations, {"width", "height"}, 0, 0, [](Float32 x, Float32 y){ return QSizeF(x, y); }); group->shapes.insert(std::move(shape)); load_shape_group(object, group.get(), animations); return group; } std::unique_ptr load_ellipse(Object* object, const detail::AnimatedProperties& animations) { auto group = std::make_unique(document); auto shape = std::make_unique(document); shape->name.set(object->get("name")); load_property(object, shape->size, animations, {"width", "height"}, 0, 0, [](Float32 x, Float32 y){ return QSizeF(x, y); }); group->shapes.insert(std::move(shape)); load_shape_group(object, group.get(), animations); return group; } std::unique_ptr load_fill(Object* object, const detail::AnimatedProperties& animations) { auto shape = std::make_unique(document); load_styler(object, shape.get(), animations); /// \todo fillRule return shape; } std::unique_ptr load_stroke(Object* object, const detail::AnimatedProperties& animations) { auto shape = std::make_unique(document); load_styler(object, shape.get(), animations); load_property(object, shape->width, animations, "thickness"); /// \todo cap + join return shape; } void load_styler(Object* object, model::Styler* shape, const detail::AnimatedProperties& animations) { shape->name.set(object->get("name")); shape->visible.set(object->get("isVisible", true)); load_property(object, shape->opacity, animations, "opacity", 1); for ( const auto& child : object->children() ) { if ( child->type().id == TypeId::SolidColor ) load_property(child, shape->color, load_animations(child), "colorValue", QColor("#747474")); else if ( child->type().id == TypeId::LinearGradient ) shape->use.set(load_gradient(child, model::Gradient::Linear)); else if ( child->type().id == TypeId::RadialGradient ) shape->use.set(load_gradient(child, model::Gradient::Radial)); } } model::Gradient* load_gradient(Object* object, model::Gradient::GradientType type) { auto colors = std::make_unique(document); colors->name.set(object->get("name")); auto colors_ptr = colors.get(); document->assets()->gradient_colors->values.insert(std::move(colors)); auto gradient = std::make_unique(document); gradient->name.set(object->get("name")); gradient->colors.set(colors_ptr); gradient->type.set(type); auto animations = load_animations(object); load_property(object, gradient->start_point, animations, {"startX", "startY"}, 0, 0, &make_point); load_property(object, gradient->end_point, animations, {"endX", "endY"}, 0, 0, &make_point); /// \todo color animations QGradientStops stops; for ( const auto& child : object->children() ) { if ( child->type().id == TypeId::GradientStop ) { stops.push_back({ child->get("position"), child->get("colorValue"), }); } } colors_ptr->colors.set(stops); auto ptr = gradient.get(); document->assets()->gradients->values.insert(std::move(gradient)); return ptr; } detail::AnimatedProperties load_animations(Object* object) { using namespace glaxnimate::io::detail; AnimatedProperties props; for ( const auto& anim : object->animations() ) { AnimatedProperty& prop = props.properties[anim.property->name]; for ( auto kf : anim.keyframes ) { model::KeyframeTransition transition; /// \todo prop.keyframes.push_back({ kf->get("frame", 0), ValueVariant(kf->get_variant("value")), transition }); } } return props; } std::unique_ptr load_polygon(Object* object, const detail::AnimatedProperties& animations, model::PolyStar::StarType type) { auto group = std::make_unique(document); load_shape_group(object, group.get(), animations); auto shape = std::make_unique(document); shape->name.set(object->get("name")); shape->type.set(type); /// \todo cornerRadius load_property(object, shape->points, animations, "points", 5); shape->outer_radius.set(100); load_property(object, shape->inner_radius, animations, {"innerRadius"}, 0.5, [](Float32 pc){ return pc * 100; }); load_property(object, shape->points, animations, "points", 5); load_property(object, group->transform->scale, animations, {"scaleX", "scaleY", "width", "height"}, 1, 1, 0, 0, [](Float32 sx, Float32 sy, Float32 w, Float32 h){ return QVector2D(w / 200 * sx, h / 200 * sy); }); group->shapes.insert(std::move(shape)); return group; } std::unique_ptr load_triangle(Object* object, const detail::AnimatedProperties& animations) { auto group = std::make_unique(document); auto shape = std::make_unique(document); shape->name.set(object->get("name")); load_property(object, shape->shape, animations, {"width", "height"}, 0, 0, [](Float32 w, Float32 h){ math::bezier::Bezier path; path.add_point({-w/2, h/2}); path.add_point({0, -h/2}); path.add_point({w/2, h/2}); path.close(); return path; }); group->shapes.insert(std::move(shape)); load_shape_group(object, group.get(), animations); return group; } std::unique_ptr load_path(Object* object, const detail::AnimatedProperties& animations) { auto shape = std::make_unique(document); shape->name.set(object->get("name")); bool closed = object->get("isClosed"); shape->closed.set(closed); math::bezier::Bezier bez; for ( const auto& child : object->children() ) { math::bezier::Point p; p.pos = QPointF(child->get("x", 0), child->get("y", 0)); if ( child->type().id == TypeId::CubicMirroredVertex ) { p.type = math::bezier::Symmetrical; auto tangent = math::from_polar( child->get("distance"), child->get("rotation") ); p.tan_in = p.pos - tangent; p.tan_out = p.pos + tangent; } else if ( child->type().id == TypeId::CubicAsymmetricVertex ) { p.type = math::bezier::Smooth; p.tan_in = p.pos - math::from_polar( child->get("inDistance"), child->get("rotation") ); p.tan_out = p.pos + math::from_polar( child->get("outDistance"), child->get("rotation") ); } else if ( child->type().id == TypeId::CubicDetachedVertex ) { p.type = math::bezier::Corner; p.tan_in = p.pos + math::from_polar( child->get("inDistance"), child->get("inRotation") ); p.tan_out = p.pos + math::from_polar( child->get("outDistance"), child->get("outRotation") ); } else if ( child->type().id == TypeId::StraightVertex ) { p.type = math::bezier::Corner; p.tan_in = p.tan_out = p.pos; } else { continue; } bez.push_back(p); } bez.set_closed(closed); /// \todo animation Q_UNUSED(animations); shape->shape.set(bez); return shape; } std::unique_ptr load_precomp(Object* object, const detail::AnimatedProperties& animations) { auto shape = std::make_unique(document); shape->name.set(object->get("name")); load_property(object, shape->opacity, animations, "opacity", 1); QRectF box; if ( object->has("artboardId") ) { auto id = object->get("artboardId"); shape->size.set(artboards_id[id]->size); shape->composition.set(artboards_id[id]->comp); box.setSize(artboards_id[id]->size); } load_transform(object, shape->transform.get(), animations, box); return shape; } model::Bitmap* load_image_asset(Object* object) { auto image = std::make_unique(document); image->filename.set(object->get("name")); image->width.set(object->get("width")); image->height.set(object->get("height")); auto ptr = image.get(); document->assets()->images->values.insert(std::move(image)); return ptr; } std::unique_ptr load_image(Object* object, const detail::AnimatedProperties& animations) { auto shape = std::make_unique(document); shape->name.set(object->get("name")); auto id = object->get("assetId"); QSizeF size; if ( auto bmp = qobject_cast(assets[id].asset) ) { size = bmp->size(); shape->transform->anchor_point.set(QPointF( bmp->width.get() / 2., bmp->height.get() / 2. )); shape->image.set(bmp); } load_transform(object, shape->transform.get(), animations, {QPointF(0, 0), size}); return shape; } model::Document* document = nullptr; std::map artboards; std::vector objects; Artboard* artboard = nullptr; Object* keyed_object = nullptr; PropertyAnimation* keyed_property = nullptr; RiveFormat* format = nullptr; std::vector artboards_id; std::vector assets; model::Composition* main = nullptr; }; } // namespace RiveLoader::RiveLoader(BinaryInputStream& stream, RiveFormat* format) : document(nullptr), stream(stream), format(format) { extra_props = read_property_table(); QObject::connect(&types, &TypeSystem::type_not_found, [format](int type){ format->error(QObject::tr("Unknown object of type %1").arg(int(type))); }); if ( stream.has_error() ) format->error(QObject::tr("Could not read property table")); } std::vector RiveLoader::load_object_list() { if ( stream.has_error() ) return {}; std::vector objects; while ( !stream.has_error() && !stream.eof() ) objects.emplace_back(read_object()); return objects; } bool RiveLoader::load_document(model::Document* document) { if ( stream.has_error() ) return false; LoadCotext context(format, document); while ( !stream.has_error() && !stream.eof() ) if ( auto obj = read_object() ) context.objects.emplace_back(std::move(obj)); for ( auto& object : context.objects ) context.preprocess_object(&object); for ( auto& object : context.objects ) context.process_object(&object); return true; } Object RiveLoader::read_object() { auto type_id = TypeId(stream.read_uint_leb128()); if ( stream.has_error() ) { format->error(QObject::tr("Could not load object type ID")); return {}; } Object obj = types.object(type_id); if ( !obj ) return {}; while ( true ) { Identifier prop_id = stream.read_uint_leb128(); if ( stream.has_error() ) { format->error(QObject::tr("Could not load property ID in %1 (%2)") .arg(int(type_id)).arg(obj.definition()->name)); return {}; } if ( prop_id == 0 ) break; auto prop_def = obj.type().property(prop_id); if ( !prop_def ) { auto unknown_it = extra_props.find(prop_id); if ( unknown_it == extra_props.end() ) { format->error(QObject::tr("Unknown property %1 of %2 (%3)") .arg(prop_id).arg(int(type_id)).arg(obj.definition()->name)); return {}; } else { format->warning(QObject::tr("Skipping unknown property %1 of %2 (%3)") .arg(prop_id).arg(int(type_id)).arg(obj.definition()->name)); } } else { obj.set(prop_def, read_property_value(prop_def->type)); if ( stream.has_error() ) { format->error(QObject::tr("Error loading property %1 (%2) of %3 (%4)") .arg(prop_id).arg(prop_def->name).arg(int(type_id)).arg(obj.definition()->name)); return {}; } } } return obj; } QVariant RiveLoader::read_property_value(PropertyType type) { switch ( type ) { case PropertyType::Bool: return bool(stream.next()); case PropertyType::Bytes: return read_raw_string(); case PropertyType::String: return read_string_utf8(); case PropertyType::VarUint: return QVariant::fromValue(stream.read_uint_leb128()); case PropertyType::Float: return stream.read_float32_le(); case PropertyType::Color: return QColor::fromRgba(stream.read_uint32_le()); } return {}; } PropertyTable RiveLoader::read_property_table() { std::vector props; while ( true ) { VarUint id = stream.read_uint_leb128(); if ( stream.has_error() ) return {}; if ( id == 0 ) break; props.push_back(id); } quint32 current_int = 0; quint32 bit = 8; PropertyTable table; for ( auto id : props ) { if ( bit == 8 ) { current_int = stream.read_uint32_le(); if ( stream.has_error() ) return {}; bit = 0; } int type = (current_int >> bit) & 3; if ( type == 0 ) table[id] = PropertyType::VarUint; else if ( type == 1 ) table[id] = PropertyType::String; else if ( type == 2 ) table[id] = PropertyType::Float; else if ( type == 3 ) table[id] = PropertyType::Color; bit += 2; } return table; } void RiveLoader::skip_value(glaxnimate::io::rive::PropertyType type) { switch ( type ) { case PropertyType::Bool: case PropertyType::VarUint: stream.read_uint_leb128(); break; case PropertyType::Bytes: case PropertyType::String: read_raw_string(); break; case PropertyType::Float: stream.read_float32_le(); break; case PropertyType::Color: stream.read_uint32_le(); break; } } const PropertyTable& RiveLoader::extra_properties() const { return extra_props; } QByteArray RiveLoader::read_raw_string() { auto size = stream.read_uint_leb128(); if ( stream.has_error() ) return {}; return stream.read(size); } QString RiveLoader::read_string_utf8() { return QString::fromUtf8(read_raw_string()); } src/modules/glaxnimate/glaxnimate/external/QtAppSetup/src/app/widgets/clearable_keysequence_edit.ui000664 001750 001750 00000004773 14477652011 037276 0ustar00ddennedyddennedy000000 000000 mlt-7.22.0 ClearableKeysequenceEdit 0 0 195 34 0 0 0 0 0 0 Use Default Use Default Clear Clear .. toolButton_2 clicked() ClearableKeysequenceEdit use_default() 131 15 158 115 toolButton clicked() ClearableKeysequenceEdit use_nothing() 182 17 236 18 use_default() use_nothing() mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/plugin/action.hpp000664 001750 001750 00000003156 14477652011 027566 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include "service.hpp" namespace glaxnimate::plugin { class ActionService; class PluginActionRegistry : public QObject { Q_OBJECT public: static PluginActionRegistry& instance() { static PluginActionRegistry instance; return instance; } QAction* make_qaction(ActionService* action); void add_action(ActionService* action); void remove_action(ActionService* action); const std::vector& enabled() const; signals: void action_added(ActionService* action, ActionService* sibling_before); void action_removed(ActionService*); private: std::vector::iterator find(ActionService* as); static bool compare(ActionService* a, ActionService* b); PluginActionRegistry() = default; ~PluginActionRegistry() = default; std::vector enabled_actions; }; class ActionService : public PluginService { Q_OBJECT public: ServiceType type() const override { return ServiceType::Action; } QString name() const override { return label; } void enable() override { PluginActionRegistry::instance().add_action(this); } void disable() override { PluginActionRegistry::instance().remove_action(this); emit disabled(); } QIcon service_icon() const override; QString label; QString tooltip; QString icon; PluginScript script; public slots: void trigger() const; signals: void disabled(); }; } // namespace glaxnimate::plugin mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/rive/type_ids.hpp000664 001750 001750 00000006521 14477652011 030206 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once /** * NOTE: This file is generated automatically, do not edit manually * To generate this file run * ./external/rive_typedef.py -t ids >src/core/io/rive/type_ids.hpp */ namespace glaxnimate::io::rive { enum class TypeId { NoType = 0, Artboard = 1, Node = 2, Shape = 3, Ellipse = 4, StraightVertex = 5, CubicDetachedVertex = 6, Rectangle = 7, Triangle = 8, Component = 10, ContainerComponent = 11, Path = 12, Drawable = 13, PathVertex = 14, ParametricPath = 15, PointsPath = 16, RadialGradient = 17, SolidColor = 18, GradientStop = 19, Fill = 20, ShapePaint = 21, LinearGradient = 22, Backboard = 23, Stroke = 24, KeyedObject = 25, KeyedProperty = 26, Animation = 27, CubicInterpolator = 28, KeyFrame = 29, KeyFrameDouble = 30, LinearAnimation = 31, CubicAsymmetricVertex = 34, CubicMirroredVertex = 35, CubicVertex = 36, KeyFrameColor = 37, TransformComponent = 38, SkeletalComponent = 39, Bone = 40, RootBone = 41, ClippingShape = 42, Skin = 43, Tendon = 44, Weight = 45, CubicWeight = 46, TrimPath = 47, DrawTarget = 48, DrawRules = 49, KeyFrameId = 50, Polygon = 51, Star = 52, StateMachine = 53, StateMachineComponent = 54, StateMachineInput = 55, StateMachineNumber = 56, StateMachineLayer = 57, StateMachineTrigger = 58, StateMachineBool = 59, LayerState = 60, AnimationState = 61, AnyState = 62, EntryState = 63, ExitState = 64, StateTransition = 65, StateMachineLayerComponent = 66, TransitionCondition = 67, TransitionTriggerCondition = 68, TransitionValueCondition = 69, TransitionNumberCondition = 70, TransitionBoolCondition = 71, BlendState = 72, BlendStateDirect = 73, BlendAnimation = 74, BlendAnimation1D = 75, BlendState1D = 76, BlendAnimationDirect = 77, BlendStateTransition = 78, Constraint = 79, TargetedConstraint = 80, IKConstraint = 81, DistanceConstraint = 82, TransformConstraint = 83, KeyFrameBool = 84, TransformComponentConstraint = 85, TransformComponentConstraintY = 86, TranslationConstraint = 87, ScaleConstraint = 88, RotationConstraint = 89, TransformSpaceConstraint = 90, WorldTransformComponent = 91, NestedArtboard = 92, NestedAnimation = 93, NestedStateMachine = 95, NestedSimpleAnimation = 96, NestedLinearAnimation = 97, NestedRemapAnimation = 98, Asset = 99, Image = 100, Folder = 102, FileAsset = 103, DrawableAsset = 104, ImageAsset = 105, FileAssetContents = 106, Vertex = 107, MeshVertex = 108, Mesh = 109, Text = 110, ContourMeshVertex = 111, ForcedEdge = 112, TextRun = 113, StateMachineListener = 114, ListenerTriggerChange = 115, ListenerInputChange = 116, ListenerBoolChange = 117, ListenerNumberChange = 118, LayeredAsset = 119, LayerImageAsset = 120, NestedInput = 121, NestedTrigger = 122, NestedBool = 123, NestedNumber = 124, ListenerAction = 125, ListenerAlignTarget = 126, }; } // namespace glaxnimate::io::rive mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/custom_font.hpp000664 001750 001750 00000003550 14477652011 030451 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include #include #include #include #include #include "app/utils/qstring_hash.hpp" namespace glaxnimate::model { class CustomFont; enum class FontFileFormat { Unknown, TrueType, OpenType, Woff2, Woff }; class CustomFontDatabase : public QObject { Q_OBJECT public: static CustomFontDatabase& instance(); static FontFileFormat font_data_format(const QByteArray& data); CustomFont add_font(const QString& name_alias, const QByteArray& ttf_data); CustomFont get_font(int database_index); std::vector fonts() const; QFont font(const QString& family, const QString& style_name, qreal size) const; std::unordered_map> aliases() const; private: CustomFontDatabase(); ~CustomFontDatabase(); CustomFontDatabase(const CustomFontDatabase&) = delete; CustomFontDatabase& operator=(const CustomFontDatabase&) = delete; class Private; class CustomFontData; std::unique_ptr d; using DataPtr = std::shared_ptr; friend CustomFont; }; class CustomFont { public: explicit CustomFont(int database_index); CustomFont(CustomFontDatabase::DataPtr d); CustomFont(); ~CustomFont(); bool is_valid() const; QString family() const; QString style_name() const; int database_index() const; QFont font(int size) const; const QRawFont& raw_font() const; QByteArray data() const; const QString& source_url() const; const QString& css_url() const; void set_source_url(const QString& url); void set_css_url(const QString& url); private: CustomFontDatabase::DataPtr d; }; } // namespace glaxnimate::model mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/utils.hpp000664 001750 001750 00000000523 14477652011 026555 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include "model/animation/animatable.hpp" namespace glaxnimate::io { std::vector> split_keyframes(model::AnimatableBase* prop); } // namespace glaxnimate::io mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/shapes/path_modifier.hpp000664 001750 001750 00000001106 14477652011 032201 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #pragma once #include "shape.hpp" namespace glaxnimate::model { /** * \brief Base class for modifiers than only alter the bezier points */ class PathModifier : public Modifier { Q_OBJECT public: using Modifier::Modifier; std::unique_ptr to_path() const override; protected: void on_paint(QPainter* painter, FrameTime t, PaintMode mode, model::Modifier* modifier) const override; }; } // namespace glaxnimate::model mlt-7.22.0/src/modules/glaxnimate/glaxnimate/external/QtAppSetup/src/app/settings/settings.cpp000664 001750 001750 00000004441 14477652011 034224 0ustar00ddennedyddennedy000000 000000 /* * SPDX-FileCopyrightText: 2019-2023 Mattia Basaglia * * SPDX-License-Identifier: GPL-3.0-or-later */ #include "settings.hpp" #include "app/application.hpp" #include #include void app::settings::Settings::load() { QSettings settings = app::Application::instance()->qsettings(); auto avail_groups = settings.childGroups(); std::set unprocessed(avail_groups.begin(), avail_groups.end()); avail_groups.clear(); for ( const auto& group : groups_ ) { unprocessed.erase(group->slug()); settings.beginGroup(group->slug()); group->load(settings); settings.endGroup(); } } void app::settings::Settings::save() { QSettings settings = app::Application::instance()->qsettings(); for ( const auto& group : groups_ ) { settings.beginGroup(group->slug()); group->save(settings); settings.endGroup(); } } void app::settings::Settings::add_group(QString slug, utils::TranslatedString label, const QString& icon, SettingList settings) { add_group(std::make_unique(std::move(slug), std::move(label), std::move(icon), std::move(settings))); } void app::settings::Settings::add_group(CustomSettingsGroup group) { auto slug = group->slug(); if ( !order.contains(slug) ) order[slug] = groups_.size(); groups_.push_back(std::move(group)); } QVariant app::settings::Settings::get_value ( const QString& group, const QString& setting ) const { if ( !order.contains(group) ) return {}; return groups_[order[group]]->get_variant(setting); } QVariant app::settings::Settings::get_default(const QString& group, const QString& setting) const { if ( !order.contains(group) ) return {}; return groups_[order[group]]->get_default(setting); } bool app::settings::Settings::set_value ( const QString& group, const QString& setting, const QVariant& value ) { if ( !order.contains(group) ) return false; return groups_[order[group]]->set_variant(setting, value); } QVariant app::settings::Settings::define(const QString& group, const QString& setting, const QVariant& default_value) { if ( !order.contains(group) ) return default_value; return groups_[order[group]]->define(setting, default_value); } mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/model/assets/000775 001750 001750 00000000000 14477652011 026677 5ustar00ddennedyddennedy000000 000000 mlt-7.22.0/src/modules/glaxnimate/glaxnimate/src/core/io/000775 001750 001750 00000000000 14477652011 024704 5ustar00ddennedyddennedy000000 000000